“the studio shipped. then the studio couldn’t talk to itself. one line later, it could.”

Morning — preflight + safety flips

Locked the R.1 preflight decisions (af6f9fe) and shipped R.2 T.3 (7c3477f): werbos serve now defaults to operator- approval before any listener binds an externally-reachable port. The legacy --confirm-bind opt-in is gone; you opt OUT now with --unsafe-immediate-bind. The same flip the security audit wanted, just expressed the right way around — the safer path is the path with fewer flags.

Mid-day — SD.1 + SD.2 in the same window

SD.1 (3fe6bd1) built the design-studio agent + two new tools:

  • image_generate — uses Pollinations.ai’s free FLUX endpoint. No auth, URL-encode the prompt, get a JPEG back. Saves under ~/werbos/images/<ts>-<slug>.jpg. Local-first inference for the LM still wins; visual references can hit a free remote when local Stable Diffusion isn’t worth the disk.
  • screenshot_url — headless Playwright chromium with a hardened allow-list (file:// or localhost only by default, https:// optional). The MVP was three turns: generate a reference image → write a single-file HTML to match → screenshot and self-check.

SD.2 (c11cad6) wrapped a dashboard around it. List + wizard + 3-pane canvas (file tree, preview iframe, embedded chat). 25 new endpoint tests + 8 session-level intent.md tests. 2,472 lines of TSX. Same shape as Claude Design — the operator expected one.

SD.2 day-2 + AO.1 Part 2 (dd1bc8b) was the small fix that unlocks the rest: chats bound to a project read the project’s intent.md as ambient context. Operator stops re-typing the brief that’s already sitting on disk. POST /api/chats accepts project= (body or query) → SessionRegistry.create() threads it into ContextBuilder.project_context. The “✦ Chat about this project” button in StudioDetail uses it.

Evening — orchestration ensemble (AO.1 Part 1)

349ddaa was the bigger reframe. Instead of trying to make one agent good at every step of building a thing, @studio became an orchestrator with a single primary tool — dispatch_to_agent. Three new specialist agents took over the artifact work:

  • @visionary (qwen2.5:14b) — vague request → focused product brief in markdown (spec.md).
  • @front-end (qwen2.5-coder:7b) — HTML+JS bones, no styling. Emits one fenced html block which the artifact extractor lifts to disk.
  • @css-wizard (qwen2.5:14b) — takes the existing index.html and rewrites only the <style> block, keeping structure + JS identical.

dispatch_to_agent runs each specialist in a fresh pitts.Agent sub-session with its own persona, captures the text content, parses it with extract_artifacts (four strategies: explicit path=foo fence, language-hint fence mapped to suffix, unfenced single, fallback warning), and writes files under ~/werbos/projects/<project>/ on the specialist’s behalf. Recursion-guarded depth 4. Brief capped at 16 KB. Per-agent model routing — the V1.8 plumbing that landed two days ago — finally cashed in here: 14b for design judgment, coder:7b for structure, all routed by the same orchestrator with one HTTP host.

The clever bit: specialists emit text content, never structured tool calls, so the framework writes files for them. Whatever tool-fidelity issues qwen-class models have, the specialists dodge them entirely. The orchestrator was still expected to tool-call structurally to invoke dispatch_to_agent — which set up the day’s last sprint.

Late night — AO.0, the bug the whole week was paying for

Drove a test through the chat UI. @studio produced a working todo HTML — but as chat output, zero tool calls. Same failure family as the qwen2.5-coder:7b observation from yesterday. The orchestrator was narrating “I’ll use my X tool” instead of structurally emitting the call.

It looked like a model-fidelity problem on qwen2.5:14b. It wasn’t.

The dumper

Added WERBOS_BELLMAN_DUMP to pitts (391399f on the sibling repo). When the env var is set, BellmanBackend appends every outgoing request payload and every incoming response chunk to <path>.{request,response}.ndjson. ~30 LOC; never raises on dump failures; no-op when the env var is unset. Set it, restarted serve, drove one chat turn against @studio + qwen2.5:14b on SR8, looked at the JSON:

system prompt: 19,403 chars
                 ↳ contains '# Tools' section: True
                 ↳ contains '## dispatch_to_agent' subsection: True
tools count in structured payload: 9 (including dispatch_to_agent)

Two representations of every tool — the prose docs inline in the system prompt on top of the structured tools list already in the API payload. Some turns qwen picked the structured channel; some turns it picked narration. Same model, same prompt, stochastic.

The one-line fix (8ec97c3)

werbos/cli/serve.py:228 already sets inline_tool_docs=False on the default ContextBuilder. The fix werbos code shipped weeks ago. But SessionRegistry._context_builder_for_agent (sessions.py:566) builds a fresh ContextBuilder for agent- bound chat sessions and silently dropped that flag. Dataclass default is True.

return ContextBuilder(
    persona=combined_persona,
    character=base.character,
    mirror_ratio=base.mirror_ratio,
    max_tokens=base.max_tokens,
    compaction_threshold=base.compaction_threshold,
    chars_per_token=base.chars_per_token,
    tokenizer=base._tokenizer,
    # AO.0 fix (2026-05-16): propagate from the base.
    inline_tool_docs=base.inline_tool_docs,
)

After: system prompt dropped to 11,073 chars (-42%). Same payload replayed against ollama directly — qwen2.5:14b emitted a clean structured dispatch_to_agent("@visionary", ...) tool_call with the operator’s brief verbatim. Exactly the contract the orchestrator persona writes.

Why this matters beyond AO.0

The class of bug: dataclass defaults bite at copy boundaries. Any time you build a “fresh X seeded from existing X,” every field needs an explicit pass-through. Regression tests should pin the flag itself, not a downstream symptom — symptom tests miss it because “the model didn’t tool-call” is a hundred possible causes.

Corollary: two representations of the same affordance → models pick the weaker one. Tools-in-prompt + tools-in- payload meant qwen had a choice between “narrate” and “emit.” Sometimes it picked the wrong one. The fix wasn’t model training; it was deleting the bad option.

The werbos chat REPL (chat.py:2347 + 2361) constructs ContextBuilder without inline_tool_docs=False — same class of bug, hasn’t been retested with the AO.0 fix in place. That’s tomorrow’s sweep.

Numbers worth keeping

  • 11 commits on main.
  • +58 new tests. 3,802 → 3,859 just from AO.1 Part 1, plus the AO.0 regressions both directions. Full suite green.
  • System prompt -42% on agent-bound chats (19,403 → 11,073 chars) the moment inline_tool_docs propagated.
  • 4 specialist agents installed: @studio (orchestrator), @visionary (scope, 14b), @front-end (HTML/JS, coder:7b), @css-wizard (restyle, 14b).
  • Tool-result preview cap <2KB so the orchestrator’s context budget survives a three-step chain.

AHA

The dumper that cracked AO.0 took 30 minutes to write. The bug had been costing me visible model-fidelity confusion for the better part of a week. The dump-on-env-var pattern is cheap and worth keeping — WERBOS_BELLMAN_DUMP stays in pitts; any future “why doesn’t this model do X” question gets a payload diff instead of a model-blame.

Carryover

  • AO.1 Part 3 — pipeline indicator UI in StudioDetail. Reuse the .host-pill aesthetic. New /api/studio/projects/<name>/pipeline endpoint that walks the project’s chat archives so the operator sees ✓ spec / ✓ bones / ⋯ styled as the chain advances.
  • AO.1 Part 4 — shared-scratch pipeline.md per project, so specialists can see what previous steps decided instead of re-deriving from the brief.
  • Wider AO.0 sweep — re-test werbos chat REPL, werbos eval, and the Python SDK surface after propagating inline_tool_docs=False. Same class of bug, multiple call sites.
  • EN.1 encryption-at-rest — 6-7 day v1 sprint drafted at docs/sprints/en1-encryption-at-rest.md. 2SKD architecture ported from the 1Password reverse-engineering. Three decisions to lock before kickoff: Argon2id vs PBKDF2, SQLCipher vs accept-v1-metadata-leak, master-passphrase caching policy.

Quote of the day

“i 100% want that encrypted”

Re: sensitive chats. The seed for EN.1.

Aphorisms

  • Dataclass defaults bite at copy boundaries.
  • Two representations of the same affordance → models pick the weaker one.
  • The dump-on-env-var pattern costs nothing and turns multi-day mysteries into 30-minute diffs.