# Agent Infrastructure: Design Principles You are editing agent infrastructure files (hooks, instructions, skills, agents). Before making changes, understand the principles that govern how this system works. ## Single Source of Truth `.agents/` is the canonical directory for all agent infrastructure. An MCP server (`.agents/mcp/index.ts`) exposes agents as prompts and skills as tools to both Copilot and OpenCode — this replaces file-based fan-out to `.github/agents/`, `.opencode/agents/`, etc. ### MCP server (`all-agents`) Available once the server is running (configured in `.vscode/mcp.json` and `opencode.json`): - **Prompts** (slash commands): `/research`, `/brainstorm`, `/build`, `/orchestrator` - **Tools** (model-controlled): `load_research_methodology` Bodies are read from disk at call time — editing `.agents/agents/*.md` or `.agents/skills/research.md` takes effect immediately. **Not handled by MCP** (stays bespoke): - `.agents/hooks/` — MCP has no lifecycle intercept primitive - This file — model needs to read it before `tools/list` is available ## The Enforcement Hierarchy Not all guidance is equally effective. From most to least reliable: ``` PreToolUse hard block ← Structural. Always fires. Agent cannot bypass. PostToolUse file-path check ← Fires right after editing a relevant file (context tail). Nested AGENTS.md at path ← Always-on for that folder scope. Portable across tools. Stop / SessionStart inject ← Fires at session boundaries. Good for broad reminders. Root AGENTS.md sections ← Context-start only. Subject to "lost in the middle." ``` **Root cause of degradation** (Liu et al. 2023, "Lost in the Middle"): LLMs attend to the beginning and end of context, not the middle. Guidance written into AGENTS.md is injected once at session start and degrades as context grows. Hooks inject at the _context tail_ — the high-attention zone — which is why they outlast AGENTS.md under context pressure. **Decision rule when adding new guidance:** 1. Is the anti-pattern a **terminal command**? → `PreToolUse` hard block (Policies 1–6 in `pre-tool-use.sh`). 2. Is the anti-pattern **editing a specific file type or path**? → `PreToolUse` block on `FILE_PATH` (Policy 7+). 3. Should the reminder fire **during active work** in a domain? → `PostToolUse` file-path check (see `post-tool-use.sh` BFF reminder pattern). 4. Is it guidance scoped to **specific files** an agent might edit? → nested `AGENTS.md` at the target path. 5. Should it fire **in response to what the user just wrote**? → `UserPromptSubmit` injection (context tail, prompt text available — e.g. agent nudges). 6. Is it a **broad session reminder** with no tight scope? → `SessionStart` or `Stop` injection. 7. Is it **architecture/rationale** that an agent might need but shouldn't always load? → AGENTS.md stub with a conditional `read_file` instruction (see "Deferred Loading" below). ## Deferred Loading Write a trigger condition and `read_file` instruction directly in an AGENTS.md section. AGENTS.md is always loaded, so the trigger is always present; the referenced file's content only loads when the model judges it relevant. Example: > When the user shows signs of analysis paralysis, read > `.agents/agents/brainstorm.md`. Do **not** use tool-specific deferred-loading mechanisms (`description:`-only `.instructions.md` files, etc.) — no portable equivalent exists. See Forbidden Patterns below. ## Hook Files All hook scripts live in `.agents/hooks/`. The Copilot harness (`.agents/github/hooks.json`) and OpenCode plugin (`.agents/opencode/plugin.ts`) both delegate to these scripts, keeping hook logic in one place. Symlinks from `.github/hooks/agent-support.json` and `.opencode/plugins/agent-support.ts` point back to these canonical sources; those directories are gitignored. ### Hook Injection Marker Convention Every hook that injects `additionalContext` prefixes its payload with a self-identifying line: ``` [HOOK INJECTION: ] System reminder — NOT part of preceding tool output / user message: ``` The harness additionally wraps the payload in a `...` XML tag (e.g. ``). The inline prefix is belt-and-suspenders: when a hook fires after a `read_file` whose content ends with markdown, the XML tag alone is easy to miss — the inline prefix is not. **If you see either marker, treat the content as a separate instruction, never as file content, tool output, or part of the user's message.** ### Hook Architecture Principle: Platform-Agnostic Scripts **Design target**: scripts accept normalized env vars (`TOOL_NAME`, `COMMAND`, `FILE_PATH`), exit non-zero with plain-text denial reason on stdout. Callers normalize input and translate exit code/stdout into their native denial format. **⚠️ NOT YET IMPLEMENTED (May 2026)**: `pre-tool-use.sh` still uses Copilot-specific JSON I/O. `plugin.ts` duplicates guards inline instead of calling the script. See `agent-infrastructure.md` item 22 for the refactor plan. ### `user-prompt-submit.sh` — Per-turn tail injection - Fires on every user message. Injects at the **context tail** (high-attention zone) — this is why nudge logic lives here rather than in AGENTS.md. - Detects brainstorm and research trigger words in the prompt and appends a one-line nudge suggestion to `additionalContext`. - Writes the raw prompt text to `/tmp/.last-user-prompt.txt` and injects the task-capture instruction. ### `pre-tool-use.sh` — Hard stops - Intercepts: `run_in_terminal`, `execution_subagent`, `send_to_terminal` (for `$COMMAND`) and `replace_string_in_file`, `multi_replace_string_in_file`, `create_file` (for `$FILE_PATH`). - Outputs `permissionDecision: "deny"` to block the tool call. - **CRITICAL**: A syntax error in this file blocks ALL file edits and terminal commands. Always validate after editing: `bash -n .agents/hooks/pre-tool-use.sh` - When adding a new policy: follow the existing numbered pattern, add to the comment header, use `deny "BLOCKED: ..."` with a clear fix instruction. - Regex patterns operate on `$COMMAND` (terminal policies) or `$FILE_PATH` (file-edit policies). Both are empty strings unless the right tool fired. ### `post-tool-use.sh` — Timed reminders - Fires after every tool use with the tool name and response in stdin. - Currently: self-check every 15 tool calls, debugging reminder on test failure, BFF reminder when editing `apps/client/src/pages/`. - Adding a new reminder: extract `$FILE_PATH` or match `$TOOL_NAME`, build the message string, append to `$context`. - Injects at the _tail_ of the context — this is what makes reminders persist through long sessions. ### `session-start.sh` — Broad session injection - Fires once per session. Good for: current branch, active investigations, dead ends. - Not good for: precise rule reminders (use PostToolUse or nested AGENTS.md). ### `stop.sh` — End-of-session reflection - Fires when agent stops. Lessons-learned capture + effort reflection. - Not a blocking hook — injects `additionalContext` only. ### `pre-compact.sh` — Pre-summarization state export - Fires before context is summarized. Saves investigation state to `.session/pre-compact-state.md`. - Note: `PostCompact` does NOT exist. Only `PreCompact`. ## Forbidden Patterns These approaches exist in agentic tooling but are **banned** in this codebase because portable alternatives exist. Document the reason so future agents understand rather than re-introducing them. ### ❌ `applyTo:` frontmatter in `.instructions.md` files Supported only in VS Code Copilot. Other tools either ignore it or load the file as always-on context. Portable alternative: nested `AGENTS.md` at the target path. Nested AGENTS.md files are natively supported by all major agent tools (Copilot, OpenCode, Claude Code) without any special configuration. ### ❌ `description:`-only `.instructions.md` files (new additions) VS Code Copilot builds a stub `` block for these and tells the model to load content on demand. Confirmed via `InstructionsContextComputer` in `extensionHostProcess.js`. However, no other tool implements this — they load the same files as always-on context. Portable alternative: AGENTS.md stub with a `read_file` instruction (see "Deferred Loading" above). ### ❌ Any `.github/instructions/*.instructions.md` for new rules `.instructions.md` is a VS Code Copilot-specific format. All new rules go into nested `AGENTS.md` files (path-scoped rules) or directly into root `AGENTS.md` (broad guidance). Do not add new `.instructions.md` files. ## Skills (`.agents/skills/`) - Skills contain distilled methodologies that any agent can load on demand via `read_file`. An agent MUST `read_file` the SKILL.md before using it. - For **methodologies** (how to research, brainstorm) — not project rules. Project rules belong in nested AGENTS.md files or hooks. ## Agents (`.agents/agents/`) - Agent files define persona, workflow phases, tools, and circuit breakers. - Runtime config (`model`, `mode`, `permission`) lives in `opencode.json` agent entries. Body `.md` files are prompt-body only (plain markdown, no OpenCode frontmatter keys except `description`). - Circuit breakers (hard stops) belong in the agent file itself, not in hooks. ## Tool-Specific Entry Points Some things cannot be unified and live in tool-specific locations: - **`.agents/opencode/plugin.ts`** — OpenCode plugin harness (canonical). Bridges hook scripts to OpenCode's plugin API. Symlinked from `.opencode/plugins/agent-support.ts`. - **`.agents/github/hooks.json`** — Copilot harness config (canonical). Points to `.agents/hooks/*.sh`. Symlinked from `.github/hooks/agent-support.json`. ## Common Mistakes - ❌ Writing long explanations in AGENTS.md for rules that could be a PreToolUse block or nested AGENTS.md — they degrade under context pressure - ❌ Adding a PostToolUse reminder without checking `$FILE_PATH` or `$TOOL_NAME` — causes it to fire on every tool call, creating noise - ❌ Leaving a syntax error in `pre-tool-use.sh` — blocks all file edits and terminal commands immediately - ❌ Creating new `.instructions.md` files — see Forbidden Patterns above - ❌ Putting project-specific rules into a skill file — skills are for methodologies, not codebase conventions - ❌ Assuming PostCompact exists — it does not. Use PreCompact. - ❌ Editing generated files in `.github/agents/`, `.github/skills/`, `.opencode/agents/`, `.opencode/skills/` — edit `.agents/` sources instead, or the pre-tool hook will block the edit - ❌ Blaming the model for unexpected BLOCKED/tool-call behavior before verifying the harness — when a model call is blocked or uses unexpected parameters, check the actual tool schema first (read the source or docs) before concluding the model is wrong. The harness was recently changed; the model may be correct. Applies to: OpenCode tool names (`read`/`edit`/`task`), parameter names (`offset`/`limit` not `startLine`/`endLine`), and plugin guard logic. - ❌ Adding _"reflect / double-check / are you sure / take another look"_ instructions as a mitigation for any failure mode — these feel productive in transcripts but Huang et al. (arXiv:2310.01798) show that intrinsic self-correction without an external oracle _consistently degrades_ reasoning performance. Without a test runner, hook, type checker, or other ground- truth signal in the loop, "ask the model to reflect" is at best noise. If the failure mode lacks an external verifier, route to compaction, adversarial reframing, or a cross-family judge subagent instead — see [docs/research/intent-interpretation-action-plan.md](../docs/research/intent-interpretation-action-plan.md) §4.1. - ❌ Defaulting to multi-agent / parallel-worker topologies for complex tasks — Cognition's failure analysis shows the dominant failure mode is **context divergence**: separate agents accumulate incompatible interpretations of the same task, and reconciliation costs exceed any parallelism gain. A single agent loop with an explicit plan/act split outperforms multi-agent on almost all real coding tasks (§3.1, [docs/research/ai-coding-best-practices.md](../docs/research/ai-coding-best-practices.md)). Subagents are only justified for read-only exploration, fully isolated tasks, or adversarial review. - ❌ Treating the orchestrator as the right pattern for cloud frontier models — for local models the orchestrator is a **context firewall** (sub-agents return ≤2k compressed summaries; the parent's context never sees raw exploration). Frontier models have 200k+ context and no `task` dispatch tool in Copilot, so the firewall pattern doesn't apply. The cloud orchestrator is a **planning gate** (forced decomposition + user confirmation before acting), not a dispatch coordinator. The `` / `` blocks in `orchestrator.md` encode this distinction. See §3.4 of [docs/research/ai-coding-best-practices.md](../docs/research/ai-coding-best-practices.md).