privacyagent guideagent apiledger specanchoringopentelemetry exportagents supported

Agents and adapters

agent-blackbox records and gates one canonical event stream. Claude Code's hook events (SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, Stop, ...) with Claude Code's tool names (Bash, Read, Write, Edit, WebFetch, mcp__server__tool) are that format. The policy, the vault and the ledger know nothing else.

An adapter (src/adapters/<agent>.js) is the only code that knows one agent's own hook format. It does three things:

MethodDoes
decode(native)turns the agent's hook payload into a canonical event, naming tools the way the policy expects (run_shell_command becomes Bash). Returns null for events nothing is recorded for.
encode(reply, native, opts)turns the recorder's neutral verdict (permission: 'ask' | 'deny' | null, the human's reason, the model's uninformative message, a notice) into what the agent reads on stdout and by exit code.
failClosed(event, reason, native)what to print when the recorder is down and failMode is closed.

capabilities states, per agent, what its hooks can do. It is documentation the code can check, not a promise made on the agent's behalf:

Events recorded for an agent other than Claude Code carry agent (codex, cursor, gemini); Claude Code's carry none, as before.

The hook script picks its adapter with --agent <id> (default claude). Adapters load lazily and use no dependencies.

What each agent can enforce

AgentInstallBlock before a tool runsAsk the humanSees tool resultsNotes
Claude Codeblackbox install (or the plugin)yesyesyes14 lifecycle events, OpenTelemetry too
OpenAI Codex CLIblackbox install --agent codexyes, for Bash, apply_patch and MCP callsno: an ask becomes a blockyessee below
Gemini CLIblackbox install --agent geminiyes, for every toolno: an ask becomes a blockyessee below
Cursorblackbox install --agent cursoryes, for shell and MCP callsyesshell, MCP and file reads (with content)file edits are recorded after the fact; see below

Codex CLI

Hooks live in ~/.codex/hooks.json (or $CODEX_HOME). The adapter registers SessionStart, UserPromptSubmit, PreToolUse, PostToolUse and Stop. Codex's payloads follow Claude Code's, so the adapter only renames what differs: an apply_patch call becomes an Edit of every file in the patch (so the memory-write and hook-tamper rules see all of them), and web_search becomes WebSearch.

Limits to know before relying on it:

Cursor

Hooks live in ~/.cursor/hooks.json, one command per event name. The adapter registers beforeShellExecution, afterShellExecution, beforeMCPExecution, afterMCPExecution, beforeReadFile, afterFileEdit, beforeSubmitPrompt and stop, and maps them to the canonical events (beforeShellExecution is a PreToolUse of Bash, an MCP call is mcp__<server>__<tool>, and so on). Cursor names the MCP tool but not always the server, so the server is taken from the payload's server name, else the URL's host, else the command's name.

Limits to know before relying on it:

Gemini CLI

Hooks live in ~/.gemini/settings.json under hooks. The adapter registers SessionStart, SessionEnd, BeforeAgent (the prompt), AfterAgent, BeforeTool, AfterTool and Notification, and renames the tools: run_shell_command is Bash, read_file and read_many_files are Read, write_file is Write, replace is Edit, web_fetch is WebFetch, google_web_search is WebSearch, and MCP tools (named by their mcp_context) are mcp__<server>__<tool>. save_memory, which appends to the GEMINI.md later sessions load as instructions, is seen as a write to that file, so the memory-write rule covers it. Gemini's web_fetch takes a prompt that holds the URLs; every URL in it is checked.

Limits to know before relying on it:

Adding an agent

  1. src/adapters/<id>.js exporting an Adapter (see src/types.d.ts), and its id in src/adapters/index.js.
  2. Map the agent's events and tool names onto the canonical ones. Keep fields the policy reads (command, file_path, url, tool_response) under Claude Code's names.
  3. State only the capabilities you verified against the agent's own documentation, and say where it cannot block.
  4. Test it with test/adapter-harness.js, which runs the real hook script against a real recorder.

Edit this page on GitHub