# 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 new panels through `pi-subagents` `workflowScript`; the panel and judge remain separate durable runs. At start, Fusion persists a small resolved profile snapshot (panel labels/models/roles, quorum, synthesis and judge settings) and uses it after restart, so later config edits cannot change an active run's reconciliation, report, or synthesis spawn. Older runs created before this snapshot, including single-chain runs, retain the legacy config-lookup restore fallback. Before each public RPC spawn, Fusion persists a spawn intent. If Pi crashes after that RPC might have started but before its remote run ID is saved, restore fails that local run with an explicit recovery warning instead of spawning a possible duplicate (public RPC cannot safely adopt by correlation key). A corrupt newest run snapshot is likewise refused rather than reviving an older active run.

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`, and `xhigh`.
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`.

## 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
- `concurrency`: max parallel panelists. Ordinary panels immediately start the next queued member when a slot finishes or fails, while retaining configured result order. 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 multiplied by `ceil(panel size / concurrency)`. 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), `"all"`, or a positive number. 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.
- `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.

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.

### 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`, or `xhigh`
- `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 0.43.0 or later
- reload Pi
- retry `/fusion status`

`RPC spawn no longer accepts top-level chain or parallel inputs; use workflowScript.`

- update `pi-fusion` to 0.6.1 or later
- reload Pi so it loads the updated extension
- retry `/fusion`; do not change your Fusion profile

`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.
