Agentic systems / Pattern 09 / Build the working path

Build revision-bound approval and single-use resume

Trace the original approval module from proposal fingerprint to an atomic local dispatch marker.

By Sandeep Belgavi · 23 October 2026 · Architecture and design patterns

Run the local approval path

Place pattern_09.py and its tests in one folder. With Python 3.11 or newer, run py -3 -B pattern_09.py on Windows or python3 -B pattern_09.py on macOS. The reference was executed on Windows. The macOS invocation is a suggested portability check, not a statement that a Mac test has already passed.

The JSON result reports decision approved, approval approval-1 and dispatch local-marker. The last value is intentionally explicit: the script authorizes a local marker and performs no remediation. It creates temporary SQLite storage, proposes a synthetic worker restart, records a review and consumes the decision. No browser, authentication server, cloud API, model or paid service is involved.

Architecture and request sequence

Component diagram for Build revision-bound approval and single-use resume
Original reference architecture by Sandeep Belgavi. Arrows show the responsibilities and transitions described below.
Read the architecture as text
  • ProposalAdapter: Synthetic proposal only
  • digest(action): Canonical JSON hash
  • Pending row: Reviewer, revision, expiry
  • decide: Validate and record choice
  • consume: Conditional single-use update
  • ProposalAdapter → digest(action): structured action
  • digest(action) → Pending row: bind proposal
  • Pending row → decide: review request
  • decide → consume: approved state
Request sequence for Build revision-bound approval and single-use resume
Illustrative sequence for this design; failure and recovery variants are explained in the article.
Read the sequence as text
  1. Caller → Store: Request pending approval
  2. Reviewer → Store: Submit action and revision
  3. Store → Reviewer: Return approved state
  4. Caller → Store: Consume before expiry
  5. Store → Caller: Return local dispatch marker

Inspect the proposal fingerprint

The digest function serializes the action with sorted keys and compact separators, encodes the bytes and computes SHA-256. The action fixture contains incident, action and target fields. This gives the approval a stable binding to that structured object. It does not prove that the object is safe or that its natural-language explanation is truthful; those remain separate review concerns.

ProposalAdapter is the provider seam. It returns the same synthetic action each time and has no approval authority. A future model adapter could propose different actions, but trusted code must validate their schema and allowed operation types before presenting them. Do not let a provider choose its own reviewer or expiry by including those fields in a proposal. The reference request method receives authorization metadata through a separate application call.

Create and decide a pending request

ApprovalStore creates one table with identifier, fingerprint, revision, reviewer, expiry and state. Request validates basic metadata and inserts a pending row. Duplicate identifiers raise rather than silently replacing an existing review. That prevents a changed proposal from overwriting the record a reviewer is currently considering, although a production API would usually translate the database exception into a clearer conflict response.

Decide begins a write transaction, loads the row and checks reviewer identity, proposal fingerprint, revision, expiry and pending state. It also requires a real boolean decision rather than accepting a truthy string. An authorized approval becomes approved; a denial becomes rejected. Invalid attempts leave the record pending. These checks happen in application code before the state update, with the transaction preventing another local writer from racing the decision.

Consume only a current approval

Consume performs a conditional update requiring the identifier, action fingerprint, revision, future expiry and approved state to match. Exactly one changed row is required. It then returns the local marker. A second call cannot consume the same row, and a proposal that expired after approval is rejected. Checking expiry again at consumption closes the gap between review time and dispatch time.

This is not an external execution protocol. If a caller consumes approval and crashes before contacting a remote tool, the module has no pending dispatch record to resume. Keep that limitation visible. A deployment should atomically create a durable dispatch entry with consumption, then use an adapter with a stable request identity. Do not solve the crash by resetting consumed to approved without investigating whether the remote action might already have happened.

Run the negative tests

Run py -3 -B -m unittest test_pattern_09, or replace py -3 with python3 on macOS. Ten tests cover the wrong reviewer, changed target, stale revision, expiry at decision time, single consumption, rejection, reopening a pending request, expiry after approval, duplicate decisions and the demonstration output. The test suite checks denial paths because the most dangerous approval bugs often appear on a nearly valid request.

The fixture principal is a trusted string passed by the test harness. A real endpoint must derive it from authenticated session context, not a JSON field the caller can edit. Similarly, a production clock must be trusted by the service. Passing time explicitly in this module is a testing technique that makes boundaries deterministic; exposing arbitrary client-selected time in an approval API would defeat expiry enforcement.

Connect a UI without weakening the boundary

A reference service could expose read-proposal, submit-decision and inspect-status endpoints, with a separate internal dispatcher. Protect review requests against replay and cross-session confusion, render the exact bound proposal and return meaningful conflict or expiry messages. If a workflow framework is used, resume only after these application checks succeed. LangGraph notes that resumed execution restarts the interrupted node from its beginning, so code before the pause must tolerate repetition.

Keep a human-readable audit record of who decided, what was shown and which revision was approved. The lab stores the minimum state, not a complete audit trail. Add evidence references and decision timestamps before a hosted trial, then test the whole path using synthetic incidents. A responsive approval button is not enough; the service must preserve the same authorization semantics under refreshes, retries and restarts.

Sources and implementation