Current focus (owner direction, 2026-09-21)
Two cases are the optimization targets, and each is to be matched against
v0.1.6 rather than argued about from memory:
|
case |
family |
route |
timer |
size |
hops to the Store |
| 1 |
namespace-10000 |
init_namespace |
namespace |
layerstack_init_ns |
10,000 files / 300 MB + 100 MB anchor = 400 MB |
direct, host-side |
| 2 |
payload-create-100m |
payload_create_read |
workspace |
pure_call_sum_ns |
100 MB payload |
container FUSE + daemon to the host service |
Acceptance bar — APPROVED (owner, 2026-09-21). namespace-10000 must
(1) reproduce ≤ 578.245 ms (≥ 691.8 MB/s) with a declared cache contract and a
passing verification, (2) pair against the v0.1.6 reference arm — same seed,
fresh-output, topology, harness and image identity, same declared cache state — with
the candidate median ≤ the reference median, both with spread, and (3) be met by
one declared sample per arm, never by selecting the fastest of several runs. The
executing plan is
issue219-namespace-10000-plan.md.
Case naming. In init_namespace the number in the case id is a file count,
not megabytes**: namespace-{100,1000,10000,100000} at 100 files / 5 MB (+1 MB
anchor), 1,000 / 20 MB (+5 MB), 10,000 / 300 MB (+100 MB) and 100,000 / 500 MB
(+100 MB). There is no namespace-100mb case; the 100 MB is the anchor file
(NAMESPACE_ANCHOR_BYTES), which only the two largest tiers carry. Case 1 is
therefore the 10,000-file tier with the 100 MB anchor, and namespace-100000 is its
larger sibling with the same anchor.
Neither focus case has a baseline arm (per init_namespace case: 100 = 11
candidate / 3 baseline, 1,000 = 6 / 0, 10,000 = 8 / 0, 100,000 = 32 / 15;
payload_create_read = none at all), so both must produce their reference arm.
namespace-100000 is the substitute if a case with an existing pair is preferred.
Case 1 writes the namespace into the host-owned Store directly (the runner stages
it under payload/, and namespace/store-footprint are the routes that take no
payload/input staging and no daemon hop). Case 2 carries its payload from the
container through the daemon and the host service into the same Store
(LAYERFS_EXEC_TRANSPORT=daemon, LAYERFS_FUSE_TRANSPORT=daemon; the receipt's
scope line is "Linux daemon/FUSE container command window; host coordinator/Store
process CPU/RSS/IO reported separately"). Both end in one Store commit.
The namespace-init multi-worker count (4 = SMALL_CONTENT_WORKERS,
crates/layerfs-layerstack-store/src/objects.rs; 8 =
construction_worker_limit(), crates/layerfs-workspace/src/changes.rs) is
recorded and is not treated as the explanation for our slowness: it is the
cheapest thing to refute by measurement, and the RCA continues past it.
Exploration, pairing and gap-finding come first; no optimization is authorized by
this section. The prompt is
issue219-two-case-rca-prompt.md.
Status: Blocked; not implemented. Nothing in the product admits, counts or refuses Workspace creation today.
Why this exists
#216 defined two primary operator-facing concurrency controls. Its writer half — max_concurrent_writes_per_store — landed in #217 (merge 1f34ac939) and is measured and tested. The other half, max_workspaces_per_sandbox, could not be implemented there: there is no Workspace, sandbox or FUSE lifecycle component in this tree, because pair 1 (#179) owns it and is not merged. #216 was closed as completed by owner decision; this issue carries the remaining acceptance row so it is not lost.
The control, as defined in #216
|
|
| Setting |
max_workspaces_per_sandbox (proposed name) |
| Meaning |
maximum live Workspace instances owned by one sandbox |
| Unit |
a live instance, not a Branch, Commit or stored root. A frozen generation and its live successor are one instance |
| Enforcement owner |
pair 1 sandbox/Workspace lifecycle |
| Reservation |
capacity is checked and reserved before the instance becomes visible |
| Teardown |
an incomplete teardown keeps counting until its resources are released |
| Aggregation |
separate sandboxes have independent counts; this needs a trusted sandbox identity and an explicit rule if more than one caller can create Workspaces |
Default, supported range, validation and configuration-change behavior are open and must be selected with the implementation. The previously discussed 16 was illustrative and is not an approved default. The writer control's own precedent is available: one persisted value, an explicit refusal at the bound, retained ownership that a lowering never releases, and a documented change path (Store::set_max_concurrent_writes).
Acceptance
Boundaries
Blocked on #179; do not invent a Workspace lifecycle here, and do not enforce the limit in a component that does not own creation. No new durability, no third-party patching, no aggregate gate.
Current focus (owner direction, 2026-09-21)
Two cases are the optimization targets, and each is to be matched against
v0.1.6 rather than argued about from memory:
namespace-10000init_namespacenamespacelayerstack_init_nspayload-create-100mpayload_create_readworkspacepure_call_sum_nsAcceptance bar — APPROVED (owner, 2026-09-21).
namespace-10000must(1) reproduce ≤ 578.245 ms (≥ 691.8 MB/s) with a declared cache contract and a
passing verification, (2) pair against the v0.1.6 reference arm — same seed,
fresh-output, topology, harness and image identity, same declared cache state — withthe candidate median ≤ the reference median, both with spread, and (3) be met by
one declared sample per arm, never by selecting the fastest of several runs. The
executing plan is
issue219-namespace-10000-plan.md.Case naming. In
init_namespacethe number in the case id is a file count,not megabytes**:
namespace-{100,1000,10000,100000}at 100 files / 5 MB (+1 MBanchor), 1,000 / 20 MB (+5 MB), 10,000 / 300 MB (+100 MB) and 100,000 / 500 MB
(+100 MB). There is no
namespace-100mbcase; the 100 MB is the anchor file(
NAMESPACE_ANCHOR_BYTES), which only the two largest tiers carry. Case 1 istherefore the 10,000-file tier with the 100 MB anchor, and
namespace-100000is itslarger sibling with the same anchor.
Neither focus case has a baseline arm (per
init_namespacecase: 100 = 11candidate / 3 baseline, 1,000 = 6 / 0, 10,000 = 8 / 0, 100,000 = 32 / 15;
payload_create_read= none at all), so both must produce their reference arm.namespace-100000is the substitute if a case with an existing pair is preferred.Case 1 writes the namespace into the host-owned Store directly (the runner stages
it under
payload/, andnamespace/store-footprintare the routes that take nopayload/inputstaging and no daemon hop). Case 2 carries its payload from thecontainer through the daemon and the host service into the same Store
(
LAYERFS_EXEC_TRANSPORT=daemon,LAYERFS_FUSE_TRANSPORT=daemon; the receipt'sscope line is "Linux daemon/FUSE container command window; host coordinator/Store
process CPU/RSS/IO reported separately"). Both end in one Store commit.
The namespace-init multi-worker count (4 =
SMALL_CONTENT_WORKERS,crates/layerfs-layerstack-store/src/objects.rs; 8 =construction_worker_limit(),crates/layerfs-workspace/src/changes.rs) isrecorded and is not treated as the explanation for our slowness: it is the
cheapest thing to refute by measurement, and the RCA continues past it.
Exploration, pairing and gap-finding come first; no optimization is authorized by
this section. The prompt is
issue219-two-case-rca-prompt.md.Why this exists
#216 defined two primary operator-facing concurrency controls. Its writer half —
max_concurrent_writes_per_store— landed in #217 (merge1f34ac939) and is measured and tested. The other half,max_workspaces_per_sandbox, could not be implemented there: there is no Workspace, sandbox or FUSE lifecycle component in this tree, because pair 1 (#179) owns it and is not merged. #216 was closed as completed by owner decision; this issue carries the remaining acceptance row so it is not lost.The control, as defined in #216
max_workspaces_per_sandbox(proposed name)Default, supported range, validation and configuration-change behavior are open and must be selected with the implementation. The previously discussed 16 was illustrative and is not an approved default. The writer control's own precedent is available: one persisted value, an explicit refusal at the bound, retained ownership that a lowering never releases, and a documented change path (
Store::set_max_concurrent_writes).Acceptance
Boundaries
Blocked on #179; do not invent a Workspace lifecycle here, and do not enforce the limit in a component that does not own creation. No new durability, no third-party patching, no aggregate gate.