shariq-pi-extensions 0.6.0 → 0.7.1
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 +2 -2
- package/docs/EXTENSIONS.md +1 -1
- package/extensions/subagents/README.md +32 -1
- package/extensions/subagents/index.ts +55 -9
- package/extensions/subagents/src/backends/pi.ts +2 -1
- package/extensions/subagents/src/child-bridge.ts +2 -0
- package/extensions/subagents/src/config.ts +82 -13
- package/extensions/subagents/src/fusion.ts +96 -0
- package/package.json +2 -1
- package/skills/fusion/SKILL.md +62 -0
- package/skills/subagents/SKILL.md +3 -3
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ Run `/reload` in an existing Pi session after installation; new sessions load th
|
|
|
23
23
|
- structured user questions (`ask_user`)
|
|
24
24
|
- persistent goals with evidence checklists and budgets
|
|
25
25
|
- a task list for multi-deliverable work, with a live progress dock
|
|
26
|
-
- Pi subagents with profiles, worktree isolation,
|
|
26
|
+
- Pi subagents with profiles, worktree isolation, a takeover dashboard, and an optional Fusion mode (lead plus persistent sidekick across light, medium, and heavy model tiers)
|
|
27
27
|
- Smart Compaction: structured checkpoints with file and worktree state
|
|
28
28
|
- managed background terminals
|
|
29
29
|
- Firecrawl web search, scraping, and developer search
|
|
@@ -33,7 +33,7 @@ Run `/reload` in an existing Pi session after installation; new sessions load th
|
|
|
33
33
|
- configurable steer, interrupt, or follow-up Enter behavior
|
|
34
34
|
- `/exit` as an alias for `/quit`
|
|
35
35
|
|
|
36
|
-
The package also ships the `ember-warm-dark` theme and the `background-terminals` and `subagents` skills. Pi loads the skills from the installed package; copying them into `<agent-dir>/skills` would leave stale duplicates after removal.
|
|
36
|
+
The package also ships the `ember-warm-dark` theme and the `background-terminals`, `fusion`, and `subagents` skills. Pi loads the skills from the installed package; copying them into `<agent-dir>/skills` would leave stale duplicates after removal.
|
|
37
37
|
|
|
38
38
|
The suite is developed and tested against Pi 1.0. See [`docs/EXTENSIONS.md`](docs/EXTENSIONS.md) for commands, tools, configuration, and external dependencies.
|
|
39
39
|
|
package/docs/EXTENSIONS.md
CHANGED
|
@@ -18,7 +18,7 @@ Persistent, branch-safe objectives with an evidence checklist, token budgets, pa
|
|
|
18
18
|
|
|
19
19
|
### [Subagents](../extensions/subagents/README.md)
|
|
20
20
|
|
|
21
|
-
Flat Pi child agents with profiles, capability policies, an optional per-profile process runtime that gives children Pi's built-in extensions (codemode, MCP, tool search), review options that drop skills or `AGENTS.md` files, context forks, resumable sessions, optional git worktree isolation, peer messaging, and a takeover dashboard. Children cannot start agents. The model delegates only when the user asks. Results are delivered as custom messages that start a parent turn when the parent is idle. Configuration lives in `<agent-dir>/subagents.json`, with trusted-project overrides in `.pi/subagents.json`.
|
|
21
|
+
Flat Pi child agents with profiles, capability policies, an optional per-profile process runtime that gives children Pi's built-in extensions (codemode, MCP, tool search), review options that drop skills or `AGENTS.md` files, context forks, resumable sessions, optional git worktree isolation, peer messaging, and a takeover dashboard. Children cannot start agents. The model delegates only when the user asks, unless optional Fusion mode is on: then the main session leads, a persistent `sidekick` implements decided work, and light `worker` and `verifier` profiles take routine edits and checks, each on a user-configured model tier. Results are delivered as custom messages that start a parent turn when the parent is idle. Configuration lives in `<agent-dir>/subagents.json`, with trusted-project overrides in `.pi/subagents.json`.
|
|
22
22
|
|
|
23
23
|
### [Smart Compaction](../extensions/smart-compaction/README.md)
|
|
24
24
|
|
|
@@ -89,11 +89,42 @@ User profiles and personas live in `<agent-dir>/subagents.json`. Trusted project
|
|
|
89
89
|
|
|
90
90
|
Project configuration is ignored for untrusted projects. `maxConcurrent` is bounded to 1–50 and defaults to 50. `/subagents profiles` lists profiles, and `/subagents config` opens a validated editor for the global or trusted-project file.
|
|
91
91
|
|
|
92
|
+
## Fusion mode
|
|
93
|
+
|
|
94
|
+
Fusion mode is optional and off by default. When it is on, the main session acts as the lead and delegates without being asked: one persistent `sidekick` per workstream implements decided work, and light profiles take routine edits and checks. The lead keeps intent, planning, architecture, security, and final review. Turn it on with `/subagents fusion on`, or set `"fusion": true` in either configuration file; a trusted project's setting wins over the global one.
|
|
95
|
+
|
|
96
|
+
Fusion adds these profiles and tiers:
|
|
97
|
+
|
|
98
|
+
| Profile | Tier | Capability | Use |
|
|
99
|
+
|---|---|---|---|
|
|
100
|
+
| `sidekick` | medium | all | decided implementation and debugging from a concrete failure |
|
|
101
|
+
| `explore` | medium | execute | substantive read-only tracing |
|
|
102
|
+
| `reviewer` | medium | execute | one fresh review of a final diff |
|
|
103
|
+
| `worker` | light | all | fully specified mechanical edits, docs, narrow tests |
|
|
104
|
+
| `verifier` | light | execute | builds, tests, and reproductions; reports failures only |
|
|
105
|
+
| `general-purpose` | heavy | all | hard decided work after the sidekick is blocked |
|
|
106
|
+
|
|
107
|
+
Each tier maps to a model and thinking level for children; the lead keeps the model you selected in Pi. An unset tier, or an unset field, inherits the parent session's, so with no `tiers` block every child runs on the parent's model:
|
|
108
|
+
|
|
109
|
+
```json
|
|
110
|
+
{
|
|
111
|
+
"fusion": true,
|
|
112
|
+
"tiers": {
|
|
113
|
+
"light": { "model": "provider/fast-model", "thinking": "medium" },
|
|
114
|
+
"medium": { "model": "provider/coding-model", "thinking": "xhigh" }
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Any profile can set `"tier"`, with or without Fusion mode. A profile's own `model` and `thinking` win over its tier, and a call's overrides win over both. Profiles you define with the same names are merged over the Fusion defaults, so you can change one field without restating the rest. `list_agent_profiles` and `/subagents profiles` show the mode, the tiers, and each profile's tier. A child started from a Fusion profile can still be resumed after the mode is turned off.
|
|
120
|
+
|
|
121
|
+
Only the lead sees the Fusion guideline and the `fusion` skill; children keep the default rules and cannot start agents.
|
|
122
|
+
|
|
92
123
|
## Context and continuation
|
|
93
124
|
|
|
94
125
|
New children start without parent context. `fork_turns` can be `all` or a positive number of recent user turns; the fork keeps user messages and final assistant text and drops thinking, tool calls, and tool results.
|
|
95
126
|
|
|
96
|
-
Every child has a persistent Pi session file. `resume_from` continues a finished or cancelled child with its transcript, tool state, and id, including after a parent reload or restart. An archived child resumes with its stored profile, persona, capability, and worktree unless the call overrides them. A child still loaded in this session keeps its settings, so a resume call that passes different ones is rejected. A run interrupted by cancellation or reload is recorded as `cancelled`, not as a failure.
|
|
127
|
+
Every child has a persistent Pi session file. `resume_from` continues a finished or cancelled child with its transcript, tool state, and id, including after a parent reload or restart. An archived child resumes with its stored profile, persona, capability, and worktree unless the call overrides them, and takes its model and thinking level from that profile, its tier, or the call. A child still loaded in this session keeps its settings, so a resume call that passes different ones is rejected. A run interrupted by cancellation or reload is recorded as `cancelled`, not as a failure.
|
|
97
128
|
|
|
98
129
|
A child running in another Pi process cannot be resumed, and its worktree cannot be changed, until that process finishes or exits; a crashed owner is detected and its child becomes resumable.
|
|
99
130
|
|
|
@@ -68,6 +68,7 @@ import {
|
|
|
68
68
|
type IsolationMode,
|
|
69
69
|
} from "./src/config.ts";
|
|
70
70
|
import { buildTaskPrompt, forkConversation, parseForkTurns } from "./src/context.ts";
|
|
71
|
+
import { FUSION_LEAD_GUIDELINE, isFusionSkill } from "./src/fusion.ts";
|
|
71
72
|
import {
|
|
72
73
|
applyAgentWorktree,
|
|
73
74
|
applyAgentWorktreeTo,
|
|
@@ -375,7 +376,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
375
376
|
ctx: ExtensionContext,
|
|
376
377
|
options: SpawnOptions,
|
|
377
378
|
): Promise<PreparedSpawn> => {
|
|
378
|
-
const config = loadSubagentConfig(ctx.cwd, ctx.isProjectTrusted());
|
|
379
|
+
const config = loadSubagentConfig(ctx.cwd, ctx.isProjectTrusted(), { resumable: Boolean(options.resumeFrom) });
|
|
379
380
|
const liveSource = options.resumeFrom ? manager.view.get(options.resumeFrom) : undefined;
|
|
380
381
|
if (liveSource?.status === "running") {
|
|
381
382
|
throw new Error(`Cannot resume running subagent "${options.resumeFrom}"; use send_message instead.`);
|
|
@@ -481,8 +482,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
481
482
|
title,
|
|
482
483
|
origin: options.origin ?? "model",
|
|
483
484
|
cwd,
|
|
484
|
-
model:
|
|
485
|
-
reasoningEffort:
|
|
485
|
+
model: profile.model,
|
|
486
|
+
reasoningEffort: profile.thinking,
|
|
486
487
|
capability: profile.capability,
|
|
487
488
|
childOptions: profile.childOptions,
|
|
488
489
|
agentType: profile.agentType,
|
|
@@ -696,6 +697,20 @@ export default function (pi: ExtensionAPI) {
|
|
|
696
697
|
await closing?.dispose();
|
|
697
698
|
});
|
|
698
699
|
|
|
700
|
+
// Fusion mode swaps the lead's activation rule and reveals the fusion skill.
|
|
701
|
+
// Children never have spawn_agent; they also hide the skill through hideFusionSkill.
|
|
702
|
+
pi.on("before_agent_start", (event, ctx) => {
|
|
703
|
+
const options = event.systemPromptOptions;
|
|
704
|
+
const lead = options.selectedTools.includes("spawn_agent");
|
|
705
|
+
const fusion = lead && loadSubagentConfig(ctx.cwd, ctx.isProjectTrusted()).fusion;
|
|
706
|
+
if (fusion && options.toolGuidelines.spawn_agent) {
|
|
707
|
+
options.toolGuidelines.spawn_agent = options.toolGuidelines.spawn_agent.map((guideline) =>
|
|
708
|
+
guideline === SUBAGENT_SPAWN_PROMPT_GUIDELINES[0] ? FUSION_LEAD_GUIDELINE : guideline,
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
if (!fusion) options.skills = options.skills.filter((skill) => !isFusionSkill(skill.baseDir));
|
|
712
|
+
});
|
|
713
|
+
|
|
699
714
|
// --- Tools -------------------------------------------------------------
|
|
700
715
|
|
|
701
716
|
pi.registerTool({
|
|
@@ -1029,7 +1044,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
1029
1044
|
const config = loadSubagentConfig(ctx.cwd, ctx.isProjectTrusted());
|
|
1030
1045
|
const profileLines = Object.entries(config.profiles).map(
|
|
1031
1046
|
([name, profile]) =>
|
|
1032
|
-
`profile ${name}: ${profile.description ?? "no description"}; capability=${profile.capability ?? "all"}; isolation=${profile.isolation ?? "none"}`,
|
|
1047
|
+
`profile ${name}: ${profile.description ?? "no description"}; capability=${profile.capability ?? "all"}; isolation=${profile.isolation ?? "none"}${profile.tier ? `; tier=${profile.tier}` : ""}`,
|
|
1048
|
+
);
|
|
1049
|
+
const tierLines = Object.entries(config.tiers).map(
|
|
1050
|
+
([tier, setting]) => `tier ${tier}: model=${setting?.model ?? "parent"}; thinking=${setting?.thinking ?? "parent"}`,
|
|
1033
1051
|
);
|
|
1034
1052
|
const personaLines = Object.entries(config.personas).map(
|
|
1035
1053
|
([name, persona]) => `persona ${name}: ${persona.description ?? "no description"}`,
|
|
@@ -1037,9 +1055,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
1037
1055
|
return {
|
|
1038
1056
|
content: [{
|
|
1039
1057
|
type: "text",
|
|
1040
|
-
text: [`maxConcurrent=${config.maxConcurrent}`, ...profileLines, ...personaLines].join("\n"),
|
|
1058
|
+
text: [`maxConcurrent=${config.maxConcurrent}`, `fusion=${config.fusion ? "on" : "off"}`, ...tierLines, ...profileLines, ...personaLines].join("\n"),
|
|
1041
1059
|
}],
|
|
1042
|
-
details: { maxConcurrent: config.maxConcurrent, profiles: config.profiles, personas: config.personas },
|
|
1060
|
+
details: { maxConcurrent: config.maxConcurrent, fusion: config.fusion, tiers: config.tiers, profiles: config.profiles, personas: config.personas },
|
|
1043
1061
|
};
|
|
1044
1062
|
},
|
|
1045
1063
|
renderCall(_args, theme) {
|
|
@@ -1431,9 +1449,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
1431
1449
|
});
|
|
1432
1450
|
|
|
1433
1451
|
pi.registerCommand("subagents", {
|
|
1434
|
-
description: "Inspect agents, peer messages, profiles, or configuration",
|
|
1452
|
+
description: "Inspect agents, peer messages, profiles, or configuration, or switch Fusion mode",
|
|
1435
1453
|
getArgumentCompletions: (prefix) =>
|
|
1436
|
-
["agents", "peers", "profiles", "config"]
|
|
1454
|
+
["agents", "peers", "profiles", "config", "fusion", "fusion on", "fusion off"]
|
|
1437
1455
|
.filter((value) => value.startsWith(prefix.trim()))
|
|
1438
1456
|
.map((value) => ({ value, label: value })),
|
|
1439
1457
|
handler: async (args, ctx) => {
|
|
@@ -1462,6 +1480,33 @@ export default function (pi: ExtensionAPI) {
|
|
|
1462
1480
|
}
|
|
1463
1481
|
return;
|
|
1464
1482
|
}
|
|
1483
|
+
if (action === "fusion" || action.startsWith("fusion ")) {
|
|
1484
|
+
const value = action.slice("fusion".length).trim();
|
|
1485
|
+
const enabled = () => loadSubagentConfig(ctx.cwd, ctx.isProjectTrusted()).fusion;
|
|
1486
|
+
if (!value) {
|
|
1487
|
+
ctx.ui.notify(`Fusion mode is ${enabled() ? "on" : "off"}. Use /subagents fusion on or off.`, "info");
|
|
1488
|
+
return;
|
|
1489
|
+
}
|
|
1490
|
+
if (value !== "on" && value !== "off") {
|
|
1491
|
+
ctx.ui.notify("Use /subagents fusion on or off", "error");
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
try {
|
|
1495
|
+
const document = JSON.parse(loadConfigDocument("global", ctx.cwd)) as Record<string, unknown>;
|
|
1496
|
+
document.fusion = value === "on";
|
|
1497
|
+
saveConfigDocument("global", ctx.cwd, JSON.stringify(document));
|
|
1498
|
+
const overridden = enabled() !== (value === "on");
|
|
1499
|
+
ctx.ui.notify(
|
|
1500
|
+
overridden
|
|
1501
|
+
? `Saved fusion ${value} globally, but this project's .pi/subagents.json overrides it`
|
|
1502
|
+
: `Fusion mode ${value}; it applies from your next message`,
|
|
1503
|
+
overridden ? "warning" : "info",
|
|
1504
|
+
);
|
|
1505
|
+
} catch (error) {
|
|
1506
|
+
ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
|
|
1507
|
+
}
|
|
1508
|
+
return;
|
|
1509
|
+
}
|
|
1465
1510
|
const manager = await getManager();
|
|
1466
1511
|
if (action === "peers") {
|
|
1467
1512
|
await openPeerMessageViewer(ctx, manager.view);
|
|
@@ -1470,6 +1515,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1470
1515
|
if (action === "profiles") {
|
|
1471
1516
|
const config = loadSubagentConfig(ctx.cwd, ctx.isProjectTrusted());
|
|
1472
1517
|
const entries = [
|
|
1518
|
+
{ label: `fusion: ${config.fusion ? "on" : "off"} · tiers`, text: JSON.stringify({ fusion: config.fusion, tiers: config.tiers }, null, 2) },
|
|
1473
1519
|
...Object.entries(config.profiles).map(([name, value]) => ({
|
|
1474
1520
|
label: `profile: ${name}`,
|
|
1475
1521
|
text: JSON.stringify(value, null, 2),
|
|
@@ -1485,7 +1531,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1485
1531
|
return;
|
|
1486
1532
|
}
|
|
1487
1533
|
if (action !== "agents") {
|
|
1488
|
-
ctx.ui.notify('Use /subagents agents, peers, profiles, or
|
|
1534
|
+
ctx.ui.notify('Use /subagents agents, peers, profiles, config, or fusion', "error");
|
|
1489
1535
|
return;
|
|
1490
1536
|
}
|
|
1491
1537
|
if (manager.view.size() === 0) {
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
shutdownAndDisposeChildSession,
|
|
38
38
|
} from "../../../shared/child-session.ts";
|
|
39
39
|
import { createToolCallTimeoutGuard } from "../../../shared/tool-call-timeout.ts";
|
|
40
|
+
import { hideFusionSkill } from "../fusion.ts";
|
|
40
41
|
import { makeProcessSession } from "./pi-process.ts";
|
|
41
42
|
|
|
42
43
|
const CHILD_SHUTDOWN_TIMEOUT_MS = 5_000;
|
|
@@ -457,7 +458,7 @@ const makePiSession = (
|
|
|
457
458
|
const { loader, settingsManager } = await createChildResources({
|
|
458
459
|
cwd: task.cwd,
|
|
459
460
|
projectTrusted: task.parent.projectTrusted,
|
|
460
|
-
extensionFactories: [capabilityGuardExtension(task.capability, task.childOptions?.tools)],
|
|
461
|
+
extensionFactories: [capabilityGuardExtension(task.capability, task.childOptions?.tools), hideFusionSkill],
|
|
461
462
|
noSkills: task.childOptions?.skills === false,
|
|
462
463
|
noContextFiles: task.childOptions?.contextFiles === false,
|
|
463
464
|
noExtensions: task.childOptions?.extensions === false,
|
|
@@ -11,6 +11,7 @@ import { Type } from "typebox";
|
|
|
11
11
|
import { CHILD_EXCLUDED_TOOL_NAMES } from "../../shared/child-session.ts";
|
|
12
12
|
import { childToolAllowed } from "./backends/pi.ts";
|
|
13
13
|
import type { CapabilityMode } from "./config.ts";
|
|
14
|
+
import { hideFusionSkill } from "./fusion.ts";
|
|
14
15
|
|
|
15
16
|
export const CHILD_ENV = "PI_SUBAGENT_CHILD";
|
|
16
17
|
export const CHILD_CAPABILITY_ENV = "PI_SUBAGENT_CAPABILITY";
|
|
@@ -50,6 +51,7 @@ export default function childBridge(pi: ExtensionAPI) {
|
|
|
50
51
|
};
|
|
51
52
|
pi.on("session_start", restrict);
|
|
52
53
|
pi.on("agent_start", restrict);
|
|
54
|
+
hideFusionSkill(pi);
|
|
53
55
|
|
|
54
56
|
if (process.env[CHILD_BRIDGE_ENV] !== "1") return;
|
|
55
57
|
pi.registerTool({
|
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { CONFIG_DIR_NAME, getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
4
|
-
import type
|
|
4
|
+
import { REASONING_EFFORTS, type ReasoningEffort } from "./domain.ts";
|
|
5
|
+
import { FUSION_PROFILES } from "./fusion.ts";
|
|
5
6
|
|
|
6
7
|
export const AGENT_TYPES = ["general-purpose", "explore", "plan"] as const;
|
|
7
8
|
export const CAPABILITY_MODES = ["read-only", "read-write", "execute", "all"] as const;
|
|
8
9
|
export const ISOLATION_MODES = ["none", "worktree"] as const;
|
|
9
10
|
export const RUNTIME_MODES = ["in-process", "process"] as const;
|
|
11
|
+
export const TIERS = ["light", "medium", "heavy"] as const;
|
|
10
12
|
|
|
11
13
|
export type CapabilityMode = (typeof CAPABILITY_MODES)[number];
|
|
12
14
|
export type IsolationMode = (typeof ISOLATION_MODES)[number];
|
|
13
15
|
export type RuntimeMode = (typeof RUNTIME_MODES)[number];
|
|
16
|
+
export type Tier = (typeof TIERS)[number];
|
|
17
|
+
|
|
18
|
+
/** Model and thinking level for one tier; unset fields inherit the parent session's. */
|
|
19
|
+
export interface TierSetting {
|
|
20
|
+
model?: string;
|
|
21
|
+
thinking?: ReasoningEffort;
|
|
22
|
+
}
|
|
14
23
|
|
|
15
24
|
export interface AgentProfile {
|
|
16
25
|
description?: string;
|
|
@@ -18,6 +27,8 @@ export interface AgentProfile {
|
|
|
18
27
|
capability?: CapabilityMode;
|
|
19
28
|
model?: string;
|
|
20
29
|
thinking?: ReasoningEffort;
|
|
30
|
+
/** Take model and thinking from this tier when the profile sets neither. */
|
|
31
|
+
tier?: Tier;
|
|
21
32
|
isolation?: IsolationMode;
|
|
22
33
|
/** "process" runs the child as a separate `pi --mode rpc` process with Pi's built-in extensions. */
|
|
23
34
|
runtime?: RuntimeMode;
|
|
@@ -48,6 +59,9 @@ export interface PersonaProfile {
|
|
|
48
59
|
|
|
49
60
|
export interface SubagentConfig {
|
|
50
61
|
maxConcurrent: number;
|
|
62
|
+
/** Adds the Fusion profiles and lets the main session delegate without being asked. */
|
|
63
|
+
fusion: boolean;
|
|
64
|
+
tiers: Partial<Record<Tier, TierSetting>>;
|
|
51
65
|
profiles: Record<string, AgentProfile>;
|
|
52
66
|
personas: Record<string, PersonaProfile>;
|
|
53
67
|
}
|
|
@@ -71,6 +85,8 @@ const BUILTIN_PROFILES: Record<string, AgentProfile> = {
|
|
|
71
85
|
|
|
72
86
|
const DEFAULT_CONFIG: SubagentConfig = {
|
|
73
87
|
maxConcurrent: 50,
|
|
88
|
+
fusion: false,
|
|
89
|
+
tiers: {},
|
|
74
90
|
profiles: BUILTIN_PROFILES,
|
|
75
91
|
personas: {},
|
|
76
92
|
};
|
|
@@ -108,8 +124,10 @@ function parseProfile(value: unknown): AgentProfile | undefined {
|
|
|
108
124
|
const capability = enumValue(value.capability, CAPABILITY_MODES);
|
|
109
125
|
if (capability) profile.capability = capability;
|
|
110
126
|
if (typeof value.model === "string") profile.model = value.model;
|
|
111
|
-
const thinking = enumValue(value.thinking,
|
|
127
|
+
const thinking = enumValue(value.thinking, REASONING_EFFORTS);
|
|
112
128
|
if (thinking) profile.thinking = thinking;
|
|
129
|
+
const tier = enumValue(value.tier, TIERS);
|
|
130
|
+
if (tier) profile.tier = tier;
|
|
113
131
|
const isolation = enumValue(value.isolation, ISOLATION_MODES);
|
|
114
132
|
if (isolation) profile.isolation = isolation;
|
|
115
133
|
const runtime = enumValue(value.runtime, RUNTIME_MODES);
|
|
@@ -135,6 +153,21 @@ function parsePersona(value: unknown): PersonaProfile | undefined {
|
|
|
135
153
|
};
|
|
136
154
|
}
|
|
137
155
|
|
|
156
|
+
function parseTiers(base: SubagentConfig["tiers"], value: unknown): SubagentConfig["tiers"] {
|
|
157
|
+
if (!isRecord(value)) return base;
|
|
158
|
+
const tiers = { ...base };
|
|
159
|
+
for (const tier of TIERS) {
|
|
160
|
+
const entry = value[tier];
|
|
161
|
+
if (!isRecord(entry)) continue;
|
|
162
|
+
const setting: TierSetting = { ...tiers[tier] };
|
|
163
|
+
if (typeof entry.model === "string" && entry.model.trim()) setting.model = entry.model.trim();
|
|
164
|
+
const thinking = enumValue(entry.thinking, REASONING_EFFORTS);
|
|
165
|
+
if (thinking) setting.thinking = thinking;
|
|
166
|
+
tiers[tier] = setting;
|
|
167
|
+
}
|
|
168
|
+
return tiers;
|
|
169
|
+
}
|
|
170
|
+
|
|
138
171
|
function mergeConfig(base: SubagentConfig, raw: Record<string, unknown> | undefined): SubagentConfig {
|
|
139
172
|
if (!raw) return base;
|
|
140
173
|
const profiles = { ...base.profiles };
|
|
@@ -153,6 +186,8 @@ function mergeConfig(base: SubagentConfig, raw: Record<string, unknown> | undefi
|
|
|
153
186
|
}
|
|
154
187
|
return {
|
|
155
188
|
maxConcurrent: boundedInt(raw.maxConcurrent, base.maxConcurrent, 1, 50),
|
|
189
|
+
fusion: typeof raw.fusion === "boolean" ? raw.fusion : base.fusion,
|
|
190
|
+
tiers: parseTiers(base.tiers, raw.tiers),
|
|
156
191
|
profiles,
|
|
157
192
|
personas,
|
|
158
193
|
};
|
|
@@ -195,13 +230,13 @@ function validateProfileDocument(name: string, value: unknown, persona: boolean)
|
|
|
195
230
|
if (value.model !== undefined && typeof value.model !== "string") {
|
|
196
231
|
throw new Error(`${name}.model must be a string.`);
|
|
197
232
|
}
|
|
198
|
-
if (
|
|
199
|
-
value.thinking !== undefined &&
|
|
200
|
-
!enumValue(value.thinking, ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const)
|
|
201
|
-
) {
|
|
233
|
+
if (value.thinking !== undefined && !enumValue(value.thinking, REASONING_EFFORTS)) {
|
|
202
234
|
throw new Error(`${name}.thinking is invalid.`);
|
|
203
235
|
}
|
|
204
236
|
if (persona) return;
|
|
237
|
+
if (value.tier !== undefined && !enumValue(value.tier, TIERS)) {
|
|
238
|
+
throw new Error(`${name}.tier must be one of ${TIERS.join(", ")}.`);
|
|
239
|
+
}
|
|
205
240
|
if (value.runtime !== undefined && !enumValue(value.runtime, RUNTIME_MODES)) {
|
|
206
241
|
throw new Error(`${name}.runtime must be one of ${RUNTIME_MODES.join(", ")}.`);
|
|
207
242
|
}
|
|
@@ -230,6 +265,22 @@ export function saveConfigDocument(scope: ConfigScope, cwd: string, text: string
|
|
|
230
265
|
) {
|
|
231
266
|
throw new Error("maxConcurrent must be an integer from 1 to 50.");
|
|
232
267
|
}
|
|
268
|
+
if (raw.fusion !== undefined && typeof raw.fusion !== "boolean") {
|
|
269
|
+
throw new Error("fusion must be true or false.");
|
|
270
|
+
}
|
|
271
|
+
if (raw.tiers !== undefined) {
|
|
272
|
+
if (!isRecord(raw.tiers)) throw new Error("tiers must be an object keyed by light, medium, or heavy.");
|
|
273
|
+
for (const [tier, value] of Object.entries(raw.tiers)) {
|
|
274
|
+
if (!enumValue(tier, TIERS)) throw new Error(`Unknown tier "${tier}"; use ${TIERS.join(", ")}.`);
|
|
275
|
+
if (!isRecord(value)) throw new Error(`tiers.${tier} must be an object.`);
|
|
276
|
+
if (value.model !== undefined && (typeof value.model !== "string" || !value.model.trim())) {
|
|
277
|
+
throw new Error(`tiers.${tier}.model must be a non-empty string.`);
|
|
278
|
+
}
|
|
279
|
+
if (value.thinking !== undefined && !enumValue(value.thinking, REASONING_EFFORTS)) {
|
|
280
|
+
throw new Error(`tiers.${tier}.thinking is invalid.`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
233
284
|
if (raw.profiles !== undefined && !isRecord(raw.profiles)) {
|
|
234
285
|
throw new Error("profiles must be an object keyed by profile name.");
|
|
235
286
|
}
|
|
@@ -250,12 +301,29 @@ export function saveConfigDocument(scope: ConfigScope, cwd: string, text: string
|
|
|
250
301
|
fs.chmodSync(file, 0o600);
|
|
251
302
|
}
|
|
252
303
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
304
|
+
/**
|
|
305
|
+
* `resumable` also defines Fusion profiles that no file defines, so a child
|
|
306
|
+
* started under Fusion mode can be continued after the mode is turned off.
|
|
307
|
+
*/
|
|
308
|
+
export function loadSubagentConfig(cwd: string, projectTrusted: boolean, options: { resumable?: boolean } = {}): SubagentConfig {
|
|
309
|
+
const global = readJson(subagentConfigPath("global", cwd));
|
|
310
|
+
const project = projectTrusted ? readJson(subagentConfigPath("project", cwd)) : undefined;
|
|
311
|
+
// Fusion profiles sit under user documents so either file can tune them.
|
|
312
|
+
const fusion = [project?.fusion, global?.fusion].find((value) => typeof value === "boolean") ?? DEFAULT_CONFIG.fusion;
|
|
313
|
+
const base = fusion ? withFusionProfiles(DEFAULT_CONFIG) : DEFAULT_CONFIG;
|
|
314
|
+
const config = mergeConfig(mergeConfig(base, global), project);
|
|
315
|
+
if (fusion || !options.resumable) return config;
|
|
316
|
+
const profiles = { ...config.profiles };
|
|
317
|
+
for (const [name, profile] of Object.entries(FUSION_PROFILES)) profiles[name] ??= profile;
|
|
318
|
+
return { ...config, profiles };
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function withFusionProfiles(config: SubagentConfig): SubagentConfig {
|
|
322
|
+
const profiles = { ...config.profiles };
|
|
323
|
+
for (const [name, profile] of Object.entries(FUSION_PROFILES)) {
|
|
324
|
+
profiles[name] = { ...profiles[name], ...profile };
|
|
257
325
|
}
|
|
258
|
-
return config;
|
|
326
|
+
return { ...config, profiles };
|
|
259
327
|
}
|
|
260
328
|
|
|
261
329
|
export function resolveProfile(
|
|
@@ -280,13 +348,14 @@ export function resolveProfile(
|
|
|
280
348
|
throw new Error(`Unknown persona "${personaName}". Available: ${Object.keys(config.personas).join(", ") || "none"}.`);
|
|
281
349
|
}
|
|
282
350
|
const instructions = [profile.instructions, persona?.instructions].filter(Boolean).join("\n\n");
|
|
351
|
+
const tier = profile.tier ? config.tiers[profile.tier] : undefined;
|
|
283
352
|
return {
|
|
284
353
|
agentType,
|
|
285
354
|
persona: personaName,
|
|
286
355
|
instructions,
|
|
287
356
|
capability: options.capability ?? profile.capability ?? "all",
|
|
288
|
-
model: options.model ?? profile.model ?? persona?.model,
|
|
289
|
-
thinking: options.thinking ?? profile.thinking ?? persona?.thinking,
|
|
357
|
+
model: options.model ?? profile.model ?? tier?.model ?? persona?.model,
|
|
358
|
+
thinking: options.thinking ?? profile.thinking ?? tier?.thinking ?? persona?.thinking,
|
|
290
359
|
isolation: options.isolation ?? profile.isolation ?? persona?.isolation ?? "none",
|
|
291
360
|
childOptions: {
|
|
292
361
|
runtime: profile.runtime ?? "in-process",
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import type { AgentProfile } from "./config.ts";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Fusion mode: the main session is the lead and keeps intent,
|
|
9
|
+
* planning, and review; one persistent sidekick per workstream implements
|
|
10
|
+
* decided work on the medium tier; light profiles take routine edits and checks.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const RESULT_LINE = "Start your final report with `RESULT: COMPLETE` or `RESULT: BLOCKED (<reason>)`.";
|
|
14
|
+
|
|
15
|
+
export const FUSION_PROFILES: Record<string, AgentProfile> = {
|
|
16
|
+
"general-purpose": { tier: "heavy" },
|
|
17
|
+
sidekick: {
|
|
18
|
+
description: "Persistent implementer for decided work and concrete-failure debugging; continue it instead of spawning another",
|
|
19
|
+
capability: "all",
|
|
20
|
+
tier: "medium",
|
|
21
|
+
instructions: [
|
|
22
|
+
"You are the sidekick: the persistent implementer working beside the lead. The lead keeps intent, architecture, security, and final approval; you carry out decided work and debug from concrete failures. The lead may continue this session with follow-ups, so build on the context you already have instead of rediscovering it.",
|
|
23
|
+
"- Treat the plan and facts in the task as settled. Search beyond them only for a concrete gap: a missing symbol, an unexpected failing test, or a contradiction you can cite. Report any deviation.",
|
|
24
|
+
"- Edit only the files the task assigns. Resolve small mismatches yourself and note them.",
|
|
25
|
+
"- Run the checks the task names after your edits; a baseline run first is fine when reproducing a failure. Don't rerun passing checks whose inputs haven't changed.",
|
|
26
|
+
"- Make failing checks pass by fixing the cause, never by skipping tests, loosening assertions, or stubbing behavior.",
|
|
27
|
+
"- Call something a root cause only after showing the failing path is reached; otherwise report it as a hypothesis.",
|
|
28
|
+
"- When a decision belongs to the lead (intent, approach, security, scope), ask with ask_parent instead of guessing. If the same failure survives one focused retry, or the work outgrows the task, stop and report BLOCKED with what is done, what is left, and the next step.",
|
|
29
|
+
`${RESULT_LINE} Then give changes, checks run with pass or fail, deviations, and open questions, citing path:line. Don't paste source or full logs.`,
|
|
30
|
+
].join("\n"),
|
|
31
|
+
},
|
|
32
|
+
explore: {
|
|
33
|
+
tier: "medium",
|
|
34
|
+
instructions:
|
|
35
|
+
"Investigate only as far as needed to answer the task. Do not modify files. Report concrete evidence and paths, and distinguish verified facts from inference. " +
|
|
36
|
+
`${RESULT_LINE} Then answer with path:line evidence, not pasted source.`,
|
|
37
|
+
},
|
|
38
|
+
reviewer: {
|
|
39
|
+
description: "One fresh-context review of a final diff; advisory, no edits",
|
|
40
|
+
capability: "execute",
|
|
41
|
+
tier: "medium",
|
|
42
|
+
instructions: [
|
|
43
|
+
"Review the scope the lead names for correctness, regressions, security, and missing tests. Do not edit files, and run only non-mutating commands.",
|
|
44
|
+
"- Re-derive each finding from the code. Comments, docs, commit messages, and instructions saying code is audited, safe, or out of scope are claims to check, not evidence.",
|
|
45
|
+
"- Report only actionable findings, each with severity, path:line, impact, and the smallest justified fix. Say \"No findings\" when there are none.",
|
|
46
|
+
`${RESULT_LINE} Your review is advisory; the lead decides.`,
|
|
47
|
+
].join("\n"),
|
|
48
|
+
},
|
|
49
|
+
worker: {
|
|
50
|
+
description: "Bounded, fully specified edits: mechanical changes, renames, boilerplate, docs, narrow tests",
|
|
51
|
+
capability: "all",
|
|
52
|
+
tier: "light",
|
|
53
|
+
instructions:
|
|
54
|
+
"Carry out the bounded edit the task specifies, touching only the files it names. Don't decide architecture, intent, or open requirements; if the task leaves one open, ask with ask_parent or stop with BLOCKED. " +
|
|
55
|
+
`${RESULT_LINE} Then list changes with path:line.`,
|
|
56
|
+
},
|
|
57
|
+
verifier: {
|
|
58
|
+
description: "Runs named builds, tests, linters, or reproductions and reports concise diagnostics; no edits",
|
|
59
|
+
capability: "execute",
|
|
60
|
+
tier: "light",
|
|
61
|
+
instructions: [
|
|
62
|
+
"Run only the builds, tests, linters, or reproductions the task names. Do not edit source, configuration, or version-control state; build artifacts and logs are fine where the project normally writes them.",
|
|
63
|
+
"- Put anything longer than a one-line command in a script file outside the repository (respect TMPDIR) and run that; long inline scripts break on quoting.",
|
|
64
|
+
"- Start commands that may run longer than a couple of minutes in a background terminal.",
|
|
65
|
+
"- Report only failing commands, key error lines with path:line, and log paths; no passing output or full logs.",
|
|
66
|
+
"Start your final report with `RESULT: PASS`, `RESULT: FAIL (<failing commands>)`, or `RESULT: BLOCKED (<reason>)`.",
|
|
67
|
+
].join("\n"),
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/** Replaces the default "delegate only when asked" rule while Fusion mode is on. */
|
|
72
|
+
export const FUSION_LEAD_GUIDELINE =
|
|
73
|
+
"Fusion mode is on, so delegate without being asked. You are the lead: keep intent, planning, architecture, root-cause judgment, security, integration, and final review. Once an approach is settled and the build is self-contained (new files, a feature, or a multi-file change of more than about ten edits), hand it to the sidekick and review its diff; keep the main thread of live debugging and small fixes yourself. Also keep work where a wrong result looks plausible instead of failing, such as counts, metrics, data analysis, queries against shared systems, and evaluation or model configuration: author and check it yourself. Route other bounded work to explore, reviewer, worker, and verifier as the fusion skill describes. Read the fusion skill before splitting multi-step work. Unless fork_turns is set, write a message that stands on its own: the goal, relevant paths, allowed scope, and the report you expect.";
|
|
74
|
+
|
|
75
|
+
const FUSION_SKILL_DIR = fileURLToPath(new URL("../../../skills/fusion", import.meta.url));
|
|
76
|
+
|
|
77
|
+
function realDir(dir: string) {
|
|
78
|
+
try {
|
|
79
|
+
return fs.realpathSync(dir);
|
|
80
|
+
} catch {
|
|
81
|
+
return path.resolve(dir);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** True for this package's fusion skill, which only the lead sees and only while Fusion mode is on. */
|
|
86
|
+
export function isFusionSkill(baseDir: string) {
|
|
87
|
+
return realDir(baseDir) === realDir(FUSION_SKILL_DIR);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Child sessions load this even without extensions, so no child sees the fusion skill. */
|
|
91
|
+
export function hideFusionSkill(pi: ExtensionAPI) {
|
|
92
|
+
pi.on("before_agent_start", (event) => {
|
|
93
|
+
const options = event.systemPromptOptions;
|
|
94
|
+
options.skills = options.skills.filter((skill) => !isFusionSkill(skill.baseDir));
|
|
95
|
+
});
|
|
96
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shariq-pi-extensions",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Cross-platform extension suite for the Pi coding agent.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Shariq Riaz",
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
],
|
|
53
53
|
"skills": [
|
|
54
54
|
"./skills/background-terminals",
|
|
55
|
+
"./skills/fusion",
|
|
55
56
|
"./skills/subagents"
|
|
56
57
|
],
|
|
57
58
|
"themes": [
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fusion
|
|
3
|
+
description: Lead doctrine for Fusion mode in Pi subagents. Use when Fusion mode is on and you are planning multi-step work, routing it across the light, medium, and heavy tiers, writing delegation messages, reviewing what children return, or recovering from a failed child.
|
|
4
|
+
compatibility: Pi with the subagents extension and Fusion mode turned on in subagents.json.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Fusion mode
|
|
8
|
+
|
|
9
|
+
You are the lead, on whichever model the user picked for this session; the heavy tier means that model unless a `heavy` tier is configured. One persistent sidekick per workstream implements decided work on the medium tier, and light profiles take routine edits and checks. The user turned this mode on, which is standing permission to delegate; it is not permission to widen the task. The subagents skill covers the tool mechanics; this one covers judgment.
|
|
10
|
+
|
|
11
|
+
## Route each deliverable
|
|
12
|
+
|
|
13
|
+
`list_agent_profiles` shows the profiles, their tiers, and the model behind each tier.
|
|
14
|
+
|
|
15
|
+
- **Keep it yourself:** user intent, planning, architecture, ambiguity, root-cause judgment, security calls, integration, and final review. Work whose judgment is the deliverable loses its point when delegated, and a trivial or already-known step costs more to hand off than to do. So does work where a wrong result looks plausible instead of failing; a child may run a recipe you wrote exactly, but the authoring and the checking stay with you.
|
|
16
|
+
- **`sidekick` (medium):** decided implementation, integration, and debugging from a concrete failure or reproduction. Keep one per workstream: continue an idle one with `send_message` and a finished one with `resume_from` so it keeps its context.
|
|
17
|
+
- **`explore` (medium):** substantive read-only tracing when the answer needs many files or commands, and separate evidence-gathering threads that need commands but no edits, such as collecting logs or reproducing a failure on a second service while you debug the first. Have it return the evidence, and draw the conclusions yourself.
|
|
18
|
+
- **`reviewer` (medium):** one fresh review of the final diff of a non-trivial change.
|
|
19
|
+
- **`worker` (light):** mechanical edits, renames, boilerplate, docs, and narrow tests that are fully specified.
|
|
20
|
+
- **`verifier` (light):** builds, tests, linters, and reproductions. It reports failures and does not fix them.
|
|
21
|
+
- **`general-purpose` (heavy):** rare; for hard, decided work after the sidekick hits a concrete blocker.
|
|
22
|
+
|
|
23
|
+
Light output is only as good as your check of it, so give light profiles work whose result is cheap to check, such as an edit that applies or a command that passes, and not analysis, diagnosis, or summaries you would pass on unchecked.
|
|
24
|
+
|
|
25
|
+
## Write the message
|
|
26
|
+
|
|
27
|
+
Children start without your conversation. Settle interfaces, consequential choices, and hypotheses before dispatch, and specify rather than describe:
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
Repo: <absolute path>
|
|
31
|
+
Goal: <one deliverable>
|
|
32
|
+
Known: <verified facts, decisions, files, running servers to reuse; mark anything unchecked as an assumption>
|
|
33
|
+
Scope: <files or directories it owns>
|
|
34
|
+
Leave alone: <files owned elsewhere, configuration, generated output>
|
|
35
|
+
Return: <fields>, at most <N> bullets, each with path:line
|
|
36
|
+
Stop when: <ambiguities or blockers that need you>
|
|
37
|
+
Validation: <exact commands, or none>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Children treat the known facts as settled and reopen them only for a concrete gap, so an unmarked guess becomes their premise. Don't tell a reviewer that code is already audited or safe; that kind of framing hides real bugs.
|
|
41
|
+
|
|
42
|
+
## Review what comes back
|
|
43
|
+
|
|
44
|
+
- A child's report is a claim; its diff and check output are the evidence. Read the whole change before acting, then send every correction in one follow-up to the same child.
|
|
45
|
+
- Don't rerun checks a child already passed unless the evidence is missing or looks wrong, or the inputs changed since.
|
|
46
|
+
- Treat a root cause without a shown failing path, a ranked list of candidate causes, and delegated analysis as hypotheses; check the load-bearing findings yourself before you state them as fact.
|
|
47
|
+
- Answer `ask_parent` questions that are yours to decide with `reply_question`. Ask the user only for decisions that need their authority.
|
|
48
|
+
- Use one reviewer, not panels. In measured review runs, extra reviewers raised false positives without finding more defects. When two children disagree, check the disputed fact yourself instead of adding a voter.
|
|
49
|
+
|
|
50
|
+
## Concurrency and ownership
|
|
51
|
+
|
|
52
|
+
- One writer per file. Concurrent writers in a repository that builds or typechecks each get `isolation: "worktree"`.
|
|
53
|
+
- About four read-only children or three editing children at once is a sensible ceiling unless the user sets another. Run heavy builds and test suites one child at a time when they compete for CPU, memory, or locks.
|
|
54
|
+
- Delegation hands over ownership: don't edit inside a running child's scope or redo its work afterwards.
|
|
55
|
+
- After compaction or a restart, check `list_agents` and the files on disk before relaunching anything.
|
|
56
|
+
|
|
57
|
+
## Escalate and recover
|
|
58
|
+
|
|
59
|
+
- **Ambiguity:** decide it, then continue the same child with the decision.
|
|
60
|
+
- **Escalation:** move work from a light profile to `explore` or `sidekick` when it needs real tracing or implementation. Take it back if the sidekick reports BLOCKED twice or the task turns judgment-heavy.
|
|
61
|
+
- **Failures:** allow one scoped retry, then re-scope or take the work back. Upstream server errors and concurrency limits usually clear, so retry once with fewer children running; authentication and quota failures don't clear by retrying.
|
|
62
|
+
- **Fallback:** when no child can run, do bounded work directly and say so.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: subagents
|
|
3
|
-
description: Use only when the user explicitly asks Pi to use subagents, delegate work, run agents in parallel, or explicitly names this skill. Do not invoke merely because a task is large, complex, multi-domain, or potentially parallelizable.
|
|
3
|
+
description: Use only when the user explicitly asks Pi to use subagents, delegate work, run agents in parallel, or explicitly names this skill, or when Fusion mode is on. Do not invoke merely because a task is large, complex, multi-domain, or potentially parallelizable.
|
|
4
4
|
compatibility: Pi with the Pi-only subagents extension and its spawn, task, messaging, question, profile, dashboard, and worktree tools.
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -10,7 +10,7 @@ Pi child agents each get their own context window and persistent session, cannot
|
|
|
10
10
|
|
|
11
11
|
## Activation boundary
|
|
12
12
|
|
|
13
|
-
Delegate only when the user explicitly asks for it. Task size or convenience is not authorization. If the user asks only to set up or explain delegation, do not start productive work beyond that request.
|
|
13
|
+
Delegate only when the user explicitly asks for it. Task size or convenience is not authorization. The exception is Fusion mode (`"fusion": true` in `subagents.json`): the user has already authorized delegation, and the `fusion` skill decides what to hand off. If the user asks only to set up or explain delegation, do not start productive work beyond that request.
|
|
14
14
|
|
|
15
15
|
## Choose the operation
|
|
16
16
|
|
|
@@ -56,7 +56,7 @@ Give each child the narrowest capability that can finish the work:
|
|
|
56
56
|
- `read-write`: inspection plus direct file edits, without command execution.
|
|
57
57
|
- `all`: everything, including extension tools no other mode allows. Use it only when the work needs that.
|
|
58
58
|
|
|
59
|
-
Use an existing profile or persona when one fits; check `list_agent_profiles` rather than inventing names. Omit model and thinking overrides so children inherit the parent's. Never move children to a more expensive model or higher thinking level unless the user asked; a batch of ten children multiplies that cost by ten. When an override is needed, use only a provider and model the current Pi registry exposes.
|
|
59
|
+
Use an existing profile or persona when one fits; check `list_agent_profiles` rather than inventing names. Omit model and thinking overrides so children inherit the parent's, or the tier the user configured for their profile. Never move children to a more expensive model or higher thinking level unless the user asked; a batch of ten children multiplies that cost by ten. When an override is needed, use only a provider and model the current Pi registry exposes.
|
|
60
60
|
|
|
61
61
|
Keep `isolation` at `none` for read-only work and for writers with clearly separate files. When two or more children will write concurrently in a repository that compiles or typechecks, give each writer `isolation: "worktree"`, because half-finished edits in one checkout break each other's builds. Worktree isolation needs a clean source checkout; commit or stash first, or run the writers one after another. Isolation does not authorize publishing.
|
|
62
62
|
|