4.6 KiB
Agent profiles
How sherlock supports multiple agent CLIs without code changes.
Why this exists
sherlock should not be Copilot-shaped. We want to swap the underlying agent CLI (Copilot, Claude Code, Aider, future tools) without changing sherlock's code, because:
- We may switch agents.
- Different operators may prefer different agents.
- Per-task we may want a specific agent (a smaller model for batch ops, a bigger one for review).
An agent profile is a TOML file describing one agent CLI: how to launch it, how to pass it the per-session MCP config sherlock renders, and what env it needs. Adding a new agent = adding one TOML file. No Go changes.
Location
- Built-in:
internal/agent/profiles/*.toml, embedded via//go:embed. Sherlock ships with at leastcopilot.toml. - User overrides:
~/.config/sherlock/agents.d/*.toml. Same filename as a built-in profile shadows the built-in entirely.
Invocation
sherlock run <agent-name> [args...] # canonical
sherlock copilot [args...] # sugar for `run copilot`
sherlock claude [args...] # sugar for `run claude` once claude.toml exists
The aliases are auto-derived from the set of loaded profiles, so they appear/disappear with agents.d/.
Schema
[agent]
name = "copilot" # unique; matches the file's basename
executable = "copilot" # looked up on PATH unless absolute
description = "GitHub Copilot CLI"
[mcp]
# How the rendered MCP config is delivered to this agent.
mode = "cli-flag" # cli-flag | env-var | config-file
# When mode = "cli-flag": the flag name.
flag = "--mcp-config"
# When mode = "env-var": the env var name (value is the path to the rendered file).
# var = "MCP_CONFIG"
# When mode = "config-file": the absolute path the rendered file is written to.
# path = "~/.config/claude-code/mcp.json"
[env]
# Static env injected into the agent process. Values may reference
# ${HOME}, ${XDG_RUNTIME_DIR}, etc.
# Example:
# GH_TOKEN_KIND = "oauth"
[argv]
# argv built around the agent's positional args:
# <executable> <prefix...> <user-args...> <suffix...>
# When mcp.mode = "cli-flag", the flag + value are appended after prefix.
prefix = []
suffix = []
[forbid]
# Env vars sherlock will strip before exec'ing the agent, even if they exist
# in the operator's shell. Defense against accidental credential leaks.
env = []
Built-in: copilot.toml
[agent]
name = "copilot"
executable = "copilot"
description = "GitHub Copilot CLI"
[mcp]
mode = "cli-flag"
flag = "--mcp-config"
[argv]
prefix = []
suffix = []
[forbid]
env = []
Worked example: Claude Code (operator-supplied, not built-in yet)
[agent]
name = "claude"
executable = "claude"
description = "Anthropic Claude Code"
[mcp]
# Claude Code reads a JSON config file from a stable path.
mode = "config-file"
path = "${XDG_CONFIG_HOME}/claude-code/sherlock.mcp.json"
[argv]
# Claude Code is invoked with the project dir as a positional; users pass
# extra flags after.
prefix = []
suffix = []
[forbid]
# Make sure no stray ANTHROPIC_API_KEY from the shell leaks in if we want
# the broker to be the only source of credentials in future revisions.
env = ["ANTHROPIC_API_KEY"]
Drop the file at ~/.config/sherlock/agents.d/claude.toml and sherlock claude starts working — no rebuild.
Resolution order
internal/agent/profiles/(embedded built-ins) — provides defaults.~/.config/sherlock/agents.d/— operator overrides; sameagent.nameshadows the built-in.
A profile is malformed → it's logged and skipped. Sherlock never silently runs an agent with a half-loaded profile.
What the wrapper does at runtime (Phase 1)
- Parse
sherlock <subcommand> .... Resolve the agent profile by name. - Ensure the broker is up; ensure the operator is logged in (
whoamisucceeds). - For each service registered under
services.d/: ask the broker for a per-service token and collect{ env_var: token }pairs. - Render an MCP config file in
$XDG_RUNTIME_DIR/sherlock/<agent>.mcp.jsonlisting each MCP'scommand,args, and theenvblock populated with the tokens from step 3. - Build argv from the profile (
prefix + [mcp-flag pair if applicable] + user args + suffix). - Build env from the operator's env +
[env]+ strip[forbid].env. execthe agent. No fork: sherlock itself exits, the agent becomes the foreground process.
Non-goals
- We do not translate MCP protocol versions per agent. All supported agents must speak the MCP version sherlock ships with.
- We do not rewrite the agent's own state. Each agent keeps its config / history / login wherever it normally does.