Claude Agent SDK
Mount Entorin under the Claude Agent SDK via httpx transport, wrapped MCP sessions, and a per-run sandbox.
The SDK uses an internal httpx.AsyncClient to talk to Anthropic and uses mcp.ClientSession for any MCP servers it spawns. Two integration points:
import httpx
from pydantic import SecretStr
from adapters.claude_sdk import (
EntorinAnthropicTransport, WrappedMCPSession, run_sandbox,
)
from entorin.auth import Capability, Principal
from entorin.budget import BudgetGate, MemoryLedger
from entorin.context import RunContext
from entorin.events import EventBus
from entorin.model.adapters.anthropic import CAPABILITY_KIND
# 1. The substrate primitives — one EventBus / Ledger / Gate per run.
bus = EventBus()
ledger = MemoryLedger()
gate = BudgetGate(ledger)
ledger.set_cap("alice", Decimal("5.00"))
# 2. Identity + capabilities. The API key is a Capability — substrate
# never reads os.environ itself.
principal = Principal(
user_id="alice",
caps=(Capability(kind=CAPABILITY_KIND, value=SecretStr(...)),),
)
ctx = RunContext(run_id=str(uuid.uuid4()), principal=principal)
# 3. Mount the substrate transport on the SDK's httpx client.
transport = EntorinAnthropicTransport(
ctx=ctx, bus=bus, ledger=ledger, gate=gate,
)
sdk_http_client = httpx.AsyncClient(
transport=transport, base_url="https://api.anthropic.com",
)
# Pass `sdk_http_client` into the SDK in place of its default.
# 4. Wrap each MCP session before handing it to the SDK.
wrapped_fs = WrappedMCPSession(
server_id="fs", session=raw_mcp_session, bus=bus, ctx=ctx,
)
# SDK uses `wrapped_fs` exactly like a raw mcp.ClientSession; the
# substrate sees and audits every call_tool, the SDK doesn't notice.
# 5. Confine spawned subprocesses to a per-run sandbox.
async with run_sandbox(ctx=ctx, bus=bus, artifacts_root="artifacts") as sb:
# Generated files land in artifacts/{run_id}/ and survive the run.
# On exit (normal or BudgetError mid-run), every spawned subprocess
# is killed cleanly — no zombies leak across runs.
...
Outcomes
- API key flows from
Capability→ request header. The SDK can keep readingANTHROPIC_API_KEYfrom env all it likes; the substrate overwrites the header on every Messages call. BudgetGate.checkruns before any wire activity. A cap-exceeded run terminates withBudgetErrorand never contacts Anthropic.tool.call.pre/post|errorevents fire for every tool the SDK invokes. Capability denial emitspolicy.violationand the wrapped session never proxies the call to the underlying server.