Skip to content
allsrc.dev
Go back

Your Agent's Most Important Actions Are The Ones It Didn't Take

Six weeks after go-live, somebody asks: “the agent issued a refund on order 90311 in June. On whose authority? Did it try anything else first?”

You have three things, none of which answer the question. Application logs rotated at 14 days. The APM trace was sampled at 5% and this run was not in it. The chat transcript shows the final message but not the two tool calls the policy engine denied on the way there.

And those denials are exactly what is being asked about. The agent’s most important actions are often the ones that did not happen, and nothing recorded them.

TL;DR

Observability and audit are different systems with incompatible requirements:

ObservabilityAudit
Coveragesampledcomplete
Mutabilitymutable, aggregatedappend-only
Retentiondays to weeksyears
Answers“why is it slow?”“who is accountable?”

Making one system do both gets you a log that is neither debuggable nor admissible. So: a hash-chained, append-only record of every decision and every policy verdict, where each record commits to the previous one — and which deliberately does not contain the model’s reasoning.

OWASP: ASI10 (Rogue Agents) and the evidence layer for everything else. Runnable code: patterns/governance/decision_trace_audit/

The run, reconstructed

uv run python -m patterns.governance.decision_trace_audit.demo
=== WITH the pattern: the run, reconstructed ===
  support-agent-7 started a run in prod: 'Customer 4521 was double-charged, sort it out'
    proposed execute_sql(query='SELECT email, total FROM orders WHERE customer_id = 4521')
    executed execute_sql (result db8c7f3d6813e279)
    proposed execute_sql(query='DELETE FROM orders WHERE id = 90311')
    DENIED execute_sql — write operations are not permitted (DELETE detected)
    proposed issue_refund(order_id='90311', amount=49)
    executed issue_refund (result f39d2dfd2738cba8)
    run completed

  chain verification: True (chain intact)

=== what is NOT in the log ===
  model's prose in log: False
  card number in log:   False

=== someone edits the log to hide the denied DELETE ===
  chain verification: False (record seq=11 does not follow the previous record)

The pattern

trace = DecisionTrace(path=Path("audit/2026-09.jsonl"))
harness = Harness(model, tools, hooks=[broker, gate, trace])

1. Record decisions, not deliberation. Chain-of-thought never enters the log. Model prose is reduced to {"proposed_tools": [...]}. Storing an agent’s private reasoning creates a discovery liability and a PII surface with no corresponding audit value — what matters is what it asked to do and what the policy layer said.

This is the design decision I would push hardest on, because the instinct runs the other way. “Log everything, we might need it” feels prudent and produces a corpus your legal team will regret. Every extra field is retention cost and breach surface. If a field would not be quoted in an incident review, drop it.

2. Denials are first-class records. tool_denied and tool_paused are the highest-value rows in the log: they are the evidence your controls fired. An audit system that only records successes tells you nothing about your security posture.

3. Results are digests, not payloads. Tool output is stored as a truncated SHA-256, so you can prove “this is the result the agent acted on” without warehousing customer data.

4. Tamper-evident ≠ tamper-proof. Anyone who can rewrite the whole file can recompute the whole chain. The chain only becomes proof when the head is published somewhere the agent’s credentials cannot reach — a WORM bucket, your SIEM, a notary, the next day’s opening record. I have seen this oversold as “immutable audit” more than once; it is not immutable, it is detectable, and the difference is where you put the head.

When to use it

When NOT to use it

Trade-offs and failure modes

Frequently asked questions

Why not use my existing SIEM directly?

Do — as the sink. The pattern is about what you emit and how it is chained, not where it lands. The reason to chain before shipping is that a SIEM ingest pipeline is a place records can be dropped or reordered, and a chain makes that detectable rather than invisible.

Is a blockchain needed for tamper-evidence?

No. A hash chain plus an externally-published head gives you the property that matters, at roughly zero operational cost. A distributed ledger buys consensus among mutually distrusting parties, which is not the problem — you are one party trying to prove your own log was not edited.

What about the model’s reasoning for explainability?

That is a legitimate need and it belongs in a different, shorter-lived store with its own access controls, if you keep it at all. Conflating “explain this decision to a user” with “prove this decision to an auditor” is how reasoning traces end up under legal hold.

How much does this cost to run?

Negligibly. It is a SHA-256 and an append per audit-relevant event, and the demo runs in milliseconds. The real cost is storage and retention policy, which is a governance conversation rather than an engineering one.

References


Part of the agent harness and governance series. Next: cost and tool budgeting — why detection is not a control when the thing being detected is money.



Previous Post
The Prompt Injection That Waits Until Tomorrow
Next Post
You Will Find Out About The Runaway Agent From The Invoice