Task Management

June 13, 2026 · View on GitHub

← Docs index

This guide covers task creation, lifecycle behavior, task metadata, and operational workflows.

Task Creation Options

1) Quick Entry (dashboard)

Use the inline input on board/list view:

  • Type description
  • Press Enter
  • Task is created in planning

Duplicate-task detection at creation time (Quick Entry)

Dashboard POST /tasks now performs a pre-create duplicate gate using token-overlap similarity against recent non-done tasks (default threshold 0.45, excluding done/archived).

  • POST /api/tasks/duplicate-check accepts { title?, description, limit?, threshold? } and returns { matches } for UI preflight warnings.
  • POST /api/tasks accepts optional acknowledgedDuplicates?: string[] and bypassDuplicateCheck?: boolean.
    • If candidates remain after acknowledgement filtering, the route returns 409 with { error: "duplicate_candidates", details: { matches } }.
    • If bypassDuplicateCheck: true, the gate is skipped.
  • When creation proceeds with acknowledged candidates, created task metadata stores:
    • task.source.sourceMetadata.duplicateWarningOverridden = true
    • task.source.sourceMetadata.acknowledgedDuplicateIds = [...]
  • Override creates emit activity type task:duplicate-warning-overridden with acknowledged IDs and scored candidate metadata.
  • Duplicate lineage is persisted on the task row via canonical source fields (sourceType: "task_duplicate", sourceParentTaskId) plus sourceMetadata.duplicateOfTaskIds when available, so fn task show <id> and Task Detail views can render duplicate-of linkage directly from task provenance.

Deterministic duplicate guard (FN-4918, extended by FN-5060)

Fusion applies a deterministic guard for exact normalized content matches (title + description fingerprint) within a 60s window. The shared implementation lives in packages/core/src/duplicate-guard.ts (runDeterministicDuplicateGuard + reconcileDeterministicDuplicate) and is wired into all primary task-creation surfaces:

  • Dashboard POST /tasks
  • CLI fn task create (runTaskCreate)
  • Engine createAgentTask (powers fn_task_create, including triage subtask splits)
  • Mission feature triage (MissionStore.triageFeature)

Behavior is consistent across these surfaces:

  • If an existing same-fingerprint task is found in-window, create returns/behaves as a link-to-existing result (duplicate_candidates for API, Linked existing ... for CLI/tool responses).
  • If two creates race across processes and both reach persistence, post-create reconciliation keeps the older canonical task and auto-archives the newer sibling.
  • New rows stamp task.source.sourceMetadata.contentFingerprint for deterministic matching.
  • Reconciled losers stamp task.source.sourceMetadata.deterministicDuplicateOf = <canonicalTaskId> and are archived (not deleted).
  • Reconciliation archives record activity event task:auto-archived-deterministic-duplicate.

Bypass controls:

  • Dashboard API: bypassDuplicateCheck: true skips deterministic + similarity duplicate gates.
  • CLI: fn task create --no-dedup skips deterministic duplicate guard.

This deterministic layer complements (does not replace) the FN-4829 similarity warning gate. FN-4892 remains a separate engine-side same-agent intake heuristic at triage finalize.

Fail-open boundary (FN-5084)

The deterministic pre-check now fails open: transient store query failures, in-process mutex bookkeeping failures, and leader-lock promise rejections do not block task creation. The route logs one runtimeLogger.warn line tagged FN-5084 (including projectId and contentFingerprint) and continues through the FN-4829 similarity gate to normal create handling.

Only legitimate deterministic duplicate detections continue to propagate as 409 via ApiError from conflict("duplicate_candidates", ...). The post-create FN-4918 reconciliation pass remains the second line of defense.

Near-duplicate intent guard (FN-5152)

Fusion adds a separate near-duplicate layer for tasks whose raw descriptions differ but whose implementation intent converges.

Order on dashboard POST /api/tasks is:

  1. Deterministic fingerprint guard (FN-4918 / FN-5060)
  2. Similarity warning gate (FN-4829)
  3. Near-duplicate intent guard (FN-5152)

Both FN-5152 layers fail open: extraction errors, store-query failures, parse failures, and timeout paths log a warning and continue through normal create/finalize flow.

Layer 1 runs synchronously on dashboard intake before task creation. It extracts an intent signature from the incoming title/description and compares it with active recent tasks. The signature stores four capped arrays:

  • routePaths — route-like paths such as /pr/options or /api/tasks/:id/pr/preflight
  • filePaths — concrete source/docs paths such as packages/dashboard/src/routes/register-task-workflow-routes.ts
  • identifiers — high-signal identifier-shaped tokens such as PrCreateModal
  • titleTokens — non-stopword title tokens used for the title Jaccard check

When at least two distinct high-signal tokens overlap and title-token Jaccard is at least 0.30, dashboard intake returns 409 duplicate_candidates with matches carrying reason: "near-duplicate-intent" plus sharedTokens so callers can explain the overlap. Clients may bypass that specific match with acknowledgedDuplicates: ["FN-..."]; bypassDuplicateCheck: true still skips the create-time duplicate gates entirely.

To avoid false positives from broad hot files, the matcher treats an overlap consisting only of generic large files (register-git-github.ts, register-task-workflow-routes.ts, store.ts, types.ts, styles.css) more strictly and requires title-token Jaccard of at least 0.50.

Layer 1 persists source.sourceMetadata.intentSignature on created tasks so later checks can reuse pre-extracted vectors.

CLI direct-store coverage (FN-5171)

CLI fn task create now runs the same near-duplicate intent guard after the FN-4918 deterministic fingerprint guard, using shared extractIntentSignature / findNearDuplicates helpers from @fusion/core. Thresholds and the 7-day comparison window match the dashboard layer exactly. --no-dedup remains the single bypass across both duplicate layers: it skips the comparison but still stamps source.sourceMetadata.intentSignature when high-signal tokens were extracted. When a near-duplicate is detected, interactive TTY runs prompt Create anyway? [y/N]; non-interactive runs refuse creation with exit code 1 and instruct the caller to re-run with --no-dedup. The guard is still fail-open: extraction/list/query errors log a warning and continue. fn task import (GitHub import) and fn task plan intentionally continue to skip both duplicate guards per the FN-5060 same-content-sibling contract.

Layer 2 runs in triage finalizeApprovedTask after PROMPT.md is written and parses ## File Scope as an additional backstop. If the new spec overlaps an older active task on concrete File Scope / intent tokens and still clears the title threshold, the newer task is flagged for user confirmation instead of being silently auto-archived.

