Wytness sits between your AI agent and your compliance team. The agent emits a record of what it just did; the compliance team gets a cryptographically verifiable, queryable, retention-managed trail of every action. The space between the two is where the engineering lives. This post walks the full path of a single event, from the moment it leaves your agent to the moment an auditor pulls it back out years later.
The shape of one event
An event is whatever a tool call looks like in your environment: the agent invoked send_email, or refund_order, or create_pull_request. Wytness wraps that call. The record we keep includes the agent identifier, the human operator on whose behalf the agent acted, the tool name, the parameters (PII-redacted, secrets stripped), the response (truncated and redacted), success or failure, duration, error code, model version, prompt hash, and the cryptographic glue that proves none of it has been changed.
Cryptographic glue is three layers, not one. The Microsoft Agent Governance Toolkit (AGT) running inside your agent signs every event with an Ed25519 identity key bound to the agent's DID, then adds the event to a SHA-256 Merkle hash chain so tamper with one entry and every entry after it stops verifying. On top of that, the Wytness wrapper SDK signs a per-event envelope with a second Ed25519 key you control (your non-repudiation key), and the signature covers the canonical JSON of the event's identity, timestamp, type, and payload. Neither private key ever leaves your side; Wytness only holds the public halves you register against your org.
The path from agent to archive
Once the SDK has built and signed an event, the journey looks like this:
SDK signs event with local Ed25519 private key └─ POST /ingest (api.wytness.ai) with Authorization: Bearer wyt_… └─ ingest: dispatch on Content-Type ├─ application/vnd.wytness.agt+json (wrapper mode, wytness SDKs) ├─ application/cloudevents-batch+json (direct AGT batch) └─ application/cloudevents+json (single CloudEvent) └─ wrapper mode only: verify per-event envelope signature against the org's registered Ed25519 public key └─ subscription + quota gate └─ per-event write to ClickHouse audit_events (per-org partition) └─ batch fan-out: await archive_events_batch(rows) ├─ archives JSON to Blob audit-archive (7-year retention) └─ on archive failure: raw body → poison-events (DLQ) └─ returns 202 Accepted { accepted, rejected[] }One endpoint, one synchronous dispatch. The SDK signs. The API verifies the envelope, gates on quota, writes the row to ClickHouse, and then fans the batch out to Blob in a single non-blocking call. A 202 means the row is durably in ClickHouse and queued for archive; an archive failure routes the raw body to the poison-events container and an anomaly alert fires.
Why two stores, not one
Hot and cold storage do different jobs and have different physics. ClickHouse is the query layer: column-oriented, fast aggregation, the engine behind every chart and filter in the dashboard. It holds the hot tier: 30 days on Starter, 90 days on Growth, 7 years on Business. Blob is the cold tier: append-only by application convention, all events retained for 7 years regardless of plan while the subscription is active, so the record outlives the rolling hot window; on cancellation the archive is handed over as signed Evidence Packs and then deleted after a 30-day hold.
The contract between them is deliberate. ClickHouse can be truncated, reindexed, migrated, or rebuilt from Blob without affecting the durable record, because the durable record is in Blob. Blob is the thing your auditor's auditor would ask to see. ClickHouse is the thing your security analyst queries on a Tuesday morning. Rebuild is a real operation rather than a theoretical one: we have measured the path end-to-end against a populated tenant, with a recovery time of around six and a half minutes for ten thousand events.
Three-way reconciliation
Tamper-evidence catches modification. It does not catch absence. An attacker who could simply prevent an event from ever reaching the API would leave no chain break behind, because there would be no break to leave; the missing event would not exist.
Wytness counters this with three-way reconciliation. A background integrity checker continuously compares three independent counters: what the API says it ingested, what ClickHouse says it stored, and what Blob says it archived. Drift between any two surfaces an anomaly alert before a human ever notices. The checker runs on every tenant, every four hours, and writes its results into a dedicated table that the dashboard surfaces as a status banner.
The signing model
The hardest part of any audit story is the trust model. Who is trusted to say "this event happened" and how do you prove they had the authority to say it? Wytness answers with a delegated signing model:
- You generate the keypair. Open the Keys page in the dashboard, click Generate on the signing card; the private half is created in your browser, offered for download, and the public half auto-registers against your org. The private key never leaves your infrastructure.
- You register the public key. The browser flow above does this for you. Until a signing public key exists against your org, the ingest endpoint rejects wrapper-mode events with a 401 Unauthorized. There is no implicit trust, no TOFU.
- The API verifies, every time. Every wrapper-mode event is verified against the registered public key whose fingerprint matches the envelope's key_id, before the row is written. A signature failure rejects the event at the boundary; AGT-side identity and the AGT Merkle hash chain are also checked downstream. Events that pass verification are written to ClickHouse with the envelope and the AGT chain marker stored alongside.
- Multi-key rollover. An org can register multiple signing keys at once, allowing a clean rotation without a flag day. Each envelope carries the fingerprint of the key that signed it; the API looks up the matching registered key per event.
The customer-storage option
Most customers are happy with their data sitting in our managed ClickHouse and Blob. Some (usually those whose own customers ask them where the data lives) want the durable record in their own infrastructure. The Business tier supports exactly that: per-organisation routing, configured in the dashboard, sends both the hot store and the archive to the customer's own ClickHouse cluster and Blob container. Credentials are AES-256-GCM encrypted with the org identifier bound as additional authenticated data. The platform code reaches the right store for each tenant via a cached client lookup; no application path bypasses the routing.
What the platform deliberately does not do
Equally important is the list of things Wytness cannot do, and that the design enforces, not just the policy:
- We cannot forge an event in your name. We do not hold your signing private key. A backdoored Wytness server could fabricate a row in ClickHouse, but the row would not verify against your public key, and any auditor running the chain check would see it.
- We cannot silently drop an event. Three-way reconciliation makes a missing event a visible anomaly within one integrity-check cycle.
- Rewrites are detectable and operationally reversible. The Blob archive is append-only with long-term (7-year) retention and a soft-delete recovery window. Every event is Ed25519-signed and hash-chained, so any overwrite or delete is cryptographically detectable and hash-chain breaks surface as anomalies in your dashboard. Customer-hosted storage (Business+) can opt into Azure Blob immutability_policy on the customer's own bucket for full write-once treatment.
The bar this clears
The shape above is what regulated industries (financial services, healthcare, anything that ever ended up in front of a court) have spent decades converging on. We adapted it for AI agents because the engineering shape is the same: an actor takes an action, a record is produced, the record needs to be trustworthy years later.
If you want to dig into the layers below this (the SDK contract, the data model, the storage topology), head to the docs. If you want to see what it looks like running against a real agent, the Framework Integrations guide is the shortest path from pip install wytness-ai to your first verified event.