Repository navigation
Integrations LlamaIndex
Experimental: this works and is tested, but may change and gets less hardening than the stable core.
tracekit.integrations.llama_index.Tracekit gates every tool call of a LlamaIndex agent workflow: a FunctionAgent,
ReActAgent, CodeActAgent or your own BaseWorkflowAgent, and an AgentWorkflow with its handoffs.
Tracekit(run).gate(workflow) overrides the workflow's call_tool step, the step every tool call runs through, the way
LlamaIndex customises a workflow
(a subclass that overrides a step). The signer decides each call before the tool runs (an ask waits for a person), and
the outcome is recorded after.
pip install tracekit-ai "llama-index-core>=0.14.25,<0.15"Tested with llama-index-core 0.14.25, which needs Python 3.10+. Add your model's integration (llama-index-llms-openai,
llama-index-llms-anthropic, ...); the gate itself needs only the core package.
import asyncio
from llama_index.core.agent.workflow import AgentWorkflow, FunctionAgent
from tracekit.integrations.llama_index import Tracekit
from tracekit.sdk.client import Client
async def main(run):
workflow = Tracekit(run).gate(AgentWorkflow(agents=[triage, billing], root_agent="triage"))
print(await workflow.run(user_msg="close ACC-9"))
with Client().run(agent="support-agent") as run:
asyncio.run(main(run))Gate the object you call .run() on. An AgentWorkflow runs every agent's tools, the handoff tool too, in its own
call_tool step: gate the workflow, as above (gating only its agents gates nothing there). A single agent run on its
own: gate the agent. An agent that a tool of yours runs (an agent as a tool) is a workflow of its own: gate it too, with
the same Tracekit. gate returns the object it was given, so you can gate it where you build it. A gated object
belongs to its Tracekit's run for life (gating it again raises): build and gate the workflow inside each run, as
above, not once for the whole process; a server builds it per conversation, in that conversation's run.
tracekit.instrument() doesn't wire LlamaIndex, and in an app that uses only LlamaIndex it returns None, so there is no
process run to share: open the run with Client().run(...). An AsyncRunHandle works as well as a RunHandle. Then
tracekit last exports and verifies the run. A runnable offline example (a scripted mock model from llama_index.core,
no API key): examples/v2/llama_index.
-
Overrides one step.
gateswitches the object's class to a subclass of its own class (the same name, soisinstanceandreprdon't change) whosecall_toolstep decides the call, runs LlamaIndex's owncall_tooland records the outcome. An object is gated once; gating it again raisesValueError. An object that takes tool calls in any other step thancall_tool(a LlamaIndex version that runs tools elsewhere) is not gated:gateraisesTypeError. -
Runs outermost. A
call_toolyour agent's or workflow's own class overrides runs inside the gate: if it changes the call's arguments, the call runs and is recorded asstatus: error, "the call's arguments changed after the decision". -
Decides the call's id and the model's arguments. The signer decides
ToolCall.tool_idandtool_kwargs, the arguments as LlamaIndex's model integration parsed them from the model ({}for none). For a function-calling model the id is the model's tool-call id;ReActAgentandCodeActAgentmake a UUID for each call, since their model writes the action as text. A call whose model integration names it by its tool (Ollama's, Google's) or gives it no id is decided under a UUID of its own, so two calls of one tool are two calls. 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. -
Covers every tool the workflow runs. Sync and async
FunctionTools, your ownBaseTools, tools from atool_retriever, each of several parallel calls on its own, thehandofftool,return_directtools, andCodeActAgent's code, decided as oneexecutecall with{"code": ...}. A tool name the agent doesn't have is decided too, and recorded as an error. -
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 skip the tool: the model reads Tracekit's reason
as an error tool result (as for a tool the agent doesn't have) and the run goes on. A refused
return_directtool doesn't end the run, and a refused handoff hands nothing off. Under an open fail mode a call runs unrecorded during an outage, with a warning. -
Records failures as failures. LlamaIndex turns a tool's exception into an error result for the model; the call is
recorded as
status: errorwith the exception. A tool's result is recorded as the text the model reads (ToolOutput.content), so it is what the LLM gateway sees sent back. An untrusted tool that returned a structure (a dict, list, model or dataclass) has it observed too, as JSON (input.observed, sourcellama_index:<tool>:<id>), so each of its values reaches provenance, not only those the text's patterns find. -
Waits for people. An
askwaits up to 5 minutes (the signer's cap for one wait) in thecall_toolstep; then the call is skipped with the approval's state, and an unanswered approval is withdrawn. So is one whose workflow is cancelled or times out while it waits. -
Keeps LlamaIndex's own human in the loop. A tool that waits for an event (
ctx.wait_for_event) stops, and LlamaIndex runs its step again from the start once the event comes. The call is not completed the first time; it is decided again as its nextattemptwhen it runs again. -
Decides MCP calls once. A tool from an MCP server (
llama-index-tools-mcp) is decided under its LlamaIndex name; theClientSessionthatinstrument()wires doesn't decide it again.
The gate records each AgentOutput before its calls run: its ThinkingBlocks (summaries), text and tool calls
under the tool_ids they are decided by. A call decided under the gate's own UUID (a model integration that names
the call by its tool, or gives no id) is in it without an id, so it pairs by its arguments only. 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.
- Only what you gate: an agent or workflow you didn't gate, an agent inside an
AgentWorkflowyou gated only by itself, and tools called outside an agent workflow'scall_toolstep (llm.predict_and_call, a router or sub-question query engine choosing tools) run ungated. While a gated call runs, the MCPClientSessionthatinstrument()wires decides nothing made inside it, so an ungated agent that a tool runs has its MCP tools run undecided: gate every agent a tool runs. - The functions
CodeActAgent's code calls inside yourcode_execute_fnare not decided one by one: the code is decided as a whole, asexecute. Run it in a sandbox. -
ReActAgentandCodeActAgentcalls carry an id LlamaIndex makes, not one from the model: with model calls recorded (the LLM gateway, autotrace), each reads asreconcile.fabricated, since no model tool use has its id. A call named by its tool is decided under a UUID of the gate's own, so it pairs with the model's tool use only by its arguments (paired_by_args), never when two calls in one turn are alike; a tool-named call that waits for an event gets a new UUID when it runs again, not a next attempt. - A tool that waits for an event runs from its start again (LlamaIndex re-runs the step): an
askis asked again for its next attempt, and the first attempt reads asdecided_not_completedonrun.final. The attempt count lives in theTracekitobject, so a workflow resumed from a savedContextin a new process decides its calls from attempt 0 again. - Structured output (
output_cls,structured_output_fn) runs no code the model chose and is not decided. - An
askholds its step while it waits: a workflowtimeoutunder 300 s cancels the wait (and withdraws the approval). Each signer call, and each wait, holds a thread of asyncio's default executor; many asks waiting at once can starve it. - A
call_toolstep your own subclass declares withnum_workersor aretry_policyruns with LlamaIndex's defaults once gated, and an argument change in an override of LlamaIndex's private_call_toolis not seen. - An untrusted tool's structure that can't be observed (the signer refuses it, or it is too big for one request) only warns: the values in it that the text's patterns don't find are missing from provenance.
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