# pi-fusion user guide

README covers the why. This guide covers commands, config, and troubleshooting.

## Mental model

`pi-fusion` turns one hard question into a small parallel panel:

```text
prompt → parallel panel → judge picks the best answer → report
```

The command stays simple. One prompt starts the panel. One synthesis step then
turns the collected evidence into a Markdown report.

There is a second shape. Give panel members a `question` and they answer
*different facets* instead of the same question, and a composer merges their
answers rather than picking between them:

```text
prompt → panel, one facet each → composer merges → report
```

You do not select between these. Facets decide: a panel with `question` fields
merges, a panel without them selects.

Panel diversity comes from different model choices, different perspective
prompts, or both. **Mixing models is the main lever.** The config that
`/fusion init` writes sets no `model`. By default you therefore get one model in
three roles. Give each member its own `model` to get the real benefit.

Fusion launches panels through the `pi-subagents` RPC `script` field; the panel and judge remain separate durable runs. Before dispatch, Fusion persists the resolved profile, exact stage parameters, request digest, and RPC correlation ID. Ordinary workflows carry a stable Fusion run/stage identity in `args`, so a new same-prompt review cannot reuse another review's children. Project snapshots in `.pi/fusion/runs` preserve identity across sessions and process restarts. An uncertain launch or runtime replacement keeps admission blocked; see [Recovery required](#recovery-required). Corrupt snapshots block recovery instead of reviving stale state.

The base Pi session stays in control. Fusion is a tool for decisions, not a replacement for normal coding.

## Commands

Preferred command shape:

```text
/fusion
/fusion <prompt>
/fusion --profile <name> <prompt>
/fusion -p <name> <prompt>
/fusion --panel <entries> <prompt>
/fusion status
/fusion stop
/fusion continue <fusion-run-id> <panelist-number>
/fusion finish <fusion-run-id> <panelist-number>
/fusion init
```

Notes:

- Bare `/fusion` shows a short help message.
- `/fusion status` shows the active run, last run, warnings, and subagent run IDs.
- `/fusion stop` stops the active panel, legacy chain, or judge run.
- `/fusion init` writes `.pi/fusion.json` for the current trusted project.
- Exact one-word prompts `init`, `status`, and `stop` are reserved as `/fusion` subcommands.

### `--panel`

Builds a one-off panel without editing config, for trying a composition before
committing to it. The resolved profile still supplies the judge and every other
setting. Only the panel changes.

```text
/fusion --panel opus,openai/gpt-5.5 Which design should we pick?
/fusion --panel=opus,openai/gpt-5.5 Which design should we pick?
/fusion --profile audit --panel opus,gemini-pro What did we miss?
```

Each comma-separated entry is `<model>` or `<agent>:<model>`:

```text
--panel opus,gpt-5.5                                     # both use the default panelist
--panel pi-fusion.fusion-panelist-web:gpt-5.5,opus       # first member gets web access
```

An entry counts as agent-qualified only when the part before the first `:`
contains a `.` and the part after it is not a thinking level. That keeps both
`opus:high` and `gpt-4.1:high` models rather than agent references.

The thinking levels are `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`. The selected model must support the requested level; Fusion does not translate arbitrary aliases such as `ultra`.
A word that is not one of them is read as a model, so `gpt-4.1:ultra` asks for
the agent `gpt-4.1`, and the run fails with an unknown-agent error. Use a real
level, or write the agent in full.
Claude alias shorthand works inline: `--panel claude-work/opus-4.8`.

### `--judge`

Overrides the judge of the resolved profile for one run, without editing config
or keeping a near-duplicate profile. It is per-field: everything the spec does
not name stays as the profile declares it.

```text
/fusion --judge review-judge What did the panel miss?
/fusion --judge review-judge:openai/gpt-5.5 What did the panel miss?
/fusion --judge review-judge:max What did the panel miss?
/fusion --judge review-judge:openai/gpt-5.5:max What did the panel miss?
```

The spec has one to three `:`-separated segments, `<agent>[:<model>[:<level>]]`,
so the shape decides the meaning:

| Spec | Agent | Model | Thinking |
| --- | --- | --- | --- |
| `judge` | replaced | profile | profile |
| `judge:gpt-5.5` | replaced | replaced | profile |
| `judge:max` | replaced | profile | replaced |
| `judge:gpt-5.5:max` | replaced | replaced | replaced |

A tail segment that is a thinking level is always read as one, which is what
distinguishes `--judge judge:max` from a model id. A tail that is not a level
stays part of the model, so variant ids survive:

