Your first crash-survival⌗
Five minutes. We're going to crash an agent mid-tool-call and watch the recovery reactor pick it up. The point isn't the demo — the point is that you see Tape's contract with your own eyes:
The confirmed effect is not re-executed. The pending effect is resolved (re-dispatched, observed, or compensated). The agent resumes from the first journal seq that doesn't have a record.
Prereqs⌗
- Installed the Python SDK + CLI
- Have a project directory; you don't need an agent yet
Step 1 — scaffold a project⌗
The scaffold ships with one agent (app/agent.py), one tool, and a
tape.yaml. Look at it. Everything is obvious.
Step 2 — run the kill-resume demo⌗
This brings up:
tape-serveragainst./.tape/dev.db(SQLite)- The recovery reactor
- The reconciler reactor
- The outbox reactor
- Your agent
Then it does three things:
- Triggers the agent to start a run.
- Crashes the agent process after
BeginEffectbut beforeCompleteEffect. - Watches the recovery reactor re-drive the run.
The expected output:
✓ tape-server up
✓ reactors started (recovery, reconciler, outbox, timers, compensation)
✓ agent started
→ triggering run...
✗ agent killed mid-effect (pid 12345)
↻ recovery reactor: run r-... is RUNNABLE; re-driving
✓ agent re-attached at decision_index=2, seq=8
✓ effect `wire_money` short-circuited (already CONFIRMED)
✓ run terminated cleanly
ASSERT: upstream observed exactly 1 wire ✓
The last line is the contract. One wire, even though the agent re-attached. That's Tape doing its job.
Step 3 — look at what the journal recorded⌗
You'll see one run, status TERMINAL, with decisions=N, effects=M. To
peek at the actual records:
You'll see the decision records, the effect records (with CONFIRMED),
the budget rows, and the lease history. That's the journal.
Step 4 — go off-script⌗
Try this:
# In one shell:
tape dev
# In another:
tape status --follow
# Then trigger your agent and watch the rows light up.
Kill the agent (Ctrl-C) at different points:
- Before any tool call → recovery reactor re-drives; the agent re-asks the model from the same decision index.
- After
BeginEffect, beforeCompleteEffect→ recovery reactor re-drives; the journalled effect is replayed from history on the second attempt (not re-executed). - After
CompleteEffect, before the next model call → recovery re-drives; the next model call starts where it left off.
Each of these is a separate state of the world. Tape distinguishes between them with the per-decision idempotency key and the effect's status.
What you just demonstrated⌗
- Decisions are durable. The agent doesn't re-ask the model for a decision it already made.
- Effects are idempotent on the key. A confirmed effect is replayed from history, not re-executed.
- The reactor owns recovery. You didn't write retry code. The reactor did it for you, once, on a lease.
Next⌗
- The mental model behind all of this: Concepts.
- The same thing, but with a non-idempotent upstream and the outbox: Non-idempotent upstreams.
- Now deploy this to GCP: Cloud Run.