@arhen/pi-core-subagent 1.3.50 → 1.3.52
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/package.json +1 -1
- package/src/index.ts +11 -20
- package/src/manager.ts +8 -1
- package/src/schemas.ts +6 -13
- package/src/types.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arhen/pi-core-subagent",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.52",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "pi extension: fast in-process subagents with a dependency-graph scheduler (needs edges gate tasks and carry upstream output into dependent prompts), plus background runs, intercom and agent-to-agent mailbox. Leader defines agents inline.",
|
|
6
6
|
"license": "MIT",
|
package/src/index.ts
CHANGED
|
@@ -128,21 +128,18 @@ export default function (pi: ExtensionAPI) {
|
|
|
128
128
|
label: "Subagent",
|
|
129
129
|
|
|
130
130
|
description:
|
|
131
|
-
"Run isolated subagents (own context, own session)
|
|
131
|
+
"Run isolated subagents (own context, own session) in the background: returns a runId immediately, completion notifies you. One call = one agent (`agent`+`task`) or many (`tasks`, or `chain` with `{previous}`). `needs` edges gate tasks and prepend upstream outputs to their prompts. A user agent file (`.agents/agents`, `.claude/agents`, `.pi/agents`; project dirs, then home) whose `description` matches the goal is authoritative: body = system prompt, frontmatter `model`/`tools` apply, but explicit per-call `tools`/`write` override the file's tools. Write agents get an isolated git worktree; the result reports the branch. Children always carry talk tools (ask/notify the leader, message siblings).",
|
|
132
132
|
promptSnippet: "Define and delegate work to specialized subagents.",
|
|
133
133
|
promptGuidelines: [
|
|
134
134
|
"Use subagent when independent review, testing, research, or parallel analysis improves quality.",
|
|
135
|
-
"
|
|
136
|
-
"
|
|
137
|
-
"
|
|
138
|
-
"
|
|
139
|
-
"
|
|
140
|
-
"
|
|
141
|
-
"
|
|
142
|
-
"
|
|
143
|
-
"Never block with nothing to do: if you have no work left after spawning, end your turn. Task completion notifies you and wakes a fresh turn with the results — await_subagent/autoAwait in that situation only burns time and tokens.",
|
|
144
|
-
"autoAwait:true only when the same turn must consume the result immediately (e.g. you spawn a reviewer and then must act on its verdict before replying). Otherwise spawn background and read results from the completion notice, or subagent_result when you come back.",
|
|
145
|
-
"await_subagent is for the rare case where you have parallel work of your own and need to sync at a specific point — not the default follow-up to a spawn.",
|
|
135
|
+
"Batch every sub-task in ONE call: subagent({ tasks: [...] }) — never multiple parallel subagent calls.",
|
|
136
|
+
"Declare ordering with `needs` edges on the tasks, never by splitting into separate calls; dependents receive upstream outputs automatically — do not restate them. Prefer flat `tasks` (plain parallel); add `needs` only when ordering genuinely matters.",
|
|
137
|
+
"End each task with a runnable check, e.g. 'Verify: bun test'. A subagent's claim of success is not evidence.",
|
|
138
|
+
"Write agents work in an isolated git worktree; their changes land on a branch — review the diff, then merge with `git merge --no-ff <branch>`. Never leave a worktree branch unmerged at the end of the task.",
|
|
139
|
+
"Define each agent inline: invented name, focused system prompt, read-only by default (write:true to edit). A matched agent file takes over (see description); matching is by description, not name — name the agent whatever fits the goal.",
|
|
140
|
+
"Right after spawning, call subagent_status(runId) ONCE before any other work — a child that died on spawn (or never started) is invisible until far later otherwise. If it shows a task failed/never started, fix or respawn immediately.",
|
|
141
|
+
"Never block with nothing to do: if you have no work left after spawning, end your turn — completion notifies you and wakes a fresh turn with the results. await_subagent/autoAwait while idle only burns time and tokens.",
|
|
142
|
+
"autoAwait:true only when this SAME turn must consume the result immediately. await_subagent is for syncing with your own parallel work — not the default follow-up to a spawn.",
|
|
146
143
|
],
|
|
147
144
|
parameters: SubagentParams,
|
|
148
145
|
executionMode: "parallel",
|
|
@@ -263,11 +260,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
263
260
|
name: "subagent_status",
|
|
264
261
|
label: "Subagent Status",
|
|
265
262
|
description:
|
|
266
|
-
"Live status of a subagent run (non-blocking)
|
|
263
|
+
"Live per-task status of a subagent run (non-blocking), incl. each child's session file path (JSONL) to `tail -f` from outside. Call once right after spawning to verify children actually started.",
|
|
267
264
|
promptSnippet: "Check progress of a subagent run; use right after spawn as a health check.",
|
|
268
|
-
promptGuidelines: [
|
|
269
|
-
"Health-check every background spawn with one subagent_status(runId) before continuing — catch dead-on-arrival children early instead of at completion time.",
|
|
270
|
-
],
|
|
271
265
|
parameters: RunIdParam,
|
|
272
266
|
async execute(_id, params) {
|
|
273
267
|
const { runId } = params as { runId: string };
|
|
@@ -313,10 +307,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
313
307
|
name: "await_subagent",
|
|
314
308
|
label: "Await Subagent",
|
|
315
309
|
description:
|
|
316
|
-
"Block until a run finishes (or timeoutMs elapses).
|
|
317
|
-
promptGuidelines: [
|
|
318
|
-
"Do not call await_subagent right after spawning with no other work pending — end the turn and let the completion notice wake you.",
|
|
319
|
-
],
|
|
310
|
+
"Block until a run finishes (or timeoutMs elapses). Only when you have your own work to sync — otherwise end your turn; completion notifies you. While parked, child→leader messages (asks, notifies, completions) wake the wait and arrive inside the result.",
|
|
320
311
|
parameters: AwaitParam,
|
|
321
312
|
async execute(_id, params) {
|
|
322
313
|
const { runId, timeoutMs } = params as { runId: string; timeoutMs?: number };
|
package/src/manager.ts
CHANGED
|
@@ -434,7 +434,11 @@ export class SubagentManager {
|
|
|
434
434
|
}
|
|
435
435
|
|
|
436
436
|
private notifyTask(run: RunSnapshot, task: TaskSnapshot, kind: "completed" | "failed" | "aborted"): void {
|
|
437
|
-
|
|
437
|
+
// ponytail: child already pushed its summary via notify_parent — pointer only, no second copy. Upgrade: drop notice entirely if the pointer noise also proves useless.
|
|
438
|
+
const body =
|
|
439
|
+
kind === "completed" && task.notifiedParent
|
|
440
|
+
? `Task ${task.agent} (${task.id}) completed in run ${run.id} — summary already reported. Use subagent_result(runId: "${run.id}", taskId: "${task.id}") for full output.`
|
|
441
|
+
: makeTaskNotice(run, task, kind);
|
|
438
442
|
|
|
439
443
|
if (this.collectParked(run.id, { kind: "done", taskId: task.id, agent: task.agent, text: body })) {
|
|
440
444
|
this.emit("subagent:notification", { runId: run.id, taskId: task.id, kind, body });
|
|
@@ -452,6 +456,8 @@ export class SubagentManager {
|
|
|
452
456
|
extra?: { taskId?: string; question?: string },
|
|
453
457
|
): void {
|
|
454
458
|
if (kind !== "asked" && run.awaited) return;
|
|
459
|
+
// single-task completed run: the task notice already said everything (failure paths may not have notified per-task)
|
|
460
|
+
if (kind === "completed" && run.tasks.length === 1 && run.notifyPerTask) return;
|
|
455
461
|
const body =
|
|
456
462
|
kind === "asked"
|
|
457
463
|
? `A subagent is asking you a question (task ${extra?.taskId}): ${extra?.question ?? ""}\nReply with reply_subagent(runId: "${run.id}", taskId: "${extra?.taskId}", message: ...).`
|
|
@@ -576,6 +582,7 @@ export class SubagentManager {
|
|
|
576
582
|
},
|
|
577
583
|
onNotifyParent: (_taskId, message, level) => {
|
|
578
584
|
this.emit("subagent:intercom", { runId: run.id, taskId: task.id, kind: "notify", level, message });
|
|
585
|
+
task.notifiedParent = true;
|
|
579
586
|
|
|
580
587
|
if (this.collectParked(run.id, { kind: "notify", taskId: task.id, agent: task.agent, text: message })) return;
|
|
581
588
|
try {
|
package/src/schemas.ts
CHANGED
|
@@ -5,23 +5,17 @@ import { DEFAULT_CONCURRENCY, MAX_CONCURRENCY } from "./manager.ts";
|
|
|
5
5
|
const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
|
|
6
6
|
const TaskItem = Type.Object({
|
|
7
7
|
id: Type.Optional(Type.String({ description: "Optional stable task id" })),
|
|
8
|
-
agent: Type.String({
|
|
9
|
-
minLength: 1,
|
|
10
|
-
description:
|
|
11
|
-
"Agent name you invent. Always define the agent inline: prompt (system prompt) + toolset (write: true for write access). Never create agent files.",
|
|
12
|
-
}),
|
|
8
|
+
agent: Type.String({ minLength: 1, description: "Agent name you invent (defined inline via `prompt`)" }),
|
|
13
9
|
task: Type.String({ minLength: 1, description: "Task for this agent" }),
|
|
14
|
-
prompt: Type.Optional(
|
|
15
|
-
Type.String({ description: "System prompt defining this agent's behavior. Optional — a minimal default is used." }),
|
|
16
|
-
),
|
|
10
|
+
prompt: Type.Optional(Type.String({ description: "System prompt defining this agent's behavior" })),
|
|
17
11
|
write: Type.Optional(
|
|
18
12
|
Type.Boolean({
|
|
19
|
-
description: "true = write toolset (
|
|
13
|
+
description: "true = write toolset (adds bash, edit, write); default false = read-only (read, grep, find, ls)",
|
|
20
14
|
}),
|
|
21
15
|
),
|
|
22
16
|
model: Type.Optional(Type.String({ description: "Model override (provider/model-id)" })),
|
|
23
17
|
thinking: Type.Optional(StringEnum(THINKING_LEVELS, { description: "Thinking level override" })),
|
|
24
|
-
cwd: Type.Optional(Type.String({ description: "Working directory
|
|
18
|
+
cwd: Type.Optional(Type.String({ description: "Working directory (default: current project)" })),
|
|
25
19
|
tools: Type.Optional(Type.Array(Type.String(), { description: "Explicit tool allowlist (overrides the toolset)" })),
|
|
26
20
|
maxRuntimeMs: Type.Optional(Type.Number({ description: "Per-task timeout (ms)" })),
|
|
27
21
|
needs: Type.Optional(
|
|
@@ -50,13 +44,12 @@ export const SubagentParams = Type.Object({
|
|
|
50
44
|
maxRuntimeMs: Type.Optional(
|
|
51
45
|
Type.Number({
|
|
52
46
|
description:
|
|
53
|
-
"Per-task timeout, ms. Omit unless a hard bound is genuinely required — a
|
|
47
|
+
"Per-task timeout, ms. Omit unless a hard bound is genuinely required — a ceiling always applies (6 h, or 1 h with `/subagents auto-limit on`).",
|
|
54
48
|
}),
|
|
55
49
|
),
|
|
56
50
|
autoAwait: Type.Optional(
|
|
57
51
|
Type.Boolean({
|
|
58
|
-
description:
|
|
59
|
-
"Start the run in the background, then park this tool call until it finishes and return the final result inline (runId + summary in one response). Default false.",
|
|
52
|
+
description: "Park this call until the run finishes and return the result inline. Default false.",
|
|
60
53
|
}),
|
|
61
54
|
),
|
|
62
55
|
notifyPerTask: Type.Optional(
|