pi-crew 0.11.17 → 0.11.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +85 -0
- package/NOTICE.md +13 -0
- package/README.md +1 -0
- package/dist/index.mjs +576 -527
- package/docs/usage.md +66 -0
- package/package.json +3 -3
- package/skills/real-test-pi-crew/SKILL.md +15 -3
- package/src/config/config-validation.ts +26 -1
- package/src/config/env-allowlist-registry.ts +8 -0
- package/src/config/env-vars.ts +11 -0
- package/src/config/types.ts +36 -0
- package/src/extension/run-index.ts +30 -5
- package/src/runtime/child-pi/mock-fixtures.ts +36 -0
- package/src/runtime/live-session/embedded-tier.ts +994 -0
- package/src/runtime/live-session/live-session-runtime.ts +230 -30
- package/src/runtime/model/runtime-policy.ts +45 -0
- package/src/runtime/output/pi-json-output.ts +12 -25
- package/src/runtime/recovery/crash-recovery.ts +4 -0
- package/src/runtime/recovery/retry-executor.ts +90 -0
- package/src/runtime/scheduling/nested-slots.ts +26 -0
- package/src/runtime/session-usage.ts +48 -5
- package/src/runtime/task-runner/child-executor.ts +7 -2
- package/src/runtime/task-runner/hybrid-tier.ts +439 -0
- package/src/runtime/task-runner/live-executor.ts +53 -0
- package/src/runtime/task-runner/pre-execution.ts +1 -1
- package/src/runtime/task-runner.ts +295 -132
- package/src/runtime/verification/verify-gate.ts +164 -7
- package/src/schema/config-schema.ts +13 -0
- package/src/skills/validate.ts +1 -1
- package/src/state/atomic-write.ts +10 -3
- package/src/state/contracts.ts +15 -0
- package/src/state/coordination/locks.ts +118 -2
- package/src/state/coordination/process-lock.ts +199 -0
- package/src/state/event-log/cursor.ts +292 -84
- package/src/state/event-log/event-log-rotation.ts +30 -1
- package/src/state/event-log/poison-marker.ts +251 -0
- package/src/state/event-log/worker-atomic-writer.ts +282 -41
- package/src/state/types.ts +14 -3
- package/src/ui/syntax-highlight.ts +1 -1
- package/src/utils/git.ts +6 -71
- package/src/utils/incremental-reader.ts +316 -36
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,90 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
## [0.11.19] — pi-durable wave: events.jsonl durability, run-lock socket dual-guard, usage ledger, verify-gate determinism (2026-10-13)
|
|
6
|
+
|
|
7
|
+
Spec-driven delivery of the pi-durable research wave (packets U22/U16/U21/U13-ADJ/U9-ADJ + D4) against upstream pi-durable `a4d040d30`. Full SDD record: `../sdd-pi-durable-2026-10-13/` (SPEC + 6 đính chính, WORKITEMS W0–W31, GATES ~21 evidence rows); live battery: `docs/real-test/reports/real-test-2026-10-13-battery-live-session.md`. 15 implementation commits `85917a44`..`1acd01e7` on `d29df7dd`, merged fast-forward 2026-10-13.
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **feat(state): U22 — torn-vs-poison unified scanner + sticky sidecar markers for events.jsonl** — every read path (`parseJsonlEvents` full-read, cursor/tail incremental reads) now classifies corruption identically: a **torn** final line (no trailing newline — crash mid-append) is skipped non-stickily for this read only, while a **poison** complete-but-unparseable line is recorded in a sidecar `<events>.poison.jsonl` (`{lineIndex, byteOffset, reason, firstSeen}`) and skipped directly on later reads without re-parsing. Marker **lifetime is bound to the file instance it describes** (review-round M1, found independently by two reviewers): log rotation MOVES the sidecar alongside the archive, compaction DELETES it, and every marker hit is re-parsed once before skipping — a line that parses means the marker is stale and gets pruned, which also self-heals injected/stale same-path markers. Cap 1000 entries → last-resort whole-file quarantine (`.corrupt-<ts>` rename, `quarantineCorruptFile` pattern); `sweepOldArchives` now sweeps `<archive>.poison.jsonl` too. Result metadata gains optional `tornTail`/`poisonSkipped`; clean files are read bit-for-bit as before.
|
|
12
|
+
- **feat(state): U22-b0 — worker-atomic-writer hardening** — fsync of the temp file AND its directory on every rename publish; per-request timeout (env `PI_CREW_WORKER_ATOMIC_TIMEOUT_MS`, default 10000) so a hung worker thread can never wedge the append queue; `on("exit")` drain of in-flight requests; temp-dir cache; worker-side append opens with `O_NOFOLLOW` (non-win32) closing the TOCTOU symlink-swap window between the main-side guard and the worker-side open (review F3). Opt-in surface unchanged.
|
|
13
|
+
- **feat(state): U21 — run-lock dual-guard (file→socket)** — new `src/state/coordination/process-lock.ts` (adapted from `@earendil-works/chord` `manager.ts` MIT — see `NOTICE.md`): abstract-namespace unix socket `\0pi-crew-<sha256ns>` on Linux, tmp-file socket + `accepting()` probe takeover of leftovers on other platforms; `EADDRINUSE` maps to a structured `ProcessLockHeldError {reason:"locked"}`. `withRunLock` (async) acquires the socket AFTER the file lock as a second guard for hosts where file locks are weak (NFS etc.); socket-held contention releases the file token and continues the existing WAIT/backoff within the `staleMs*2` deadline (D1: live-holder steal stays a warn, never a dual-entry). Release order is socket-then-file with `try/finally` so a socket release failure cannot leak the file token (review FIX-4). The re-entrance token is registered IMMEDIATELY after file acquisition, before the socket listen macrotask — closing the window where a same-process contender saw an "invisible" token and stole a live holder's corpse (RR-011). `withRunLockSync` remains file-only by design (a sync path cannot await listen — documented deviation). Env `PI_CREW_RUN_LOCK_SOCKET` (default ON; `0` restores pre-U21 behavior bit-for-bit).
|
|
14
|
+
- **feat(runtime): U13-ADJ — verify-gate determinism layer 3 + never-fail-task belt** — env `PI_CREW_VERIFY_DETERMINISM` (default OFF). When ON and the gate is enabled, after the default checks (typecheck, test:critical) the most critical check is re-run once; PASS→FAIL (or FAIL→PASS) downgrades that check's verdict to **INCONCLUSIVE** ("deterministic-check: flaky <name>"), never FAILED, and executor death on the rerun is INCONCLUSIVE as well. No rerun when checks already FAILED. The verifier prompt no longer asserts build state as "ground truth" after an executor error — it renders the state as UNKNOWN (prompt-bias gap). The belt around verifier spawn now also wraps the skip-path `writeArtifact` (FS errors degrade to a logged warning, never a task failure), and the finalizer is hoisted OUT of the belt's try so a finalizer throw propagates instead of falling through to a verifier spawn after a skip (review M2).
|
|
15
|
+
- **feat(runtime): U9-ADJ — retry-loop no-progress detection hook + attempt ownership fields** — `RetryHooks` gains an optional `noProgress({attempt, lastResult, previousResult, durationMsLast})` predicate (caller-defined semantics; executor stays generic): consulted after a failed attempt that `shouldRetry` approved, a `true` verdict stops the loop with a distinct `no_progress` reason; a throwing hook is treated as false and logged, never crashes. Dormant by construction — no production caller yet (wired for the F4 timeout packet). `TaskAttemptState` gains optional additive `owner`/`intent` fields; both the tasks.json and sqlite validators accept them.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- **fix(runtime): U16 — usage ledger 2-tier (embedded + subprocess)** — the embedded-tier usage read was dead on BOTH branches (`session.getStats` never existed in the SDK; the `session.stats` fallback property neither), so `liveResult.usage` was always `undefined` for SDK sessions. Now reads `session.getSessionStats?.()` (the real SDK method) and delegates to the single unified parser `usageFromValue` (session-usage.ts): nested `tokens.{input,output,cacheRead,cacheWrite}` + `cost` (accepts BOTH the SDK session-level `cost: number` and the message-level `cost: {…, total}` object — `cost.total` wins), flat legacy shapes still parse, and the nested-key allowlist regains `"message"` (dropped by an earlier refactor but accepted by the historical `pi-json-output` parser). `lifetimeUsage` in the child executor now accumulates `cacheRead` and `cost` (not just input/output/cacheWrite), and `compactUsage` always keeps a stable `cost` key (0 when unseen) so ledger consumers see a stable schema. Live-verified end to end: run tasks.json carries `cacheRead` + numeric `cost` from message-level cost objects, and `team action=summary` renders them.
|
|
20
|
+
- **fix(build): US-011 — strip-types regression from a TS parameter property** — `WorkerRequestBookkeeper` used `constructor(private …)`, which `--experimental-strip-types` (the child test/bench path) cannot transform — every strip-only load died. Parameter properties remain banned in `src/` (R3-1 recurrence; caught by the FOLLOWING phase's gate, not the author's — the rule "run one spawn-child test file after any constructor-syntax phase" is now in the real-test skill).
|
|
21
|
+
- **fix(deps): D4 — pin `pi-tui` to a version window** — peerDependency `>=1.0.4 <2.0.0` (floor for the vendored OSC-7501 wire format + `PROGRAM_STATUS_QUERY`, cap against a 2.x breaking change) and devDependency pinned exact `1.0.4`. Deviation from the spec's "exact peer pin": a floor+cap avoids install conflicts with pi 1.2.x hosts that ship their own pi-tui.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- **chore(gates): lazy-import budget ratchet 523→525** — the two new state modules (`poison-marker.ts`, `process-lock.ts`) are legitimate new entries in the `index.bundle.ts` graph; budget bumped with justification (the ratchet stays strict otherwise).
|
|
26
|
+
- **chore(build): `dist/index.mjs` is now committed for release commits** — `check-bundle-staleness --committed-hash` requires the shipped bundle to match a fresh rebuild bit-for-bit (T11j); release commits carry the built artifact (md5 `c197855f917997a404ae39111d8ccfdc`, 1735.0 KB).
|
|
27
|
+
|
|
28
|
+
## [0.11.18] — U11 hybrid 2-tier worker + Δ-wave benches/gates + windows CI repair (2026-10-12)
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- **bench(runtime): b15 — broker/scheduler task-array scan-cost microbench (Δ3)** — `bench/b15-broker-scan-cost.bench.ts` (b14 convention): measures the REAL scheduler paths (`taskGraphSnapshot`, `getReadyTasks`), a line-cited replica of the module-private `dagReadyTaskIds` call-site, the REAL crew-broker delegate/wait call-sites over a unix socket with an injected instant fake spawner, `loadRunManifestById` cold/warm floors, and the `scanZombieTempWorkspaces` walk at N=10/100/1000 in isolated mkdtemp homes. Scan-count instrumentation is bench-local (Array.prototype wrapped in-process only) — zero hot-file edits. **Δ3 verdict (bench-first rule): all 6 candidate index sites NOT-JUSTIFIED** — at realistic N≤11 tasks/run, scans cost 0.006-0.05 ms/call vs ms-scale I/O floors (loadCold 8.1 ms, DAG planner 28.8 ms @ n1000); even at 90× N, scans (0.86 ms) sit below the parse floor. **Held-lifetime audit closed no-op:** no structure is UNBOUNDED-RISK (details: `docs/perf/broker-scan-audit-2026-10-11.md`).
|
|
33
|
+
- **feat(runtime): U11 — hybrid 2-tier worker model (embedded tier, DEFAULT-OFF)** — per-role opt-in to the EMBEDDED worker tier (in-process Pi session on the U4 live-session stack) with the subprocess tier kept as the default and automatic fallback:
|
|
34
|
+
- **Config** `runtime.embeddedWorkers {enabled, roles, maxConcurrent, faultThreshold}` (types + schema + lenient parse stay in sync, F19-1 discipline; all-invalid block collapses to undefined so a malformed config can never enable the tier). With the block absent/disabled, tier routing is a byte-identical no-op — no imports, no events, no budget reads (pinned by regression tests in both lanes).
|
|
35
|
+
- **Routing** (`resolveTaskRuntimeKind`, optional 5th input — every pre-U11 call site unchanged): an opted-in role upgrades a child-process resolution to live-session only. `isolationPolicy.isolatedRoles`, the scaffold kind, and the `PI_CREW_DEPTH > 0` nesting guard ALWAYS beat the opt-in (the depth rule is applied to the embedded upgrade itself — extended, never weakened). The legacy `runtime.mode: live-session` path is deliberately frozen (ADR 2026-08-15 (a)) — it gets no budget/ledger semantics.
|
|
36
|
+
- **Orchestration** (`task-runner/hybrid-tier.ts`, lazily imported only when the policy is enabled): fail-fast embedded ceiling (blast-radius bound, default `max(1, floor(worker-cap/2))`) → SHARED global worker slot (the exact `acquireWorkerSlot` semaphore the subprocess tier uses — no concurrency bypass; abort while queued takes no slot) → durable per-task fault ledger (`task.diagnostics.embeddedTier`, disk-merged freshest-count so retries/recovery see it) → automatic fallback to subprocess after `faultThreshold` counted faults (default 2; aborts and capacity rejections never count). `tier.decision` / `tier.fault` / `tier.fallback` events registered in `TEAM_EVENT_TYPES`. The subprocess fallback runs strictly sequentially after the live attempt resolved — the single-writer sink invariant is preserved.
|
|
37
|
+
- **Mitigations** (`live-session/embedded-tier.ts`, gated on `embeddedMitigations` passed only for embedded-routed tasks): (a) builtin-tools-only enforcement with verify-after-set and fail-closed degradation (no `mcp__*`, no extension drift); (b) provider `timeoutMs` clamp + restore on the session's ModelRuntime; (c) turn-count watchdog (bounded grace → dispose + escape); (d) session-side concurrency cap (fail-fast, never queue); ref-counted process `uncaughtException` guard (quarantine-all embedded sessions, host kept alive, 20-quarantine circuit breaker restores default crash behavior); ring-capped rate-limited memory metrics; factory-surface + global-state asserts (U4 pattern).
|
|
38
|
+
- **Fix round (2026-10-11, P1)**: provider-timeout registration is now ref-counted per (ModelRuntime, providerId) with wrapper-base chasing — concurrent embedded sessions share ONE ModelRuntime, and the original apply/restore could stack wrappers and permanently leak the embedded timeout clamp onto every later session on that runtime (silently — restore reported success). The registered wrapper now always delegates to the chased BASE provider at the min of the active clamps; the base is re-registered exactly when the last session leaves; any restore order is safe (pinned by two regression tests).
|
|
39
|
+
- **docs(runtime): U11 DEFAULT-OFF policy guide** — `docs/usage.md` gains a "Hybrid 2-tier workers" section (config example, per-key semantics, the full safety model, fallback stickiness, `tier.*` / `live-session.embedded_*` observability) and the README config table documents `runtime.embeddedWorkers`.
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
|
|
43
|
+
- **fix(utils): drop dead `hosted-git-info` branch in `parseGitUrl` — Δ2 gate allowlist back to strict** — the Δ2 finding's `require()` in `src/utils/git.ts` was verified to never resolve in ANY environment (undeclared, and in the repo checkout even the transitive path via peer `@earendil-works/pi-coding-agent` is unreachable from `src/`), so the hosted-parse candidate loops were dead code in every deployment and `parseGenericGitUrl` was the only path that ever executed. Removed the require, its types/loader, and both candidate loops (zero behavior change); `OPTIONAL_RUNTIME_REQUIRES` in `scripts/check-lazy-imports.mjs` is empty again — the gate is strict, resolving the "pending `optionalDependencies` decision" note in the Δ2 entry below.
|
|
44
|
+
|
|
45
|
+
- **feat(scripts): Δ2 — declared-deps/lazy-import gate rewritten from regex to AST** (esbuild type-strip + acorn walk of `ImportDeclaration` / `export … from` / runtime `import()` / `require()`-`require.resolve()`, ports upstream check-runtime-deps semantics; per-entry budget `{maxFiles: 521, forbid: [test/, scripts/, docs/, dist/]}` for `index.bundle.ts`, ports upstream check-entry-graphs; `--root` fixture seam for tests) — closes every RR-021 residual regex blind spot; first run surfaced one real finding: `hosted-git-info` is an undeclared optional `require()` in `src/utils/git.ts` (resolves transitively via peer `@earendil-works/pi-coding-agent`; explicitly allowlisted in the gate pending an `optionalDependencies` decision).
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
- **fix(state): register 3 mailbox event types — CI `check:event-types --enforce` green** — `mailbox.send` (`crew-broker.ts:993`), `mailbox.pickup` and `mailbox.pickup_deduped` (`inbox-poll.ts`) were emitted but missing from `TEAM_EVENT_TYPES`, failing the CI registry-enforce gate (U5 exactly-once delivery audit trail; dedup key + receipt payload only — never message bodies). Bundle note: the registry const is tree-shaken from `dist/index.mjs` (fresh build byte-identical to the committed dist — `check-bundle-staleness --committed-hash` passes with no dist delta to commit).
|
|
50
|
+
- **fix(test): fill 4 empty arrow-function bodies in `socket-path-fbat1.test.ts`** — comment-filled the `destroy: () => {}` / `net.createServer(() => {})` bodies that tripped biome `noEmptyBlockStatements` in CI; zero assertion changes.
|
|
51
|
+
- **fix(test): `verify-gate-tool` Windows path assertion — exact string compare instead of `new RegExp(path)`** — building a RegExp from an absolute Windows path consumes the backslashes as regex escapes (`\t`→TAB, `\r`→CR, `\U`/`\p` identity-dropped), so the pattern could never match the literal path (windows-latest CI failure). Now `assert.equal` on the exact command string, plus a regression pin proving a raw-backslash RegExp does NOT match a Windows path.
|
|
52
|
+
- **fix(test): U15 concurrent-dispose CI flake — fork-free busy loop closes the deferred-TERM-trap race** — with `while :; do sleep 1; done` there is a write→fork gap right after the readiness `echo`: dispose's group TERM lands inside that gap on loaded runners, dash defers the pending trap until the NEXT (post-signal) sleep dies (~1s) > `gracefulMs` 500, so SIGKILL beats the trap and the marker never appears (~30% flake; probe-pinned: failing runs always tReady<6ms, passing runs >20ms). `while :; do :; done` has no fork to straddle — dash runs the pending trap at the next simple-command boundary (ms), far inside the grace window. 11/11 pass locally.
|
|
53
|
+
- **fix(state): stop unit-test runs leaking into the real user crew root** — the
|
|
54
|
+
test runner now isolates the crew home per suite (`scripts/test-runner.mjs`
|
|
55
|
+
sets BOTH `PI_CREW_HOME` and `PI_TEAMS_HOME` to a per-suite temp dir; PI_TEAMS_HOME
|
|
56
|
+
wins the mirror pair, so setting only one is silently defeatable; opt-out
|
|
57
|
+
`PI_CREW_TEST_NO_HOME_ISOLATION=1`; cleanup on all exit paths). Until now every
|
|
58
|
+
fixture that created a run from a bare `/tmp` cwd routed its state to
|
|
59
|
+
`~/.pi/agent/extensions/pi-crew/` via `scopeBaseRoot()`'s no-repo fallback — and
|
|
60
|
+
every live Pi session's async-notifier toasted those fixture failures as
|
|
61
|
+
`Error: pi-crew run failed: team_… (inline-seam-fixture/none)` (104 leaked run
|
|
62
|
+
dirs since June; quarantined + cleaned 2026-10-11).
|
|
63
|
+
- **fix(run-index): hide terminal bare-temp-cwd runs from non-temp crew roots**
|
|
64
|
+
(defense-in-depth for the same leak) — `collectRuns()`'s 2.19 temp guard now
|
|
65
|
+
also covers the CWD dimension: in NON-temp roots (the real project/user roots)
|
|
66
|
+
a TERMINAL run whose cwd sits under a temp dir WITHOUT repo markers is a
|
|
67
|
+
test/isolated-workspace fixture parked by the no-repo fallback and is hidden.
|
|
68
|
+
Temp-root scans keep their original semantics (active runs liveness-gated,
|
|
69
|
+
terminal runs listed — prune/health consumers rely on it); temp cwds WITH repo
|
|
70
|
+
markers (deliberate /tmp projects, isolated worktrees) and active bare-temp-cwd
|
|
71
|
+
runs stay listed. By-ID resolution (`team status <runId>`) is unchanged.
|
|
72
|
+
Covered by `test/unit/extension/core/run-index-temp-cwd.test.ts`.
|
|
73
|
+
|
|
74
|
+
### Docs
|
|
75
|
+
|
|
76
|
+
- **docs(u12): upstream pi-durable tracking doc** — new
|
|
77
|
+
`docs/reviews/pi-upstream-tracking-u12.md` (living re-check log, rerun every pi npm
|
|
78
|
+
release). Records the U12 spec's 4 adoption triggers with verdicts pinned at upstream
|
|
79
|
+
`origin/main` = `cdaf6c227`: T1 banner/API-freeze NOT met (`README.md:3` banner stands,
|
|
80
|
+
`[Unreleased]` opens with 5 breaking bullets), T2 multi-process NOT met (everything still
|
|
81
|
+
under `packages/coding-agent/src/experimental/`, `PI_EXPERIMENTAL=1`-gated), T3 subagent
|
|
82
|
+
conversations via services NOT met (TODO open at `services/README.md:44`), T4 concurrency
|
|
83
|
+
cap MET via pi-crew's own `resolveBatchConcurrency` cap (upstream `cb4375493` changed the
|
|
84
|
+
pass algorithm, added no cap). Appends the 7-item blacklist of unreleased post-1.1.0 churn
|
|
85
|
+
from the 2026-10-11 delta §5, each with verbatim `[Unreleased]` changelog wording. Overall:
|
|
86
|
+
still track-only (1/4 vs ≥3/4 bar); exact-pin policy unchanged.
|
|
87
|
+
|
|
3
88
|
## [0.11.17] — upgrade wave A: pi-1.1.0 adoption, central env registry, OSC 7501 program-status (2026-10-11)
|
|
4
89
|
|
|
5
90
|
Implements the 10-item Wave A from `pi-crew-upgrade-research-round2.md` +
|
package/NOTICE.md
CHANGED
|
@@ -45,6 +45,19 @@ imports); the vendoring rationale (no peer version floor + the symbol is not
|
|
|
45
45
|
re-exported from the package root before 1.1.0) is documented in the file
|
|
46
46
|
header and `src/ui/pi-ui-compat.ts`.
|
|
47
47
|
|
|
48
|
+
### Process-lock socket guard (2026-10-13)
|
|
49
|
+
|
|
50
|
+
`src/state/coordination/process-lock.ts` is adapted from
|
|
51
|
+
`@earendil-works/chord` (MIT), `packages/chord/src/manager.ts:82-170 @
|
|
52
|
+
a4d040d30` — processLock adapted from @earendil-works/chord (MIT),
|
|
53
|
+
packages/chord/src/manager.ts:82-170 @ a4d040d30. No code was copied verbatim;
|
|
54
|
+
the adaptation renames the socket namespace (`pi-crew-`), drops the
|
|
55
|
+
env-passing lock-inheritance mechanism (pi-crew derives lock names from run
|
|
56
|
+
dir paths and never passes lock state through child env), unrefs the
|
|
57
|
+
listening socket so a held lock cannot keep the extension host alive, and
|
|
58
|
+
adds typed contention errors. See the file header for the full adaptation
|
|
59
|
+
list.
|
|
60
|
+
|
|
48
61
|
## crew-vibes font assets
|
|
49
62
|
|
|
50
63
|
- `assets/runner-spritesheet.png` and the 16 runner glyphs (U+E700..U+E70F) traced
|
package/README.md
CHANGED
|
@@ -124,6 +124,7 @@ Most-used keys (full set: [docs/usage.md](docs/usage.md) · [schema.json](schema
|
|
|
124
124
|
| Key | What it does |
|
|
125
125
|
|-----|--------------|
|
|
126
126
|
| `runtime.mode` 🔒 | `auto \| scaffold \| child-process \| live-session` — how workers execute |
|
|
127
|
+
| `runtime.embeddedWorkers` | U11 hybrid tier, **DEFAULT-OFF**: opt roles into in-process (embedded) workers with 2-fault fallback to subprocess — see [docs/usage.md](docs/usage.md#hybrid-2-tier-workers-embedded-tier-u11-default-off) |
|
|
127
128
|
| `executeWorkers` 🔒 | `false` = dry-run planning only, no child processes |
|
|
128
129
|
| `asyncByDefault` 🔒 | detach every run by default (survives session switches) |
|
|
129
130
|
| `limits.maxConcurrentWorkers` | hard cap on parallel workers |
|