Summary
Agents running on claude-native never receive their own prompt / instructions. The agent spec is resolved and the composed instructions are built, but the native path never delivers them to Claude Code — so the session behaves as plain Claude Code with no role, scope or constraints.
This affects the bundled polly orchestrator directly: all six of its sub-agents are declared as *-native, and each ships a detailed role prompt that never arrives.
Controlled experiment
Parent agent on claude-sdk with one sub-agent. The sub-agent's prompt contains a code word:
# agents/worker/config.yaml
prompt: |
You are a PlusTeam probe worker. Your code word is: SUBPROMPT-ALIVE.
If asked for the code word or who you are, answer exactly:
"SUBPROMPT-ALIVE, I am the PlusTeam probe worker".
The parent dispatches via sys_session_send and returns the reply verbatim. The only variable changed between runs is the sub-agent's harness:
| sub-agent harness |
reply |
claude-sdk |
SUBPROMPT-ALIVE, I am the PlusTeam probe worker |
claude-native |
No code word is set — I don't see one. I am Claude Code (Sonnet 5) |
Same parent, same spec, same dispatch. The prompt is present in the parsed spec in both cases (AgentSpec.instructions is populated — verified by parsing the bundle directly).
The same happens for a top-level (non-sub-agent) session: a claude-native agent asked to quote its own role answered "there is no such block in my context, neither as a file nor as an injection".
Where it is lost
augment_claude_args(...) already accepts append_system_prompt and forwards it to Claude Code's --append-system-prompt flag. But both call sites pass only the framework's session-rename instruction:
runner/app.py:5825-5840 (managed-host path) — append_system_prompt=session_rename_instruction(...)
claude_native.py:4166 / claude_native.py:3229 (local CLI path) — same
Meanwhile the composed instructions are produced: build_instructions(cached_spec, …) at runner/app.py:13822, placed into harness_body["instructions"] at runner/app.py:13874-13875. Nothing on the native bridge consumes that field — the only instructions hit in claude_native_bridge.py is MCP serverInfo, unrelated.
Inspecting the live bwrap command line of a running native session confirms it: the launch carries --mcp-config and --settings, and no --system-prompt / --append-system-prompt / --agent.
Impact on polly
resources/examples/polly/agents/{claude_code,codex,opencode,cursor,hermes}/config.yaml are all *-native and each carries ~1.7k characters of role: the IMPLEMENT / REVIEW / EXPLORE contract, "stay strictly within the named scope", the Co-authored-by: omnigent <noreply@omnigent.ai> commit trailer, "never push to a protected branch or force-push", and "report with file:line evidence".
None of that reaches the worker. Polly still functions because the orchestrator restates purpose and contract inside every dispatched task — but the sub-agent role definitions in the shipped example are inert, and any behaviour they are meant to guarantee (the commit trailer, the force-push ban, the review-never-edits rule) rests on the task text alone.
Proposed fix
Pass the session's resolved agent instructions into append_system_prompt, merged with the existing framework instruction rather than replacing it — append_framework_instructions() in runtime/prompt.py already implements exactly this ordering (user-authored first, framework metadata last).
Concretely, at runner/app.py:5834:
append_system_prompt=append_framework_instructions(
resolved_instructions, # from build_instructions(cached_spec, …)
[session_rename_instruction(initial_session=…)],
),
Notes:
AgentSpec.instructions is already resolved for sub-agents on this path — _find_spec_by_name swaps in the child's spec before dispatch (runner/app.py:13757+), so the correct prompt is in hand at that point.
- If the omission is deliberate (the docstring calls the channel "framework-owned instructions"), it would help to say so in the docs and to mark the sub-agent
prompt: field in examples/polly as non-functional for native harnesses — right now it reads as working configuration.
Environment
- Omnigent 0.6.0, self-hosted server, Linux (Ubuntu 24.04)
- Claude Code 2.1.215, subscription auth
- Reproduced on both the local-runner path (
omnigent run <bundle> --server …) and a top-level session
Test bundles and sessions were deleted after the runs. Happy to re-run the experiment against a patch.
Summary
Agents running on
claude-nativenever receive their ownprompt/instructions. The agent spec is resolved and the composed instructions are built, but the native path never delivers them to Claude Code — so the session behaves as plain Claude Code with no role, scope or constraints.This affects the bundled
pollyorchestrator directly: all six of its sub-agents are declared as*-native, and each ships a detailed role prompt that never arrives.Controlled experiment
Parent agent on
claude-sdkwith one sub-agent. The sub-agent's prompt contains a code word:The parent dispatches via
sys_session_sendand returns the reply verbatim. The only variable changed between runs is the sub-agent'sharness:claude-sdkSUBPROMPT-ALIVE, I am the PlusTeam probe workerclaude-nativeNo code word is set — I don't see one. I am Claude Code (Sonnet 5)Same parent, same spec, same dispatch. The prompt is present in the parsed spec in both cases (
AgentSpec.instructionsis populated — verified by parsing the bundle directly).The same happens for a top-level (non-sub-agent) session: a
claude-nativeagent asked to quote its own role answered "there is no such block in my context, neither as a file nor as an injection".Where it is lost
augment_claude_args(...)already acceptsappend_system_promptand forwards it to Claude Code's--append-system-promptflag. But both call sites pass only the framework's session-rename instruction:runner/app.py:5825-5840(managed-host path) —append_system_prompt=session_rename_instruction(...)claude_native.py:4166/claude_native.py:3229(local CLI path) — sameMeanwhile the composed instructions are produced:
build_instructions(cached_spec, …)atrunner/app.py:13822, placed intoharness_body["instructions"]atrunner/app.py:13874-13875. Nothing on the native bridge consumes that field — the onlyinstructionshit inclaude_native_bridge.pyis MCPserverInfo, unrelated.Inspecting the live
bwrapcommand line of a running native session confirms it: the launch carries--mcp-configand--settings, and no--system-prompt/--append-system-prompt/--agent.Impact on polly
resources/examples/polly/agents/{claude_code,codex,opencode,cursor,hermes}/config.yamlare all*-nativeand each carries ~1.7k characters of role: the IMPLEMENT / REVIEW / EXPLORE contract, "stay strictly within the named scope", theCo-authored-by: omnigent <noreply@omnigent.ai>commit trailer, "never push to a protected branch or force-push", and "report with file:line evidence".None of that reaches the worker. Polly still functions because the orchestrator restates purpose and contract inside every dispatched task — but the sub-agent role definitions in the shipped example are inert, and any behaviour they are meant to guarantee (the commit trailer, the force-push ban, the review-never-edits rule) rests on the task text alone.
Proposed fix
Pass the session's resolved agent instructions into
append_system_prompt, merged with the existing framework instruction rather than replacing it —append_framework_instructions()inruntime/prompt.pyalready implements exactly this ordering (user-authored first, framework metadata last).Concretely, at
runner/app.py:5834:Notes:
AgentSpec.instructionsis already resolved for sub-agents on this path —_find_spec_by_nameswaps in the child's spec before dispatch (runner/app.py:13757+), so the correct prompt is in hand at that point.prompt:field inexamples/pollyas non-functional for native harnesses — right now it reads as working configuration.Environment
omnigent run <bundle> --server …) and a top-level sessionTest bundles and sessions were deleted after the runs. Happy to re-run the experiment against a patch.