Describe the bug
The [Workspace] system-prompt section never reaches protocol-v1 (legacy-framing) harnesses on any platform. Because upstream ACP's current protocol version is 1, that includes the shipping Claude Code and Codex adapters — so most real Buzz agent runs lose the absolute-cwd grounding, not an edge case.
workspace_section() (pool.rs:1165-1177) has exactly one producer, framed_system_prompt (pool.rs:1137-1158), whose output is consumed by two routes only:
pool.rs:828-839 — session/new, wrapped in session_new_system_prompt (pool.rs:185-195):
if is_goose || protocol_version < 2 { None } else { prompt }
For a v1 agent the combined prompt is built and then discarded.
pool.rs:841-858 — Goose's session_set_goose_system_prompt extension, which is version-independent, so Goose is fine.
The legacy path assembles its own prompt and structurally cannot render the section: prepend_base_for_legacy (pool.rs:1090-1101, called at pool.rs:1593 initial message and pool.rs:1731 heartbeat) emits [Base] and [Channel Canvas] only, and takes no cwd argument. Per-turn assembly in format_prompt (queue.rs:1429-1443) is the same, and FormatPromptArgs (queue.rs:1352-1375) has no cwd field at all.
Why protocol-v1 is the live path, not a relic. Buzz requests protocolVersion: 2 (acp.rs:126) and stores the response, defaulting to 1 when absent (lib.rs:3775-3776, lib.rs:3864: unwrap_or(1)). The v2 request is self-described as non-standard — acp.rs:540-541:
// Requesting version 2 is an intentional temporary pin — we are squatting on ACP v2 ahead of the upstream ACP RFD. Revisit when that RFD merges.
and again in buzz-agent/src/lib.rs:280-283. Upstream ACP is at version 1, and the spec requires an agent that does not support the requested version to answer with the latest it does support. So a spec-compliant upstream adapter answers 1.
Against KNOWN_ACP_RUNTIMES (discovery.rs:66-198):
| Runtime |
Gets [Workspace]? |
Why |
Claude Code (claude-agent-acp, discovery.rs:101-132) |
No |
negotiates v1 → has_system_prompt_support false (pool.rs:173-183) → legacy framing |
Codex (codex-acp, discovery.rs:133-165) |
No |
same |
Goose (discovery.rs:67-100) |
Yes |
version-independent extension (pool.rs:845); lost only if the probe caches Some(false) (pool.rs:849-855) |
Buzz Agent (discovery.rs:166-197) |
Yes |
pins PROTOCOL_VERSION = 2 (buzz-agent/src/config.rs:3), min(requested, 2) (buzz-agent/src/lib.rs:284) |
Bounding the impact honestly: [Base] is still delivered on the legacy path — queue.rs:1431 per turn, pool.rs:1593 initial, pool.rs:1731 heartbeat — and it is default-on (lib.rs:1539-1544, opt out only via --no-base-prompt / BUZZ_ACP_NO_BASE_PROMPT, config.rs:409-410). So base_prompt.md:84-100's Workspace Layout table, REPOS/ and OUTBOX/ included, plus the "never run find over $HOME" line at :100, do arrive. What is lost is only the absolute-path anchor ("Your absolute working directory is <cwd>... this is where you already are"). Same shape as #3124, much wider blast radius. Expected symptom is a probabilistic uptick in path-guessing and wasted exploration, not a functional failure.
Steps to reproduce
Code-level, no harness needed:
$ rg -n 'cwd' crates/buzz-acp/src/queue.rs | rg -n 'FormatPromptArgs' -A2
# FormatPromptArgs has no cwd field -> the per-turn legacy path cannot render [Workspace]
workspace_section has one caller (pool.rs:1154), reachable only from create_session_and_apply_model (pool.rs:820); neither legacy assembly point calls it.
End-to-end: run a managed Claude Code or Codex agent on any platform and inspect the prompt the harness receives (desktop transcript observer, or the harness log). Expect [Base] with no [Workspace] block. Compare against a buzz-agent agent on the same host, which does get it.
Expected behavior
Protocol-v1 harnesses receive the same workspace anchor as v2 harnesses.
Version and platform
Logs / additional context
Origin — an omission, not a decision. workspace_section came in with 1011cea2 ("fix(desktop): ground agent workspace, migrate legacy nest, configurable repos_dir", #1194, 2026-06-23) — the only commit git log -S workspace_section -- crates/buzz-acp/ returns. Inside the crate it touched only pool.rs and base_prompt.md; it did not touch queue.rs, and within pool.rs it did not touch prepend_base_for_legacy — the diff sat in framed_system_prompt, then inside an explicit if agent.protocol_version >= 2 { … } else { … }, and the legacy arm was left alone. All seven tests added target framed_system_prompt/workspace_section in isolation. The doc comment added at pool.rs:1151-1153 reasons about persona-only agents and the / fallback but never mentions protocol version. prepend_base_for_legacy predates it (f0858824, #981, 2026-06-12; later 73cc31cc #1277, 3b9b371f #1755, 64b8fea6 #2034) and no commit in that set considers [Workspace]. (Inference from diffs and messages: never added, rather than consciously excluded.)
Fix shape — three insertion points, one shared renderer:
queue.rs:1352-1375 — add cwd: Option<&'a str> to FormatPromptArgs; pool.rs:1771-1784 already has ctx.cwd in scope.
queue.rs:1429-1432 — push the workspace section ahead of base_section(bp), matching framed_system_prompt's ordering.
pool.rs:1090-1101 — add a cwd parameter for the initial-message (pool.rs:1593) and heartbeat (pool.rs:1731) call sites. Missing the heartbeat site causes exactly the drift the pool.rs:1088-1089 doc comment says the helper exists to prevent.
workspace_section is private in pool.rs and would need to move to a shared module or be re-exported.
Risks worth weighing before doing it:
- Per-turn re-injection is real —
format_prompt runs on every batch flush. But [Base] (~150 lines) plus [System], [Team Instructions], [Agent Memory — core], and [Channel Canvas] are already re-sent every legacy turn. [Workspace] is one ~70-word paragraph, well under 1% of existing per-turn overhead.
- Preserve the
Some(base_prompt) coupling. framed_system_prompt emits [Workspace] only when a base prompt exists (pool.rs:1154-1157); a persona-only legacy agent must not get an anchor with no layout to go with it — the case pool.rs:1151-1153 warns about.
MAX_SYSTEM_PROMPT_BYTES does not apply (the legacy path uses no system prompt). The observer size trimmer (queue.rs:1396-1402) handles a new section gracefully, but it adds a leaf to the desktop "Prompt context" panel — worth a visual check.
Relationship to #3124 — independent, neither subsumes the other. #3124 fixes whether the renderer returns Some (the POSIX-only predicate), which helps callers that already invoke it. This issue is that the legacy path never invokes it. A Windows Claude Code agent is broken for both reasons; a macOS Claude Code agent for this one alone. They could land in one PR — the natural implementation here is "call the predicate-fixed workspace_section from the legacy assembly points" — but closing one must not close the other.
Open question for maintainers: given the acp.rs:540-541 note about squatting on v2 ahead of the upstream RFD, is the intended long-term answer to fix the legacy path, or to converge on upstream versioning so the v1/v2 split disappears? That choice decides whether this is worth fixing directly or is better absorbed into the RFD follow-up.
Duplicate search: searched issues for "workspace prompt", "OUTBOX", and PRs for "base_prompt workspace" — none found. Nearest is #2334 (base prompt shown as session title in harness UIs), a display concern.
Describe the bug
The
[Workspace]system-prompt section never reaches protocol-v1 (legacy-framing) harnesses on any platform. Because upstream ACP's current protocol version is 1, that includes the shipping Claude Code and Codex adapters — so most real Buzz agent runs lose the absolute-cwd grounding, not an edge case.workspace_section()(pool.rs:1165-1177) has exactly one producer,framed_system_prompt(pool.rs:1137-1158), whose output is consumed by two routes only:pool.rs:828-839—session/new, wrapped insession_new_system_prompt(pool.rs:185-195):pool.rs:841-858— Goose'ssession_set_goose_system_promptextension, which is version-independent, so Goose is fine.The legacy path assembles its own prompt and structurally cannot render the section:
prepend_base_for_legacy(pool.rs:1090-1101, called atpool.rs:1593initial message andpool.rs:1731heartbeat) emits[Base]and[Channel Canvas]only, and takes nocwdargument. Per-turn assembly informat_prompt(queue.rs:1429-1443) is the same, andFormatPromptArgs(queue.rs:1352-1375) has nocwdfield at all.Why protocol-v1 is the live path, not a relic. Buzz requests
protocolVersion: 2(acp.rs:126) and stores the response, defaulting to 1 when absent (lib.rs:3775-3776,lib.rs:3864:unwrap_or(1)). The v2 request is self-described as non-standard —acp.rs:540-541:and again in
buzz-agent/src/lib.rs:280-283. Upstream ACP is at version 1, and the spec requires an agent that does not support the requested version to answer with the latest it does support. So a spec-compliant upstream adapter answers1.Against
KNOWN_ACP_RUNTIMES(discovery.rs:66-198):[Workspace]?claude-agent-acp,discovery.rs:101-132)has_system_prompt_supportfalse (pool.rs:173-183) → legacy framingcodex-acp,discovery.rs:133-165)discovery.rs:67-100)pool.rs:845); lost only if the probe cachesSome(false)(pool.rs:849-855)discovery.rs:166-197)PROTOCOL_VERSION = 2(buzz-agent/src/config.rs:3),min(requested, 2)(buzz-agent/src/lib.rs:284)Bounding the impact honestly:
[Base]is still delivered on the legacy path —queue.rs:1431per turn,pool.rs:1593initial,pool.rs:1731heartbeat — and it is default-on (lib.rs:1539-1544, opt out only via--no-base-prompt/BUZZ_ACP_NO_BASE_PROMPT,config.rs:409-410). Sobase_prompt.md:84-100's Workspace Layout table,REPOS/andOUTBOX/included, plus the "never runfindover$HOME" line at:100, do arrive. What is lost is only the absolute-path anchor ("Your absolute working directory is<cwd>... this is where you already are"). Same shape as #3124, much wider blast radius. Expected symptom is a probabilistic uptick in path-guessing and wasted exploration, not a functional failure.Steps to reproduce
Code-level, no harness needed:
workspace_sectionhas one caller (pool.rs:1154), reachable only fromcreate_session_and_apply_model(pool.rs:820); neither legacy assembly point calls it.End-to-end: run a managed Claude Code or Codex agent on any platform and inspect the prompt the harness receives (desktop transcript observer, or the harness log). Expect
[Base]with no[Workspace]block. Compare against abuzz-agentagent on the same host, which does get it.Expected behavior
Protocol-v1 harnesses receive the same workspace anchor as v2 harnesses.
Version and platform
main@87b3fcd3. All platforms — this is not OS-specific.[Workspace]was introduced in fix(desktop): ground agent workspace, migrate legacy nest, configurable repos_dir #1194 (2026-06-23).Logs / additional context
Origin — an omission, not a decision.
workspace_sectioncame in with1011cea2("fix(desktop): ground agent workspace, migrate legacy nest, configurable repos_dir", #1194, 2026-06-23) — the only commitgit log -S workspace_section -- crates/buzz-acp/returns. Inside the crate it touched onlypool.rsandbase_prompt.md; it did not touchqueue.rs, and withinpool.rsit did not touchprepend_base_for_legacy— the diff sat inframed_system_prompt, then inside an explicitif agent.protocol_version >= 2 { … } else { … }, and the legacy arm was left alone. All seven tests added targetframed_system_prompt/workspace_sectionin isolation. The doc comment added atpool.rs:1151-1153reasons about persona-only agents and the/fallback but never mentions protocol version.prepend_base_for_legacypredates it (f0858824, #981, 2026-06-12; later73cc31cc#1277,3b9b371f#1755,64b8fea6#2034) and no commit in that set considers[Workspace]. (Inference from diffs and messages: never added, rather than consciously excluded.)Fix shape — three insertion points, one shared renderer:
queue.rs:1352-1375— addcwd: Option<&'a str>toFormatPromptArgs;pool.rs:1771-1784already hasctx.cwdin scope.queue.rs:1429-1432— push the workspace section ahead ofbase_section(bp), matchingframed_system_prompt's ordering.pool.rs:1090-1101— add acwdparameter for the initial-message (pool.rs:1593) and heartbeat (pool.rs:1731) call sites. Missing the heartbeat site causes exactly the drift thepool.rs:1088-1089doc comment says the helper exists to prevent.workspace_sectionis private inpool.rsand would need to move to a shared module or be re-exported.Risks worth weighing before doing it:
format_promptruns on every batch flush. But[Base](~150 lines) plus[System],[Team Instructions],[Agent Memory — core], and[Channel Canvas]are already re-sent every legacy turn.[Workspace]is one ~70-word paragraph, well under 1% of existing per-turn overhead.Some(base_prompt)coupling.framed_system_promptemits[Workspace]only when a base prompt exists (pool.rs:1154-1157); a persona-only legacy agent must not get an anchor with no layout to go with it — the casepool.rs:1151-1153warns about.MAX_SYSTEM_PROMPT_BYTESdoes not apply (the legacy path uses no system prompt). The observer size trimmer (queue.rs:1396-1402) handles a new section gracefully, but it adds a leaf to the desktop "Prompt context" panel — worth a visual check.Relationship to #3124 — independent, neither subsumes the other. #3124 fixes whether the renderer returns
Some(the POSIX-only predicate), which helps callers that already invoke it. This issue is that the legacy path never invokes it. A Windows Claude Code agent is broken for both reasons; a macOS Claude Code agent for this one alone. They could land in one PR — the natural implementation here is "call the predicate-fixedworkspace_sectionfrom the legacy assembly points" — but closing one must not close the other.Open question for maintainers: given the
acp.rs:540-541note about squatting on v2 ahead of the upstream RFD, is the intended long-term answer to fix the legacy path, or to converge on upstream versioning so the v1/v2 split disappears? That choice decides whether this is worth fixing directly or is better absorbed into the RFD follow-up.Duplicate search: searched issues for "workspace prompt", "OUTBOX", and PRs for "base_prompt workspace" — none found. Nearest is #2334 (base prompt shown as session title in harness UIs), a display concern.