Files
fusion/packages/cli
gsxdsm 3badc244a7 U12 part 2: bind the three U5 reconciliation guards — USER-VISIBLE (and one path that couldn't run under PostgreSQL at all) (#2512)
## U12 part 2 — the three U5 reconciliation guards now actually fire

USER-VISIBLE. Taken on standing authority; here is exactly what changed
for operators.

All three read the RAW `experimentalFeatures.workflowColumns` key via
`store.workflowColumnsFlagOn()`. Nothing in production writes it, so all
three have been inert since the workflow-columns cutover.

| Guard | Before (every real project) | After |
|---|---|---|
| Workflow edit removing an **occupied** column | Save succeeded; cards
left in a column the workflow no longer declares | Save fails with
`OccupiedColumnsError` unless `rehomeTo` is supplied |
| Workflow **delete** | Occupant capture returned `[]`; cards sat in the
deleted workflow's columns until the next engine start | Cards move to
the default workflow's entry column as part of the delete |
| Workflow **switch** | Never reconciled; the `reconciliation` field in
the declared return type was never populated | Card in an undeclared
column moves to the resolved target; a declared column is preserved |

Both consumers already handle the new outcomes and needed no change:
`register-workflow-routes.ts` maps `OccupiedColumnsError` to a
structured 409 carrying per-column occupant counts, and
`fn_workflow_update` returns a retryable structured result. The
dashboard editor's `rehomeTo` retry flow becomes reachable for the first
time. I only updated two stale "flag-ON" comments there — that code was
correct all along and simply never fired.

### What an operator actually sees (USER-VISIBLE — read this bit)

Four changes to what the board and the API do. Nothing here is silent.

1. **Editing a workflow to remove a column that has cards in it now
FAILS.** Previously the save succeeded and the cards were left in a
column their workflow no longer declared. The dashboard shows the
existing 409 with per-column occupant counts and prompts for a re-home
target; retrying with `rehomeTo` moves the cards and saves. Removing an
EMPTY column is unaffected.
2. **Deleting a workflow moves its cards immediately** to the default
workflow's entry column, instead of leaving them until the next engine
start.
3. **Switching a task's workflow moves the card** when the new workflow
does not declare its current column. A card whose column IS declared
stays exactly where it is. The API response now carries the
`reconciliation` summary it always promised.
4. **A switch whose re-home would be REJECTED is now refused before
anything is written.** If the destination column is at its WIP limit,
the switch fails with a structured 409 (`workflow-switch-rehome-failed`)
naming the task, both columns and the reason — and **nothing changes**:
the task keeps its current workflow AND its current column. Retry after
making room. Previously this combination committed the selection and
then silently reported a move that never happened, leaving selection and
column disagreeing.

**Can a torn card still happen? Yes, in one narrow case, and here is how
you recover.** If the destination fills in the window between the
pre-flight and the move, the selection is already committed and the card
ends up in a column its new workflow does not declare. That case is not
silent: it writes a `task:workflow-switch-torn` run-audit row, and the
error carries `selectionCommitted: true` with both columns. Recovery:
make room in the destination and move the card there, or switch the task
back — and if neither happens, the R7 startup sweep
`reconcileUndeclaredTaskColumns` re-homes it on the next engine start.
The card is never lost; it is visible in a lane the board may not draw
until one of those runs.

The one thing to watch after merge: (1) converts a previously-silent
success into a visible failure, so an operator mid-edit on a busy
workflow will start seeing a 409 they never saw before. That is the
point — the alternative was stranding their cards — but it is the change
most likely to generate a "this used to work" report.

### The thing that made this more than a gate removal

Un-gating the switch guard surfaced that
`selectTaskWorkflowAndReconcileImpl` read the task through
`store.readTaskFromDb` — the **synchronous SQLite** reader, which throws
under PostgreSQL:

```
TaskStore.db: SQLite Database is not available in backend mode
```

The flag returned before that line, so the gate was hiding a path that
**could not execute at all in the production backend**, not merely a
disabled feature. Ported to the async `readTaskRow`. Found by the new
tests, not by reading the code.

### Review round 2 (both findings real, both fixed)

**Torn write with no alarm — fixed by ORDERING, not by a louder
message.** My first attempt only made the error loud, which left the
torn state intact. The real fix is that the deterministic rejection
cause (destination at its WIP limit) is now checked BEFORE
`selectTaskWorkflow` commits, by resolving the target IR straight from
`workflowId` instead of through the task's selection. Nothing commits on
that path.

For the residual race the failure is loud AND recorded: `rehomeOccupant`
now returns `{ moved, error? }` (additive; sweep callers ignore it), the
switch writes a `task:workflow-switch-torn` run-audit row, and throws
`WorkflowSwitchRehomeFailedError` with `committed: true`. Consumers
translate it: the dashboard route returns a structured 409 with
`selectionCommitted`, and `fn_task_set_workflow` returns the same fields
— no more generic "something went wrong".

**Fabricated column for a deleted task.** My first fix fell back to
`fromColumn` when the final read found no row, so a task soft-deleted
mid-switch was reported as having its old column *preserved*. Absent now
reads as absent (the optional `reconciliation` is omitted). Extracted as
the pure `buildSwitchReconciliation` seam because the window is not
reachable through the public call — `selectTaskWorkflow` rejects an
already-deleted task up front — so it is a genuine race, and I test the
decision directly rather than asserting it from reading the code.

### Revert-proof, measured

New `workflow-reconciliation-production-shape.pg.test.ts` — 6 cases,
with the flag **never written**, which is the configuration every real
project has. Each flip reverted individually:

- re-gate the edit guard → **2 failures** (OccupiedColumnsError case;
rehomeTo re-home case)
- re-gate the delete capture → **1 failure** (card stays in
`custom-hold`)
- restore the switch early return → **2 failures** (`reconciliation`
undefined; card does not move)
- all three in place → **6/6 green**

Round-2 fixes, also measured:
- restore the `fromColumn` fallback → the "row is gone" case fails
(reports `preserved: true` for a deleted task)
- drop the `!outcome.moved` throw → the capacity-blocked case fails
(resolves instead of raising)
- **move the capacity pre-flight back AFTER the commit → the case fails
on the SELECTION assertion** (expected `WF-002`, received `WF-001`),
i.e. it proves the ordering, not the wording

The pre-existing coverage in `workflow-authoritative-reads.pg.test.ts`
reached the occupied-column guard by **writing the flag ON itself** —
same pattern as the ListView/Board suites in part 1. Its flag write is
removed; it now runs in the production shape.

### Where I nearly got this wrong

My first revert harness was buggy and I briefly concluded the delete
re-home was **redundant** — I had probed the stored column and seen
`triage` with what I thought was the flip reverted. It wasn't.
`workflow-ops.ts` contains two identical `const occupantTaskIds = await
store.listWorkflowOccupantTaskIds(id, false)` lines (field-reconcile
block, delete path), so my first-match edit reverted the wrong one.
Re-run anchored on surrounding context, the delete case fails as
predicted. Recorded in the test header as a caution. I also chased and
**refuted** a scarier hypothesis along the way — that an unrelated
`updateTask` coerces a custom column back to `triage`. It does not; the
column survives.

### Deliberately NOT in this PR

The v1-IR rollback-compat persistence (`downgradeIrToV1IfPure`) on the
workflow UPDATE path. It shared the same `flagOn` variable, which is how
it surfaced: **one flag read was feeding two unrelated decisions, so the
flag has more decision sites than call sites** — my earlier 9-site
inventory undercounted. It chooses the stored *shape* of the graph
rather than gating a guard, so it is a persistence-format change with a
different blast radius. It now reads the flag explicitly, behaviour
unchanged, for a follow-up.

The `moves.ts` group remains U2b's.

### Verification

`pnpm test:gate` (307 + 10 + 71), `pnpm lint`, `pnpm verify:fast` (17
steps), both typechecks green. Full `packages/core` PostgreSQL suite:
**1042 passed, 3 failed** — `central-archive-secrets.test.ts`
(log-prefix assertion) and
`workflow-settings-project-identity.pg.test.ts` (×2, project-id
resolution). I confirmed the identical 3 failures on a stashed clean
tree: pre-existing, unrelated. No Fusion instance booted.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Workflow edits now prevent removal of occupied columns unless cards
are moved to a specified destination.
* Cards are automatically re-homed when workflows are deleted or
switched.
* Workflow switches now check destination capacity before committing and
provide clear conflict details when re-homing fails.
* Reconciliation results now indicate whether cards were moved or
preserved.


<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 18:52:46 -07:00
..
2026-06-10 18:06:51 -07:00
2026-07-26 18:11:47 -07:00

Fusion

@runfusion/fusion

From rough idea to production code — automatically.

Multi-node agent orchestrator — tasks, agents, missions, git, files, and worktrees, with any model, local or cloud.

runfusion.ai → · GitHub · Docs


Fusion reel: from rough idea to production code

Install

Zero install, straight from npm:

npx runfusion.ai

Boots the dashboard. Subcommands forward through (npx runfusion.ai task list, etc). Long form: npx @runfusion/fusion dashboard.

One-line installer (macOS & Linux — auto-picks Homebrew, falls back to npm):

curl -fsSL https://runfusion.ai/install.sh | sh

Homebrew (macOS & Linux):

brew install runfusion/fusion/fusion

Fully-qualified install auto-taps and, on Homebrew 6.0+, trusts only this formula. If short-name install fails with “untrusted tap”, run brew trust --formula runfusion/fusion/fusion then brew install fusion.

npm global:

npm install -g @runfusion/fusion
fn dashboard              # or: fusion dashboard

Launch the dashboard

From a shell:

fn dashboard                 # or: fusion dashboard / npx @runfusion/fusion dashboard
fn dashboard --paused        # start with automation paused
fn dashboard --dev           # development-mode dashboard + AI engine
fn dashboard --no-engine     # web UI only, no AI engine

The dashboard gives you:

  • A live kanban board — tasks move through columns automatically as AI works on them
  • Task detail view — generated spec, step-by-step progress, reviewer verdicts, full execution log
  • Dependency-aware scheduling — declare task dependencies or let the engine infer them
  • Auto-merge — on by default; reviewed work squash-merges without you lifting a finger
  • Parallel execution — independent tasks run simultaneously in isolated git worktrees
  • Self-sustaining board — agents may spawn follow-up tasks; the board feeds itself

Your entire dev environment. On a single pane of glass.

Describe a task in plain language. A triage agent reads your project, understands context, and writes a full PROMPT.md spec — steps, file scope, acceptance criteria. Then Fusion plans, reviews, executes, and reviews again, in an isolated git worktree, with a human approval gate wherever you want one.

One board. Controlled from anywhere. Laptop, Mac mini, Linux server, cloud VM, phone — all connected.

Fusion mesh: laptop, Mac mini, Linux server, cloud VM, phone — all synced

Run an agent company

Import a team. Run it autonomously for weeks. 440+ agents across 16 companies, wired for missions, mailboxes, and inter-agent delegation.

npx companies.sh add paperclipai/companies/gstack
Fusion agent company: import a team, run it autonomously for weeks

How it works

You create a task with a rough description. A pipeline of specialized agents takes over.

Specification. A triage agent reads your codebase — file structure, existing patterns, related code — and turns your rough idea into a detailed spec. It breaks the work into discrete steps, identifies which files are in scope, writes acceptance criteria, and assigns a complexity rating that determines how aggressively the work gets reviewed.

Scheduling. Tasks declare dependencies on each other. The scheduler builds a dependency graph and starts work only when upstream tasks are done. Independent tasks run in parallel — each in its own isolated git worktree, so there are no conflicts during execution.

Execution & review. An executor agent works through the spec step by step in the worktree. At each step boundary, a separate reviewer agent, with read-only access, independently evaluates the work. The reviewer can approve (continue), request revisions (fix specific issues), or force a rethink (change the approach entirely). Review depth scales with the task's complexity rating: trivial tasks get light checks, complex tasks get thorough multi-pass review.

Merge. When execution finishes and the reviewer signs off, the task moves to In Review:

  • Direct merge (default) — automatically squash-merges the completed task branch into your current branch with a clean commit.
  • Pull request — automatically creates or links a GitHub PR, waits for reviews/checks, then merges once policy conditions are satisfied.

autoMerge controls whether Fusion performs completion automatically. If disabled, tasks stay in In Review until you finish the merge yourself. For PR-first mode, authenticate GitHub with gh auth login.

Tasks flow through: Triage → Todo → In Progress → In Review → Done.

This execution model is heavily based on Taskplane.


What makes it different

🧠 AI specification Rough idea in, detailed PROMPT.md out — steps, file scope, acceptance criteria.
🔁 Workflow gates Plan → Review → Execute → Review on every step. Block or pass automatically.
🌳 Worktree isolation Each task runs in its own branch and worktree. Parallel tasks. Zero conflicts.
⚡ Smart merge Passing every gate? Fusion squash-merges and moves on.
🛰️ Multi-node mesh Laptop, server, cloud, phone — all synced. Desktop, mobile, web.
🧩 Any model Anthropic, OpenAI, Ollama, and more.
🏢 Agent companies Import pre-built teams — 440+ agents across 16 companies.
📬 Inter-agent messaging Built-in mailbox between agents. Delegate, clarify, coordinate.
🗺️ Missions Hierarchical planning with autopilot and validation contracts.
🔓 Open source. MIT. No vendor lock-in. Run it on your own hardware.

Working from chat

Manage tasks without leaving the conversation:

"Every ten minutes, analyze the server code for logic the client hasn't implemented yet and create tasks. Tasks may spawn additional tasks, so just add enough to keep the board saturated."

"Create a Fusion task to fix the login redirect bug"

"Add a task for dark mode support, it depends on FN-003"

"What's the status of FN-042"

"Attach screenshot.png to FN-007"

"Pause FN-012 — I want to add more context first"

The Fusion extension exposes tools to create tasks, check progress, attach files, and pause or resume automation.


Standalone CLI

See STANDALONE.md for additional installation and usage options.

Optional provider: Factory AI via Droid CLI

@runfusion/fusion now ships a vendored @fusion/droid-cli extension in the published CLI bundle.

To use it:

  1. Install the droid binary and ensure it is on your PATH
  2. Authenticate with Droid CLI (droid auth login)
  3. In Fusion dashboard, go to Settings → Authentication and enable Factory AI — via Droid CLI
  4. Restart Fusion when prompted so the extension is loaded into the runtime

Once enabled, droid-cli models appear in Fusion model selection.

Maintainer note: workspace plugins in published CLI bundles

When CLI or dashboard runtime code imports workspace plugin packages (for example @fusion-plugin-examples/roadmap), those imports must stay statically analyzable and covered by packages/cli/tsup.config.ts noExternal rules so plugin runtime code is inlined into dist/bin.js.

Do not introduce dynamic or variable module specifiers for workspace plugin runtime paths in the published execution path. If a workspace plugin is needed for bundled auto-install, stage a bundled plugin entry (dist/plugins/<id>/bundled.js) rather than copying raw TypeScript source into dist/.

Full documentation

Architecture details, development setup, and contributor info live in the project README.

License

MIT — see LICENSE.