TraceWeft
TraceWeft is an open-source Rust-first observability and debugging toolkit for LLM agents. It captures model calls, tool calls, memory operations, retrievals, state transitions, checkpoints, handoffs, and errors as structured traces, then lets developers inspect, replay, diff, and export them through OpenTelemetry-compatible pipelines.
TraceWeft is local-first by default: run a Rust agent, open the local debugger, and inspect the full execution without sending prompts or tool outputs to a SaaS service.
Screenshots
The local trace workbench (trace-weft dev plus the web UI):

| Span tree & inspector | Trace graph |
|---|---|
![]() |
![]() |
Install
TraceWeft is not yet published to crates.io. Depend on it by git, pinning a revision for reproducible builds:
[]
= { = "https://github.com/kidoz/trace-weft", = "<commit-sha>" }
The SDK is sqlite-by-default (a SQLite mirror alongside the JSONL stream). For
a pure local-JSONL integrator that pulls no sqlx:
[]
= { = "https://github.com/kidoz/trace-weft", = "<commit-sha>", = false }
Requires Rust 1.96+ (edition 2024). Install the CLI from a checkout:
Quickstart
Initialize a recorder once at startup, then record spans. The recorder is a
process-wide singleton; init_local wires the JSONL + SQLite recorder and the
blob store for content capture.
use ;
async
For tests and evaluation, swap in an in-memory store:
use Arc;
use ;
let store = new;
init_custom?;
// ... run the agent, then assert over store.spans / store.events
To compile tracing in but disable it at runtime, use the no-op store:
init_custom(Arc::new(trace_weft::NullStore)).
The builder — the primary API
SpanBuilder is the capable, imperative recorder. Build a span, set the rich
fields you have on hand, and wrap the work in .run(...):
use ;
let answer = build_llm_call
.provider
.model
.prompt_version
.input_ref
.token_usage
.cost
.cache_hit
.attribute
.run
.await?;
What .run captures
SpanBuilder::run(f) executes the closure and records:
- latency —
start_time/end_time/latency_msaround the closure; - status —
OkonOk(_),ErroronErr(e)(witherror_typefromDebugand a redacted message fromDisplay); - parenting — auto-links to the ambient span context (see below) unless you
set one explicitly with
.with_parent(...); - replay — short-circuits to a mocked value when replay is configured.
It does not infer inputs/outputs by itself — set .input_ref(label, &value)
or .output_ref(label, &value) for values you want captured under the active
CapturePolicy (or use the macros, which capture arguments and successful
returns for you). If you already have a content-addressed blob, use
.input_blob_ref(blob_ref) / .output_blob_ref(blob_ref). For closures that
don't return Result, use .run_infallible(|| async { ... }).
Builder setters cover the high-value SpanRecord fields: .provider(),
.model(), .prompt_version(), .tool_name(), .input_ref(),
.output_ref(), .token_usage(), .cost(), .cache_hit(),
.retrieval(query_hash, doc_refs), .attribute(k, v), .attributes(map).
Wrapping an LlmClient / Tool
Wrap each trait method body in a builder call — the span kind matches the role:
Macros
#[agent], #[tool], and #[llm_call] instrument a function (free function or
impl/trait-impl method, including &self) and record a span with the matching
SpanKind. They:
- set the correct span kind and
Errorstatus when the body returnsErr; - auto-link to the ambient span context, so nested instrumented calls form a tree with no manual ID threading;
- capture inputs/outputs under the configured
CapturePolicy— every captured argument must beSerialize; opt an argument out with#[trace(skip)].
use ;
Trait definitions carry no body, so annotate the concrete impl. The
instrumented function must be async.
Capture policy
CapturePolicy (set via LocalConfig.capture_content) governs content capture:
| Policy | Behavior |
|---|---|
MetadataOnly |
No content captured (zero serialization cost). |
RedactedPreview |
Redacts content, stores it, sets a redacted preview. |
FullContentLocalOnly / FullContentExportable |
Stores full content while keeping the preview redacted. |
Captured content is hashed, written to the blob store, and referenced from the
span as input_ref / output_ref.
Events
Spans have duration; events are point-in-time occurrences inside a span (a
retry, a budget check, a guardrail trip, an REPL step). Build one with event
and .record() it — it auto-links to the ambient span and gets a monotonic
ordering seq:
use ;
event
.attribute
.record
.await;
EventKind: LlmCall, ToolCall, ReplExec, Rpc, Budget, Guardrail,
Retry, Termination, Log, Custom.
Ambient context
SpanBuilder::run and the macros install the current span as a task-local
parent for the duration of the body. Child spans and events created inside it
link automatically; with_parent(trace_id, run_id, span_id) is the explicit
override for cross-task / cross-thread handoffs. Construct IDs with
TraceId::new(), SpanId::new(), RunId::new() — no direct uuid dependency
required.
Human-in-the-loop breakpoints
SpanBuilder::wait_for_approval() records the span as PendingApproval, blocks
until the UI/server approves or rejects, then resumes — a debugger-style
breakpoint for risky actions, and a differentiator over plain OpenTelemetry:
match build_tool.wait_for_approval.await?
Local Dev Workflow
Once your application produces traces into .trace-weft/traces.sqlite, inspect
them visually:
trace-weft dev starts only the API server (port 3000 by default,
local-first auth). To view the React UI in a browser, run it against that API:
Then open http://localhost:5173 for the Trace List, Span Tree, Waterfall, and
Replay/Diff UI. Alternatively, the desktop app (apps/desktop) bundles the
built UI and embeds the API server in one window.
The UI's API base is import.meta.env.VITE_API_BASE (empty by default → same
-origin /api, which the Vite dev server proxies); the desktop build sets it to
the embedded server's http://127.0.0.1:3000.
Server: storage backends and authentication
The server query endpoints (/api/traces, /api/traces/{id}, /api/evals)
run against either backend, selected by the database URL:
- SQLite (default, local-first) — any path or
sqlite://URL. - Postgres — a
postgres:///postgresql://URL. The schema is created on first connect and the same endpoints serve it with output matching SQLite. A local instance for development and the Postgres-backed tests is provided viadocker compose up -d postgres; run the parity suite withjust test-pg.
API-key authentication and per-project tenant isolation are configured via environment variables:
TRACE_WEFT_API_KEYS— comma-separatedraw_key:project_idpairs. Keys are hashed (SHA-256) at startup and never stored in the clear; requests authenticate withAuthorization: Bearer <raw_key>. Trace queries are scoped to the key's project, and ingested spans are stamped with it server-side.TRACE_WEFT_DEV_MODE=1— enable the dev bypass (no key required; queries span all tenants). The embedded local server (trace-weft dev, desktop app) defaults this on when no keys are configured; productionstart_serveruse defaults it off, rejecting unauthenticated requests with401.
OTLP/HTTP JSON ingestion is served at POST /v1/traces: payloads are decoded by
the trace-weft-ingest crate (opentelemetry-proto types, preserving original
trace/span/parent IDs, 400 for malformed bodies), then — like /api/v1/batch
— the authenticated project is stamped onto every span before it is persisted.
A 400 is returned for malformed bodies.
Development Checks
Workspace policy, API contract generation, CI checks, and UI test commands are
documented in DEVELOPMENT.md. The short version:
Crate Layout
crates/trace-weft- main user-facing SDK facade (builder, macros, events, capture, HITL, replay)crates/trace-weft-core- IDs, schemas, span/event types, redaction traitscrates/trace-weft-macros- proc macros:#[agent],#[tool],#[llm_call]crates/trace-weft-otel- OpenTelemetry export/import bridgecrates/trace-weft-openinference- OpenInference compatibility mappingcrates/trace-weft-recorder- local JSONL/SQLite/blob recorder (sqlitefeature, on by default)crates/trace-weft-ingest- OTLP ingestion viaopentelemetry-proto, preserving original IDscrates/trace-weft-server- axum API, SQLite + Postgres query layer, API-key auth and tenant scopingcrates/trace-weft-cli- CLI:dev(starts the local API server);import/export/replayare plannedapps/web- React / TypeScript / Vite UI

