Summary
Replace the fragile file-based platform instruction injection (CLAUDE.local.md written via HTTP push) with runtime injection via --append-system-prompt on every Claude Code invocation. The backend controls the instructions and sends them with every chat/task request. The agent server passes them through to the CLI. One path, zero coordination.
Problem
The current injection model is push-based: the backend calls POST /api/trinity/inject on the agent after startup, which writes CLAUDE.local.md inside the container. This creates a coordination problem — every code path that starts/restarts a container must remember to inject. Multiple paths already don't:
lifespan() startup — lists agents but never injects into running ones
system_agent_service.ensure_deployed() — calls container_start() directly
- Fleet restart (
/api/ops/fleet/restart) — calls container_start() directly
- Any container recreation (volume wipe, image rebuild)
Additionally, the file lives inside the agent's filesystem, so agents could theoretically modify or delete it.
Solution
Backend-controlled runtime injection. The backend reads platform instructions + custom prompt from DB, sends them as system_prompt with every request to the agent. The agent server passes them to Claude Code via --append-system-prompt. This flag is per-invocation (not accumulated across --continue calls), so it's safe to use on every call.
Backend Agent Server Claude Code
│ │ │
│ POST /api/chat │ │
│ {message, system_prompt} ──────► │ │
│ │ claude --append-system-prompt │
│ │ "platform instructions" ──► │
Changes Required
Backend — Build and send platform instructions
1. Create src/backend/services/platform_prompt_service.py
- Single function:
get_platform_system_prompt() -> str
- Reads static platform instructions (agent collaboration, operator queue, package persistence — currently hardcoded in agent-side
trinity.py:107-143)
- Reads
trinity_prompt custom prompt from DB setting
- Returns combined string
- This is the single source of truth for platform instructions
2. Modify src/backend/routers/chat.py — Chat endpoint
POST /api/agents/{name}/chat (line ~211): Add system_prompt to payload sent to agent
- Call
get_platform_system_prompt() and include in every request
- Payload becomes:
{"message": ..., "stream": ..., "model": ..., "system_prompt": ...}
3. Modify src/backend/services/task_execution_service.py — Task execution
execute_task() (line ~218): If no system_prompt provided by caller, inject platform prompt
- If caller provides
system_prompt, prepend platform instructions to it
- All scheduled tasks, manual tasks, and MCP-triggered tasks flow through here
Agent Server — Pass system_prompt through to Claude Code
4. Modify docker/base-image/agent_server/routers/chat.py — Chat endpoint
POST /api/chat: Accept system_prompt field in ChatRequest
- Pass it through to
runtime.execute()
5. Modify docker/base-image/agent_server/services/claude_code.py — Chat execution
execute_claude_code() (line ~456): Accept system_prompt parameter
- Add
--append-system-prompt flag when system_prompt is provided
- This is already done for
execute_headless_task() (line ~785) — mirror the same pattern
6. Modify docker/base-image/agent_server/services/runtime_adapter.py
AgentRuntime.execute(): Add system_prompt parameter to abstract method signature
- Update
ClaudeCodeRuntime.execute() to pass it through
Cleanup — Remove old injection mechanism
7. Delete inject_trinity_meta_prompt() and all call sites
src/backend/services/agent_service/lifecycle.py:68-95 — Delete function
src/backend/services/agent_service/lifecycle.py:237 — Remove call from start_agent_internal()
src/backend/routers/system_agent.py:180,240 — Remove calls from reinitialize/restart
src/backend/routers/system_agent.py:34-40 — Remove set_inject_trinity_meta_prompt() callback
src/backend/routers/agents.py:41 — Remove import
8. Delete AgentClient.inject_trinity_prompt()
src/backend/services/agent_client.py:398-464 — Delete method
9. Remove agent-side file injection endpoint
docker/base-image/agent_server/routers/trinity.py — Remove POST /api/trinity/inject endpoint and all CLAUDE.local.md writing logic
- Keep
GET /api/trinity/status but update to reflect new model (no longer checks for file)
- Remove
POST /api/trinity/reset (no files to reset)
- Remove
TrinityInjectRequest, TrinityInjectResponse models
10. Remove CLAUDE.local.md references from startup.sh
docker/base-image/startup.sh:182-184 — Remove comment about injection being handled by agent-server
11. Clean up fleet restart / system agent paths
src/backend/routers/ops.py:294-303 — No longer needs injection (it's per-request now)
src/backend/services/system_agent_service.py:56-119 — ensure_deployed() no longer needs injection concern
Static content relocation
12. Move platform instruction text to backend
- The static platform instructions currently hardcoded in agent-side
trinity.py:107-143 move to platform_prompt_service.py
- Content: agent collaboration (MCP tools), operator communication reference, package persistence
config/trinity-meta-prompt/prompt.md content gets incorporated into the platform prompt
What NOT to change
config/trinity-meta-prompt/ directory and volume mount — still useful for .trinity/prompt.md (operator communication protocol reference)
- The
trinity_prompt DB setting and its admin UI — still the mechanism for custom instructions
- Agent-side MCP injection (
inject_trinity_mcp_if_configured) — unrelated mechanism
- Credential injection (
inject_assigned_credentials) — unrelated mechanism
Acceptance Criteria
Testing
- Start an agent, send a chat message — verify platform instructions appear in Claude Code's system prompt
- Restart backend, send a chat message to existing agent — instructions still present
- Change
trinity_prompt setting — next message to any agent reflects the change immediately
- Fleet restart — agents receive instructions on next interaction
- Grep codebase for
CLAUDE.local.md, inject_trinity_meta_prompt, /api/trinity/inject — zero results
Supersedes #135
Summary
Replace the fragile file-based platform instruction injection (
CLAUDE.local.mdwritten via HTTP push) with runtime injection via--append-system-prompton every Claude Code invocation. The backend controls the instructions and sends them with every chat/task request. The agent server passes them through to the CLI. One path, zero coordination.Problem
The current injection model is push-based: the backend calls
POST /api/trinity/injecton the agent after startup, which writesCLAUDE.local.mdinside the container. This creates a coordination problem — every code path that starts/restarts a container must remember to inject. Multiple paths already don't:lifespan()startup — lists agents but never injects into running onessystem_agent_service.ensure_deployed()— callscontainer_start()directly/api/ops/fleet/restart) — callscontainer_start()directlyAdditionally, the file lives inside the agent's filesystem, so agents could theoretically modify or delete it.
Solution
Backend-controlled runtime injection. The backend reads platform instructions + custom prompt from DB, sends them as
system_promptwith every request to the agent. The agent server passes them to Claude Code via--append-system-prompt. This flag is per-invocation (not accumulated across--continuecalls), so it's safe to use on every call.Changes Required
Backend — Build and send platform instructions
1. Create
src/backend/services/platform_prompt_service.pyget_platform_system_prompt() -> strtrinity.py:107-143)trinity_promptcustom prompt from DB setting2. Modify
src/backend/routers/chat.py— Chat endpointPOST /api/agents/{name}/chat(line ~211): Addsystem_promptto payload sent to agentget_platform_system_prompt()and include in every request{"message": ..., "stream": ..., "model": ..., "system_prompt": ...}3. Modify
src/backend/services/task_execution_service.py— Task executionexecute_task()(line ~218): If nosystem_promptprovided by caller, inject platform promptsystem_prompt, prepend platform instructions to itAgent Server — Pass system_prompt through to Claude Code
4. Modify
docker/base-image/agent_server/routers/chat.py— Chat endpointPOST /api/chat: Acceptsystem_promptfield inChatRequestruntime.execute()5. Modify
docker/base-image/agent_server/services/claude_code.py— Chat executionexecute_claude_code()(line ~456): Acceptsystem_promptparameter--append-system-promptflag whensystem_promptis providedexecute_headless_task()(line ~785) — mirror the same pattern6. Modify
docker/base-image/agent_server/services/runtime_adapter.pyAgentRuntime.execute(): Addsystem_promptparameter to abstract method signatureClaudeCodeRuntime.execute()to pass it throughCleanup — Remove old injection mechanism
7. Delete
inject_trinity_meta_prompt()and all call sitessrc/backend/services/agent_service/lifecycle.py:68-95— Delete functionsrc/backend/services/agent_service/lifecycle.py:237— Remove call fromstart_agent_internal()src/backend/routers/system_agent.py:180,240— Remove calls from reinitialize/restartsrc/backend/routers/system_agent.py:34-40— Removeset_inject_trinity_meta_prompt()callbacksrc/backend/routers/agents.py:41— Remove import8. Delete
AgentClient.inject_trinity_prompt()src/backend/services/agent_client.py:398-464— Delete method9. Remove agent-side file injection endpoint
docker/base-image/agent_server/routers/trinity.py— RemovePOST /api/trinity/injectendpoint and allCLAUDE.local.mdwriting logicGET /api/trinity/statusbut update to reflect new model (no longer checks for file)POST /api/trinity/reset(no files to reset)TrinityInjectRequest,TrinityInjectResponsemodels10. Remove
CLAUDE.local.mdreferences fromstartup.shdocker/base-image/startup.sh:182-184— Remove comment about injection being handled by agent-server11. Clean up fleet restart / system agent paths
src/backend/routers/ops.py:294-303— No longer needs injection (it's per-request now)src/backend/services/system_agent_service.py:56-119—ensure_deployed()no longer needs injection concernStatic content relocation
12. Move platform instruction text to backend
trinity.py:107-143move toplatform_prompt_service.pyconfig/trinity-meta-prompt/prompt.mdcontent gets incorporated into the platform promptWhat NOT to change
config/trinity-meta-prompt/directory and volume mount — still useful for.trinity/prompt.md(operator communication protocol reference)trinity_promptDB setting and its admin UI — still the mechanism for custom instructionsinject_trinity_mcp_if_configured) — unrelated mechanisminject_assigned_credentials) — unrelated mechanismAcceptance Criteria
--append-system-promptget_platform_system_prompt()is the single source of truth for platform instructionstrinity_promptsetting is included and takes effect immediately (no agent restart needed)CLAUDE.local.mdfile writing anywhere in the codebaseinject_trinity_meta_prompt()function or call sites remain/api/trinity/injectendpoint remainsTesting
trinity_promptsetting — next message to any agent reflects the change immediatelyCLAUDE.local.md,inject_trinity_meta_prompt,/api/trinity/inject— zero resultsSupersedes #135