How do I trace a Python AI agent running in AWS Lambda?
Rius traces a Python agent in AWS Lambda when you call rius.init() once at module level and call client.flush() at the end of every invocation. Lambda freezes the process between invocations, so a background export that has not run yet does not happen until the next thaw, if ever. Flushing in a finally block sends each invocation’s spans before the freeze. Also turn the heartbeat off, because a frozen process cannot send it.
How it works
Rius queues spans in memory and exports them in batches from a background thread. That is what keeps tracing off your agent’s hot path, and it is also why Lambda needs extra care.
- Init runs once per execution environment. Code outside your handler runs on a cold start and is reused by warm invocations. Put
rius.init()there. A second globalinit()logs a warning and returns the existing client, so calling it inside the handler is noise at best. - A frozen process exports nothing. Between invocations Lambda suspends the process. A suspended background thread sends no batch, and the exit hook that normally drains spans may never run.
flush()forces the export. It blocks until the batch is sent and returnsFalseon timeout or when the most recent export was rejected, for example a 401 from a bad API key.Truemeans your spans left the process.- The heartbeat is a long-lived-process signal. Rius pings the platform for the whole life of the process to power the Agents view. In Lambda the pings stop at every freeze, so the instance flaps between alive,
staleandgone. Turning it off avoids that.
Because flush() blocks, it counts toward your invocation time. Call it once at the end of the invocation, never inside loops or tool calls.
Set it up
- Add the SDK and the extra for your model library to your deployment package or layer:
pip install "glassflow-rius[anthropic]" anthropic. Use a Lambda runtime of Python 3.10 or newer. The installation page has the full configuration reference. - In the function configuration, set
RIUS_API_KEYto your key, which starts withri_. The SDK reads it, so it stays out of your code. - Initialize at module level, wrap your agent function with
rius.observe, and flush in afinallyblock so the spans leave even when the handler raises:
import anthropic
import rius
client = rius.init(
service_name="my-agent",
heartbeat=False,
)
claude = anthropic.Anthropic() # reads ANTHROPIC_API_KEY
@rius.observe
def run_agent(query: str) -> str:
response = claude.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=200,
messages=[{"role": "user", "content": query}],
)
return response.content[0].text
def handler(event, context):
try:
return {"answer": run_agent(event["query"])}
finally:
if not client.flush(timeout_millis=30_000):
print("rius: the most recent span export failed")- Keep
service_nameconstant across deploys. Every invocation groups under one agent. With the heartbeat off, the agent shows as no heartbeat on the Agents page and its Instances section stays empty, while its traces, cost and versions still appear. Put your release inservice_versionorRIUS_SERVICE_VERSIONinstead of the name. - Optionally set
RIUS_HEARTBEAT=falseinstead ofheartbeat=Falseif you prefer to configure it outside the code.
With a global init(), every installed integration turns on by itself, so the model calls in run_agent arrive as generations with tokens and cost. See Integrations for the libraries and frameworks covered.
What you get
- One trace per invocation, with the span for each model call and tool call under your
run_agentroot, and cost per call as a list-price estimate in US dollars. - Spans from a failed invocation too, because the flush sits in
finally. - A signal when delivery fails:
flush()returnsFalse, so you can log it in CloudWatch instead of losing spans silently. - A stable agent in the Agents view, with no false
staleorgoneinstances from frozen processes. The agent is marked no heartbeat and has no Instances section, which is expected in Lambda. - Failures in tracing never propagate into your handler. Exporter errors are logged and your function carries on.
When something else fits
- If the flush adds too much to each invocation, lower the volume with sampling (
RIUS_SAMPLE_RATE). The flush time grows with the number of spans in the invocation. Reliability covers the export settings. - If your agent runs as a long-lived container or server instead of Lambda, you do not need a per-invocation flush, and the heartbeat is useful, so leave it on.