@alexeiled/pi-fusion 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -95,7 +95,8 @@ A panel helps because:
95
95
  - a judge can select or synthesize the best realistic answer from the set
96
96
 
97
97
  The result is slower, but usually better for design choices, risk review,
98
- tricky debugging, and research-heavy questions.
98
+ tricky debugging, and research-heavy questions. It is not intended for routine
99
+ edits, formatting, or obvious one-step fixes.
99
100
 
100
101
  ## What the judge actually does
101
102
 
@@ -170,7 +171,10 @@ For commands, config, and troubleshooting details, see [`docs/user-guide.md`](./
170
171
  - Output appears as a Pi custom message. Active progress also uses the `fusion` status key.
171
172
  - Active runs are reconciled from `pi-subagents` lifecycle artifacts, not only completion events.
172
173
  - `pi-fusion` does not own the footer.
173
- - Prompts and inspected snippets may be sent to your configured model providers through `pi-subagents`.
174
+ - Prompts and inspected snippets may be sent to every configured panel provider and to the judge through `pi-subagents`.
175
+ - Reports include available per-panel and judge time, aggregate model time, usage, estimated cost, and model failure details. Missing provider usage is shown as unknown; `$0.0000` remains a known zero-cost value.
176
+ - `Model` is lifecycle metadata. `Configured model` is the profile request; both appear when execution differs from the request.
177
+ - `stopWhenPanelAgrees` is an opt-in profile setting. It requires matching high-confidence decision records with no request for more evidence, stops only unfinished panelists, and still runs the judge.
174
178
 
175
179
  ## Read more
176
180
 
@@ -15,7 +15,13 @@ You are a pi-fusion panelist.
15
15
  Work independently. Inspect relevant local files when the task needs code evidence.
16
16
  Do not edit files. Do not ask other agents. Do not run subagents.
17
17
 
18
- Return concise Markdown with these sections:
18
+ Return concise Markdown with these sections.
19
+
20
+ When the task includes a decision-record contract:
21
+
22
+ - Append exactly one `<fusion-panel-decision>{...}</fusion-panel-decision>` JSON record as the final line.
23
+ - Do not write any text after it.
24
+ - Fusion uses the record only for early-stop orchestration; users receive the preceding Markdown answer.
19
25
 
20
26
  ## Summary
21
27
 
@@ -10,7 +10,7 @@ README covers the why. This guide covers commands, config, and troubleshooting.
10
10
  prompt → parallel panel → judge synthesis → final report
11
11
  ```
12
12
 
13
- Normal execution is a single `pi-subagents` async chain. If that chain completes without a judge result but at least two panelists still produced usable answers, `pi-fusion` runs one fallback judge pass instead of losing the review.
13
+ Fusion keeps the command simple: one prompt starts the panel, then the judge turns the collected evidence into a human-readable Markdown report. Older runs created as a single `pi-subagents` chain remain supported when restored.
14
14
 
15
15
  Panel diversity can come from different model choices, different perspective prompts, or both. In practice, mixing models is usually the main lever.
16
16
 
@@ -34,7 +34,7 @@ Notes:
34
34
 
35
35
  - Bare `/fusion` shows a short help message.
36
36
  - `/fusion status` shows the active run, last run, warnings, and subagent run IDs.
37
- - `/fusion stop` stops the active chain or fallback judge run.
37
+ - `/fusion stop` stops the active panel, legacy chain, or judge run.
38
38
  - `/fusion init` writes `.pi/fusion.json` for the current trusted project.
39
39
  - Exact one-word prompts `init`, `status`, and `stop` are reserved as `/fusion` subcommands.
40
40
 
@@ -84,7 +84,8 @@ Run this inside a trusted project:
84
84
  },
85
85
  "concurrency": 3,
86
86
  "timeoutMs": 300000,
87
- "context": "fresh"
87
+ "context": "fresh",
88
+ "stopWhenPanelAgrees": false
88
89
  }
89
90
  }
90
91
  }
@@ -104,6 +105,7 @@ Profile:
104
105
  - `concurrency`: max parallel panelists
105
106
  - `timeoutMs`: async subagent timeout in milliseconds
106
107
  - `context`: `fresh` or `fork`
108
+ - `stopWhenPanelAgrees`: optional boolean, default `false`. When enabled, Fusion may stop unfinished panelists only when at least two completed panelists have the same normalized recommendation, every successful panelist reports `high` confidence, none requests more evidence, and work remains. The judge still runs over the collected answers. The policy is intentionally fixed; there are no agreement threshold knobs.
107
109
 
108
110
  Panel member:
109
111
 
@@ -191,14 +193,7 @@ Deliberate review:
191
193
 
192
194
  ## Output
193
195
 
194
- Panelists return:
195
-
196
- - summary
197
- - recommendation
198
- - evidence
199
- - risks
200
- - confidence
201
- - open questions
196
+ When agreement stopping is enabled, panelists append a final tagged JSON decision record containing a short recommendation, confidence, and whether more evidence is needed. Fusion uses it only to decide whether an unfinished panel may stop early; malformed, missing, or non-final records disable early stopping. Users see the preceding human-readable Markdown answer, not the record.
202
197
 
203
198
  The judge returns:
204
199
 
@@ -212,23 +207,64 @@ The judge returns:
212
207
  - risks
213
208
  - next step
214
209
 
210
+ When lifecycle data is available, the final report also includes per-panel and judge time, aggregate model time, token usage, estimated cost, and concise model/provider failure summaries. Aggregate model time sums agent durations and is not wall-clock latency when panelists overlap. Missing usage is shown as unknown; local zero-cost usage remains zero. `Model` comes from lifecycle metadata; `Configured model` is the profile request. Both appear when a provider reports a different executed model.
211
+
215
212
  ## Status and footer integration
216
213
 
217
214
  `pi-fusion` uses only the Pi status key `fusion` while a run is active.
218
215
 
219
216
  It does not own the footer. If you use a footer extension, configure it to read the `fusion` status key.
220
217
 
221
- ## Privacy and provider use
218
+ ## Data sharing and provider use
219
+
220
+ Fusion uses model providers the same way normal Pi work does. The difference is fan-out:
221
+
222
+ - normal work usually sends a prompt and tool results to one selected model;
223
+ - Fusion sends the prompt to every configured panel model;
224
+ - local file snippets read by a panelist go to that panelist's model;
225
+ - the judge receives the original prompt plus successful panel answers and failure summaries.
226
+
227
+ This is not an extra privacy guarantee. A mixed-provider panel can send copies of the work to several providers. An all-local panel can keep those model calls local, depending on your Pi model configuration. Bundled Fusion agents are read-only, but providers still receive the context needed to answer.
222
228
 
223
- Fusion sends work to `pi-subagents`. Those subagents use your configured Pi model providers.
229
+ Fusion does not currently inspect or rewrite the final provider payload. Configure provider privacy and local-model routing in Pi.
224
230
 
225
- Data that may leave your machine:
231
+ ## Small and economical profiles
226
232
 
227
- - the prompt you pass to `/fusion`
228
- - relevant local file snippets read by panelists or the judge
229
- - panel outputs sent to the judge
233
+ These are configuration examples, not built-in provider presets. Omit `model` to inherit the model selected in Pi, or set any model IDs supported by your Pi `models.json` configuration.
234
+
235
+ Small local-style panel:
236
+
237
+ ```json
238
+ {
239
+ "defaultProfile": "small",
240
+ "profiles": {
241
+ "small": {
242
+ "panel": [
243
+ {
244
+ "id": "reviewer",
245
+ "label": "Reviewer",
246
+ "agent": "pi-fusion.fusion-panelist",
247
+ "thinking": "low",
248
+ "role": "practical risks and next step"
249
+ },
250
+ {
251
+ "id": "tester",
252
+ "label": "Tester",
253
+ "agent": "pi-fusion.fusion-panelist",
254
+ "thinking": "low",
255
+ "role": "edge cases and verification"
256
+ }
257
+ ],
258
+ "judge": { "agent": "pi-fusion.fusion-judge", "thinking": "low" },
259
+ "concurrency": 2,
260
+ "timeoutMs": 120000,
261
+ "context": "fresh"
262
+ }
263
+ }
264
+ }
265
+ ```
230
266
 
231
- Default bundled agents are read-only, but model providers still receive the context needed to answer.
267
+ 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.
232
268
 
233
269
  ## Troubleshooting
234
270
 
@@ -258,6 +294,6 @@ Need the run IDs:
258
294
 
259
295
  Notes:
260
296
 
261
- - `Chain run` is the normal end-to-end Fusion run.
262
- - `Fallback judge run` appears only when Fusion had enough panel output to recover a missing judge result.
297
+ - `Panel run` is the normal panel phase for new Fusion runs.
298
+ - `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.
263
299
  - If `pi-subagents` completion notifications are delayed or missed, Fusion still reconciles from lifecycle artifacts written under the subagent async run directory.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alexeiled/pi-fusion",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Stronger answers for hard Pi questions via a parallel model panel + judge, built on pi-subagents",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -13,6 +13,10 @@ Use the `start_fusion_review` tool to launch a fusion panel without the user nee
13
13
 
14
14
  ## When to use
15
15
 
16
+ Use Fusion for hard decisions, design tradeoffs, risk review, tricky debugging, or questions where independent model perspectives are useful.
17
+
18
+ Do not use Fusion for routine edits, formatting, obvious one-step fixes, or simple questions. Keep those on the normal Pi path.
19
+
16
20
  - "invoke fusion panel to discuss this"
17
21
  - "run fusion on this"
18
22
  - "get a panel review of …"
package/src/config.ts CHANGED
@@ -79,6 +79,7 @@ export function createDefaultFusionConfig(): FusionConfig {
79
79
  concurrency: 3,
80
80
  timeoutMs: 300_000,
81
81
  context: "fresh",
82
+ stopWhenPanelAgrees: false,
82
83
  },
83
84
  },
