Stream · Audit
audit
Typed, first-class audit events — the 5 Ws + H enforced at the type level, per-code retention, PII-aware. The trail an auditor accepts as-is.
tamper-evident audit · cross-transport correlation · open source
Logs tell you what happened. Traces tell you where. fasten tells you who changed what, and why — every request and tool call as a typed, tamper-evident row, threaded by one request_id across every transport. Run it as your audit and log store, or on top of the one you already have.
One Substrate · Three Streams
Stream · Audit
Typed, first-class audit events — the 5 Ws + H enforced at the type level, per-code retention, PII-aware. The trail an auditor accepts as-is.
Stream · Syslog
Structured application logs, request_id auto-stamped by the shims. Ring-buffered by default; ship stdout to Loki downstream when you need it.
Stream · API Access
One row per HTTP request — the access log. Ring-buffer + stdout by default; opt into durable persistence via FASTEN_API_DSN.
One request_id threads all three streams across every transport — HTTP, MQTT, schedulers, the CLI. A syslog line, an API row, and an audit event for the same request join in one query.
How it works
request_id, across every stream.Beyond observability
Observability gets you to the bug. fasten is built for the questions that come after it — the incident review, the SOC 2 auditor, the "who touched this customer's data in the last 90 days." A log line was never going to answer those.
Observability has three pillars — logs, metrics, traces — for seeing the system. fasten adds four on top, for trusting the record. This is why "audit" undersells it: it isn't a log you write, it's these four.
Who did it, not just what happened. Every event carries the actor, the workspace, and the timestamp — they ride in from context, so the callsite can't forget them.
One request_id threads the whole chain — the API request and every worker task it spawned — across HTTP, MQTT, schedulers, and the CLI, not just the HTTP spans a tracer sees.
The trail is append-only and hash-chained, so the record is provable, not just present — the difference between a log you hope nobody edited and an artifact an auditor accepts as-is.
A registry types every event — the five Ws, the How, and a correlation key — enforced at boot, not at 3am. Add a coverage lint and the build fails when a mutation doesn't emit, so the trail can't quietly rot.
Run it as your audit and log store, or on top of the stack you already have. It does what an audit log does, plus the part observability leaves out.
Get Started in 30 Seconds
fasten is a library — it runs in your process, no daemon, no sidecar. Audit rows go to durable storage; the emit call is identical whether that store is a SQLite file or a Postgres cluster.
One file on disk. Zero dependencies. WAL mode. Perfect for dev, tests, edge nodes, and single-host services.
Same code, change one env var. Three naming strategies: dedicated DB, shared DB + fasten schema, or shared DB + fasten_ prefix.
↳ The emit call is identical in both modes. Storage is swapped at the env-var boundary.
The Entire API Surface
Each language's signature matches its dominant structured-logging idiom — structlog, slog, tracing, pino, SLF4J. The audit row schema, the seven anchors, and the request_id contract are identical across implementations: a row written in Go reads cleanly from Python.
fasten.init() reads env — service id, node id, the audit DSN.fasten.emit() writes a typed audit row — code, target, actor, detail.Audit Model · 5 Ws + H, threaded by one correlation key
Six describe the event — Who, What, When, Where, Whom, How. The seventh, the correlation key, wraps them: one request_id that binds every event into a chain. No string-soup attributes.
Query
Audit is only useful if it is queryable. Three ways out: the bundled fasten CLI for operators and CI, a central aggregator (self-hosted, or fasten fleet) for fleets, and the HTTP reader when your service owns the UI.
Three operator utilities, bundled — dump, tail, doctor. A debug tool, not a product surface.
Mount where you need programmatic read access — a central aggregator, your own UI, external clients. Three endpoints, identical shape across services.
Retention · PII · API Log
Not every audit row deserves the same retention. Compliance-core events stay for years; high-volume trigger events sweep early; anything tagged pii_in_detail is forced to minimisation. The API log is opt-in; default is ring-buffer only.
Every code declares a class — short, medium, long. The sweep deletes expired rows; on edges, never before they have replicated to the aggregator.
Codes declared pii_in_detail: true force the short class regardless — the shortest safe retention by default. Retention applies to all three streams; FASTEN_SYS_RING_SIZE and FASTEN_API_RING_SIZE cap the ring buffers.
Advanced · Optional · Cross-Transport
HTTP is table stakes. fasten's wedge is the transports HTTP-only tools skip — MQTT for industrial / IoT / pub-sub, and the scheduler for autonomous, cron-fired jobs. Each is an opt-in shim. Most adopters never need these.
_req into the payload; the subscriber's handler reads it back into ctx.scheduler-<run_id> at job start; every row for the run shares the id.AI Agent Fit
LLM-ops tracing and span-timing frameworks (OpenTelemetry, agent SDKs) aren't typed audit. fasten fits natively: every tool call is an audit row, with actor, target, method, and correlation.
Multi-Agent Workflows
Agent A calls agent B calls a tool. One request_id threads the whole chain.
Cost Accounting
Every TOOL_CALLED row carries detail.cost_usd. Sum by actor for billing.
Regulated AI
A clinical summariser touches PHI → an audit row with pii_in_detail and GDPR retention.
Agent Debugging
"Why did the agent say X yesterday?" One request_id query pulls the trail.
↳ For agent state governance — what it believes, why, and what drifted from your system of record — that is membrane, built on the fasten substrate.
Language Coverage
The audit row schema, the seven anchors, and the request_id contract are identical across implementations. A row written in Go reads cleanly from Python; a Rust service feeds a Node + TypeScript report.
Python
Go
C++14
Node + TypeScript
Requires Rust 1.85 + fasten-core build — see prerequisites.
Rust
Swift
Requires Rust 1.85 + fasten-core build — see prerequisites.
↳ Source-install today. pip install ./python, npm install ./js, or cargo add --path ./rust from the repo. Go works today via its module path; C++14 is always a single-header copy.
What It Replaces
fasten is OpenTelemetry-compatible: request_id maps to trace_id, so it coexists with the tools you already run. Here is exactly what it replaces — and what it does not.
One request_id query pulls every row from every service.
Keep Jaeger. request_id ↔ trace_id, so they coexist.
The mountable /logs/audit reader queries the durable store directly. Fleet-wide aggregation is fasten fleet's job.
Ship stdout to Loki downstream if you need it.
stdout stays the contract — used through the /logs/sys reader.
A native AuditRepository. Seven anchors enforced.
FAQ
No. fasten runs next to whatever you already use. Keep structlog, slog, pino, or winston for application logs, and call fasten.emit() for the events you want on the audit trail. If you'd rather have one logging API, fasten.log.* follows each language's habits (kwargs in Python, positional pairs in Go, object-first in pino) and writes the audit and sys streams for you. The third stream, api, is the HTTP access log; the request shim records it, so you never write those lines yourself.
It answers a different question. Tracing and metrics tell you whether the system is healthy and why it's slow. fasten tells you what happened, who did it, and whether the record can be trusted: every request and tool call written as a typed row, threaded by one request_id across HTTP, MQTT, schedulers, and the CLI, on an append-only hash chain. OpenTelemetry is untyped at the semantic layer and stops at the HTTP edge; fasten commits to a typed shape and follows the request across transports. The two run together fine. The next question covers where fasten lets you drop a tool.
Usually not as much as you pay for today. The expensive part of an observability bill is log and audit ingest: storing and searching everything you emit. fasten can be that store itself — audit, sys, and API are your structured logs and your audit trail in one typed place, ring-buffered by default so you keep only what you choose, with retention set per code, and fasten fleet makes them searchable across the fleet and turns them into evidence packs. Or it sits on top of the store you already have — a cloud-provider log service, a hosted log indexer, OpenSearch, anything — and just adds the correlation and attribution. For request-level observability, fasten fleet now gives you the graphs (rate, errors, p50/p95 latency, top routes) and a request trace view over those same streams — so you don't need a separate tracing tool or request dashboard. What stays elsewhere is host/infra metrics (CPU, memory) and deep profiling; pair OpenTelemetry with Prometheus and Grafana (free) for those. Between fleet for request-level observability + audit and open-source OTel for infra, most teams don't need a paid observability contract.
Three typed streams share one request_id: audit (first-class events, the 5 Ws + H), sys (your structured app logs), and API (one row per HTTP request: method, route, status, actor, target). Because they share the id, the API call, the logs it produced, and the audit event it triggered all come back in one query. The API stream is ring-buffer plus stdout by default; set FASTEN_API_DSN to persist it.
No. fasten runs inside your process. There is no fastend, no sidecar, no service to stand up. The CLI it ships with is for reading the trail while you debug, not a background process.
Yes, it runs in production, including ours. emit() stays off the request path: by default it hands the write to a background drainer with exponential backoff and returns in under 0.1 ms. In raise mode an emit is about 0.1 to 0.5 ms on SQLite WAL and 1 to 5 ms on Postgres. Ring buffers cost roughly 8 MB per service at 10k lines, and noisy codes can be sampled with the short retention class and a periodic sweep. For clean shutdowns, fasten.flush(timeout=5.0) drains deterministically and is a no-op in raise mode, so the same teardown works either way; fasten.queue_stats() and GET /logs/audit/doctor report pipeline health for probes.
Nothing. Apache-2.0, no seat limits, no per-event charges, no free-tier traps. The paid product is fasten fleet; reach out about pricing.
fasten is the open-source SDK, the substrate that records what happened. fasten fleet is the commercial layer that pulls many nodes into one queryable plane and generates the compliance evidence. membrane is the commercial state layer for AI agents: what the agent believes, which source won, what has gone stale, and what has drifted from your system of record. You only need the SDK to start; add the other two when you want the fleet view or agent governance.
Each tool call and model response becomes an audit row with actor, target, method, and correlation, so you can replay exactly what the agent did and prove it was not changed afterward. membrane reads those rows to work out what the agent believes, catch drift from your systems of record, and correct it. Per-call LLM tracing tools are built for prompt/token visibility — useful but a different question. fasten records governed actions on a substrate you own, and membrane governs the state on top. Run both side by side if you like; they answer different questions.
The SDK gives you the building blocks: a per-code pii_in_detail flag that forces minimum retention for GDPR, secret-key redaction that runs before anything is written, and audit rows carrying actor, target, method, and timestamp in a shape you can defend. The finished reports (HIPAA, SOC 2, GMP, ISO 26262, FSSC 22000) come from fasten fleet, which the SDK is built to feed.
fasten is on GitHub at github.com/nerdapplabs/fasten. Issues and pull requests are welcome; start with CONTRIBUTING in the repo.
Open source, seven languages, Apache-2.0. init → emit in your process — no daemon, no sidecar. Add membrane and fasten fleet when you need belief and the fleet view.
The audit substrate for distributed systems — and the belief layer for the AI agents on top of them.