@sgtbeatdown/pi-tasks 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +514 -0
- package/package.json +52 -0
- package/skills/wolfpack-pi-task-delegation/SKILL.md +75 -0
- package/src/auto-notify.ts +24 -0
- package/src/extension.ts +308 -0
- package/src/protocol.ts +73 -0
- package/src/store.ts +494 -0
- package/src/stores/filesystem.ts +40 -0
- package/src/task-communication.ts +68 -0
- package/src/transports/wolfpack.ts +29 -0
- package/src/types.ts +114 -0
- package/tsconfig.json +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
# pi-tasks
|
|
2
|
+
|
|
3
|
+
Structured task communication for Pi agents.
|
|
4
|
+
|
|
5
|
+
`pi-tasks` lets one Pi session hand work to another agent, session, process, or
|
|
6
|
+
worker and get durable structured results back. The core model is transport- and
|
|
7
|
+
runner-neutral: local sessions, HTTP workers, queues, hosted control planes, and
|
|
8
|
+
terminal multiplexers can all implement the same task lifecycle.
|
|
9
|
+
|
|
10
|
+
## what this is
|
|
11
|
+
|
|
12
|
+
A task lifecycle and communication layer:
|
|
13
|
+
|
|
14
|
+
1. create a structured assignment
|
|
15
|
+
2. persist task state in a store
|
|
16
|
+
3. dispatch the assignment through a transport
|
|
17
|
+
4. let the sender keep working
|
|
18
|
+
5. record status/results in structured files/data, not terminal prose
|
|
19
|
+
6. require the assignee to finish through the task protocol
|
|
20
|
+
|
|
21
|
+
## what this is not
|
|
22
|
+
|
|
23
|
+
This is **not** a subagent spawner. Use any process manager, terminal/session
|
|
24
|
+
manager, HTTP worker pool, queue, cron job, or custom runner to create/host
|
|
25
|
+
agents. `pi-tasks` only gives those agents a shared task protocol.
|
|
26
|
+
|
|
27
|
+
## mental model
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
parent pi session
|
|
31
|
+
agent_task_send
|
|
32
|
+
↓
|
|
33
|
+
TaskStore creates task + durable refs
|
|
34
|
+
TaskTransport delivers assignment text to target
|
|
35
|
+
↓
|
|
36
|
+
target runner/session/worker
|
|
37
|
+
receives pi.task.assignment.v1
|
|
38
|
+
does work
|
|
39
|
+
completes task through agent_task_done or equivalent store/server completion API
|
|
40
|
+
↓
|
|
41
|
+
parent pi session
|
|
42
|
+
agent_task_status / agent_task_wait / agent_task_inbox
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The `to` field is transport-specific:
|
|
46
|
+
|
|
47
|
+
- session name
|
|
48
|
+
- worker id
|
|
49
|
+
- queue/topic name
|
|
50
|
+
- tmux pane
|
|
51
|
+
- process id
|
|
52
|
+
- service-specific address
|
|
53
|
+
|
|
54
|
+
## requirements for any runner
|
|
55
|
+
|
|
56
|
+
For task communication to work, you need exactly these pieces:
|
|
57
|
+
|
|
58
|
+
1. **running target** — an already-open Pi session, worker, process, or service.
|
|
59
|
+
`pi-tasks` does not open or close it.
|
|
60
|
+
2. **transport** — code that delivers the assignment text to that target. This
|
|
61
|
+
can be `tmux paste-buffer`, HTTP POST, queue publish, file write, ssh, etc.
|
|
62
|
+
3. **reachable task store** — parent and target must read/write the same task
|
|
63
|
+
state. With `createFilesystemTaskStore`, that means the same cwd and shared
|
|
64
|
+
`.pi/tasks` directory. For multi-host setups without shared disk, implement a
|
|
65
|
+
remote `TaskStore`.
|
|
66
|
+
4. **completion capability** — Pi targets need this extension loaded so they have
|
|
67
|
+
`agent_task_done`. Non-Pi workers need an equivalent completion API/store
|
|
68
|
+
write.
|
|
69
|
+
5. **preserved task id** — the target must complete the exact `taskId` from the
|
|
70
|
+
`pi.task.assignment.v1` assignment.
|
|
71
|
+
|
|
72
|
+
If those are true, session lifecycle is irrelevant. Open agents with tmux,
|
|
73
|
+
screen, ssh, a process manager, or manually; `pi-tasks` only handles the task
|
|
74
|
+
protocol once the agents exist.
|
|
75
|
+
|
|
76
|
+
## install
|
|
77
|
+
|
|
78
|
+
Install locally into Pi:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
pi install npm:@sgtbeatdown/pi-tasks
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Temporary one-off run:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
pi -e npm:@sgtbeatdown/pi-tasks
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Every participating Pi session must load this extension. If a target Pi session
|
|
91
|
+
does not have `agent_task_done`, the sender can still create/dispatch the task,
|
|
92
|
+
but it will remain non-terminal until timeout or cancellation.
|
|
93
|
+
|
|
94
|
+
## quick start: local/custom transport
|
|
95
|
+
|
|
96
|
+
Most integrations reuse the filesystem store and provide only a transport:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
100
|
+
import { registerAgentTaskTools } from "@sgtbeatdown/pi-tasks/src/extension";
|
|
101
|
+
import { createFilesystemTaskStore } from "@sgtbeatdown/pi-tasks/src/stores/filesystem";
|
|
102
|
+
import type { TaskTransport } from "@sgtbeatdown/pi-tasks/src/task-communication";
|
|
103
|
+
|
|
104
|
+
function createCliTransport(pi: ExtensionAPI): TaskTransport {
|
|
105
|
+
return {
|
|
106
|
+
name: "local-cli",
|
|
107
|
+
getCurrentSessionName(env) {
|
|
108
|
+
return env.PI_TASK_SESSION ?? env.USER ?? "unknown-session";
|
|
109
|
+
},
|
|
110
|
+
async dispatchTask({ target, assignment, signal }) {
|
|
111
|
+
const result = await pi.exec("my-session-send", [target, assignment], { signal });
|
|
112
|
+
if (result.code === 0) return { ok: true };
|
|
113
|
+
return {
|
|
114
|
+
ok: false,
|
|
115
|
+
message: result.stderr || result.stdout || "my-session-send failed",
|
|
116
|
+
retryable: true,
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export default function extension(pi: ExtensionAPI): void {
|
|
123
|
+
registerAgentTaskTools(pi, {
|
|
124
|
+
store: createFilesystemTaskStore({ tasksDir: ".pi/tasks" }),
|
|
125
|
+
transport: createCliTransport(pi),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Then call `agent_task_send` from the parent session:
|
|
131
|
+
|
|
132
|
+
```json
|
|
133
|
+
{
|
|
134
|
+
"to": "worker-a",
|
|
135
|
+
"task": "inspect the auth middleware and report risks",
|
|
136
|
+
"timeoutMs": 1800000
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The parent should keep working and later use `agent_task_status`,
|
|
141
|
+
`agent_task_wait`, or `agent_task_inbox`. Do **not** infer completion from
|
|
142
|
+
terminal output.
|
|
143
|
+
|
|
144
|
+
## tmux recipe: already-open pi agents
|
|
145
|
+
|
|
146
|
+
Use this when you manually open Pi agents in tmux panes and only need task
|
|
147
|
+
communication between them.
|
|
148
|
+
|
|
149
|
+
### 1. create a tmux transport extension
|
|
150
|
+
|
|
151
|
+
Create `.pi/extensions/pi-tasks-tmux.ts` in the project:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
155
|
+
import { registerAgentTaskTools } from "@sgtbeatdown/pi-tasks/src/extension";
|
|
156
|
+
import { createFilesystemTaskStore } from "@sgtbeatdown/pi-tasks/src/stores/filesystem";
|
|
157
|
+
import type { TaskTransport } from "@sgtbeatdown/pi-tasks/src/task-communication";
|
|
158
|
+
|
|
159
|
+
function createTmuxTransport(pi: ExtensionAPI): TaskTransport {
|
|
160
|
+
return {
|
|
161
|
+
name: "tmux",
|
|
162
|
+
getCurrentSessionName(env) {
|
|
163
|
+
return env.PI_TASK_SESSION ?? env.TMUX_PANE ?? `pid-${process.pid}`;
|
|
164
|
+
},
|
|
165
|
+
async dispatchTask({ target, assignment, task, signal }) {
|
|
166
|
+
const bufferName = `pi-task-${task.id}`;
|
|
167
|
+
const setBuffer = await pi.exec("tmux", ["set-buffer", "-b", bufferName, assignment], { signal });
|
|
168
|
+
if (setBuffer.code !== 0) {
|
|
169
|
+
return {
|
|
170
|
+
ok: false,
|
|
171
|
+
message: setBuffer.stderr || setBuffer.stdout || "tmux set-buffer failed",
|
|
172
|
+
retryable: true,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const paste = await pi.exec("tmux", ["paste-buffer", "-d", "-b", bufferName, "-t", target], { signal });
|
|
177
|
+
if (paste.code !== 0) {
|
|
178
|
+
return {
|
|
179
|
+
ok: false,
|
|
180
|
+
message: paste.stderr || paste.stdout || "tmux paste-buffer failed",
|
|
181
|
+
retryable: true,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const enter = await pi.exec("tmux", ["send-keys", "-t", target, "Enter"], { signal });
|
|
186
|
+
if (enter.code !== 0) {
|
|
187
|
+
return {
|
|
188
|
+
ok: false,
|
|
189
|
+
message: enter.stderr || enter.stdout || "tmux send-keys failed",
|
|
190
|
+
retryable: true,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return { ok: true };
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export default function extension(pi: ExtensionAPI): void {
|
|
200
|
+
registerAgentTaskTools(pi, {
|
|
201
|
+
// All tmux panes must run in the same project cwd so they share .pi/tasks.
|
|
202
|
+
store: createFilesystemTaskStore({ tasksDir: ".pi/tasks" }),
|
|
203
|
+
transport: createTmuxTransport(pi),
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
This transport does not open panes. It only pastes assignment text into an
|
|
209
|
+
already-running pane and presses Enter.
|
|
210
|
+
|
|
211
|
+
### 2. open the agents yourself
|
|
212
|
+
|
|
213
|
+
Start each Pi agent in the same repo/cwd and load the extension. Give each one a
|
|
214
|
+
stable session name:
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
# pane 1
|
|
218
|
+
PI_TASK_SESSION=parent pi -e ./.pi/extensions/pi-tasks-tmux.ts
|
|
219
|
+
|
|
220
|
+
# pane 2
|
|
221
|
+
PI_TASK_SESSION=worker-a pi -e ./.pi/extensions/pi-tasks-tmux.ts
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
If the extension is installed with `pi install`, you can omit `-e`; the important
|
|
225
|
+
parts are same cwd, same `.pi/tasks`, and distinct `PI_TASK_SESSION` values.
|
|
226
|
+
|
|
227
|
+
### 3. find the target pane id
|
|
228
|
+
|
|
229
|
+
From inside tmux:
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
tmux list-panes -a -F '#{pane_id} #{pane_current_path} #{pane_title}'
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Pane ids look like `%12`. Use that pane id as `agent_task_send.to`.
|
|
236
|
+
|
|
237
|
+
### 4. send a task
|
|
238
|
+
|
|
239
|
+
From the parent Pi session, call:
|
|
240
|
+
|
|
241
|
+
```json
|
|
242
|
+
{
|
|
243
|
+
"to": "%12",
|
|
244
|
+
"task": "inspect the auth middleware and report risks",
|
|
245
|
+
"timeoutMs": 1800000
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Expected flow:
|
|
250
|
+
|
|
251
|
+
1. parent creates `.pi/tasks/<taskId>/...`
|
|
252
|
+
2. tmux transport pastes the assignment into pane `%12`
|
|
253
|
+
3. worker receives `pi.task.assignment.v1`
|
|
254
|
+
4. worker does the work
|
|
255
|
+
5. worker calls `agent_task_done` with that `taskId`
|
|
256
|
+
6. parent reads the result via `agent_task_status`, `agent_task_wait`, or
|
|
257
|
+
`agent_task_inbox`
|
|
258
|
+
|
|
259
|
+
### tmux troubleshooting
|
|
260
|
+
|
|
261
|
+
- If the target cannot complete the task, confirm it loaded the extension and has
|
|
262
|
+
`agent_task_done` available.
|
|
263
|
+
- If status never changes, confirm parent and worker are in the same cwd and both
|
|
264
|
+
can see the same `.pi/tasks/<taskId>` directory.
|
|
265
|
+
- If dispatch fails, run `tmux paste-buffer` manually against the same target id;
|
|
266
|
+
the pane id may be stale.
|
|
267
|
+
- Do not use terminal output as the source of truth. The task result is the store
|
|
268
|
+
state, not visible prose in the pane.
|
|
269
|
+
|
|
270
|
+
## using with non-pi workers
|
|
271
|
+
|
|
272
|
+
A non-Pi worker can participate if it implements the same lifecycle:
|
|
273
|
+
|
|
274
|
+
1. receive `pi.task.assignment.v1`
|
|
275
|
+
2. do the assigned work
|
|
276
|
+
3. write/report a terminal result with the same status and payload semantics
|
|
277
|
+
4. never overwrite an already-terminal task
|
|
278
|
+
|
|
279
|
+
Example HTTP transport from a Pi extension:
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
283
|
+
import { registerAgentTaskTools } from "@sgtbeatdown/pi-tasks/src/extension";
|
|
284
|
+
import { createFilesystemTaskStore } from "@sgtbeatdown/pi-tasks/src/stores/filesystem";
|
|
285
|
+
import type { TaskTransport } from "@sgtbeatdown/pi-tasks/src/task-communication";
|
|
286
|
+
|
|
287
|
+
const httpTransport: TaskTransport = {
|
|
288
|
+
name: "http",
|
|
289
|
+
getCurrentSessionName(env) {
|
|
290
|
+
return env.PI_TASK_SESSION ?? "unknown-session";
|
|
291
|
+
},
|
|
292
|
+
async dispatchTask({ target, assignment, task, signal }) {
|
|
293
|
+
const response = await fetch(`https://tasks.example/sessions/${target}/assignments`, {
|
|
294
|
+
method: "POST",
|
|
295
|
+
headers: { "content-type": "application/json" },
|
|
296
|
+
body: JSON.stringify({ taskId: task.id, assignment }),
|
|
297
|
+
signal,
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
if (response.ok) return { ok: true };
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
ok: false,
|
|
304
|
+
message: await response.text(),
|
|
305
|
+
retryable: response.status >= 500,
|
|
306
|
+
};
|
|
307
|
+
},
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
export default function extension(pi: ExtensionAPI): void {
|
|
311
|
+
registerAgentTaskTools(pi, {
|
|
312
|
+
store: createFilesystemTaskStore({ tasksDir: ".pi/tasks" }),
|
|
313
|
+
transport: httpTransport,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
For non-Pi workers, expose a completion endpoint or let workers use your store
|
|
319
|
+
implementation directly. The worker should report a terminal result equivalent
|
|
320
|
+
to:
|
|
321
|
+
|
|
322
|
+
```json
|
|
323
|
+
{
|
|
324
|
+
"taskId": "task_...",
|
|
325
|
+
"status": "completed",
|
|
326
|
+
"summary": "finished the assigned work",
|
|
327
|
+
"result": {}
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## file inbox / polling workers
|
|
332
|
+
|
|
333
|
+
A pushed message is not required. A transport can durably drop assignments into a
|
|
334
|
+
file inbox or queue and return success once the assignment is written.
|
|
335
|
+
|
|
336
|
+
One simple design:
|
|
337
|
+
|
|
338
|
+
- store tasks in `.pi/tasks`
|
|
339
|
+
- transport writes assignment text to `.pi/task-inbox/<target>/<taskId>.txt`
|
|
340
|
+
- worker watches its inbox directory
|
|
341
|
+
- worker reads the assignment, does the work, then completes through a store API
|
|
342
|
+
or by invoking a Pi session that has `agent_task_done`
|
|
343
|
+
|
|
344
|
+
Completion is separate from dispatch. Dispatch means “assignment delivered,” not
|
|
345
|
+
“work finished.”
|
|
346
|
+
|
|
347
|
+
## when to implement a custom store
|
|
348
|
+
|
|
349
|
+
Do **not** implement a custom store just because you have a new dispatch
|
|
350
|
+
mechanism. Implement `TaskStore` only when task state itself must be somewhere
|
|
351
|
+
else:
|
|
352
|
+
|
|
353
|
+
- central HTTP service
|
|
354
|
+
- Redis/Postgres/SQLite shared by multiple machines
|
|
355
|
+
- multi-host queue system
|
|
356
|
+
- hosted control plane
|
|
357
|
+
|
|
358
|
+
If only delivery changes, keep the filesystem store.
|
|
359
|
+
|
|
360
|
+
## included wolfpack transport
|
|
361
|
+
|
|
362
|
+
This package includes a Wolfpack transport for convenience. It is only a delivery
|
|
363
|
+
adapter:
|
|
364
|
+
|
|
365
|
+
```bash
|
|
366
|
+
wolfpack session send <target-session> <assignment>
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
It does not own storage. The default exported extension uses the generic
|
|
370
|
+
filesystem store at `.pi/tasks/` plus the included Wolfpack transport.
|
|
371
|
+
|
|
372
|
+
If you are using Wolfpack, install this extension in every participating Pi
|
|
373
|
+
session and target a session name/id with `agent_task_send`.
|
|
374
|
+
|
|
375
|
+
If you are not using Wolfpack, register the tools with your own `{ store,
|
|
376
|
+
transport }` composition.
|
|
377
|
+
|
|
378
|
+
## tools
|
|
379
|
+
|
|
380
|
+
| tool | purpose |
|
|
381
|
+
| --- | --- |
|
|
382
|
+
| `agent_task_send` | create a task and dispatch it through the configured transport |
|
|
383
|
+
| `agent_task_status` | read compact structured status for one task |
|
|
384
|
+
| `agent_task_wait` | wait in tool code for a terminal result when the user wants it now |
|
|
385
|
+
| `agent_task_inbox` | list terminal tasks for the current parent session |
|
|
386
|
+
| `agent_task_cancel` | cancel a non-terminal task |
|
|
387
|
+
| `agent_task_done` | assignee-side structured completion; terminates the target response |
|
|
388
|
+
|
|
389
|
+
### completion contract
|
|
390
|
+
|
|
391
|
+
Assigned Pi agents must call `agent_task_done` as their final action:
|
|
392
|
+
|
|
393
|
+
```json
|
|
394
|
+
{
|
|
395
|
+
"taskId": "task_...",
|
|
396
|
+
"status": "completed",
|
|
397
|
+
"summary": "inspected auth middleware; no bypass found",
|
|
398
|
+
"result": {
|
|
399
|
+
"filesReviewed": ["src/auth.ts"]
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
Non-Pi workers should complete through an equivalent store/server API with the
|
|
405
|
+
same terminal status and payload semantics.
|
|
406
|
+
|
|
407
|
+
Valid terminal statuses:
|
|
408
|
+
|
|
409
|
+
- `completed`
|
|
410
|
+
- `failed`
|
|
411
|
+
- `cancelled`
|
|
412
|
+
- `timed_out`
|
|
413
|
+
- `rejected`
|
|
414
|
+
|
|
415
|
+
Terminal completion is first-writer-wins.
|
|
416
|
+
|
|
417
|
+
## task protocol
|
|
418
|
+
|
|
419
|
+
Assignments are delivered as human-readable text plus a structured envelope with:
|
|
420
|
+
|
|
421
|
+
- `type: "pi.task.assignment.v1"`
|
|
422
|
+
- `taskId`
|
|
423
|
+
- `fromSession`
|
|
424
|
+
- `instructions`
|
|
425
|
+
- protocol requirements telling the assignee to use `agent_task_done`
|
|
426
|
+
|
|
427
|
+
Task state includes:
|
|
428
|
+
|
|
429
|
+
- `pending`
|
|
430
|
+
- `dispatched`
|
|
431
|
+
- `running`
|
|
432
|
+
- terminal status
|
|
433
|
+
- assignment/result refs
|
|
434
|
+
- event history
|
|
435
|
+
- optional result payload/artifacts/error
|
|
436
|
+
|
|
437
|
+
Filesystem layout:
|
|
438
|
+
|
|
439
|
+
```text
|
|
440
|
+
<tasksDir>/<taskId>/
|
|
441
|
+
├── task.json
|
|
442
|
+
├── assignment.json
|
|
443
|
+
├── events.jsonl
|
|
444
|
+
└── result.json
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
Generic filesystem storage defaults to `.pi/tasks/` when used directly.
|
|
448
|
+
|
|
449
|
+
## architecture
|
|
450
|
+
|
|
451
|
+
`pi-tasks` separates storage from delivery:
|
|
452
|
+
|
|
453
|
+
```ts
|
|
454
|
+
interface TaskStore {
|
|
455
|
+
createOrReuseDispatchedTask(...): Promise<...>;
|
|
456
|
+
readTask(...): Promise<...>;
|
|
457
|
+
readTaskResult(...): Promise<...>;
|
|
458
|
+
expireTaskIfOverdue(...): Promise<...>;
|
|
459
|
+
waitForTask(...): Promise<...>;
|
|
460
|
+
listInbox(...): Promise<...>;
|
|
461
|
+
ackTask(...): Promise<...>;
|
|
462
|
+
cancelTask(...): Promise<...>;
|
|
463
|
+
completeTask(...): Promise<...>;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
interface TaskTransport {
|
|
467
|
+
getCurrentSessionName(env): string;
|
|
468
|
+
dispatchTask(input): Promise<DispatchTaskResult>;
|
|
469
|
+
}
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
Registration composes both:
|
|
473
|
+
|
|
474
|
+
```ts
|
|
475
|
+
registerAgentTaskTools(pi, {
|
|
476
|
+
store: createFilesystemTaskStore({ tasksDir: ".pi/tasks" }),
|
|
477
|
+
transport: myTransport,
|
|
478
|
+
});
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
## exported building blocks
|
|
482
|
+
|
|
483
|
+
- `src/extension.ts`
|
|
484
|
+
- `registerAgentTaskTools`
|
|
485
|
+
- `createDefaultTaskCommunicationLayer`
|
|
486
|
+
- `src/task-communication.ts`
|
|
487
|
+
- `TaskStore`
|
|
488
|
+
- `TaskTransport`
|
|
489
|
+
- `TaskCommunicationLayer`
|
|
490
|
+
- `src/stores/filesystem.ts`
|
|
491
|
+
- `createFilesystemTaskStore`
|
|
492
|
+
- `src/transports/wolfpack.ts`
|
|
493
|
+
- `createWolfpackTaskTransport`
|
|
494
|
+
- `src/protocol.ts`
|
|
495
|
+
- `buildAssignment`
|
|
496
|
+
- `compactTaskResult`
|
|
497
|
+
- `src/store.ts`
|
|
498
|
+
- low-level filesystem store functions, if you need finer control than
|
|
499
|
+
`createFilesystemTaskStore`
|
|
500
|
+
|
|
501
|
+
## development
|
|
502
|
+
|
|
503
|
+
```bash
|
|
504
|
+
bun install
|
|
505
|
+
bun test
|
|
506
|
+
bun run typecheck
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
## current limitations
|
|
510
|
+
|
|
511
|
+
- no built-in HTTP/Redis transport yet
|
|
512
|
+
- no runtime transport selector yet; compose a store/transport in an extension
|
|
513
|
+
- default extension uses the generic filesystem store plus the included Wolfpack transport
|
|
514
|
+
- package is source-first; install with `pi install npm:@sgtbeatdown/pi-tasks`
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sgtbeatdown/pi-tasks",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Structured task communication for Pi agents with pluggable stores and transports.",
|
|
5
|
+
"module": "src/extension.ts",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/almogdepaz/pi-tasks.git"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"pi-package",
|
|
12
|
+
"pi",
|
|
13
|
+
"agent",
|
|
14
|
+
"tasks"
|
|
15
|
+
],
|
|
16
|
+
"files": [
|
|
17
|
+
"README.md",
|
|
18
|
+
"src",
|
|
19
|
+
"skills",
|
|
20
|
+
"tsconfig.json"
|
|
21
|
+
],
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"@earendil-works/pi-ai": "*",
|
|
27
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
28
|
+
"@earendil-works/pi-tui": "*",
|
|
29
|
+
"typebox": "*"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@earendil-works/pi-ai": "^0.80.10",
|
|
33
|
+
"@earendil-works/pi-coding-agent": "^0.80.10",
|
|
34
|
+
"@earendil-works/pi-tui": "^0.80.10",
|
|
35
|
+
"@types/bun": "^1.3.14",
|
|
36
|
+
"typebox": "^1.3.6",
|
|
37
|
+
"typescript": "^7.0.2"
|
|
38
|
+
},
|
|
39
|
+
"pi": {
|
|
40
|
+
"extensions": [
|
|
41
|
+
"./src/extension.ts"
|
|
42
|
+
],
|
|
43
|
+
"skills": [
|
|
44
|
+
"./skills"
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"test": "bun test",
|
|
49
|
+
"typecheck": "tsc --noEmit"
|
|
50
|
+
},
|
|
51
|
+
"type": "module"
|
|
52
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wolfpack-pi-task-delegation
|
|
3
|
+
description: Use this whenever the user asks to open, spawn, create, or use a Wolfpack/Pi subagent to do work, delegate work to another session, or check delegated task results. This skill connects existing Wolfpack session-control knowledge with the agent_task communication tools so agents spawn/select sessions, send work with agent_task_send, and avoid terminal-output completion polling.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Wolfpack Pi Task Delegation
|
|
7
|
+
|
|
8
|
+
This skill is the Wolfpack transport guidance for the task communication layer. It does not replace `wolfpack-tailnet-control`; use that
|
|
9
|
+
skill/instructions for opening, selecting, inspecting, or controlling Wolfpack
|
|
10
|
+
sessions.
|
|
11
|
+
|
|
12
|
+
## Goal
|
|
13
|
+
|
|
14
|
+
When the user says something like “open a subagent and do X”, create or select a
|
|
15
|
+
Wolfpack/Pi session using existing Wolfpack control, then send X through the
|
|
16
|
+
structured `agent_task_*` task communication tools. Keep Wolfpack terminals
|
|
17
|
+
visible and steerable, but treat task state/results as the protocol.
|
|
18
|
+
|
|
19
|
+
## Workflow
|
|
20
|
+
|
|
21
|
+
1. If the request includes opening/spawning/creating a subagent, use the
|
|
22
|
+
existing Wolfpack control workflow (`wolfpack-tailnet-control`) to create the
|
|
23
|
+
session. Prefer the canonical CLI path:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
wolfpack agent spawn <project> --prompt 'you are a task worker. wait for structured pi.task.assignment.v1 assignments; finish assigned work only with agent_task_done.' --json
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`wolfpack session open <project> --prompt ... --json` is a deprecated alias;
|
|
30
|
+
use it only when that is the available command in the environment.
|
|
31
|
+
|
|
32
|
+
2. Parse the structured JSON response from Wolfpack for the created session
|
|
33
|
+
handle/name/id. Treat it as opaque. Do not infer the target from browser UI
|
|
34
|
+
labels, terminal prose, or pane previews.
|
|
35
|
+
|
|
36
|
+
3. Send the real work with `agent_task_send` to the selected/spawned session.
|
|
37
|
+
The task text should be the user’s requested work, not the bootstrap prompt.
|
|
38
|
+
|
|
39
|
+
4. Return immediately with the task id and target session unless the user asked
|
|
40
|
+
to wait. Continue local work if there is other useful work to do.
|
|
41
|
+
|
|
42
|
+
5. Use structured task tools for follow-up:
|
|
43
|
+
- `agent_task_status` for one task
|
|
44
|
+
- `agent_task_wait` only when the user wants the result now
|
|
45
|
+
- `agent_task_inbox` to check completed delegated work
|
|
46
|
+
- `agent_task_cancel` to cancel non-terminal tasks
|
|
47
|
+
|
|
48
|
+
6. In target sessions, finish assigned tasks with `agent_task_done`. After
|
|
49
|
+
`agent_task_done`, do not send extra prose; the tool result is the completion
|
|
50
|
+
channel.
|
|
51
|
+
|
|
52
|
+
## Do Not
|
|
53
|
+
|
|
54
|
+
- Do not poll terminal output to decide that delegated work is complete.
|
|
55
|
+
- Do not ask the target to report completion in prose.
|
|
56
|
+
- Do not use `wolfpack session wait` for task completion; it waits for literal
|
|
57
|
+
terminal text and is the wrong protocol for this package.
|
|
58
|
+
- Do not duplicate Wolfpack session-control rules here. If session control is
|
|
59
|
+
ambiguous, consult/use `wolfpack-tailnet-control`.
|
|
60
|
+
|
|
61
|
+
## Examples
|
|
62
|
+
|
|
63
|
+
User: “open a subagent and inspect the auth middleware”
|
|
64
|
+
|
|
65
|
+
Expected approach:
|
|
66
|
+
1. Spawn a Wolfpack child session with the existing Wolfpack control workflow.
|
|
67
|
+
2. Call `agent_task_send` with the spawned session as `to` and “inspect the auth
|
|
68
|
+
middleware” as `task`.
|
|
69
|
+
3. Tell the user the task id and that you’ll check structured results later.
|
|
70
|
+
|
|
71
|
+
User: “check on the subagent”
|
|
72
|
+
|
|
73
|
+
Expected approach:
|
|
74
|
+
- Use `agent_task_inbox` or `agent_task_status`; do not read terminal prose to
|
|
75
|
+
infer done-ness.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { AgentTaskRecord } from "./types";
|
|
2
|
+
|
|
3
|
+
export function selectUnpromptedTerminalTasks(
|
|
4
|
+
tasks: readonly AgentTaskRecord[],
|
|
5
|
+
autoPromptedTaskIds: ReadonlySet<string>,
|
|
6
|
+
): readonly AgentTaskRecord[] {
|
|
7
|
+
return tasks.filter((task) => !autoPromptedTaskIds.has(task.id));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function buildBackgroundTaskNotificationPrompt(tasks: readonly AgentTaskRecord[]): string {
|
|
11
|
+
const taskLines = tasks.map((task) => {
|
|
12
|
+
const summary = task.error?.message ?? task.status;
|
|
13
|
+
return `- ${task.id}: status=${task.status}, target=${task.targetSession}, summary=${summary}`;
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
return [
|
|
17
|
+
"background task results are ready for this parent session.",
|
|
18
|
+
"call `agent_task_inbox` with `{ ack: true }` now to read and acknowledge the structured results.",
|
|
19
|
+
"summarize the completed task results to the user concisely.",
|
|
20
|
+
"do not call agent_task_wait; these tasks already reached a terminal state.",
|
|
21
|
+
"tasks:",
|
|
22
|
+
...taskLines,
|
|
23
|
+
].join("\n");
|
|
24
|
+
}
|