```text
--judge judge:qwen3.6:35b-a3b-coding-nvfp4    # model id, not a level
```

Two consequences are worth knowing. A tail segment that names a level is never read
as part of the model, so a model whose own id ends in a level name cannot be given
in the tail: `--judge judge:gpt-5.5:high` means model `gpt-5.5` at level `high`,
never a model called `gpt-5.5:high`. And a thinking-only override needs a model to
attach to, so it fails at start rather than silently running at the profile's level
when the profile judge declares no `model`.

`--judge` composes with `--profile` and `--panel`, and the composed judge is
recorded in the run snapshot, so `restore` keeps it. Claude alias shorthand
resolves for an overridden judge model exactly as it does for a configured one.

```text
/fusion --profile audit --judge review-judge:gpt-5.5:high What did we miss?
/fusion --panel opus,gpt-5.5 --judge review-judge:high What did we miss?
```

## Config files

Config lookup order:

1. trusted project config: `.pi/fusion.json`
2. global config: `~/.pi/agent/fusion.json`
3. built-in defaults

Run this inside a trusted project:

```text
/fusion init
```

## Minimal config

```json
{
  "defaultProfile": "quality",
  "profiles": {
    "quality": {
      "panel": [
        {
          "id": "architect",
          "label": "Architect",
          "agent": "pi-fusion.fusion-panelist",
          "role": "architecture, tradeoffs, and failure modes"
        },
        {
          "id": "implementer",
          "label": "Implementer",
          "agent": "pi-fusion.fusion-panelist",
          "role": "implementation details, API contracts, and edge cases"
        },
        {
          "id": "tester",
          "label": "Tester",
          "agent": "pi-fusion.fusion-panelist",
          "role": "test strategy, regressions, and verification"
        }
      ],
      "judge": {
        "agent": "pi-fusion.fusion-judge"
      },
      "concurrency": 3,
      "panelTimeoutMs": 900000,
      "judgeTimeoutMs": 900000,
      "panelToolBudget": { "soft": 8, "hard": 12, "block": "*" },
      "judgeToolBudget": { "soft": 8, "hard": 12, "block": "*" },
      "context": "fresh",
      "stopWhenPanelAgrees": false
    }
  }
}
```

## Profile fields

Top level:

- `defaultProfile`: profile used when `/fusion` has no `--profile`
- `profiles`: named profile map

Profile:

