Skip to content

docs(agents): forbid ending a subagent turn narrating an intention to wait - #5019

Open
bobmatnyc wants to merge 1 commit into
mainfrom
fix/base-agent-no-narrated-wait
Open

docs(agents): forbid ending a subagent turn narrating an intention to wait#5019
bobmatnyc wants to merge 1 commit into
mainfrom
fix/base-agent-no-narrated-wait

Conversation

@bobmatnyc

@bobmatnyc bobmatnyc commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Defect

A subagent handed a long-running operation ends its turn narrating an intention to wait — "I'll wait for the pull to complete", "will resume automatically when the monitor reports completion", "monitoring in the background" — with its task unfinished. Nothing resumes it. Observed five times in one session; the parent had to notice and re-engage each one by hand.

This is a harness gap, not misuse. A subagent's turn ends the moment it stops emitting tool calls, and that stop is its result to the parent. CLAUDE_SUBAGENT_BG_SHELL_MAX_MS explicitly lets a subagent-owned background shell outlive the subagent's turn, but nothing reconnects the two when it finishes: the main loop is re-invoked on background completion, and there is no subagent equivalent. Parent-driven SendMessage is the only resume mechanism.

Resolution

One new section in BASE-AGENT.md, Never Narrate a Wait, carrying five rules: never end a turn narrating a wait; stay in the turn via run_in_background plus an until-loop; foreground sleep is blocked in this harness so the waiter must be backgrounded too; reporting unfinished state and stopping is a correct outcome; never re-issue a long-running command because a foreground shell returned early at the ~120s cap.

Rule 3 is called out explicitly because a prior session recorded that plain "poll" advice was itself producing the parking it meant to prevent — sleep 60 && check is the shape agents reach for, and it does not run here.

The now-redundant "never spawn a background monitor as a wake mechanism" bullet under Your own gates DO block, in the foreground collapses to a pointer at the new section.

Where it sits, and why

Immediately before ## Git Workflow, in the operating-rules band after PM Authority & Escalation — roughly a quarter of the way into the file.

