Observability

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(), and report() add the plan, policy, and tool spans for you.
  • If the block raises, the trace ends with status error. Otherwise it ends with ok.
  • Close the session before you close the client.
  • You can call session.close() or session.dispose() instead of the with block. Both are safe to call more than once and never raise.

Optional session calls

CallWhat 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"),
)
FieldDefaultEffect
enabledTrueTurns tracing off for the client when False
exporter"armoriq""armoriq", "external", or "provider". See Choose who exports your traces.
endpointnoneThe full OTLP traces URL for external export
tracer_providernoneYour OpenTelemetry tracer provider, for provider export
capture_modenone (no local limit)The most data that you allow locally: off, metadata, enhanced, or debug. The organization policy can lower it more.
service_nameThe client agent_idThe OpenTelemetry service.name of an SDK-owned provider
headersnoneExtra headers for external export
protocolhttp/protobufThe only supported value
timeout_millis10,000Export 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

VariableEffect
ARMORIQ_API_KEYThe API key, if you pass none
ARMORIQ_BACKEND_URL or BACKEND_ENDPOINTThe ArmorIQ backend. The SDK sends traces to <backend>/v1/traces and reads the policy from <backend>/observability/policy/lease.
ARMORIQ_OBSERVABILITY0, false, off, no, or disabled turns tracing off for the process
ARMORIQ_OTEL_EXPORTERarmoriq, external, or provider. It overrides the option.
ARMORIQ_OTEL_CAPTURE_MODEThe local capture limit. It overrides the option.
ARMORIQ_OTEL_ENDPOINT, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, OTEL_EXPORTER_OTLP_ENDPOINTThe endpoint for external export
OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_TRACES_HEADERSHeaders for external export
OTEL_EXPORTER_OTLP_TRACES_TIMEOUT, OTEL_EXPORTER_OTLP_TIMEOUT, ARMORIQ_OTEL_EXPORT_TIMEOUT_MSThe 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_NAMEThe service name, if service_name is not set
ARMORIQ_ENVThe 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 nameWhen
armoriq.agent.runThe root span of the session
armoriq.intent.planThe plan
armoriq.policy.evaluateEach policy decision
gen_ai.chatEach model call
armoriq.tool, mcp.execute, bash.execute, cli.execute, command.executeTool and command work. Group operations by the armoriq.operation.category attribute, not by span name.
armoriq.approval.wait, armoriq.delegation.waitA 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 external endpoint is missing or invalid, the SDK exports nothing and logs why. It does not use localhost or ArmorIQ instead.
  • Concurrent requests stay apart. The SDK keeps context separate for each task and thread.

Next steps

On this page