Connect an agent to Contro1
Choose a Contro1 connection path: five agent setup guides for production agents, Claude Code, Codex, Cursor and personal agents, plus web sign-in for Grok, ChatGPT and Claude.ai. Follow exact steps and verify access.
Core API
Know which agent did what: identity from the credential, sub-agents that register themselves, one trace per run with every tool call, hooks and OpenTelemetry that report without relying on the model, and HMAC-signed evidence.
Updated Sep 28, 2026
How an agent and its sub-agents are identified, how a run is traced end to end, how to report every tool call without trusting the model, how Contro1 notices an agent that stopped reporting, and how to verify the signed evidence.
Adopt it one rung at a time. Each step is a small addition to what you already send.
| Step | What you add | What it gives you |
|---|---|---|
| 1. Identity | An Agent Credential for each agent | Every call is attributed to that agent, and nothing in a request body can change which agent it is. |
| 2. Sub-agents | The Contro1-Sub-Agent header, or client.as_sub_agent(name) | Each part of a multi-agent system shows up under its parent, with its own trail. |
| 3. One run, one trace | trace_id, and parent_trace_id for a sub-agent's run | Every step of a run on one timeline, as a tree when agents call agents. |
| 4. What it did | A hook that reports every tool call | Every tool it called, with input, outcome and timing, without trusting the model to say so. |
| 5. Why it did it | context.machine_observed, context.agent_reported, retrieved_context[] | What triggered the step, the data it used, and its own explanation, labelled apart. |
| 6. Evidence | GET /v1/requests/:id/evidence | A signed record of identity, action, context and decision you can verify. |
Give each agent its own Agent Credential (Settings, Agent keys, Add agent key) or connect it with contro1 connect, which holds a key on the machine. Contro1 reads the agent from the credential on every call.
A request or audit record may repeat actor.agent_id for readability, but it must match the credential. A different value is refused with HTTP 403 agent_identity_mismatch, and on a runtime connection the attempt is itself recorded. One agent cannot write into another agent's trace by naming it.
Most multi-agent systems run in one process with one credential: a supervisor and its workers in LangGraph, a CrewAI crew, the subagents Claude Code starts. Each part says who it is with one header on the parent's credential, and Contro1 registers it under the parent the first time it is seen. There is nothing to set up.
A part can never do more than its parent. It runs on the parent's grants, its owner is the parent's owner, and blocking the parent blocks every part of it. On the org map and in Agent Inventory each part is drawn under its parent. A new part is marked with an orange ! until an administrator approves it; it can already act within its parent's authority meanwhile, so the mark asks for a look rather than stopping anything.
A sub-agent that runs as a separate program, somewhere else, with its own credential, is registered on its own and linked to its parent: contro1 init --parent <agent_id>, or register_agent with parent_agent_id. It appears under the parent, gets its own grants and inherits nothing. Only someone who owns or built the parent can link to it.
| The sub-agent | How it identifies itself | Its authority |
|---|---|---|
| Runs inside the parent's process | Contro1-Sub-Agent: <name> with the parent's credential | The parent's, narrowed by anything set on the part. Never more. |
| Runs as a separate program | Its own Agent Credential, registered with a parent | Its own grants. Nothing is inherited. |
trace_id (format trc_...) groups everything from one run: approval requests, reported steps, and the Actions Contro1 ran through the Gateway. parent_trace_id attaches a sub-agent's run to its parent. GET /v1/traces/:trace_id returns the whole run: the timeline, the tool calls and the sub-agents.
Every tool call in a trace says where it came from. gateway_verified means Contro1 made the call itself with an account it holds. client_reported means the agent's code sent it. The trace page shows the two apart, and that difference is the evidence.
Create the trace_id once, when the run starts, and pass it to every call the run makes. trace_id is not thread_id (a conversation) or correlation_id (a business case); send whichever you have.
Two kinds of statement go into a trace, and an auditor has to be able to tell them apart. Machine-observed facts are what your code captured from the runtime: the tool it called, the input, the output, the event that started the run. Agent-reported text is what the model says about itself, such as why it chose a tool.
Send the first in tool_calls and context.machine_observed, and the second in context.agent_reported. Agent-reported text is capped shorter on purpose, and it informs the reviewer only: it never raises or lowers routing, risk_level or approval policy.
Contro1 does not capture a model's private reasoning. If you want the agent's explanation, make it a required argument of the risky tool, for example issue_refund(order_id, amount, reason), so it is written at the moment of the decision and travels with the call.
A model told to "always report" can skip it, forget it, or describe something else. Report from the code around the model instead: a hook that runs before and after every tool call, which the model cannot see and cannot skip. Use the one for your framework.
| Runtime | What to add | Notes |
|---|---|---|
| LangChain, LangGraph | from centcom.hooks.langchain import Contro1TraceHandler, then config={"callbacks": [handler]} | One handler per run. |
| OpenAI Agents SDK | from centcom.hooks.openai_agents import Contro1RunHooks, then Runner.run(..., hooks=hooks) | per_agent=True reports each agent in the run as a sub-agent. |
| Strands Agents | from centcom.hooks.strands import Contro1HookProvider, then Agent(hooks=[provider]) | |
| CrewAI | from centcom.hooks.crewai import Contro1CrewHooks, then with Contro1CrewHooks(client): crew.kickoff() | per_agent=True reports each crew member as a sub-agent. centcom 1.7.0. |
| Pydantic AI | from centcom.hooks.pydantic_ai import contro1_toolset, then Agent(toolsets=[contro1_toolset(tools, client)]) | centcom 1.7.0. |
| Mastra (and Vercel AI SDK tools) | import { traceTools } from "@contro1/sdk", then new Agent({ tools: traceTools(run, tools) }) | @contro1/sdk 1.7.0. |
| n8n | The "Contro1: report a step" workflow from the centcom-n8n repo, called before and after a step | One execution is one trace. On Error: Stop Workflow fails closed. |
| Claude Code | contro1 activity hook in PreToolUse and PostToolUse | One session is one trace. contro1 0.2.19 or later. |
| Any Python or TypeScript code | TraceRun: run.wrap(tool) or with run.tool(name, input) | run.sub_agent(name) for a part, as a child run. |
| Already on OpenTelemetry | An OTLP exporter pointed at Contro1 | See below. No code of ours. |
If your agent is already instrumented with OpenTelemetry, point an OTLP exporter at Contro1 and its runs arrive with no code of ours. Contro1 reads the GenAI semantic conventions: a span with gen_ai.operation.name execute_tool, or with gen_ai.tool.name, becomes a tool call, with gen_ai.tool.call.arguments and gen_ai.tool.call.result when present.
Each export batch becomes one reported step per trace, with the OpenTelemetry trace id as trc_<32 hex>. Only the tool call fields, span names and model names are kept. Other attribute values, such as prompts and completions, are not stored.
An instruction in a prompt or a skill is not enforcement. Four controls do hold, from strongest to weakest.
For application Actions, a grant in the agent's capsule is the only thing that authorizes. The agent's scopes can only narrow it: allowed_action_ids is a ceiling, and an action in the ceiling with no grant is still refused. For a sub-agent that runs as part of its parent, both the parent's ceiling and its own apply.
For approval requests, the agent's scopes bound what it may ask for: allowed_action_types, allowed_department_ids (who it may act for), max_risk_level, and require_approval_above, which forces a human decision above that risk. Anything out of scope is refused with HTTP 403 agent_policy_blocked. There is no advisory mode: a ceiling that only warned let a critical action through and flagged it afterwards, so it was removed.
An agent Contro1 has seen but nobody has verified is shown as claimed in Agent Inventory, so it is visible rather than silently trusted. An administrator verifies it, gives it an owner and scopes, or blocks it. A blocked agent, and every part of it, is refused on every call with HTTP 403 until it is unblocked.
GET /v1/requests/:id/evidence returns an evidence packet with the verified agent, the trace, tool_calls, sub_agents, retrieved_context, the approval records and an integrity signature. GET /v1/agents/:agent_id/evidence returns a signed bundle of every packet for one agent, or CSV with ?format=csv.
The signature is HMAC-SHA256 over the canonical packet, computed with your organization webhook secret, the same secret that signs callbacks. Recompute it to prove a packet has not been altered.
Webhooks and signed callbacks · Audit records and cases · API endpoint index
The signed webhook is cryptographic proof of a human decision. Verify it inside the system that executes the action - not inside the agent. Any tool that must never run without human sign-off (payments, deploys, data deletion) should refuse to act without a verified approval; that way no agent, including shadow agents nobody registered, can trigger it by skipping Contro1.
Give each agent its own credential, add the reporting hook for its framework (or point its OpenTelemetry exporter at Contro1), and keep one trace_id per run. Actions it runs through the Gateway are traced by Contro1 on their own. GET /v1/traces/:trace_id then returns the whole run.
Not if it runs inside its parent. Send the Contro1-Sub-Agent header (client.as_sub_agent in the SDKs) and Contro1 registers it under the parent the first time. Register only a sub-agent that runs as a separate program with its own credential, and link it with --parent.
No. A part runs on its parent's grants, narrowed by anything set on the part, and blocking the parent blocks all of its parts. A separately registered sub-agent has only the grants given to it.
Yes, by not leaving it to the model. Use a hook that runs around every tool call and fails closed. For application actions, run them through the Action Gateway, where Contro1 records the call itself. Agent Inventory flags an agent whose reports stop.
No. It records what your code captured and what the agent chose to report, labelled apart. To keep the agent's explanation, make a reason argument part of the risky tool, so it is written when the decision is made.
Recompute HMAC-SHA256 over the canonical packet, without the integrity field, using your organization webhook secret, and compare it to integrity.signature. It is the same routine as verifying a signed webhook.
Choose a Contro1 connection path: five agent setup guides for production agents, Claude Code, Codex, Cursor and personal agents, plus web sign-in for Grok, ChatGPT and Claude.ai. Follow exact steps and verify access.
Choose the applications your organization approves, decide who may do what, and connect one MCP. Every person and agent gets exactly their access, and every action is recorded on their name.
Install and use the framework-agnostic Contro1 Python SDK to request human approvals or input, verify signed webhooks, log autonomous actions, and fetch audit evidence.
Install and use @contro1/sdk in Node, TypeScript, Express, Fastify, or Next.js backends to create approval requests, verify webhooks, and write audit records.
Install and use the contro1 CLI to register AI agents, create approval requests, push AI inventory, retrieve evidence and traces, and test workflows before using the SDK or API.