Agentseam
Use agentseam without chock
Install agentseam, write a handler, wire it into your agents' hooks and see it refuse, from a recorded run.
A handler is a Python function that takes an Event and returns a Decision. Agentseam wires the command that runs it into each agent's hook config. Every output on this page is from a real run, recorded with the agentseam release this site documents.
Install
Agentseam is one Python package with no runtime dependencies and one console script, agentseam. Installing chock installs it too, at the commit below. Agentseam's README installs it from PyPI with pip install agentseam. A PyPI release can differ from this commit. To get the code these pages document, install from the commit (ask the user before running it):
pip install "agentseam @ git+https://github.com/open-coder-ai/agentseam@17dd700eb0b1f8af9c6bfd42bd9438fd5e4445cb"
It needs Python >=3.9.
The files
The run below starts in an empty git repository holding these files. my_handler.py is the handler from agentseam's README Quick start, word for word. The site's tests check it against agentseam's docs/quickstart.sh at the pinned commit. The two payloads take the shape of the Claude Code PreToolUse example in agentseam's examples/generated/claude_code.md, with their content changed. examples/generated/claude_code.md
my_handler.py
The handler. run(handler) reads one payload from stdin, identifies the agent that sent it (best effort, with adapters.detect), calls the handler with the normalized Event, and prints the reply in that agent's dialect.
from agentseam import run, Decision
def handler(event):
if event.event == "pre_tool" and "AKIA" in (event.content or ""):
return Decision.deny("no AWS keys in memory files")
return Decision.allow()
run(handler)
deny.json
A Claude Code PreToolUse payload: the agent wants to write a line with AKIA in it to AGENTS.md.
{"cwd": "/repo", "hook_event_name": "PreToolUse", "permission_mode": "default", "session_id": "example", "tool_input": {"content": "aws_access_key_id = AKIA-PLACEHOLDER", "file_path": "AGENTS.md"}, "tool_name": "Write", "transcript_path": "/repo/.claude/transcript.jsonl"}
allow.json
The same payload with harmless content.
{"cwd": "/repo", "hook_event_name": "PreToolUse", "permission_mode": "default", "session_id": "example", "tool_input": {"content": "Use ruff for lint.", "file_path": "AGENTS.md"}, "tool_name": "Write", "transcript_path": "/repo/.claude/transcript.jsonl"}
in_process.py
The same check called in process with handle(), which returns the reply instead of printing it.
import json
from agentseam import Decision, handle
def handler(event):
if event.event == "pre_tool" and "AKIA" in (event.content or ""):
return Decision.deny("no AWS keys in memory files")
return Decision.allow()
with open("deny.json", encoding="utf-8") as payload:
raw = json.load(payload)
text, code, event, decision = handle(raw, handler, "claude_code")
print("event:", event.agent, event.event, event.tool, event.path)
print("decision:", decision.outcome, decision.reason)
print("exit code:", code)
print("printed:", text)
The recorded run
Real runs, recorded by python -m sitegen.agentseamdoc run --record with agentseam at the pinned commit. The steps of data/agentseam-run.yaml ran in order, offline, in one fresh git repository at /work/demo (made with git init -b main), with HOME at /home/demo. Those paths stand for the throwaway folders the runs used. Output is stdout and stderr together. CI re-runs the steps and fails when any output or exit code differs.
Step 1 of 9, exit code 0. The version of the installed agentseam.
$ agentseam --version
0.4.1
Step 2 of 9, exit code 0. Every agent in agentseam's matrix: its id, tier and hook config file. install takes these ids, or all.
$ agentseam agents
aider none (no hook config)
amp unadapted (no hook config)
antigravity block+rewrite .agents/hooks.json
claude_code block+rewrite .claude/settings.json
cline block .clinerules/hooks
codex_cli block+rewrite .codex/hooks.json
copilot unadapted (no hook config)
cursor block+rewrite .cursor/hooks.json
deepseek_harness unadapted (no hook config)
devin block+rewrite .devin/hooks.v1.json
factory_droid block .factory/hooks.json
gemini_cli block+rewrite .gemini/settings.json
goose block .agents/plugins/agentseam/hooks/hooks.json
grok block .grok/hooks/agentseam.json
junie block+rewrite ~/.junie/config.json
kilo_code unadapted (no hook config)
kimi_code block ~/.kimi-code/config.toml
kiro unadapted (no hook config)
mistral_vibe block+rewrite .vibe/hooks.toml
opencode unadapted (no hook config)
openhands block .openhands/hooks.json
replit unadapted (no hook config)
roo_code unadapted (no hook config)
tabnine block+rewrite .tabnine/agent/settings.json
vscode_copilot block+rewrite .github/hooks/*.json
windsurf block .devin/hooks.json
zed none (no hook config)
Step 3 of 9, exit code 0. The README's install line. It wires python3 my_handler.py at pre_tool into each agent that has a hook adapter, and prints each agent's grade there. Junie's and Kimi Code's config files sit under HOME.
$ agentseam install all 'python3 my_handler.py' --events pre_tool --repo .
wired antigravity -> .agents/hooks.json [pre_tool=best-effort]
wired claude_code -> .claude/settings.json [pre_tool=best-effort]
wired cline -> .clinerules/hooks [pre_tool=best-effort]
not live until trusted: in the VS Code extension, tick Enable Hooks under Cline's Feature settings; hooks run on macOS and Linux only
wired codex_cli -> .codex/hooks.json [pre_tool=best-effort]
not live until trusted: run /hooks in Codex and trust the new hook (trust is per hook hash: re-trust after any change)
wired cursor -> .cursor/hooks.json [pre_tool=enforceable]
wired devin -> .devin/hooks.v1.json [pre_tool=best-effort]
wired factory_droid -> .factory/hooks.json [pre_tool=best-effort]
wired gemini_cli -> .gemini/settings.json [pre_tool=best-effort]
wired goose -> .agents/plugins/agentseam/hooks/hooks.json [pre_tool=best-effort]
wired grok -> .grok/hooks/agentseam.json [pre_tool=best-effort]
not live until trusted: trust it in the agent
wired junie -> /home/demo/.junie/config.json [pre_tool=best-effort]
wired kimi_code -> /home/demo/.kimi-code/config.toml [pre_tool=best-effort]
wired mistral_vibe -> .vibe/hooks.toml [pre_tool=best-effort]
not live until trusted: trust it in the agent
wired openhands -> .openhands/hooks.json [pre_tool=best-effort]
wired tabnine -> .tabnine/agent/settings.json [pre_tool=best-effort]
wired vscode_copilot -> .github/hooks/agentseam.json [pre_tool=best-effort]
wired windsurf -> .devin/hooks.json [pre_tool=best-effort]
Step 4 of 9, exit code 0. What it wrote for Claude Code. Agentseam marks each entry _agentseam, so uninstall removes only its own entries.
$ cat .claude/settings.json
{
"hooks": {
"PreToolUse": [
{
"hooks": [
{
"type": "command",
"command": "python3 my_handler.py claude_code",
"_agentseam": "agentseam"
}
],
"_agentseam": "agentseam"
}
]
}
}
Step 5 of 9, exit code 0. The hook as Claude Code runs it, fed the payload with AKIA. The handler returned Decision.deny, and agentseam printed the refusal in Claude Code's PreToolUse dialect.
$ python3 my_handler.py < deny.json
{"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "no AWS keys in memory files"}}
Step 6 of 9, exit code 0. The harmless payload. The handler returned Decision.allow(), and agentseam printed nothing: the exit code carries the answer.
$ python3 my_handler.py < allow.json
Step 7 of 9, exit code 0. In process, handle() returns the normalized Event, the Decision, the exit code and the text run() would print.
$ python3 in_process.py
event: claude_code pre_tool Write AGENTS.md
decision: deny no AWS keys in memory files
exit code: 0
printed: {"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "no AWS keys in memory files"}}
Step 8 of 9, exit code 0. uninstall all removes agentseam's entries from every agent's config.
$ agentseam uninstall all --repo .
antigravity removed
claude_code removed
cline removed
codex_cli removed
cursor removed
devin removed
factory_droid removed
gemini_cli removed
goose removed
grok removed
junie removed
kimi_code removed
mistral_vibe removed
openhands removed
tabnine removed
vscode_copilot removed
windsurf removed
Step 9 of 9, exit code 0. Claude Code's settings file after uninstall.
$ cat .claude/settings.json
{}
Built from engine 5a4b6287a46e and catalog 1e60d211c1bb.