TraceAct - Tracemap

What Is TraceAct? Zero-Dependency Agentic Observability SDK for Python

TraceAct is an open-source Python SDK for agentic observability: action-level tracing for AI agents, jobs, and web apps, with a built-in live trace viewer.

Table of contents

TLDR: TraceAct is an MIT-licensed open-source Python SDK for action-level tracing and agentic observability. It records the full story of what happens when a function, background job, or AI agent turn runs and ships with a local web viewer to explore those traces live.

Context

An AI agent finishes a run in nine seconds and returns the wrong answer. In that window it called a model twice, ran four tools, wrote to a database, and retried an HTTP request. Your logs hold fragments of that story scattered across a thousand interleaved lines.

TraceAct records the whole story as one object. It’s an open-source Python SDK for action-level tracing and agentic observability: every traced function call, background job, or agent turn produces a single JSON record containing each step taken, resource touched, event recorded, and failure encountered. The package ships with a local web viewer for exploring those records live, carries zero runtime dependencies, runs on Python 3.10+, and installs in one command:

pip install traceact

The project lives at github.com/traceact/traceact under an MIT license. It’s unrelated to other companies using a similar name. If you heard about TraceAct in a Python, debugging, or AI agent context, this is the one.

What a trace contains

Each record follows a small, fixed vocabulary:

ConceptMeaning
TraceThe full record of one action, from start to finish
StepA human-readable timeline marker within a trace
EventA structured operation: db, http, file, model, tool, queue
TouchA resource involved in the trace, derived from its events
SinkWhere finished traces go: JSONL, SQLite, console, HTTP, OTLP

The core API is a decorator. Wrap a function and every call writes one trace the moment it returns:

from traceact import traced_action, configure, JsonlSink

configure(project="my-app", sinks=[JsonlSink("data/traces.jsonl")])

@traced_action(action="note.create", kind="app", actor="user")
def create_note(title, body):
    ...

A context-manager API (ActionTrace.start) covers manual control, and helper methods (trace.db, trace.http, trace.model, trace.tool, trace.queue) record common operations in one line. Writes hit the sink immediately by default, so a crash can’t erase a finished trace. And if you skip configure() entirely, traces print to stdout instead of disappearing.

Where traces go: sinks

A sink is where a finished trace lands, and configure(sinks=[...]) accepts one or more of them at once, writing every record to each. Skip the argument and TraceAct falls back to printing traces to stdout.

SinkWrites toNotes
JsonlSinkA .jsonl fileThread-safe; optional max_bytes rotation renames the active file to a timestamped segment instead of deleting anything
ConsoleSinkstdoutThe zero-config fallback
SqliteSinkA local SQLite databaseWAL mode for concurrent reads and writes; stores the full record as JSON plus common fields (action, kind, status, started_at) as indexed columns
HttpSinkAn HTTP or HTTPS endpointPOSTs each trace as JSON over stdlib urllib; failed deliveries count in HttpSink.failed instead of raising
OtlpSinkAn OTLP collectorCovered below, under OpenTelemetry
AsyncSinkWraps any other sink(s)Moves writes to a background thread

JsonlSink is the default choice for local development and single-process apps; SqliteSink suits anyone who wants to query traces with SQL instead of grepping a file. HttpSink and OtlpSink both make a synchronous network call per write, so I recommend wrapping either in AsyncSink for production, and AsyncSink can wrap a list of sinks at once, so one failing sink doesn’t cost the others their records.

AsyncSink offers three backpressure policies when its queue fills up: drop_newest (the default), drop_oldest, and block, plus a .dropped counter so any loss under sustained overload is still visible. It also registers an atexit hook to flush buffered records on normal process exit and resets itself cleanly after os.fork(), so a forked worker doesn’t inherit a dead background thread.

The built-in viewer and trace map

Installing the package also installs the traceact command. Point it at your trace file:

traceact view data/traces.jsonl

