fix(#55): generalize fleet context dual-injection fix across backends#56
Merged
Conversation
Before this change every non-Claude backend received the same fleet context (identity, role, workflow, decisions, custom prompt) twice: once via the workspace project doc the CLI auto-loads (GEMINI.md, AGENTS.md, .kiro/steering/) and once via the MCP `instructions` capability emitted by mcp-server.ts. This wasted tokens and diluted attention. Issue #55 originally scoped this to Gemini, but the same pattern affects Codex, Kiro, and (latently) OpenCode. Approach: single declarative flag on each backend says how it natively injects fleet instructions; the daemon uses it to decide whether the MCP `instructions` capability still fires. - Add `nativeInstructionsMechanism: 'append-flag' | 'project-doc' | 'none'` to `CliBackend`. Tag claude-code/opencode as `append-flag`, gemini-cli/codex/kiro-cli as `project-doc`, mock as `none`. - `Daemon.buildBackendConfig`: when the flag is non-`none`, drop the fleet-context env vars (`AGEND_DISPLAY_NAME`, `AGEND_DESCRIPTION`, `AGEND_WORKFLOW`, `AGEND_CUSTOM_PROMPT`, `AGEND_DECISIONS`) from the MCP server's env and set `AGEND_DISABLE_MCP_INSTRUCTIONS=1`. - `mcp-server.ts`: omit the `instructions` capability when `AGEND_DISABLE_MCP_INSTRUCTIONS=1` is set. Fallback path (capability active) preserved for backends with no native injection. - The backend's `writeConfig()` always receives the assembled instructions string for native injection, so identity/workflow/etc. still reach the model — just through one channel, not two. Notes: - Claude was never doubly-injected (it uses an instance-dir file via `--append-system-prompt-file`, not a workspace doc) but we still tag it explicitly so the rule is uniform. - `AGEND_DECISIONS` was the second silent dual-injection source: it was both passed to the daemon-side `buildFleetInstructions()` (which embeds decisions in the project doc) and re-passed through mcpEnv to rebuild instructions on the MCP side. Now gated together with the rest. - All existing `writeConfig()` paths are idempotent (`appendWithMarker` for project-doc backends, plain overwrite for the others), so upgrading existing instances does not require a migration. The MCP server is respawned on every CLI launch, so it picks up the new capability shape automatically. - `instructionsReloadedOnResume` and the daemon's force-new-session detection are unaffected: this fix changes only the *delivery channel*, not the instructions text content. Tests: - New `tests/backend/native-instructions-mechanism.test.ts` asserts the flag value on every backend. - New `tests/daemon-build-backend-config.test.ts` exercises `buildBackendConfig` against all 6 backends and verifies fleet context env vars / `AGEND_DISABLE_MCP_INSTRUCTIONS` follow the gate. - `npx tsc --noEmit` clean. `npx vitest run` 62 files / 517 tests pass. Docs: - New `docs/fleet-instructions-injection.md` explains per-backend delivery channels and how to verify a new backend. Closes #55. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
suzuke
added a commit
that referenced
this pull request
Apr 27, 2026
Sprint 0 outcome: 0 open issues, 0 open PRs. Three PRs shipped (#56 #55 fix; #57 #24 fix; #58 #24 follow-up broadcast surface), four issues closed-with-migration to agend-terminal (#52 #53 #54 #8). Adds: - docs/ts-team-sprint-0-2026-04-27-wrap.md — sprint outcome, fix details, process notes, next-sprint pointer - docs/ts-team-backlog.md — non-blocking gaps surfaced during deprecated-mode maintenance. B-001 = session-routing edge case in cost-guard gate (deferred from PR #57 review) No production code changes.
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #55.
Summary
Before this change every backend received the same fleet context (identity, role, workflow, decisions, custom prompt) twice: once via the backend's native injection (
--append-system-prompt-filefor Claude/OpenCode, workspace project doc for Gemini/Codex/Kiro) and once via the MCPinstructionscapability emitted bymcp-server.ts. Wastes tokens, dilutes attention. Issue #55 originally scoped this to Gemini, triage extended it to all backends — same pattern everywhere.Approach
A single declarative flag on each backend says how it natively delivers fleet instructions; the daemon uses it to decide whether the MCP
instructionscapability still fires.CliBackendgainsnativeInstructionsMechanism: 'append-flag' | 'project-doc' | 'none'.claude-code,opencode→'append-flag'(file outside workspace, loaded via flag/config).gemini-cli,codex,kiro-cli→'project-doc'(workspace markdown).mock→'none'.Daemon.buildBackendConfig: when the flag is non-none, drop the fleet-context env vars (AGEND_DISPLAY_NAME,AGEND_DESCRIPTION,AGEND_WORKFLOW,AGEND_CUSTOM_PROMPT,AGEND_DECISIONS) from the MCP server's env, setAGEND_DISABLE_MCP_INSTRUCTIONS=1.mcp-server.ts: whenAGEND_DISABLE_MCP_INSTRUCTIONS=1, omit theinstructionscapability entirely. Fallback path (capability active) preserved for'none'backends.writeConfig()always receives the assembled instructions string — fleet context still reaches the model, through one channel instead of two.Claude is included even though its native channel is a private instance-dir file (not a workspace doc): pre-fix it still received the same content twice (via
--append-system-prompt-fileAND via the MCPinstructionscapability), so the gate covers it uniformly. The "no workspace artefact" property only changed the symptom (no surprisingCLAUDE.mdon disk), not the duplication.AGEND_DECISIONSwas the second silent dual-injection source: passed both to the daemon-sidebuildFleetInstructions()(embedded into the project doc) and re-passed through mcpEnv for the MCP server to rebuild instructions. Now gated together.Risks considered
writeConfig()paths are idempotent (appendWithMarkerfor project-doc backends, plain overwrite for the others). MCP server is respawned on every CLI launch, so capability shape updates automatically. No migration code needed.instructionsReloadedOnResumeandprev-instructionsforce-new-session logic are unaffected — this changes the delivery channel, not the instructions text. Existing sessions continue with the same content.agent_mode: 'cli'): does not spawn an MCP server, so the gate has no effect there. Verified the branch leavesisCliModepaths untouched.instructionssurface behaviour: not relied upon — every real backend already injects natively.docs/fleet-instructions-injection.mddocuments how to verify a new backend before promoting it to'none'.Test plan
npx tsc --noEmit— cleannpx vitest run— 62 files, 517 tests pass (no regressions)tests/backend/native-instructions-mechanism.test.ts— flag value asserted for all 6 backendstests/daemon-build-backend-config.test.ts—buildBackendConfigexercised against all 6 backends, asserts:AGEND_INSTANCE_NAME,AGEND_WORKING_DIR,AGEND_SOCKET_PATH) always presentcfg.instructionsalways populated (so native injection still works)'none'backend → all fleet context env vars present, noAGEND_DISABLE_MCP_INSTRUCTIONS'append-flag'/'project-doc'backends →AGEND_DISABLE_MCP_INSTRUCTIONS=1, no fleet context env varsFiles
src/backend/types.ts— interface field + JSDocsrc/backend/{claude-code,opencode,gemini-cli,codex,kiro,mock}.ts— flag valuesrc/daemon.ts—buildBackendConfiggatesrc/channel/mcp-server.ts— capability gatetests/backend/native-instructions-mechanism.test.ts— newtests/daemon-build-backend-config.test.ts— newdocs/fleet-instructions-injection.md— new🤖 Generated with Claude Code