Implementation worksheet · 6 min read
An Audit Trail Specification for Autonomous Campaigns
Record nine fields per action: what was done, by which agent and version, on whose authority (autonomous under a named policy, or approved by a named person), to which audience with the resolved count, at what time, with what content reference, which eligibility and suppression rules were evaluated and their outcomes, what the agent's stated reason was, and whether it succeeded. Write it in business language rather than as a trace, because the people who read it are answering 'why did this customer get this message' rather than debugging a run. Retention has to outlast the complaint window, which is usually longer than your logging default.
Teams adopting marketing agents usually have observability — traces, token counts, latency. What they do not have is the artefact a compliance question needs. 'Why did this customer receive a promotional message three days after opting out' is not answerable from a span tree, and the gap surfaces at the worst moment: when someone external is asking.
Put it into practice
1. Separate the audit trail from the trace, deliberately
Same event, two records, two audiences, two retention policies. The trace is engineering data and can expire in weeks. The audit trail is a business record and must outlive it. Trying to serve both from one store means either paying to retain debug data for years or losing the record you actually needed.
2. Record the authority for every action, not just the action
Autonomous under policy X version 3, or approved by a named person at a timestamp. 'The system sent it' is not an authority. This single field is what turns a log into an audit trail, and it is the one most often missing because it requires the permission matrix to exist first.
3. Store the resolved audience count and the segment definition as evaluated
Not the segment name — names stay the same while definitions change. Capture the count and a reference to the definition version used at send time. Reconstructing 'who was in this segment last March' from a name is not possible, and that is precisely the question that gets asked.
4. Log the eligibility and suppression rules that ran, with outcomes
Which rules were evaluated, how many each excluded, and the total. A rule that silently stopped matching is invisible without this, and 'the suppression was applied' is a much weaker claim than 'these six rules ran and excluded 412, 0, 38, 7, 0 and 2 accounts'.
5. Capture the agent's stated reason in its own words
Why it chose this audience and this timing. It is not proof of anything — a model's explanation is a generated artefact, and the record should say so — but it is highly useful for spotting the run where the reasoning went somewhere unexpected while the output still looked fine.
6. Set retention from the complaint window, not the logging default
Consent disputes and regulatory questions arrive months later. Decide the period deliberately against the longest window that applies to you, and make sure the record survives a log rotation that was configured for debugging. Then test that you can actually retrieve a specific send from the oldest retained period — an audit trail nobody has ever queried is a hypothesis.
The audit record
Copy this structure into your review document and record your observed result for each row.
| Field | Example | Why it is there | Set |
|---|---|---|---|
| Action | campaign send | what happened | |
| Agent and version | lifecycle-agent v4.2 | which behaviour produced it | |
| Authority | autonomous under policy v3 / approved by role at time | the field that makes it an audit trail | |
| Audience definition version | segment def #118 | names change, definitions must not | |
| Resolved audience count | 4,812 | the number that exposes a broken filter | |
| Timestamp | ISO, with timezone | reconstructing sequence | |
| Content reference | template id + version | what they actually received | |
| Rules evaluated + outcomes | 6 rules, 459 excluded | proves suppression ran | |
| Agent's stated reason | free text, marked as generated | spotting odd reasoning | |
| Result | sent / failed / partial | did it complete | |
| Retention period | set from complaint window | not the logging default |
A failure worth checking
Discovering at the moment of a complaint that the audit data expired. The trace store had a 30-day retention set by whoever configured observability, the question arrives at day 95, and the honest answer becomes 'we cannot reconstruct it'. That answer is much worse than an unflattering one — it converts a single customer complaint into a question about whether the company can account for its own automated messaging at all.
Common questions
Is a model's stated reason evidence?
No, and the record should label it as generated text rather than as a cause. It is useful for triage and pattern-spotting, not for establishing why something happened. The authority field, the rule outcomes and the resolved count are the parts that carry evidential weight.
Who should be able to read the audit trail?
Whoever answers customer and regulatory questions, which usually means support and compliance rather than only engineering. If reading it requires a query language, it will not be read, and the record will be reconstructed by guesswork under time pressure.
Basis and scope
This is a proposed implementation method using illustrative examples, not a measured benchmark or a customer case study. Prepared with AI assistance. Validate product-specific behavior against current documentation and your own test environment.