Python SDK Observability
Trace policy decisions, model calls, and tool work with the ArmorIQ Python SDK. Set the export, capture ceiling, and per-session switch.
This page is for Python developers. You learn how tracing works in the armoriq-sdk package, which options and environment variables control it, and which session calls are optional. The SDK builds one OpenTelemetry trace for each session, and sends it to ArmorIQ without telemetry code in your agent.
This page describes armoriq-sdk 0.8.17, the current release. Older versions used a different recorder. See Migrate from the legacy recorder.
Install
You need an organization-scoped ArmorIQ API key. The SDK supports Python 3.10 to 3.13.
python -m pip install "armoriq-sdk==0.8.17"Install an extra for the framework that you use: armoriq-sdk[langchain], armoriq-sdk[strands], or armoriq-sdk[google-adk]. The import package is armoriq_sdk. The OpenTelemetry packages that the SDK needs (opentelemetry-*, version 1.27 up to, but not including, 2) install with it.
Default setup
Tracing is on by default. With a framework adapter you add no tracing code. See Set up tracing for a complete example and Framework support for tested versions.
import os
from armoriq_sdk import ArmorIQClient
from armoriq_sdk.integrations.langchain import ArmorIQLangChain
armoriq = ArmorIQLangChain(
armoriq_client=ArmorIQClient(api_key=os.environ["ARMORIQ_API_KEY"]),
mode="sdk",
)
handler = armoriq.for_user("alice@example.com", goal=user_message)
agent_executor.invoke({"input": user_message}, config={"callbacks": [handler]})Creating an ArmorIQClient also patches the openai and anthropic packages, so that direct calls are traced while a session is active.
Use a session without an adapter
If you call the session API yourself, the session is also the trace. Use it as a context manager. When the block ends, the SDK ends the trace and sends the spans.
from armoriq_sdk import ArmorIQClient, ReportOptions, SessionOptions
client = ArmorIQClient(api_key=os.environ["ARMORIQ_API_KEY"], agent_id="crm-assistant")
tool_name = "crm-mcp__search_contacts"
tool_input = {"query": "Acme"}
with client.start_session(SessionOptions(mode="sdk")) as session:
session.start_plan(
[{"name": tool_name, "args": tool_input}],
goal="Find contacts at Acme",
)
decision = session.check(tool_name, tool_input)
if decision.allowed:
result = run_the_tool(tool_name, tool_input)
session.report(tool_name, tool_input, result, ReportOptions(status="success"))start_plan(),check(), andreport()add the plan, policy, and tool spans for you.- If the block raises, the trace ends with status
error. Otherwise it ends withok. - Close the session before you close the client.
- You can call
session.close()orsession.dispose()instead of thewithblock. Both are safe to call more than once and never raise.
Optional session calls
| Call | What it does |
|---|---|
session.record_generation(model=..., input_tokens=..., output_tokens=...) | Adds one model span from usage that you report. Optional arguments are cache_read_tokens, cache_write_tokens, prompt, completion, finish_reason, duration_ms, and cost_usd. Pass cost_usd only when your provider reported a cost. Framework adapters do this for you. |
session.flush_observability() | Ends the current plan span and sends queued spans. The session stays open, so you can start another plan. Most agents do not need it. |
SessionOptions(observability_enabled=False) | Turns tracing off for this session only. |
None of these calls changes a policy decision. They never raise into your code.
Options
Pass ObservabilityOptions to ArmorIQClient(observability=...).
from armoriq_sdk import ArmorIQClient, ObservabilityOptions
client = ArmorIQClient(
api_key=os.environ["ARMORIQ_API_KEY"],
observability=ObservabilityOptions(capture_mode="metadata"),
)| Field | Default | Effect |
|---|---|---|
enabled | True | Turns tracing off for the client when False |
exporter | "armoriq" | "armoriq", "external", or "provider". See Choose who exports your traces. |
endpoint | none | The full OTLP traces URL for external export |
tracer_provider | none | Your OpenTelemetry tracer provider, for provider export |
capture_mode | none (no local limit) | The most data that you allow locally: off, metadata, enhanced, or debug. The organization policy can lower it more. |
service_name | The client agent_id | The OpenTelemetry service.name of an SDK-owned provider |
headers | none | Extra headers for external export |
protocol | http/protobuf | The only supported value |
timeout_millis | 10,000 | Export time limit. The SDK keeps it between 100 and 30,000. |
A local capture_mode can only lower what the organization allows. It can never raise it. See Organization controls.
Environment variables
| Variable | Effect |
|---|---|
ARMORIQ_API_KEY | The API key, if you pass none |
ARMORIQ_BACKEND_URL or BACKEND_ENDPOINT | The ArmorIQ backend. The SDK sends traces to <backend>/v1/traces and reads the policy from <backend>/observability/policy/lease. |
ARMORIQ_OBSERVABILITY | 0, false, off, no, or disabled turns tracing off for the process |
ARMORIQ_OTEL_EXPORTER | armoriq, external, or provider. It overrides the option. |
ARMORIQ_OTEL_CAPTURE_MODE | The local capture limit. It overrides the option. |
ARMORIQ_OTEL_ENDPOINT, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, OTEL_EXPORTER_OTLP_ENDPOINT | The endpoint for external export |
OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_TRACES_HEADERS | Headers for external export |
OTEL_EXPORTER_OTLP_TRACES_TIMEOUT, OTEL_EXPORTER_OTLP_TIMEOUT, ARMORIQ_OTEL_EXPORT_TIMEOUT_MS | The export time limit |
OTEL_EXPORTER_OTLP_CERTIFICATE, OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, OTEL_EXPORTER_OTLP_CLIENT_KEY (and the _TRACES_ forms) | Custom CA and client certificate files for external export |
OTEL_SERVICE_NAME | The service name, if service_name is not set |
ARMORIQ_ENV | The deployment.environment label on exported spans (default unknown) |
The armoriq export ignores the OTEL_EXPORTER_OTLP_* variables. Only external export reads them. Header values use URL encoding. The full rules for endpoints and headers are in Choose who exports your traces.
What the SDK records
The SDK adds these spans without code from you:
| Span name | When |
|---|---|
armoriq.agent.run | The root span of the session |
armoriq.intent.plan | The plan |
armoriq.policy.evaluate | Each policy decision |
gen_ai.chat | Each model call |
armoriq.tool, mcp.execute, bash.execute, cli.execute, command.execute | Tool and command work. Group operations by the armoriq.operation.category attribute, not by span name. |
armoriq.approval.wait, armoriq.delegation.wait | A hold that waits for approval |
See What data is captured for fields and Data model for the attributes.
How it behaves
- The SDK reads your organization policy first. It fetches the capture policy with your API key. The first span waits for the answer for up to 5 seconds. If the policy is not available, the SDK records nothing until it is.
- Failures are fail-open. A tracing error never changes an allow, hold, or block result, and never raises into your code.
- Closing is bounded.
close()makes one final send with a limit of 3 seconds. - No silent fallback. If an
externalendpoint is missing or invalid, the SDK exports nothing and logs why. It does not uselocalhostor ArmorIQ instead. - Concurrent requests stay apart. The SDK keeps context separate for each task and thread.
Next steps
TypeScript SDK Observability
Trace policy decisions, model calls, and tool work with the ArmorIQ TypeScript SDK. Set the export, capture ceiling, and per-session switch.
Choose Who Exports Your Traces
Decide whether ArmorIQ or your application owns the OpenTelemetry tracer provider, and set the destination for your traces.