tamper-evident audit · cross-transport correlation · open source

Who Changed What, When, and What Happened Next.

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.

emit.py
import fasten fasten.init() fasten.emit( code="USER_CREATED", target="u-42", ) # → 7 anchors · req=3a7b1c · hash-chained
One request_id3 Streams7 AnchorsApache-2.0Python · Go · Rust · Node · TypeScript · C++ · Swift

One Substrate · Three Streams

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.

Stream · Syslog

sys

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

api

One row per HTTP request — the access log. Ring-buffer + stdout by default; opt into durable persistence via FASTEN_API_DSN.

The Correlation Key

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

One request_id, across every stream.

Beyond observability

The real reason it exists.

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.

Attribution

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.

Correlation

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.

Integrity

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.

Enforcement

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

One env var picks the engine. Same code, SQLite to Postgres.

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.

SQLite — single-node
# .env — dev · single-host prod FASTEN_SERVICE_ID=my-service FASTEN_NODE_ID=host-01 FASTEN_AUDIT_DSN=sqlite:///./audit.db # app.py import fasten fasten.init() # reads env

One file on disk. Zero dependencies. WAL mode. Perfect for dev, tests, edge nodes, and single-host services.

Postgres — multi-node / fleet
# .env — pick one: # dedicated DB FASTEN_AUDIT_DSN=postgres://user:pw@db/fasten_audit # shared DB, own schema FASTEN_AUDIT_DSN=postgres://user:pw@db/app?table=fasten.audit_log # shared DB, prefix only FASTEN_AUDIT_DSN=postgres://user:pw@db/app?table=fasten_audit_log # app.py — identical import fasten fasten.init() # reads env

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

init → emit + log. That's It.

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.
  • Shims auto-stamp request_id on the syslog and API streams.
Read the Docs
emit.py
import fasten from fasten.shim.http import RequestIDMiddleware, APILogger fasten.init() # reads env app.add_middleware(RequestIDMiddleware) # X-Request-ID app.add_middleware(APILogger) # api row per request fasten.emit(code="USER_CREATED", target="u-42", actor="admin", detail={"email": "a@ex.com"}) fasten.log.info("user_created", user_id="u-42") # Three streams — audit · sys · api # One request_id threads all three.

Audit Model · 5 Ws + H, threaded by one correlation key

Seven Anchors, Enforced at the Type Level.

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.

request_id

The seventh anchor — the correlation key that wraps the six below, threading them across every stream and transport.

Who

actor · actor_kind

admin · scheduler · system · agent.

What

code · action

PIPELINE_DEPLOYED · TOOL_CALLED.

When

timestamp · seq

UTC ms · per-node monotonic.

Where

node · service · tenant

Multi-tenant / multi-site filter.

Whom

target · category · domain

The object of the action.

How

method

http · mqtt · cli · scheduler · ui · agent_tool.

Query

Read It Back — CLI First, API When You Need It.

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.

fasten CLI — bundled
$ fasten dump # every registered code, sorted: id,domain,severity AUTH_LOGIN,user,info ORDER_PLACED,commerce,info PAYMENT_PROCESSED,commerce,info USER_CREATED,user,info ... $ fasten tail --stream audit --request-id a1b2c3d4 2026-04-22T12:34:56Z USER_CREATED admin target=u-42 2026-04-22T12:34:56Z ORDER_PLACED u-42 target=ord-9001 2026-04-22T12:34:57Z PAYMENT_PROCESSED system target=ord-9001 $ fasten doctor FASTEN_SERVICE_ID=api-server FASTEN_NODE_ID=host-01 audit store reachable (sqlite:///./logs.db) http shim wired emit roundtrip OK (id=evt-01HZ...)

Three operator utilities, bundled — dump, tail, doctor. A debug tool, not a product surface.

HTTP — mountable reader
$ curl "http://$HOST/api/v1/logs/audit?request_id=a1b2c3d4" $ curl "http://$HOST/api/v1/logs/audit?code=PIPELINE_DEPLOYED&since=2026-04-20" $ curl "http://$HOST/api/v1/logs/audit?actor=admin&target=u-42&limit=20" $ curl "http://$HOST/api/v1/logs/sys?request_id=a1b2c3d4" $ curl "http://$HOST/api/v1/logs/api?request_id=a1b2c3d4" # Mount in your service — Python app.mount("/api/v1/logs", fasten.reader.router()) # Go r.Mount("/api/v1/logs", Fasten.Reader())

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

Tune Without Code Changes. Per-Code TTL, PII-Aware.

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.

