- step() test helper now carries skillName, so the WS-6 round-trip fixture
actually exercises the INVERSION CONTRACT for skillName (was silently dropped).
- executeWorkflowStep now strips an inherited FUSION_HEADLESS on board runs
(unattended=false), preserving the U3 default-safe invariant — a board step
nested under a headless-env parent could otherwise skip user questions.
Added a regression test for the inherited-env strip.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address confirmed code-review findings on the CE workflow-step change:
- Graph-path spawn lifecycle (adversarial A-1/A-2): the graph path returns from
execute() before its outer finally that calls terminateAllChildren, so U8's new
coding-mode fn_spawn_agent children orphaned their sessions/worktrees and their
ids accumulated in the per-parent spawn budget, starving later steps' fan-out.
Call terminateAllChildren in maybeExecuteWorkflowGraph's finally (mirrors the
non-graph cleanup).
- INVERSION CONTRACT parity (api-contract AC-2 + testing TF-001): add skillName to
the workflow-steps-to-ir round-trip projections + a skill-step fixture, so the
contract the comment claims is actually asserted.
- Silent skill-load degradation (adversarial A-3 / Risk-4): warn when a step names
a skill but FUSION_CE_SKILLS_DIR is unset, instead of failing silent.
- Dead branch (maintainability M-01): drop the always-false unattendedRun guard;
keep the delete + extension-point comment.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The builtin compound-engineering workflow runs via runGraphCustomNode, which
never loaded the named skill or threaded the plugin-injected runtime env, and
fn_spawn_agent was registered only in the main session. This wires the real seam:
- U8: thread injected FUSION_CE_* env into skill/model graph steps (shared
buildInjectedRuntimeEnv helper); register createSpawnAgentTool for coding-mode
skill steps (readonly still strips spawn).
- U1: merge the step's skillName (namespaced + bare) into requestedSkillNames and
pass FUSION_CE_SKILLS_DIR as additionalSkillPaths so the bundled SKILL.md is
discovered and selected.
- U2: prepend the Fusion workflow-step conventions preamble (await-input sentinel,
FUSION_HEADLESS degrade, persona fan-out via systemPromptOverride).
- U3: explicit unattended opt-in sets FUSION_HEADLESS=1 (default-safe board run).
- U9: path-confined persona read documented in the preamble; accepted
write-capability posture documented at the coding-mode tool registration.
- KTD-6: verdict-JSON contract required only for gate / skill-less steps.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Reliability (P3): emit an `Auto-recovered:`-prefixed log on the
benign-todo stale-failure clear path so NotificationService
proactively cancels the pending failure timer (recoveredStatus path)
instead of relying only on the fire-time re-check, which is
race-contingent when failureNotificationDelayMs is near 0. Scoped to
the actual-clear path so the common no-failure re-queue isn't
mislabeled as a recovery.
- Project-standards (P3): add the required yyyy-MM-dd-hh:mm stamp to the
new FNXC comments (AGENTS.md FNXC_LOG convention).
- Maintainability (P3): extract the scheduler "queued" waiting marker to
a named SCHEDULER_WAITING_STATUS constant.
- Testing: pin the guard's skip on a clean todo row, assert the
Auto-recovered log fires on the stale-failure path, and add a
paused+unassigned in-progress idle case (paused early-return wins over
the ephemeral active-session path).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A pause/resume abort parked status:"failed" on an earlier non-todo
observation stays dispatchable (scheduler filters on column+paused, not
status, scheduler.ts:1288) and re-enters the FN-6782 benign-todo branch.
That branch logged "benign" but left status:"failed"/error on the row,
so the board kept showing it failed and the deferred failure
notification fired (notification-service fire-time check sees
status==="failed") — contradicting the benign log. recoverPausedAbortFailures
that would clear it is suppressed during global/engine pause
(self-healing.ts:8125), so the failure survived the pause window.
Reconcile the row with the benign reclassification: clear status/error
when the live row carries them, so the board agrees it's benign and the
pending notification is suppressed at dispatch.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses findings from a multi-agent review of the two prior fixes.
P0 (executor.ts): the stale-conflict recovery force-removed worktreePath with
no bounds check; that path can come from a git admin entry resolving outside
.worktrees/. Now refuses unless the path is inside the worktrees dir, not a
symlink (realpathSync), not a registered worktree, and not actively owned, and
re-verifies liveness in the catch instead of trusting the error string. Also
excludes spawn failures (spawn git ENOENT) from the stale-path classification.
worktree-pool.ts: resolveGitdirPointer -> dotGitPointerIsDangling. Reaps only
when a .git link's gitdir target is confirmed missing; a real .git dir,
unparseable pointer, or any read/stat failure is treated as NOT dangling
(conservative) so a transient read error on a live worktree can't trigger rm.
Drops the string|"directory"|null sentinel union.
core store.ts: bypass the reconcile recency window when the live task table is
empty (corruption/restore: surviving task.json keep old mtimes) and when
fusion.db was auto-recovered on startup, so .recover row loss isn't stranded.
Adds an ignoreRecencyWindow option.
Tests: executor recovery + out-of-bounds refusal, unparseable .git skip,
recency boundary, empty-DB/forced bypass. engine 135 + core 12 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Directories under .worktrees/ that survive with a dangling .git pointer
(present on disk, but their .git/worktrees/<name> admin entry is gone) are
invisible to `git worktree list`/`prune` yet collide with freshly generated
worktree names. The executor's conflict cleanup then fails with
"is not a working tree", failing the workflow graph at node 'execute' after
3 attempts.
- executor.ts: extend FN-4813 stale-conflict recovery to also treat
"is not a working tree" and ENOENT (not just "validation failed, cannot
remove working tree") as "no live worktree here" — prune the admin entry,
force-remove the leftover dir, and proceed with fresh creation.
- worktree-pool.ts: reapOrphanWorktrees skipped any dir on mere .git-file
presence, contradicting its own documented invariant. Resolve the .git
pointer and only skip when the gitdir target exists; reap dangling
pointers like any other orphan so they stop accumulating across runs.
- Tests for both the dangling (reaped) and valid (skipped) .git cases.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Substantive (A1 recoverPausedAbortFailures):
- Self-guard on globalPause/enginePaused at method entry (greptile P1) — the
public method must not requeue tasks an operator intentionally froze.
- Re-validate the FULL predicate with a FRESH executing set on the re-read
before the backward move (coderabbit Major + greptile): add fresh.userPaused
and column re-check so a task that became ineligible across awaits is skipped.
- Isolate audit emission in its own try/catch (coderabbit) so an audit throw
after a successful mutation can't log a false "recovery failed".
- Decouple the recovery predicate from the literal error text via shared
PAUSE_ABORT_PARK_ERROR_MARKER/OPERATOR_MARKER constants (greptile) — the
executor builds the parked message from the same constants.
- Use the wired clearPhantomExecutorBinding (live-session-guarded) instead of
the declared-but-never-wired releaseExecutorWorktreeOwnership, which no-op'd.
Nits:
- FNXC-prefix new comments in executor.ts, run-audit.ts, and the benign test
per repo comment policy.
- Fix a test-only type error on the clearPhantomExecutorBinding mock.
Added a test asserting the globalPause self-guard. Engine typecheck clean;
pause-abort/reaper/benign + regression suites pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
reapLeakedConcurrencySlots() reclaims in-memory worktree slots whose
holder is no longer in-progress (the FN-6756 "in todo yet still a
maxWorktrees holder" leak) without an engine restart — defense-in-depth
behind the source fix.
- executor: new listWorktreeHolders() read-only introspection over
activeWorktrees; wired through in-process-runtime to SelfHealingManager.
- reaper releases ONLY when every guard agrees: not executing, task
missing or in todo/triage, past a 60s grace, and clearPhantomExecutor
Binding itself refuses (returns false) if a live session surface is
registered — so it can never pull a worktree from a running agent.
- registered in maintenance batch 2 (respects globalPause/enginePaused
skip + FN-4962 ordering).
- widened the clearPhantomExecutorBinding option type to surface its
boolean refusal signal.
Engine typecheck clean; 19 tests pass (new reaper 7 cases + regression).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A global pause/resume cycle parked tasks that had re-queued to todo as
status:"failed" ("operator action required") and leaked their in-memory
worktree slot. The scheduler kept re-dispatching the todo task, the
genuine-pause-abort branch re-fired on the still-set pausedAborted marker,
and it re-parked instantly with no backoff — a retry storm (75x/hr) that
pinned maxWorktrees=3/3 and concurrency-starved the whole queue.
- R1+R2 (executor.ts handleGraphFailure): treat a pause-abort that left a
task in `todo` as benign (FN-6782) — don't park failed, clear the
pausedAborted marker so the next dispatch is clean, and release the
leaked activeWorktrees slot. Operator-action failure preserved for
genuinely stranded non-todo columns (FN-6478).
- A1 (self-healing.ts recoverPausedAbortFailures): new maintenance sweep
that auto-recovers any pause-abort park still on the board and requeues
it (status:null = schedulable) so the board self-heals.
- run-audit.ts: new mutation types for the recovery telemetry.
Corrected the spec's null-vs-queued assumption: the scheduler dispatch set
is column==="todo" && !paused (scheduler.ts:1288); status:"queued" is the
*blocked* marker, status:null is runnable — so recovered tasks are left null.
Deferred (documented): A2 leaked-slot reaper needs a new executor
listWorktreeHolders introspection API to reap in-memory worktree slots
safely; R1 closes the observed leak at its source.
Tests: self-healing-paused-abort-recovery.test.ts (3),
executor-paused-abort-todo-benign.test.ts (2). Engine typecheck clean;
106 existing pause/graph-failure/limbo tests still pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Recover wedged in-progress tasks by clearing stale executor bindings only after liveness proves the owner is gone.
- Add a guarded executor escape hatch that clears only stale in-memory task bookkeeping while refusing live session surfaces.
- Teach self-healing to identify phantom executor-active bindings using age, checkout, heartbeat, run-audit, and worktree liveness signals before requeueing preserved work.
- Record reclaim events in run audit and cover preserved-worktree recovery with reliability interaction tests.
- Document the recovery path and add a patch changeset for the published CLI package.
Files changed:
.changeset/fn-6736-phantom-executor-binding.md | 5 +
AGENTS.md | 1 +
docs/architecture.md | 1 +
.../reclaim-phantom-executor-binding.test.ts | 244 +++++++++++++++++++++
packages/engine/src/executor.ts | 35 +++
packages/engine/src/run-audit.ts | 2 +
packages/engine/src/runtimes/in-process-runtime.ts | 3 +-
packages/engine/src/self-healing.ts | 112 ++++++++++
8 files changed, 402 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-6736
Fusion-Task-Lineage: c76191ba-f4c3-4832-a790-67676e258ba2
The paused-after-completion graceful-exit path finalizes a fully completed task to in-review while leaving a non-user paused:true flag set (handoffToReview/applyInReviewEnterEffects clear status/blockedBy but not paused). handleGraphFailure's completion-finalized guards required paused!==true, so once the volatile completion markers were lost (execute() re-entry deletes completionFinalizedTaskIds; teardown overwrites provenance to hard-cancel) the trailing graph failure was misclassified as an operator-action pause abort and the completed task was parked status:failed (FN-6638 recurrence). Drop the paused!==true requirement from alreadyFinalizedToReview and suppressFinalizedCompletionAbort, and gate genuinePauseAbort's bare paused clause on the completion suppression. Genuine userPaused/global-pause/in-progress tasks are unaffected.
Fusion-Task-Id: FN-6648
Prevent completed no-commit executions that already advanced to review from being re-parked as pause-abort failures.
- Add completion-finalize pause-abort provenance and exclude it from genuine pause handling after review handoff.
- Mark paused-after-completion finalization paths with the new provenance before handing tasks to review.
- Cover the finalize-to-review abort recovery path with executor regression tests and document the lifecycle exception.
- Add a patch changeset for the published Fusion package.
Files changed:
.changeset/fn-6625-finalize-to-review-abort.md | 5 +
docs/architecture.md | 2 +-
.../engine/src/__tests__/executor-recovery.test.ts | 158 ++++++++++++++++++++-
packages/engine/src/executor.ts | 26 +++-
4 files changed, 185 insertions(+), 6 deletions(-)
Fusion-Task-Id: FN-6625
Fusion-Task-Lineage: 728f6fe5-4c27-4597-b17e-e16ff97b9277
Ensure task-detail comments are delivered to live executor threads and preserved for the next step prompt when no step session is active.
- Forward steering comments through legacy, step-session, and workflow-step executor targets with delivery status logging.
- Keep step-session task details updated and include pending steering comments in full and reduced step prompts.
- Track delivered steering comment IDs so comments are injected or queued exactly once across active and subsequent step sessions.
- Update step-session executor tests for live steering, queued prompt fallback, and reduced prompt behavior.
Files changed:
.../src/__tests__/executor-step-session.test.ts | 467 ++++++---------------
.../src/__tests__/step-session-executor.test.ts | 63 ++-
packages/engine/src/executor.ts | 34 +-
packages/engine/src/step-session-executor.ts | 69 ++-
4 files changed, 283 insertions(+), 350 deletions(-)
Fusion-Task-Id: FN-6590
Fusion-Task-Lineage: 18fffd41-7632-4f29-8721-daaf3c239a74
Resolves conflicts in the lazy-loaded heavy-views inventory. main independently
grew the curated list to 22 (adding AppModals lazy modals); this branch added the
Command Center view. Combined count is 23 — updated the AGENTS.md prose/inventory
and the lazy-loaded-views-docs test contract (count + length assertions) to 23,
keeping main's richer "App-level and AppModals" wording.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Schema migration 117→118 adds usage_events; events captured via a dedicated
emitUsageEvent seam wired through AgentLogger tool hooks + executor session
context (model/provider/nodeId), not by widening log signatures. meta is
size-capped and carries only non-sensitive descriptors.
Resolves the onLoad conflict in the compound-engineering plugin by keeping both
the bundled ce-* persona-def install (this branch) and main's
recoverStaleSessionsForContext call.
Also addresses PR #1672 review feedback:
- executor: sentinel resume now guards on !live.paused (mirrors
runAwaitInputNode) so a still-paused task can't consume a reply and re-enter
the skill early.
- TaskCard: make the Answer-questions button text/title/aria-label fallbacks
consistent ("Answer questions") for label-in-name a11y; update test.
- ce-work: replace the unshipped `skill: ce-worktree` reference with the real
git worktree commands so Option B can't dead-end.
- ce-resolve-pr-feedback: invoke bundled scripts by absolute path via the new
FUSION_CE_SKILLS_DIR env (sessions run with cwd=projectRoot); add the Fusion
await-input sentinel path instead of AskUserQuestion for workflow steps;
normalize whitespace-only review bodies like PR comments.
- plugin: expose FUSION_CE_SKILLS_DIR (installed skills root) to step sessions.
- plan doc: add language to fenced block (markdownlint MD040).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When a skill in a graph workflow step emits ===FUSION_AWAIT_INPUT===,
runGraphCustomNode now parks the task awaiting-user-input with the
question (reusing the runAwaitInputNode pause/watermark model so the
dashboard input banner + task-card button surface it), and halts the
walk. On resume the node re-runs; the resume check consumes the user's
steering reply and lets the skill continue with the answer.
Pure sentinel parser unit-tested (6 cases). End-to-end pause/resume
through the graph interpreter needs verification on a running board.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an optional systemPromptOverride to spawnAgentParams. When non-empty,
the spawned child runs under that persona system prompt instead of the
generic child base prompt (executor instructions still appended), so a
caller can spawn a specific persona — the primitive the compound-
engineering reviewer/research fan-out needs.
Two spikes confirmed the need: fn_spawn_agent had no persona param, and
Fusion has no plugin agent-contribution channel — so the lightweight path
is a generic override here + plugin-local persona defs the skill reads and
passes inline (revised KTD-4/U2/U3 in the plan). Behavioral coverage lands
with U10.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
U1: workflow-step sessions now carry FUSION_WORKFLOW_STEP=1 (scoped to
the step session, not the main executor) so skills detect autonomous
context and surface questions via await-input instead of a dead blocking
tool.
U8: bundle ce-commit, ce-commit-push-pr, and ce-resolve-pr-feedback
(vendored from compound-engineering 3.9.4) so the CE merge/PR flow has
its skills. Registered in COMPOUND_ENGINEERING_SKILLS; manifest test
updated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reconcile the FN-352 no-commit coordination guard skip with main's
independently-landed FN-424 work. Both branches added a no_commits
guard bypass at the same fn_task_done call site:
- FN-352 (this branch): getNoCommitEligibilityReason(task) covering
Review Level 1 coordination prompts with board-only scope and
explicit no-source intent (e.g. "Do not change product source").
- FN-424 (main): evaluatePromptDerivedNoCommitEligibility(...) plus
non-blocking audit logging of the skip.
Their prompt phrase-matching differs, so neither alone satisfies the
merged test file. Resolved by OR-ing both eligibility checks and
wrapping the skip in main's audit-logging block. Both helpers still
refuse the contradictory implementation+coordination prompt.
All 18 executor-task-done-invariant tests pass; engine typecheck clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resolve conflicts from main's FN-6232 single-source triage prompt refactor:
- Drop the inline FAST_TRIAGE_SYSTEM_PROMPT const removed by main; the fast
triage prompt now lives in agent-prompts.ts (planning-fast seam).
- Migrate the PR's no-commit/operational-routing heuristic edits into the
fast block of agent-prompts.ts (standard block already carried them).
- Point triage tests at FAST_PLANNING_PROMPT/TRIAGE_POLICY_PROMPT instead of
the removed const.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>