Quickstart · June 7, 2026

Tamper-Evident Logging for AI Agents in 10 Minutes

Most agent logs can be edited after the fact and nothing in the record shows it. This walkthrough adds cryptographically signed, tamper-evident logging to any agent — and ends with you breaking the seal yourself so you trust it for the right reason: because you checked, not because we said so.

What "tamper-evident" means here

Tamper-evident is not the same as tamper-proof. We are not claiming a log that cannot be changed. We are claiming a log where any change is detectable. Every event is hashed and signed; each event carries the previous event's hash, forming a chain; the chain's terminal hash is sealed with an Ed25519 signature. Edit one byte, drop one event, reorder two — the chain breaks and verification returns a failure. The cryptography is classical (HMAC-SHA256 + Ed25519); it is not quantum-resistant, and we will not pretend otherwise.

Minute 0–3: install and capture a run

Install the binary (statically linked, runs offline), then wrap whatever command launches your agent:

# Wrap your existing agent entrypoint — no code change required
steelspine capture -- python my_agent.py

# Or any command:
steelspine capture -- node agent.js --task "summarize tickets"

That single wrap records the run's events into a signed chain. Nothing about your agent's logic changes; capture sits around it.

Minute 3–6: produce the audit artifact

Turn the captured run into a report a human auditor can read and a machine can verify:

steelspine verify-run --compliance-html > audit.html
# → opens to a per-event list, the chain status, the key fingerprint,
#   and a VERIFIED verdict

To hand the evidence to someone outside your environment, pack it into a self-contained, signed bundle:

steelspine pack-create <run-id> -o run.spine.tgz
steelspine pack-verify run.spine.tgz       # → VERIFIED ✓

Minute 6–10: break it yourself

This is the part that earns the trust. You do not need the SteelSpine binary to verify a signed run — you need the public key and about twenty lines of standard-library Python. Verify a run, then corrupt one byte and watch the verdict flip:

from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
from cryptography.exceptions import InvalidSignature
import hashlib, json, base64

pub = Ed25519PublicKey.from_public_bytes(base64.b64decode(PUBLIC_KEY_B64))
events = json.load(open("events.json"))

# Recompute the hash chain
prev = b""
for ev in events:
    payload = json.dumps(ev["data"], sort_keys=True).encode() + prev
    h = hashlib.sha256(payload).digest()
    assert base64.b64decode(ev["hash"]) == h, "CHAIN BROKEN at %s" % ev["id"]
    prev = h

# Verify the Ed25519 seal over the terminal hash
try:
    pub.verify(base64.b64decode(SEAL_SIG), prev)
    print("VERIFIED ✓")
except InvalidSignature:
    print("TAMPERED !")

Run it: VERIFIED ✓. Now open events.json, change a single character in any event's data, save, and run it again: CHAIN BROKEN or TAMPERED !. That is the whole promise, demonstrated in your own terminal in under a minute.

The point of publishing the verifier is that you should not have to trust the vendor. If the verification logic lives in twenty lines of stdlib you can read, the integrity claim stands on math you control, not on our reputation.

What you now have

The honest limit

The signing key is held by the operator. So the chain proves no one tampered with the log after capture without the key, and it lets a third party verify integrity — but it does not, on its own, prove the operator could not have generated a different log to begin with. That is a key-custody question (HSM, KMS, third-party timestamping), and it is a separate problem from the logging format. We flag it up front because the property you should rely on is the one that is actually true.

Ten minutes, your machine, no signup

The free tier runs the full capture-and-verify flow locally. Download a sample signed run, verify it with the snippet above, then try to break it.

Start free   Full getting-started guide →