A third census-invisible class, measured — plus the two worst instances
fixed.
## The shape
```ts
if (task.column !== "in-review") { … return; } // the census counts THIS
await this.store.moveTask(taskId, "in-progress"); // and cannot see THIS
```
The census is an AST scan for **comparisons**. A `moveTask` destination
is a **call argument**, so no backlog entry ever points at one.
Converting the guard alone is *worse than converting neither*: the
handler starts admitting work on a renamed board and then tries to move
the card into a lane that board may not declare.
This bit twice in one week — #2797 (`branch-worktree` requeued into a
lane that may not exist) and #2807 (a GitHub "changes requested" review
dropped, then a move to a hardcoded `in-progress`). Both times it was
found only because the guard *next to it* happened to be under
conversion. So I went looking.
## Measured
Across `core`/`engine`/`dashboard`/`cli`/`plugins`, excluding
`__tests__`/`*.test.*` and comment lines:
| | count |
| --- | ---: |
| hardcoded `moveTask` destinations in production | **51** |
| …passing `recoveryRehome: true` — **deliberate**, not defects | 22 |
| …plain, rejected on a board that does not declare the target | **29**
|
**The 22 must not be "fixed".** `moves.ts` exempts them on purpose
(#1411): a card stranded in an undeclared column has to stay rescuable
to a legacy safe-landing column, or it can never be recovered at all. A
sweep that converts them deletes the rescue path. That distinction is
the reason this is 29 and not 51, and it is why I measured before
writing.
## Why this got sharper recently
The `workflowHasColumn(workflowIr, toColumn)` rejection used to sit
inside a block gated on `isWorkflowColumnsCompatibilityFlagEnabled` — a
settings key **nothing in production writes** — so it never executed and
the legacy `VALID_TRANSITIONS` table decided instead. U12 hoisted it out
of that dead branch and it is now live, proven on a real store by
`live-move-path-undeclared-target.test.ts`:
```
moveTask(card in "todo" -> "triage") now REJECTS: /Unknown column for this workflow/
```
That changed the failure mode of all 29 from *"silently lands the card
in an undeclared column"* to *"throws"*.
**29 is not a crash count.** Whether a throw surfaces or disappears
depends on whether the caller catches, which is per-site and I did
**not** measure it — the doc says so explicitly rather than letting the
number imply severity it hasn't earned.
## Fixed here: 9 of the 29
`duplicate-intake` and `duplicate-guard` both archive a duplicate. On a
renamed archive lane the move is rejected, so **the duplicate is never
archived and keeps sitting on the operator's board as live work** — and
in `duplicate-guard` the row has already been stamped
`deterministicDuplicateOf`, so it is *marked* a duplicate while
occupying an active lane. Half-applied, which is the same trap as
#2797's branch clear.
Both now resolve the `archived`-trait column from the task's own
workflow through one shared helper, unioned with the legacy id.
**`cli/commands/task-lifecycle`** — `finalizePullRequestMerge` and
`finalizeNoOpMergeTask` both move the card to a hardcoded `"done"`, and
both run `updateTask({ status: null, mergeRetries: 0 })` *first*. On a
rejection the merge has already landed and the bookkeeping is already
cleared while the card never reaches its complete lane: the operator
sees a merged branch, a card still sitting in review, and a reset retry
counter. Same half-applied shape as #2797's branch clear. Both now route
through one resolver so they cannot drift.
**`contamination` / `foreign-only-contamination` (×2) /
`restart-recovery-coordinator`** — four recovery requeues to a hardcoded
`"todo"`, none of them a `recoveryRehome` escape. On a board without
that column the move is rejected and **the recovery never completes** —
the card stays contaminated or stranded, which is precisely the state
these paths exist to clear.
**Consolidation.** `resolveReboundTargetForTask` and
`resolveArchiveTargetForTask` now live beside
`resolveTaskLifecycleColumns` in `workflow-lifecycle-traits`, already
the store-dependent resolution seam. My first pass put the archive
helper inside `duplicate-intake` and had `duplicate-guard` import it
from there — wrong home, and it would have grown a copy per caller as
more sites converted. Seven call sites now share two definitions.
**Plain (non-`recoveryRehome`) destinations: 29 → 21.**
**Coverage on the CLI pair is scoped, and I'd rather say so than imply
more:** the test covers the *resolver*, not the two call sites. Both
enclosing functions are private and reachable only through
`processPullRequest`, which needs a live GitHub surface — exporting them
purely to test wiring is a worse trade than stating what is covered.
Three cases: renamed lane resolves, no-workflow falls back to the legacy
id (which also pins that a default board is byte-identical), and a
throwing lookup falls back.
## Revert result (measured)
| conversion | reverted → |
| --- | --- |
| duplicate archive destination | new case fails — `moveTask` called
with `"archived"` on a board whose archive lane is `boxed` |
| CLI complete-lane resolver | replacing the body with a bare `return
"done"` fails the renamed case |
| both move-target resolvers | replacing either body with a bare return
of its legacy id fails 5 cases across the resolver suite and
`duplicate-guard` |
Each resolver has a **non-vacuous companion** asserting it does *not*
return the legacy id on a renamed board — without it, a resolver
returning any string would pass. The fallback cases are load-bearing
rather than padding: `resolveWorkflowIrForTask` degrades to the built-in
IR rather than throwing, and the built-in rebound/archive lanes *are*
`todo`/`archived`, so those cases also pin that a default board is
byte-identical.
The pre-existing case asserting the legacy `"archived"` passes both
ways, which is exactly why it could not detect this and why the new one
supplies a workflow.
## Ownership note
`packages/core` was `batch-core`'s territory and `packages/cli` was
`batch-cli-plugins`'. Both batches have landed, and this is
newly-discovered work in the class documented here rather than leftover
conversion backlog. Four sites, two shared helpers — happy for either
half to move if those owners would rather carry it.
## Verification
- `pnpm test:gate` — 161 + 487 + 13 + 71, green
- `duplicate-guard` + `duplicate-intake` — 40 passed
- `tsc` on core and engine — clean
- `pnpm lint`, `check:changesets`, census `--strict` — all clean (run
explicitly; a clean `pnpm lint` alone is not evidence the CI Lint check
passes)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **Bug Fixes**
- Duplicate tasks are now archived to each workflow’s configured archive
lane.
- Completed tasks are moved to the workflow-specific completion lane,
with a safe fallback for older workflows.
- Recovery and requeue actions now use each workflow’s configured
rebound lane instead of assuming a fixed destination.
- **Documentation**
- Added guidance on avoiding failures caused by hardcoded workflow
destinations and incomplete lifecycle conversions.
- **Tests**
- Added coverage for renamed workflow lanes, fallback behavior,
duplicate archiving, and recovery destinations.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@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
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.
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
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:
- Install the
droidbinary and ensure it is on yourPATH - Authenticate with Droid CLI (
droid auth login) - In Fusion dashboard, go to Settings → Authentication and enable Factory AI — via Droid CLI
- 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.