A local server opens at 127.0.0.1:8765 (auto-increments if the port is taken) and tails the file live, so traces appear as your app writes them. Pass a folder and it merges per-process shards chronologically. Run the command from a second terminal and it reuses the running viewer instead of spawning another server.

Beyond the live trace log and inspector, there’s a trace map: a visual replay of one trace, with the action as origin and its events and resources as connected nodes, each carrying its own status and a red marker on failures. The map plays back as a sequential step-through with a speed slider from 1x to 10x, pause and play, and full zoom and pan for large traces. I think watching an agent run unfold node by node beats scrolling a log.

Two more helpful commands: traceact doctor runs local health checks (Python version, state directory, source validity) to rule out setup problems; and traceact doctor --scan audits trace files already on disk for leaked credentials, exiting nonzero on any finding, so you can drop it into CI as a gate.

Security: token auth and redaction

The viewer binds to localhost only. On a shared machine, though, a different OS user could reach that port, so the --require-token flag gates every API request behind a random token. The token is generated in-process with secrets, never accepted as a command-line value (the process list or shell history would expose it), stored at mode 0600, and compared in constant time.

Captured data passes through two redaction layers. Field-name matching catches keys like password and api_key, including deep inside nested structures. On top of that, default-on value scanning catches credential formats themselves (AWS keys, sk- tokens, JWTs, PEM blocks, Bearer values) wherever they appear, even mid-sentence in free text. Matches become named placeholders like [redacted:aws-key] with the surrounding prose intact.

Built for AI agents

One agent turn is a model call plus tool calls, and telling those apart is the point of tracing an agent. TraceAct ships a dedicated "tool" event kind, explicit parenting for callback-style frameworks where start and end fire on unrelated stacks, and a LangChain adapter that turns chain, model, tool, and retriever runs into traces with correct parent links and one shared correlation ID per run. Prompt and response text goes unrecorded unless you opt in, and opted-in content still flows through redaction. The adapter imports langchain-core only when you import it, so import traceact remains dependency-free.

For long-running agent work, opt-in streaming (stream_progress=True) appends slim progress stubs while a trace is open, so the viewer shows a running row that fills in live. A process killed mid-trace leaves its last snapshot on disk as crash evidence instead of losing the trace entirely.

OpenTelemetry, queues, and services

TraceAct complements OpenTelemetry: OtlpSink exports finished traces to any OTLP-compatible collector (Jaeger, Grafana Tempo, Honeycomb, the Datadog agent) over OTLP/HTTP+JSON using only the standard library; no opentelemetry-sdk install required. Tool events export as INTERNAL spans following OTel’s GenAI semantic conventions, and original TraceAct IDs ride along as span attributes so both systems are cross-referenceable.

Queue boundaries break ambient context, so TraceAct sends it across as job data: inject_context() on the producer, a reserved traceact_context kwarg on the worker. The worker’s trace links back through upstream_trace_id and correlation_id, and the pattern works with Celery, RQ, or any queue that carries a dict. For HTTP hops, two headers plus bundled WSGI and ASGI middleware (Flask, Django, FastAPI, Starlette) propagate trace identity across services.

There’s also a query layer for code: TraceLog filters trace files programmatically (.filter(status="failed").last(10)), so a test suite or an AI agent can read traces without parsing JSONL by hand.

Budgets and the design principle behind it all

Production tracing needs limits, and TraceBudget caps events, payload bytes, and depth, with sampling via sample_rate. But wherever TraceAct drops or truncates data, it leaves a signal: truncated traces set budget_hit, records dropped under backpressure increment a counter, and a failure inside a sampled-out trace still writes a failure record marked sampled_out. The library never hides its own losses from you. Silent by default, observable by choice.

Start with pip install traceact, decorate one function, and run traceact view. See the usage reference for more.

Get a free audit

Book a 30-minute call to see where AI could help your organisation.