Voice Agent Dynamic Context Testing: Mutation Template

Sumanyu Sharma
Sumanyu Sharma
Founder & CEO
, Voice AI QA Pioneer

Hamming has 10M+ mins protected across voice-agent QA workflows.

September 25, 2026•Updated September 25, 2026•12 min read
Voice Agent Dynamic Context Testing: Mutation Template

Voice agent dynamic context testing proves that an agent uses the right context value at the right turn, especially when the value changes while the call is still running.

A static FAQ agent with no personalization, tools, handoffs, or changing backend state does not need this template. A few multi-turn transcript tests are enough.

This guide is for agents where context changes what the system may say or do: eligibility becomes approved, an order status changes, a human adds a note, a tool returns a new account state, or a handoff moves the call into a different policy boundary. The transcript can sound coherent while the agent acts on yesterday's value.

TL;DR: Treat dynamic context as versioned state, not prompt decoration.

  • Name the source, trust level, version, effective boundary, and expiry for every context value.
  • Test initial, missing, updated, delayed, duplicated, handed-off, and expired context.
  • Assert both the expected behavior and the forbidden action after each mutation.
  • Save the update receipt, decision turn, tool arguments, spoken response, and final state together.
  • Block CI when stale context can change identity, money, compliance, routing, or a backend write.

Scope and sources: This is a proposed test contract with synthetic examples, not a report of measured production outcomes. The provider notes cite public LiveKit, Retell, and Vapi documentation checked on September 25, 2026. Each runtime exposes a different update boundary; adapt the fixture to the boundary your adapter can observe.

Last Updated: September 2026

Related Guides:

What Is Dynamic Context in a Voice Agent?

Dynamic context is data that influences a voice agent's decisions and may differ by call, caller, turn, workflow state, or time. It includes account status, identity confidence, order state, policy version, available appointment slots, transfer destination, and data returned by tools.

Dynamic context testing verifies which context version was visible when the agent made a decision. Here is the trap: the call ends with eligibility=approved, but the agent still refused the caller at a decision boundary where the approved version should already have been effective.

That timing distinction is the whole problem. A refusal before the update became effective can be correct; a refusal based on stale state after that boundary is a failure. We call this context time travel: the final record looks correct, but the behavior came from a stale point in the call.

Call variables can be ordinary fixtures when a name or campaign label stays fixed after the greeting. Once a tool, webhook, supervisor, or handoff changes the correct answer mid-call, the test must also model when that change becomes effective.

Define the Context Contract Before You Test

Do not begin with a caller script. Begin with the context contract.

FieldQuestion to answerSample valueFailure if missing
KeyWhat stable name identifies the value?account.eligibilityTests assert the wrong field.
SourceWhich system produced it?account serviceCaller speech gets mistaken for backend truth.
TrustHow may the agent use it?verified, claimed, derived, advisoryUntrusted text drives a sensitive tool.
VersionWhich revision does the value belong to?account-v18A later snapshot hides a stale read.
Observed atWhen did the runtime receive it?turn_06 + 184msOrdering cannot be reconstructed.
Effective boundaryWhich decision may first use it?next LLM turnThe test expects an impossible same-turn update.
ExpiryWhen must the runtime refresh it?after payment attemptOld eligibility survives a state change.
Allowed usesWhich prompt, branch, or tool may read it?refund eligibility checkContext leaks into unrelated behavior.
Forbidden usesWhat must never depend on it?identity authorizationA convenient field becomes a security boundary.

Separate values by trust. A phone number from the telephony layer, an account tier from your backend, and a caller saying "I am a premium member" are three different facts even if they contain the same words.

The caller identity checklist covers that boundary in depth. For dynamic context, the rule is simpler: the mutation record must retain the source and trust label alongside the value.

Which Dynamic Context Cases Should Every Voice Agent Test?

Start with seven cases. Add domain-specific cases only after these pass.

CaseMutationExpected behaviorForbidden behaviorEvidence to retain
Initial valueLoad plan=standard before greetingAgent uses standard policy from turn oneNo premium promiseFixture hash, first prompt version, first response
Missing valueOmit account_idAgent asks for safe recovery or hands offNo guessed lookup keyMissing-key event, recovery branch, tool ledger
Mid-call updateChange eligibility=pending to approved after tool resultNext eligible decision uses approved stateNo stale refusalUpdate receipt, effective turn, response, tool args
Delayed updateHold the update until after one decision boundaryAgent follows documented old-state policy, then changes laterNo nondeterministic branchSend time, receive time, decision timestamps
Duplicate updateDeliver version v18 twiceOne logical mutation, no repeated side effectNo duplicate booking or messageDedupe key, receipts, side-effect count
HandoffTransfer from intake to specialist with case_id and consent=trueDestination receives allowed context and continues correctlyNo lost consent or cross-role leakageSource and destination versions, handoff receipt
Expired valueAdvance beyond the value's TTLAgent refreshes or blocks the actionNo write from expired stateExpiry timestamp, refresh attempt, blocked action