84
85
  };
@@ -202,6 +203,12 @@ function isFusionProfile(value: unknown): value is FusionProfile {
202
203
  return false;
203
204
  if (value.context !== undefined && !isFusionContextMode(value.context))
204
205
  return false;
206
+ if (
207
+ value.stopWhenPanelAgrees !== undefined &&
208
+ typeof value.stopWhenPanelAgrees !== "boolean"
209
+ ) {
210
+ return false;
211
+ }
205
212
  return true;
206
213
  }
207
214
 
package/src/index.ts CHANGED
@@ -8,15 +8,18 @@ import {
8
8
  import { FusionRunStore } from "./run-store.js";
9
9
  import { SubagentsRpcClient } from "./subagents-rpc.js";
10
10
 
11
- function registerFusionTool(pi: ExtensionAPI): void {
11
+ function registerFusionTool(
12
+ pi: ExtensionAPI,
13
+ orchestrator: FusionOrchestrator,
14
+ ): void {
12
15
  pi.registerTool({
13
16
  name: "start_fusion_review",
14
17
  label: "Fusion Review",
15
18
  description:
16
- "Start a pi-fusion multi-model panel review. Use when the user asks to invoke fusion, run a panel review, get multi-model opinions, or discuss something through the fusion panel.",
19
+ "Start a pi-fusion multi-model panel review for a hard decision, design tradeoff, risk review, tricky debugging question, or research-heavy topic. Do not use for routine edits, formatting, or obvious one-step fixes.",
17
20
  promptSnippet: "Start a fusion panel review for a topic or code",
18
21
  promptGuidelines: [
19
- "Use start_fusion_review when the user says 'invoke fusion', 'run fusion', 'fusion panel', 'multi-model review', 'panel review', or similar.",
22
+ "Use start_fusion_review only for hard decisions, design tradeoffs, risk review, tricky debugging, or research-heavy questions. Do not use it for routine edits, formatting, or obvious one-step fixes.",
20
23
  ],
21
24
  parameters: Type.Object({
22
25
  prompt: Type.String({ description: "What to review or discuss" }),
@@ -24,15 +27,28 @@ function registerFusionTool(pi: ExtensionAPI): void {
24
27
  Type.String({ description: "Fusion profile name (optional)" }),
25
28
  ),
26
29
  }),
27
- execute(_toolCallId, params) {
28
- const cmd = params.profile
29
- ? `/fusion --profile ${params.profile} ${params.prompt}`
30
- : `/fusion ${params.prompt}`;
31
- pi.sendUserMessage(cmd, { deliverAs: "followUp" });
32
- return Promise.resolve({
33
- content: [{ type: "text", text: "Starting fusion panel review…" }],
34
- details: { prompt: params.prompt, profile: params.profile },
35
- });
30
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
31
+ const result = await orchestrator.startRun(
32
+ {
33
+ prompt: params.prompt,
34
+ ...(params.profile ? { profile: params.profile } : {}),
35
+ },
36
+ ctx,
37
+ );
38
+ const text =
39
+ result.status === "started"
40
+ ? "Fusion panel review started. The report will be posted when the panel and judge finish."
41
+ : result.status === "conflict"
42
+ ? `A fusion run is already active (${result.activeRunId}). Do not start another; wait for its report.`
43
+ : `Fusion review failed to start: ${result.status === "failed" ? result.error : result.status}`;
44
+ return {
45
+ content: [{ type: "text", text }],
46
+ details: {
47
+ prompt: params.prompt,
48
+ profile: params.profile,
49
+ status: result.status,
50
+ },
51
+ };
36
52
  },
37
53
  });
38
54
  }
@@ -45,7 +61,7 @@ export default function fusionExtension(pi: ExtensionAPI): void {
45
61
  });
46
62
 
47
63
  registerFusionCommands(pi, orchestrator);
48
- registerFusionTool(pi);
64
+ registerFusionTool(pi, orchestrator);
49
65
 
50
66
  const unsubscribeComplete = pi.events.on(
51
67
  SUBAGENT_ASYNC_COMPLETE_EVENT,