Framework Support
See which agent frameworks ArmorIQ traces without extra code, which versions are tested, when each trace ends, how native spans are handled, and what the limits are.
This page is for developers who choose an agent framework, and for teams that review what ArmorIQ can trace. You get the tested versions, the guarantees about when a trace ends, how ArmorIQ treats spans that the framework or another tool already creates, and the known limits.
A version range in a package setting is not a promise of support. Only the versions in the Tested version column were run against the real framework by the ArmorIQ test suites. Other versions can work. Test the exact versions in your own application before you rely on tracing.
Support matrix
| Framework | Python | TypeScript |
|---|---|---|
| LangChain | Supported. Tested: langchain 1.2.15, langchain-core 1.2.31. | Supported. Tested: @langchain/core 1.2.4 (Node.js 20 or later). |
| LangGraph | No version is tested. Uses the LangChain callback path. | No version is tested. Uses the LangChain callback path. |
| Strands Agents | Supported. Tested: strands-agents 1.54.0. The package allows >=1.54.0,<1.55.0. | Supported. Tested: @strands-agents/sdk 1.11.2 (Node.js 20 or later). |
| Google ADK | Supported. Tested: google-adk 1.14.1. | Supported. Tested: @google/adk 0.6.1. |
| CrewAI | Not supported yet for tracing. | Not available. |
| Direct OpenAI calls | Supported. Tested: openai 2.32.0. Streaming is not traced. | Supported. Tested: openai 6.49.0. Streaming is traced. |
| Direct Anthropic calls | Supported. Tested: anthropic 0.96.0. Streaming is not traced. | Supported. Tested: @anthropic-ai/sdk 0.111.0. Streaming is traced. |
The SDK package settings allow wider ranges than the tested versions. The settings are langchain-core>=0.2.0, google-adk>=1.0.0, openai>=1.0.0, and anthropic>=0.20.0 for Python, and openai >=4.0.0 and @anthropic-ai/sdk >=0.51.0 for TypeScript. Versions below the tested ones are not tested.
What every supported adapter does
For LangChain, Strands, and Google ADK, you add the ArmorIQ adapter once. See LangChain, Strands, and Google ADK. You then add no span code, no trace ID code, and no flush code. The adapter gives you these guarantees:
- One connected trace per run. A run that calls tools and a run that calls no tools both produce one trace. It has a root span, a plan span, and child spans for policy decisions, model calls, and tool calls.
- The trace ends one time. The adapter ends it when the run succeeds, fails, is cancelled, or is denied by policy. If the framework sends more than one end event, the extra events do nothing.
- A short, fixed wait at the end. The SDK waits a bounded time for spans to send. It never waits without a limit.
- Your own callbacks stay. The adapter adds its hooks next to yours. It does not remove them. For Google ADK, it wraps your callbacks and puts the originals back when you uninstall.
- Runs do not mix. Concurrent users and concurrent runs keep separate context and identity.
- Telemetry is fail-open. A tracing error never changes the result of the agent or a policy decision.
Native spans and duplicate spans
A native span is a span that the framework or another tool, such as OpenInference, already creates for the same model call or tool call.
- ArmorIQ always sends its own spans. It sends its own model, tool, and policy spans to its own destination. Another tool would send its spans to a different destination. If ArmorIQ stopped its own spans, ArmorIQ would receive no data for the call.
- ArmorIQ never changes a native span. It does not attach to it, reuse it, or write ArmorIQ attributes onto it.
- ArmorIQ detects OpenInference for LangChain. When
openinference-instrumentation-langchain(Python) or@arizeai/openinference-instrumentation-langchain(TypeScript, tested at 4.1.1) is active, ArmorIQ writesarmoriq.observability.native_span.state = coexistingonce on the root span. If it is not active, the value isemitted. The value tells you whether two tools trace the same run. - ArmorIQ does not detect other tools. LangSmith and other instrumentors cannot be detected today. ArmorIQ still sends its own spans next to theirs.
- ArmorIQ avoids two spans for one model call. Inside a LangChain, Strands, or Google ADK adapter in Python, the adapter owns the model call. A direct
openaioranthropiccall under it adds no second span. In TypeScript this is not complete. See Known limits.
LangChain and LangGraph
| Item | Python | TypeScript |
|---|---|---|
| Install | pip install "armoriq-sdk[langchain]" | @armoriq/sdk plus @langchain/core |
| Wire it | ArmorIQLangChain(armoriq_client=..., mode="sdk").for_user(email, goal=...), then config={"callbacks": [handler]} | new ArmorIQLangChain({client, mode: 'sdk'}).forUser(email, {goal}), then callbacks: [...existing, handler] |
| Trace ends when | The root chain ends or fails. A run with only a model call ends when the model call ends or fails. | The same events |
| Close by hand | handler.close(status) or await handler.aclose(status) | handler.close(status) |
| Cancel, timeout, disconnect | A cancelled async model-only run ends as cancelled. For a timeout or a dropped connection, call handler.close() if the framework sends no end event. | The SDK maps an aborted run to cancelled, a timeout to timeout, and a dropped connection to disconnected |
| Callback chaining | LangChain keeps the callback list. ArmorIQ does not touch the other handlers. | You append the handler to your existing list |
| Concurrency | One handler can serve runs one after another. Two top-level runs at the same time on one handler fail closed. Create one handler for each request. | One handler can serve many runs. Each root run has its own request state. |
| Native spans | Detects OpenInference. Not LangSmith. | Detects OpenInference in the CommonJS and ES module builds. Not LangSmith. |
Limits for LangChain and LangGraph:
- No LangGraph version is tested. LangGraph passes LangChain callbacks, but ArmorIQ has not run a real LangGraph graph in its tests. Test your exact LangGraph version.
- In Python, the model span is recorded after the model call ends, from the usage that LangChain reports. It is not timed by a start and an end hook.
- In TypeScript, if a model call at the root returns tool calls, the trace stays open until the chain ends or you call
handler.close(). - After
handler.close(), the handler is finished for good. A later tool call on it is blocked. Create a new handler. - If the framework gives no tool name, ArmorIQ blocks the tool call (fail closed).
- A TypeScript bootstrap failure only logs a warning, and the handler uses a safe fallback for tool names.
Strands Agents
| Item | Python | TypeScript |
|---|---|---|
| Install | pip install "armoriq-sdk[strands]" | @armoriq/sdk plus @strands-agents/sdk |
| Wire it | ArmorIQStrands(armoriq_client=..., mode="sdk").for_user(email, goal=...), then Agent(..., hooks=[hooks]) | new ArmorIQStrands({client, mode: 'sdk'}).forUser(email, {goal}), then new Agent({..., plugins: [plugin]}) |
| Hooks used | Before and after invocation, before and after model call, before and after tool call | After model call, before and after tool call, and (when the framework has them) before model call and after invocation |
| Trace ends when | The invocation ends. The status is error if there is no result, cancelled for a stop reason such as cancelled or interrupted, denied after a policy block, and ok otherwise. | The invocation ends. The status comes from the cancel signal (timeout, disconnect, or cancelled), then from an error, and is ok otherwise. |
| Close by hand | hooks.close() | plugin.close(status). It finishes the plugin for good. |
| Concurrency | Uses the mutable invocation_state of Strands for each request. If it is missing, ArmorIQ fails closed. | The plugin keys request state by invocationState |
| Native spans | No detection. Strands telemetry is not read. | No detection. |
Limits for Strands:
- Register exactly one ArmorIQ hook provider on an agent. A second one raises an error. Your own hooks can stay.
- In Python the ArmorIQ guard wraps the tool run stage of Strands 1.54.x. If that part of Strands changes, ArmorIQ fails closed.
- In TypeScript the event classes must come from the same Strands copy as the agent. If your host gives older classes without the invocation-end event, call
plugin.close()yourself. - In TypeScript, without the before-model event, the model span has about 0 ms duration.
- If you pass no
goal, the root and model input show that the framework gave no goal (framework_goal_not_provided).
Google ADK
| Item | Python | TypeScript |
|---|---|---|
| Install | pip install "armoriq-sdk[google-adk]" | @armoriq/sdk plus @google/adk |
| Wire it | ArmorIQADK(api_key=...).for_user(email, goal=...), then scope.install(root_agent) and scope.uninstall(root_agent) | new ArmorIQADK({apiKey}).forUser(email, {goal}), then bundle.install(agent) |
| Hooks used | Before and after model callback, before and after tool callback | The same four callbacks |
| Trace ends when | uninstall() runs and closes with status ok. To record a failure, call close("error") before uninstall(). | The wrapped runAsync ends or fails. Success is ok. An error gets the status of the error. Leaving the loop early is cancelled. |
| Close by hand | close(status) or aclose(status) | await bundle.close(status). A bundle serves one request. |
| Callback chaining | The adapter wraps your four callbacks and calls them with ADK rules. uninstall() restores them exactly. | The adapter puts its callbacks first and keeps yours after them in the same list. If you replace a slot later, ArmorIQ chains behind it and warns. |
| Concurrency | Uses a context variable for each request. The callback registry is process-wide. A stale context fails closed. | Installing a second live bundle on the same agent in the same async context raises an error. |
| Native spans | No detection. | No detection. The adapter pairs one model span for each ADK invocation. |
Limits for Google ADK:
- The Python
ArmorIQADKbuilds its own client. It has noobservabilityargument, so set export options with environment variables. See Choose who exports your traces. - In TypeScript, ADK must supply
functionCallIdfor exact tool matching. Without it, the adapter matches tool calls in order and logs one warning for the process. The warning has no tool, user, or argument data. - In TypeScript, a plan-assurance gate can run after the model call if
papGatewayUrlis set orARMORIQ_PAP_FORCE=1. It can replace the model response.
Direct OpenAI and Anthropic calls
You do not install an adapter for these. The SDK patches the provider packages when you create an ArmorIQClient. A call produces one model span, but only while an ArmorIQ session is active.
| Item | Python | TypeScript |
|---|---|---|
| Traced calls | OpenAI chat completions and responses (create, parse). Anthropic messages and beta messages (create). | OpenAI chat completions and responses (create). Anthropic messages and beta messages (create). This includes streaming calls and messages.stream(). |
| Streaming | Not traced. A call with stream=True produces no span. | Traced. The span ends when the stream finishes, fails, or is closed early. A stream that you stop before it finishes is cancelled. |
| Data captured | Model name and provider, input and output tokens, cache tokens, and status. The prompt and completion are captured only when the capture mode and organization allow content. | The same |
| Manual call | instrument_openai(), instrument_anthropic() | instrumentOpenAI(OpenAI), instrumentAnthropic(Anthropic). An ES module app must call instrumentOpenAI(OpenAI) itself, because the automatic patch loads the CommonJS build. |
| Which session | The session bound to the current context, or the only open session. With several open sessions and none bound, the call produces no span. | The session bound to the current async flow |
| Under an adapter | One span only. The adapter owns the model call. | Can produce a second span. See below. |
In Python, a model call produces no direct span when any of these is true: the call streams, no session is bound, a framework adapter owns the call, or the call is already inside another direct span. An OpenAI call that times out ends as timeout. A cancelled call ends as cancelled.
In TypeScript, an OpenAI streaming chat request gets stream_options: { include_usage: true } by default so that token counts arrive. Set ARMORIQ_OTEL_STREAM_USAGE=0 (or observability.streamUsage: false) if your gateway rejects it. An explicit stream_options on your request is never changed.
CrewAI
CrewAI tracing is not supported yet. The Python ArmorIQCrew class still does intent verification and enforcement for CrewAI tools. It creates no session, no spans, and no trace. Do not expect model, policy, tool, or end-of-run spans from a CrewAI run. Zero-touch CrewAI tracing is planned work, tracked in armoriq-sdk-customer issue 124.
Known limits
- Python and TypeScript differ for direct calls under an adapter. In TypeScript, a raw
openaioranthropiccall made under a LangChain, Strands, or Google ADK adapter produces its owngen_ai.chatspan in addition to the adapter's span. Python avoids the second span. This TypeScript gap is tracked in armoriq-sdk-customer-ts issue 287. - Spans that start before the first policy answer. The SDK reads your organization policy before it records data. The TypeScript SDK does not wait for it with the
armoriqandproviderexports, so spans that start before the first answer are not recorded. The Python SDK waits for the first answer, for up to 5 seconds. See Troubleshooting. - A run that never ends. The SDK exports only spans that have ended. If a framework never sends an end event and you do not close the session, the root span is not exported.
- Process exit. A TypeScript process that exits normally drains ended spans, up to a fixed time, and stops after three idle rounds. The SDK does not handle
SIGINT,SIGTERM, orprocess.exit(). Close sessions before your process stops. - TypeScript adapter imports. The adapters are not exported from the package root. Import them from
@armoriq/sdk/dist/integrations/<name>.
Next steps
What Data Is Captured
See exactly which fields ArmorIQ records in each capture mode, which fields are never recorded, and what leaves your network when you export traces.
Organization Controls
Set what ArmorIQ collects for your organization, limit it by product, start temporary debug capture, watch quota and pipeline health, and set alerts.