“Smallest, most secure, timeless, protocol we can make.”

The design directive that shaped the second half of Day 4. By the end of the night, the orchestration plane had a name (observe), a flagship consumer (watchtower), two planned siblings (warden, beacon), three new repos, two SPEC drafts, and an Archaeologist review verdict of DURABLE.

The morning was much more practical: closing every blocker that showed up during yesterday’s bad demo.

Morning: closing the demo loop

The Day 3 night marathon ended with streaming working but image upload broken on the phone. Day 4 morning fixed each remaining blocker in turn.

The bugs that fell

  • Empty-{} placeholder strip (107adb2 in pitts). Stock llama3.1:8b sometimes opens replies with a literal {} followed by self-aware narration (“Note: the question doesn’t require a tool, providing an empty response.”). Salvage parser correctly ignored the empty dict (no name field), but didn’t strip it either, so the user saw raw JSON in the bubble. Bug captured in session 907897d64d93. Fix: post-process leading {} if no tool calls were salvaged. Mid-content {} is preserved (e.g. “empty dict in Python is {}”).
  • werbos serve registered no tools by default (2eac612). Web search/fetch and shell tools required args.no_search / args.no_shell to exist on the namespace; the serve parser didn’t define those flags, so hasattr(args, ...) returned False and the tools never registered. Fix: mirror werbos chat’s flag set in serve. Bug captured in session 46c4dee93de8 (“when is the best time to shore fish in Cabo?” returned the literal text-form tool call JSON instead of executing).
  • Mobile-header was the wrong selector (2ca6a84). The 35px safe-area-inset padding I added on Day 3 night landed on .chat-header (the chat-title subheader) instead of the actual top bar with the hamburger and cog. Move the safe-area fix to .mobile-header and revert chat-header to its original padding.
  • Bubble wrap + viewport (db385c2). Long URLs from web search results forced horizontal overflow. Mobile chat-scroll wouldn’t actually scroll because 100vh doesn’t track the WebView’s actually-visible area when system UI shifts. Fix: overflow-wrap: anywhere on bubbles, min-width: 0 on flex ancestors, 100dvh on the app root, viewport-fit=cover on the meta. Inspired by Cheng Lou’s pretext markdown-chat experiment (his approach is fully virtualized; the load-bearing CSS primitives translate cleanly to flow layout).
  • Image cap bump (0f376f3). The 10 MB-per-image cap was tripping legitimate phone-camera shots; raised to 30 MB. Lifted the SR8 in-place sed hotfix into mainline so SR8 stops diverging on git pull.
  • /phone-api/* HTTP proxy (74e71b7). The phone bridge runs on SR8 alongside werbos serve; the React UI calls /phone-api/calls through werbos serve (proxied to localhost:9000 via stdlib urllib in asyncio.to_thread). One origin, no separate CORS surface, one less runtime to keep alive.
  • Optional games-routes import (12e6d8a). SR8 was crashing on werbos serve boot because app.py imported werbos.server.games but the module was uncommitted WIP on the Mac. Wrap in try/except so app.py boots cleanly without it; once games.py is finalized, the routes get picked up automatically.

v5-ttt LoRA + MLX endpoints landed

9ecef8c and 84b1cf4 from earlier in the morning shipped the v5-ttt rationale corpus generator and the /api/games/ttt/move MLX-backed endpoint — the v5-ttt LoRA loaded onto Llama-3.2-3B-4bit serves moves to the React GameView. First piece of an actual game training pipeline running end-to-end.

Vision triage

llama3.1:8b doesn’t see images. llama3.2-vision:11b sees images but Ollama returns 400 if you send tools to it. Picking one means losing one feature. Day 4 punted: stay on llama3.1:8b for tools support; multi-model routing (vision when an image is attached, text otherwise) goes on the streaming-followups backlog.

The win moment

“UI IS CLEAN!!!!!”

When the rebuilt APK landed via the Syncthing pipeline (Desktop/APK_DROPOFF/Werbos/werbos.apk is now a deploy target — the Mac drops a build, the phone receives via Syncthing, install locally; no USB, no adb), the mobile header cleared the camera, the composer cleared the home indicator, photos uploaded, search executed via end-of-stream salvage. Six checkpoints from the rebuild plan, all green.

Evening: naming the orchestration plane

The afternoon question was small: “24/7 background agents that yield to humans?” The answer to that question — built up in conversation across two hours — became a five-sibling architectural plan.

werbos/BACKLOG.md first

Created the project’s first backlog file (0fd700b). Eight sections: background agents, watchtower, streaming/UX, phone bridge, training, infra, mobile UI, dev tooling. Status legend ([idea] / [planned] / [in-progress] / [shipped]). Recently- shipped section near the bottom for cross-reference, prunes after a week or two.

Background agents named as a concept (8685f15)

The defining property is yielding: a background agent runs when nothing else is using the model and gets out of the way the instant a foreground turn arrives. Cooperative, not preemptive. This distinguishes it from cron (which fires regardless of state) and from foreground turns (which always win). Once “background agent” is a noun in the system, every autonomous idea is just an instance of it.

Five orchestration primitives surfaced:

  1. Foreground-busy signal — SessionRegistry.is_foreground_busy()
  2. Background work queue — asyncio task draining a FIFO of work items
  3. Pending-actions queue — destructive ops require human approval
  4. Claude budget gate — daily cap on Claude API calls
  5. Shared task memory (86316cf) — SQLite table that lets one agent fill in the next

The pending-actions queue is the load-bearing safety primitive. Without it, autonomous werbos is scary. With it, autonomous werbos is a quiet apprentice handing you a daily clipboard of “approve to act.”

Watchtower as the 5th sibling (480a9b6, 749f498, 9084b27)

The decomposition was too clean to ignore:

SiblingVerbConcern
pittsthinkagent loop
mccullochjudgeeval
partyparrottalkreal-time wire
pfmpersistarchive
watchtowersummontriggers + dispatch + monitoring

Watchtower owns one thing: see the world, decide when to ring the bell. It does not know what agents think (pitts), how they’re scored (mcculloch), or where they archive (pfm).

Two use cases the same protocol covers: productivity (background agents fire on schedules / events) AND defense (active security monitoring — the same “ring the bell” shape). Static defenses prevent; watchtower detects when something gets close.

Architectural commitments locked in:

  • Protocol first, runtime second. Same as partyparrot. Spec is primary; the Python reference runtime is one implementation.
  • Universal — never assumes werbos. Dispatches via three generic primitives: HTTP request, shell command, in-process function call. werbos serve happens to be a frequent HTTP target; nothing in the protocol names it.
  • Standalone SPA wrapper. watchtower-ui is its own deliverable, not a tab in the werbos chat app. Configurable backend URL.
  • Three deliverables, three repos: watchtower (protocol + reference Python), watchtower-ui (Vite SPA), watchtower-py (future, if a non-Python runtime ever happens — mirrors partyparrot/partyparrot-py).

The full SPEC.md draft + Archaeologist review

Wrote a complete protocol skeleton: resources (trigger, dispatch, pending, task, audit, alert), endpoints (/v1/*), state machine for trigger-fire, threat model, versioning policy, open questions. Then handed it to the Archaeologist agent for the 25-50 year survival review.

Verdict: DURABLE. Three blockers needing fix before v1.0 lock:

  1. ULID → UUIDv7 (RFC 9562). ULID is not in IETF/ISO; UUIDv7 is ULID-shaped (time-ordered, lexicographically sortable) AND standardized. A 2050 archaeologist parses UUIDv7 cold; ULID requires finding a GitHub README that may not exist.
  2. Pin shell allow-list match semantics. Argv-prefix equality; substring/regex/glob explicitly forbidden; default-deny on empty allow-list. As written, a careless 2030 implementer could soften to substring match and call it spec-compliant.
  3. Pin canonical-JSON to RFC 8785 (JCS). Otherwise two conformant runtimes compute different selfHash values and the audit chain breaks across them.

Plus smaller fixes: emit the magic identifier on the wire (in /v1/version and the WS/SSE hello frame), specify webhook HMAC-SHA256 + constant-time compare, IANA tzdata required for schedule timezones, content-negotiated WS-or-SSE for the events stream, default body cap 1 MiB with 413 on overflow, threat-model expansion (“untrusted-network deployment requires mTLS or equivalent”).

All folded in. Watchtower SPEC went DRAFT-1 → DRAFT-2 in one editing pass.

The suite split

“What if we make this a suite? Watchtower… Lighthouse and Sentry… one might look for tasks and delegate, one only looks for hacks, bad data… and the other would build and support.”

The instinct held up. Three siblings, each “sees” something but with distinct emit paths:

SiblingSeesEmits
Watchtowertasks, schedules, triggersdispatch actions
Warden (planned)logs, traffic, file-state, allow-list violationsalerts + auto-defense
Beacon (planned)builds, deploys, health endpointsstatus + notifications

Naming dodged the famous-tool collisions: “Sentry” (sentry.io, error tracking SaaS) and “Lighthouse” (Google’s web auditor) are both heavily indexed. Warden and Beacon are unambiguous and fit the security/observation theme already in the project’s vocab (Mr BlackKeys, Specter, Hardcoder, etc.).

The shared see-layer extraction — observe

If three siblings all need to describe what they’re watching using the same vocabulary, you don’t want three slightly-different versions of identical logic. The shared bits got pulled out as a new sibling.

observe is the see-layer primitive — schema + library, no service of its own. DRAFT-1 covers:

  • Source envelope ({protocol, type, config}) with cron, fs, predicate, webhook variants
  • Closed predicate operator language (eq, ne, lt, lte, gt, gte, in, not, and, or; no DSL escape hatch, no arithmetic, no string ops)
  • Webhook auth — HMAC-SHA256 + constant-time compare
  • Shared wire conventions — UUIDv7, ISO-8601 UTC, IANA tzdata, RFC 8785 canonical JSON, newline preservation, 1 MiB body cap
  • ForegroundBusy interface signature
  • Error envelope ({error, detail, fields?})

Watchtower’s SPEC went DRAFT-2 → DRAFT-3 in the same editing pass: section 0 (Conventions) and section 2 (Errors) point to observe instead of redefining; section 3.1 (Trigger) embeds an observe Source instead of duplicating per-type schedule schemas. Watchtower SPEC shrunk by ~80 lines while gaining clarity.

Shape of the suite at end-of-day

                      ┌─────────────────┐
                      │     observe     │   shared see-layer
                      │  (cron, fs,     │   schema + library
                      │   predicate,    │   no service
                      │   webhook,      │
                      │   predicate     │
                      │   ops, HMAC,    │
                      │   conventions)  │
                      └────────┬────────┘
                               │
            ┌──────────────────┼──────────────────┐
            ▼                  ▼                  ▼
     ┌──────────────┐   ┌──────────────┐   ┌──────────────┐
     │  watchtower  │   │    warden    │   │    beacon    │
     │   summon     │   │   defend     │   │   signal     │
     │  (DRAFT-3)   │   │  (planned)   │   │  (planned)   │
     └──────────────┘   └──────────────┘   └──────────────┘

Three new repos initialized

  • ~/Documents/Companies/werbosLLM/observe/ — README + SPEC, git-init’d
  • ~/Documents/Companies/werbosLLM/watchtower/ — README + SPEC, git-init’d
  • ~/Documents/Companies/werbosLLM/watchtower-demo/ — README, git-init’d

No remotes yet. They’ll be gh repo create’d before any runtime code lands. Per the Day 4 design directive, no implementation work happens tonight — just the design, pressure-tested, written down.

Numbers worth keeping

MetricValue
Day 4 commits to werbos repo19 (mobile fixes ×7, serve fixes ×3, BACKLOG ×7, ttt ×2)
Day 4 commits to pitts repo1 (107adb2 empty-{} strip)
Day 4 commits to other siblings0 (phone, mcculloch, partyparrot, pfm untouched)
New sibling repos initialized3 (observe, watchtower, watchtower-demo)
Lines added to BACKLOG.md~600
Watchtower SPEC draftsDRAFT-1 → DRAFT-2 → DRAFT-3 (one day)
Archaeologist verdictDURABLE with 3 blockers (all folded in)
Famous-tool naming collisions dodged2 (Sentry, Lighthouse)
Sibling primitives now in the family6 (pitts, mcculloch, partyparrot, pfm, observe, watchtower) — was 4
Hours of design work that produced no code~3 (and that’s a feature)

AHAs

  • The pending-actions queue is the trust foundation for autonomous werbos. Same compute, completely different relationship: without it, “werbos deletes my files while I sleep”; with it, “werbos hands me a daily clipboard of approve-to-act.”
  • The most ship-able thing in this whole project might be watchtower. Chat assistants are crowded; PFM archives are niche; LoRA personality training is researchy. “A protocol + open-source SPA for managing background AI agents that yields to your foreground work, with a pending-actions queue so it can’t run wild” — that’s a thing nobody else has cleanly packaged.
  • Watchtower is the agent-management plane; werbos serve is the runtime plane. That’s how Kubernetes is organized (control vs data plane), how AWS is organized, how Tailscale is organized. Following a well-trodden pattern by accident usually means it’s the right pattern.
  • Continuity-of-intent is the lighthouse feature. The cron stuff and janitor are useful; “you asked for v5, here’s the bench result waiting when you wake up” is the line that distinguishes a local-first sovereign assistant from a cloud chatbot. Worth designing the orchestration primitives with that endpoint in mind even if it ships last.
  • Same protocol covers productivity AND defense. Watchtower doesn’t only schedule the janitor; it can also be the active security monitor for prompt-injection patterns, supply-chain drift, ollama outbound calls. The thing that schedules your janitor is the same thing that watches for prompt injections. That symmetry is unusual and good.
  • Universal protocol > werbos-only protocol. Naming werbos anywhere in observe or watchtower would have permanently coupled three siblings to one consumer. Pulling werbos out of the spec vocabulary preserves the option for a future tool to use the same primitives without dragging in the agent runtime.
  • Hosted-SPA + local-watchtower is the right deploy story. It’s how Plausible, Cal.com, etc. work — a hosted UI for convenience, user’s instance lives wherever they want. CSP-lock the SPA so even a hosted build can’t phone home, and you’ve got a privacy story that’s airtight by construction.
  • No building tonight was the load-bearing constraint. The user’s directive — “smallest, most secure, timeless, protocol we can make” — would have been compromised by rushing to a runtime. The Archaeologist review caught three real blockers (ULID, shell allow-list, canonical JSON) that would have been wired into actual code before being noticed.

Quote of the day

“Smallest, most secure, timeless, protocol we can make.”

The design directive that produced everything else. Every spec choice in observe and watchtower traces back to it: HTTP/1.1 + JSON because curl-and-jq survives forever; UUIDv7 because RFC; RFC 8785 because cross-runtime hash agreement; argv-prefix shell allow-list because softer matchers always lose; CSP-locked SPA because the threat model has to survive hosted deploys.

The bar wasn’t “good.” The bar was “still-implementable in 2050.”

What’s next

  • v5 specialist LoRAs on 3B-base (debate, fun-fact, scripture).
  • Multi-model vision+tools routing in BellmanBackend (vision_model config; vision model fires only when an image is attached, with tools=[]).
  • CLI streaming parity for werbos chat.
  • The first orchestration primitive — SessionRegistry.is_foreground_busy(). Smallest first step that unlocks everything else in the watchtower roadmap.
  • gh repo create for observe, watchtower, watchtower-demo when the SPECs feel locked enough to publish.
  • A npm run apk:ship script wrapping the tauri android build → bundletool extract → cp to APK_DROPOFF pipeline into one command. Manual ceremony every time we want to push a build to the phone has gotten old fast.