- `panel`: one or more panel members
- `judge`: judge agent config
- `wakeOnCompletion`: optional boolean, default `false`. Controls **Fusion's final-report wake**, not pi-subagents' native notifications. With the default, Fusion appends its report without starting or interrupting a turn. When enabled, only the same interactive session that started the run is woken; an idle wake uses a normal user prompt so `before_agent_start` runs, and a busy wake queues a follow-up. Fusion does not wake the interactive agent for RPC starts; their controller owns continuation. However, pi-subagents 0.76.1 independently wakes the parent after native panel/judge completion, including RPC starts. Its public RPC has no per-run suppression option. Only the successful terminal-state writer publishes the Fusion report: duplicate events, competing writers, and reload do not replay it, but a crash between persistence and sending can lose a wake. This is at-most-once attempt, not guaranteed delivery, and does not retry the review.
- `concurrency`: max parallel panelists. Ordinary panels immediately start the next queued member when a slot finishes or fails, while retaining configured result order. Each slot uses failure-collecting `runs.all` semantics, so a rejected panelist does not discard successful siblings or prevent queued members from running. When `stopWhenPanelAgrees` is on, Fusion initially executes only the resolved synthesis quorum (capped by `concurrency`) before launching another batch, so a non-default quorum—not always the first two—governs early agreement.
- `timeoutMs`: legacy shared wall-clock timeout in milliseconds. It is a fallback only; `/fusion status` warns when it supplied an effective deadline.
- `panelistSoftTimeoutMs`: optional soft deadline, measured from each child's actual start, not its queue time. Requires `pi-subagents` RPC advertising `nonRecoveringSteer`. It must leave more than one minute before the effective hard child deadline, and the panel budget must cover every concurrency wave plus grace.
- `panelistTimeoutMs`: per-panelist deadline. Fusion caps it below the enclosing panel deadline.
- `panelTimeoutMs`: panel workflow wall-clock deadline. When neither it nor legacy `timeoutMs` is set, the default is 15 minutes per execution wave. Agreement mode uses its quorum-capped concurrency when counting waves. An explicit short deadline can still cut queued work off; soft-deadline profiles reject insufficient wave budgets.
- `panelGraceMs`: reserved time between a child deadline and the enclosing panel deadline (default 5 seconds).
- `judgeTimeoutMs`: synthesis workflow deadline. For every deadline the precedence is per-run CLI/tool/RPC override, stage profile field, legacy `timeoutMs`, then the built-in default.
- `minimumSuccessfulPanelists`: `"majority"` (default, half rounded up), `"all"`, or a positive number. Every multi-member panel requires at least two answers, including two-member majority panels. It is the panel quorum for synthesis. For a multi-member panel, numeric `1` is effectively `2`: synthesis needs two candidate answers. A one-member panel remains a direct single-panel result.
- `context`: `fresh` or `fork`
- `stopWhenPanelAgrees`: optional boolean, default `false`. When it is on, Fusion can stop the panelists that have not finished yet only after the configured synthesis quorum is already met. The agreement conditions also require two or more finished panelists to give the same normalized recommendation, every one to report `high` confidence, none to ask for more evidence, and work to remain. Thus an `"all"` quorum never skips unfinished panelists. The judge still runs over the answers already collected. This policy is fixed on purpose. There is no threshold to tune. Exact caller contracts such as `plan-review-v1` forbid the required decision record, so combining them with agreement stopping is rejected before launch.
- `synthesis`: rarely needed. Inferred from the panel — any member with a `question` means `merge`, otherwise `select`. Set it only to override that. See [Synthesis modes](#synthesis-modes).
- `blindPanelLabels`: optional boolean, default `false`. When it is on, the judge sees `Candidate A`, `Candidate B`, and so on, instead of the configured labels. Fusion also withholds agent names and artifact paths, because they contain the member id. A role label reads as an authority cue before the judge compares any content. Your report always shows the real names.
- `panelToolBudget`: optional `{ "soft": n, "hard": n, "block": "*" | [tools...] }` applied to each panelist. Fusion uses `{ "soft": 8, "hard": 12, "block": "*" }` when omitted. After `hard`, the selected tools are blocked so the panelist can still finalise.
- `judgeToolBudget`: optional `{ "soft": n, "hard": n, "block": "*" | [tools...] }` for the judge or composer. Fusion uses `{ "soft": 8, "hard": 12, "block": "*" }` when omitted. `soft` is a nudge. After `hard`, the selected tools are blocked so synthesis can still finalise. `soft` or `hard` must be positive integers when present, and `soft` must not be larger than `hard` when both are present. Legacy soft-only budgets remain valid.

Timeout values must be integers from 1 through 2147483647 milliseconds (Node's timer limit); larger values are rejected rather than silently shortened.

Timeouts are hard workflow deadlines. A child terminated at the deadline can report exit 143. Fusion durably keeps verified completed slots, turns terminal running/interrupted slots into typed failures, and fails closed when lifecycle sources genuinely disagree. A timed-out judge never becomes a panel-only success. When at least one valid panel result exists, Fusion produces either synthesis at quorum or an explicitly unsynthesized partial report below quorum; failures and timeouts are disclosed as missing coverage. Only zero successful outputs fail outright. Fusion never automatically retries a failed panelist, restarts a panel, or extends a deadline.

### Runtime compatibility

Fusion requires Pi 1.0.2 or later in the 1.x series. The current compatibility checks use Pi 1.0.4 and pi-subagents 0.76.1. Restart Pi after updating either upstream package; `/reload` cannot safely replace already-imported upstream modules. Restart all sessions owning this project's Fusion state before switching versions. Do not downgrade while a run is active or requires recovery.

| Runtime | Contract checked |
| --- | --- |
| Pi 1.0.4 | API/types, isolated SDK loading, model-only tools, commands, and a separate-process integration with a localhost model fixture |
| pi-subagents 0.76.1 | Real RPC validation and select/merge/single-member execution, operation replay, lifecycle artifacts, and host shutdown |
| pi-subagents before 0.74.0 | Not supported by the current `script` request format; no automatic fallback/retry |

Fusion subscribes to advertised `childStatus` and `processTerminal` channels. Correlated events only schedule a status/artifact reconciliation, never count as successful results. Bursts are coalesced; events during a pending check request one follow-up check. The two-second fallback poll and restore-time reconciliation remain because events can be missed. Subscriptions and queued hints are cleared on shutdown.

The `start_fusion_review` tool returns a structured receipt (`status`, plus `runId`, `activeRunId`, or `error` when applicable); failures and conflicts set `isError`. An uncertain admission remains nonterminal and includes `recoveryRequired`; `status: "started"` alone is not proof that native execution was admitted. `resolve_fusion_deadline` returns a structured decision receipt, not proof that a child complied. Both tools are model-only: they cannot be nested inside codemode or invoked through `ctx.executeTool()`. Extension controllers should use `fusion:rpc:v1`.

Known upstream workflow `failureKind` categories are persisted and exposed in RPC run state; failure reports include the category. Older snapshots without a category remain readable. Unknown categories are ignored, not guessed. Categories are diagnostic evidence, not an automatic retry policy.

Lifecycle hints are not stop or containment proofs. An ordinary stop acknowledgement means the request was accepted, not that every descendant exited. Explicit lifetime mode requires the stronger contract described below. If pi-subagents imposes a concurrency cap below the profile's effective concurrency, allow extra panel time; Fusion cannot discover that cap through the current RPC.

#### Ordinary workflows and strict lifetime

Use pi-subagents 0.76.1 for the checked ordinary path. Start it as usual, for example `/fusion Compare these designs`; leave `executionLifetime` out of tool and RPC requests. The `script` panel and separate judge use the profile's ordinary timeout rules above. Disabling upstream `workflow-scripts` also disables ordinary Fusion; Fusion does not bypass that policy.

Explicit `executionLifetime` is not available with the checked pi-subagents 0.76.1 capabilities. Native `ping` advertises async spawn, stop, non-recovering steer, and process terminal proof, but not durable operations, explicit lifetimes, or owned-tree containment. Fusion rejects an explicit lifetime before spawning. Tune ordinary stage timeouts when more time is needed; they are not a strict execution-lifetime guarantee. Fusion calls native RPC directly and does not use `@alexeiled/pi-subagents-bridge`; installing or updating Bridge does not enable this mode.

### Recovery required

`recoveryRequired` is a nonterminal quarantine, exposed in the tool receipt, RPC run state, `/fusion status`, and the `fusion` status key:

- `launch-unknown`: dispatch may have succeeded but the native ID was not safely recorded. A transport timeout, malformed reply, or missing ID is not proof of rejection. Older active snapshots missing native IDs are also held. **Accepted limitation in 0.12.0:** native `execution_failed` is ambiguous. It also covers definite capacity/configuration rejections, which therefore can quarantine the project even when no workflow started. A synchronous error, its wording, or an empty status lookup is not no-start proof. There is no supported reset for that quarantine in this release.
- `runtime-replaced`: pi-subagents stopped the workflow coordinator during reload/session replacement; awaited children may still be running.

Fusion preserves the run and its evidence and blocks new runs, synthesis, and automatic replay. `/fusion stop` records pending cancellation while ownership is unresolved. The live owner polls for a late binding and requests stop only for the recorded exact native ID. Transport failures and mismatched acknowledgements stay pending and are retried for that ID. Native `not_found` or `invalid_state` replies instead persist `state: "undeliverable"` and stop automatic retries, including after restart. A runtime-replaced coordinator has no live controller, so its surviving children cannot be stopped through that coordinator ID. Inspect those children separately in the owning native session. An explicit `/fusion stop` or RPC `cancel` retries delivery to the same recorded ID if ownership or availability has changed. An accepted acknowledgement is persisted as `cancellationDelivery: { runId, state: "delivered" }`, so normal polls and restarts do not repeat it. A lost reply can still require another stop request. This receipt never proves process exit or releases quarantine; `terminal` and `cancelled` remain false. A still-pending spawn that returns its exact ID to the same live runtime can bind that ID and deliver cancellation. A disposed runtime records a late matching ID as evidence only: it keeps quarantine and pending cancellation, and cannot restart polling or issue control commands. The live owner's cancellation polling consumes that binding. RPC run state and `/fusion status` distinguish `pending`, `delivered`, and `undeliverable` delivery. None releases quarantine or proves child exit. A proven RPC rejection before the executor runs can fail normally without holding admission.

There is no automatic release/reset for quarantined runs in this version. Inspect the recorded native IDs and lifecycle artifacts with the owning session; preserve the Fusion snapshots and request identity for recovery. Do not delete snapshots or launch replacement workers to clear the warning. Safe reattachment/release needs verified ownership and terminal evidence; blindly repeating the same upstream workflow can retry failed children or missing journal entries. Updating packages alone does not establish this evidence.

### Explicit execution lifetime

The `start_fusion_review` tool and `fusion:rpc:v1` `start` method accept
`executionLifetime: { "mode": "unbounded" }` or
`{ "mode": "bounded", "timeoutMs": 120000 }`. The setting applies to the panel
workflow, each panelist, the judge workflow, and its child. Unbounded execution
omits elapsed deadlines throughout that chain. Omitting `executionLifetime`
preserves the profile and legacy timeout rules above. Do not combine it with
per-stage timeout overrides.

Explicit mode sends panel tasks as a native `ownedWorkflow` parallel data graph
and launches the judge directly under kernel ownership. It does not send
arbitrary workflow JavaScript. The native runtime must advertise both
`single-async` and `parallel-data` ownership routes with `requestMode: "kernel"`.
Profiles with `stopWhenPanelAgrees: true` are rejected before admission because
this owned route does not yet implement that stopping policy. Choose a profile
without agreement stopping; normal lookup and cancellation remain available.

Before using this mode, check RPC `ping` capabilities. Fusion advertises
`executionLifetime` version 1 only when the connected `pi-subagents` runtime
also supports durable operation lookup, idempotent replay, cancellation fences,
and workflow process-tree closure evidence. An incompatible runtime fails
preflight before launch. Successful native admission is reported as
`effectiveExecutionLifetime`; missing or conflicting confirmation remains
unresolved.

Explicit execution also requires `processTreeOwnership` version 1 with
`scope: "owned-process-tree"` and `escapedDescendants: "contained"`. A runtime
that proves only an empty POSIX process group does not satisfy this requirement:
an escaped descendant can still be alive. Fusion forwards the native ownership
capability unchanged and refuses that runtime before explicit dispatch.

Reuse the same `operationId` and parameters when an RPC reply is lost. A changed
request under the same identity is rejected. Cancellation is durable, but its
receipt is not completion: `cancelled: false` with `cancellationRequested: true`
means cleanup is still pending. A terminal `workflowTerminalProof` contains
closed dispatch and recursively verified native child proofs. Status also
retains the last native observation for phase and activity diagnosis.

Cancellation before native dispatch has a separate durable outcome. Fusion
atomically arbitrates launch admission against cancellation; only a cancellation
that wins before dispatch returns `state: "cancelled"`, `neverStarted: true`,
and the matching `operationId`. The same receipt is available from `status`
after restart, and delayed starts remain fenced. A claimed or admitted operation
without this evidence is not proof that no child exists.

When no run, intent, admission, or cancellation exists, `status` returns
`{ operationId, state: "absent", replaySafe: true }`. This authorizes replay of
the exact original request under the same immutable identity; a timeout or
generic lookup failure does not. A caller may send its frozen request hash as
start parameter `digest`. Fusion echoes it unchanged as `requestDigest` and
reports its own canonical hash separately as `fusionRequestDigest`.

Code-review callers can supply `cwd` and `reviewedCommit` together. `cwd` must be
absolute and `reviewedCommit` must be a full Git commit hash. Fusion verifies the
candidate checkout's HEAD before admission, later stages, replay, and acceptance.
The frozen context is returned as `reviewContext` and survives restart. Panel
and judge launches use that candidate directory, while operation lookup and
cancellation retain the original Pi session's journal namespace. Changing the
context under an existing operation ID is rejected. The native launch digest
includes the directory and reviewed commit, in addition to the launch parameters.

### Soft deadline decisions

For a six-member panel with concurrency four, a bounded review budget can be:

```json
{
  "panelistSoftTimeoutMs": 600000,
  "panelistTimeoutMs": 960000,
  "panelTimeoutMs": 2100000,
  "panelGraceMs": 30000,
  "judgeTimeoutMs": 600000
}
```

After ten minutes of actual child runtime, Fusion asks the parent agent to decide
and requests a progress update from that panelist. The parent may ask the user,
then call `resolve_fusion_deadline` with the Fusion `runId`, one-based `panelist`
number, and `decision: "continue"` or `"finish"`. Users can use the corresponding
`/fusion continue` or `/fusion finish` command. The judge is not involved.

A decision is accepted only for the same live child in a pending request. No
reply within one minute requests finalization. A continuation uses the already
reserved budget: in this example, investigation ends at minute 15 and the hard
stop remains minute 16. There is no second extension. Decisions survive reload;
expired requests and completed/replaced children cannot receive a new grant.

Guidance is sent through non-recovering `steer` RPC. Receipts are not proof of
model compliance. Missing routes are visible in `/fusion status`; Fusion does
not revive a child, reset a timer, or retry inference. A blocked provider or tool
may not produce a final answer before the hard stop. Verified completed answers
remain available for partial reporting.

At a terminal workflow deadline, incomplete lifecycle snapshots get up to five
seconds to settle. A late child error keeps its actual cause. After that window,
slots still absent with unambiguous identities become deadline failures. Duplicate
or conflicting identities still fail closed. Incomplete *successful* workflows
are not silently repaired into successes.

Panel member:

- `id`: stable machine name
- `label`: optional report label. It defaults to `id`
- `agent`: subagent name. This is where a member's tool access comes from — see [Panel agents and tools](#panel-agents-and-tools).
- `model`: optional model override, and usually the main source of panel diversity. It accepts normal Pi model ids. If `pi-claude-alias` is installed, it also accepts Claude alias shorthand like `claude-work/opus-4.8`
- A Claude alias handle must be unique across the global and project alias files. Fusion rejects a duplicate handle.
- `thinking`: optional `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, or `max`
- `role`: optional perspective hint layered on top of the model
- `question`: optional facet prompt sent **instead of** the raw task. `{task}` is substituted with the original prompt. Use with `synthesis: "merge"` to divide the work rather than duplicate it. If the template omits `{task}`, the original task is still appended so the panelist keeps its context.

## Panel agents and tools

A panel member's tools come from its **agent definition**, not from `fusion.json`.
`pi-subagents` has no per-task tool override, so `agent` is the knob.

Fusion ships five:

| Agent                            | Tools                                                        | Use for                                                        |
| -------------------------------- | ------------------------------------------------------------ | -------------------------------------------------------------- |
| `pi-fusion.fusion-panelist` | `read, grep, find, ls` | Default. Local inspection only. It needs no extra extension. |
| `pi-fusion.fusion-panelist-web` | above plus `web_search, web_contents, web_answer` | Opt-in. **Requires `pi-web-providers`.** |
| `pi-fusion.fusion-panelist-full` | above plus `bash, edit, write, web_research` | Opt-in. Requires `pi-web-providers`. **See the warning below.** |
| `pi-fusion.fusion-judge` | `read, grep, find, ls` | Judge. The read tools let it verify contested claims. |
| `pi-fusion.fusion-composer` | `read, grep, find, ls` | Synthesis under `synthesis: "merge"`. |

> **Tool names are a strict allowlist, not a loader.** If an agent declares a tool
> whose provider extension is not installed, every task using that agent fails
> with `requested unavailable child tools`. That is why the default agents stay on
> Pi core tools: `pi-web-providers` is optional, so depending on it by default
> breaks every run for anyone without it. Install it before you use the `-web`
> or `-full` variant:
>
> ```bash
> pi install npm:pi-web-providers
> ```

Mix them per member:

```json
{
  "panel": [
    {
      "id": "arch",
      "label": "Architect",
      "agent": "pi-fusion.fusion-panelist"
    },
    {
      "id": "local",
      "label": "Local",
      "agent": "pi-fusion.fusion-panelist-web"
    },
    { "id": "mine", "label": "Custom", "agent": "my-package.my-panelist" }
  ]
}
```

For any other combination, write your own agent markdown file with the `tools:`
frontmatter you want and point `agent` at it. Valid tool names are the Pi core
tools `read, bash, edit, write, grep, find, ls` and, when `pi-web-providers` is
installed, `web_search, web_contents, web_answer, web_research`. A name outside
that set resolves to nothing and the run fails with a missing-tool error.

`web_research` is excluded from the default panelist on purpose: it routes to a
deep-research model that takes minutes. A panel runs it for every member at the
same time.

> **`fusion-panelist-full` voids the read-only guarantee.** It grants `edit`,
> `write`, and `bash`. Fusion runs panelists **in parallel in the same working
> directory**, so at `concurrency > 1` two members can mutate the same files or
> git state at once. The prompt asks them not to, but nothing enforces this.
> Use it with `"concurrency": 1`, or do not use it at all.

## Synthesis modes

`select` (default) — every panelist answers the whole question and
`fusion-judge` compares them, reporting consensus, disagreements, contested
claims, unique insights, and blind spots. Use it for decision questions, where
the failure mode is one model's bad reasoning path and redundancy is the fix.

`merge` — panelists answer **different facets** and `fusion-composer` unions
them, reporting a coverage map, the combined answer, gaps, and conflicts only
where facets genuinely overlap. **To get this, give the members a `question`.**
You do not also have to set `synthesis`. Use merge for breadth questions such as
audits, "what did we miss", and release sweeps. There the failure mode is
incomplete coverage, and redundant panelists all miss the same things.

Merge mode swaps the synthesis agent, not the run lifecycle. It reuses the judge
run slot, so a `fusion:rpc:v1` consumer sees no new phase.

**If you set a custom `judge.agent`, Fusion uses it in both modes.** The merge
contract then becomes your responsibility. Fusion substitutes `fusion-composer`
only when `judge.agent` is still the bundled `pi-fusion.fusion-judge`. Explicit
config always wins.

Under `synthesis: "merge"` your agent gets the composer instructions. It must
emit `Coverage Map`, `Combined Answer`, `Gaps`, and `Conflicts At Seams`. For a
section it does not produce, Fusion writes "Not specified by the composer". The
run does not fail, so a mismatch appears as an empty report, not as an error.

```json
{
  "defaultProfile": "audit",
  "profiles": {
    "audit": {
      "panel": [
        {
          "id": "security",
          "agent": "pi-fusion.fusion-panelist",
          "question": "Cover ONLY the security and data-exposure surface of: {task}"
        },
        {
          "id": "perf",
          "agent": "pi-fusion.fusion-panelist",
          "question": "Cover ONLY throughput, latency, and resource use of: {task}"
        },
        {
          "id": "ops",
          "agent": "pi-fusion.fusion-panelist",
          "question": "Cover ONLY rollout, rollback, and observability of: {task}"
        }
      ],
      "judge": { "agent": "pi-fusion.fusion-judge", "thinking": "high" },
      "concurrency": 3
    }
  }
}
```

Under `merge`, surviving outputs at quorum go to the composer even when some facets are unavailable. The composer must name uncovered facets rather than presenting complete coverage. Below quorum Fusion posts an explicitly partial coverage report; it never presents one facet as the full answer.

Judge:

- `agent`: judge subagent name
- `model`: optional model override
- `thinking`: optional thinking level

## Example profiles

Fast and cheap:

```json
{
  "defaultProfile": "fast",
  "profiles": {
    "fast": {
      "panel": [
        {
          "id": "reviewer",
          "label": "Reviewer",
          "agent": "pi-fusion.fusion-panelist",
          "thinking": "low",
          "role": "practical risks and next step"
        }
      ],
      "judge": {
        "agent": "pi-fusion.fusion-judge",
        "thinking": "low"
      },
      "concurrency": 1,
      "timeoutMs": 120000,
      "context": "fresh"
    }
  }
}
```

Deliberate review:

```json
{
  "defaultProfile": "quality",
  "profiles": {
    "quality": {
      "panel": [
        {
          "id": "architect",
          "label": "Architect",
          "agent": "pi-fusion.fusion-panelist",
          "model": "claude-work/sonnet-4.6",
          "thinking": "high",
          "role": "architecture and failure modes"
        },
        {
          "id": "tester",
          "label": "Tester",
          "agent": "pi-fusion.fusion-panelist",
          "model": "openai/gpt-5.5",
          "thinking": "medium",
          "role": "tests, regressions, and observability"
        }
      ],
      "judge": {
        "agent": "pi-fusion.fusion-judge",
        "model": "claude-work/sonnet-4.6",
        "thinking": "high"
      },
      "concurrency": 2,
      "panelTimeoutMs": 900000,
      "judgeTimeoutMs": 900000,
      "context": "fresh"
    }
  }
}
```

## Output

When agreement stopping is on, each panelist appends one tagged JSON decision record. The record holds a short recommendation, a confidence level, and whether the panelist needs more evidence. Fusion uses it only to decide whether an unfinished panel can stop early. A record that is malformed, missing, or not final turns early stopping off. You see the Markdown answer above the record, not the record itself.

An original task can define the strict plan-review contract: exactly `NO_FINDINGS`, or complete `FINDING`/`Evidence`/`Fix` blocks with no other prose. That caller contract overrides Fusion's normal report headings and agreement record. Fusion validates the synthesis, returns it unchanged, and fails rather than fabricating a clean result when the judge violates the contract. RPC `result` also exposes a validated value as `callerOutput`.

The judge returns:

- summary
- agent status
- consensus
- disagreements
- unique insights
- blind spots
- recommendation
- risks
- next step

When lifecycle data is available, the report gives more. It adds per-panel and judge time, total model time, token usage, and estimated cost. It also adds a short failure summary per model and provider. Total model time is the sum of the agent durations. It is not wall-clock latency, because panelists overlap. Missing usage is shown as unknown, and local zero-cost usage stays zero. `Model` comes from lifecycle metadata. `Configured model` is what the profile asked for. Both appear when a provider reports a different model.

## Status and footer integration

`pi-fusion` uses only the Pi status key `fusion` while a run is active.

It does not own the footer. If you use a footer extension, configure it to read the `fusion` status key.

## Data sharing and provider use

Fusion uses model providers the same way normal Pi work does. The difference is fan-out:

- normal work usually sends a prompt and tool results to one model
- Fusion sends the prompt to every panel model
- a local file snippet that a panelist reads goes to the model of that panelist
- the judge gets the original prompt, the successful panel answers, and the failure summaries

**Panelists can reach the web, but only if you opt in.** The default panelist,
judge, and composer are local-only. A member that uses `fusion-panelist-web` or
`fusion-panelist-full` can search. Your prompt, and any query that the member
derives from your code, then go to the provider set in
`~/.pi/agent/web-providers.json`. That provider is a third party, separate from
your model provider. Keep every member on the default agent to hold a run
entirely off the web.

This is not an extra privacy guarantee. A panel with several providers sends copies of the work to each of them. An all-local panel keeps those model calls local, if your Pi model config allows it. Every bundled Fusion agent except `fusion-panelist-full` is read-only. Each provider still gets the context it needs to answer. `fusion-panelist-full` is not read-only. See [Panel agents and tools](#panel-agents-and-tools).

Fusion does not currently inspect or rewrite the final provider payload. Configure provider privacy and local-model routing in Pi.

## Small and economical profiles

These are config examples, not built-in provider presets. Omit `model` to inherit the model that Pi has selected. You can also set any model id that your Pi `models.json` config supports.

Small local-style panel:

```json
{
  "defaultProfile": "small",
  "profiles": {
    "small": {
      "panel": [
        {
          "id": "reviewer",
          "label": "Reviewer",
          "agent": "pi-fusion.fusion-panelist",
          "thinking": "low",
          "role": "practical risks and next step"
        },
        {
          "id": "tester",
          "label": "Tester",
          "agent": "pi-fusion.fusion-panelist",
          "thinking": "low",
          "role": "edge cases and verification"
        }
      ],
      "judge": { "agent": "pi-fusion.fusion-judge", "thinking": "low" },
      "concurrency": 2,
      "timeoutMs": 120000,
      "context": "fresh"
    }
  }
}
```

For an economical mixed panel, give each member a fast or inexpensive frontier, Chinese-lab, or local Ollama/LM Studio/vLLM model ID. Keep the profile composition small instead of adding provider-specific code to Fusion.

## Troubleshooting

`pi-subagents RPC is unavailable`

- install or update `pi-subagents` to the checked 0.76.1 release
- restart Pi
- retry `/fusion status`

`RPC spawn workflowScript was removed; pass inline script text as script.`

- update Fusion to a version using the `script` RPC field
- restart Pi after upstream package changes
- inspect `/fusion status` before retrying; never repeat an unresolved launch

`Fusion recovery required`

- inspect the reason, native IDs, and pending cancellation with `/fusion status`
- follow [Recovery required](#recovery-required); a new same-prompt run is not a recovery operation

`Unknown fusion profile`

- verify `defaultProfile`
- verify the requested `--profile` name
- run `/fusion init` to regenerate a known-good template

`Workflow script timed out` or judge exit 143

- raise `panelTimeoutMs` or `judgeTimeoutMs` for slower models
- keep `panelToolBudget` and `judgeToolBudget` bounded so agents finalise before the deadline
- inspect `/fusion status`; panel failures and the workflow timeout must both be present
- a timeout ends that child/run attempt; Fusion does not retry panelists, restart the panel, or extend deadlines
- failed-only retry is deliberately not exposed yet: terminal failure state persists the failed panel slot indices for recovery/provenance, but `/fusion` starts a new independent run rather than replaying only those slots
- retry manually only after the run is terminal

Run is stuck or no longer useful:

```text
/fusion stop
```

Need the run IDs:

```text
/fusion status
```

Notes:

- `Panel run` is the normal panel phase for new Fusion runs.
- `Judge run` is the normal synthesis phase for new runs. `Fallback judge run` appears only while restoring a legacy chain that completed without its judge result.
- If `pi-subagents` completion notifications are delayed or missed, Fusion still reconciles from lifecycle artifacts written under the subagent async run directory. The full result artifact is preferred over compact completion events.
