Skip to content

refactor: Runtime injection of platform instructions via --append-system-prompt #136

Description

@vybe

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-119ensure_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

  • Every Claude Code invocation (chat and task) receives platform instructions via --append-system-prompt
  • get_platform_system_prompt() is the single source of truth for platform instructions
  • Custom prompt from trinity_prompt setting is included and takes effect immediately (no agent restart needed)
  • No CLAUDE.local.md file writing anywhere in the codebase
  • No inject_trinity_meta_prompt() function or call sites remain
  • No /api/trinity/inject endpoint remains
  • Fleet restart, system agent restart, and all other start paths work without special injection logic
  • Agents started via any method (API, fleet restart, docker restart) receive platform instructions on first interaction
  • Platform instructions cannot be modified or deleted by the agent

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions