Concept 7 of 7

Build a Tool-Using AI Agent with Safe Retries in Python

This lab builds a small agent loop that can inspect and cancel fictional orders. The execution layer checks scope, ownership and eligibility. A SQLite transaction records each completed cancellation with a durable operation ID.

The exercise makes a specific failure visible. A cancellation can succeed while its response is lost, leaving the planner uncertain whether it should retry. The goal is to reconcile that uncertainty without applying the effect twice.

Run the offline agent

Download and extract the Python lab bundle. From its directory, use Python 3.10 or later:

python agent_lab.py --demo --lose-response

The command creates orders.sqlite and writes a trace under lab output/agent. All records are fictional, and no external service is contacted.

Demo mode uses a scripted planner so the failure and recovery are reproducible. It does not measure a model's ability to choose tools.

The local orders are A-4471, owned by alice and packed; A-4472, owned by alice and already dispatched; and B-9001, owned by bob and packed.

Inspect the action contract

Each proposed action is a JSON object with exactly action and order_id. The allowed actions are lookup_order, cancel_order and finish.

The runtime supplies customer identity and operation identity. The model cannot choose either through the action object. It also cannot switch the target order after the task begins.

The customer command line option simulates an authenticated session for teaching. A real application must obtain that identity from its authentication system.

The protocol intentionally uses ordinary JSON text so validation is visible. It is not provider native function calling or strict structured output. Converting it to a provider's tool protocol is an extension exercise.

Follow the lost response trace

The scripted run performs a lookup, requests cancellation, receives an unknown outcome observation, retries the same cancellation and finishes.

Open trace.json. The second action's effect has committed even though the planner sees a lost response. The retry should return replayed as true.

lookup_order   current state is packed
cancel_order   response lost after commit
cancel_order   prior operation result replayed
finish         final state checked by the application

The report's verified_success field comes from database state and the operation record. It does not trust the planner's finish request as proof of completion.

Inspect the transaction that prevents duplicates

The cancellation function starts a write transaction and looks up the operation ID. If it already exists for the same customer and order, the function returns the stored result.

If the ID is new, the function checks ownership and current eligibility, changes the order and inserts the operation record before committing. The effect and deduplication record therefore share the same transaction.

An existing operation ID paired with a different target is rejected. This distinguishes retrying an intent from accidentally reusing its identity for another action.

The guarantee is local to this database. A separate payment or messaging API needs its own idempotency or reconciliation contract. Recording a local success row cannot atomically protect an unrelated remote effect.

Verify recovery across a process restart

Run the command again with the same database and operation identity but a new output directory:

python agent_lab.py --demo --out lab-output/agent-resume

The stored operation should be replayed without another cancellation. The database persists between runs.

For an independent experiment, use a new database path and an appropriate new operation identity. Do not create a fresh identity merely to retry an action whose outcome is unknown.

The included tests also close and reopen the database before retrying, checking that deduplication is durable rather than a process local cache.

Test forbidden and ineligible actions

Use separate output directories:

python agent_lab.py --demo --order A-4472 --operation-id cancel-002 --out lab-output/agent-dispatched
python agent_lab.py --demo --order B-9001 --operation-id cancel-003 --out lab-output/agent-other-owner

The first request must not cancel a dispatched order. The second must not read or cancel bob's order while the simulated session belongs to alice.

The scripted planner still attempts its fixed sequence, which is useful here. The execution layer must reject an invalid request even when the planner proposes it.

Run the offline checks to test scope, lookup requirements, ownership, operation conflicts and lost response recovery:

python -m unittest -v test_labs.py

Connect a live planner

Set ANTHROPIC_API_KEY outside the code and pass an available model ID:

python agent_lab.py --live --model YOUR_MODEL_ID --db live-orders.sqlite --lose-response --out lab-output/agent-live

Only the planner uses the paid model API. Order changes remain local. The loop permits at most six model calls, each capped at 256 output tokens with a network timeout.

Invalid JSON or an unexpected response stops the run. Invalid tool proposals return explicit statuses. These are intentionally modest controls; a production service also needs shared spend accounting, end to end deadlines and proper authentication.

Acceptance criteria and submitted artefacts

Submit a successful trace, a lost response trace, an ownership denial trace and a short explanation of the transaction boundary.

Demonstrate one operation record for the repeated successful action and no changes to the other customer's order. Explain the difference between planner success, verified final state and constraint compliance.

For a final extension, add a confirmation record bound to the customer, order and operation. Make execution reject a stale or mismatched approval, then add a test showing that changed arguments cannot reuse the original approval.

Related material