cd /news/ai-agents/show-hn-mnestiq-tamper-evident-logs-… · home › topics › ai-agents › article
[ARTICLE · art-154551] src=github.com ↗ pub= topic=ai-agents verified=true sentiment=↑ positive

Show HN: Mnestiq – tamper-evident logs of what an AI agent saw and did

A developer released Mnestiq, an open-source "flight recorder" for AI agents that produces tamper-evident, signed records of what an agent saw, decided and did, aimed at incident response rather than dashboards. Mnestiq hash-chains records using RFC 8785 canonical JSON and SHA-256 and periodically seals them with Ed25519-signed Merkle checkpoints under RFC 9162 trees, so edits, deletions, insertions and reordering fail verification. The tool captures provenance tags for system, user, model, tool_output, retrieved_doc, web, agent_msg and memory context, records outbound HTTP and raw TCP connections with local and remote IP and port, and ships as a drop-in SDK wrapping the Anthropic and OpenAI clients plus a `mnestiq proxy` mode that records model calls without code changes.

read9 min views4 publishedOct 21, 2026
Show HN: Mnestiq – tamper-evident logs of what an AI agent saw and did
Image: Michielbdejong (auto-discovered)

A flight recorder for AI agents. Tamper-evident, signed evidence of what an agent saw, decided and did, built for incident response rather than dashboards.

When an agent emails the customer list to an attacker, observability traces tell you that it happened. Evidence tells you which run and step did it, that the instruction came from a web page rather than the user, that an auto-approve policy waved it through, and lets you prove none of that was edited after the fact.

firewall: 10.0.4.17:53120 -> 198.51.100.9:25 (50 bytes)   [no agent identity]

mnestiq:  agent=support-bot run=c7831ff7 step=6 sandbox=container:7f3a9c operator=acme-helpdesk
          untrusted input earlier in the run: step 4: web via fetch_page
          authorized by policy:email-auto-approve (policy): recipient not checked against customer domain
  • Attribution: every event carriesrun_id ,step_id ,parent_run_id ,agent_id ,operator_id andsandbox_id (auto-detected for containers and k8s pods).
  • Egress identity: outbound HTTP getsX-Mnestiq-Run-Id /X-Mnestiq-Egress-Id headers, and every outbound requestand raw TCP connection (SMTP, databases, sockets) is recorded with its local and remote IP and port: the join key to flow and firewall logs.
  • Provenance: every piece of model context is tagged at capture time:system ,user ,model ,tool_output ,retrieved_doc ,web ,agent_msg ,memory . That's what makes prompt-injection analysis possible afterwards.
  • Tamper evidence: records are hash-chained (RFC 8785 canonical JSON, SHA-256) and periodically sealed by Ed25519-signed Merkle checkpoints (RFC 9162 trees). Edits, deletions, insertions and reordering fail verification.
  • Redaction that keeps the proof: strip prompts, tool arguments and tool output before handing evidence to an auditor; the chain still verifies.
  • Drop-in SDK capture: wraps the Anthropic and OpenAI clients (sync and async, Chat Completions and Responses APIs). Recording fails open: it never breaks the agent.
  • No code changes needed:mnestiq proxy records an agent's model calls from outside it, whatever language or framework it is written in. Point its base URL at the proxy.
pip install "mnestiq[anthropic]"     # or mnestiq[openai]
mnestiq keygen --out keys
python
import anthropic
from mnestiq import FileSink, Recorder
from mnestiq.keys import load_private_key
from mnestiq.egress import instrument_egress
from mnestiq.integrations.anthropic import instrument_anthropic

recorder = Recorder(FileSink("evidence.jsonl"), agent_id="support-bot",
                    operator_id="acme-helpdesk", signing_key=load_private_key("keys/signing.key"))

@recorder.tool(source="web")          # output of this tool is untrusted web content
def fetch_page(url: str) -> str: ...

client = instrument_anthropic(anthropic.Anthropic(), recorder)
instrument_egress()                   # stamp + record outbound connections during runs

with recorder.run(effective_permissions={"email": "send"}):
    client.messages.create(model="claude-sonnet-5-5", max_tokens=1024, messages=[...])

recorder.close()                      # writes the final signed checkpoint
mnestiq verify evidence.jsonl --trusted-key keys/signing.pub
mnestiq inspect evidence.jsonl
mnestiq redact evidence.jsonl evidence.redacted.jsonl

A key file the agent can read is fine for trying it out. In production, keep the key where the agent can't read it: signer=SignerClient.from_env() with mnestiq signer serve running under another account, or signer=AzureKeyVaultSigner(...). See docs/signing.md.

mnestiq proxy evidence.jsonl --agent support-bot --key keys/signing.key
export OPENAI_BASE_URL=http://127.0.0.1:8787/openai/v1      # and/or
export ANTHROPIC_BASE_URL=http://127.0.0.1:8787/anthropic

The agent runs unchanged. Every model call it makes is written to evidence.jsonl, signed. What the proxy can and cannot see, and how to deploy it so an agent can't go around it: docs/proxy.md.

A scripted support agent reads a poisoned web page and emails customer data out. You then investigate the evidence it left behind.

git clone https://github.com/mnestiq/mnestiq && cd mnestiq
python -m venv .venv && . .venv/bin/activate        # Windows: .venv\Scripts\activate
pip install -e python httpx
python examples/injection_incident.py --out evidence
mnestiq dashboard evidence --trusted-key evidence/incident.pub
mnestiq dashboard evidence/ --trusted-key keys/signing.pub

