@sgtbeatdown/pi-tasks 0.1.0 → 0.1.2
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 +69 -447
- package/package.json +9 -4
- package/skills/task-context-summary/SKILL.md +34 -0
- package/skills/wolfpack-pi-task-delegation/SKILL.md +34 -56
- package/src/extension.ts +217 -259
- package/src/gateway-client.ts +409 -0
- package/src/in-memory-task-relay.ts +114 -0
- package/src/index.ts +35 -0
- package/src/legacy-extension.ts +248 -0
- package/src/legacy-task-inbox.ts +148 -0
- package/src/metrics-cli.ts +20 -0
- package/src/metrics.ts +288 -0
- package/src/sqlite-database.ts +68 -0
- package/src/task-artifacts.ts +153 -0
- package/src/task-board.ts +365 -0
- package/src/task-core.ts +383 -0
- package/src/task-inbox.ts +109 -0
- package/src/task-protocol.ts +129 -0
- package/src/task-relay-conformance.ts +23 -0
- package/src/task-store.ts +238 -0
- package/src/v1-compat-extension.ts +1 -0
- package/src/wolfpack-task-relay.ts +319 -0
- package/src/auto-notify.ts +0 -24
- package/src/protocol.ts +0 -73
- package/src/store.ts +0 -494
- package/src/stores/filesystem.ts +0 -40
- package/src/task-communication.ts +0 -68
- package/src/transports/wolfpack.ts +0 -29
- package/src/types.ts +0 -114
package/README.md
CHANGED
|
@@ -1,502 +1,131 @@
|
|
|
1
1
|
# pi-tasks
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
```
|
|
3
|
+
`pi-tasks` is a Pi extension client for Wolfpack task transports. The retained v1 adapter uses Wolfpack's machine-global `~/.wolfpack/tasks` store; v2 keeps endpoint-owned Pi task state locally and uses Wolfpack as a content-blind relay. Neither adapter writes assignments to terminals. Read Wolfpack's [harness-neutral task adapter contract](https://github.com/almogdepaz/wolfpack/blob/main/docs/task-adapter-contract.md) for canonical adapter behavior and its [task gateway guide](https://github.com/almogdepaz/wolfpack/blob/main/docs/task-gateway.md) for routes and operations.
|
|
89
4
|
|
|
90
|
-
|
|
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
|
-
}
|
|
5
|
+
## v2 endpoint-owned relay
|
|
121
6
|
|
|
122
|
-
|
|
123
|
-
registerAgentTaskTools(pi, {
|
|
124
|
-
store: createFilesystemTaskStore({ tasksDir: ".pi/tasks" }),
|
|
125
|
-
transport: createCliTransport(pi),
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
```
|
|
7
|
+
The default extension uses the configured local Wolfpack relay v2 adapter, never the in-memory conformance relay. It registers an opaque endpoint with `POST /api/task-relay/v2/connect`, stores endpoint-owned task state at the deterministic per-session path `~/.pi/tasks/v2/sessions/<sha256(WOLFPACK_SESSION_NAME)>/tasks.sqlite`, and exchanges opaque relay envelopes through Wolfpack. The adapter needs a Wolfpack release exposing the stable [relay v2 control-api contract](https://github.com/almogdepaz/wolfpack/blob/main/docs/control-api-schema.md#pi-tasks-relay-v2-boundary); a v1-only gateway is incompatible and does not silently fall back.
|
|
129
8
|
|
|
130
|
-
|
|
9
|
+
Set `WOLFPACK_SESSION_NAME` for every Pi process. The adapter uses `WOLFPACK_PORT` when the local control port differs from `18790`; `WOLFPACK_SESSION_NAME` resolves the active Pi process to its relay endpoint. On startup, obtain a target's opaque `{ relay, id }` endpoint from its Wolfpack session-status projection after that target has registered, then send it through the endpoint-owned tool shape:
|
|
131
10
|
|
|
132
11
|
```json
|
|
133
12
|
{
|
|
134
|
-
"to": "
|
|
135
|
-
"task": "
|
|
136
|
-
"timeoutMs": 1800000
|
|
13
|
+
"to": { "relay": "wolfpack-pi-tasks-v2", "id": "target-opaque-endpoint-id" },
|
|
14
|
+
"task": "implement the narrow change and run focused tests"
|
|
137
15
|
}
|
|
138
16
|
```
|
|
139
17
|
|
|
140
|
-
|
|
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.
|
|
18
|
+
Wolfpack's relay owns durable mailbox delivery and peer forwarding; Pi owns task lifecycle, logical event order, receipts, and its local SQLite state. Insertion receipts are idempotent by `{ taskId, eventId }`, so replay after Pi has structurally recorded an event cannot create an additional logical receipt. `createInMemoryTaskRelay` remains exported solely as a deterministic conformance fixture.
|
|
210
19
|
|
|
211
|
-
|
|
20
|
+
## v1 gateway adapter (retained)
|
|
212
21
|
|
|
213
|
-
|
|
214
|
-
stable session name:
|
|
22
|
+
The v1 gateway client is available only through the published `@sgtbeatdown/pi-tasks/v1-compat-extension` entry. This is an explicit opt-in: after resolving the installed package root, add `src/v1-compat-extension.ts` as an extension path in Pi settings instead of loading the package default. The package manifest loads only `src/extension.ts` (v2); v2 does not fall back to v1 when its relay is unavailable or incompatible. Its v1 behavior and operational requirements are documented below.
|
|
215
23
|
|
|
216
|
-
|
|
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
|
-
```
|
|
24
|
+
## requirements and trust boundary
|
|
223
25
|
|
|
224
|
-
|
|
225
|
-
|
|
26
|
+
- a reachable local Wolfpack v1 task gateway on every participating machine;
|
|
27
|
+
- this package loaded in every participating Pi process;
|
|
28
|
+
- `WOLFPACK_SESSION_NAME` set by Wolfpack so the local gateway can resolve the caller to a stable broker ID; and
|
|
29
|
+
- `WOLFPACK_PORT` when the local control port is not the default `18790`.
|
|
226
30
|
|
|
227
|
-
|
|
31
|
+
The boundary is trusted local processes and trusted Tailnet machines. Pi only calls its own local gateway. The gateways perform direct fetch federation; Pi never fetches a peer directly. v1 adds no task capability authentication. Normal Wolfpack global JWT behavior is not bypassed, and JWT federation is unsupported: peer delivery fails clearly when credentials would be required.
|
|
228
32
|
|
|
229
|
-
|
|
33
|
+
## task address and send
|
|
230
34
|
|
|
231
|
-
|
|
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:
|
|
35
|
+
A target is always `{ machine, sessionId }`, where `sessionId` is the stable opaque broker ID returned by Wolfpack session control. Use `machine: "local"` for same-machine work. For a peer, use the receiver's canonical HTTPS Tailnet origin, for example `https://worker.example.ts.net`; do not supply a hostname label, path, query, fragment, credentials, unexpected port, or arbitrary HTTPS URL.
|
|
240
36
|
|
|
241
37
|
```json
|
|
242
38
|
{
|
|
243
|
-
"to": "
|
|
244
|
-
"task": "
|
|
245
|
-
"
|
|
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
|
-
};
|
|
39
|
+
"to": { "machine": "local", "sessionId": "receiver-broker-id" },
|
|
40
|
+
"task": "implement the narrow change and run focused tests",
|
|
41
|
+
"context": {
|
|
42
|
+
"summary": "## constraints and preferences\n- keep scope narrow\n## key decisions\n- preserve the public contract",
|
|
43
|
+
"refs": [{ "path": "src/extension.ts", "purpose": "affected tool wiring" }]
|
|
307
44
|
},
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
registerAgentTaskTools(pi, {
|
|
312
|
-
store: createFilesystemTaskStore({ tasksDir: ".pi/tasks" }),
|
|
313
|
-
transport: httpTransport,
|
|
314
|
-
});
|
|
45
|
+
"metadata": { "phaseId": "phase-1", "issueId": "task-3", "verificationTier": "focused" },
|
|
46
|
+
"onCompletePrompt": "review the receiver diff before reporting completion",
|
|
47
|
+
"timeoutMs": 1800000
|
|
315
48
|
}
|
|
316
49
|
```
|
|
317
50
|
|
|
318
|
-
|
|
319
|
-
implementation directly. The worker should report a terminal result equivalent
|
|
320
|
-
to:
|
|
51
|
+
### minimal valid send envelope
|
|
321
52
|
|
|
322
53
|
```json
|
|
323
54
|
{
|
|
324
|
-
"
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
55
|
+
"to": {
|
|
56
|
+
"machine": "local",
|
|
57
|
+
"sessionId": "receiver-broker-id"
|
|
58
|
+
},
|
|
59
|
+
"task": "implement the narrow change and run focused tests"
|
|
328
60
|
}
|
|
329
61
|
```
|
|
330
62
|
|
|
331
|
-
|
|
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.
|
|
63
|
+
`INVALID_REQUEST.error.path` is an optional RFC 6901 JSON Pointer to the rejected send field, for example `/preflight/requiredProject`; use it rather than parsing error prose. A pre-persistence validation rejection creates no task, so correct the field and retry. If creation status is uncertain, idempotency remains necessary.
|
|
359
64
|
|
|
360
|
-
|
|
65
|
+
Remote addressing changes only `to.machine`:
|
|
361
66
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
```bash
|
|
366
|
-
wolfpack session send <target-session> <assignment>
|
|
67
|
+
```json
|
|
68
|
+
{ "to": { "machine": "https://worker.example.ts.net", "sessionId": "receiver-broker-id" }, "task": "review the cited diff" }
|
|
367
69
|
```
|
|
368
70
|
|
|
369
|
-
|
|
370
|
-
filesystem store at `.pi/tasks/` plus the included Wolfpack transport.
|
|
71
|
+
Context is optional, curated Markdown plus deliberately selected ref metadata. The extension renders refs but never reads or copies their contents. Relative refs resolve in the receiver project; absolute refs are same-machine only and must remain inside the authoritative parent or receiver project root. Missing refs are warnings, not copied content or transcript transfer.
|
|
371
72
|
|
|
372
|
-
|
|
373
|
-
session and target a session name/id with `agent_task_send`.
|
|
73
|
+
Initial limits are 16 KiB UTF-8 each for task instructions and context summary, 48 KiB for the assignment envelope, and 64 KiB for an HTTP request body. The initial size limits require representative payload benchmarking before adjustment.
|
|
374
74
|
|
|
375
|
-
|
|
376
|
-
transport }` composition.
|
|
75
|
+
`agent_task_send` waits for durable gateway acceptance, not adapter insertion or task execution; delivery remains pending until `task.delivered`. A remote initial send has one initial attempt: the receiver persists a provisional receipt, then receives sender confirmation before Pi can see the assignment. Later peer confirmation, messages, terminal updates, cancellation, delivery notices, and parent acknowledgment have four total attempts: the initial attempt plus retries around 1, 2, and 4 seconds with jitter. Exhaustion is a visible local delivery failure, not a background queue or offline dispatch promise.
|
|
377
76
|
|
|
378
|
-
|
|
77
|
+
Normal delegation is fire-and-forget. Use `agent_task_wait` only when the user explicitly asks to block.
|
|
78
|
+
|
|
79
|
+
## tools, messages, and acknowledgment
|
|
379
80
|
|
|
380
81
|
| tool | purpose |
|
|
381
82
|
| --- | --- |
|
|
382
|
-
| `agent_task_send` | create a
|
|
383
|
-
| `agent_task_status` | read
|
|
384
|
-
| `agent_task_wait` |
|
|
385
|
-
| `agent_task_inbox` |
|
|
386
|
-
| `
|
|
387
|
-
| `
|
|
83
|
+
| `agent_task_send` | create a durable gateway assignment |
|
|
84
|
+
| `agent_task_status` | read task state, history, result, and warnings |
|
|
85
|
+
| `agent_task_wait` | explicitly poll a task until terminal or timeout |
|
|
86
|
+
| `agent_task_inbox` | read task events without acknowledgment |
|
|
87
|
+
| `agent_task_ack` | acknowledge one independently verified terminal task |
|
|
88
|
+
| `agent_task_message` | send a durable `question`, `answer`, or `information` event |
|
|
89
|
+
| `agent_task_cancel` | request cancellation |
|
|
90
|
+
| `agent_task_done` | receiver-only terminal completion; terminates the tool turn |
|
|
388
91
|
|
|
389
|
-
|
|
92
|
+
Use `agent_task_message` for clarification instead of terminal prose. Only one question can be unresolved per task; answers link to that question. Information is durable and does not change task state. An accepted receiver question terminates that receiver turn; a parent question does not.
|
|
390
93
|
|
|
391
|
-
|
|
94
|
+
Report source modifications in `result.changedFiles`. Artifacts are receiver-project-relative regular files for a parent to inspect, not a changed-file list:
|
|
392
95
|
|
|
393
96
|
```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
|
-
}
|
|
97
|
+
{ "result": { "changedFiles": ["src/extension.ts"] }, "artifacts": [{ "path": "verification/task-2.md" }] }
|
|
402
98
|
```
|
|
403
99
|
|
|
404
|
-
|
|
405
|
-
same terminal status and payload semantics.
|
|
406
|
-
|
|
407
|
-
Valid terminal statuses:
|
|
100
|
+
See the canonical Wolfpack artifact contract for provenance, containment, and warning behavior.
|
|
408
101
|
|
|
409
|
-
- `
|
|
410
|
-
- `failed`
|
|
411
|
-
- `cancelled`
|
|
412
|
-
- `timed_out`
|
|
413
|
-
- `rejected`
|
|
102
|
+
The sender gateway owns canonical event order, timeout, and terminal state. The first accepted terminal event wins. Sender timeout triggers best-effort remote cancellation; late terminals remain diagnostics. After independently verifying one terminal result, call `agent_task_ack({ taskId })`. Remote acknowledgment is two-phase: the receiver durably confirms pending acknowledgment before the sender records final parent acknowledgment. If that delivery fails, the task remains visible and a later explicit acknowledgment repairs it with the same event ID.
|
|
414
103
|
|
|
415
|
-
|
|
104
|
+
## structured delivery and replay
|
|
416
105
|
|
|
417
|
-
|
|
106
|
+
The extension polls the local gateway every five seconds. With no pending message, it submits each visible event as a structured custom message using Pi's safe `deliverAs: "followUp"` mode: idle Pi starts immediately, while an active turn finishes before the queued event starts the next turn. It never steers or interrupts active work. Delivery remains pending until `task.delivered`. The `{ taskId, eventId }` details let task context participate in the session without parsing prose.
|
|
418
107
|
|
|
419
|
-
|
|
108
|
+
On start or resume it rebuilds incorporated IDs from the full durable session entry set, not only active prompt context. A session gets only missing events; a new Pi session in the same Wolfpack PTY gets complete active history. The receiver records gateway delivery only after the structured insertion exists. A restart after insertion and before acknowledgment therefore acknowledges the existing entry without duplicate injection. Unknown model-visible events fail closed without advancing the cursor. Gateway delivery is at-least-once; this does not claim exactly-once model execution.
|
|
420
109
|
|
|
421
|
-
|
|
422
|
-
- `taskId`
|
|
423
|
-
- `fromSession`
|
|
424
|
-
- `instructions`
|
|
425
|
-
- protocol requirements telling the assignee to use `agent_task_done`
|
|
110
|
+
## parent workflow
|
|
426
111
|
|
|
427
|
-
|
|
112
|
+
1. create or select a Wolfpack Pi session with the canonical session-control workflow and retain its stable broker ID. Spawn a disposable worker without an initial model prompt so it reaches idle before assignment; put worker instructions in `agent_task_send.task`.
|
|
113
|
+
2. before remote dispatch, complete Wolfpack's [Live-peer readiness checklist](https://github.com/almogdepaz/wolfpack/blob/main/docs/task-gateway.md#live-peer-readiness-checklist) and retain operator-recorded package/reload evidence; only then check the target's local requirements and send compact instructions with curated context and selected refs when they save receiver investigation.
|
|
114
|
+
3. keep working; use `agent_task_status` or `agent_task_inbox` for structured follow-up, and `agent_task_message` for questions, answers, and decisions.
|
|
115
|
+
4. verify cited files, diffs, tests, and paths-only artifact metadata independently before reporting success.
|
|
116
|
+
5. acknowledge the accepted terminal result, then clean up only sessions this parent spawned. Keep a reusable implementer alive while review or correction remains possible; never make workers close their own sessions.
|
|
428
117
|
|
|
429
|
-
-
|
|
430
|
-
- `dispatched`
|
|
431
|
-
- `running`
|
|
432
|
-
- terminal status
|
|
433
|
-
- assignment/result refs
|
|
434
|
-
- event history
|
|
435
|
-
- optional result payload/artifacts/error
|
|
118
|
+
Isolated coverage is the deterministic acceptance gate, but a specific live peer still requires current readiness at the time of use. If the checklist cannot pass, stop before task creation and report fixture-only verification. Do not use direct peer URLs from Pi.
|
|
436
119
|
|
|
437
|
-
|
|
120
|
+
See `skills/wolfpack-pi-task-delegation/SKILL.md` for the operational workflow and `skills/task-context-summary/SKILL.md` for recovery/reuse summaries.
|
|
438
121
|
|
|
439
|
-
|
|
440
|
-
<tasksDir>/<taskId>/
|
|
441
|
-
├── task.json
|
|
442
|
-
├── assignment.json
|
|
443
|
-
├── events.jsonl
|
|
444
|
-
└── result.json
|
|
445
|
-
```
|
|
122
|
+
## deferred follow-up
|
|
446
123
|
|
|
447
|
-
|
|
124
|
+
These are documented operational debt, not production TODOs: exact Pi runtime registration and heartbeat/capability leases; durable offline initial dispatch; JWT/authenticated peer federation; artifact byte transfer and retention; representative payload benchmarking; automated recovery summaries; and summary caching only after measured repeated-generation waste. v1 has no queue, scheduler, artifact transfer, or transcript transfer.
|
|
448
125
|
|
|
449
|
-
##
|
|
126
|
+
## legacy historical metrics
|
|
450
127
|
|
|
451
|
-
`pi
|
|
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`
|
|
128
|
+
`task-metrics` and the task board remain read-only analyzers for already-existing historical `.pi/tasks` directories. They are not part of the runtime task protocol and must not create, mutate, or deliver v1 gateway tasks. Replacing or retiring that historical reporting surface requires a product decision about gateway-wide query/metrics ownership.
|
|
500
129
|
|
|
501
130
|
## development
|
|
502
131
|
|
|
@@ -505,10 +134,3 @@ bun install
|
|
|
505
134
|
bun test
|
|
506
135
|
bun run typecheck
|
|
507
136
|
```
|
|
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
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sgtbeatdown/pi-tasks",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
5
|
-
"module": "src/
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Durable Pi agent delegation through the Wolfpack task gateway.",
|
|
5
|
+
"module": "src/index.ts",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./src/index.ts",
|
|
8
|
+
"./v1-compat-extension": "./src/v1-compat-extension.ts"
|
|
9
|
+
},
|
|
6
10
|
"repository": {
|
|
7
11
|
"type": "git",
|
|
8
|
-
"url": "https://github.com/almogdepaz/pi-tasks.git"
|
|
12
|
+
"url": "git+https://github.com/almogdepaz/pi-tasks.git"
|
|
9
13
|
},
|
|
10
14
|
"keywords": [
|
|
11
15
|
"pi-package",
|
|
@@ -46,6 +50,7 @@
|
|
|
46
50
|
},
|
|
47
51
|
"scripts": {
|
|
48
52
|
"test": "bun test",
|
|
53
|
+
"task-metrics": "bun src/metrics-cli.ts",
|
|
49
54
|
"typecheck": "tsc --noEmit"
|
|
50
55
|
},
|
|
51
56
|
"type": "module"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: task-context-summary
|
|
3
|
+
description: Recover or reuse a bounded Wolfpack task context summary when the parent needs to reconstruct handoff context without copying a transcript.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# task context summary
|
|
7
|
+
|
|
8
|
+
parent authors normal summaries. use this skill only for recovery or reuse when the parent lacks enough already-loaded context.
|
|
9
|
+
|
|
10
|
+
## evidence order
|
|
11
|
+
|
|
12
|
+
1. inspect relevant docs/code first.
|
|
13
|
+
2. use an existing session summary next, if one exists.
|
|
14
|
+
3. inspect only the selective recent transcript needed to resolve remaining gaps.
|
|
15
|
+
|
|
16
|
+
do not copy a full transcript, infer unseen file contents, or automatically cache generated summaries. when evidence disagrees, state the disagreement explicitly instead of silently choosing a version.
|
|
17
|
+
|
|
18
|
+
## output
|
|
19
|
+
|
|
20
|
+
return concise Markdown, under 16KiB UTF-8, using any useful subset:
|
|
21
|
+
|
|
22
|
+
```md
|
|
23
|
+
## constraints and preferences
|
|
24
|
+
## progress
|
|
25
|
+
### done
|
|
26
|
+
### in progress
|
|
27
|
+
### blocked
|
|
28
|
+
## key decisions
|
|
29
|
+
## critical context
|
|
30
|
+
## failed approaches
|
|
31
|
+
## open questions
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
include selected refs separately in the task request. mention a ref's path, selector, and purpose only; do not read or duplicate ref contents merely to populate the summary.
|