The negative assertion matters. “The agent used the updated value” is weaker than “the agent used version v18 and did not execute the action allowed only by v17.” The second statement tells you what regression the test will catch.

For workflows with customer-specific rules, pair each row with the customer-specific rule matrix. The context fixture should identify the tenant and rule version, but this template owns when that state becomes effective.

A Copyable Dynamic Context Test Fixture

Keep the fixture small enough to review in a pull request. This illustrative format needs an adapter in your test runner; it is not a provider API payload.

id: refund_eligibility_mid_call_updateowner: voice-platformrisk: blockinginitial_context:  - key: account.eligibility    value: pending    source: account-service    trust: verified    version: account-v17    effective_at: call_start    expires_after: payment_attemptmutation:  id: eligibility_update_18  trigger: tool_result  trigger_ref: verify_recent_payment  key: account.eligibility  from: pending  to: approved  source: account-service  trust: verified  version: account-v18  expected_effective_boundary: next_agent_decision  delivery:    delay_ms: 347    duplicate_count: 1caller_turns:  - "I paid this morning. Can you check again?"  - "Am I eligible for the refund now?"assertions:  context_version_at_decision: account-v18  spoken_outcome: refund_eligible  allowed_tools:    - create_refund_review  forbidden_tools:    - deny_refund    - issue_refund_without_confirmation  side_effect_count:    create_refund_review: 1evidence:  retain:    - context_mutation_receipt    - conversation_turns    - trace    - tool_call_ledger    - final_sandbox_state

The 347ms delay is illustrative, not a measured provider latency. Here, duplicate_count: 1 means one additional delivery of the same mutation ID. Run the fixture at delays on both sides of the decision boundary, then keep the smallest set that reproduces the risk.

Store this beside your tests-as-code suite. If a provider adapter changes its update semantics, the fixture diff should show which expected boundary changed and why.

How Do You Prove a Context Update Took Effect?

Capture one evidence envelope across the update, agent decision, and downstream action. Do not reconstruct it from separate dashboard screenshots.

{  "testRunId": "ctx_run_2026_09_25_0047",  "callId": "synthetic_call_0047",  "traceId": "4bf92f3577b34da6a3ce929d0e0e4736",  "mutationId": "eligibility_update_18",  "contextKey": "account.eligibility",  "source": "account-service",  "trust": "verified",  "previousVersion": "account-v17",  "newVersion": "account-v18",  "mutationSentAtMs": 18420,  "mutationObservedAtMs": 18767,  "timebase": "test_runner_monotonic_ms_since_call_start",  "effectiveBoundary": "next_agent_decision",  "decisionTurnId": "turn_08",  "decisionStartedAtMs": 18810,  "contextVersionAtDecision": "account-v18",  "spokenOutcome": "refund_eligible",  "toolCalls": [    {      "name": "create_refund_review",      "contextVersion": "account-v18",      "idempotencyKey": "ctx_run_2026_09_25_0047:create_refund_review"    }  ],  "forbiddenToolsObserved": [],  "finalSandboxState": {    "refundReviewCount": 1  }}

Use the same run ID in the transcript, mutation receipt, trace, tool ledger, and sandbox query. The OpenTelemetry guide explains how to correlate those records across services.

The timestamps above share one test-runner clock. Do not subtract unsynchronized clocks from different services. Capture contextVersionAtDecision at prompt assembly or the equivalent runtime boundary; a tool's later version or an update receipt cannot establish what the model saw. If the adapter cannot observe that boundary, mark the decision evidence unavailable and keep the result inconclusive.

Context mutation evidence is complete only when it connects the versioned update to the exact decision turn and the resulting action. An update receipt without the decision is delivery evidence, not behavior evidence.

If the action writes data, verify the final state using the sandbox testing runbook. A correct spoken response does not prove the backend used the same context version.

Test Ordering, Retries, and Handoffs

Ordering deserves a separate test even when every required field is present.

SymptomLikely causeDiagnosticTest correction
Agent uses old value for one turnUpdate arrived after prompt assemblyCompare observed time with decision startDefine the next legal effective boundary.
Agent flips back to old valueOut-of-order deliveryCompare monotonic versions, not timestamps aloneReject versions older than the current one.
Tool runs twice after update retryDelivery retry lacks a stable mutation IDCount receipts and side effects by idempotency keyReplay the same update and assert one write.
Handoff loses a fieldDestination gets a filtered or rebuilt contextCompare source and destination allowlistsAssert required keys and forbidden keys separately.
Caller correction is ignoredConversation-derived value loses to a stale cached fieldInspect source precedence at the decision turnTest correction, conflict, and confirmation policy.
Sensitive value reaches the promptTool-only context was added to model-visible historyInspect prompt and tool request independentlySplit model-visible and tool-only context channels.

Do not fix these tests with arbitrary sleeps. A sleep can make the race disappear without defining correctness. The test should wait for an observable receipt or intentionally exercise both sides of the boundary.