Near-duplicate flagging now keeps the task in its normal flow column (todo / approval flow) and records metadata for UI warnings:

  • source.sourceMetadata.nearDuplicateOf = <canonicalTaskId>
  • source.sourceMetadata.nearDuplicateScore = <number>
  • source.sourceMetadata.nearDuplicateSharedTokens = <string[]>
  • optional source.sourceMetadata.nearDuplicateDismissed = true after user chooses Keep
  • activity event task:near-duplicate-flagged

Dashboard surfaces this as a yellow Duplicate chip plus modal actions:

  • Archive (user-initiated archive path)
  • Keep (dismisses the warning by setting nearDuplicateDismissed: true)

This layer complements, rather than replaces, FN-4829 similarity detection, FN-4918 deterministic deduplication, and FN-4892 same-agent intake heuristics.

Explicit duplicate-marker guard (FN-5220)

Fusion also recognizes the canonical one-line redirect marker:

  • DUPLICATE: FN-1234

  • `DUPLICATE: FN-1234`

  • **DUPLICATE: FN-1234**

  • fenced single-line wrappers such as:

    DUPLICATE: FN-1234
    

The shared parser lives in packages/core/src/explicit-duplicate-marker.ts (parseExplicitDuplicateMarker). It is intentionally strict: after trimming outer whitespace and one optional wrapper layer, the content must reduce to exactly one substantive line matching ^DUPLICATE:\s*FN-\d+$. Any extra prose, multiple markers, or full PROMPT bodies that merely mention duplicate text are ignored.

This guard adds three fail-open layers on top of the existing duplicate stack, in final order:

  1. Deterministic fingerprint guard (FN-4918 / FN-5060)
  2. Similarity warning gate (FN-4829)
  3. Near-duplicate intent guard (FN-5152)
  4. Explicit duplicate-marker guard (FN-5220)

Layer behavior:

  • Dashboard intake (POST /api/tasks) — after deterministic/similarity/near-duplicate checks and before createTask, intake returns 409 duplicate_candidates with reason: "explicit-marker" when the combined title/description is exactly a canonical redirect and the canonical target exists. acknowledgedDuplicates and bypassDuplicateCheck: true both suppress the conflict. Because this guard runs before task creation, the activity breadcrumb is attached to the canonical target.
  • Triage planning loop — after triage reads the generated PROMPT.md but before the fn_review_spec() APPROVE gate, an exact redirect marker short-circuits directly into finalizeApprovedTask(). This prevents one-line redirect specs from burning review reminders or fallback planning retries.
  • Self-healing sweep — maintenance Batch 2 runs resolveExplicitDuplicateMarkerTasks() across triage/todo tasks to clean up older stuck marker tasks. The sweep is best-effort, capped at 50 marker tasks per cycle, and can be disabled with the internal setting resolveExplicitDuplicateMarkerEnabled: false (default true).

All three layers fail open: parse errors, task lookup failures, file-read failures, activity-recording errors, or other unexpected exceptions log a warning and continue normal intake/triage/self-healing flow instead of blocking task creation or recovery.

Activity uses the existing task:auto-archived-duplicate event with metadata.source disambiguators:

  • explicit-marker — triage short-circuit / duplicate finalize path
  • explicit-marker-sweep — self-healing maintenance sweep
  • explicit-marker-intake — dashboard intake rejection breadcrumb

The duplicate-close task log line remains Duplicate of <canonicalTaskId> — closed for triage/sweep paths.

Intake auto-archive (ghost-bug preflight + same-agent duplicate)

Fusion applies two conservative intake heuristics that may auto-archive newly filed tasks before execution starts:

  • Ghost-bug preflight (triage finalize path): for bug-fix-shaped specs that cite concrete constructs/commands, Fusion probes current main. If all definitive probes show the cited bug does not reproduce, the task is archived as auto-resolved-ghost-bug.
  • Same-agent duplicate intake (create path): if the same source.sourceAgentId filed a highly similar task within 24h (threshold 0.75), the later task is archived as auto-resolved-duplicate and the earliest sibling is kept.

Both heuristics are fail-open: probe/detection errors, timeouts, or inconclusive signals do not block normal intake — the task continues in the regular flow.

Activity + run-audit event types:

  • task:auto-archived-ghost-bug
  • task:auto-archived-duplicate

These appear in task activity history; run-audit entries are emitted where run context exists (triage/engine paths). Store-only intake paths record activity without synthetic run context.

Recovery is reversible: restore archived tasks via dashboard Unarchive or fn_task_unarchive.

2) Plan Mode (AI interview)

Use the 💡 button to open planning mode:

  • AI asks clarifying questions
  • AI reasoning (thinking output) is preserved and visible throughout the session — expand the reasoning toggle to review the model's analysis before answering each question or accepting the summary
  • Produces summary + key deliverables
  • Create one task or Break into Tasks (multi-task generation with dependencies)
  • Summary view includes a Priority selector (low, normal, high, urgent) so single-task creation can set priority before task creation
  • Break-into-tasks mode includes per-subtask Priority selectors (low, normal, high, urgent) so each generated task can be prioritized before creation
  • Break-into-tasks descriptions are structured with subtask-specific guidance first, then a separate larger-plan context section (plus ## Planning Interview Context when interview history exists)
  • Final multi-task creation now uses a compact request payload: unchanged generated subtask descriptions stay server-side, while any edits to title, description, size, priority, and dependencies are preserved when tasks are created
  • Sessions persist when the modal is closed — resume from the sidebar list at any time; reasoning context is restored automatically
  • Back navigation rewinds the server-side planning session to the previous answered question so you can revise earlier answers and continue from the corrected turn
  • On the summary screen, Refine Further continues through the backend planning session (including resumed completed sessions) and waits for a real follow-up question or updated summary; it does not switch to an empty question view

3) Todo item → Plan Mode

In Todos view, each todo item includes a planning action:

  • Click the planning (💡) action on a todo item
  • Opens Planning Mode with that todo text pre-filled as the initial plan
  • Starts an AI planning interview (clarification questions + summary)
  • You can then create one task or break the plan into multiple tasks

This action starts a planning session; it does not immediately create a task.

For full Todo View behavior (enablement, list/item actions, API routes, and storage), see Todo View.

4) Subtask Breakdown Dialog

Use the 🌳 button:

  • Generate 2–5 candidate subtasks
  • Drag to reorder
  • Add dependencies only on earlier items
  • Set each subtask's Priority (low, normal, high, urgent) before create
  • Create tasks in one action

5) Expanded Controls

