fix(runtime): announce sub-agent wake notices to the model on trusted channels - #6250
Conversation
… channels The wake notice a parent receives when a sub-agent finishes was never described to the model: it arrived as a user turn claiming to be "System" and ordering a tool call, which a safety-tuned orchestrator reads as prompt injection and refuses, stranding the orchestration. Quote the exact notice, and say that it comes from the runtime, in the composed system prompt of every agent that can dispatch sub-agents, in the sys_session_send and sys_read_inbox tool descriptions, and in the per-dispatch launching tool result. The notice text itself is unchanged, so the web and REPL marker rendering keeps working. Claude-Session: https://claude.ai/code/session_01LfrfEJXdimBBoy5zEttpgL Signed-off-by: dbczumar <corey.zumar@databricks.com>
|
…atchers Review follow-up: describe the notice as a routine runtime status message rather than asserting it is "not a prompt injection" (a user-turn channel cannot prove origin), and extend the prompt gate to agents whose only dispatch path is the web_fetch builtin, whose researcher wakes the parent the same way. Claude-Session: https://claude.ai/code/session_01LfrfEJXdimBBoy5zEttpgL Signed-off-by: dbczumar <corey.zumar@databricks.com>
|
Adopted both Polly suggestions in the follow-up commit: the instruction now frames the notice as a routine runtime status message (no authenticity claim), and the prompt gate also covers agents whose only dispatch path is the |
|
Note on the red |
|
Live verification on a server, web UI, and host all built from this branch (dedicated server on 127.0.0.1:8957 with its own SQLite DB, claude-sdk (Debby, claude-native ( Two side observations, not from this PR: a web-created session with |
|
🏷️ Doc impact: Internal prompt/tool-description wording that announces the sub-agent wake-notice to the model plus a message-builder refactor; no user-facing surface, integration, or built-in policy changed. Auto-classified on merge. Set the label manually before merging to override. · run |
Related issue
Closes #4682
Summary
Sub-agent wake notices (
[System: sub-agent <agent>/<title> finished (<status>) — <N> results waiting in inbox. Call sys_read_inbox to collect.]) were never described to the model anywhere. Neither the composed system prompt nor thesys_session_send/sys_read_inboxtool descriptions mention them, so the orchestrator's first encounter with one is a user turn that claims to be "System" and orders a tool call. A safety-tuned Claude reads that as prompt injection and refuses, stranding the orchestration.This PR fixes what the model reads, on channels it already trusts and that reach every harness:
omnigent/runtime/prompt.pygainsSUBAGENT_WAKE_NOTICE_SHAPEandSUBAGENT_WAKE_NOTICE_INSTRUCTION;build_instructions/build_instructions_nullableappend the announcement for any agent that can dispatch sub-agents (thesys_session_sendregistration gate, declaredtools.agentsorspawn: true, plus theweb_fetchbuiltin whose researcher dispatch wakes the parent the same way). Other agents' prompts are untouched.sys_session_sendandsys_read_inboxquote the exact notice and say it comes from the runtime, not from a person. Tool descriptions reach native harnesses (including the reporter's claude-native) through the MCP relay, which the composed system prompt does not.The notice text itself is unchanged, so the web/REPL
[System: …]marker rendering and the existing e2e wake tests keep working. A drift-guard test pins the announced shape to what_format_subagent_wake_noticeactually produces.Why not
role="system"(#6057)? The Claude Agent SDK has no inbound system-role turn: every prompt is wrapped as{"type":"user","message":{"role":"user"}}, and claude-native types the notice into the TUI. On the resumed-session path the SDK-bound prompt is byte-identical for a user-role and a system-role tail, so a wire-level role change never reaches the two harnesses the bug is about. Telling the model what the notice is, before it arrives, does.ELI5: the app kept slipping the orchestrator a note signed "System" without ever saying such notes exist. Now the orchestrator's briefing (system prompt), its tool manual (descriptions), and the receipt it gets when it dispatches a helper all say: "when your helper finishes, you'll get exactly this note from us — go read your inbox."
Test Plan
tests/runtime/test_prompt.py: announcement appended fortools.agents, forspawn: true, and for aweb_fetch-only agent, ordered after authored text and before per-turn framework instructions; nullable path returns the announcement alone without the fabricated fallback; drift guard assertsSUBAGENT_WAKE_NOTICE_SHAPEmatches_format_subagent_wake_noticeoutput.tests/tools/builtins/test_sys_session.py,tests/tools/builtins/test_async_inbox.py: both tool descriptions quote the notice shape and its runtime origin.tests/runtime+tests/tools(2236 passed; 3 failures intest_openai_agents_sdk_spawn_env.py/test_provider_spawn_env.pyreproduce identically onmainand come from this machine's~/.omnigent/config.yamlproviders), plus the instruction/dispatch-related selection acrosstests/runner,tests/inner,tests/server(77 passed).examples/debby(declared sub-agents) andexamples/polly(spawn: true) throughomnigent.spec.load+build_instructions+ToolManager: the announcement is present in both prompts, and the registeredsys_session_send/sys_read_inboxdescriptions quote the wake shape.dev/lintpre-commit scripts pass on the changed files.Not done here: a live-model check that a Claude orchestrator now calls
sys_read_inboxon wake. The mock-LLM suites cannot observe a refusal, so that check belongs in the nightly live-model job. To try it by hand: check out this branch, run an orchestrator with sub-agents on claude-sdk and on claude-native (e.g.examples/debby), dispatch a sub-agent whose send gets backgrounded, let the parent's turn end, and confirm the parent callssys_read_inboxwhen the[System: sub-agent … finished …]notice arrives instead of objecting to it. Inspect the system prompt the harness received (claude-sdk: thesystem_promptoption; API harnesses: theinstructionsfield) to see the announcement paragraph at its tail.Demo
Tail of the composed system prompt for
examples/debbyon this branch:Type of change
Test coverage
Coverage notes
Manual verification = rendering the composed prompt and tool schemas for the bundled
debbyandpollyorchestrators through the real spec loader and tool manager (see Test Plan). The behavioral outcome (no refusal on wake) needs a real model and is left to the nightly live-model job.Changelog
Orchestrators that dispatch sub-agents are now told, in their system prompt and tool descriptions, exactly what a sub-agent wake notice looks like and that it comes from the runtime, so Claude no longer mistakes it for prompt injection and reliably collects the inbox.
https://claude.ai/code/session_01LfrfEJXdimBBoy5zEttpgL