Audit is only useful if it's queryable. Three ways out:
The bundled fasten CLI — for operators and CI.
A central aggregator (self-hosted, or fasten Cloud) — for fleets.
The HTTP reader — only when your service owns the UI.
CLI — fasten
bundled
Three operator utilities, bundled. Not a product surface.
$ fasten dump
# every registered code, sorted: id,domain,severity# feeds the cross-language consistency gate
AUTH_LOGIN,user,info
ORDER_PLACED,commerce,info
PAYMENT_PROCESSED,commerce,info
USER_CREATED,user,info
...
$ fasten tail --stream audit --request-id a1b2c3d4
# stream audit rows for one request_id from local or remote
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
# env vars? store reachable? shims wired? sample emit roundtrip?
✓ 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...)
HTTP — mountable router
optional
Mount where you need programmatic read access — central aggregator, your own UI, external clients. Three endpoints, identical shape across services.
API log — opt-in persistence; default is ring-buffer only.
Per-code retention class
audit
Every code declares a class: short, medium,
long. The retention sweep deletes expired rows — on
edges, never before they've replicated to the aggregator.
# 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 changeFASTEN_AUDIT_RETENTION_CLASS_LONG=2555 # 7y (regulated)FASTEN_AUDIT_RETENTION_OVERRIDE="long=7y,medium=1y"FASTEN_RETENTION_SWEEP_INTERVAL=6h
PII minimisation + API log persistence
opt-in
Codes declared pii_in_detail: true force the
short class regardless — shortest safe retention by
default. API log rows go ring-buffer + stdout by default; persist
them via FASTEN_API_DSN when you need a durable HTTP
access trail.
# .env — opt into persistent API logFASTEN_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 — single source across every SDK
fasten.codes.load("fleet.codes.yaml")
Retention applies to all three streams.
Syslog + API log also have ring-buffer size caps:
FASTEN_SYS_RING_SIZE · FASTEN_API_RING_SIZE
Agents call tools. Tools touch resources. Who audits that today? Nobody.
LangSmith, OTel, OpenAI Assistants — LLM-ops or timing tools, not
typed audit. fasten fits natively: every tool call
is an audit row — actor / target / method / correlation.
Multi-agent workflows
Agent A calls agent B calls a tool. One request_id threads the chain.
Cost accounting
Every TOOL_CALLED row carries detail.cost_usd. Sum by actor for billing.
Regulated AI
Clinical-summariser touches PHI → audit row with pii_in_detail, GDPR retention.
Agent debugging
"Why did the agent say X yesterday?" One request_id query pulls the trail.
language coverage
Wire it once. Read it anywhere.
The audit row schema, the 7 anchors, the request_id
contract — identical across implementations. A row written in Go
reads cleanly from Python. A Rust service feeds a Node + TypeScript report.
Pythonreference
pip install fasten
Gousable
git clone & install from source — module path lands with v1.0 GA
C++14single-header
#include "fasten.hpp"
Node + TypeScriptbeta
npm install @nerdapplabs/fasten
Rustbeta
cargo add fasten
Javacoming soon
sh.fasten:fasten — coming soon
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.
Registry publish lands with the v1.0 GA tag.
advanced · optional
Beyond HTTP — cross-transport correlation.
HTTP is table stakes. fasten's wedge is the transports HTTP-only tools skip:
MQTT — industrial / IoT / pub-sub.
scheduler — autonomous, cron-fired jobs.
Each is an opt-in shim. Most adopters never need these.
MQTT round-trip
industrial · IoT
mqtt_shim.publish() auto-injects
_req into the payload. The subscriber's
mqtt_shim.handler() reads it back into ctx. Every log
the subscriber emits during handling carries the same id.
# 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() into payload
# 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.log.info("setpoint_received")
fasten.emit(code="DEVICE_SETPOINT_UPDATED", ...)
# operator — one query stitches both services$ fasten tail --stream audit --request-id abc
SETPOINT_REQUESTED api-server actor=admin target=dev-42
DEVICE_SETPOINT_UPDATED device-42 actor=system # same id ✓
Scheduler shim
autonomous jobs
Cron-fired jobs have no external caller. Without a shim, every
scheduled audit row has null or freshly-minted request_id.
The scheduler shim mints scheduler-<run_id> at
job start, stashes it in ctx.
@scheduler.cron("0 */6 * * *")
@fasten.shim.scheduler.instrumentasync def nightly_cleanup():
# request_id = "scheduler-a1b2c3d4"
fasten.emit(code="RETENTION_SWEEP_RAN", ...)
fasten.log.info("cleanup_done")
# all rows for this run share the same id
Custom transport (gRPC, NATS, AMQP, Kafka)? Write a 10-line shim following the same read-id-from-wire / set-ctx / call-downstream pattern.
what it replaces
Honest split. No overclaiming.
Stack today
Replaced?
Where it lands
Jaeger · causal debugging
✓ yes
One request_id query pulls every row from every service.
Jaeger · span-level waterfall
✗ no
Keep Jaeger. request_id ↔ trace_id so they coexist.
Loki · per-node audit search
✓ yes
The mountable /logs/audit reader queries the durable store directly. Fleet-wide aggregation lands with fasten Cloud (in development).
Loki · fleet syslog at scale
✗ no
Ship stdout to Loki downstream if you need it.
Docker logs
kept
stdout stays the contract. Used through /logs/sys reader.
Homegrown audit tables
✓ yes
Native AuditRepository. 7 anchors enforced.
origin
Why we built this
01
The setup.
An industrial edge platform — many services, many languages — MQTT where HTTP can't reach.
Gateway · Python
Fleet manager · Go
Sync relay · Go
Connectors · Node + Go
02
The recurring question.
"This event fired at 14:02 — show me what happened next across all these services."
Answer, every week: grep + timestamp math + cursing.
03
What we tried — in the order we tried it.
Our log libraries.
structlog and slog format beautifully — but they're loggers, not auditors.
No typed shape, no correlation contract.
Both still ship in our stack; fasten sits alongside.
OpenTelemetry.
Traces threaded HTTP cleanly. The moment we crossed into MQTT,
scheduler-fired jobs, or a deploy pipeline, we were propagating
context by hand — and OTel still wouldn't commit to a typed audit row.
Our own audit tables.
Worked. Then drifted. Three services, three shapes, no shared
retention contract — the classic in-house path.
Buying it.
Splunk cost more than the rest of our infrastructure combined.
Enterprise compliance tools were opaque black boxes — we
couldn't audit the auditor.
None of these, alone or stitched, covered the gap:
typed audit threaded across every transport, with one correlation key.
04
So we built the missing piece.
5 Ws + H enforced at the type level.
One request_id threaded across every transport.
A storage contract portable across six languages.
Open-source because:
Regulated buyers won't adopt a closed audit library.
Our moat is the paid data plane — fasten Cloud.
The community writes adapters we'd never ship ourselves.
faq
Common questions.
Do I have to replace my existing logger (structlog / slog / pino)?
No. fasten sits alongside your existing logger. Keep using structlog / slog / pino / winston for free-form application logs; use fasten.emit() for typed audit events. fasten.log.* is optional — it mirrors the idioms of each language (kwargs in Python, positional pairs in Go, object-first in pino) if you want one logger across both streams.
How is this different from OpenTelemetry?
OTel is deliberately untyped at the semantic layer. fasten commits to a typed audit shape and ships shims for transports beyond HTTP (MQTT, scheduler). The two coexist — keep OTel for span-level tracing; use fasten for the typed audit row + cross-transport correlation. A direct request_id ↔ trace_id bridge is on the roadmap, not shipped.
Do I need fasten Cloud to use the SDK?
No. The SDK is Apache-2.0 and complete on its own. fasten Cloud is the commercial fleet-aggregation + compliance-report layer; single-node and small-fleet deployments use the SDK alone.
Is there a daemon, sidecar, or agent to run?
No. fasten runs in your process — no fastend, no sidecar, no required hosted service. The bundled CLI is a debug tool, not a background process.
Is this production-ready?
v1.0.0-beta. The reference implementation runs in our own platform; the public API is the one that ships at v1.0 GA. Safe for pilots, evaluators, internal tools today. Audit-store failure handling shipped queue-by-default in v1.0.0-beta across all five SDKs (background drainer with exp backoff; emit() stays off the request path). One known caveat before betting critical compliance reporting on it: Postgres fleet-scale validation lands with v1.1, not v1.0 GA. Tracked publicly in the issue tracker.
What about GDPR / HIPAA / regulated-industry compliance?
The SDK gives you the primitives: per-code pii_in_detail flag forces minimum retention (GDPR minimisation), secret-key redaction runs before emit, audit rows carry actor / target / method / timestamp in a defensible typed shape. Generated compliance reports (HIPAA, SOC 2, GMP, ISO 26262, FSSC 22000) are planned for fasten Cloud, which is in development — the SDK is built to feed it.
What does the SDK cost?
Nothing. Apache-2.0, no seat limits, no per-emit charges, no free-tier gotchas. The paid layer (fasten Cloud) is in development; pricing will be published once it ships.
Why not LangSmith or Langfuse for AI-agent audit?
LangSmith locks you to LangChain. Langfuse is close in scope but LLM-ops-first. fasten coexists — you can ship rows to both; they answer different questions.
What's the performance overhead?
~0.1–0.5 ms per emit() with SQLite WAL. ~1–5 ms with Postgres. Ring buffers use ~8 MB per service at 10 k lines. No background goroutines or threads — all synchronous in the calling thread. High-volume codes can be sampled via short retention class + periodic sweep.
How do I contribute?
fasten is open-source at github.com/nerdapplabs/fasten. Issues + PRs are welcome; a CONTRIBUTING guide is in flight and will land before the v1.0 GA tag.