Expand the creation panel (▼) to access additional controls:

  • Refine (✨) — Improve the description with AI
  • Deps (🔗) — Link existing tasks as dependencies
  • Attach — Add image attachments
  • Models (🧠) — Set per-task model overrides (executor, validator, planning)
  • Priority (🚩) — Set task priority (low, normal, high, urgent) before creation; the selected value is applied to the created task (it does not reset to default unless omitted)
  • Agent — Assign an agent to the task
  • Branch settings (branch / baseBranch) remain available in full task forms and task detail editing (not in Quick Entry)
  • Review — Set review rigor level (None, Plan Only, Plan and Code, Full)
  • Optional workflow steps — Enable workflow-declared optional steps for the selected workflow. The built-in coding workflow exposes Browser Verification here and keeps it opt-in by default.

6) CLI creation

fn task create "Fix API timeout handling"
fn task plan "Implement role-based access control"
fn task create "Bug" --attach screenshot.png --depends FN-002

7) Create/Enrich from Research findings

From the standalone Research view, each finding supports two task actions:

  • Create Task — creates a new task with sourceType: "research"
  • Enrich Task — appends/updates research content on an existing task

Research actions persist detailed output in task documents (and optional attachments), not in long task descriptions.

Task Lifecycle

Fusion task columns:

Fusion task columns use persisted enum values as the API/filter contract. Callers use enum values such as triage, todo, in-progress, in-review, done, and archived; UI labels are presentation only. In particular, triage is displayed as Planning, but Planning is not a valid persisted column value. Dashboard task-list API requests such as GET /api/tasks?column=triage return only rows whose persisted task.column is exactly triage, and invalid column values are rejected.

  1. triage (displayed as Planning) — idea intake; AI writes a full plan
  2. todo — ready for scheduling
  3. in-progress — executor active in isolated worktree
  4. in-review — implementation complete; awaiting finalization
    • If merge/finalization hits a terminal error, tasks can remain in in-review with status: "failed" for explicit follow-up. This state is intentionally preserved by recovery (not auto-bounced to todo).
    • Retry behavior splits by execution-vs-merge signals: in-review tasks with incomplete steps (pending/in-progress) are treated as execution failures and retried back to todo with preserveProgress: true; zero-step in-review tasks use mergeRetries as the tie-breaker (mergeRetries === 0 or undefined → execution failure path back to todo, mergeRetries > 0 → merge/finalization retry in in-review with merge retry state reset); tasks whose steps are all terminal (done/skipped/failed) also stay on the merge/finalization retry path.
    • Manual retry now clears the full persisted retry-budget counter set (stuckKillCount, recoveryRetryCount, taskDoneRetryCount, worktreeSessionRetryCount, workflowStepRetries, verificationFailureCount, postReviewFixCount, mergeConflictBounceCount, branchConflictRecoveryCount, reviewerContextRetryCount, reviewerFallbackRetryCount, completionHandoffLimboRecoveryCount, mergeAuditBounceCount) plus nextRecoveryAt; merge retry counters clear only on merge-failure/generic retry paths. retrySummary is recomputed from persisted counters at read time, so manual retry resets the dashboard retry badge/details back to zero immediately.
    • Persisted executor session state is resumed only when it still matches the task's current worktree context. If a retry fails with Refusing to start coding agent in missing worktree: ... and the persisted session points at stale worktree metadata, recovery clears stale session pointers and retries fresh so review retries do not reopen deleted worktree paths.
    • Merge-confirmed tasks still respect getTaskMergeBlocker() before the final in-reviewdone move. If merge is confirmed but a blocker remains (for example, incomplete steps), Fusion parks the task in in-review with status: "failed" and an explicit blocker error instead of retry-looping auto-finalization.
    • Self-healing can still auto-finalize retry-exhausted failed review tasks when it can prove their branch content already landed on the merge target, so already-merged work does not deadlock in in-review.
    • Repeated engine merge-queue drops now escalate to an explicit recoverable review failure: if auto-recovery hits Auto-merge starvation: in the task error, Fusion has already seen three consecutive enqueue attempts rejected by the engine merge queue. Operators can recover by clearing the failed state from the dashboard, which lets the usual unpause/clear flow re-attempt merge once the underlying queue wedge is resolved.
    • Non-recoverable state-machine errors during finalization (for example Invalid transition: 'todo' → 'done') are treated as terminal review failures: recovery must not re-enqueue these tasks for merge unless task state changes prove they are recoverable.

Self-healing: stranded-completed-todo recovery

Fusion now enforces a recovery invariant for completed work that is accidentally returned to todo:

  • todo tasks with all steps terminal (done/skipped), no active execution, and no unrecoverable error are auto-promoted by self-healing.
  • Promotion always respects the transition table (todo → in-progress → in-review) via TaskStore.moveTask(); no direct todo → in-review mutation is used.
  • Review Level 0 tasks are finalized to done by the existing no-op review finalization flow after promotion to in-review; Review Level >0 tasks remain in in-review for normal review/merge handling.

This is a forward-safety guard for stranded completed tasks. See FN-4055/FN-4079 for deeper lifecycle root-cause and operational repair work.

In-review stall signal

Fusion now derives task.inReviewStall for non-paused in-review tasks when a known stuck-state shape is detected. This signal is state-based (not log-heuristic) and is computed server-side on task hydration.

InReviewStallCode values:

  • transient-merge-status-no-owner — task is still in merging/merging-pr/merging-fix after the stale-merging age threshold, but no active merger owns it.
  • merge-retries-exhaustedmergeRetries reached the auto-merge retry cap without mergeDetails.mergeConfirmed === true.
  • no-worktree-no-merge-confirmed — task has no worktree path and merge is not confirmed (excluding explicit no-op merges).
  • merge-blockergetTaskMergeBlocker() reports a merge/finalization blocker.

Invariant: inReviewStall is diagnostic-only. It must never be used as an auto-completion trigger.

Self-healing surfaces this diagnosis via task log entries in the form:

  • In-review stall surfaced [<code>]: <reason>

These entries are rate-limited per (task, code) over taskStuckTimeoutMs, so unchanged stalls are not spammed every cycle while state transitions can still surface a new code immediately.

Dashboard surface: In-review, non-paused tasks with inReviewStall set show a Stall badge on TaskCard and a code-specific diagnostic row in TaskDetailModal above the PR section. The diagnostic row includes headline/description/action copy, raw reason text, observed timestamp, and a View activity log deep-link that switches to Logs → Activity and highlights the most recent matching In-review stall surfaced [<code>]: <reason> entry. This UI is diagnostic only: neither the badge nor the jump button mutates task state.

Stale paused review signal

