Designing the audit row: what every voice agent should write back to your CRM.
The audit row is the single most important artifact a voice agent produces. It is not the transcript and it is not the reasoning trace. It is a structured record, written into the operator's system of record, that answers 'what happened on that call?' without anyone having to listen to it. Here is the field model we ship and the design choices behind it.
Every voice call your agent handles produces three artifacts. The transcript is the verbatim text of what was said. The reasoning trace is the model's internal log of what it thought and why. The audit row is the structured record a human will read later to answer 'what happened on that call?' Those three artifacts are not interchangeable, and most CX-AI vendors ship the first two reasonably well while neglecting the third. The audit row is the artifact your operations team, your compliance team, and your dashboard all depend on, and it is the one most often missing or malformed.
We think of the audit row as the load-bearing data structure of a voice deployment. The transcript is the body of evidence; the audit row is the index. A triage lead at 9 AM does not have time to read transcripts. They open the row, scan eight or nine fields, and either close the case or pull the transcript on the cases that look anomalous. If the row is missing fields, or the fields are vague, or the row was written to a vendor's log instead of the operator's CRM, the triage workflow falls apart and the operator goes back to listening to calls. We have seen this happen at three production deployments before we tightened our own field model. This post is the field model we shipped, the rationale behind each field, and the principles that hold it together.
What the audit row is, and what it is not
The audit row is a single structured record per call, written into the operator's CRM after the call completes, summarizing the call's business outcome in a form a non-technical reviewer can read in under thirty seconds. It includes who called, what they wanted, what the agent did, which tools fired, what the model decided, who is responsible, and the timestamps needed to reconstruct the call's shape. It does not include the verbatim transcript (that is a separate artifact, linked by ID). It does not include the model's reasoning chain (also separate, also linked). It does not include raw audio (linked, never embedded).
The separation matters because each artifact has a different consumer. The transcript is read by the small number of cases where the triage lead wants to verify what the agent said in the agent's own words. The reasoning trace is read by the engineering team when debugging a model behavior. The audit row is read by everyone else, on every call, every day. Conflating them produces a record nobody can use efficiently: too long for triage, too summarized for debugging.
The audit row is the index. The transcript is the body of evidence. A triage lead opens the index, not the evidence, and the index has to be good enough to make that the default.
The fields we ship, and why each one is on the row
Here is the canonical audit-row shape we write back to the operator's CRM. The field names are normalized; the actual field mapping is per-CRM (HubSpot, Salesforce, ServiceTitan, Epic, Mindbody, Tekmetric each take a different target schema), but the universal shape is consistent across deployments.
{
// who called
"caller_id": "+14155551234", // E.164 phone, or customer_id if known
"caller_record_id": "crm_contact_8421", // CRM contact ID if matched, else null
"anonymous": false, // true if no match and no consent to record PII
// what they wanted
"intent": "reschedule_appointment", // controlled vocabulary, per-tenant
"intent_confidence": 0.94, // classifier confidence at call end
// what the agent did
"actions": [
{
"type": "appointment.reschedule",
"status": "completed", // completed | failed | declined | escalated
"target_record_id": "appt_55129", // pointer into the CRM, not a copy
"details": { "from": "2026-05-26T14:00Z", "to": "2026-05-28T10:00Z" }
}
],
// which tools fired (with success / failure)
"tools_called": [
{ "name": "lookup_contact", "status": "ok", "latency_ms": 142 },
{ "name": "fetch_availability", "status": "ok", "latency_ms": 318 },
{ "name": "reschedule_appointment","status": "ok", "latency_ms": 461 }
],
// what the model decided
"model_decisions": [
{
"moment": "intent_classification",
"decision": "reschedule_appointment",
"confidence": 0.94,
"alternatives": [
{ "label": "cancel_appointment", "confidence": 0.04 },
{ "label": "ask_about_pricing", "confidence": 0.02 }
]
}
],
// accountability
"method": "ai_agent_v3.2.1", // the actor in the literal sense
"responsible_party": "user_492", // the human operator whose CRM permissions were used
"operator_override": null, // populated if a human overrode mid-call
"escalation": null, // populated if call escalated; see shape below
// timing
"timestamps": {
"call_started": "2026-05-24T09:14:02Z",
"first_agent_word": "2026-05-24T09:14:03Z", // perceived latency anchor
"first_tool_call": "2026-05-24T09:14:11Z",
"call_ended": "2026-05-24T09:15:48Z"
},
// links to the heavy artifacts (never embedded)
"transcript_ref": "tx_8a91f2",
"reasoning_trace_ref": "rt_8a91f2",
"audio_ref": "au_8a91f2"
}Most of these fields look obvious in isolation. The reason we are writing the post is that the obvious ones get cut by vendors who treat the audit row as an afterthought, and the consequences only surface in production triage. The caller identifier needs the anonymous flag because regulated tenants cannot store a phone number if the caller did not consent, and a triage lead has to know whether the blank caller field is a missing identifier or a consent-suppressed one. The intent confidence has to be on the row because a low-confidence intent on a high-impact action is the signature of a misrouted call, and the triage workflow keys off exactly that signal.
The tools-called array has to record both success and failure, because failure modes are where the agent's behavior actually diverges from the model's intent. A tool that returned an error and got retried is a different call than one where the tool returned a stale result and the agent acted on it. The model_decisions field has to include alternatives, not just the picked answer, because the buyer-side question is rarely 'what did the model do' and almost always 'why did it not do the other thing.' An audit row that records only the chosen branch loses the answer to the more useful question.
Method versus responsible_party: the two-actor split
The fields we are most careful about are method and responsible_party. They look redundant. They are not. The method field names the literal actor: which agent build, what version, what model. The responsible_party field names the human whose CRM permissions were used to fire the action and whose name is on the record from the compliance perspective. For an autonomous voice agent under operator-led architecture, the method is some identifier like ai_agent_v3.2.1 and the responsible_party is the user account inside the operator's tenant that owns the deployment.
Splitting these two fields lets the audit story stay consistent with the legal story. The compliance officer who pulls the row in a regulatory review reads responsible_party first. The engineering team that pulls the same row to debug a model regression reads method first. The buyer who wants to track agent versions in their dashboard groups by method. The buyer who wants to track operator-level activity groups by responsible_party. Collapsing the two into a single 'actor' field saves nothing and breaks both queries.
The method field names the agent build. The responsible_party field names the human whose authority the agent acted under. Both belong on the row, and they are not interchangeable.
Escalation: the most important shape on the row
When the agent escalates, the audit row carries the full escalation context as a substructure. This is where the most expensive audit failures we have seen in the wild happen. A vendor logs the escalation as a flag (escalated: true) without recording the reason, the recipient, the handoff transcript, or the recommended next step, and the operator inherits a queue of escalations they cannot triage. We treat escalation as a first-class field with its own schema:
"escalation": {
"reason": "clinical_question_outside_scope", // controlled vocabulary
"trigger": "low_confidence_on_response", // why the agent escalated
"confidence_at_escalation": 0.41, // the value that crossed the threshold
"recipient": "queue_clinical_triage", // who picks it up
"context_handoff": "summary_ref_8a91f2", // pointer to a structured handoff doc
"recommended_next_step": "callback_within_2h" // what the agent thinks should happen next
}The recommended_next_step field is the one that surprises buyers most. A voice agent that just punted to a human without a recommendation produces a queue the human has to triage from scratch. A voice agent that hands off with 'this caller has a clinical question about a prescription interaction; book a callback with a clinician within two hours' produces a queue the human can act on. The recommendation is not authoritative; it is an opinion the model commits to on the way out. The compliance posture is clean because the field is labeled clearly. The operational lift is significant because the human starts from somewhere instead of nowhere.
Schema mapping: the universal shape, the per-CRM target
The shape above is the universal audit-row contract. Every voice agent should produce it, regardless of which CRM the operator runs. The vendor's job is the translation layer that maps the universal shape into the customer's CRM schema. HubSpot wants a Note attached to the Contact with structured properties; Salesforce wants a Task with custom fields; ServiceTitan wants a Job Note linked to the customer record; Epic wants a structured intake document; Mindbody wants a Visit Note on the client profile; Tekmetric wants a customer-history entry on the RO. None of these CRMs share a schema. All of them can receive the universal shape with field-level mapping defined per-tenant.
The mapping work is real engineering. It is also the work most vendors skip, which is how you end up with audit data that lives in the vendor's dashboard and a CRM that only knows the call happened. The right architectural commitment is that the vendor's log is telemetry (kept for observability, retained on a short window) and the CRM is the record of authority (kept by the operator, on the operator's retention schedule, under the operator's compliance posture). Putting the audit row in the vendor's log instead of the operator's CRM is the same architectural error we wrote about in our sibling post on CRM-as-source-of-truth: the data layer becomes the moat, and the operator pays for that moat for as long as they stay on the contract.
The pitfalls we see in the wild
The most common pitfall is writing audit data only to the vendor log. The operator's CRM gets a generic Note that says 'AI handled this call' and the vendor's dashboard gets the structured row. If the operator leaves the vendor, the audit trail leaves with the vendor. The second pitfall is writing audit data to the CRM as a free-text Note instead of structured fields. A free-text Note is unqueryable. The third is recording outcomes without recording the alternatives the model considered, which makes any 'why did the agent decide that' investigation guesswork. The fourth is omitting the operator-of-record, which leaves the row unattributable for compliance purposes.
The unifying principle behind all four pitfalls is that the vendor implicitly treats their own data layer as the system of record. The audit row becomes the vendor's asset rather than the operator's. The fix is structural: the CRM is canonical, the vendor log is telemetry, and the field model is rich enough that the operator can answer the questions that matter without ever opening the vendor's dashboard. That posture is harder for the vendor to ship (forty CRM targets, forty mapping layers, forty release pipelines), and it is the architectural choice that makes the audit row trustworthy.
The principle in one sentence
The audit row is the place where the AI is the actor and the operator is the responsible party. The customer's CRM is the canonical store; the vendor's log is telemetry. If the row that ends up in the CRM cannot answer 'what happened on that call' in under thirty seconds, the design failed regardless of how good the model was on the call itself. The audit row is the deliverable. Everything else is supporting evidence.
Operator-led AI
The architectural choice to keep your team as the protagonist and the AI as their amplifier. The category lens Vorel was built around.
Read the guide
