Ephemeral Scratchpad Harnesses β Design
Status: Draft Last Updated: 2026-08-19 Implements: π ADR-0017
Executive Summaryβ
harness run is a thin client verb over one new control op, scratch_run. The
daemon reuses the entire supervisor stack: it validates the definition with the
same rules project_up applies, mints a collision-free name under the registry
lock, registers the supervisor with a third provenance value (scratch), and
starts it. Ephemerality is structural: provenance scratch is excluded from
Save and Restore by construction, so no scratchpad can ever reach
state.json. Teardown is the existing remove op.
Architectureβ
Componentsβ
Protocol (internal/protocol)β
OpScratchRun Op = "scratch_run".- Request fields reuse the project payload vocabulary:
Harness(kind),Args,Workdir,Name(optional slug override). - Response:
ScratchRunData { Name string; Info HarnessInfo }. - Validation failures reuse the
invalid_projectcode (same validation layer); no new error code is needed.
Supervisor (internal/supervisor)β
Manager.ScratchRun(h core.Harness, slug string) (name string, err error): validates via the sharedvalidateProjectDefs-style rules (refactored to a definition-scoped helper), mints<slug>-<suffix>retrying on collision underm.mu, callsaddSupervisorLockedwith an OnChange ofnil(scratchpads never dirty state.json), recordsprovenance[name] = "scratch", appends order, unlocks, starts.- Name slug: sanitize(invocation words) β lowercase, non-alphanumerics to
-, collapse repeats, trim, cap at 40 chars. Suffix: 4 random base36 chars fromcrypto/rand. Saveskips names whose provenance isscratch(like the pre-2026-08-19 project exclusion, but for the scratch class only). Because the sentinel lives in the same string-keyed map as project ownership, the project namescratchis reserved βproject_uprefuses it β soSavecan never silently drop a real project's harnesses.Restorenever re-registers scratchpads (nothing persisted to read).- Restart policy forced to
no(session semantics) unless the definition says otherwise via the wirerestartfield.
Daemon (internal/daemon)β
opScratchRunmirrorsopProjectUp: wireβcore conversion (reuseharnessFromWire),mgr.ScratchRun, broadcastconfig_reloadedon success, reply with name +infoFor(snapshot).
Client / CLI (cmd/harness)β
- Cobra command
run:Args: cobra.MinimumNArgs(1), flags--workdir,--kind,--name. First positional dispatch: known kind (via a small alias table βclaudeβclaude-code) β kind + rest as args; otherwisegenericwith all positionals as onesh -ccommand (--kindoverrides). - Prints the minted name (JSON mode prints
ScratchRunData). rmandattach/logs/describeneed no changes β scratchpads are ordinary supervisors.
Data Modelβ
No new durable state. Provenance map gains the value scratch for scratchpad
names; the HarnessInfo.Project projection carries it so list/TUI can badge
scratchpads without a protocol change.
Testingβ
- Supervisor: minting (slug shape, suffix entropy, collision retry under
concurrency), no-persistence invariant (Save with live scratchpad), restart
policy default,
RemoveHarnesson a scratchpad. - Daemon:
scratch_runround-trip over a real socket, empty-argv validation error, list projection shows provenancescratch. - CLI: positional dispatch matrix (kind vs. generic vs.
--kind).
Alternatives Consideredβ
- Synthetic
scratchproject β rejected (π ADR-0017 Option 1): inherits the persistence and scoping semantics projects now carry. - Client-side temp harness.toml β rejected (π ADR-0017 Option 3): writes files, leaks crash residue, and ephemerality becomes unenforceable.
Related Artifactsβ
Direct relationships declared in YAML frontmatter (per ADR-0023 / SPEC-0018). Run /sdd:graph chain SPEC-0011 for the transitive view.