@tshu-w/pi-agents 0.0.0-stage → 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/LICENSE +661 -0
- package/README.md +81 -2
- package/bin/daemon.mjs +204 -0
- package/bin/host.mjs +170 -0
- package/bin/pd.mjs +139 -0
- package/docs/PROGRAM.md +310 -0
- package/docs/SPEC.md +427 -0
- package/docs/WORKBENCH.md +165 -0
- package/package.json +62 -4
- package/src/agents/agents.ts +882 -0
- package/src/agents/registry.ts +257 -0
- package/src/agents/scheduler.ts +204 -0
- package/src/agents/search.ts +131 -0
- package/src/agents/tool.ts +205 -0
- package/src/daemon/client.mjs +84 -0
- package/src/daemon/router.mjs +103 -0
- package/src/daemon/sessions.mjs +129 -0
- package/src/daemon/version.mjs +19 -0
- package/src/host/client.mjs +139 -0
- package/src/host/extension.ts +74 -0
- package/src/host/frames.mjs +34 -0
- package/src/host/keys.mjs +19 -0
- package/src/index.ts +208 -0
- package/src/output.ts +56 -0
- package/src/params.ts +4 -0
- package/src/pi.mjs +16 -0
- package/src/programs/codemode.ts +74 -0
- package/src/programs/execute.ts +131 -0
- package/src/programs/host-patches.ts +128 -0
- package/src/programs/loadout.ts +99 -0
- package/src/programs/programs.ts +284 -0
- package/src/programs/sandbox.ts +90 -0
- package/src/programs/scope.ts +94 -0
- package/src/programs/tool.ts +198 -0
- package/src/render-call.ts +90 -0
- package/src/roots/discovery.mjs +113 -0
- package/src/roots/guard.mjs +227 -0
- package/src/roots/lock-extension.mjs +167 -0
- package/src/roots/locks.mjs +167 -0
- package/src/roots/paths.mjs +66 -0
- package/src/roots/report.ts +156 -0
- package/src/roots/runtime.ts +174 -0
- package/src/roots/snapshot.mjs +46 -0
- package/src/roots/socket-recovery.mjs +21 -0
- package/src/roots/transport.mjs +341 -0
- package/src/ui/panel.ts +96 -0
- package/src/ui/viewer.ts +445 -0
- package/src/workbench.mjs +587 -0
package/docs/SPEC.md
ADDED
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
# Pi Agents Specification
|
|
2
|
+
|
|
3
|
+
Pi Agents lets Agents in Pi discover, message, and wait for each other.
|
|
4
|
+
Programs built on these operations are specified in PROGRAM.md.
|
|
5
|
+
|
|
6
|
+
Terms follow Pi's
|
|
7
|
+
[Pico5 specification](https://github.com/earendil-works/pi/blob/main/packages/durable/docs/pico-v5.md).
|
|
8
|
+
|
|
9
|
+
## 1. Semantic model
|
|
10
|
+
|
|
11
|
+
An **Agent** is an addressable conversation with a stable identity. The Agent
|
|
12
|
+
that spawns another Agent is its **owner**. A root Agent has no owner and is
|
|
13
|
+
usually opened by a user. Ownership forms trees.
|
|
14
|
+
|
|
15
|
+
An Agent is shown by its name and **short ID**: the shortest prefix of its
|
|
16
|
+
ID, at least 8 characters, that no other Agent visible to the reader has.
|
|
17
|
+
An address is an ID, a name, or a unique ID prefix, tried in that order.
|
|
18
|
+
|
|
19
|
+
A **message** carries text from one Agent to another. An **input** asks the
|
|
20
|
+
recipient to act. A **write** only adds to the recipient's conversation.
|
|
21
|
+
|
|
22
|
+
A **turn** is one continuous run of an Agent's model and tools. Its final
|
|
23
|
+
assistant message is the **answer** to the inputs it handled.
|
|
24
|
+
|
|
25
|
+
An owner can **wait** for answers to the inputs it sends to its owned Agents.
|
|
26
|
+
|
|
27
|
+
**Visibility** determines which Agents an Agent can discover and message.
|
|
28
|
+
|
|
29
|
+
## 2. Operations
|
|
30
|
+
|
|
31
|
+
### 2.1 spawn
|
|
32
|
+
|
|
33
|
+
`spawn` creates a named, owned Agent and sends it a first input. It
|
|
34
|
+
accepts an optional working directory, model, thinking level, and forked
|
|
35
|
+
context, and returns the Agent's ID. Names are unique among siblings.
|
|
36
|
+
|
|
37
|
+
### 2.2 send
|
|
38
|
+
|
|
39
|
+
`send` sends a message to a visible Agent by address. An ambiguous or
|
|
40
|
+
unknown address is rejected.
|
|
41
|
+
|
|
42
|
+
An input to an Agent with a turn in progress is either answered by the current turn
|
|
43
|
+
(`steer`) or by a new turn after it (`followUp`, the default).
|
|
44
|
+
|
|
45
|
+
A write is added at the recipient's next tool boundary, or at once if it is
|
|
46
|
+
idle. A write can go to several Agents at once.
|
|
47
|
+
|
|
48
|
+
### 2.3 wait
|
|
49
|
+
|
|
50
|
+
`wait` returns results of inputs sent to owned Agents. It can target
|
|
51
|
+
specific Agents and take a timeout.
|
|
52
|
+
|
|
53
|
+
### 2.4 list
|
|
54
|
+
|
|
55
|
+
`list` returns visible Agents with their ID, name, working directory, and
|
|
56
|
+
state.
|
|
57
|
+
|
|
58
|
+
### 2.5 abort
|
|
59
|
+
|
|
60
|
+
`abort` stops an owned Agent's current turn and withdraws its queued
|
|
61
|
+
inputs. The Agent stays available.
|
|
62
|
+
|
|
63
|
+
## 3. Lifecycle
|
|
64
|
+
|
|
65
|
+
An Agent is `running` while a turn is in progress, `waiting` while its turn
|
|
66
|
+
waits in `wait` (§4), `queued` while its next turn waits for a slot (§4),
|
|
67
|
+
and `idle` otherwise. An Agent goes offline when its runtime unloads it; it
|
|
68
|
+
stays `idle`.
|
|
69
|
+
|
|
70
|
+
`send` returns once the recipient's runtime accepts the message, waking it
|
|
71
|
+
if needed. Acceptance does not mean the recipient has handled it; a message
|
|
72
|
+
not yet handled is lost if the recipient exits.
|
|
73
|
+
|
|
74
|
+
An input to an offline Agent wakes it. A write never wakes an Agent.
|
|
75
|
+
|
|
76
|
+
Owned Agents run independently of their owner's turns. When their owner
|
|
77
|
+
goes offline, they go offline too, from the bottom of the tree up, and their
|
|
78
|
+
unfinished inputs end as `aborted`. They return as `idle` when their owner
|
|
79
|
+
is loaded again.
|
|
80
|
+
|
|
81
|
+
On abort or when going offline, work that has not stopped within a timeout
|
|
82
|
+
is abandoned: it no longer counts as live work and may keep running until
|
|
83
|
+
the process exits; the timeout is reported.
|
|
84
|
+
|
|
85
|
+
A root Agent is `running` while it has a turn in progress or pending messages,
|
|
86
|
+
or while its tree has live work (§7 Task panel); otherwise it is `idle`.
|
|
87
|
+
|
|
88
|
+
## 4. Visibility and limits
|
|
89
|
+
|
|
90
|
+
An Agent sees every Agent in its own tree. A root Agent also sees other root
|
|
91
|
+
Agents.
|
|
92
|
+
|
|
93
|
+
Limits apply to owned Agents in each tree.
|
|
94
|
+
|
|
95
|
+
An owned Agent needs a slot to run a turn. `maxConcurrent` (default 3) is
|
|
96
|
+
the number of slots. An input that would start a turn waits for a free
|
|
97
|
+
slot, and `spawn` or `send` reports that it is queued. A `steer` input to
|
|
98
|
+
an Agent with a turn in progress joins it and needs no new slot. An Agent gives
|
|
99
|
+
up its slot while it waits in `wait`, and takes one again before
|
|
100
|
+
continuing, ahead of inputs that have not started.
|
|
101
|
+
|
|
102
|
+
An input is outstanding from acceptance until its turn ends or it is
|
|
103
|
+
withdrawn. `maxOutstanding` (default 8) limits outstanding inputs; an input
|
|
104
|
+
beyond it is rejected. `maxConcurrent` cannot exceed `maxOutstanding`.
|
|
105
|
+
|
|
106
|
+
## 5. Results and notifications
|
|
107
|
+
|
|
108
|
+
An input sent to an owned Agent ends as `completed` when its turn answers
|
|
109
|
+
it, `failed` when its turn fails, or `aborted` when it is aborted or
|
|
110
|
+
withdrawn. Its result is the answer or the error. Only inputs from the
|
|
111
|
+
owner have results; the recipient replies to other senders with `send`.
|
|
112
|
+
|
|
113
|
+
`wait` returns unread results in order and marks them read. On timeout, it
|
|
114
|
+
returns the results so far and names the Agents still pending. It can also
|
|
115
|
+
return read results again.
|
|
116
|
+
|
|
117
|
+
When an input ends and its owner is not waiting for that Agent, the owner
|
|
118
|
+
receives a notification, unless the owner aborted it. The notification is an input delivered as `steer`,
|
|
119
|
+
and it names the Agent and how the input ended. It does not include the
|
|
120
|
+
result; the owner reads the result with `wait`.
|
|
121
|
+
|
|
122
|
+
A recipient sees each message with its sender's name and ID.
|
|
123
|
+
|
|
124
|
+
## 6. Model-facing tool
|
|
125
|
+
|
|
126
|
+
### `agent`
|
|
127
|
+
|
|
128
|
+
description:
|
|
129
|
+
|
|
130
|
+
Create, message, and coordinate Agents. `spawn` creates an owned Agent and
|
|
131
|
+
sends its first input; `send` sends an input or a write to a visible Agent;
|
|
132
|
+
`wait` waits for owned Agents and returns unread results; `list` finds
|
|
133
|
+
visible Agents; `abort` stops an owned Agent's current turn and queued
|
|
134
|
+
inputs while keeping it available. When an owned Agent finishes an input
|
|
135
|
+
while its owner is not waiting for it, the owner receives a notification
|
|
136
|
+
naming the Agent and outcome; `wait` returns the result. Owned Agents under
|
|
137
|
+
the same root share `${maxConcurrent}` execution slots and a limit of
|
|
138
|
+
`${maxOutstanding}` inputs that have not ended. Inputs that start a new
|
|
139
|
+
turn queue when all slots are busy; new inputs are rejected at the input
|
|
140
|
+
limit.
|
|
141
|
+
|
|
142
|
+
promptSnippet:
|
|
143
|
+
|
|
144
|
+
Create, message, and coordinate Agents
|
|
145
|
+
|
|
146
|
+
promptGuidelines:
|
|
147
|
+
|
|
148
|
+
- Use `agent(action='spawn', name=..., message=...)` for concrete, bounded
|
|
149
|
+
work with a clear expected result. Keep the first message self-contained;
|
|
150
|
+
use `context='fork'` only when the Agent needs the caller's conversation.
|
|
151
|
+
- For independent work, start the Agents before waiting and partition tasks
|
|
152
|
+
into non-overlapping responsibilities.
|
|
153
|
+
- Use `agent(action='wait', ...)` when the next step depends on Agent
|
|
154
|
+
results. Prefer one longer wait over repeated short polling.
|
|
155
|
+
|
|
156
|
+
parameters:
|
|
157
|
+
|
|
158
|
+
- `action`: Operation and applicable parameters: `spawn(name, message, cwd?, context?, model?, thinkingLevel?)`, `send(target, message, deliverAs?)`, `wait(target?, history?, timeout?)`, `list(query?, state?, limit?, offset?)`, or `abort(target)`.
|
|
159
|
+
- `name`: Name of the new Agent for `spawn`; unique among the caller's Agents.
|
|
160
|
+
- `message`: Non-empty text for `spawn` or `send`.
|
|
161
|
+
- `cwd`: Working directory for `spawn` (default: the caller's current working directory).
|
|
162
|
+
- `context`: Context for `spawn` (default: fresh). fresh starts without the caller's conversation; fork snapshots it.
|
|
163
|
+
- `model`: Model for `spawn` as provider/modelId (default: the caller's model).
|
|
164
|
+
- `thinkingLevel`: Thinking level for `spawn`: off, minimal, low, medium, high, or xhigh (default: the caller's level).
|
|
165
|
+
- `target`: Agent ID, name, or unique ID prefix for `send`, `wait`, or `abort`. `wait` and `send` with `deliverAs='write'` also accept an array. When omitted for `wait`, selects all owned Agents with pending or unread results.
|
|
166
|
+
- `deliverAs`: Delivery for `send` (default: followUp). followUp waits until the recipient's current turn ends; steer delivers after its current tool calls, before the next model call; write delivers like steer but never starts a turn and has no result.
|
|
167
|
+
- `history`: Number of most recent read results to return again per selected Agent for `wait` (default: 0).
|
|
168
|
+
- `timeout`: Maximum seconds for `wait` (default: 30, min: 10, max: 3600). Timeout does not abort Agents.
|
|
169
|
+
- `query`: Search query for `list`, matching ID, name, cwd, summaries, and user messages. Case-insensitive; spaces mean AND; `|` means OR.
|
|
170
|
+
- `state`: State for `list`: running, waiting, queued, or idle.
|
|
171
|
+
- `limit`: Maximum Agents returned by `list` (default: 20, max: 200).
|
|
172
|
+
- `offset`: Number of Agents to skip for `list` (default: 0).
|
|
173
|
+
|
|
174
|
+
results:
|
|
175
|
+
|
|
176
|
+
In these formats, `<id>` is the Agent's short ID. Details use full IDs;
|
|
177
|
+
arrays remain present when empty.
|
|
178
|
+
|
|
179
|
+
`spawn`:
|
|
180
|
+
|
|
181
|
+
```text
|
|
182
|
+
Agent <name> (<id>) started.
|
|
183
|
+
Agent <name> (<id>) queued: all <N> slots are busy.
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Details:
|
|
187
|
+
|
|
188
|
+
```text
|
|
189
|
+
{ id, name, queued }
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
`send` returns acceptance, not completion:
|
|
193
|
+
|
|
194
|
+
```text
|
|
195
|
+
Input accepted by <name> (<id>).
|
|
196
|
+
Input accepted by <name> (<id>), queued: all <N> slots are busy.
|
|
197
|
+
Write accepted by <name> (<id>), ...
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Details:
|
|
201
|
+
|
|
202
|
+
```text
|
|
203
|
+
followUp / steer: { id, queued }
|
|
204
|
+
write: { ids }
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
`wait` returns one block per result, followed by pending Agents on timeout:
|
|
208
|
+
|
|
209
|
+
```text
|
|
210
|
+
<agent-result name="<name>" id="<id>" status="<outcome>">
|
|
211
|
+
<answer or error>
|
|
212
|
+
</agent-result>
|
|
213
|
+
|
|
214
|
+
Still pending: <name> (<id>), ...
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
A result returned again through `history` adds `history="true"`. A single
|
|
218
|
+
unread completed result, with nothing pending, is returned as the answer
|
|
219
|
+
alone. When there are no results or pending inputs:
|
|
220
|
+
|
|
221
|
+
```text
|
|
222
|
+
No results.
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Details:
|
|
226
|
+
|
|
227
|
+
```text
|
|
228
|
+
{ results: [{ id, name, state, history, result? }], pending: [id] }
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
`state` is the input outcome (§5); `history` marks a previously read result.
|
|
232
|
+
|
|
233
|
+
`list` returns one entry per Agent. With a query, entries are ordered by
|
|
234
|
+
their most recent match. When a summary or user message matches, the entry
|
|
235
|
+
shows the latest matching excerpt and its time:
|
|
236
|
+
|
|
237
|
+
```text
|
|
238
|
+
<name> (<id>) <state> <cwd>
|
|
239
|
+
YYYY-MM-DD HH:MM …<matching excerpt>…
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
When more entries remain:
|
|
243
|
+
|
|
244
|
+
```text
|
|
245
|
+
[N more results. Use offset=X to continue.]
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
When no Agents match:
|
|
249
|
+
|
|
250
|
+
```text
|
|
251
|
+
No matching Agents.
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Details:
|
|
255
|
+
|
|
256
|
+
```text
|
|
257
|
+
{ total, agents: [{ id, name?, ownerId?, state }] }
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
`agents` is the current page; `total` counts all matches. `state` is the
|
|
261
|
+
Agent's current state (§3).
|
|
262
|
+
|
|
263
|
+
`abort`:
|
|
264
|
+
|
|
265
|
+
```text
|
|
266
|
+
Agent <name> (<id>) aborted.
|
|
267
|
+
Agent <name> (<id>) has no turn or queued inputs.
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Details:
|
|
271
|
+
|
|
272
|
+
```text
|
|
273
|
+
{ id, aborted }
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
For unnamed root Agents, show the ID without a name or parentheses.
|
|
277
|
+
|
|
278
|
+
errors:
|
|
279
|
+
|
|
280
|
+
Invalid calls fail with an error. Errors that need a follow-up say what to
|
|
281
|
+
do:
|
|
282
|
+
|
|
283
|
+
```text
|
|
284
|
+
No visible Agent matches "<target>". Use list to find Agents.
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
```text
|
|
288
|
+
"<target>" matches several Agents:
|
|
289
|
+
<name> (<id>) <state> <cwd>
|
|
290
|
+
...
|
|
291
|
+
|
|
292
|
+
Retry with a longer ID prefix.
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
```text
|
|
296
|
+
Name "<name>" is already used by <id>.
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
```text
|
|
300
|
+
Input rejected: <N> inputs have not ended. Wait for results or abort an Agent.
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
```text
|
|
304
|
+
<name> (<id>) is not owned by the caller.
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
truncation:
|
|
308
|
+
|
|
309
|
+
Text beyond Pi's output limits is truncated with Pi's native notice, and
|
|
310
|
+
the full text is saved to a file whose path appears in the notice. `wait`
|
|
311
|
+
adds results in order; the result that crosses the limit is truncated but
|
|
312
|
+
keeps its closing tag, and later results are omitted. A truncated result
|
|
313
|
+
counts as read. Omitted results stay unread, and the text ends with:
|
|
314
|
+
|
|
315
|
+
```text
|
|
316
|
+
[Results omitted: <name> (<id>), ... Use wait with fewer targets.]
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Details retain metadata, `truncation`, and `fullOutputPath`.
|
|
320
|
+
`wait.results[].result` contains only retained text and is absent for
|
|
321
|
+
omitted results.
|
|
322
|
+
|
|
323
|
+
messages:
|
|
324
|
+
|
|
325
|
+
A notification to the owner:
|
|
326
|
+
|
|
327
|
+
```text
|
|
328
|
+
Agent <name> (<id>) <outcome>.
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
A recipient sees each message in an `agent-message` element naming the
|
|
332
|
+
sender. An input from a sender other than the owner also carries a note
|
|
333
|
+
asking for a reply:
|
|
334
|
+
|
|
335
|
+
```text
|
|
336
|
+
<agent-message from="<name>" id="<id>">
|
|
337
|
+
<body>
|
|
338
|
+
</agent-message>
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
```text
|
|
342
|
+
<agent-message from="<name>" id="<id>" note="Reply with send">
|
|
343
|
+
<body>
|
|
344
|
+
</agent-message>
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
## 7. Human interface
|
|
348
|
+
|
|
349
|
+
### Task panel
|
|
350
|
+
|
|
351
|
+
A panel above the editor shows the current Agent's live work:
|
|
352
|
+
|
|
353
|
+
Tasks (<N> live, /tasks to hide)
|
|
354
|
+
Agent <name> (<id>) <state> <N> pending
|
|
355
|
+
Agent <name> (<id>) <state>
|
|
356
|
+
Program <id> running
|
|
357
|
+
Agent <name> (<id>) <state>
|
|
358
|
+
+<N> more
|
|
359
|
+
|
|
360
|
+
The live work is the owned Agents that are not `idle`, and the running
|
|
361
|
+
background Programs with their Agents that are not `idle`. The rows list
|
|
362
|
+
them as a tree in the order they started, each
|
|
363
|
+
under its owner and each Program under its caller, with the owners needed to
|
|
364
|
+
keep the tree. Rows that do not fit are counted in the last row.
|
|
365
|
+
|
|
366
|
+
The panel updates as states change and is shown while it has a row.
|
|
367
|
+
`/tasks` hides or shows it.
|
|
368
|
+
|
|
369
|
+
### Agent viewer
|
|
370
|
+
|
|
371
|
+
`/agents` lists the Agents in the current Agent's tree, including Programs'
|
|
372
|
+
Agents, those not `idle` first and newest first within each group, with their state
|
|
373
|
+
and latest activity, and opens the selected one in a viewer. An Agent not owned
|
|
374
|
+
by the current Agent is named with its owners up to it, as
|
|
375
|
+
`<name> ‹ <owner> ‹ …`, where a Program is `Program <id>`. The viewer shows the
|
|
376
|
+
Agent's conversation and updates while it works; the current Session keeps
|
|
377
|
+
running behind it.
|
|
378
|
+
|
|
379
|
+
Text entered in the viewer is the user's input to that Agent and is sent as
|
|
380
|
+
in Pi's editor.
|
|
381
|
+
|
|
382
|
+
`app.interrupt` stops the Agent's current turn, as `abort` does.
|
|
383
|
+
`app.clear` clears the input, and closes the viewer when the input is empty.
|
|
384
|
+
|
|
385
|
+
### Workbench
|
|
386
|
+
|
|
387
|
+
`pd agents` opens the workbench, which lists every root Session on this
|
|
388
|
+
machine and lets the user open and message them. Interactive Sessions keep
|
|
389
|
+
running after their terminal closes. See [WORKBENCH.md](WORKBENCH.md).
|
|
390
|
+
|
|
391
|
+
## 8. Configuration
|
|
392
|
+
|
|
393
|
+
Settings live under `"pi-agents"` in the global `settings.json`:
|
|
394
|
+
|
|
395
|
+
```json
|
|
396
|
+
{
|
|
397
|
+
"pi-agents": {
|
|
398
|
+
"maxConcurrent": 3,
|
|
399
|
+
"maxOutstanding": 8,
|
|
400
|
+
"extensions": []
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
- `maxConcurrent`, `maxOutstanding`: limits from §4. Both are positive
|
|
406
|
+
integers with `maxConcurrent <= maxOutstanding`.
|
|
407
|
+
- `extensions`: extension paths loaded by owned Agents, relative to Pi's
|
|
408
|
+
agent directory. An owned Agent can use only tools its owner can use.
|
|
409
|
+
|
|
410
|
+
Invalid settings produce a startup warning, and the defaults are used.
|
|
411
|
+
|
|
412
|
+
## 9. Runtime
|
|
413
|
+
|
|
414
|
+
Each Agent is a persistent Pi Session. A root Agent is an ordinary Session.
|
|
415
|
+
An owned Agent is a child Session that records its owner and root, and runs
|
|
416
|
+
in its owner's process.
|
|
417
|
+
|
|
418
|
+
A delivered message enters the recipient's current branch. An offline root
|
|
419
|
+
Agent is started in a host, detached, with its saved working directory and
|
|
420
|
+
model and the current configuration (WORKBENCH §4).
|
|
421
|
+
If a root process cannot finish cleanup within a grace period on exit, it is
|
|
422
|
+
forcibly terminated.
|
|
423
|
+
Before shutting down an idle Session, another extension can emit
|
|
424
|
+
`busy:query` on `pi.events` with `{ busy: false }`; it is set to `true`
|
|
425
|
+
while the tree has live work.
|
|
426
|
+
One runtime uses a Session at a time. If a Session is occupied at interactive
|
|
427
|
+
startup, its lock error stays visible until the user dismisses it, then Pi exits.
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# Pi Agents Workbench
|
|
2
|
+
|
|
3
|
+
## 1. Overview
|
|
4
|
+
|
|
5
|
+
The workbench shows the user's root Sessions in one terminal view. From it the
|
|
6
|
+
user opens a Session, reads its latest result, or sends it a message. Sessions
|
|
7
|
+
started with `pd` keep running after their terminal closes.
|
|
8
|
+
|
|
9
|
+
Three kinds of process take part:
|
|
10
|
+
|
|
11
|
+
- **Host**: one per Session. It runs Pi's full TUI in a pseudo-terminal and
|
|
12
|
+
relays it to whichever terminal is attached.
|
|
13
|
+
- **Daemon**: one per user. It tracks Sessions, delivers messages, and starts
|
|
14
|
+
hosts.
|
|
15
|
+
- **Workbench**: the daemon's terminal view, opened with `pd agents`.
|
|
16
|
+
|
|
17
|
+
Every running root Session reports its state to the daemon. The daemon starts
|
|
18
|
+
when a Session or the workbench first needs it, and exits 30 seconds after its
|
|
19
|
+
last connection closes.
|
|
20
|
+
|
|
21
|
+
Files live in `${XDG_STATE_HOME:-~/.local/state}/pi/agents/`:
|
|
22
|
+
|
|
23
|
+
- `workbench.json`: seen marks and which Sessions are listed. The file is
|
|
24
|
+
versioned.
|
|
25
|
+
- `host.log`, `daemon.log`: logs.
|
|
26
|
+
|
|
27
|
+
Sockets and Session locks go in `$XDG_RUNTIME_DIR/pi-agents/`. macOS has no
|
|
28
|
+
`XDG_RUNTIME_DIR`, so there they go in `runtime/` under the state directory.
|
|
29
|
+
|
|
30
|
+
## 2. Setup
|
|
31
|
+
|
|
32
|
+
Install with npm to get the `pd` command, also named `pi-agents`, then have Pi
|
|
33
|
+
load the extension from the same copy, so the command and the extension are
|
|
34
|
+
always the same version:
|
|
35
|
+
|
|
36
|
+
npm install -g @tshu-w/pi-agents
|
|
37
|
+
pi install "$(npm root -g)/@tshu-w/pi-agents"
|
|
38
|
+
|
|
39
|
+
`pd` runs whichever `pi` is on `PATH`. Sessions started with plain `pi` also
|
|
40
|
+
load the extension, but run outside hosts and end with their terminal.
|
|
41
|
+
|
|
42
|
+
## 3. Commands
|
|
43
|
+
|
|
44
|
+
- `pd [pi options]` starts Pi interactively in a new host and attaches the
|
|
45
|
+
terminal. If the Session is already running in a host, `pd` attaches to it
|
|
46
|
+
instead.
|
|
47
|
+
- `pd agents` opens the workbench.
|
|
48
|
+
- `pd attach <session>` attaches to a Session, starting it first if it is not
|
|
49
|
+
running.
|
|
50
|
+
- Non-interactive starts (`-p`, `--mode`, piped stdin) and package commands
|
|
51
|
+
(`pd install`, `pd update`, ...) run Pi directly.
|
|
52
|
+
|
|
53
|
+
A host inherits the environment of the process that asked for it, because Pi
|
|
54
|
+
reads terminal features from the environment once, at startup.
|
|
55
|
+
|
|
56
|
+
A Session running outside a host still appears in the workbench, but can be
|
|
57
|
+
attached only after it exits.
|
|
58
|
+
|
|
59
|
+
## 4. Attach and detach
|
|
60
|
+
|
|
61
|
+
Only one terminal is attached at a time. A new terminal takes over, and the
|
|
62
|
+
previous one is told how to reattach.
|
|
63
|
+
|
|
64
|
+
On attach, the host restores the screen and the terminal modes Pi has set
|
|
65
|
+
(keyboard protocols, bracketed paste, mouse, title, program status). Images
|
|
66
|
+
come back when the host then has Pi redraw.
|
|
67
|
+
|
|
68
|
+
A host started from a terminal waits for that terminal to attach before it
|
|
69
|
+
starts Pi, so Pi detects the terminal's colors and keyboard protocol just as it
|
|
70
|
+
would without a host. A host started in the background passes the queries Pi
|
|
71
|
+
sent at startup to the first terminal that attaches, and Pi finishes detecting
|
|
72
|
+
then.
|
|
73
|
+
|
|
74
|
+
Closing the window, pressing `Ctrl+Z` (the `pi-agents.detach` keybinding), or
|
|
75
|
+
running `/detach` detaches. The Session keeps running, and the terminal prints:
|
|
76
|
+
|
|
77
|
+
Pi keeps running. To reattach, run pd attach <session>
|
|
78
|
+
|
|
79
|
+
A host disables suspend, since a suspended Pi would stay stopped; `Ctrl+Z`
|
|
80
|
+
detaches in its place.
|
|
81
|
+
|
|
82
|
+
A detached Session exits once its tree has had no live work for 5 minutes. A
|
|
83
|
+
message to a Session that is not running starts it in a host, detached.
|
|
84
|
+
|
|
85
|
+
## 5. Workbench
|
|
86
|
+
|
|
87
|
+
The list shows running Sessions, Sessions that ran without a terminal (started
|
|
88
|
+
here, woken by a message, or detached), Sessions opened here, and Sessions
|
|
89
|
+
with an unseen turn. Older Sessions are reached with `/resume`.
|
|
90
|
+
|
|
91
|
+
Each row shows a root Session's name and what it did last. The name is the
|
|
92
|
+
Session name, or else the first line of its first message. While the Session
|
|
93
|
+
works, the row shows the running tool; otherwise, the start of its last reply.
|
|
94
|
+
Running owned Agents and Programs are counted, as in `3 agents running`.
|
|
95
|
+
|
|
96
|
+
Rows are grouped by state, newest first in each group:
|
|
97
|
+
|
|
98
|
+
- **Needs input**: a dialog waiting for the user, or a finished turn the user
|
|
99
|
+
has not seen. A turn that failed after Pi's retries, or a host that exited
|
|
100
|
+
abnormally, is marked failed.
|
|
101
|
+
- **Working**: the Session or its tree has live work.
|
|
102
|
+
- **Completed**: everything else.
|
|
103
|
+
|
|
104
|
+
While a Session works, its icon spins. Otherwise the shape tells whether Pi is
|
|
105
|
+
still running (`●`) or has exited (`○`), and the color marks a finished (green)
|
|
106
|
+
or failed (red) turn, or a dialog waiting for the user (yellow). The header
|
|
107
|
+
shows Pi's logo, the size of each group, and key hints.
|
|
108
|
+
|
|
109
|
+
A turn counts as seen once it has been shown in an attached terminal. Sessions
|
|
110
|
+
from before the workbench start out seen. A Session that holds its lock but is
|
|
111
|
+
not reporting to the daemon, as while its Pi exits, shows as unknown (`?`); the
|
|
112
|
+
daemon checks it again every second until that changes.
|
|
113
|
+
|
|
114
|
+
Keys:
|
|
115
|
+
|
|
116
|
+
- `Enter` attaches; detaching returns here.
|
|
117
|
+
- `Space` previews the selected Session below the list: the conversation
|
|
118
|
+
since the last compaction, with thinking left out and each run of tool
|
|
119
|
+
calls folded to one line, such as `... (6 tool calls: bash, read, edit)`.
|
|
120
|
+
The preview follows the Session as it works.
|
|
121
|
+
The input keeps `@<session>` at its start, so a message goes to the
|
|
122
|
+
previewed Session. In the preview:
|
|
123
|
+
- `Enter` sends a typed message; with none typed, it attaches.
|
|
124
|
+
- `Ctrl+O` (`app.tools.expand`) attaches.
|
|
125
|
+
- The page keys of fullscreen mode (`tui.altScreen.pageUp`/`pageDown`)
|
|
126
|
+
scroll; with no message typed, `↑`/`↓` scroll too.
|
|
127
|
+
- `Esc` clears a typed message; with none typed, it closes the preview.
|
|
128
|
+
- `Ctrl+X` stops the current turn, as `app.interrupt` does.
|
|
129
|
+
- `Ctrl+D` removes the selected Session from the list. Its file stays, and
|
|
130
|
+
`/resume` brings it back. A running Session must quit first.
|
|
131
|
+
- `Ctrl+Z` (`pi-agents.detach`) exits the workbench, as detaching leaves a
|
|
132
|
+
Session; every Session keeps running.
|
|
133
|
+
|
|
134
|
+
The input at the bottom:
|
|
135
|
+
|
|
136
|
+
- `@` on an empty input opens Pi's session picker. The chosen Session is
|
|
137
|
+
selected in the list, and the input starts with `@<session>`; the text after
|
|
138
|
+
it goes to that Session as a user message. After `/`, Tab completes a
|
|
139
|
+
command.
|
|
140
|
+
- `/resume` opens Pi's session picker over all directories, newest first, and
|
|
141
|
+
attaches the chosen Session. `Ctrl+D` in the picker deletes a Session file.
|
|
142
|
+
- `/quit` exits the workbench, as do `Esc` and `Ctrl+C` twice on an empty
|
|
143
|
+
input.
|
|
144
|
+
- Any other text starts a new Session in the current directory with Pi's
|
|
145
|
+
default settings, detached.
|
|
146
|
+
|
|
147
|
+
## 6. Upgrades
|
|
148
|
+
|
|
149
|
+
A host keeps running the version it started with until its Session exits.
|
|
150
|
+
When the daemon sees a newer pi-agents version, it restarts once idle, and
|
|
151
|
+
Sessions reconnect to the new daemon.
|
|
152
|
+
|
|
153
|
+
## 7. Platform
|
|
154
|
+
|
|
155
|
+
macOS and Linux. Every dependency is pure JavaScript or prebuilt: hosts use
|
|
156
|
+
`@lydell/node-pty`, and a Session lock is a file that records the process
|
|
157
|
+
holding it.
|
|
158
|
+
|
|
159
|
+
## Not in this version
|
|
160
|
+
|
|
161
|
+
Attaching several terminals to one Session, Sessions on other machines,
|
|
162
|
+
model-written summaries, and a confirmation before quitting while work is
|
|
163
|
+
running. Keeping Sessions alive across host crashes and reboots needs a
|
|
164
|
+
durable runtime, which [Pi Durable](https://earendil.com/posts/pi-durable/)
|
|
165
|
+
explores.
|
package/package.json
CHANGED
|
@@ -1,6 +1,64 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tshu-w/pi-agents",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Multi-agent runtime for Pi: async agents, programmatic delegation, and inter-agent messaging",
|
|
5
|
+
"license": "AGPL-3.0-only",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/tshu-w/pi-agents.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/tshu-w/pi-agents#readme",
|
|
11
|
+
"type": "module",
|
|
12
|
+
"bin": {
|
|
13
|
+
"pd": "bin/pd.mjs",
|
|
14
|
+
"pi-agents": "bin/pd.mjs"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"bin",
|
|
18
|
+
"src",
|
|
19
|
+
"docs"
|
|
20
|
+
],
|
|
21
|
+
"keywords": [
|
|
22
|
+
"pi-package",
|
|
23
|
+
"pi-extension",
|
|
24
|
+
"agents",
|
|
25
|
+
"multi-agent"
|
|
26
|
+
],
|
|
27
|
+
"pi": {
|
|
28
|
+
"extensions": [
|
|
29
|
+
"./src/index.ts"
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"test": "node --test --test-timeout=30000 tests/",
|
|
34
|
+
"typecheck": "tsc -p tsconfig.json"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@lydell/node-pty": "^1.2.0-beta.15",
|
|
38
|
+
"@xterm/addon-serialize": "^0.14.0",
|
|
39
|
+
"@xterm/headless": "^6.0.0"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"@earendil-works/pi-ai": "*",
|
|
43
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
44
|
+
"@earendil-works/pi-tui": "*",
|
|
45
|
+
"typebox": "*"
|
|
46
|
+
},
|
|
47
|
+
"peerDependenciesMeta": {
|
|
48
|
+
"@earendil-works/pi-ai": {
|
|
49
|
+
"optional": true
|
|
50
|
+
},
|
|
51
|
+
"@earendil-works/pi-coding-agent": {
|
|
52
|
+
"optional": true
|
|
53
|
+
},
|
|
54
|
+
"@earendil-works/pi-tui": {
|
|
55
|
+
"optional": true
|
|
56
|
+
},
|
|
57
|
+
"typebox": {
|
|
58
|
+
"optional": true
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"typescript": "5.9.3"
|
|
63
|
+
}
|
|
64
|
+
}
|