Fusion now derives task.stalePausedReview for paused in-review tasks whose review-column age is at/over stalePausedReviewThresholdMs (age source: columnMovedAt ?? updatedAt) while merge is still unconfirmed (mergeDetails.mergeConfirmed !== true).

StalePausedReviewCode values:

  • stale-paused-review

Invariant: stalePausedReview is diagnostic-only. It never auto-unpauses, retries, archives, or moves tasks.

Self-healing surfaces this diagnosis via task log entries in the form:

  • Stale paused review surfaced [<code>]: paused <duration>; disposition options — unpause, retry, archive, or create follow-up task. pausedReason=<reason|none>

These entries are rate-limited per (task, code) over stalePausedReviewThresholdMs so unchanged paused-review debt is visible without log spam.

Dashboard surface: paused in-review tasks with stalePausedReview set show a Paused stall badge on TaskCard, a diagnostic row in TaskDetailModal (age/threshold/observed timestamp + disposition copy + View activity log jump), and a Stale paused review list filter that narrows to tasks where task.stalePausedReview != null.

Disposition options:

  • Unpause
  • Retry
  • Archive
  • Create follow-up task

Task age staleness signal

Fusion now derives task.ageStaleness for tasks in in-progress and in-review (including paused tasks). Age is computed from columnMovedAt and falls back to updatedAt when needed.

Signal levels:

  • warning
  • critical

Threshold settings:

  • staleInProgressWarningMs (default 4h)
  • staleInProgressCriticalMs (default 24h)
  • staleInReviewWarningMs (default 24h)
  • staleInReviewCriticalMs (default 72h)

0 or undefined disables that level.

Invariant: ageStaleness is diagnostic-only. It never moves, retries, pauses/unpauses, or auto-finalizes tasks.

Scheduler-side reporting emits structured, rate-limited task-log and engine-log entries in this exact form:

  • Stale task age threshold crossed [<level>]: column=<column> paused=<bool> ageMs=<n> warningThresholdMs=<n> criticalThresholdMs=<n>

Dashboard surface: tasks with ageStaleness show a stale badge on TaskCard, a diagnostic row in TaskDetailModal, and the list view provides a Stale only filter that narrows to tasks with a live signal.

Success metric: maintainers can find all stale cards from the board UI in under 30 seconds.

Auto-completion/finalization remains owned by existing recovery passes:

  • recoverStaleMergingStatus
  • finalizeNoOpReviewTasks
  • recoverMergeableReviewTasks
  • recoverAlreadyMergedReviewTasks
  1. done — merged/finalized
  2. archived — preserved history, optionally cleaned from filesystem

Board ordering behavior:

  • todo mirrors scheduler dispatch order: priority first (urgentlow), then oldest createdAt within a priority tier, then task ID as deterministic tie-break.
  • triage, in-progress, and in-review remain priority-first with task-ID tie-breaks (in-review still pins merge-active statuses above non-merging tasks).
  • The done column is recency-ordered by completion time (newest first), using columnMovedAt as primary and falling back to updatedAt then createdAt for legacy tasks.
  • The dashboard list view default ordering matches these same per-column semantics until a user clicks a sortable header (manual list sorting still overrides defaults).

Lifecycle commands

fn task move FN-001 todo
fn task merge FN-001
fn task archive FN-001
fn task unarchive FN-001

Lifecycle invariants

Paused-state normalization on reopen: When a task is moved from in-progress, in-review, or done back to todo or triage (retry/requeue), Fusion clears task.paused and task.pausedByAgentId to prevent contradictory todo + paused or in-progress + paused states. A paused task in todo is excluded from scheduler dispatch.

Manual cancel park (userPaused): A user-initiated move from in-progress to todo (moveSource: "user") sets task.userPaused = true and hard-cancels active executor work. Scheduler treats userPaused tasks as intentionally parked and will not auto-dispatch them until userPaused is cleared — either by explicitly unpausing the task from the dashboard or by moving it back to in-progress. Engine/internal requeues (moveSource: "engine") do not set this flag.

Paused-state normalization on explicit completion: When an agent calls fn_task_done on a paused task, Fusion clears task.paused and task.pausedByAgentId regardless of the task's column (in-progress or todo). task.paused prevents new work from starting, but does not block an agent from completing in-flight work and transitioning the task to done. The scheduler respects globalPause independently.

Global pause vs task pause: settings.globalPause gates new scheduler dispatches and is checked by the fn_task_done handoff logic. Task-level task.paused is a per-task gate that blocks execution start. They are independent — a task can be paused individually even when globalPause is false, and clearing task.paused does not affect globalPause.

Branch-conflict tripwire: Executor tracks branch-conflict failures per task in-memory. After more than 5 BranchConflictError events for the same task in one executor lifetime, Fusion short-circuits recovery, marks the task status: "failed", sets paused: true, and sets pausedReason: "branch-conflict-tripwire" to stop further automatic retries and suppress additional "Branch conflict recovery required" emissions for that task.

Stranded-worktree recovery

When Fusion detects uncommitted task-attributable changes in a task worktree during a requeue/release path, it will not silently move the task back to todo/triage. Instead, it parks the task in status: "failed" so operators can recover the stranded workspace state.

Diagnostics are written to both places:

  • task.error (task detail summary)
  • task activity log entry (timeline)

Each diagnostic includes task ID, absolute worktree path, dirty-file count, and sample dirty paths.

Recovery flow:

  1. Inspect the worktree: cd <worktreePath> && git status
  2. Rescue changes as needed: git diff and/or git stash push -u
  3. Clear/retry the failed task from the dashboard/CLI so it can re-enter scheduling after the workspace is safe.

Branch metadata semantics

Task cards on the board only surface branch metadata when it is non-default/user-meaningful: they hide the conventional auto-generated working branch (fusion/<task-id> and suffixed variants) and hide the default merge target (main), while still showing custom working branches and non-default merge targets.

The board header search panel now includes two board-only branch filters:

  • Working branch filters by task.branch
  • Target branch filters by task.baseBranch

These filters apply only to board rendering (not list view). Each filter supports concrete branch values plus a No branch option that matches tasks where branch or baseBranch is unset. Persisted filter state remains intentionally deferred to follow-up task FN-3426.

Task branch fields are intentionally distinct:

  • task.branch — the actual working branch used for the task worktree (for example fusion/fn-1234 or a conflict-suffixed variant).
  • task.baseBranch — the task's configured merge target/base branch intent.
  • task.executionStartBranch — internal execution provenance used when scheduler/executor temporarily start from a dependency branch; this is transient and cleared during execution resets/recovery.

PrInfo.baseBranch is unchanged and continues to represent pull-request target branch metadata.

Dependency reconciliation guidance

When a task was created to resolve a temporary failure state in another task (for example, a preserved in-review/failed merge condition), its dependency contract may become stale after recovery.

Use supported TaskStore/API paths to reconcile safely:

  • Remove/replace stale dependencies through task update APIs (do not hand-edit task.json/SQLite)
  • Add a single comment/log entry explaining why the dependency changed
  • Keep downstream blockers coherent (only tasks that still truly depend on unfinished work should remain blocked)

Completion gating treats dependencies as resolved only when the dependency task is in done, in-review, or archived.

Auto-merge recovery follow-up creation is deduplicated: Fusion creates at most one active (not done/archived) recovery task per unresolved parent failure, and merge-conflict recovery also deduplicates by active branch ownership to prevent parallel duplicate follow-ups on the same conflict branch.

Landed-task state reconciliation (maintenance)

If a task already shipped (column: done) but still carries transient failure metadata (status: failed, error, worktree, blockedBy, recovery retry fields), reconcile through supported TaskStore/API paths so SQLite and task JSON stay in sync.

Recommended pattern:

  • Audit first (dry-run) for contradictory done + transient-failure state.
  • Apply reconciliation with TaskStore-backed mutations (for example moveTask(id, "done") for done-normalization cleanup).
  • Add one durable reconciliation log entry explaining why stale transient fields were cleared (avoid duplicating historical failure logs).
  • Re-audit after apply and resolve or explicitly disposition any related stale follow-up tasks.

Do not patch .fusion/fusion.db directly without synchronizing .fusion/tasks/*/task.json through a supported store-backed path.

Branch conflict handling

When executor branch allocation finds fusion/<task-id> already checked out elsewhere, Fusion fails loudly by default instead of silently renaming to sibling branches. The task is moved back to todo with status: "failed", and logs include the conflicting worktree path, tip SHA, and stranded commits.

Fusion no longer provides a dedicated task-branch conflict CLI command. Resolve conflicting local branches/worktrees with standard git tooling, then retry the task. The legacy executorAllowSiblingBranchRename setting still exists as an opt-in escape hatch for older workflows.

Task Execution Modes

Each task has an execution mode that controls how the executor agent approaches the task:

ModeDescription
standardFull execution with complete review workflow (default)
fastExpedited execution with minimal overhead for simple tasks

Fast Mode Bypassed Gates

When executionMode: "fast", the following automated review/validation gates are bypassed:

GateStandard ModeFast Mode
review_step tool enforcementAvailable to executor agentNot injected
Pre-merge workflow-step executionRuns configured stepsSkipped
Custom graph pre-merge prompt/script/gate nodesRun in selected custom workflowsSkipped
Workflow revision loopEnabled (feedback → fix → re-review)Disabled

Fast Mode Mandatory Gates

The bypass applies to both the legacy workflow-step path and the workflow graph executor path (including custom non-builtin:coding workflows). undefined or null execution mode is treated as standard mode.

The following quality gates remain enforced in fast mode:

GateBehavior
task_done requirementAgent must call task_done() to complete
Completion blocker checksTests, build, and typecheck from PROMPT.md still enforced
Post-merge workflow stepsRun as normal (merger-owned)

Execution Mode Matrix

FeatureStandardFast
Executor agent sessionFull prompt + toolsFull prompt (minus review_step)
Pre-merge workflow steps (legacy, builtin, and custom graph workflows)✅ Run❌ Bypassed
Custom graph prompt/script/gate validation nodes✅ Run❌ Bypassed
review_step tool✅ Available❌ Not available
Post-merge workflow steps✅ Run✅ Run
Completion blockers (test/build/typecheck)✅ Enforced✅ Enforced
task_done() requirement✅ Required✅ Required

Setting Execution Mode

Execution mode can be set during task creation or editing:

  • Via API: Include executionMode field in task create/update payload
  • Via dashboard: Select execution mode in the task creation dialog or task detail modal
  • Task detail quick toggle: In read mode, use the inline lightning-bolt control in task metadata to switch between Standard and Fast without entering full edit mode
  • Values: "standard" (default) or "fast"

Example API payload:

{
  "description": "Simple fix",
  "executionMode": "fast"
}

Task provenance and research enrichment

Agent-created tasks now show a compact Created by agent marker directly on dashboard task cards when creation provenance indicates agent/automation origin (sourceType: agent_heartbeat or sourceType: automation, with legacy fallback to populated sourceAgentId). Where available, displays should prefer sourceMetadata.agentName over raw sourceAgentId.

Research-created tasks show provenance as Created via Research in the task detail header and Source: Research in fn task show output.

When sourceMetadata.findingLabel is present, the UI/CLI include it as context; otherwise they fall back to runId when available.

Research enrichment uses a canonical per-run document key:

  • research-{sanitizedRunId}

Repeated enrichment from the same run writes new revisions to that same key (no sibling keys for the same run). Optional exported artifacts can also be attached to the task; duplicate attachment records are skipped unless an explicit replacement path is used.

Research document content appears in the existing Documents tab in Task Detail. Optional artifacts appear in existing task attachments.

Task Detail Modal (Dashboard)

The task detail modal exposes multiple tabs.

In read mode, task metadata includes lightweight inline controls: priority can be changed from the priority chip, and execution mode can be toggled with a one-click lightning-bolt fast-mode button (Fast ↔ Standard) without opening full edit mode. These controls are intentionally aligned as a paired row (matched control height, baseline alignment, and mobile-safe wrapping) so frequent priority/mode changes stay quick and visually consistent.

Task metadata also shows compact Created / Updated timestamps: recent values render as relative time (just now, Xm, Xh, Xd) and older values switch to short month/day dates; both timestamps stay on one row across desktop and mobile widths for a compact metadata presentation.

Task settings edited from the modal now auto-save as you edit (change/blur with debounce for text-like fields). This includes title, description, dependencies, working/base branch (branch/baseBranch), workflow-step selection, model overrides in the edit form, and source issue metadata. In shared create/edit task forms, the GitHub Tracking controls are placed at the bottom of More options after Workflow Steps. The footer Save button remains available, but normal field edits no longer depend on a manual save click.

The edit footer shows inline autosave state (saving/saved/error), and successful saves propagate the returned task through onTaskUpdated so open detail/list state stays fresh.

The task detail modal exposes multiple tabs:

  • Details — primary metadata and description
  • Steps — progress across plan/implementation steps
  • Log — task event history
  • Changes — merge diff/change summary
  • Workflow — workflow step results (pass/fail/skip)
  • Review — actionable feedback surface (PR reviews/threads or reviewer-agent findings), manual refresh controls, and same-task revision actions for selected items
  • Stats — execution timing + token usage breakdown
    • Total execution time prefers cumulative active runtime (cumulativeActiveMs + live in-progress segment)
    • executionStartedAt is a current-attempt anchor and may reset on reopen bounces; cards no longer rely on it alone for lifetime runtime
    • firstExecutionAt is the immutable first-dispatch wall-clock anchor; executionCompletedAt remains first-time-reaching-done
    • Task Detail also shows Wall-clock since first execution when it differs from active runtime (for bounced/reopened tasks)
    • Fallback order for legacy tasks: timedExecutionMs when present, otherwise [timing] log sum + workflow runtime
    • Workflow runtime is shown as a separate metric and is not double-counted into totals when timedExecutionMs is already available
  • Comments — collaboration thread + steering controls
  • Model — per-task model overrides and thinking level

PROMPT.md Plan Structure

After planning, each task gets a structured PROMPT.md with sections like:

  • Mission
  • Dependencies
  • Context to read first
  • File scope
  • Steps
  • Acceptance criteria
  • Guardrails / Do NOT list
  • Build/test/typecheck requirements

This file is the contract for execution and review.

Task Comments vs Steering Comments

  • Task comments (fn task comment) are general collaboration notes.
  • Review tab feedback is dedicated actionable review input (PR review data in pull-request mode, reviewer-agent findings in direct/non-PR mode) used to request same-task revisions.
  • Review data is served from task-scoped API endpoints: GET /api/tasks/:id/review and POST /api/tasks/:id/review/refresh.
  • Both endpoints return one normalized contract (TaskReviewData) with stable per-item itemId values and sourceMode (pull-request or reviewer-agent) so Review UI and per-item progress can share one read model.
  • The Review tab supports manual refresh without closing/reopening Task Detail:
    • Pull-request mode refreshes live GitHub-backed review decision/thread/comment state and updates PR metadata freshness.
    • Direct/non-PR mode refreshes normalized reviewer-agent feedback from persisted task review artifacts and does not call GitHub.
  • In direct/non-PR auto-merge mode, the Review tab shows parsed reviewer-agent feedback with explicit loading/error/empty states instead of sending users to raw comments or agent logs.
  • Review item bodies render markdown by default, and users can switch between Markdown and Plain modes from the Review tab action bar; the preference persists locally per user.
  • Comments remains the general discussion surface; Review remains the actionable review surface.
  • Steering comments (fn task steer) are execution guidance for the running agent.

Steering comments can be injected mid-run into active executor sessions.

When users select review items and trigger Request revision from the Review tab, Fusion starts an in-place same-task AI revision pass (no refinement child task):

  • Review addressing progress is persisted per selected item in task state, including a durable snapshot and lifecycle timestamps.

  • Each selected item transitions through queuedin-progressaddressed/failed, so Review progress survives refreshes and reloads even if upstream review data changes.

  • Persisted snapshots are rendered in the Review tab when the original source item is no longer present, while Comments remains dedicated to general discussion.

  • in-progress tasks receive compact steering guidance from the selected review items and continue on the same task/branch/worktree context.

  • in-review tasks are resumed back to in-progress, reopen the last completed step, and keep same-task branch/worktree context for the revision pass.

  • Assigned immediate-response agents are woken on-demand only when there is no active session; otherwise guidance is injected without forcing a new wake.

User comments and triage re-consideration

User comments can trigger re-triage for already-planned but non-executing work:

  • triage + awaiting-approval → user comment sets status: "needs-replan"
  • triage or todo with a real (non-bootstrap-stub) PROMPT.md → user comment sets status: "needs-replan"
  • triage or todo with only bootstrap-stub/unplanned prompt content → no re-triage transition

Execution ownership is preserved for active work:

  • User comments on in-progress and in-review tasks do not re-route those tasks back through triage.
  • Agent/system comments do not trigger comment-driven re-triage.

This is distinct from steering comments: steering feedback targets the currently running executor session, while comment-driven re-triage requests a fresh specification pass for planned work.

Refinement Tasks

fn task refine <id> creates a new planning task that depends on the original done/in-review task.

Example:

fn task refine FN-042 --feedback "Add explicit rollback tests for partial failure"

Behavior:

  • New title format: Refinement: <source label>
  • New task depends on source task
  • Created in planning
  • Refinement tasks inherit the source task's GitHub tracking state (unlinked sources opt out; linked sources inherit enabled and optional repoOverride, but never copy the source issue link)

Archive and Restore

Archive behavior

  • fn task archive <id> moves any live-board task (triage, todo, in-progress, in-review, or done) to archived; tasks already in archived are rejected.
  • Archive records the task's preArchiveColumn so restore can return to the original live column instead of always assuming done.
  • Dashboard delete confirmations for live tasks include an Archive Instead action so users can preserve history without soft-deleting the task.
  • Cleanup mode can persist compact metadata and remove the task directory
  • Archived tasks are read-only for task log/document writes:
    • logEntry() throws Task <id> is archived — logging is read-only
    • upsertTaskDocument() throws Task <id> is archived — documents are read-only
    • fn_task_log returns ERROR: Cannot log to archived task — this task is read-only

Cleanup behavior

  • Archived entries are persisted as compact archive snapshots (current runtime stores these in SQLite archivedTasks; legacy docs may refer to .fusion/archive.jsonl)
  • Task directory (task.json, PROMPT.md, agent.log, attachments) can be removed

Compact archive entry format

Archive entries preserve key metadata needed for restoration, including:

  • id, title, description, priority, column, preArchiveColumn
  • dependencies, steps, currentStep
  • size, reviewLevel, prInfo (primary mirror), prInfos (canonical linked PR list), issueInfo
  • attachments metadata
  • task log
  • timestamps (createdAt, updatedAt, columnMovedAt, archivedAt)
  • model override fields (modelProvider, modelId, validatorModel*, planningModel*)

agent.log content is intentionally not preserved in compact archive entries.

Restore behavior

fn task unarchive <id>:

  • Restores archive entry if directory is missing
  • Rebuilds PROMPT.md
  • Moves task back to its recorded preArchiveColumn when available, falling back to the archived snapshot's prior column, then to done for legacy archive entries.
  • Logs “Task restored from archive” when recovering from compact archive entry

Task-ID collision safety and operator recovery

  • Ordinary task creation, duplicate, and refine flows now fail safely if the chosen task ID already exists in active storage or archive storage. Existing task rows/files always win; the new create attempt must retry with a fresh reservation instead of overwriting data.
  • A failed create may burn a distributed reservation. Gaps in FN-* numbering are expected and are safer than reissuing a possibly-colliding ID.
  • config.nextId is legacy/read-only. The live allocator state is distributed_task_id_state.nextSequence, reconciled on store open against live tasks, archived task snapshots, and reservation history.

If you suspect historical overwrites from pre-FN-4044 builds, inspect surviving evidence in this order:

  1. archive.db / archived task snapshots for the missing ID
  2. .fusion/tasks/<id>/task.json.bak, PROMPT.md, attachments, and any surviving worktree branch named for the task
  3. agent run logs / task documents / activity log entries that still mention the original ID
  4. git commits whose subject/body references the original task ID but no longer matches the current task metadata

Recovery/backfill guidance:

  • If the original task row still exists in archive storage, unarchive or manually recreate the task from that snapshot.
  • If only prompt/worktree/git evidence survives, create a replacement task with a new ID and copy over the recovered description, prompt, documents, and attachments manually.
  • If both the active row and archive snapshot were overwritten, Fusion cannot reconstruct lost attachments/comments automatically; recreate them from git history, branch/worktree contents, screenshots, or external issue trackers.
  • Record the incident in the replacement task so future audits understand why the task ID and commit history diverge.

GitHub Issue Import and PR Creation

Import issues:

  • GitHub-imported tasks retain typed source issue metadata (sourceIssue.provider/repository/externalIssueId/issueNumber/url), which executor and merger flows use to include Ref: owner/repo#N in commit bodies.
  • When githubCloseSourceIssueOnDone is enabled (default: false), Fusion also closes linked source-imported GitHub issues with state_reason: completed when the task moves into done. On startup, a bounded reconciliation sweep checks done tasks and closes any still-open source issue links that were missed due to transient failures.
fn task import owner/repo --labels bug --limit 20
fn task import owner/repo --interactive

Create PR for an in-review task:

fn task pr-create FN-120 --title "Fix flaky auth flow" --base main

Manual/non-auto-merge behavior:

  • Task PR branches use fusion/<task-id-lower>.
  • In the dashboard task detail modal (in-review), the existing primary footer action can manually drive PR-first completion when mergeStrategy: "pull-request" and autoMerge: false:
    • Start PR Review (no PR linked yet)
    • Check PR Status (open PR linked; refreshes PR status from GitHub, does not prompt merge)
    • Finish & Close (PR already merged)
  • Manual PR creation first checks for an existing PR on that branch and links it when found.
  • If no PR exists, Fusion pushes the task branch to origin before creating the PR.
  • In the dashboard Create-PR modal, if preflight detects merge conflicts with the selected base branch, you can choose Resolve conflicts with AI. Fusion resolves the task branch in-place, commits the result, pushes the updated branch to origin, and then lets you retry PR creation.
  • When buffered actionable PR feedback exists on a PR that is already merged/closed and the task leaves in-review, Fusion creates a dependency-linked follow-up task in triage so feedback is not stranded.

GitHub Tracking Issues

GitHub tracking issues are optional issues Fusion can create from Fusion tasks. They are not the same as imported source issues (issueInfo / sourceIssue): imported issues represent an existing GitHub issue that created the task, while tracking issues are new GitHub issues opened to track a Fusion task.

Agent- and tool-created task coverage

When task creation runs with tracking enabled, Fusion attempts issue creation via a universal post-create hook (setTaskCreatedHook) that fires for every task-creation path: dashboard HTTP routes, pi extension tools (fn_task_create, fn_task_import_github*, fn_delegate_task), CLI commands (fn task add, fn task duplicate, fn task refine), mission/feature triage, automation create-task workflow steps, agent-driven delegation, routine/cron-created tasks, and subtask creation paths. Dashboard REST API coverage explicitly includes POST /api/tasks, POST /api/tasks/:id/duplicate, POST /api/tasks/:id/refine, and POST /api/subtasks/create-tasks. The hook is registered once per process entrypoint by calling registerGithubTrackingHook() at startup before any reachable store.createTask path. See the FN-5057 audit matrix (task document audit) for the current surface-by-surface registration map and resolved sourceType values. The hook is best-effort and non-blocking: task creation still succeeds even if repo resolution fails or GitHub calls fail. For existing tasks, PATCH first persists any githubTracking mutation (enable/disable, repo override, or unlink), then evaluates whether the updated task is enabled and still unlinked and should trigger best-effort issue creation (including non-githubTracking edits). This keeps retry/create behavior consistent from Task Detail instead of relying on stale pre-patch state.

Tracking behavior is controlled per task:

  • task.githubTracking.enabled turns tracking on for that task.
  • task.githubTracking.repoOverride optionally forces a specific target repo (owner/repo).
  • In the dashboard Task Detail modal, eligible existing tasks (triage, todo, in-progress, in-review) always show a compact GitHub tracking summary row. When tracking is currently disabled and editable, the header exposes a one-click Enable GitHub tracking button; linked-issue details and the rest of the tracking controls remain behind the disclosure arrow for disable/retarget flows.
  • After an engine/dashboard restart, Task Detail preserves the fetched full githubTracking payload even when the board opened the modal from a slim task row that intentionally omitted tracking metadata.
  • When a task is already tracking-enabled but still unlinked, Task Detail exposes a Create tracking issue action in the disclosure content (including non-editable columns like done) so "Issue not yet created" is not a dead-end state.
  • Clearing the Task Detail repo override stores null, which reverts repo resolution to project/global defaults.
  • Explicit task-level enablement is honored even when project/global GitHub tracking defaults are unset. If enabled: true and the repo resolves at task scope (for example via repoOverride), Fusion attempts tracking-issue creation on both create-time and eligible edit-time flows, including tasks imported from GitHub (sourceType: "github_import").
  • Explicit manual unlink (githubTracking.issue: null) does not recreate a tracking issue in that same update request, and disabling tracking does not create new issues.
  • On board cards, Fusion shows both the imported-source provenance marker and tracking link when they refer to different issues. The tracking chip is hidden only when the linked tracking issue exactly matches the source issue (owner/repo#number) to avoid duplicate badges.

Repository resolution order:

  1. Task override: task.githubTracking.repoOverride
  2. Project default: githubTrackingDefaultRepo
  3. Global default: githubTrackingDefaultRepo

When Fusion creates a tracking issue, it uses:

  • Title: [FN-XXXX] Task title
  • Body prefix: Fusion task: FN-XXXX
  • Body content: bounded plain-text task summary snippet (not full prompt content)

When tracked tasks later move to in-progress or done, Fusion also posts a short lifecycle comment on the linked tracking issue. The in-progress comment stays plain-text and capped, while the done comment can include the merge commit SHA/subject, task branch, PR link, file-change stats, and merge timestamp when those fields are available.

When a tracked task moves into done, Fusion closes the linked GitHub issue with state_reason: completed; when it leaves done for an active column, Fusion reopens the issue with state_reason: reopened; and when the Fusion task is permanently deleted from the dashboard, Fusion now prompts for issue handling (close, delete, or leave). If no explicit choice is provided by API callers, Fusion preserves the legacy default and closes the linked issue with state_reason: not_planned.

For source-imported GitHub issues (task.sourceIssue / sourceType: "github_import") without tracking metadata, delete now follows the same close/delete/leave prompt. The prompt references the source issue (owner/repo#number) and forwards the selected githubIssueAction through the existing delete route. For non-interactive callers that omit githubIssueAction (or send "auto"), the source-imported delete default is close.

GitHub authentication/settings are configured in Settings Reference via githubAuthMode (gh-cli or token) and githubAuthToken.

Completion Modes (mergeStrategy)

  • direct: local squash-merge flow into target branch
  • pull-request: PR-first completion flow via GitHub checks/reviews

Configured via settings.

Per-Task Model Overrides

Each task may override:

  • Executor model (modelProvider + modelId)
  • Validator model (validatorModelProvider + validatorModelId)
  • Planning model (planningModelProvider + planningModelId)
  • Thinking level (off|minimal|low|medium|high)

Overrides are configured from the task model tab or task creation actions.

Node Routing

Tasks execute on an effective node selected by routing precedence:

  1. Per-task node override (Task.nodeId)
  2. Project default node (defaultNodeId in project settings)
  3. Local execution (no node configured)

At dispatch time, scheduler routing is persisted on the task as:

  • effectiveNodeId
  • effectiveNodeSource (task-override, project-default, or local)

Create-time routing semantics

Cluster-aware task creation separates two routing decisions:

  • Transport node: which node receives the POST /api/tasks request (can be local or proxied remote).
  • Execution target (Task.nodeId): which node should run the task later.

These are intentionally independent. Routing a create request through node A does not force execution on node A unless Task.nodeId also points there.

Per-task node override

You can set or clear a task override from:

  • Task detail modal → Routing tab
  • Quick/create flows that support node selection
  • Bulk task actions
  • CLI:
    • fn task set-node <task-id> <node-name-or-id>
    • fn task clear-node <task-id>
    • fn task create "..." --node <node-name-or-id>
  • Pi extension tool fn_task_update with nodeId

Active-task blocking

Node override changes are blocked while a task is active/in progress. Core validation (validateNodeOverrideChange) returns reason: "task-in-progress" and users must pause/stop or wait for completion before changing routing.

Task detail routing summary

The Routing tab shows:

  • Effective node (with health indicator when known)
  • Routing source (override vs project default vs local)
  • Unavailable-node policy value (block or fallback-local)
  • Lock banner when routing is currently immutable for an active task

Activity log entries

When a task is dispatched, task activity/log records include routing decisions such as:

  • Node routing resolved: <node-or-local> (source: <source>)

Use fn task show <id> or task logs to inspect current node routing context.

Examples

# Route one task to a specific remote node
fn task set-node FN-204 edge-runner

# Remove override and return to project default routing
fn task clear-node FN-204

# Create a task with node override immediately
fn task create "Reproduce flaky node error" --node edge-runner

# Inspect routing summary from CLI
fn task show FN-204

See also: Settings Reference → Node Routing settings and Architecture → Task Routing Architecture.

Review Level

Review levels control the rigor of the review process for a task:

LevelNameDescription
0NoneNo review
1Plan OnlyReview only the plan
2Plan and CodeReview both the plan and implementation
3FullFull review with all checks

Review level can be set during task creation (in the New Task dialog under More options) or when editing a task (in the task detail modal).

The review level affects how the reviewer agent evaluates the task but does not override workflow steps or model presets.

Model Presets and Auto-Selection by Size

Project settings support reusable model presets:

  • modelPresets
  • autoSelectModelPreset
  • defaultPresetBySize (S, M, L)

Users can apply presets at task creation; manual model selection can override them.

AI Title Summarization

When autoSummarizeTitles is enabled and a task has a long untitled description, Fusion can auto-generate a concise title. This applies to tasks created from the dashboard/API as well as tasks created by agents and tooling flows (fn_task_create, delegated tasks, and triage-created child tasks). GitHub tracking now waits for the createTask-level summarizer (explicit or auto-attached from settings) to settle before filing, then uses that resulting title and falls back to deterministic description-derived title generation only when summarization is unavailable.

If a configured title summarizer model is stale after a pi upgrade, Fusion logs a warning naming that provider/model and retries once with automatic model resolution before falling back to deterministic title generation. Genuine AI-service failures are not masked by this retry.

Screenshots

Board/task cards + quick entry

Task cards and quick entry on board view

Task detail modal

Task detail modal

For UI-level details, see Dashboard Guide.

Decision-only tasks (noCommitsExpected)

Use noCommitsExpected: true for tasks where the deliverable is a decision/report, not code changes.

  • Meaning: executor allows fn_task_done with zero commits for that task.
  • Triage auto-sets it only when the task is clearly decision-shaped (e.g. "Decide whether...", "Evaluate...", "Verify...", "Audit...") with explicitly observational acceptance criteria and explicit no-code language.
  • Review Level 1 coordination/routing tasks that are board-only, explicitly say not to change source, and scope only task documents/metadata can also complete without commits even if older prompts omitted the explicit flag. This fallback is intentionally narrow and exists to recover plan-only coordination work; it does not bypass wrong-worktree or wrong-branch checks.
  • Ambiguous/forked tasks (e.g. "Investigate..." or "Investigate and fix if needed") leave it unset by default.
  • Implementation, feature, bug-fix, source-docs, test, config, or broad investigation tasks still require commits unless they have an explicit and valid no-commit contract.
  • If a legacy coordination task is stuck with fn_task_done refused: no_commits, prefer setting/verifying noCommitsExpected and re-running normal no-op finalization rather than editing .fusion/fusion.db directly.
  • You can manually set/clear it in Task Detail via No commits expected (decision-only task).
  • Task cards show a decision-only badge when enabled.
  • Finalization still uses the existing no-op review/merge path (mergeDetails.noOpMerge: true, mergeConfirmed: true); no synthetic merge strategy values are introduced.