Handoffs need two assertions: required context arrives, and forbidden context does not. Use the handoff testing runbook for destination, summary, and receipt checks, then add the context version checks from this page.

Where Do Providers Expose Context?

The test contract is shared. The capture point is not.

RuntimeDocumented context surfaceTesting implication
Retell dynamic variablesPer-call variables can be used in prompts, messages, tools, transfer settings, and webhooks; user-provided values are strings.Test missing values, string parsing, defaults, and every field where substitution affects behavior.
Retell live call updateAn ongoing call can receive fields_to_override.override_dynamic_variables and call_control.additional_context.Capture the update receipt and define the first decision allowed to observe the change.
LiveKit external dataInitial context can be loaded before session start. In STT–LLM–TTS pipelines, on_user_turn_completed can retrieve context before the next LLM response.Test initialization and turn-time retrieval separately; do not assume that hook is available for every realtime model path.
Vapi variablesCall-start overrides populate variables used by prompts and messages, alongside built-in call and transport identifiers.Verify the exact call fixture and keep provider-generated identity separate from caller claims.

None of these docs promises one universal update latency or precedence rule. That contract belongs to your adapter, so write it down and keep a regression case for it.

What This Template Cannot Prove

This template has a hard limit: it cannot tell whether the source data was true. It can only show that the runtime received a version and behaved according to the contract you declared.

That proof has three costs:

  • More observability can expose more data. Keep secrets and sensitive fields out of model-visible history when the agent only needs them for a tool request.
  • Deterministic timing is limited. Streaming speech, network delivery, and prompt assembly can overlap. Define a visible boundary instead of promising an exact millisecond.
  • Fixtures drift. A perfectly repeatable fixture can still encode last month's policy. Give context contracts owners and refresh dates.

Caller corrections and verified backend state need a policy per key. A shipping-address correction and an identity claim have different risks. Encode that choice explicitly instead of hiding it in a global precedence list.

Dynamic Context Release Checklist

  • Every context key has a source, trust label, version, effective boundary, expiry, and owner.
  • The suite covers initial, missing, updated, delayed, duplicated, handed-off, and expired values.
  • Each test names the expected behavior and a forbidden behavior.
  • Tool arguments record the context version used at execution time.
  • Retried mutations use a stable ID and do not duplicate side effects.
  • Handoff tests assert required and forbidden context separately.
  • Model-visible context is separated from tool-only or storage-only data.
  • Context-driven writes run against a sandbox, mock, or scoped test target.
  • Failures retain one run ID across transcript, mutation receipt, trace, tool ledger, and final state.
  • Identity, eligibility, money, compliance, routing, and side-effect context cases block CI.

When a real call reveals stale or misordered context, add it to the failed production call regression runbook. Fixing the prompt once is not a regression strategy.

Frequently Asked Questions

Treat each context change as versioned state with a source, trust label, effective boundary, and expiry. Hamming's template tests seven cases: initial, missing, updated, delayed, duplicated, handed-off, and expired context, with an expected behavior and forbidden action for each.

A dynamic context fixture should contain the initial value, source, trust label, version, mutation trigger, delivery delay, expected effective boundary, caller turns, and action assertions. Hamming recommends retaining five linked artifacts: mutation receipt, conversation turns, tool ledger, trace, and final sandbox state.

Send a versioned update at a known trigger, record when the runtime observes it, and assert the first decision allowed to use the new value. Hamming's template also reruns the case with delayed and duplicate delivery so one successful immediate update does not hide ordering or idempotency failures.

Capture the context version at prompt assembly or the equivalent decision boundary and beside every context-dependent tool call. Compare it with the version effective at that boundary. A mismatch or stale-only action fails the test; if the decision version cannot be observed, retain an inconclusive result rather than infer it from the final state.

Caller-provided context should be labeled as claimed or conversation-derived, not silently promoted to verified backend state. Hamming recommends separate policies for each key so a low-risk preference can update directly while identity, payment, and authorization fields require stronger proof.

Capture the source context version, destination context version, handoff receipt, and allowlist of keys that may cross the boundary. Hamming's template requires two checks: all required context arrives, and every forbidden or role-inappropriate field stays out.

Block CI on context changes that control identity, eligibility, money, compliance, routing, handoffs, or backend side effects. Keep lower-risk personalization and long-tail timing combinations in scheduled suites, but retain at least one delayed and one duplicate-update case for every critical workflow.

Sumanyu Sharma

Sumanyu Sharma

Founder & CEO

Previously Head of Data at Citizen, where he helped quadruple the user base. As Senior Staff Data Scientist at Tesla, grew AI-powered sales program to 100s of millions in revenue per year.

Researched AI-powered medical image search at the University of Waterloo, where he graduated with Engineering honors on dean's list.

“At Hamming, we're taking all of our learnings from Tesla and Citizen to build the future of trustworthy, safe and reliable voice AI agents.”