Skip to content

feat: stateful workspaces (suspend / resume across daemon restarts) — closes #116#122

Merged
WaylandYang merged 4 commits into
mainfrom
feat/stateful-workspaces
May 19, 2026
Merged

feat: stateful workspaces (suspend / resume across daemon restarts) — closes #116#122
WaylandYang merged 4 commits into
mainfrom
feat/stateful-workspaces

Conversation

@WaylandYang
Copy link
Copy Markdown
Contributor

Closes #116 (item 4 of meta #112). Stickiness track — Daytona's killer feature, ported to forkd.

What

A workspace is a long-lived sandbox tracked by name. Users can suspend it (snapshot the live VM, kill the firecracker, retain the state on disk) and resume it later — even across daemon restarts.

API

GET    /v1/workspaces
POST   /v1/workspaces                  { name, snapshot_tag, ... }
GET    /v1/workspaces/:name
DELETE /v1/workspaces/:name            — kills sandbox + drops state snapshot
POST   /v1/workspaces/:name/suspend    { diff: bool }
POST   /v1/workspaces/:name/resume

CLI

forkd workspace create <name> --snapshot <tag>
forkd workspace suspend <name> [--diff]
forkd workspace resume <name>
forkd workspace list
forkd workspace delete <name>

Smoke test (verified end-to-end on dev box)

$ forkd workspace create test-ws --snapshot mem-512
test-ws  running    source=mem-512   state=-  live=sb-6a0cbf07-0000

$ forkd workspace suspend test-ws --diff
test-ws  suspended  source=mem-512   state=ws-test-ws-state  live=-

$ forkd workspace resume test-ws
test-ws  running    source=mem-512   state=ws-test-ws-state  live=sb-6a0cbf13-0001

$ forkd workspace suspend test-ws --diff   # chain head reused → fast
test-ws  suspended  ...

$ forkd workspace delete test-ws
deleted workspace 'test-ws'

Design choices

  • State tag scheme: overwrite-on-suspend (ws-<name>-state), bounded to one snapshot per workspace on disk.
  • Source snapshot preserved on delete — only the workspace's own state snapshot is removed.
  • Diff suspend supported — workspace tracks last_branch_memory_path across the workspace lifetime so successive suspend --diff calls chain off the previous one (just like multi-BRANCH on a sandbox).
  • Stale recovery: at daemon startup, reconcile() flips Running workspaces to Stale (live sandbox gone after restart). User runs resume manually to bring them back from current_state_tag.

Out of scope (follow-ups)

  • Auto-suspend on idle timeout
  • Persistent volumes per workspace (use existing volume API for now)
  • Python / TypeScript SDK convenience wrappers

WaylandYang and others added 4 commits May 20, 2026 03:45
…nd/resume

Stateful workspace MVP for #116. New types in api.rs:
- WorkspaceStatus (Running / Suspended / Stale)
- CreateWorkspaceRequest { name, snapshot_tag, per_child_netns, memory_limit_mib }
- WorkspaceInfo { id, name, source_snapshot_tag, current_state_tag,
  status, live_sandbox_id, created_at_unix, last_active_unix,
  last_branch_memory_path }
- SuspendWorkspaceRequest { diff: bool }

Registry gains BTreeMap<String, WorkspaceInfo> keyed by name +
list/get/insert/remove/update methods. reconcile() now also marks
Running workspaces Stale at daemon startup if their live_sandbox_id
no longer corresponds to a tracked sandbox.

HTTP routes:
- GET    /v1/workspaces
- POST   /v1/workspaces                  — create + spawn first sandbox
- GET    /v1/workspaces/:name
- DELETE /v1/workspaces/:name            — kill sandbox + drop state snapshot
- POST   /v1/workspaces/:name/suspend    — branch live sandbox to ws-<name>-state, kill sandbox
- POST   /v1/workspaces/:name/resume     — spawn from current_state_tag (or source if first resume)

State-tag scheme is overwrite-on-suspend (ws-<name>-state), bounded to
one snapshot per workspace on disk. Source snapshot is preserved on
delete; only the workspace's own state snapshot is removed.

Suspend respects v0.3 diff: true and the previous-output chain via
last_branch_memory_path persisted on WorkspaceInfo.

CLI subcommands + tests land in a follow-up commit on this branch.

Refs #116.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wraps the new daemon workspace API in CLI subcommands. Driver uses
ureq (already in the cli crate) to hit the daemon's REST endpoints.

Subcommands:
- create <name> --snapshot <tag> [--per-child-netns] [--memory-limit-mib N]
- suspend <name> [--diff]
- resume <name>
- list
- delete <name>

All accept --daemon-url (env FORKD_URL) and --daemon-token
(env FORKD_TOKEN), matching the existing snapshot subcommand
conventions.

Refs #116.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@WaylandYang WaylandYang merged commit bb9fd31 into main May 19, 2026
2 checks passed
@WaylandYang WaylandYang deleted the feat/stateful-workspaces branch May 19, 2026 19:56
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.

Stateful workspaces — sessions that survive daemon/host restart

1 participant