Reproducible Workspaces and Snapshots
Tracked by pm-rbcvt2, pm-dkrmzv, and pm-gh1089.
These SDK primitives make a pm workspace reproducible without changing normal interactive behavior. Recipes control time and identifier entropy only inside an explicit async scope. Snapshots capture authoritative tracker data while leaving clone-local caches and recovery state disposable.
Reproducible recipes
import {
PM_WORKSPACE_RECIPE_SCHEMA,
executeWorkspaceRecipe,
type WorkspaceRecipe,
} from "@unbrained/pm-cli/sdk";
const recipe: WorkspaceRecipe = {
schema: PM_WORKSPACE_RECIPE_SCHEMA,
seed: "example-fixture-v1",
clock: "2026-07-28T10:00:00.000Z",
tickMs: 1,
operations: [{ action: "create", input: { title: "Deterministic task" } }],
};
const results = await executeWorkspaceRecipe(
recipe,
async ({ action, input }) => executeProjectAction(action, input),
);
The caller owns action dispatch. pm owns the deterministic execution context:
nowIso() advances from clock by tickMs, and generated item identifiers
derive from seed. Async scopes are isolated with AsyncLocalStorage, so
concurrent recipes do not share counters. Normal CLI and MCP execution still
uses the system clock and cryptographic randomness unless the process opts in
through the environment contract below.
defineWorkspaceRecipe immediately validates the clock, seed, tick, schema,
operation actions, and recursively JSON-compatible inputs. Its TypeScript
contract additionally rejects undeclared top-level recipe keys at compile time.
The returned recipe is detached and deeply frozen, so later caller mutations
cannot change a queued replay.
Store recipes in source control only when their inputs are safe to publish. Seeds are reproducibility inputs, not secrets. A replay is byte-identical only when it begins from equivalent authoritative state and invokes the same ordered operations with the same recipe.
CLI and MCP process configuration
Shell-based harnesses can install the same SDK-owned deterministic context for
the pm and pm-mcp processes without patching JavaScript globals:
PM_CLOCK=2026-07-28T10:00:00.000Z \
PM_CLOCK_TICK_MS=1 \
PM_SEED=example-fixture-v1 \
pm create --title "Deterministic task" --type Task --json
PM_CLOCK and PM_SEED are required together. PM_CLOCK must be an ISO-8601
instant with Z or a numeric UTC offset; timezone-less and non-ISO inputs are
rejected so separate hosts cannot interpret the same configuration differently.
PM_CLOCK_TICK_MS is optional, defaults to 1, and must be a non-negative integer. Partial or invalid input
fails before command dispatch with the stable
invalid_reproducible_process_environment code and recovery that names the
missing or invalid variable. Unset all three variables for normal interactive
behavior.
Every CLI process invocation begins a fresh deterministic scope. A workflow that launches several CLI processes must derive a stable, distinct seed for each ordered step; reusing one seed intentionally reproduces the same entropy sequence and can reproduce an existing generated identifier. A long-lived MCP server instead owns one process-lifetime sequence and advances it across its serialized JSON-RPC requests. Two equivalent servers therefore produce the same files without generating duplicate identifiers inside either server.
The public SDK exports PM_REPRODUCIBLE_PROCESS_ENV,
resolveReproducibleProcessEnvironment,
runWithReproducibleProcessEnvironment, and
createReproducibleProcessRunner for custom one-shot and long-lived process
transports.
Content-addressed snapshots
The SDK exports:
createWorkspaceSnapshotinspectWorkspaceSnapshotlistWorkspaceSnapshotsrestoreWorkspaceSnapshotdeleteWorkspaceSnapshotSNAPSHOT_SCHEMA
The matching CLI surface is:
pm workspace snapshot create before-migration
pm workspace snapshot list
pm workspace snapshot inspect before-migration
pm workspace snapshot restore before-migration --dry-run
pm workspace snapshot restore before-migration --force --message "Restore verified checkpoint"
pm workspace snapshot delete before-migration
Each object is identified by a SHA-256 fingerprint over sorted tracker-relative paths, file sizes, and bytes. Repeated captures of identical state deduplicate. Optional names are mutable references to immutable objects; delete a reference before deleting the object it protects. Names cannot use the 64-character lowercase hexadecimal fingerprint shape, keeping reference and object addressing unambiguous.
Snapshots include authoritative tracker files, including item documents, history, schema, settings, and installed project extension state. They exclude:
runtime/search/locks/transactions/checkpoints/
Restore stages the complete authoritative payload beside the tracker, swaps it
into place with directory renames, preserves the snapshot object store, and
discards stale caches and locks. Restore is guarded: use
pm workspace snapshot restore <target> --dry-run to inspect exact changed,
added, and removed file counts plus affected history streams and entries.
Mutation requires --force, captures the pre-restore state as a recovery
snapshot, and appends a durable _workspace audit event containing both
fingerprints, the impact summary, author, and reason. The SDK exposes the same
contract through planWorkspaceSnapshotRestore and returns the recovery
fingerprint and audit coordinates from
restoreWorkspaceSnapshotWithRecovery. The original
restoreWorkspaceSnapshot(pmRoot, target) signature remains compatible, but
now also captures recovery state and audit evidence before returning its
manifest.
Symbolic links are rejected so a snapshot cannot escape the tracker root.
Snapshot storage is clone-local under
.agents/pm/runtime/workspace-snapshots; it must not be committed or treated as
a backup of credentials. pm gc --scope runtime removes interrupted
.create-* and .ref-* publications after 24 hours while retaining newer
entries that may still belong to active operations.
Use snapshots as short-lived migration, reproduction, and package-development checkpoints. Git plus immutable pm history remains the durable collaboration record.