Repository navigation
Description
Goal
Take over B · Sandbox compute plane and implement it per the Machine + Backend model of #533:
Machine (compute ownership)
└── Sandbox (bound to one project)
└── Backend (docker / native / namespaces / VM / ssh)
Do observable, bounded, recoverable, cleanable first; unlock Windows/cloud only after their Decisions.
- Parent: LayerFS 2.0 roadmap #446 (LayerFS 2.0 roadmap, area B)
- Design basis: LayerFS 2.0: one home, machines and Stores, one SDK #533 (one home, machines and Stores, one SDK)
- Working plan: [2.0] LayerFS 2.0 roadmap: working plan layerfs-connect#9
- Project: Main / Compute Plane (
track:Main)
Status (2026-11-06)
Index only; delivery is tracked in the native sub-issues below. Current progress is 0 / 5 closed:
| Sub-issue | Area | PR | CI / merge | State |
|---|---|---|---|---|
| #548 | B5 lifecycle — generation CAS, exec lease, Draining, Lost/Unresolved | #566 | CLEAN / MERGEABLE, not merged | open |
| #549 | B6 scratch — owned path, generation check, bounded cleanup, operator dry-run | #558 | CLEAN / MERGEABLE, not merged | open |
| #550 | B9 worker liveness/progress — no permanently hung call | #556 | CLEAN / MERGEABLE, not merged | open |
| #551 | B0 contract + B8 inspect + B10 doctor | #567 | CLEAN / MERGEABLE, not merged | open |
| #552 | StoragePlacement — live SQLite only on its owning machine; Family 2 admits only PostgreSQL/snapshot | #557 | CLEAN / MERGEABLE, not merged | open |
All five sub-issue PRs are green and CLEAN / MERGEABLE; 0 are merged. Merge permission is with maintainers.
Acceptance extra: #587 (agent-d1-p3) carries the D1 network-namespace fix and the 0701/0700 data-root fix, stacked on #556. It is also CLEAN / MERGEABLE, not merged.
Acceptance evidence is complete for the local 2.0 scope: native and Docker close/exec E2E, per-command CLONE_NEWNET (guest has only lo, cannot reach 127.0.0.1:7070), protocol-3 handshake, 0701/0700 permissions, and the five invariants. #551's live-Sandbox-across-restart is explicitly out of 2.0 (Option A, core/docs/issues/545/551-LIVE-RESTART.md).
Architecture + cs-infra review: 0 P0, 0 P1 after fixes; the remaining P2s are non-blocking. Suggested merge order: #556 -> #587, #566 -> #558, #567 -> #557.
Diagrams
Rendered from the editable Excalidraw sources in Comic Shanns (fontFamily: 8); release assets are in issue-assets-2026-11-06. All nine PNGs were checked for dimensions and re-uploaded after the font/render fix.
Compute plane
Worker liveness (#550 / #556)
Scratch (#549 / #558)
Storage placement (#552 / #557)
Boundary
Main / Compute Plane — this issue:
- B5: Sandbox lifecycle — generation CAS, exec lease, Draining and first-class Lost #548 B5 lifecycle
- B6: Sandbox scratch lifecycle — owned path, generation check, bounded cleanup #549 B6 scratch
- B9: machine worker health and liveness — a dead worker must never leave a call hanging #550 B9 liveness/progress
- B0: machine/backend contract — isolation, scheduling, capacity and read models (B8/B10) #551 B0 contract + B8/B10
- B0: refusal for a misplaced Store — StoragePlacement guard for live SQLite on Family 2 #552 StoragePlacement
Staging — blocked on Decisions, tracked separately:
- B4: Windows via a WSL2 worker and Docker backend (blocked on D2/O5) #553 B4 WSL2 — D2/O5
- B7: cloud and provider adapters — Family 1/2 contract, transport and storage binding (blocked on D6/D12) #554 B7 cloud/provider — D6/D12, next phase; not part of 2.0
- B9 restart/transport residual — D2
Agent Runtime — outside this plane, tracked separately:
- step-runtime: exec_async / exec_wait / exec_status outside the Sandbox compute plane #555 step runtime (
exec_async/exec_wait/exec_status/cancel)
#553, #554 and #555 are not native sub-issues of #545 and do not affect its 0/5 progress.
Model (per #533)
- Compute is machines.
native/dockerare built in; VM kinds may be many (apple-container,microsandbox,lima,firecracker); ansshhost is a machine.- A Sandbox has a name and is bound to one project and one machine.
- A backend is the concrete runtime inside a Sandbox.
- Records live under
machine/<machine>/sandbox/<name>/. - The local home record is the single authority for control-plane state; PostgreSQL carries Store records and receipts only.
- There is no
platform/directory, andLAYERFS_SANDBOXno longer selects a backend. - Calls are routed by records.
Four axes
| Axis | What it is | Layer |
|---|---|---|
| Abstraction | what the caller talks to | Machine → Sandbox → Backend |
| Isolation | how strong the boundary is | process / container / VM / remote |
| Scheduling | who runs work and for how long | Worker + long-lived Sandbox |
| Semantic boundary | what the state contract is | Store / Commit / Branch + Workspace |
Isolation capability
| Backend | Isolation | Kernel | Notes |
|---|---|---|---|
native / namespaces |
process | shared | namespaces + cgroup; not a security boundary against root |
docker |
container | shared | default Linux |
lima / firecracker / microsandbox / apple-container |
VM | separate | higher cost |
ssh |
remote | separate host | network boundary, latency |
The backend declares its isolation capability. Callers ask for a capability; they do not infer one from the backend name.
Cross-cutting invariants
- Sandbox is an abstraction, not an isolation strength. (all sub-issues)
-
processis a backend, not a separate top-level. (B0: machine/backend contract — isolation, scheduling, capacity and read models (B8/B10) #551) -
serverlessis scheduling, not isolation. (B0: machine/backend contract — isolation, scheduling, capacity and read models (B8/B10) #551, step-runtime: exec_async / exec_wait / exec_status outside the Sandbox compute plane #555) - The guest never reads the home on the 2.0 backends (native / docker / namespaces / ssh). Cloud/remote transport proof (B7: cloud and provider adapters — Family 1/2 contract, transport and storage binding (blocked on D6/D12) #554) is next phase and does not gate 2.0.
- Storage placement is guarded: a SQLite Store runs only on the machine that owns its file; a Family 2 backend admits only PostgreSQL or a snapshot; no live SQLite over network/NFS/FUSE/object storage. (B0: refusal for a misplaced Store — StoragePlacement guard for live SQLite on Family 2 #552)
Scope for this version
In:
- Machine / Sandbox / Backend records, routing and inspect.
- B0/B5/B6/B8/B9/B10 for the four backend families.
- Isolation capability and scheduling class in records and read models.
- StoragePlacement refusal and the no-live-remote-SQLite guard as 2.0.0 invariants.
- Default isolation for
sandbox.createand refusal when the requested class is unavailable. requestedandeffectiveisolation in the Sandbox record.
Out:
- No serverless execution backend.
- No core queue or actor.
- No agent loop or LLM.
- No Lambda / SQS / Aurora coupling.
- Step/serverless execution belongs to the agent runtime through the SDK, outside this plane.
Dependencies
| Dependency | Impact | Handling |
|---|---|---|
| #533 G0/G1/G2 | machine model, home layout, routes | align first; do not write against the old platform |
| D2 | B4 / B9 restart/transport | do B8/B10/B5/B6 first; do not wait |
| D6/D12 | B7 cloud | wait for Decision |
| #529/#530 | backend semantics | align with @yifanxuaaa; do not rewrite |
| #508 | unbounded control calls | separate issue; bounded principle adopted here |
| #422/#423 | B5/B6 | covered directly here |
| #543/#544 | controllability work | basis for B8/B10 |
Done when
- Compute Plane 2.0 delivery sub-issues are closed: B5: Sandbox lifecycle — generation CAS, exec lease, Draining and first-class Lost #548, B6: Sandbox scratch lifecycle — owned path, generation check, bounded cleanup #549, B9: machine worker health and liveness — a dead worker must never leave a call hanging #550, B0: machine/backend contract — isolation, scheduling, capacity and read models (B8/B10) #551, B0: refusal for a misplaced Store — StoragePlacement guard for live SQLite on Family 2 #552.
- B4: Windows via a WSL2 worker and Docker backend (blocked on D2/O5) #553 (B4 WSL2) remains Staging, blocked on D2/O5; B7: cloud and provider adapters — Family 1/2 contract, transport and storage binding (blocked on D6/D12) #554 (B7 cloud/provider) is explicitly next phase after D6/D12; neither is part of this issue's closure.
- step-runtime: exec_async / exec_wait / exec_status outside the Sandbox compute plane #555 (step runtime) remains Agent Runtime; it is not part of this issue's closure.
- The five cross-cutting invariants hold in the merged tree.
- Default isolation is enforced and mismatch is refused, with requested/effective recorded (B0: machine/backend contract — isolation, scheduling, capacity and read models (B8/B10) #551).
- B5 CAS close is tested against a concurrent exec and a stale generation (B5: Sandbox lifecycle — generation CAS, exec lease, Draining and first-class Lost #548).
- The storage placement guard is in 2.0.0/B0 and covered by an admission test (B0: refusal for a misplaced Store — StoragePlacement guard for live SQLite on Family 2 #552).
Lostis reachable and observable (B5: Sandbox lifecycle — generation CAS, exec lease, Draining and first-class Lost #548).
Non-goals
- Do not rewrite LayerFS 2.0: one home, machines and Stores, one SDK #533's machine/Store model.
- No area P SDK/MCP work.
- No area A/R Store/Remote work beyond the placement guard (B0: refusal for a misplaced Store — StoragePlacement guard for live SQLite on Family 2 #552).
- Do not turn the daemon into an exec supervisor.
- Do not introduce a per-platform forked daemon protocol.
- Do not treat SQLite as a network database.
- No serverless execution backend, core queue/actor, agent loop or LLM.
Design diagrams
- Architecture: machines, backends, stores
- Lifecycle: create, exec, receipt, CAS close
- Observability: B8/B10/B9
History
This body was edited heavily on 2026-11-04 (20 revisions, 3 KB → 33 KB) while the scope moved from a control-plane subset to a compute-plane index. The last full 66-checkbox audit is historical: 6 done / 16 partial / 41 not done / 3 N/A against layerfs_v2_0_0 (d2c3f2c97). Current delivery progress is the 5 sub-issues above, 0/5 closed.









Activity
mengmengmengqiang commentedon Nov 4, 2026
I'll take this issue. I'll start with B8, B10, B5, and B6; B9, B4, and B7 will follow once D2, D6, and D12 land.
The assignee is @mengmengmengqiang, with the
v2.0.0andenhancementlabels.[-][B] Sandbox control plane: observability, lifecycle and bounded recovery[/-][+][B] Sandbox compute plane: machines, backends, observability and lifecycle[/+]mengmengmengqiang commentedon Nov 4, 2026
#545 is now an index
The body has been reduced to an index: a Status block, five cross-cutting invariant checkboxes, and a per-family table. The 66-box audit is 6 done / 16 partial / 41 not done / 3 N/A (60 unchecked). All 6 checked boxes are factually correct but 0 are #545's own delivery: 4 are satisfied by merged #535/#536 work, 2 are permanent invariants, and B9-210 is the third invariant (N/A). The old 66-checkbox list is gone; status is now the open/closed state of the sub-issues.
Delivery split (8 sub-issues):
Draining, first-classLostexec_async/exec_wait/exec_status), outside the compute planeThe status scene
jhh8rntcFuwas rebuilt as Status index: corrected counts (6 / 16 / 41 + 3 N/A), 60 unchecked, no "51 unchecked" footer, and an INDEX ONLY badge. It is exported to the docs branch at commiteea0e59:sandbox-compute-plane-status-3715063e.png/.excalidraw(Comic Shanns throughout), README bumped to v6. The body references the new image URL.#545 stays OPEN as the index and is not closed by this split. Close it only when #548–#554 are closed and #555 is resolved or moved to its owning area.
Label note: the available token lacks
triage, so the new issues were created unlabeled; each has a comment requestingv2.0.0/bug/enhancement(plusfuture/deferredfor the blocked ones).docs(545): add the worker liveness architecture and sequence diagrams
10 remaining items
docs(545): state that namespaces exec is offline (Ephemeral-AI-Lab#545)
docs(545): keep the D1 protocol-3 record under the issue (Ephemeral-A…
docs(545): state that namespaces exec is offline (Ephemeral-AI-Lab#545)
docs(545): keep the D1 protocol-3 record under the issue (Ephemeral-A…
docs(545): state that namespaces exec is offline (Ephemeral-AI-Lab#545)
docs(545): add the worker liveness architecture and sequence diagrams
docs(545): keep the D1 protocol-3 record under the issue (Ephemeral-A…
docs(545): state that namespaces exec is offline (Ephemeral-AI-Lab#545)
test(545): cover the storage placement guard on both placement paths
yifanxuaaa commentedon Nov 6, 2026
Decision: remove "snapshot" from the compute plane (owner, 2026-11-06)
@mengmengmengqiang, to keep the design small, "snapshot" comes out of #551 and #552. Please change your two PRs before they merge.
1. #567 (#551 B0): drop
SnapshotCapabilityAll three backends set it to
None, so nothing reads it. Remove:SnapshotCapabilitytype and thesnapshotfield inconnect/crates/layerfs-sandbox/src/types/capabilities.rs, plus their re-exports intypes/mod.rsandlib.rssnapshot: Nonein the docker, host_process and namespaces adapters"snapshot"key in the capability read model (api/crates/layerfs-control/src/machine/capability.rs) and in.agents/skills/layerfs-js-sdk/references/methods/machine.mdcore/docs/issues/551/README.mdNone of this has merged, so keep capability
schema_versionat 1.2. #557 (#552): Family 2 admits PostgreSQL only
The placement rule becomes:
StoragePlacement552-ACCEPTANCE.md, the README, the architecture/sequence diagrams anddocs/versioned/2.0.0/machines-and-stores.md.a_sqlite_snapshot_is_admitted. If you want to keep its check thatbackup createis not blocked by the guard, name it for backups, not snapshots.store/connect.rscomments: buckets hold backups only.Not changing
The
snapshotstep inside the mergedstore push,store pullandbackup create(#536) stays. It's only the name of the step that freezes a Store's bytes before the copy.backup create/backup restoreand "object storage is never a live Store" are unchanged.The suggested merge order holds: #567 → #557.
yifanxuaaa commentedon Nov 6, 2026
Owner spec for the 2.0 compute plane (2026-11-06)
@mengmengmengqiang, this is the spec we'll build the compute plane to. Where it differs from this issue's body, the spec wins.
Decisions (owner, 2026-11-06):
snapshotfield, and a Family 2 machine admits only PostgreSQL.layerfs-controlmoves toconnect/crates/layerfs-control, as one move-only commit after the trimmed PRs below merge. This supersedes layerfs-control: one management layer for machines, Stores, Sandboxes and Workspaces #536 C1. Please don't move it in your PRs.workspace.exec: an optionalexecId, a newworkspace.exec_cancel, and an exec record undersandbox/<name>/exec/. feat(step): launcher-owned step runtime — exec_async/exec_wait/exec_status/cancel (#555) #569 (step-runtime: exec_async / exec_wait / exec_status outside the Sandbox compute plane #555) is rejected; see the comment there.isolateNetwork.Proposals for review (not decided yet): trims P-1 to P-6 for #566 and #567 (§3), and the internal layout P-7 (§6.2). Please hold the trims until they're confirmed. The snapshot removal (D-1) can go ahead now.
One correction to the body: Sandbox records live at
sandbox/<name>/(#536 C2, owner 2026-11-03), notmachine/<machine>/sandbox/<name>/.The full spec is below. It's written against
layerfs_v2_0_0at8b701f42f0, and it will be committed ascore/docs/proposals/compute-plane-2.0.md.Full spec: Compute plane 2.0
Compute plane 2.0: machines, Sandboxes, exec and the control layer
This is our own spec for the 2.0 compute plane. It takes over from the
#545 index where the two differ. Owner decisions are marked decided with
their date. Everything else is a proposal until the owner accepts it.
1. Decisions
snapshot: Option<SnapshotCapability>field is removed from the capability contract (#567). A Family 2 machine admits only PostgreSQL Stores; the "SQLite snapshot → admitted" placement row, its test and the B12 snapshot Store kind are gone, not deferred (#557). Thesnapshotstep insidestore push,store pullandbackup createstays.layerfs-controlmoves toconnect/crates/layerfs-control, after the trimmed #545 PRs merge, as one move-only commit. Supersedes #536 C1 ("in the api layer").workspace.exec(§5). PR #569 (#555, the separatestep.*family) is rejected.2. Model
sandbox/<name>/, notmachine/<m>/sandbox/<name>/as [B] Sandbox compute plane: machines, backends, observability and lifecycle #545 says. layerfs-control: one management layer for machines, Stores, Sandboxes and Workspaces #536 C2 (owner, 2026-11-03) retired the nested path; the
record names its machine.
isolation class in 2.0 (§3, P-4).
run in the caller (LayerFS 2.0: one home, machines and Stores, one SDK #533 G2). The placement guard (fix(545): refuse to place a sqlite Store on a machine that keeps no disk (B0-150 / B7-223) #557) refuses a live
sqlite Store on a machine that cannot hold its file.
3. Scope: what we keep from the #545 PRs
Production LOC is each PR's own increment over the PR it is stacked on,
measured at its head on 2026-11-06. Every PR is stacked on #556.
Unavailable/Unknownin bounded time; a live command is not cut off.Closed/Lostonly, re-check ownership before delete.Proposals:
creating,running,closing,unresolved,gone) and add one:lost. A Sandboxis
lostwhen its record has a runtime id and no close intent, and itsmachine answers that the runtime is not there.
sandbox.closeclears it.Drop
Provisioning,Starting,Ready,Busy,DrainingandFailed.opintent already serialize close; concurrent closes are rare under theone-writer model. Close is refused
Busywhile a live exec record exists(§5.2);
sandbox.close(name, { force: true })cancels those commands first.ExecLeaseand its heartbeat; liveness of a remote call is fix(545): bound worker liveness waits, never an operation #556's beat.There is no automatic reclaim in 2.0, so
reclaim.enabled,--dry-runand
--freezego.sandbox.create({ isolation }),requested_isolation/effective_isolationand the strength order.#[non_exhaustive]Capabilitythat replaces the flatHashMap<String, bool>: isolation,kernel, mount driver, idle stop and
direct_store_access(which fix(545): refuse to place a sqlite Store on a machine that keeps no disk (B0-150 / B7-223) #557reads).
machine getandsandbox getshow it.in-flight limit, queue bound and
Capacityrefusal, the Sandbox activityread model and the scheduling facts. Revisit after 2.0 with a measured need.
4. Exec network option
workspace.exec(ws, command, { spool?, isolateNetwork? })truefalsenamespaces(VM machines)loupdockerUnavailablenativeUnavailablenetwork:"none"(isolated) or"sandbox"(the networkthe Sandbox has), so a caller sees what took effect.
isolate_network: boolonExecRequest(
layerfs-sandbox/src/types/request.rs), passed to the helper as--isolate-network; the helper's exec stage(
layerfs-sandbox-helper/exec.rs) runsunshare(CLONE_NEWNET)only withthe flag, then brings
loup. The worker request carries the field.port or other services on the machine's loopback. With
falsethe port isreachable, but the worker's secret stays in
/layerfs/state/secret(directory
0700, root-owned), out of reach of the command's uid.5. Exec and cancel
5.1 Calls
One new call in the call table, with its CLI, Node and Python forms
(
layerfs workspace exec-cancel <ws> <exec-id> [--grace-ms N]).5.2 The exec record
A running command has
sandbox/<name>/exec/<execId>.json: its ownerprocess (pid, kernel start time, boot, as Store leases name theirs), where it
runs (local or a machine's worker) and how to stop it (cgroup path or process
group). It is written under the Sandbox lock when the command starts and
removed when it ends. The record is how a different process cancels a command,
and how
sandbox.closeknows commands are running. A record whose owner isdead is stale; close and cancel remove it under the Sandbox lock and log it.
5.3 How each backend stops one command
namespaces<sandbox cgroup>/exec-<id>; the helper joins it instead of the Sandbox's cgroupcgroup.kill; this also catches children that left the process groupnativesetsidat spawn)killpgSIGTERM, then SIGKILL. On macOS there is no cgroup, so a child that callssetsiditself escapes; documenteddockerkill -TERM -<pgid>, then-KILL. The Engine has no per-exec kill (docker/streams.rsrefuses one today)sandbox.exec_cancel, in the next worker protocol version5.4 Rules
cancel whose result cannot be confirmed (a worker that does not answer) is
unknown, and the exec record stays until its owner ends or close clears it.backend, like exec.
bashis not proof every descendant is gone; only the cgroup form(
namespaces) can promise that, and the result says which form ran.6. Code layout
6.1 Crates
Scope: crate level, production LOC at
8b701f42f0. Tests stay with theircrate under
tests/and are not counted. The production total (139,405) doesnot change; the counter's
apiscope drops by 18,202 andconnectgrows bythe same.
connect/crates/ [34270] layerfs-control/ [18202] (moved from api/crates/layerfs-control; D-2) layerfs-machine/ [4157] (where: one folder per platform) layerfs-sandbox/ [8610] (how: one folder per backend) layerfs-store-backup/ [3301] (bucket I/O for backups) api/crates/ [2214] (public surface only) layerfs-api/ [1427] (the call table; depends on layerfs-control) layerfs-cli/ [644] layerfs-node/ [93] layerfs-python/ [50]Layers stay
core < engine < connect < api. Inside connect:layerfs-control → layerfs-machine → layerfs-sandbox, andlayerfs-control → layerfs-store-backup. The drivers (layerfs-machine,layerfs-sandbox,layerfs-store-backup) keep their rule: every input asarguments, no home, no records.
layerfs-controlis the one connect cratethat owns the home.
Non-production changes in the same commit:
tools/production_loc.py:layerfs-controlleavesAPI_CRATESandcounts in
connect.tools/check_product_boundary.py:layerfs-control's layer becomesconnect;
ALLOWED_DEPENDENCIESis unchanged.Cargo.tomlpaths inlayerfs-apiandlayerfs-control;Cargo.lock;.github/workflows/{ci,nightly}.ymlandtools/ci_tests.pypaths.connect/AGENTS.mdandconnect/README.md(connect holds the drivers andthe control library),
api/AGENTS.md, rootAGENTS.md, anddocs/architecture/api/{control,routing}/→docs/architecture/connect/.core/docs/issues/536/DECISIONS.md: C1 marked superseded by D-2.6.2 Inside
layerfs-control: machine → Sandbox → Workspace, sqlite vs PostgreSQLProposal P-7, done as moves only (no body changes), in a commit after
the crate move. Scope:
layerfs-control/srcat8b701f42f0. A directorymarked collapsed is unchanged and its bracket counts all its files; every
other directory lists every file.
layerfs-control/src/ [18202] (moved from api/crates/layerfs-control) inventory.rs 202 lib.rs 33 calls/ [710] (8 files, collapsed) common/ [2108] (19 files, collapsed; gains random/text/lock from sandbox/registry.rs) doctor/ [618] (6 files, collapsed) machine/ [1817] (20 files, collapsed; bundle.rs gains bundle_dir/IMAGE_ARCHIVE from sandbox/deploy.rs) + migrate/ [933] (moved from common/home/migrate/: it reads every entity's records) mod.rs 6 (moved from common/home/migrate/mod.rs) open.rs 52 (moved from common/home/migrate/open.rs) v110.rs 364 (moved from common/home/migrate/v110.rs) v2.rs 418 (moved from common/home/migrate/v2.rs) volume.rs 93 (moved from common/home/migrate/volume.rs) project/ [1216] family.rs 310 init.rs 47 mod.rs 13 record.rs 80 + postgresql/ [115] (Init into a PostgreSQL Store) init.rs 115 (moved from project/postgres_init.rs) + mod.rs — new + sqlite/ [651] (folder copy to the Store's machine) + mod.rs — new send.rs 345 (moved from project/send.rs) staged.rs 175 (moved from project/staged.rs) tree.rs 131 (moved from project/tree.rs) sandbox/ [5400] close.rs 116 create.rs 125 deploy.rs 237 (bundle_dir/IMAGE_ARCHIVE move to machine/bundle.rs) direct.rs 343 dispatch.rs 104 exec.rs 53 family.rs 40 files.rs 608 get.rs 48 list.rs 18 mod.rs 28 output.rs 163 record.rs 136 recorded.rs 298 registry.rs 115 (random/text/lock move to common/) route.rs 446 runtime.rs 193 serve.rs 491 + daemon/ [884] (moved from daemon/: the channel to a Sandbox's daemon) call.rs 59 (moved from daemon/call.rs) channel.rs 283 (moved from daemon/channel.rs) failure.rs 61 (moved from daemon/failure.rs) internal.rs 79 (moved from daemon/internal.rs) mod.rs 10 (moved from daemon/mod.rs) operator.rs 12 (moved from daemon/operator.rs) pool.rs 198 (moved from daemon/pool.rs) values.rs 182 (moved from daemon/values.rs) launch/ [954] connection.rs 105 create.rs 62 local.rs 397 mod.rs 14 provider.rs 376 store/ [4302] (kind-neutral: records, routing, Store calls) call.rs 73 calls.rs 363 identity.rs 479 list.rs 39 mod.rs 23 open.rs 180 record.rs 325 resolve.rs 30 service.rs 188 status.rs 45 + backup/ [1032] (moved from backup/: backups of sqlite Stores) create.rs 226 (moved from backup/create.rs) family.rs 77 (moved from backup/family.rs) list.rs 125 (moved from backup/list.rs) mod.rs 9 (moved from backup/mod.rs) progress.rs 142 (moved from backup/progress.rs) record.rs 195 (moved from backup/record.rs) restore.rs 258 (moved from backup/restore.rs) + postgresql/ [688] (PostgreSQL-only Store code) connect.rs 248 (moved from store/connect.rs) + mod.rs — new open.rs 219 (moved from store/postgres.rs) reader.rs 221 (moved from store/reader.rs) + sqlite/ [837] (sqlite-only Store code) create.rs 35 (moved from store/create.rs) family.rs 134 (moved from store/family.rs) + mod.rs — new remove.rs 67 (moved from store/remove.rs) + transfer/ [601] mod.rs 3 (moved from store/transfer/mod.rs) phases.rs 263 (moved from store/transfer/phases.rs) resolve.rs 102 (moved from store/transfer/resolve.rs) steps.rs 233 (moved from store/transfer/steps.rs) workspace/ [863] calls.rs 97 mod.rs 4 serve.rs 249 unmount_guard.rs 274 + daemon/ [239] (moved from daemon/workspace/) commit.rs 41 (moved from daemon/workspace/commit.rs) mod.rs 10 (moved from daemon/workspace/mod.rs) mount.rs 70 (moved from daemon/workspace/mount.rs) recovery.rs 52 (moved from daemon/workspace/recovery.rs) status.rs 15 (moved from daemon/workspace/status.rs) unmount.rs 51 (moved from daemon/workspace/unmount.rs)The top-level
backup/anddaemon/folders andcommon/home/migrate/aregone after these moves. Bucket I/O stays in the
layerfs-store-backupdriver;control's
store/backup/holds bucket records and thebackup.*calls.6.3 Dependency direction inside
layerfs-controlThe folders say machine → Sandbox → Workspace; the imports must say it too.
Today they point both ways, mostly through misplaced helpers:
store,common,machine→sandbox::registryrandom,text,lockare general helpers kept insandbox/registry.rscommon/machine→sandbox::deploybundle_dir,IMAGE_ARCHIVEdescribe what a machine installsmachine/bundle.rscommon::home::migrate→ every entitymigrate/becomes its own top-level modulestore::identity→sandbox::filesproject/machine::worker::server→store,sandboxTarget rule, checked by a test that reads
use crate::…lines:Any edge outside the rule that cannot move in P-7 goes on a named allowlist
in that test, each with the issue that removes it.
7. Order of work
#556 → #587,#566 → #558,#567 → #557. The two stacksconflict in
sandbox/{record,get,mod}.rs; the second stack rebases.Steps 5 and 6 are moves only: each commit builds, passes the same tests and
reports a production LOC delta of 0 against its parent.
8. Acceptance
isolateNetwork: onnamespaces,true(and omitted) gives a commandwith only
lo(up) that cannot reach the worker port;falsereaches themachine's network;
trueondockerandnativeis refusedUnavailable. The result'snetworknames what ran.sleep 600started by one process is cancelled by anotherthrough
workspace.exec_cancel; the exec call returnskind: "cancelled"; the command's background children are gone (cgroup empty onnamespaces); cancelling an ended command answersended; a cancelagainst an unreachable worker answers
unknownand changes nothing.Busywhile an exec record is live;force: truecancelsand then closes; a stale record (dead owner) is removed and logged.
lostis reachable: remove a Sandbox's container behind control's back;sandbox.getanswerslost;sandbox.closeclears it.before and after, and the dependency test (§6.3) passes.
9. Open questions
isolateNetworkonnamespaceswhen omittedloup in isolated modeexecIdchosen by the caller, by control, or bothyifanxuaaa commentedon Nov 7, 2026
Closed by the owner (2026-11-07). Its five sub-issues (#548–#552) are closed as not planned: the PRs that were to deliver them (#556, #557, #558, #566, #567, #587) are recorded in
layerfs_v2_0_0as history only (git merge -s oursin #601), with mengmengmengqiang's commits and credit kept but no code applied. The control layer split (layerfs-project#101) replaced this area. Exec records and cancel continue in Ephemeral-AI-Lab/layerfs-project#113, which stays open.