An AI agent can remember a 30-page conversation and still perform the same action twice.
It sends a request. The connection times out. The agent r...
For further actions, you may consider blocking this person and/or reporting abuse
@polycratia I lean towards adjustments as first-class allocations, acting as explicit compensating transactions.
Versioning the basis with explicit supersession keeps the core logic conceptually clean, but in practice, you want the ledger to reflect when a correction became known just as much as what the new truth is.
Yes, treating adjustments as first-class allocations forces downstream consumers to understand them to maintain the "sum of allocations equals repayment" invariant. But hiding that complexity behind supersession often leads to downstream systems silently holding onto stale truths. If a downstream service is calculating payouts or tax, they absolutely need to see the discrete adjustment event to trigger a recalculation or re-filing, rather than just silently drifting from a magically rewritten history.
It pushes the complexity to the consumer, but the alternative—silently breaking external state because a consumer missed a supersession—is usually a much more dangerous failure mode.
Spot on about the difference between principle and practice with explicit feedback. Relying on someone to manually report a failure means you only hear about the most frustrating ones, and you miss the silent degradation entirely.
The receipt layer changes the game because it makes certainty a mechanical baseline rather than relying on human motivation. The lane split forces the agent to prove its certainty mechanically before it takes the unattended lane. I'd love to hear how it changes your operational metrics once you ship that version.
That's a fantastic point about refunds and the intent fingerprint needing to carry the originating transaction. Treating the originating transaction as part of the idempotency key instead of just the payload is exactly how you prevent that invisible duplicate.
I love the parallel to regulated payments—the idea that
outcome_unknownhas a regulatory clock attached to it makes perfect sense. The stakes are higher, but the required state machine is exactly the same. Thanks for sharing that lesson!That refund case is a strong counterexample to fingerprints built only from amount, payee, and a time window. The originating transaction is the causal identity; without it, a legitimate second refund and an accidental replay are indistinguishable.
I would scope the refund intent around the original transaction ID, refund sequence or reason, amount, currency, recipient, and the originating workflow step. The point that the counterparty is happy either way is especially useful: monitoring may never expose the duplicate, so correctness has to come from the operation identity rather than a complaint. Thanks—this is exactly the kind of failure mode the simplified payment examples miss.
Thanks for reading and for this great breakdown, Mikhail!
That "Verify-On-Read" layer you described is a fantastic parallel on the read side. Refusing to collapse an unproven claim into a false binary is exactly the principle that prevents hallucinated drift.
On the intent fingerprint "trap" case: you've hit on a critical nuance. If the fingerprint is too shallow (e.g. just endpoint + entity ID), two distinct operations in close sequence (like modifying status vs updating metadata on the same record) could collide or match the wrong prior intent. In our design, the fingerprint hashes the canonicalized semantic parameters (normalized payload fields, mutation type, and account namespace) rather than just structural tags. But your suggestion to write a dedicated regression test specifically testing near-duplicate operations with identical targets and distinct payloads is spot on—I'm adding that exact test case to our validation suite.
Regarding atomic writes: we treat torn/uncommitted temporary files as distinct from network transport
outcome_unknown. If an atomic rename never completes (e.g., node crash before replace), the original receipt remains unmodified, and the orphaned temp file triggers an explicit recovery log upon next startup rather than a generic network timeout error. Keeping that distinct helps isolate process crashes from external system unreliability.Really appreciate the thoughtful observations!
Thanks a lot for the insightful comment, Edward!
You're 100% right on the connection to idempotency keys. Framing it as the local half of the classic at-least-once + server-side deduplication contract makes the architectural lineage immediately clear to anyone who has worked with payment ledger systems. When external APIs natively support an idempotency key header, reconciliation is trivial; when they don't, our local intent fingerprinting and readback verification have to do the heavy lifting to bridge that gap safely.
Your second point about audit trails is crucial: when reconciling an
outcome_unknown, we preserve the state transition history (submitted -> unknown -> confirmed_succeeded) in the receipt record rather than overwriting it in place with a naivesucceeded. Knowing that an action lived in an ambiguous state for hours before reconciliation explains potential downstream race conditions or duplicate reviews during post-mortems.That principle—never destroying the audit trail of what was believed to be true while it was happening—is just as vital for agent state as it is for knowledge graphs. Checked out your work on Mnemoverse; great to see others thinking seriously about rigorous state boundaries in agent systems!
This is the write-side mirror of something I've been experimenting with on the read-side. I built a Verify-On-Read layer that challenges memory claims against live git HEAD at retrieval time — VERIFIED/REFUTED/INCONCLUSIVE instead of trusting whatever's stored. Your outcome_unknown is doing structurally the same job for actions: refusing to collapse "I can't prove it happened" into a binary success/fail.
One thing from my experiments that might be relevant to your reconciler design (step 2, "match the smallest safe intent fingerprint"): I hit a version of this on the read side that I didn't expect. My verification anchors were structural (file path, import string, token presence), and a claim got marked VERIFIED just because the token existed somewhere in the codebase — right token, wrong subject. Same shape of bug you'd get if two operations share an intent fingerprint that's precise enough to look unique but isn't precise enough to actually distinguish them. I only closed it by widening the anchor from "token present" to "surrounding context matches" — recall went up 4-11x once the model could see real evidence instead of a bare pattern string. Might be worth a "trap" case in your reconciler tests: two near-duplicate operations, same type, same target, different payload, and see if the fingerprint actually tells them apart.
Also curious about the atomic write detail — you mention swapping the receipt via a temp file so a crash mid-update doesn't leave a half-written JSON. Did you consider making that itself the source of truth for outcome_unknown — i.e., if the reconciler ever finds a receipt in a torn/uncommitted state, does it get treated as its own distinct case, or does it collapse into the same outcome_unknown bucket as a transport failure?
Writing the receipt before the request is the detail that makes this work, and it's the one people skip. Write it after and the exact failure you built it for is the failure that stops it existing.
Your four layers map almost exactly onto what regulated payments forces you to keep, except we're not allowed to treat the last one as optional. outcome_unknown has a regulatory name and a clock attached to it.
To your closing question: the hardest write to reconcile after a timeout is a refund. A payment has a natural fingerprint, amount plus payee plus a narrow window, and duplicates are loud because someone complains. A refund to the same customer for the same amount is a completely plausible genuine second event, the counterparty is happy either way, and nobody reports it. So the intent fingerprint has to carry the originating transaction, not just the transfer details. We learned that the expensive way.
The four-layer split is the useful part here, and the line you draw between Attempt and Effect is the one most agent frameworks never draw at all. Worth saying out loud: those two layers are not "memory" in the sense the category usually means, which is exactly why they get dropped.
One thing that might save your readers a search. Your intent_fingerprint is an idempotency key, and naming it that connects this post to a large body of settled practice. Payment APIs worked this out years ago, and the underlying result is worth knowing on its own: exactly-once delivery across a network is not achievable, so what you actually build is at-least-once delivery plus a server-side dedupe on that key. Your outcome_unknown is the honest local half of that contract. If the external system also accepts the key, reconciliation gets much cheaper than hunting for an external_id afterwards.
The second thing I would add comes from the knowledge side of memory, because the failure mode turns out to be identical. When reconciliation finally resolves an outcome_unknown, keep the ambiguity in the record rather than overwriting the state. A receipt that reads "submitted, unknown for six hours, then confirmed succeeded" answers a question later that a bare "succeeded" cannot: whether the duplicate someone found in the external system came from you. Updating in place is cheaper right up until the audit.
That is the same argument that applies to ordinary agent memory, where replacing a fact instead of closing it destroys the explanation for everything built while it was true. I wrote that one up here rather than repeating it in a comment. The parallel is the interesting part: your Attempt and Effect layers are evidence in the strongest sense, and evidence records earn their keep by staying complete.
Disclosure: I work on Mnemoverse, a memory service, so I think about the knowledge half of this daily. Your half, the one about effects in other systems, is the half my own field keeps ignoring.