retention classes
# defaults, per class short 30 days # e.g. TRIGGER_FIRED, BATCH_PROCESSED medium 180 days # e.g. CONFIG_UPDATED, JOB_COMPLETED long 3 years # e.g. AUTH_LOGIN, PAYMENT_COMPLETED # override via env — no code change FASTEN_AUDIT_RETENTION_CLASS_LONG=2555 # 7y FASTEN_AUDIT_RETENTION_OVERRIDE="long=7y,medium=1y" FASTEN_RETENTION_SWEEP_INTERVAL=6h

Every code declares a class — short, medium, long. The sweep deletes expired rows; on edges, never before they have replicated to the aggregator.

PII + API log persistence
# .env — opt into a persistent API log FASTEN_API_DSN=postgres://user:pw@db/app?table=api_log FASTEN_API_RETENTION_DAYS=180 # HIPAA-friendly # code declaration — retention + PII class on the code register("user", { "AUTH_LOGIN": Meta(..., retention_class="long"), "AUTH_FAILED": Meta(..., retention_class="long", pii_in_detail=True), }) # or declare codes in *.codes.yaml — one source per SDK fasten.codes.load("fleet.codes.yaml")

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

Beyond HTTP — Correlation Across Every 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.

  • MQTT — the publish shim injects _req into the payload; the subscriber's handler reads it back into ctx.
  • scheduler — mints scheduler-<run_id> at job start; every row for the run shares the id.
  • Custom transport (gRPC, NATS, AMQP, Kafka)? A 10-line shim follows the same read-id / set-ctx / call-downstream pattern.
mqtt round-trip
# publisher (HTTP handler — user sets a new setpoint) await fasten.shim.mqtt.publish( client, f"devices/{device_id}/setpoint", {"target_temp_c": 22.5}, ) # shim injects _req = current_request_id() # subscriber (device firmware, any language) @mqtt_shim.handler("devices/+/setpoint") async def on_setpoint(msg, ctx): # ctx.request_id == original HTTP caller's id fasten.emit(code="DEVICE_SETPOINT_UPDATED", ...) # operator — one query stitches both services $ fasten tail --stream audit --request-id abc SETPOINT_REQUESTED api-server target=dev-42 DEVICE_SETPOINT_UPDATED device-42 # same id ✓

AI Agent Fit

Agents Call Tools. Tools Touch Resources. Who Audits That?

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

One id threads the chain

Agent A calls agent B calls a tool. One request_id threads the whole chain.

Cost Accounting

Sum by actor

Every TOOL_CALLED row carries detail.cost_usd. Sum by actor for billing.

Regulated AI

PHI-aware retention

A clinical summariser touches PHI → an audit row with pii_in_detail and GDPR retention.

Agent Debugging

Pull the trail

"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

Wire It Once. Read It Anywhere.

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

pip install fasten

Go

git clone & install from source

C++14

#include "fasten.hpp"

Node + TypeScript

npm install @nerdapplabs/fasten

Requires Rust 1.85 + fasten-core build — see prerequisites.

Rust

cargo add fasten

Swift

.package(url: "github.com/nerdapplabs/fasten", branch: "main")

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

Honest Split. No Overclaiming.

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.

Jaeger · causal debugging
✓ replaced

One request_id query pulls every row from every service.

Jaeger · span-level waterfall
✗ keep it

Keep Jaeger. request_id ↔ trace_id, so they coexist.

Loki · per-node audit search
✓ replaced

The mountable /logs/audit reader queries the durable store directly. Fleet-wide aggregation is fasten fleet's job.

Loki · fleet syslog at scale
✗ keep it

Ship stdout to Loki downstream if you need it.

Docker logs
kept

stdout stays the contract — used through the /logs/sys reader.

Homegrown audit tables
✓ replaced

A native AuditRepository. Seven anchors enforced.

FAQ

Common Questions.

Do I have to replace my logger (structlog, slog, pino)?

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.

How is fasten different from OpenTelemetry or my tracing stack?

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.

Do I still need a separate, paid observability 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.

What are the three streams, and what's the API log for?

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.

Is there a daemon, sidecar, or agent to run?

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.

Is it production-ready, and what does it cost in latency?

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.

What does the SDK cost?

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.

What's the difference between fasten, fasten fleet, and membrane?

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.

How does fasten fit an AI agent, and why not the usual LLM tracing tools?

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.

What about GDPR, HIPAA, and regulated industries?

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.

How do I contribute?

fasten is on GitHub at github.com/nerdapplabs/fasten. Issues and pull requests are welcome; start with CONTRIBUTING in the repo.

Start With the Substrate.

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.

fasten

The audit substrate for distributed systems — and the belief layer for the AI agents on top of them.

Products
fastenmembranefasten fleetmbnl · control — part of membrane
Resources
DocsQuickstartThe Seven AnchorsFAQContact
© 2026 fasten · nerdAppLabs Software Solutions Pvt. Ltd.SDK Apache-2.0 · membrane & fleet commercial