Wrap Claude Code in a deterministic shell.
Pre-action guards, post-action checks, and agent tools β declared in TypeScript, enforced by hooks, observable by default.
π Read the documentation β
The three core harness primitives β guards (what it can't do), checks (what it must verify), tools (what the agent can call) β written in TypeScript and wired into Claude Code's hooks.
New to harness engineering? See Anthropic or Fowler.
npm install -D agent-harness-sdk
npx harness initRestart Claude Code from the project directory. You now have:
my-project/
βββ harness/
β βββ harness.config.ts β declarative config: which guards/checks/tools are active
β βββ harness.lock β synced-content manifest (git-tracked; updated by /harness update)
β βββ guards/ β PreToolUse filters
β βββ checks/ β PostToolUse validators
β βββ tools/ β MCP tools the agent can call
βββ .claude/
β βββ settings.json β PreToolUse + PostToolUse + SessionStart hooks
β βββ rules/harness.md β universal conventions + authoring contracts (synced)
β βββ commands/harness.md β /harness slash command (synced)
βββ .mcp.json β MCP server registration
βββ .harness/ β gitignored audit log + evolve state
The harness starts locked β it guards its own files from the agent. To let the agent scaffold or edit harness primitives, unlock it first:
npx harness security 0Scaffold a harness primitive β from inside Claude Code, describe what you want in plain language:
/harness add guard to block imports from internal/ outside its module
/harness add check that changed components have a test
/harness add tool to run typecheck and return the errors
The agent names it, scaffolds the typed stub via the CLI, registers it in harness.config.ts, and writes a first implementation with you β asking for specifics if your description needs them.
Audit the harness:
/harness evolve
Audits your codebase and harness side by side. Surfaces tiered suggestions: patterns worth enforcing, dead components to remove, drift to fix, and architectural smells worth a human look. Read-only β nothing scaffolds without your approval.
The harness manages three primitives directly β declared in harness.config.ts, enforced at runtime by hooks and the MCP server:
| Primitive | What it is | Where it lives | Enforced by |
|---|---|---|---|
| Guard | Pre-action filter β vetoes a tool call before it runs | harness/guards/<name>.ts |
PreToolUse hook |
| Check | Post-action validator β fails with feedback after a tool runs | harness/checks/<name>.ts |
PostToolUse hook |
| Tool | Deterministic MCP operation the agent calls | harness/tools/<name>.ts |
MCP server (auto-instrumented) |
All registered in one place β harness/harness.config.ts:
import { defineHarness, protectEnvFiles } from "agent-harness-sdk";
import { myGuard } from "./guards/my-guard";
import { myCheck } from "./checks/my-check";
import myTool from "./tools/my-tool";
export default defineHarness({
guards: [protectEnvFiles, myGuard],
checks: [myCheck],
tools: [myTool],
});The harness protects its own enforcement surface β harness/, the hook wiring, and its .env.agents unlock file β from the agent. A guard the agent can quietly delete isn't much of a guard, so new harnesses are locked by default.
When you need to work on the harness, unlock it (human-only β the agent can't change its own level):
npx harness security 0 # unlock (off)
npx harness security 1 # re-lock β the in-process guard (default)Four levels trade convenience for strength: 0 off Β· 1 in-process guard (default) Β· 2 OS sandbox Β· 3 external file hardening. Levels 2β3 make the surface unwritable at the kernel level for long-running / autonomous agents (macOS or Linux).
See the Security guide for the full model β including harness security audit, a red-team check that probes whether your level is actually enforcing.
Every guard fire, check run, and tool call auto-emits a JSONL line to .harness/log.jsonl (gitignored):
{
"ts": "2026-05-10T12:34:57Z",
"event": "pre-tool-use.denied",
"tool_name": "Edit",
"file_path": ".env",
"denied": [
{
"name": "protect-env-files",
"reason": "..."
}
]
}Ask Claude "what has the harness been doing this week?" β it'll call the bundled harness_status tool to aggregate the log.
Env vars (shell or .env):
HARNESS_LOG_DISABLED=1β turn off loggingHARNESS_LOG_PATH=/custom/pathβ redirect output