agentctl is a local control plane for coding agents. It gates a small set of risky actions, records a trace for every decision, and can replay prior sessions against a different policy.
go install github.com/chocks/agentctl/cmd/agentctl@latest
agentctl versionagentctl stores all of its state under ~/.agentctl/:
policy.yamltraces.jsonlapprovals.jsonl
There is no repo-local policy file and no HTTP server.
Attach to a supported agent. attach bootstraps ~/.agentctl/ and writes a default policy if one does not exist yet.
agentctl attach claude-code
# or
agentctl attach codexVerify the install:
agentctl doctorLaunch the terminal UI:
agentctl ui| Action | What it covers |
|---|---|
install_package |
pip, npm, cargo, go installs |
run_code |
shell execution, script runs |
access_secret |
reading secrets, tokens, credentials |
write_file |
file creation, overwrites, appends |
call_external_api |
outbound HTTP to external services |
Everything else stays out of the control path.
agentctl attach <agent> Configure agent integration and bootstrap ~/.agentctl/
agentctl detach <agent> Remove agent integration
agentctl doctor Check policy, trace store, approvals, and agent status
agentctl gate Evaluate one action from stdin
agentctl trace list Show recent traces
agentctl trace search Search traces
agentctl trace verify Verify the trace hash-chain
agentctl replay <session_id> Re-evaluate a recorded session
agentctl approval list List approvals
agentctl approval approve <id> Approve a pending escalation
agentctl approval deny <id> Deny a pending escalation
agentctl ui Terminal UI for traces and approvals
agentctl hook claude-code Claude Code PreToolUse hook adapter
agentctl mcp MCP server for Codex and other MCP clients
agentctl loads exactly one policy file: ~/.agentctl/policy.yaml.
- Missing file: built-in safe defaults are used.
- Malformed file:
gate,doctor, andmcpfail loudly. - Hook mode (
agentctl hook claude-code) fails open on malformed policy and writes the error to stderr.
Default policy written by attach:
actions:
install_package:
require_hashes: true
run_code:
block_patterns:
- "| bash"
- "| sh"
- "| python"
network: deny
access_secret:
require_approval: always
max_ttl: 300
write_file:
block_patterns:
- ".env"
- "*.pem"
- "*.key"
call_external_api:
allowed_domains: []allowed_domains: [] means deny all outbound calls. Omitting allowed_domains means no domain restriction.
Record a session under a stable session ID:
echo '{"action":"call_external_api","params":{"url":"https://api.openai.com/v1/responses","method":"POST"},"reason":"call provider"}' \
| agentctl gate --session demo-1Replay that session against the current global policy:
agentctl replay demo-1Or replay against an alternate policy file:
agentctl replay demo-1 --policy ./stricter-policy.yamlEvery decision is written to ~/.agentctl/traces.jsonl as an append-only,
hash-chained record. Each record carries a prev_hash and a hash computed
over its canonical contents, so altering, reordering, or deleting any record
invalidates every hash that follows.
agentctl trace verify # walk the chain, report OK or the exact break
agentctl trace verify --json # machine-readable report
agentctl trace verify --remote fetched-store.jsonlverify reports each chain as OK with its root and head hashes, or BROKEN at seq N with the reason. It also checks sequence continuity and reports any gaps.
Exit code is 0 when verified and complete, 1 otherwise. The hash is SHA-256
over the JSON encoding of the record with its hash field cleared, so the chain
can be re-verified by any independent tool.
make fmt
make build
make test
make lintMIT. See LICENSE.