claude-agent-sdk) runs the agentic loop in a
Node.js subprocess managed by the upstream package. The egisai patch
hooks into the two SDK-exposed extension points — PreToolUse and
PostToolUse — so policy enforcement happens at the same boundary the
subprocess does, before any tool runs and before Claude is shown
any tool result.
This is the only subprocess-loop framework the SDK governs with full
pre-execution and pre-feedback enforcement; everything else either runs
in-process (Tier 1 / Tier 2) or runs on managed infrastructure with no
hook point (AWS Bedrock Agents — see AWS Bedrock).
Supported surface
Both sync and async usage are covered.
Install
Use
What gets enforced
The patch inserts a small dispatcher under bothPreToolUse and
PostToolUse matcher slots. On each turn:
- Input phase — the prompt text is evaluated against your request-phase policies (PII detection, regex denylist, model allow-list, size cap, intent judge). A block refuses the turn before the subprocess starts.
- PreToolUse — for each tool the model wants to call, the SDK
runs output-side policies that target tool dispatches (
deny_tool_call,deny_mcp_call,deny_bash_command,deny_db_query,deny_financial_action,semantic_guardwithtargets: ["tool_calls"]). A block returnspermissionDecision: denyto the CLI; the tool never runs. - PostToolUse — for each tool that ran, the SDK evaluates
output-side policies against the tool’s response text
(
pii_scan,deny_output_regex,semantic_guard). A sanitize verdict masks the result in place via the SDK’supdatedToolOutput/updatedMCPToolOutputsubstitution contract; a block replaces the result with a refusal payload — in both cases the model never sees the raw bytes. - End-of-turn — the aggregated assistant response (text + structured tool calls) is evaluated one more time.
PostToolUse:
- MCP-shaped responses —
{"content": [{"type": "text", "text": "..."}]}. PII in text parts is masked or replaced; non-text parts (images, audio) pass through untouched. - Raw-string responses — typical of
Bash,Read,Write, … Replaced viaupdatedToolOutput. - Opaque-dict responses — JSON-serialized for scanning, then
replaced via
updatedToolOutput.
Audit fidelity
Atool_call step row is emitted only when a tool’s result was
non-allow (sanitized, blocked, or substituted). Allow turns stay clean —
no step inflation.
The audit row’s
request_text preview is sampled from the
post-sanitization payload, never the raw value. The privacy
contract — raw PII never leaves the SDK boundary — applies the same
way it does to every other integration.
Access inventory
The patch also feeds the dashboard’s per-agent Access tab from three declaration surfaces, automatically:mcp_servers— one MCP-server entry per configured server (name + transport label only — never the command, URL, or credentials). In-process SDK servers built withcreate_sdk_mcp_serveradditionally contribute full tool metadata (name, PII-sanitized description, parameter names, schema hash).allowed_tools— built-in tool names and fully-qualifiedmcp__…names granted to the session. Permission specifiers such asBash(git:*)and server-level grants (mcp__<server>) are understood.- The CLI’s
initsystem message — the toolset the subprocess actually loaded, filtered throughallowed_tools.
allowed_tools is treated as the grant boundary: when it is set,
CLI built-ins that load into the session but sit outside the grant
(TaskStop, CronDelete, …) are permission-gated, not reachable, and
are not declared — so the Access tab shows the agent’s effective
access instead of the CLI’s full built-in catalog. If the model
invokes a gated tool anyway, the platform records it from the audit
stream as observed access. With no allowed_tools configured, the
full loaded toolset is declared.
Backwards compatibility
On olderclaude-agent-sdk versions that don’t expose the hooks
field, the patch falls back to advisory mode and the audit rows are
honestly labeled. The PermissionError / refusal stub your code sees
on on_block="raise" / on_block="stub" is unchanged.
When a call is blocked
By default a blocked call raisesPermissionError from inside the
SDK’s receive_response() iterator. With on_block="stub", the
iterator yields a refusal-shaped message and continues so the agent
loop can keep running.
What’s next
Agent frameworks
Other agent frameworks supported by the SDK.
Two-phase governance
The phase model the policy engine applies on every turn.