A log file is a story someone has to piece together. A row per action, with the evidence the agent had when it acted, is a witness you can call. We built Magistry on the second model: every action the agents take is a row, and that row is the audit trail. Rows are not pruned; they go only when the product, variant or store they describe is deleted.
That sounds like a database design choice. It's actually a trust choice, and it's the one that decides whether an operator can hand the store keys to an agent and still sleep. The rest of this post is the case for why.
Provenance: the answer is a row, not a vibe
When the operator asks 'why did we pull that product?', the honest answer in most AI tools is 'the model thought it was a loser.' That's not an answer you can take to a budget review. In Magistry the answer is a row — with the trigger that fired, the evidence snapshot the agent acted on, and the cost confidence it had at that exact moment.
{
"id": "a1b2c3d4-9f2e-4b7a-8c1d-6e5f4a3b2c1d",
"store_id": "0f8e7d6c-…",
"product_id": "5b4a3c2d-…",
"from_state": "ACTIVE",
"to_state": "OPTIMIZING",
"trigger": "roas_below_floor",
"evidence": {
"action": "optimize_loser",
"roas_30d": 0.8,
"reasoning": "ROAS under the store floor for 30 days; rewrite before discounting."
},
"cost_tier_at_decision": "A",
"applied_to_shopify": true,
"status": "applied",
"acted_at": "2026-07-02T06:14:09Z"
}Notice what's stamped on the row: the trigger that fired, the evidence snapshot, and the lifecycle transition it made. The row is self-describing. Six months later, no one has to reconstruct intent from logs and Slack threads. The intent is the record.
Reversibility: every verb has a known inverse
Every action comes from a fixed enum, and every verb in that enum has a known inverse: a draft or vault republishes the product, a discount_test restores the original price, an optimize_loser restores the previous content version. To reverse, the revert executor reads the row, derives the inverse from evidence.action, applies it, and stamps reverted_at and reverted_by on the row.
This is the difference between 'we can probably undo that' and 'undo is a mechanical operation on a row you can point at.' Because there is no free-form action, there is no action whose undo is a guess.
A dashboard lets you ask what the system thinks now. A decision row lets you ask what it knew then. Audits only ever care about the second question.
— Magistry
Why mutability is where audits go to die
Tier-A cost confidence at decision time is not the same as Tier-A at read time. Costs get restated, suppliers change, a SKU gets recategorized. If the tier is looked up at read time, the tier you read today is not the tier the agent acted on — and now your audit is fiction with good formatting.
- The tier is stamped on the row when the decision was made — what the agent saw is what the auditor sees.
- The row is updated as the action moves on: approved_at, applied_at, failed_at and reverted_at are stamped on the same row as the trigger that started it.
- Access is controlled by row-level security — each store reads only its own rows.
The cost we pay for it
Keeping every row isn't free. Storage grows with every action, and every action has to be written through one path that records it, which is slower to build than letting each agent call the platform directly. We took those costs deliberately. Storage is cheap; a board member's trust is not.
If you only remember one thing: autonomy is not 'the agent is smart enough to trust.' Autonomy is 'every move the agent makes is a fact you can read, verify, and reverse.' A row per action is how you keep that promise even when no one is watching.
