ID@agntn/harnessesv0.4.2
Thirteen agents. One map.
Where each coding agent keeps its config, sessions, instructions, skills and hooks, per platform, with an evidence level on every path. Detect the one you're inside, run a prompt through another, keep every MCP list and AGENTS.md in sync from one file.
- Harnesses
- 13
- Linux · macOS · Windows
- Paths
- 208
- each with its evidence
- Headless
- 11 of 13
- invoke() spawns them
$ pnpm add @agntn/harnessesAnthropic Claude Code
Headless -p mode, an advisor without tools, JSONL transcripts per project.
Map
- config~/.claude/settings.json+5
- sessions~/.claude/projects/<dash-encoded-cwd>/*.jsonl+1
- instructionsCLAUDE.md+2
- skills.claude/skills/+1
- commands.claude/commands/+1
- promptTemplates.claude/commands/+1
- hooks.claude/hooks/+1
- temp/tmp/claude-<uid>/
- Binary
- claude
- Invoke
- 6 of 6 modes
- Models
- no listing
- MCP
- json · standard
- Agents file
- ~/.claude/CLAUDE.md
Every path, with its evidence
getHarness("codex") is one object with config, sessions, instructions, skills, commands, prompt templates and hooks. Nothing is fetched or scanned; it's a table someone checked against the CLI's own docs, source, or a local probe, and wrote the level down. inferred means exactly that. This panel walks through 13 harnesses, values expanded the way the library expands them.
- Each entry carries scope (user, project, system, data) and level (official, community, inferred)
- resolve({ platform, homeDir, projectRoot }) expands ~, ${HOME} and %VAR% and drops paths for other platforms
- A note where the path needs one: dash-encoded cwd, XDG on every platform, deprecated location
Resolve
import { getHarness } from "@agntn/harnesses";// Anthropic Claude Code, binary claudeconst harness = getHarness("claude");const paths = harness.resolve({ platform: "linux", homeDir: "/home/dev" });paths.config[0]?.path; // "/home/dev/.claude/settings.json"paths.sessions[0]?.path; // "/home/dev/.claude/projects/<dash-encoded-cwd>/*.jsonl"paths.instructions[0]?.path; // "CLAUDE.md"paths.skills[0]?.path; // ".claude/skills/"paths.hooks[0]?.path; // ".claude/hooks/"harness.detection.envVars; // ["CLAUDE_CODE","CLAUDECODE"]Six modes, none of them a fallback
invoke(prompt, options) expands one argument template per mode and spawns the binary. A harness whose CLI cannot switch tools off rejects advisor mode instead of pretending. The panel shows what would run for the current harness, and the error you get for a mode it doesn't have.
- tools defaults to false: an advisor with tools removed by a native flag, not by prompt wording
- readOnly needs a sandbox the CLI enforces itself. No recipe, no run, and an error that says which retry would work
- timeoutMs and signal stop the whole process group; stopped results keep their output
Modes
- advisor
- advisor · json
- read-only
- read-only · json
- agent
- agent · json
Spawned
claude -p 'Review this patch' --append-system-prompt 'This run has no tools: nothing can be called, run or read, and no tool output will come back. Never write a tool call as text, such as an <invoke> or <function_calls> block. Answer from reasoning alone, and mark any value you did not derive in the answer itself as unverified.' --strict-mcp-config --tools '' --output-format stream-json --verbose --include-partial-messagesRejected
Noneevery mode has a recipe
One list, every dialect
~/.config/agntn/mcp.jsonc is the only place a server is declared. ~ and ${HOME} expand at sync time because harnesses spawn MCP servers without a shell, and a tilde in a config is a server that silently never starts. TOML files get a surgical edit that keeps their comments. JSON files are rewritten.
- listMcpServers reads JSON and TOML, standard, OpenCode, VS Code and Antigravity shapes, into one McpServerConfig
- sync makes each user scope config exactly the master list; extras are removed, not merged around
- excludes keeps a harness's own servers and pulls master names out of it, unless a server opts back in with its own
{ "excludes": ["codex"], "mcpServers": { "harnesses": { "command": "npx", "args": [/* … */] } }}Targets
Thirteen harnesses, three platforms
Each page lists the binaries, capabilities, invocation templates, every path with its scope and level, the MCP config dialect and what the harness reads from other harnesses' folders. Cursor and Copilot scan .claude/skills/, Grok loads CLAUDE.md. That's written down too, because it decides where your file has to live.
- Linux, macOS and Windows: Linux, macOS, Windows paths tagged where they differ
- Detection by environment variable first, then by project markers; two matches is null, not a guess
- Audio and video mean a verified native route into model context, not a conversion or an MCP tool
Eleven tools, three hosts
harnesses mcp serves the tools over stdio, the Pi and OMP extensions render the same executors in the terminal. So Claude can ask Codex for a second opinion on a patch, read-only, and get the answer back as one tool result. That's what the tools are for; the table of paths alone wouldn't need a server.
- harnesses_detect, harnesses_info, harnesses_models, harnesses_run, and seven for MCP configuration, AGENTS.md, prompts and skills
- harnesses_run makes the model choose tools explicitly; unsupported modes never widen access
- The host's own request signal cancels a run, not a JSON argument the model could forget
Anthropic Claude Code
The agent gets the metadata with every path resolved for the machine it runs on.
- binaries
- claude
- invocationModes
- 6 of 6 true
- capabilities
- mcp, vision, tools, streaming
- modelListing
- false
- detection
- env CLAUDE_CODE, then 4 markers
Extend Harness, call registerHarness
Every built-in is a concrete class extending the exported abstract Harness. Yours is the same shape, one file. Put level: "inferred" on a path you haven't checked and change it when you have. The type won't let you skip the field, which is the point.
- id, name, binaries, the six path groups, capabilities, detection and invocation. Same fields the built-ins fill
- registerHarness(Class) makes it visible to getHarness, detectHarness and the CLI in your process
- invocation: null is honest for a CLI without a headless mode. invoke() then throws before spawning
import { Harness, registerHarness, type HarnessId } from "@agntn/harnesses";class Aider extends Harness { readonly id = "aider" as HarnessId; readonly config = [ { path: "~/.aider.conf.yml", scope: "user", level: "official" }, ]; /* name, binaries, capabilities, sessions, detection, … */ readonly invocation = { /* args, modelArgs */ level: "inferred" };}registerHarness(Aider);Start with one command
One install gives you the library, the harnesses CLI and the MCP server. Ask it where an agent keeps something, then go and look.
- PinPre-1.0, so pin exact versions.
- EvidenceA path marked inferred is a guess someone wrote down. Check it before you rely on it.
- LocalNothing is fetched. Detection reads env vars and files, invoke spawns a CLI you installed.
First lookup
$ pnpm add @agntn/harnessesimport { detectHarness, getHarness } from "@agntn/harnesses";detectHarness()?.id; // "claude" inside Claude Code, else undefinedconst claude = getHarness("claude");claude.config[0]?.path; // "~/.claude/settings.json", as the registry stores itclaude.invocationModes; // 6 of 6 true