Files
sherlock/docs/agent-profiles.md
T
amacocian 801bbcec09
CI / build (push) Has been cancelled
Phase 0
2026-05-24 21:53:20 +02:00

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 least copilot.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

  1. internal/agent/profiles/ (embedded built-ins) — provides defaults.
  2. ~/.config/sherlock/agents.d/ — operator overrides; same agent.name shadows 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)

  1. Parse sherlock <subcommand> .... Resolve the agent profile by name.
  2. Ensure the broker is up; ensure the operator is logged in (whoami succeeds).
  3. For each service registered under services.d/: ask the broker for a per-service token and collect { env_var: token } pairs.
  4. Render an MCP config file in $XDG_RUNTIME_DIR/sherlock/<agent>.mcp.json listing each MCP's command, args, and the env block populated with the tokens from step 3.
  5. Build argv from the profile (prefix + [mcp-flag pair if applicable] + user args + suffix).
  6. Build env from the operator's env + [env] + strip [forbid].env.
  7. exec the 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.