# Agents How sherlock decides which CLI to spawn when you type `sherlock copilot` or `sherlock claude`, and how to add a new one. ## Supported today | Name | Binary | MCP config flag | Notes | |---|---|---|---| | `copilot` | `copilot` (npm `@github/copilot`) | `--additional-mcp-config @` | Augments user's `~/.copilot/mcp-config.json`. JSON shape is the canonical `.mcp.json` schema (`{"mcpServers": ...}`). | | `claude` | `claude` (npm `@anthropic-ai/claude-code`) | `--mcp-config ` | Same `{"mcpServers": ...}` shape. `ANTHROPIC_API_KEY` is stripped from the child env so a personal key can't override the sherlock-managed session. | ## Routing ``` sherlock copilot [args...] ⇢ runs copilot sherlock claude [args...] ⇢ runs claude sherlock run copilot [args...] ⇢ same, explicit form sherlock run ... ⇢ exit 2 with "unknown agent" sherlock ⇢ exit 2 with "unknown subcommand" ``` The `run` form exists for parity with `cargo run` / `npm run`; the bare alias is the daily-driver form. ## What sherlock does per spawn 1. `keyring.Open()` — fail fast if the OS keyring isn't available (returns `*UnavailableError` with a remediation `Hint` field). 2. Resolve the agent binary on `$PATH`. Friendly error if missing. 3. Render the per-agent MCP config to `$XDG_RUNTIME_DIR/sherlock/.mcp.json` (0600). In Phase 1 the servers map is always empty; Phase 2 populates it from `services.d/`. 4. Build the child argv with the agent-specific flag. 5. Build the child env: parent env minus per-agent forbids. MCPs spawned by the agent will reach into the OS keyring (via `internal/authn.Ensure`) on their own at startup — sherlock does not pre-authenticate anything. 6. `syscall.Exec` — sherlock disappears, the agent takes its place. ## Adding a new agent It's a code change, deliberately. The TOML-overlay design was tried and scrapped: each CLI has enough idiosyncrasies (auth subcommands, permission flags, MCP config schema, env var quirks) that a Go file per agent is honest about the surface area and gives those quirks a real place to live. Drop a new file in `internal/agent/`: ```go // internal/agent/aider.go package agent import "gitea.alexandru.macocian.me/Charlie/sherlock/internal/mcp" func init() { Register(&aider{}) } type aider struct{} func (aider) Name() string { return "aider" } func (aider) Description() string { return "Aider AI pair programmer" } func (a aider) Spawn(ctx Context, args []string) error { bin, err := LookPath("aider") if err != nil { return err } // Aider's MCP schema and flag would go here. _ = bin _ = ctx _ = args return nil } ``` That's the whole API: `Name`, `Description`, `Spawn`. The CLI picks it up automatically through the `init()` registry call; `sherlock status` shows it; `sherlock aider ...` dispatches. ## Reusable helpers Available to every agent implementation in this package: | Helper | Purpose | |---|---| | `LookPath(name)` | `exec.LookPath` with a sherlock-friendly error message. | | `BuildEnv(forbid, set)` | parent env minus `forbid`, plus `set`. | | `DefaultExecer` | the package-level `Execer` (swap in tests). | | `mcp.Render(name, servers)` | writes `{"mcpServers": ...}` to `$XDG_RUNTIME_DIR/sherlock/.mcp.json`. | If a new agent needs a third MCP-config schema, add a new `Render*` function to `internal/mcp/` rather than open-coding JSON in the agent file. ## What sherlock does *not* do - Read agent config from `~/.config/sherlock/agents.d/` — that directory does not exist. - Hot-reload registered agents — the registry is sealed at process start, by design (one fewer code path). - Sandbox the agent — sherlock just `exec`s it, the agent inherits the user's full environment minus a few targeted forbids.