Skip to content

feat(pi): drain pi durable actors across upgrades - #324

Merged
eersnington merged 7 commits into
stack/feat-pi-stream-pi-durable-conversations-to-clients-rvqykpzvfrom
stack/feat-pi-drain-pi-durable-actors-across-upgrades-uymwoxpn
Oct 5, 2026
Merged

eersnington merged 7 commits into
stack/feat-pi-stream-pi-durable-conversations-to-clients-rvqykpzvfrom
stack/feat-pi-drain-pi-durable-actors-across-upgrades-uymwoxpn

Conversation

@eersnington

@eersnington eersnington commented Oct 5, 2026 •

Copy link
Copy Markdown
Member

A deploy stops actors in the middle of runs. Pi Durable resumes from its checkpoints, but the model call or tool call that was running is lost and starts again. piDurable() now lets live work finish before the actor moves.

sequenceDiagram
  participant E as Engine
  participant A as piDurable actor
  E->>A: stop (deploy, version drain)
  A->>A: take the drain deadline
  A->>A: app onSleep
  A->>A: drain: wait until no run is active, or the deadline passes
  A->>A: harness.close(), suspend the sandbox
  Note over E: requests are held until the next generation is ready
  E->>A: next generation starts
  A->>A: open, app onWake, harness.resume()
Loading
Event Behavior
Idle sleep Nothing to wait for: keepAwake already blocks idle sleep while work runs
Forced stop (deploy) Drain, then close and suspend the sandbox, all inside sleepGracePeriod
Run outlives the drain Close still runs. Pi's records are stored, and the run resumes on the next wake
Destroy No drain: stop at once, there is no next generation

RivetKit's grace deadline covers all of onSleep. The drain deadline is taken before the app's own onSleep, and keeps time back for closing:

// src/drain.ts
const closeReserve = Math.min(30_000, gracePeriodMs / 4);
return Date.now() + gracePeriodMs - closeReserve;
  • sleepGracePeriod defaults to 15 minutes for piDurable(). The engine's actor_stop_threshold (30 minutes by default) is the hard limit.
  • Blocked tasks and waits over 60 s do not hold the drain.
  • Input sent during the stop reaches the next generation. A retry with the same requestId is deduplicated by Pi Durable.
  • A failed open on wake does not fail the wake. Actions report Pi's error, and c.pi in the app's onWake throws it.

Upgrades use Pi Durable's own versioning, with nothing added on top:

  • A task stored by version 1 runs under version 2 through its migrate. Without one, it is reported blocked in harness.inspect().
  • Storage written by a newer Pi Durable schema is refused by Pi, and actions fail with internal_error. Schema upgrades are roll forward only.

This is part 5 of 11 in a stack:

@eersnington
eersnington force-pushed the stack/feat-pi-stream-pi-durable-conversations-to-clients-rvqykpzv branch from 0bfc39b to 9657e88 Compare October 5, 2026 18:53
@eersnington
eersnington force-pushed the stack/feat-pi-drain-pi-durable-actors-across-upgrades-uymwoxpn branch from 82b8dfc to 391ec91 Compare October 5, 2026 18:53
@eersnington
eersnington merged commit 5a4b59c into stack/feat-pi-stream-pi-durable-conversations-to-clients-rvqykpzv Oct 5, 2026
0 of 3 checks passed
@eersnington
eersnington deleted the stack/feat-pi-drain-pi-durable-actors-across-upgrades-uymwoxpn branch October 5, 2026 19:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant