Classify write-capable graph nodes before handler dispatch and let executor adapters fulfill the declared worktree requirement, keeping custom-node execution out of lifecycle decision making.
Fusion-Task-Id: FN-7224
Move completed-step replay and proven-merge finalization into workflow-owned execution paths so graph restarts continue from live task projection instead of failing stale step nodes.
Fusion-Task-Id: FN-7224
recoverCompletedTask refused workflow-graph re-entry when the live task
has incomplete plan steps or a remediation bounce is already scheduled.
A pre-merge optional/advisory REVISE reopens steps and schedules a
sendTaskBackForFix bounce; a competing graph re-entry re-passed the
advisory step (budget exhausted), advanced to merge, and looped forever
on the 'task has incomplete steps' gate. Defer to the bounce /
stale-incomplete-review recovery so the executor finishes the steps.
Fusion-Task-Id: FN-7210
A pre-merge optional step REVISE (Code Review / Browser Verification)
reopens the last plan step to pending and schedules a rerun bounce via
sendTaskBackForFix. The hosting graph run reports disposition "completed",
so the outer completion flow can route the task to in-review before the
setTimeout(0) bounce fires. performWorkflowRerunBounce previously handled
only in-progress/todo and threw on in-review ("cannot bounce to
in-progress"), stranding the task in-review with a pending step: the merge
gate blocks forever while self-healing only re-runs the graph (re-passing
the advisory step) and never re-launches the executor — a permanent
deadlock (observed on FN-7122, stuck >1h).
The bounce's only caller (sendTaskBackForFix) unconditionally intends
remediation, so in-review now bounces back to in-progress exactly like
in-progress (todo hop preserving step progress + worktree). Adds a
regression test asserting the in-review bounce returns "bounced" and
re-dispatches.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Final cutover — nothing reads workflow_steps at runtime, so migration 131 drops it.
- Removed the merger post-merge execution path entirely (runPostMergeWorkflowSteps,
hasEnabledPostMergeWorkflowSteps, executePostMerge{Prompt,Script}Step, post-merge
worktree helpers + call site). Graph owns post-merge.
- Executor recovery no longer reads getWorkflowStep().gateMode; gate-ness comes from the
recorded WorkflowStepResult.status.
- Removed store CRUD (create/update/deleteWorkflowStep), materializeWorkflowSteps, and
migrateLegacyWorkflowSteps; selectTaskWorkflow now seeds default-on optional-group node
ids (consistent with create-time). KEPT the plugin step-template palette (getWorkflowStep
plugin-only resolver / listWorkflowSteps plugin-only) — never touches the table.
Removed the dashboard migrate-legacy-steps route + editor migration UI.
- SCHEMA_VERSION 130→131; migration 131 DROP TABLE IF EXISTS workflow_steps; SCHEMA_SQL
table def removed; historical migrations 77/105/109/130 guarded with tableExists().
Proof nothing stranded: the graph executes IR nodes resolved from workflowId (never
stepIds/compiled rows) — materializeWorkflowSteps writes were vestigial. Full @fusion/core
suite (6290), reliability backstop (154), boot smoke, and a seed-at-130 drop test all pass
with the table gone.
Plan U7c.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
From the U1-U6 code review (correctness/adversarial/reliability/maintainability):
- Delete dead code left by the runWorkflowSteps removal: parkTaskAfterWorkflowStepPause,
handleWorkflowRevisionRequest (+ createWorkflowRevisionFollowUpTask,
injectWorkflowRevisionInstructions), handleWorkflowStepFailure, the dead
partitionWorkflowRevisionFeedback export + its test, and 2 orphaned jsdocs;
reword 2 stale comments (executor.ts FN-6722, self-healing.ts jsdoc).
- Record step output/notes on graph workflow-step results: runGraphCustomNode now
emits contextPatch:{output,notes} so the Workflow tab shows real review feedback
and [pre-merge] revision logs carry detail (was always the fallback before).
- Sync the workflow-step seam contract: core (workflow-compiler SEAM_NAMES/order,
workflow-ir column map, builtin-workflow-prompts) now rejects the workflow-step
seam to match the engine's resolveSeamName, preventing a latent run-time crash on
a persisted/cloned def that core would otherwise parse.
Residual (tracked in the PR): re-introduce the FN-4343 per-step scope gate on the
graph path; the parked-failed recovery log wording; malformed-advisory->passed edge
case; recording for non-optional-group/split-branch step realizations.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Removes the legacy workflow-step EXECUTION path now that the graph records results
(U2): delete runWorkflowSteps(), the workflow-step seam + runWorkflowStep primitive
(runtime-primitives, workflow-node-handlers, authoritative-driver), and the legacy
execute() step blocks. Keeps task.workflowStepResults + its store write path (the
graph's sink) and executeWorkflowStep/executeScriptWorkflowStep (reused by the graph).
- Watchdog recoverCompletedTask now re-enters via maybeExecuteWorkflowGraph so the
graph re-runs pending gates, records results, and owns the in-review/back-for-fix
transition (KTD-2).
- maybeExecuteWorkflowGraph fails CLOSED (parks) when a store lacks
getTaskWorkflowSelection AND the task has enabled pre-merge steps — closing the
FN-7039 silent-skip class without changing minimal-store implementation runs (KTD-5).
KNOWN GAP (follow-up): the FN-4343 per-step workflowStepScopeEnforcement leak check
lived only in runWorkflowSteps and is NOT yet replicated on the graph path. Merge-time
File Scope enforcement (FileScopeViolationError, squash overlap) is unaffected.
Plan U4.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Enabled optional-group nodes now upsert their outcome into the existing
task.workflowStepResults field keyed by node id, and emit [pre-merge] logs at
parity with the legacy runWorkflowSteps path. Disabled groups stay byte-inert.
Reuses the existing WorkflowStepResult type + store.updateTask path (no new
table/type/store method). Unblocks the unified progress bar for graph-run steps.
Plan U2.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Multiworkspace tasks could not complete due to two independent bugs:
1. task.workspaceWorktrees had no SQLite column / rowToTask mapping, so
fn_acquire_repo_worktree's updateTask write was dropped on every persist
(applyTaskPatch writes the DB-round-tripped task back to task.json). Every
later getTask returned undefined, so fn_task_done's scope verifier read {}
and blocked with "acquired no sub-repo worktrees", and isWorkspaceTask()
consumers misfired. Persist it mirroring mergeDetails (schema column + v129
migration + db-migrate + defineTaskColumn + TaskRow + rowToTask).
2. In workspace mode every task ran rooted at the shared browse-only root, and
setActiveSession registered that path keyed only by path — so a second
concurrent workspace task was rejected by the foreign-task guard
("active-session path ... is held by ..."). Give each task a task-scoped
synthetic session key (sessionRegistryPath), applied at all register and
unregister sites; the in-memory worktree Set still holds the real root.
Regression tests assert the persistence invariant across getTask/listTasks/
store-reopen and concurrent session registration across all three session
surfaces; both verified to fail without the fix.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add X-Session-Id and X-Session-Affinity headers to all outbound LLM chat
completion requests so LLM gateways can sticky-route consecutive requests
from the same conversation and observability tools (Langfuse, Arize) can
group stateless API calls into a single multi-turn trace.
The headers carry a stable identifier: the task id when available (stable
across pause/resume), otherwise the pi session id. The implementation wraps
modelRegistry.getApiKeyAndHeaders -- the single chokepoint pi-coding-agent
uses for both the main stream and compaction -- merging routing headers into
the resolved output. This covers all HTTP-based providers (built-in, custom,
and HTTP-streaming extensions) without disturbing auth resolution.
Also propagates taskId to four secondary executor sessions (retry,
verification-fix, workflow-step, child-agent) that previously fell back to
a per-instance pi id, fragmenting per-task observability grouping.
Closes#1675
- Await async spawned child session disposal
- Use own-key iteration for structured tool result previews
- Add FNXC requirement comments for new regression assertions