The existing turn-ending guidance (Finishing Work — Push, Report, Stop, Report, don't promise) lives at ~line 250 and is framed around CI. An agent reads that band only once it believes it is finishing. The stranding happens mid-task, on a build, a clone, a VM run — so the rule has to be in front of the agent before it picks its first long command, not appended at the bottom.

Register matches the file: numbered imperatives, one 🔴 marker on the blocked-sleep trap, one code block. No harness-internals essay.

Byte-parity mirror

BASE-AGENT.md exists in two crates and is held at byte parity by scripts/check_agent_assets.sh:

  • crates/trusty-mpm/src/assets/agents/BASE-AGENT.md
  • crates/trusty-code/src/assets/agents/BASE-AGENT.md

Both are in the gate's PARITY set (5 BASE-* + 24 roster agents), not the 4-file pinned-deviation set, so no re-pinning was needed and scripts/agent-asset-pins.tsv is untouched. Both files hash 0ee276e2a1d5ab5a7e9aa2de72f6a52924017ec4 after the change.

Ownership and how a user picks this up

Framework-owned, overwritten on install. crates/trusty-mpm/src/core/bundle_all.rs:107 registers it as overwrite("agents/BASE-AGENT.md", BASE_AGENT)InstallPolicy::Overwrite, not SeedOnce. A user-modified copy is still respected by the deployer's ownership rule (a file whose on-disk content no longer matches what tm wrote is left alone), but the shipped default tracks upgrades.

A rebuild alone does not deploy it. The asset is include_str!-embedded, so it reaches an agent only through an install plus an asset deploy:

cargo install --path crates/trusty-mpm --locked   # or: tctl install trusty-mpm (keeps the TCC grant)
tm sessions sync-assets                            # or start a new session

Session launch deploys agents exactly once, at launch (DOC-34/#2002); tm sessions sync-assets is the offline redeploy for a live session.

Gates

Gate Result
cargo fmt --check EXIT=0
cargo check -p trusty-mpm Finished dev profile ... in 41.53s, EXIT=0
cargo test -p trusty-mpm 4724 passed; 1 failed; 7 ignored — see below
bash scripts/check_agent_assets.sh scanned 30 byte-parity file(s) (floor 20), all match; 4 pinned deviation(s) match upstream — OK.
bash scripts/check_sld.sh scanned 56 spec doc(s) + 3115 code file(s); 0 error(s), 0 warning(s)
bash scripts/check_line_cap.sh measured 3742 tracked .rs file(s) (floor 500); 7 allowlisted, 0 violations — OK.
bash scripts/check_test_pointers.sh resolved 22070 Test: citation(s) (floor 200) — 0 dangling pointers — OK.
bash scripts/check_changelog_fragment.sh scanned 4 changed path(s); all 2 crate(s) with source changes are recorded.

The one failure is pre-existing

client::executor::tests::execute_doctor_against_test_daemon — a documented baseline flake (docs/reference/test-ladder-baseline.md). Proven, not assumed: with both BASE-AGENT.md files reverted to origin/main via git checkout origin/main -- <paths>, the full cargo test -p trusty-mpm failed on the same single test:

baseline (origin/main assets):  test result: FAILED. 4724 passed; 1 failed; 7 ignored  →  execute_doctor_against_test_daemon
with this change:               test result: FAILED. 4724 passed; 1 failed; 7 ignored  →  execute_doctor_against_test_daemon

Two other flakes seen and traced, not silenced

An earlier run also failed compose_session_instructions_display_matches_live_prompt and ..._with_override. Cause found, and it is not this diff: the assertion diff is the agent roster embedded in the PM prompt, differing by five user-tier entries (copyeditor, pangram-editor, proofreader, writer, writing-critic) between the test's two prompt builds. Both builds scan the live $CLAUDE_CONFIG_DIR/agents directory, and that directory was being rewritten by a concurrent deploy on this machine at the time (drwxr-xr-x ... Aug 6 13:31 ., mid-run). Same machine, load average 35/51/64. cargo test -p trusty-mpm --bin tm on pristine origin/main assets passed 3/3 at 1518 passed; the same suite with this change passed 1/3 and failed with a different test (guided_fallback_non_git_dir_with_managed_env_settles_quickly_returns_promptly, a timing test) on one of the reds. Nothing was ignored, cfg-gated, or excluded.

check_test_pointers.sh did not return within 9 minutes on a second invocation under that load; its result above is from the earlier chain, on identical content — this change touches zero .rs files.

Changelog

crates/trusty-mpm/changelog.d/5019-base-agent-no-narrated-wait.md and crates/trusty-code/changelog.d/5019-base-agent-no-narrated-wait.md, one Changed category each.

No issue filed — instruction fixes go straight into the assets.

🤖🤖🤖 Generated with trusty-mpm — https://github.com/bobmatnyc/trusty-tools

@bobmatnyc bobmatnyc added the trusty-mpm trusty-mpm platform and related work label Aug 6, 2026
@bobmatnyc bobmatnyc self-assigned this Aug 6, 2026
… wait

A subagent's turn ends the moment it stops emitting tool calls, and that stop
is its result to the parent. Nothing re-invokes it afterward: the main loop is
re-invoked on background-shell completion, but there is no subagent equivalent,
and `CLAUDE_SUBAGENT_BG_SHELL_MAX_MS` explicitly lets a subagent-owned
background shell outlive the turn that started it. Parent-driven `SendMessage`
is the only resume path. Agents nonetheless end turns with "I'll wait for the
pull to finish" or "will resume when the monitor reports completion" and strand
the task — observed five times in one session.

Adds a `Never Narrate a Wait` section to `BASE-AGENT.md`, placed before
`Git Workflow` so an agent reads it before starting work rather than at the
bottom under `Finishing Work`, which it reaches only when it already believes
it is done. The section names the working shape (start the operation with
`run_in_background`, poll a backgrounded until-loop), calls out that foreground
`sleep` is blocked in this harness — the trap a bare "poll" instruction walks
agents into — and states that reporting unfinished state and stopping is a
correct outcome.

The redundant "never spawn a background monitor as a wake mechanism" bullet
under `Your own gates DO block` collapses to a pointer at the new section.

Both copies stay byte-identical per `scripts/check_agent_assets.sh`.

🤖🤖🤖 Generated with trusty-mpm — https://github.com/bobmatnyc/trusty-tools
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

trusty-mpm trusty-mpm platform and related work

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants