Files
fusion/docs/task-management.md
Fusion (runfusion.ai) 216c32bfe5 feat(FN-5347): reset manual retry counters across task retry surfaces
- Add a shared manual retry reset helper in core and export it for consumers
- Wire retry counter resets into CLI task retry, extension task retry, and dashboard task workflow routes
- Align retry reset behavior with task typing updates and remove superseded task-helper reset paths
- Add focused core/CLI/extension/dashboard tests plus docs and a changeset describing retry reset behavior
2026-05-21 15:45:48 -07:00

861 lines
57 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Task Management
[← Docs index](./README.md)
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 auto-archived instead of moved to `todo`.
Near-duplicate archival is reversible and leaves lineage markers behind:
- `source.sourceMetadata.nearDuplicateOf = <canonicalTaskId>`
- activity event `task:auto-archived-near-duplicate`
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:
```text
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](./todo-view.md).
### 4) Subtask Breakdown Dialog
Use the 🌳 button:
- Generate 25 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)
- **Browser Verify** — Enable browser verification workflow step
### 6) CLI creation
```bash
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:
1. **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-review` → `done` 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-exhausted` — `mergeRetries` 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-blocker` — `getTaskMergeBlocker()` 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`
5. **done** — merged/finalized
6. **archived** — preserved history, optionally cleaned from filesystem
Board ordering behavior:
- `todo` mirrors scheduler dispatch order: priority first (`urgent` → `low`), 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
```bash
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`](./settings-reference.md#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:
| Mode | Description |
|------|-------------|
| `standard` | Full execution with complete review workflow (default) |
| `fast` | Expedited execution with minimal overhead for simple tasks |
### Fast Mode Bypassed Gates
When `executionMode: "fast"`, the following automated review/validation gates are **bypassed**:
| Gate | Standard Mode | Fast Mode |
|------|---------------|-----------|
| `review_step` tool enforcement | Available to executor agent | **Not injected** |
| Pre-merge workflow-step execution | Runs configured steps | **Skipped** |
| Workflow revision loop | Enabled (feedback → fix → re-review) | **Disabled** |
### Fast Mode Mandatory Gates
The following quality gates **remain enforced** in fast mode:
| Gate | Behavior |
|------|----------|
| `task_done` requirement | Agent must call `task_done()` to complete |
| Completion blocker checks | Tests, build, and typecheck from PROMPT.md still enforced |
| Post-merge workflow steps | Run as normal (merger-owned) |
### Execution Mode Matrix
| Feature | Standard | Fast |
|---------|----------|------|
| Executor agent session | Full prompt + tools | Full prompt (minus review_step) |
| Pre-merge workflow steps | ✅ 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:
```json
{
"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 `queued` → `in-progress` → `addressed`/`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:
```bash
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`
## Archive and Restore
### Archive behavior
- `fn task archive <id>` moves done task to `archived`
- Dashboard delete confirmations for `done` tasks now include an **Archive Instead** action so users can preserve history without soft-deleting the task. This option is shown only for `done` tasks because the store-level archive contract only allows archiving from the `done` column.
- 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`
- `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 to `done`
- 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.
```bash
fn task import owner/repo --labels bug --limit 20
fn task import owner/repo --interactive
```
Create PR for an `in-review` task:
```bash
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)
- `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.
- 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`.
GitHub authentication/settings are configured in [Settings Reference](./settings-reference.md) 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
```bash
# 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](./settings-reference.md#node-routing-settings-project-scope) and [Architecture → Task Routing Architecture](./architecture.md#task-routing-architecture).
## Review Level
Review levels control the rigor of the review process for a task:
| Level | Name | Description |
|-------|------|-------------|
| 0 | None | No review |
| 1 | Plan Only | Review only the plan |
| 2 | Plan and Code | Review both the plan and implementation |
| 3 | Full | Full 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.
## Screenshots
### Board/task cards + quick entry
![Task cards and quick entry on board view](./screenshots/dashboard-overview.png)
### Task detail modal
![Task detail modal](./screenshots/task-detail.png)
For UI-level details, see [Dashboard Guide](./dashboard-guide.md).
## 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.
- Ambiguous/forked tasks (e.g. "Investigate..." or "Investigate and fix if needed") leave it unset by default.
- 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.