Part 4 of 18 · 7 min read
Confirmation is part of the workflow
Proposal revisions, atomic idempotency and reconciliation after an uncertain write.
Arunank Sharan · Kuzushi Labs ·
In this article
An assistant can understand a request and still be unready to execute it. “Move the appointment to Tuesday” needs an identified appointment, an actual destination slot and an opportunity to inspect the change.
We represented that boundary with a pending action. I think of it as the proposal the user is reviewing: a named operation, its parameters and the plan behind it.
Save the proposal before asking for approval
The shared agent pipeline first extracts parameters and prepares a plan. If the plan requires confirmation, it returns a pending action and preview cards instead of running the plan immediately.
The pending action includes an identifier, description, parameters, risk level and the saved plan. The session manager stores it in Redis with a default lifetime of ten minutes. The assistant then presents explicit confirm and cancel controls.
On confirmation, the backend retrieves the proposal, checks that it belongs to the supplied session, reconstructs the plan and invokes the agent's execution path. Cancellation has its own path. This gives the interaction more structure than treating “yes” as an unrestricted new instruction.
The mechanism is present in the code; whether a particular business operation completes still depends on its tool adapter and service contract.
A preview must describe what will execute
The difficult detail is keeping the displayed proposal, any edits and the executable plan aligned. If the user changes the appointment time, the saved steps must use that changed time. Editing a display field or a top-level parameter dictionary is not enough when execution reconstructs its arguments from a separate plan snapshot.
My preferred contract is simple: a proposal has a version, and the confirmation identifies that version. An edit produces a new validated proposal. Execution consumes the same arguments the user reviewed.
That versioned contract is a refinement I would add to the current implementation. It makes stale confirmations and competing browser tabs easier to handle, and it gives the audit trail a precise answer to “what did the user approve?”
Collect missing information as state
The project also defines multi-step workflows with required slots. The appointment workflow describes patient resolution, availability and booking. Registration describes a duplicate check followed by record creation. Communication has recipient resolution followed by message preparation.
A slot is the piece of information needed to continue: patient identity, practitioner, date, start time or end time. Tracking those fields explicitly is more reliable than asking the model to rediscover the unfinished task from a growing transcript.
The workflow manager can identify missing slots and advance a step when its requirements are satisfied. Session methods can store an active workflow. However, the main chat path does not yet connect all of that machinery into a complete resumable workflow. A defined state machine and an integrated state machine are different milestones.
The useful next step is to make one full journey work across several messages: ask for a patient, resolve ambiguity, choose an available slot, show the final proposal, confirm and return the persisted appointment.
Confirmation does not replace execution controls
A confirmed request still needs authorization, input validation and current business-state checks. A slot available during preview may be occupied by the time the user clicks. A repeated click should not create another booking.
The current implementation retrieves a pending action, runs the plan and then removes it. That ordering alone does not establish protection against two concurrent confirmations. A production completion path needs a way to claim the action once and make retried writes safe.
This is also why I would distinguish three outcomes in the response: proposed, completed and failed. The absence of an exception is insufficient if a service returned a business failure inside an otherwise valid response.
Bind the click to an immutable proposal revision
The original confirmation path retrieves a pending action, checks its session, reconstructs a plan and executes it. There is an important seam: modifications are applied to action.parameters, while execution steps are reconstructed from the separately stored preview plan. Those two representations can diverge.
My reference design stores one canonical plan. The preview renders that plan, and the confirmation refers to its revision and digest:
{
"proposal_id": "proposal-001",
"revision": 2,
"plan_hash": "server-computed-sha256",
"idempotency_key": "confirm-request-001"
}
The client does not send a new executable plan when confirming. The server loads the saved revision and compares the digest. A digest detects a different proposal; it is not a signature, authentication mechanism or substitute for server-side permission checks.
The lab uses deterministic JSON encoding before hashing:
def canonical(value):
return json.dumps(
value, sort_keys=True, separators=(",", ":"), allow_nan=False
)
def digest(value):
return hashlib.sha256(canonical(value).encode()).hexdigest()
This is sufficient for the lab's server-generated strings and integers. A multi-language signing protocol needs a formally specified canonicalization format and number handling; I would not promote this helper into one by implication.
Put idempotency and the write in one transaction
The reference confirm operation checks permission, loads the actor-owned proposal, looks for an existing idempotency record, validates the revision and expiry, rechecks current resource versions, then writes the appointment, outbox event and cached result in one transaction.
proposal = lab.propose(actor, "p-001", "s-001", now=100)
result = lab.confirm(
actor,
proposal["proposal_id"],
proposal["revision"],
proposal["plan_hash"],
key="confirm-request-001",
now=110,
)
If the response is lost, repeating that request returns the saved result. Reusing the key for a different proposal is a conflict. A new key does not allow an already executed proposal to run again. These are three distinct behaviors worth testing.
For the local demonstration, SQLite's BEGIN IMMEDIATE serializes writers. A PostgreSQL implementation would lock the proposal and relevant slot rows, keep a uniqueness constraint on the idempotency key and retry transactions only under a defined policy. I would never hold those locks while waiting for a model or remote provider. PostgreSQL documents the transaction lifetime of row locks; the application still has to choose a consistent lock order.
Handle the case a transaction cannot cover
A local booking and local outbox event can commit together. An external EHR booking cannot join that database transaction. That requires a persisted operation state before the connector call, an external business identifier and a reconciliation path.
| Connector outcome | Local state | Next step |
|---|---|---|
| Definitive rejection | Failed or conflict | Show the reason; allow a revised proposal |
| Verified accepted resource | Succeeded | Store external ID and receipt |
| Timeout after request transmission | Outcome unknown | Query by business identifier before retrying |
| Provider has no deduplication or lookup | Needs reconciliation | Hand off with the original request evidence |
“Retry three times” is not a complete policy for a write. If the first request booked the slot but its response disappeared, a retry can create another appointment. The connector article makes that uncertainty part of the returned type.
Keep approval narrow enough to understand
For a reschedule, I would show the old and new appointment time, location, practitioner and message consequence. Approval to change a time should not silently authorize a new recipient or an unrelated cancellation. If availability changes, prepare a new proposal instead of quietly substituting another slot.
The lab tests this boundary by changing the saved slot version after the preview. Confirmation fails with resource_changed. That is a useful failure: the user gets another review opportunity before the application changes their intent.
Test the moments where trust can break
The most useful acceptance cases are specific: edit the time before approval; confirm after expiry; click twice; select a patient from ambiguous matches; lose the connection after the database write; retry a failed notification without repeating the booking.
For each case, inspect both the interface and the stored record. The card should agree with the operation, and the operation should agree with the database.
That is the point of confirmation in this product. It turns the user's understanding into an explicit part of the workflow, then gives the backend a concrete proposal to validate and execute.
The implementation kit includes the articles, diagrams and an offline reference lab. Code identified as a reference design is a new teaching implementation; it is not a claim that the original project already shipped that behavior.