@tranhoangnguyen0310/pi-flow-external 3.2.1-external.0 → 4.0.0-external.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/CHANGELOG.md +10 -0
- package/README.md +7 -3
- package/docs/README.md +2 -1
- package/docs/migration-agent-to-external-delegate.md +69 -0
- package/docs/public-contract.md +5 -5
- package/docs/supervision-and-runtime.md +4 -2
- package/docs/troubleshooting.md +2 -2
- package/package.json +2 -1
- package/src/contract/agent.ts +3 -2
- package/src/core/progress.ts +2 -1
- package/src/core/spawn.ts +2 -2
- package/src/external-help.ts +2 -2
- package/src/pi-subagent.ts +4 -3
- package/src/profile-creator.ts +2 -2
- package/src/profiles.ts +3 -3
- package/src/prompts.ts +6 -6
- package/src/tool-names.ts +2 -0
- package/src/workflow/tool.ts +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to pi-flow external are documented here.
|
|
4
4
|
|
|
5
|
+
## [4.0.0-external.0] - 2026-10-13
|
|
6
|
+
|
|
7
|
+
### Breaking
|
|
8
|
+
- Renamed the direct delegation tool **`Agent` → `external_delegate`** (#85). Flow no longer registers `Agent`; there is no alias or automatic translation. Update `tools.Agent(...)` → `tools.external_delegate(...)`, `describeTool("Agent")` → `describeTool("external_delegate")`, exact-name hooks, tool allow-lists, and receipt checks. Stale calls may reach another extension's `Agent` tool rather than fail.
|
|
9
|
+
- New direct receipts report `tool: "external_delegate"`. Envelope `contractVersion: 1`, `action: "delegate"`, run `kind: "agent"`, and workflow `agent()` remain unchanged. Settings remain version 5; no settings conversion or saved-run rewrite is required.
|
|
10
|
+
- Update Flow's entry in `pi-cc-extensions` `excludeRenderers` to `"external_delegate"` (alongside `"workflow"` and `"external_runs"`). Preserve unrelated entries, including `"Agent"` if another extension needs it, and restart Pi. See the [tool migration guide](docs/migration-agent-to-external-delegate.md).
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- Current documentation, coordinator guidance, and help use the namespaced direct tool name. Background guidance explicitly states that runs do not notify or resume the parent session: collect them through `external_runs` (`wait`, then `inspect` with `view: "final"` for a successful answer; inspect outcomes and diagnostics for failures).
|
|
14
|
+
|
|
5
15
|
## [3.2.1-external.0] - 2026-10-05
|
|
6
16
|
|
|
7
17
|
### Changed
|
package/README.md
CHANGED
|
@@ -76,7 +76,7 @@ Pi recognizes the request and delegates:
|
|
|
76
76
|
*(Programmatic / Codemode equivalent):*
|
|
77
77
|
|
|
78
78
|
```ts
|
|
79
|
-
const receipt = await tools.
|
|
79
|
+
const receipt = await tools.external_delegate({
|
|
80
80
|
description: "Map repository architecture",
|
|
81
81
|
role: "explorer",
|
|
82
82
|
harness: "claude",
|
|
@@ -130,7 +130,7 @@ Pi automatically packages recent conversation turns into the briefing:
|
|
|
130
130
|
*(Codemode):*
|
|
131
131
|
|
|
132
132
|
```ts
|
|
133
|
-
await tools.
|
|
133
|
+
await tools.external_delegate({
|
|
134
134
|
description: "Review refactoring plan",
|
|
135
135
|
role: "reviewer",
|
|
136
136
|
harness: "codex",
|
|
@@ -154,7 +154,7 @@ Pi launches the run in the background, gives you a run ID, and keeps your chat s
|
|
|
154
154
|
*(Codemode):*
|
|
155
155
|
|
|
156
156
|
```ts
|
|
157
|
-
const receipt = await tools.
|
|
157
|
+
const receipt = await tools.external_delegate({
|
|
158
158
|
description: "Audit security-sensitive endpoints",
|
|
159
159
|
role: "reviewer",
|
|
160
160
|
harness: "claude",
|
|
@@ -216,6 +216,10 @@ Settings use version 5. If upgrading from an older version:
|
|
|
216
216
|
2. Existing configurations, overrides, and disabled states are preserved.
|
|
217
217
|
3. See [Migration Guide](docs/migration-v4-to-v5.md) for full details and optional file purging.
|
|
218
218
|
|
|
219
|
+
### Upgrading from the `Agent` tool
|
|
220
|
+
|
|
221
|
+
The direct delegation tool `Agent` is now `external_delegate`, with no alias. Update codemode calls, hooks, and renderer exclusions. See [Tool Rename Migration](docs/migration-agent-to-external-delegate.md).
|
|
222
|
+
|
|
219
223
|
---
|
|
220
224
|
|
|
221
225
|
## Documentation
|
package/docs/README.md
CHANGED
|
@@ -17,6 +17,7 @@ Core reference guides for configuring and using the extension with Pi:
|
|
|
17
17
|
| **[Workflows](workflows.md)** | Scripting API, parallel fan-out (`parallel()`), `ChildRunError` handling, structured schemas, and replay cache. |
|
|
18
18
|
| **[Troubleshooting](troubleshooting.md)** | Diagnostic recipes, authentication checks, and `pi-cc-extensions` renderer compatibility. |
|
|
19
19
|
| **[Migration Guide (v4 → v5)](migration-v4-to-v5.md)** | Upgrading from older configurations, backups, command mappings, and safe file purging. |
|
|
20
|
+
| **[Tool Rename Migration (`Agent` → `external_delegate`)](migration-agent-to-external-delegate.md)** | Updating codemode calls, hooks, receipt checks, and renderer exclusions after the direct tool rename. |
|
|
20
21
|
|
|
21
22
|
---
|
|
22
23
|
|
|
@@ -25,7 +26,7 @@ Specifications for programmatic subagent delegation and tool outputs:
|
|
|
25
26
|
|
|
26
27
|
| Document | Status | Purpose |
|
|
27
28
|
|---|---|---|
|
|
28
|
-
| **[Public Contract v1](public-contract.md)** | `Normative Contract` | Schema definitions, envelope shapes (`{ ok, data, error }`), and delivery invariants for `
|
|
29
|
+
| **[Public Contract v1](public-contract.md)** | `Normative Contract` | Schema definitions, envelope shapes (`{ ok, data, error }`), and delivery invariants for `external_delegate`, `workflow`, and `external_runs`. |
|
|
29
30
|
|
|
30
31
|
---
|
|
31
32
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Migration Guide: `Agent` Tool Renamed to `external_delegate`
|
|
2
|
+
|
|
3
|
+
The direct delegation tool formerly named `Agent` is now `external_delegate` ([issue #85](https://github.com/tranhoangnguyen03/pi-flow-external/issues/85)). It joins `external_help` and `external_runs` as one tool family and no longer shares a name with Claude Code's built-in `Agent` tool.
|
|
4
|
+
|
|
5
|
+
This guide covers only the tool rename. For settings conversion, see the [v4 → v5 Migration Guide](migration-v4-to-v5.md).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## No Alias
|
|
10
|
+
|
|
11
|
+
`Agent` is not kept as a second name, and old calls are not translated. Keeping both names would bring back the name clash and add a duplicate tool to every session. A stale `Agent` call fails, or reaches another extension's `Agent` tool if one is registered, so update every caller.
|
|
12
|
+
|
|
13
|
+
The rename was motivated by `Agent` going missing when Claude runs through `pi-claude-bridge`. A live smoke test on 2026-10-13 still reported the renamed tool unavailable while the other three Flow tools were visible. No delegation was made. The rename is therefore not a confirmed fix for bridge visibility; that integration remains unresolved.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## What to Update
|
|
18
|
+
|
|
19
|
+
| Where | Before | After |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| Codemode calls | `tools.Agent({...})` | `tools.external_delegate({...})` |
|
|
22
|
+
| Codemode discovery | `describeTool("Agent")` | `describeTool("external_delegate")` |
|
|
23
|
+
| Hooks and handlers that match an exact tool name (`tool_call`, `tool_result`, `event.toolName === ...`) and tool allow-lists such as `--tools` | `"Agent"` | `"external_delegate"` |
|
|
24
|
+
| Result discriminator: the envelope `tool` field | `tool: "Agent"` | `tool: "external_delegate"` |
|
|
25
|
+
| `pi-cc-extensions` `excludeRenderers` | `"Agent"` | `"external_delegate"`, then restart Pi. Keep `"Agent"` only if another extension needs it (see [Troubleshooting](troubleshooting.md#third-party-ui-compatibility-pi-cc--ccstyle)) |
|
|
26
|
+
|
|
27
|
+
Example:
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
const receipt = await tools.external_delegate({
|
|
31
|
+
description: "Check build",
|
|
32
|
+
role: "worker",
|
|
33
|
+
prompt: "Run the build and report.",
|
|
34
|
+
});
|
|
35
|
+
if (!receipt.ok) return receipt.error;
|
|
36
|
+
return receipt.data.run;
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## What Does Not Change
|
|
42
|
+
|
|
43
|
+
- **Input parameters:** `role`, `harness`, `subagent_type`, `permission`, `context`, `resume`, `background`, and the other arguments are the same.
|
|
44
|
+
- **Envelope:** `contractVersion` is still `1`. `action` is still `"delegate"`, and runs still report `kind: "agent"`. `ok`, `data`, `error`, and `warnings` keep their meaning. See [Public Contract v1](public-contract.md).
|
|
45
|
+
- **Settings and saved runs:** settings stay at version 5. No settings conversion or saved-run rewrite is needed.
|
|
46
|
+
- **Workflows:** the workflow `agent()` function keeps its name. It exists only inside workflow scripts and never appears in a tool list.
|
|
47
|
+
- **Other tools:** `workflow`, `external_runs`, and `external_help` keep their names.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Background Runs Still Need Collecting
|
|
52
|
+
|
|
53
|
+
A background run does not notify or resume the session when it finishes. Ending the turn does not bring the result back. Collect it with `external_runs`:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
const receipt = await tools.external_delegate({
|
|
57
|
+
description: "Audit endpoints",
|
|
58
|
+
role: "reviewer",
|
|
59
|
+
prompt: "Audit the endpoints read-only.",
|
|
60
|
+
background: true,
|
|
61
|
+
});
|
|
62
|
+
if (!receipt.ok) return receipt.error;
|
|
63
|
+
const runId = receipt.data.run.runId;
|
|
64
|
+
|
|
65
|
+
await tools.external_runs({ action: "wait", runIds: [runId], mode: "all" });
|
|
66
|
+
const final = await tools.external_runs({ action: "inspect", runIds: [runId], view: "final" });
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`final` holds only a verified successful answer. For a failed run, read the outcome from `wait` and the details with `view: "diagnostics"`.
|
package/docs/public-contract.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
# Public contract v1:
|
|
1
|
+
# Public contract v1: external_delegate, workflow and external_runs
|
|
2
2
|
|
|
3
|
-
`
|
|
3
|
+
`external_delegate`, `workflow` and `external_runs` declare `outputSchema` and return matching `structuredContent`. Human `content` and renderer `details` stay separate. Native Pi codemode receives the structured result, including failures. **Check `ok`**, not just whether the call threw.
|
|
4
4
|
|
|
5
5
|
```js
|
|
6
|
-
const receipt = await tools.
|
|
6
|
+
const receipt = await tools.external_delegate({ role: "worker", description: "Check build", prompt: "Run the build and report." });
|
|
7
7
|
if (!receipt.ok) return receipt.error;
|
|
8
8
|
return receipt.data.run;
|
|
9
9
|
```
|
|
@@ -22,7 +22,7 @@ Every result carries:
|
|
|
22
22
|
|
|
23
23
|
| Tool | `action` | Success `data` | Failure `data` |
|
|
24
24
|
|---|---|---|---|
|
|
25
|
-
| `
|
|
25
|
+
| `external_delegate` | `delegate` | `{run}` | `{run}`, or `{run: null}` before registration |
|
|
26
26
|
| `workflow` | `run` | `{run}` | `{run}`, or `{run: null}` before registration |
|
|
27
27
|
| `external_runs` | the requested action | the action's payload (below) | `null` |
|
|
28
28
|
|
|
@@ -107,4 +107,4 @@ A host redaction hook that replaces content drops structured content unless it e
|
|
|
107
107
|
|
|
108
108
|
## Discovery
|
|
109
109
|
|
|
110
|
-
Pi 0.99.2 generates discriminated return types from these schemas through native `describeTool`. The `
|
|
110
|
+
Pi 0.99.2 generates discriminated return types from these schemas through native `describeTool`. The `external_delegate` input declaration currently loses its input properties, because Pi renders the root `anyOf` constraints before the properties; input validation itself remains intact. This is a known input-discovery limitation, not a runtime validation bypass.
|
|
@@ -33,12 +33,12 @@ Workspace /path/to/project
|
|
|
33
33
|
|
|
34
34
|
## Background Runs & Lifecycle
|
|
35
35
|
|
|
36
|
-
By default, calls to `
|
|
36
|
+
By default, calls to `external_delegate` and `workflow` are blocking: the caller waits until the child process completes.
|
|
37
37
|
|
|
38
38
|
Setting `background: true` queues and starts the work asynchronously while returning an immediate receipt containing a stable handle:
|
|
39
39
|
|
|
40
40
|
```ts
|
|
41
|
-
const receipt = await tools.
|
|
41
|
+
const receipt = await tools.external_delegate({
|
|
42
42
|
description: "Audit security-sensitive endpoints",
|
|
43
43
|
role: "reviewer",
|
|
44
44
|
harness: "claude",
|
|
@@ -54,6 +54,8 @@ if (!receipt.ok) {
|
|
|
54
54
|
const runId = receipt.data.run.runId; // e.g. "run_..."
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
+
Background runs do not notify or resume this session when they finish. Collect them with `external_runs`: `wait`, then `inspect` with view `final` for a successful answer. For a failed run, read the `wait` outcome and `inspect` view `diagnostics`.
|
|
58
|
+
|
|
57
59
|
### Ownership Invariants
|
|
58
60
|
- **Session-Owned:** Background children belong to the active Pi session, not a persistent OS daemon.
|
|
59
61
|
- **Orderly Shutdown:** Closing Pi gracefully signals cancellation and waits for bounded cleanup.
|
package/docs/troubleshooting.md
CHANGED
|
@@ -41,7 +41,7 @@ Solutions to common issues, authentication questions, and environment quirks whe
|
|
|
41
41
|
### 8. Child Missing Earlier Conversation Context
|
|
42
42
|
- **Fix:** Explicitly pass conversation turns using `context`:
|
|
43
43
|
```ts
|
|
44
|
-
|
|
44
|
+
external_delegate({ description: "Follow-up review", role: "reviewer", context: { mode: "recent", turns: 5 }, prompt: "..." })
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
### 9. Workflow Rejected Before Launch
|
|
@@ -66,7 +66,7 @@ Add `pi-flow-external` tool names to `excludeRenderers` in `~/.pi/agent/pi-cc-ex
|
|
|
66
66
|
|
|
67
67
|
```json
|
|
68
68
|
{
|
|
69
|
-
"excludeRenderers": ["
|
|
69
|
+
"excludeRenderers": ["external_delegate", "workflow", "external_runs"]
|
|
70
70
|
}
|
|
71
71
|
```
|
|
72
72
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tranhoangnguyen0310/pi-flow-external",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0-external.0",
|
|
4
4
|
"description": "External Claude Code, Codex CLI, Antigravity, Grok Build CLI, Muse Code, and OpenCode delegation for pi.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.ts",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"docs/workflows.md",
|
|
21
21
|
"docs/troubleshooting.md",
|
|
22
22
|
"docs/migration-v4-to-v5.md",
|
|
23
|
+
"docs/migration-agent-to-external-delegate.md",
|
|
23
24
|
"docs/public-contract.md"
|
|
24
25
|
],
|
|
25
26
|
"keywords": [
|
package/src/contract/agent.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { type JsonObject } from "@earendil-works/pi-ai";
|
|
2
2
|
import { Type, type Static } from "typebox";
|
|
3
|
+
import { EXTERNAL_DELEGATE_TOOL } from "../tool-names.ts";
|
|
3
4
|
import type { RegisteredRunEntry } from "../core/run-registry.ts";
|
|
4
5
|
import { contractEnvelope, envelope, INLINE_RESULT_BYTES, type PublicError } from "./envelope.ts";
|
|
5
6
|
import { agentRunSchema, liveAgentRun, type EvidenceIntegrity } from "./run.ts";
|
|
6
7
|
|
|
7
8
|
const agentData = Type.Object({ run: Type.Union([Type.Null(), agentRunSchema]) });
|
|
8
|
-
export const agentOutputSchema = contractEnvelope(
|
|
9
|
+
export const agentOutputSchema = contractEnvelope(EXTERNAL_DELEGATE_TOOL, Type.Literal("delegate"), agentData, agentData);
|
|
9
10
|
export type AgentReceipt = Static<typeof agentOutputSchema>;
|
|
10
11
|
|
|
11
12
|
/** Only registry-confirmed entries may supply a public handle. Never infer liveness from disk. */
|
|
@@ -25,5 +26,5 @@ export function agentReceipt({ entry, integrity = "unknown", error, inspectable
|
|
|
25
26
|
error = { code: entry.outcome.outcome === "succeeded" ? "failed" : entry.outcome.outcome, message: entry.outcome.error ?? "Child execution failed" };
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
|
-
return envelope({ tool:
|
|
29
|
+
return envelope({ tool: EXTERNAL_DELEGATE_TOOL, action: "delegate", data: { run }, warnings, error, redact: true }) as AgentReceipt & JsonObject;
|
|
29
30
|
}
|
package/src/core/progress.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AgentSessionEvent } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { EXTERNAL_DELEGATE_TOOL } from "../tool-names.ts";
|
|
2
3
|
import type {
|
|
3
4
|
SubagentBackend,
|
|
4
5
|
SubagentAssistantOutput,
|
|
@@ -138,7 +139,7 @@ function getToolArgPreview(args: unknown): string {
|
|
|
138
139
|
|
|
139
140
|
export function updateProgressFromEvent(progress: SubagentProgressNode, event: AgentSessionEvent): void {
|
|
140
141
|
if (event.type === "tool_execution_start") {
|
|
141
|
-
if (event.toolName ===
|
|
142
|
+
if (event.toolName === EXTERNAL_DELEGATE_TOOL) {
|
|
142
143
|
return;
|
|
143
144
|
}
|
|
144
145
|
const preview = getToolArgPreview(event.args);
|
package/src/core/spawn.ts
CHANGED
|
@@ -45,14 +45,14 @@ import { runRecordsDirectory } from "./retention.ts";
|
|
|
45
45
|
import { createTimeoutSignal, markSubagentTimedOut } from "./timeout.ts";
|
|
46
46
|
import type { PermissionResolution } from "./permissions.ts";
|
|
47
47
|
import { effectivePiResourcePreset, isValidThinkingLevel, VALID_THINKING_LEVELS } from "../harnesses.ts";
|
|
48
|
-
|
|
48
|
+
import { EXTERNAL_DELEGATE_TOOL } from "../tool-names.ts";
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
51
|
* The delegation tools a spawned child must never receive, so subagents cannot
|
|
52
52
|
* recursively fan out. Owned by the spawn core and used as the default exclude
|
|
53
53
|
* list so no caller can accidentally under-specify the nesting block.
|
|
54
54
|
*/
|
|
55
|
-
export const CHILD_EXCLUDED_TOOLS: readonly string[] = [
|
|
55
|
+
export const CHILD_EXCLUDED_TOOLS: readonly string[] = [EXTERNAL_DELEGATE_TOOL, "workflow"];
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
58
|
* Parameters for a single subagent run. This is the shared spawn primitive used
|
package/src/external-help.ts
CHANGED
|
@@ -95,9 +95,9 @@ function workflowHelp(workflowsEnabled: boolean, workflows: ReturnType<typeof li
|
|
|
95
95
|
|
|
96
96
|
Provide exactly one workflow source: name, scriptPath, or script. Every script starts with the literal current declaration export const meta = { apiVersion: 1, name, description }, calls agent() at least once, awaits every started call, and returns JSON-serializable data. Missing/unsupported API versions fail before any child launches. Project .pi/workflows are visible only when the project is trusted.
|
|
97
97
|
|
|
98
|
-
APIs: agent(prompt, { description, label, role, harness, subagent_type, permission, max_budget_usd, resume, context, schema, phase }); parallel(thunks); pipeline(items, ...stages); phase(title); log(message). Globals: args and cwd. Agent options other than the profile selector are optional. "description" is the common task-name option shared with the direct
|
|
98
|
+
APIs: agent(prompt, { description, label, role, harness, subagent_type, permission, max_budget_usd, resume, context, schema, phase }); parallel(thunks); pipeline(items, ...stages); phase(title); log(message). Globals: args and cwd. Agent options other than the profile selector are optional. "description" is the common task-name option shared with the direct external_delegate tool; "label" is a compatible alias — set only one, or both to the same value. Choose role/harness or legacy exact subagent_type, never both. Use unique descriptions/labels and clear task prompts. An agent() succeeds with its value or throws ChildRunError { runId, outcome, message, outputRef, diagnosticsRef }; catch optional failures explicitly. Uncaught failures terminate the workflow and drain siblings. Helpers never convert failures to null. Context options: {mode:"none"} (default), {mode:"recent",turns:N} (positive integer, includes current user turn), or {mode:"full"} (available context after compaction). All children share invocation-time context/settings; earlier child results must still be passed explicitly. Context excludes thinking/system instructions and pending calls; images and snapshots over 1 MiB fail without truncation. Context sharing cannot be combined with resume. Use schema for results that control branching or aggregation. Imports, filesystem globals, Date APIs, and Math.random() are unavailable.
|
|
99
99
|
|
|
100
|
-
Background and supervision: blocking by default — an ordinary call waits for its child and returns the result, run everything this way unless the parent has other work to do first. Set background:true only when the parent can proceed before completion, then use external_runs on the returned stable run ID while session-owned work continues. Parallel same-harness work (e.g. three agy workers) runs concurrently under the shared maxConcurrentSubagents cap: call parallel([() => agent(...), ...]) in a workflow, or issue separate
|
|
100
|
+
Background and supervision: blocking by default — an ordinary call waits for its child and returns the result, run everything this way unless the parent has other work to do first. Set background:true only when the parent can proceed before completion, then use external_runs on the returned stable run ID while session-owned work continues. Background runs do not notify or resume this session; collect them with external_runs (wait, then inspect with view final). Check failed outcomes and diagnostics; view final only supplies verified successful answers. Parallel same-harness work (e.g. three agy workers) runs concurrently under the shared maxConcurrentSubagents cap: call parallel([() => agent(...), ...]) in a workflow, or issue separate external_delegate calls with background:true in one turn, then external_runs wait/inspect/cancel. Awaiting agent() calls sequentially stays serial by construction. external_runs selects targets with runIds. list takes optional workflowRunId, cursor, workflowCursor, and limit. inspect with one id reads any view (summary, output, diagnostics, or final) and pages with an opaque cursor and limitBytes; several ids (up to 20) batch summary only. wait takes runIds in mode any or all (up to 100). cancel takes one id and an optional reason. Follow nextCursor/nextWorkflowCursor for complete results. Wait returns selected terminal outcomes and pending IDs, never cancels pending work, and returns an unsuccessful workflow early; interrupting wait stops only the wait. Cancelling a workflow stops active children, while cancelling one child is a catchable workflow error. Blocking-call interruption cancels that call; background work survives tool return but is cancelled on orderly owning-session shutdown. It is not a daemon: crashes leave unfinished evidence interrupted/uncertain, restart does not adopt work, and live steering is unavailable.
|
|
101
101
|
|
|
102
102
|
Replay: resumeFromRunId works only with persisted scriptPath and starts an explicit new attempt. It reuses the longest unchanged prefix of successful child calls; the first changed/failed/cancelled/timed-out call and its suffix run again. Script recomposition is cheap, but child reruns can cost money or repeat side effects. There is no automatic repaired-script replay.
|
|
103
103
|
|
package/src/pi-subagent.ts
CHANGED
|
@@ -50,6 +50,7 @@ import { registerProfileCreator, startRoleInterview, startHarnessInterview } fro
|
|
|
50
50
|
import { registerExternalCommand } from "./external-command.ts";
|
|
51
51
|
import { DEFAULT_EXTERNAL_SETTINGS, loadExternalSettings, resolveCtxDefaultHarness, resolveExternalSettings, renderDefaultHarness } from "./settings.ts";
|
|
52
52
|
import { EXTERNAL_HARNESSES } from "./types.ts";
|
|
53
|
+
import { EXTERNAL_DELEGATE_TOOL } from "./tool-names.ts";
|
|
53
54
|
import type {
|
|
54
55
|
PermissionTier,
|
|
55
56
|
SubagentBackend,
|
|
@@ -359,8 +360,8 @@ function createAgentTool(
|
|
|
359
360
|
options: CreateAgentToolOptions,
|
|
360
361
|
): ToolDefinition<typeof agentToolParameters, SubagentToolDetails> {
|
|
361
362
|
return defineTool({
|
|
362
|
-
name:
|
|
363
|
-
label:
|
|
363
|
+
name: EXTERNAL_DELEGATE_TOOL,
|
|
364
|
+
label: EXTERNAL_DELEGATE_TOOL,
|
|
364
365
|
description: "Delegate one task to an external Claude Code, Codex CLI, Antigravity, Grok CLI, Muse Code, OpenCode, or registered Pi harness role.",
|
|
365
366
|
promptSnippet: AGENT_PROMPT_SNIPPET,
|
|
366
367
|
parameters: agentToolParameters,
|
|
@@ -882,7 +883,7 @@ export function createSubagentExtension(options: SubagentExtensionOptions = {}):
|
|
|
882
883
|
|
|
883
884
|
pi.on("before_agent_start", (event, ctx) => {
|
|
884
885
|
const tools = pi.getAllTools();
|
|
885
|
-
if (!tools.some((tool) => tool.name ===
|
|
886
|
+
if (!tools.some((tool) => tool.name === EXTERNAL_DELEGATE_TOOL)) {
|
|
886
887
|
return;
|
|
887
888
|
}
|
|
888
889
|
// No per-turn counter reset: the shared ConcurrencyLimiter takes a slot
|
package/src/profile-creator.ts
CHANGED
|
@@ -36,13 +36,13 @@ A role is shared authoring only: one markdown file under pi-flow-external/roles/
|
|
|
36
36
|
|
|
37
37
|
Describe read-only or editing intent in the instructions. Do not ask for a permission tier: a role does not grant authority. The caller passes permission, or the global defaultPermission applies. Do not ask about backend, model, or thinking: shared roles never pin those, and backend-specific execution settings belong in /external config role set <role> --harness <name>; instruction replacements use /external config role edit <role> --harness <name>. When ready, summarize once and call ${ROLE_TOOL_NAME}.
|
|
38
38
|
|
|
39
|
-
Do not write files yourself and do not run
|
|
39
|
+
Do not write files yourself and do not run external_delegate or workflow. The tool will show what will be created for review, request confirmation, and write it offline without a backend smoke test. A role is not an authenticated connection; readiness smoke testing belongs to harness registration.`;
|
|
40
40
|
|
|
41
41
|
export const HARNESS_INTERVIEW_PROMPT = `Help me register one named Pi harness configuration through an AI-assisted interview.
|
|
42
42
|
|
|
43
43
|
Ask for a "pi-<label>" name, a provider/model id (validated live against the model registry), an optional thinking level (off/minimal/low/medium/high/xhigh; default off), and a resource preset. The preset is a simple choice of minimal or skills. minimal is the default and loads no skills. skills loads installed skills; project skills load only when the project is trusted. The six canonical roles (explorer, planner, implementer, reviewer, qa, worker) become automatically available on this harness the moment it is registered — no per-role file needed. When ready, summarize once and call ${HARNESS_TOOL_NAME}.
|
|
44
44
|
|
|
45
|
-
Do not write files yourself and do not run
|
|
45
|
+
Do not write files yourself and do not run external_delegate or workflow in this branch. The tool will show what will be registered for review, request confirmation, stage it, run a real in-process pi smoke test, and either install it or roll it back.`;
|
|
46
46
|
|
|
47
47
|
const roleParameters = Type.Object({
|
|
48
48
|
name: Type.String({ description: "Lowercase shared role name, such as security-reviewer. No backend prefix; reviewer replaces the built-in reviewer." }),
|
package/src/profiles.ts
CHANGED
|
@@ -264,7 +264,7 @@ export function loadExternalCatalog(agentDir = getAgentDir(), options: { legacyI
|
|
|
264
264
|
const { frontmatter } = parseFrontmatter<Record<string, unknown>>(content);
|
|
265
265
|
const obsolete = ["permission", "capabilitySet"].filter((key) => Object.prototype.hasOwnProperty.call(frontmatter, key));
|
|
266
266
|
if (obsolete.length) {
|
|
267
|
-
throw new Error(`obsolete metadata ${obsolete.join(", ")} does not grant authority. Remove it. A role describes intent; pass permission on the
|
|
267
|
+
throw new Error(`obsolete metadata ${obsolete.join(", ")} does not grant authority. Remove it. A role describes intent; pass permission on the external_delegate or workflow call, or set defaultPermission. Pi skills follow the harness preset.`);
|
|
268
268
|
}
|
|
269
269
|
if (Object.keys(frontmatter).some((key) => key !== "description")) throw new Error("shared roles support description only; use an exact override for execution settings");
|
|
270
270
|
} else {
|
|
@@ -442,7 +442,7 @@ export function reconcilePiProfileWithHarness(
|
|
|
442
442
|
* once, skipping any `<harness>-<role>` key that already has a real on-disk
|
|
443
443
|
* entry (same override precedence resolveExternalProfile applies at
|
|
444
444
|
* resolution time). This is the single shared merge every consumer that
|
|
445
|
-
* needs a *complete* roster reuses: the
|
|
445
|
+
* needs a *complete* roster reuses: the external_delegate tool's catalog/help text, the
|
|
446
446
|
* workflow tool's frozen per-run profile+model snapshot (so a workflow can
|
|
447
447
|
* actually execute a synthesized pi role, not just resolve its name), and
|
|
448
448
|
* /external's doctor/settings/profiles text. Real on-disk profiles are never
|
|
@@ -456,7 +456,7 @@ export function reconcilePiProfileWithHarness(
|
|
|
456
456
|
* omits `model`/`thinking` (the common case, inheriting from its harness)
|
|
457
457
|
* would carry `model: undefined` into that map and fail with "no model
|
|
458
458
|
* selected" even though the exact same profile resolves and runs fine
|
|
459
|
-
* through the
|
|
459
|
+
* through the external_delegate tool's resolveExternalProfile path.
|
|
460
460
|
*
|
|
461
461
|
* A genuine conflict is deliberately *not* thrown here: this function runs
|
|
462
462
|
* on every turn (coordinator prompt catalog) and at the top of every
|
package/src/prompts.ts
CHANGED
|
@@ -92,13 +92,13 @@ export function buildCoordinatorPrompt(
|
|
|
92
92
|
|
|
93
93
|
${formatExternalRoleCatalog(profiles, defaultHarness, configuredHarnessNames)}
|
|
94
94
|
|
|
95
|
-
Own the result and verify it yourself. Do the work directly unless a separate harness, a fresh context, or a bounded parallel review earns the overhead. A role is the job. A harness (${EXTERNAL_HARNESSES.join(", ")}, or a registered pi-* name) is the environment. Prefer the configured default harness unless the user asks for another or the task needs a capability the default lacks. Select both through
|
|
95
|
+
Own the result and verify it yourself. Do the work directly unless a separate harness, a fresh context, or a bounded parallel review earns the overhead. A role is the job. A harness (${EXTERNAL_HARNESSES.join(", ")}, or a registered pi-* name) is the environment. Prefer the configured default harness unless the user asks for another or the task needs a capability the default lacks. Select both through external_delegate or workflow. Follow the user's own approval policy before widening a delegation. This extension has no fixed multi-agent approval threshold.
|
|
96
96
|
|
|
97
97
|
Give every child an objective, the context it needs, the changes it may make, a deliverable, and the check you will run. Use absolute paths and say whether the task is read-only or may edit. Permission is the call's tier, otherwise defaultPermission (danger unless changed). Omit it when the child should keep that default, including a reviewer that may use a shell and still must not edit. A danger tier still has to stay inside the authorization the user gave you. agy accepts only danger and rejects readonly and edit.
|
|
98
98
|
|
|
99
99
|
Choose a fresh child or resume on purpose. Omit context for independent work. context {mode:"recent", turns:N} shares the last N user turns, including the current turn; {mode:"full"} shares the available post-compaction conversation. Snapshots drop thinking, system instructions, and pending calls, reject images and payloads over 1 MiB, and are sent to that harness. resume continues the same CLI child and cannot be combined with sharing. A named pi-* harness has no persisted session and no enforced budget. Prefer resume for the same task and a new child for an independent review.
|
|
100
100
|
|
|
101
|
-
Calls block unless background:true.
|
|
101
|
+
Calls block unless background:true. Background runs do not notify or resume this session; collect them with external_runs (wait, then inspect with view "final"). Check failed outcomes and diagnostics instead of expecting a final answer from a failed run. Same-harness parallel work shares one maxConcurrentSubagents cap: parallel([() => agent(...), ...]) or separate background external_delegate calls in one turn. Sequential awaits stay serial. Report milestones. Retry a failed run, or switch its model or harness, only when the user authorized that step. agy alone may make one disclosed infrastructure retry. Nested agents may start in another workspace.
|
|
102
102
|
|
|
103
103
|
Catalog availability reflects enabled harnesses and built-in/authored roles, not CLI installation or authentication. Disabled harnesses reject calls without fallback; manage them through /external config. Harness and role lifecycle commands live under /external config harness and /external config role. Role selection never falls back. external_help topic usage is the playbook; roles, permissions, and workflow are the references.`;
|
|
104
104
|
}
|
|
@@ -115,7 +115,7 @@ export interface UsageAgentExample {
|
|
|
115
115
|
context?: { mode: "none" } | { mode: "recent"; turns: number } | { mode: "full" };
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
/** One self-contained
|
|
118
|
+
/** One self-contained external_delegate call. Valid against the external_delegate tool schema. Permission stays omitted. */
|
|
119
119
|
export const USAGE_ONE_AGENT: UsageAgentExample = {
|
|
120
120
|
description: "Map the repository",
|
|
121
121
|
prompt: "Map /absolute/repo. Read only. Return the important files and how they fit together. Do not edit.",
|
|
@@ -203,7 +203,7 @@ function jsonBlock(value: unknown): string {
|
|
|
203
203
|
/** Worked playbook served by external_help topic "usage". Examples are the exported constants. */
|
|
204
204
|
export function formatUsagePlaybook(): string {
|
|
205
205
|
const script = usageWorkflowScript();
|
|
206
|
-
return `Practical delegation playbook. A role is the work. A harness is the environment: ${EXTERNAL_HARNESSES.join(", ")}, or a registered pi-* name.
|
|
206
|
+
return `Practical delegation playbook. A role is the work. A harness is the environment: ${EXTERNAL_HARNESSES.join(", ")}, or a registered pi-* name. external_delegate and workflow select every one of those harnesses the same way. Prefer the configured default harness. Name another when the user asks for it or the task needs a capability the default lacks. Do the task directly unless a separate environment, an independent context, or bounded parallel work is worth the coordination. You still own the outcome: read the result and check the deliverable. Follow the user's approval policy before widening a delegation. There is no fixed team-size threshold here.
|
|
207
207
|
|
|
208
208
|
A named pi-* harness runs in-process on the SDK's built-in tools. Preset minimal leaves skills unloaded. Preset skills loads installed skills, and project skills only when the project is trusted. Those presets select skill loading. They are not this extension's tools and they are not web search. Resume does not continue a pi-* child, and a budget there is recorded without enforcement.
|
|
209
209
|
|
|
@@ -211,9 +211,9 @@ Tell each child the objective, the context it needs, what it may change, the del
|
|
|
211
211
|
|
|
212
212
|
Choose fresh context or resume deliberately. Omit context, or pass {"mode":"none"}, for independent work such as a review. {"mode":"recent","turns":N} and {"mode":"full"} share parent conversation and cannot be combined with resume. resume continues one prior CLI run that recorded a session id, on the same backend: ${EXTERNAL_HARNESSES.join(", ")}. A named pi-* harness has no persisted session, so resume does not continue it. Prefer resume for the same task and a new child when you want an independent judgment.
|
|
213
213
|
|
|
214
|
-
Calls block until the child finishes. background true returns a run id while the originating session still owns the work. Use external_runs to wait, then
|
|
214
|
+
Calls block until the child finishes. background true returns a run id while the originating session still owns the work. Background runs do not notify or resume this session. Use external_runs to wait, then inspect with view final, and judge that answer yourself. For failed runs, check the outcome and diagnostics; view final only provides verified successful answers. A failed or aborted run stays failed. Retry it, or switch model or harness, only when the user authorized that step. agy may make one disclosed infrastructure retry; that exception is not yours to extend.
|
|
215
215
|
|
|
216
|
-
One
|
|
216
|
+
One external_delegate call. Permission stays omitted; the prompt carries the no-edit intent:
|
|
217
217
|
${jsonBlock(USAGE_ONE_AGENT)}
|
|
218
218
|
|
|
219
219
|
Independent review. Fresh context, a reviewer role, the default tier, no resume:
|
package/src/workflow/tool.ts
CHANGED
|
@@ -394,7 +394,7 @@ export function createWorkflowTool(
|
|
|
394
394
|
}),
|
|
395
395
|
});
|
|
396
396
|
// Seed an initial queued observation immediately, mirroring the
|
|
397
|
-
// direct
|
|
397
|
+
// direct external_delegate tool's own top-of-executeRun registry.update: a
|
|
398
398
|
// registered entry otherwise carries no observation at all until
|
|
399
399
|
// the backend's first progress event fires deep inside
|
|
400
400
|
// spawnSubagent (after limiter acquisition), so a workflow child
|
|
@@ -512,7 +512,7 @@ export function createWorkflowTool(
|
|
|
512
512
|
// Transition this child's OWN registry entry (not just the
|
|
513
513
|
// workflow's aggregate snapshot above) from queued to running
|
|
514
514
|
// immediately after the concurrency limiter granted its slot —
|
|
515
|
-
// mirroring the direct
|
|
515
|
+
// mirroring the direct external_delegate tool's own top-of-executeRun
|
|
516
516
|
// registry.update — and BEFORE any backend progress arrives.
|
|
517
517
|
// Without this, the child's entry only left "queued" once
|
|
518
518
|
// spawnSubagent's own onProgress first fired deep inside
|