A local investigation view at the link it prints (http://127.0.0.1:8765/?key=..., whose key keeps other accounts on the machine out): runs and sub-runs, a step-by-step timeline with every piece of model context colored by provenance, findings (below), the verification status of every record (sealed, unsealed, or tampered), and a connection finder: paste an ip:port from a firewall or flow log and jump to the agent step that opened it. It live-updates while agents write.

Rule Name Fires when
MNQ-001 untrusted argument A tool call's arguments carry an email address, URL, account number or long number that appears in untrusted content and nowhere in the system prompt or the user's messages.
MNQ-002 auto-approval An action was approved without a human after untrusted content reached the model, and the approved call carries a value from that content. A policy approving a routine reply is not a lead.
MNQ-003 egress after taint An outbound connection (not to loopback) followed untrusted content reaching the model.
MNQ-004 untrusted sensitive action A sensitive action (credentials, permissions, deletion, cancellations, account changes, payments, bookings) ran with an argument value that appears in untrusted content and not in the user's or system's words. Mark tools with @recorder.tool(sensitive=True) . Unmarked tools are judged by name.
MNQ-005 untrusted link in answer The model's answer carries an image, or a link with data in its query, to a host that appears in untrusted content and not in the user's or system's words. An image is fetched as soon as the answer is shown, so it can carry data out with no tool call.
MNQ-006 internal data to new recipient A tool sent data from the company's own systems (tool output labelled internal ) to an address or host that appears neither in those systems nor in the system prompt, such as the price list to a "new customer". No injection is needed.

MNQ-001 is the signature of a hijack: an injected instruction has to put the attacker's address, account or URL into a tool call. MNQ-004 covers hijacks with no destination, such as "change the password to ...", "delete file 13" or "cancel order 4100". MNQ-005 covers leaks through the answer itself, such as an injected image link that carries the customer's email address. MNQ-006 covers data handed to someone who simply asked for it. Findings describe what was observed, not a verdict: an agent that visits a link a colleague posted triggers MNQ-001 too. The same rules are available in Python as mnestiq.findings.

It stays on your machine: it binds to loopback only, rejects non-local Host headers, renders evidence strictly as text, and its Content-Security-Policy forbids the page from or contacting anything but the local server.

To see it with live data (no API keys needed):

python examples/live_agent.py --out evidence           # keeps a scripted agent running
mnestiq dashboard evidence --trusted-key evidence/live.pub

See examples/injection_incident.py for a full incident: an injected web page leads to exfiltration over a raw TCP connection; a firewall log entry is traced back to the exact agent step; a tampered approval is caught; a redacted copy is shared.

The recorder runs inside your agent, so it is built to never be the thing that breaks it.

Concern Behaviour
Recording fails (disk full, closed sink, odd values) The agent carries on. Failures are counted in recorder.failures , logged on themnestiq logger, and warned about once. Passstrict=True to raise instead, if an unrecorded action is worse than a failed one.
Process dies mid-write On restart, FileSink moves the torn last line to<file>.torn-<time> (never deleted), the chain resumes from the last complete record, and anote event records the recovery. A write that fails part-way is truncated, so the file never holds half a record.
Huge prompts or tool output Values over max_content_bytes (default 1 MiB) are stored as their hash only and listed inx-omitted ; the chain still verifies.
Worker threads Threads don't inherit the active run. Wrap work with run.bind(fn) , e.g.pool.submit(run.bind(fetch), url) .
Signer down (signing service or Key Vault) Records are still written. The failure is counted and signing is tried again after retry_after seconds, covering everything since the last checkpoint.
Recorder stopped or killed Recorder(..., checkpoint_interval=300) writes and signs aheartbeat when nothing was signed for 5 minutes. A recorder that stopped then shows as a gap:verify warns about any silence longer than the heartbeats allow, and wherever checkpoints are sent (aHeadFile , the Evidence Vault) can alert while it is happening.
Timestamp authority down The checkpoint is still signed and written; only the RFC 3161 token is skipped, with a warning.
Power loss FileSink(path, fsync=True) forces each record to disk before the agent continues.
Someone deletes the end of the file Recorder(..., on_checkpoint=HeadFile("/other/disk/agent.head.json")) keeps the latest checkpoint in a second place;mnestiq verify evidence.jsonl --head /other/disk/agent.head.json then reports a file cut back to an earlier checkpoint.
Sensitive content New evidence files are created owner-read/write only on Linux and macOS. On Windows a file takes its folder's access list, so keep evidence in a folder only the agent's account can read. Use mnestiq redact before sharing.
Large evidence in the dashboard The dashboard verifies the whole file but shows the most recent 20,000 records.
Path What
spec/SPEC.md The evidence format, hashing and verification rules.
spec/schema/ JSON Schema (2020-12) for records.
python/ Recorder, verifier and CLI.
examples/ Runnable demos.

The format makes evidence tamper-evident after it is written. It does not make a compromised host tell the truth at write time. On its own, a file can be cut back to any earlier checkpoint without detection; keep the latest checkpoint somewhere else with HeadFile and verify with --head to close that. Keep the signing key out of the agent's reach, in a signing service or Azure Key Vault (docs/signing.md), so it can't be copied and used to re-sign history. A signature only means something if you pin the recorder's public key with --trusted-key; otherwise someone who rewrites the whole file can re-sign it. Keys change only by a signed hand-over. See SPEC.md, sections 2 and 6, and the threat model: every attack in it is a test, and you can try your own on the break-it kit.

v0.2 draft. Done: spec, verifier, Python recorder, Anthropic + OpenAI capture, egress identity propagation (httpx, requests, raw TCP), local dashboard, signing outside the agent (signing service, Azure Key Vault) with signed key hand-overs, heartbeats, RFC 3161 timestamps from public authorities, checked by the verifier, and a model API proxy that records agents in any language from outside them.

Next:

  1. External anchoring of checkpoint heads; S3 Object Lock sink.
  2. Automatic run propagation into executors (today: run.bind ); aiohttp; subprocess egress.
  3. OpenAI Agents SDK and LangGraph capture; streaming output capture.
  4. TypeScript SDK, an MCP proxy, and an OpenTelemetry (GenAI) trace receiver.
  5. Evidence test vectors for other implementations; Merkle inclusion proofs for selective disclosure.
cd python
python -m venv .venv && source .venv/bin/activate    # Windows: .venv\Scripts\activate
pip install -e ".[dev,anthropic,openai,azure,timestamps]" httpx requests ruff mypy
pytest
ruff check src tests
mypy src

The test suite never touches the network beyond this machine: tests/_netguard.py blocks any connection or DNS lookup that isn't loopback, so a test that tried would fail rather than quietly reach the internet.

Please report vulnerabilities privately; see SECURITY.md.

Apache-2.0. See LICENSE.

── more in #ai-agents 4 stories · sorted by recency
── more on @mnestiq 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/show-hn-mnestiq-tamp…] indexed:0 read:9min 2026-10-21 · —