Repository navigation
Integrations Strands
Experimental: this works and is tested, but may change and gets less hardening than the stable core.
tracekit.integrations.strands.Tracekit is a Strands Agents hook provider
that gates every tool call an agent makes: its BeforeToolCallEvent callbacks ask the Tracekit signer for a decision
before the tool runs (an ask waits for a person), and its AfterToolCallEvent callback records the outcome. Strands
itself is not changed.
pip install tracekit-ai "strands-agents>=1.61,<1.62"Tested with Strands Agents 1.61, which needs Python 3.10+, in an environment of its own: Strands needs mcp below 2.2,
and Tracekit's other integrations are tested with mcp 2.3. Add the extra for your model provider
(strands-agents[openai], [anthropic], ...); Bedrock needs none.
from strands import Agent
from tracekit.integrations.strands import Tracekit
from tracekit.sdk.client import Client
with Client().run(agent="support-agent") as run:
agent = Agent(tools=[close_account], hooks=[Tracekit(run)])
agent("close ACC-9")Then tracekit last exports and verifies the run. An AsyncRunHandle works as well as a RunHandle.
tracekit.instrument() doesn't wire Strands: build the hook provider yourself, as above, and don't call instrument()
in the same process (its MCP wiring would decide each call of a Strands MCP tool a second time).
Every agent needs the hook, a sub-agent used as a tool (agent.as_tool()) and each agent of a Swarm or Graph
included: an agent without hooks=[Tracekit(run)] runs its tools ungated. One Tracekit can serve all of them. Given
to a Swarm or Graph itself, it raises TypeError (they fire no tool-call events). A runnable offline example (a
scripted model, no API key): examples/v2/strands.
-
Decides the model's own call. The first callback (order
-math.inf, before every other hook) sends the signer the call'stoolUseId, tool name andinput: the arguments as Strands parsed them from the model, missing or empty ones as{}. An approval is bound to those arguments and used once. Under the executor (T2), a call whose approved arguments are not the model's is refused, not run with either. -
Never runs a refused call. A deny, a rejected or unanswered approval, a refused consume, a signer refusal, an
outage under a closed fail mode and any error of Tracekit's own all set
cancel_tool: the model reads Tracekit's reason as an error tool result and the run goes on. Under an open fail mode a call runs unrecorded during an outage, with a warning. -
Refuses a call changed after the decision. The last callback (order
math.inf, after every other hook) refuses an allowed call whose id, name or input another hook replaced or edited, or whose tool (selected_tool) it swapped, and sets Tracekit's refusal again if a hook clearedcancel_tool. A call refused there, or by another hook after Tracekit allowed it, is recorded asstatus: error, "not run: ...". -
Signs what a hook does after its last check. A hook that still runs after it (at
math.inf, added after Tracekit) isn't stopped, but isn't silent: a call it changed runs and is recorded asstatus: error, "the call's arguments changed after the decision"; a call whose refusal it cleared runs and is completed against the refused decision, which the signer signs as anexecuted_against_policygap. -
Records the result the model reads. The
AfterToolCallEventcallback (ordermath.inf) records the result after any other hook rewrote it. A success isstatus: okwith its content as the model reads it: one text block as its text (a JSON object or array, as Strands sends a dict or model a tool returns, as that JSON), onejsonblock as its value, several blocks as they are. So an untrusted tool's values reach provenance. An error result isstatus: errorwith the tool's exception or the result's text; the tool's exception reaches Strands unchanged, as its error result. -
Decides each run again. A call that runs again under the same
toolUseId(another hook'sAfterToolCallEvent.retry, or a call a Strands interrupt stopped and the agent resumed, whether a hook, the tool or its sub-agent raised it) is decided again as the call's nextattempt. An attempt that never ran reads asdecided_not_completedonrun.final. -
Waits for people. An
askwaits up to 5 minutes (the signer's cap for one wait) before the call; then the call is refused with the approval's state, and an unanswered approval is withdrawn. So is one whose agent is cancelled while it waits, byagent.cancel()(checked every 0.5 s) or by cancelling its task. Calls of one model turn are decided, and wait, at once under Strands' defaultConcurrentToolExecutor; agents in several threads can share oneTracekit. -
Covers every tool path the agent runs. Sync and async tools under either executor; direct calls
(
agent.tool.close_account(...), decided under the id Strands makes for them); MCP tools, under their agent-facing name; a sub-agent used as a tool (the call to it, and its own calls when it has the hook); aSwarm'shandoff_to_agentand its agents' calls; aGraph's agents' calls; the structured output tool, under the output model's name (refused, it ends the run with Strands'StructuredOutputException, as the model never gave the output); and background tools (background_tasks), decided before they are admitted and recorded when they ran.
The hook provider records each model message at AfterModelCallEvent: its reasoningContent (a summary, with its
signature or redacted content), text and tool uses under their toolUseIds. The message itself is kept only when the signer's content vault is on; without it the record holds a commitment. A message that can't be recorded only warns. Which frameworks and providers return readable reasoning, and how to turn it on: reasoning.
- An
askblocks the tool call while it waits, and each wait holds a thread of asyncio's default executor: many asks waiting at once can starve it. It does not raise a Strands interrupt: an interrupt's response comes from your app, not from an approver the signer knows, so it can't be the approval. - An interrupt stops a call after Tracekit decided it: one another hook raises (
event.interrupt(...)), or one the tool raises (ToolContext.interrupt(...), or its sub-agent's), which also re-runs the tool from the start when the agent resumes. Resumed, the call is decided again as its next attempt, and anaskasks a person again. - A hook that runs after Tracekit's last check (at
math.inf, added after it) is recorded, not stopped (above); one that also changes the call'stoolUseIdleaves it uncompleted: allowed, it reads asdecided_not_completed; refused, its run is not recorded. Strands' internal tool middleware can replace a call unseen. - Strands parses the model's arguments before any hook runs: arguments that aren't JSON reach the hooks, and the tool,
as
{}, and the signer decides{}. - With
background_tasks, a background tool another hook asks to retry (AfterToolCallEvent.retry) runs again with noBeforeToolCallEvent: that run is neither decided nor recorded. - With the gateway's verified tool results, a result of several content blocks
is recorded as its blocks, which the gateway doesn't match to what the model is sent: such a call reads as
result_mismatch. - A remote agent (
A2AAgent) runs its tools on its own side, out of the hooks' reach; a tool of yours that calls it is decided like any other. The bidirectionalBidiAgentfires the same hooks but is not tested.
Generated from docs/ at v1.4.1. Edit the docs in the repository, not here.
Get started
- Quickstart: the v2 signer
- Quickstarts
- Quickstart: Claude Agent SDK
- Quickstart: a custom agent
- Quickstart: LangChain and LangGraph
- Quickstart: MCP client
- Quickstart: OpenAI Agents SDK
- Coding agents other than Claude Code
- tracekit doctor
- Walkthrough: Tracekit end to end
How it works
- How Tracekit works
- Overview
- The life of a tool call
- The evidence format
- Verification and assurance
- Witnesses and monitoring
- Policy
- Approvals
- Detection and containment
- Capture and completeness
- Custody
- Provenance and tracekit why
- Deployment and isolation
- Storage, durability and performance
- Integrations
- Security model
- Architecture
- tracekit why: architecture
Integrations
- Integrations
- Adapters
- OpenTelemetry
- Remote clients
- Kubernetes agent-sandbox
- agentgateway
- agentsh and Tracekit
- Browser Use
- Claude Agent SDK
- Claude Code's sandbox
- Codex's sandbox
- CrewAI
- Docker MCP Gateway
- E2B
- Falco
- Gemini CLI's sandbox
- Google ADK
- Haystack
- LangChain v1
- LiteLLM
- LlamaIndex
- MCP client
- microsandbox
- Microsoft Agent Framework
- OpenAI Agents SDK
- NVIDIA OpenShell
- Pydantic AI
- sandbox-runtime's proxy on its own
- Strands Agents
- Tetragon
- Vercel AI SDK
- Wassette
Policy and approvals
- Policy reference
- Approvals
- Findings
- Reading the model's reasoning
- Incidents and containment
- Webhooks
Deploy and operate
- Deploying the signer
- The signer as a container
- The signer on Kubernetes
- The compose stack
- Identity
- Tool executor
- MCP gateway
- LLM gateway
- Egress observation
- Witnesses
- Log monitor
- Record-key issuer
- Signer observability
- Viewer
- tracekit why: causal investigation and replay
- Migrating
- Platforms
Verify the evidence
- Reading a v2 verify report
- Evidence format v2
- Auditor guide
- Sample evidence bundle
- Signing
- What Tracekit guarantees
Security and privacy
- Threat model: the v2 signer
- Threat model: the v1 laptop signer
- HTTP security checklist
- Threat-model review packet
- Privacy
- Known limits and trade-offs
- What's stable
Compliance
- Compliance mapping
- EU AI Act
- ISO/IEC 42001
- NIST AI RMF and the Generative AI Profile
- OWASP Top 10 lists for LLM and agentic applications
Evaluation
Demos
- Demos
- Agent approvers within bounds
- Completeness and tamper evidence
- Detection and containment
- Network coverage: an egress observer
- Guardrails: allow, deny, or ask a person
- MCP tools, gated and recorded
- Prompt injection held by provenance
- Why: which input caused the action
Help