@tshu-w/pi-agents 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +94 -0
- package/bin/pd.mjs +21 -31
- package/docs/HOSTS.md +2 -2
- package/docs/PROGRAM.md +17 -28
- package/docs/SPEC.md +35 -39
- package/package.json +3 -2
- package/src/agents/agents.ts +154 -101
- package/src/agents/registry.ts +40 -7
- package/src/agents/tool.ts +31 -87
- package/src/index.ts +5 -3
- package/src/main-agent.md +5 -7
- package/src/main-agent.ts +53 -0
- package/src/output.ts +0 -18
- package/src/programs/loadout.ts +14 -19
- package/src/programs/programs.ts +39 -115
- package/src/programs/scope.ts +1 -1
- package/src/programs/tool.ts +16 -12
- package/src/roots/guard.mjs +5 -0
- package/src/roots/paths.mjs +1 -1
- package/src/ui/message.ts +41 -7
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.4.1] - 2026-11-04
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `pd main --name <name>` opens a main Agent per name, so that each channel can have its own.
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Inputs answered by the same turn share one notification.
|
|
12
|
+
|
|
13
|
+
## [0.4.0] - 2026-11-04
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- `spawn` takes `disown`: a root Agent starts another root Agent it does not own, which replies with `send`.
|
|
18
|
+
- A Program's notification carries the result's images.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- **Breaking:** `wait` only waits, for Agents and background Programs, and returns how it ended; results always arrive as notifications, also while waiting. The `history` parameter is removed.
|
|
23
|
+
- The notifications of what a `wait` waits for arrive together when it ends.
|
|
24
|
+
- `pd main` opens the main Agent from any directory, and starts one in `~` if there is none; `/new` in it starts a new main Agent.
|
|
25
|
+
|
|
26
|
+
## [0.3.0] - 2026-11-02
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- `pd main` opens the main Agent: one Session you talk to, which hands your work to other Agents and reports back.
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- The task panel and `/tasks` show the same tree, including the Agents that Programs start.
|
|
35
|
+
- `/tasks` lists only the running background Programs of other Agents.
|
|
36
|
+
- The task panel shows a task's cost only once there is any.
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
40
|
+
- Under Pi 1.4.0, a Program starts at once even while background Programs wait on Agents.
|
|
41
|
+
- The task viewer scrolls, and renders only what changed.
|
|
42
|
+
- Terminal input that arrives just before detaching no longer leaks into the shell.
|
|
43
|
+
- The workbench preview labels custom messages by their type.
|
|
44
|
+
|
|
45
|
+
## [0.2.0] - 2026-10-15
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
|
|
49
|
+
- `pd -p`, `--mode json`, and `--mode rpc` run on the Session in its host, so several clients and an attached terminal can use one Session at once.
|
|
50
|
+
- `/program <file>` runs a saved Program; `/tasks` opens background Programs.
|
|
51
|
+
- `agent().send()` in Programs accepts a `timeout`.
|
|
52
|
+
- Program results list Agents' `send()` calls and name the calls still running when the script returned.
|
|
53
|
+
- `list` shows each Agent's latest request.
|
|
54
|
+
- The panel shows how long each task has run and what its replies cost.
|
|
55
|
+
- Agent messages queued in a Session are shown and kept; queued writes are labeled apart from steers.
|
|
56
|
+
|
|
57
|
+
### Changed
|
|
58
|
+
|
|
59
|
+
- `pd` and `pd --session-id` attach to a Session already running in a host and send it their messages.
|
|
60
|
+
- After a turn is stopped, messages from other Agents are held as writes until the next input from its user or owner.
|
|
61
|
+
- `wait` ends early when a steer arrives.
|
|
62
|
+
- Stopping a Program no longer notifies its caller.
|
|
63
|
+
- A detached Session exits after 10 idle minutes instead of 5.
|
|
64
|
+
- The workbench has clearer session groups, better preview scrolling and layout, and a configurable detach key.
|
|
65
|
+
|
|
66
|
+
### Fixed
|
|
67
|
+
|
|
68
|
+
- Compatibility with Pi 1.2, including `program` reaching MCP tools without the `codemode` extension.
|
|
69
|
+
- Hosted `pd -p` prints `/program` results and exits with 1 on a failed `/program` or a truncated reply, as Pi does.
|
|
70
|
+
- Shutdown no longer waits indefinitely for Agents still loading, and Program cleanup waits for tool calls left running.
|
|
71
|
+
- Messages to a Program's Agents go through their owner.
|
|
72
|
+
- Timers, connections, and signal handlers no longer leak in the viewer and the host extension.
|
|
73
|
+
- `pd` waits for a starting or exiting Pi instead of reporting the Session in use.
|
|
74
|
+
|
|
75
|
+
## [0.1.1] - 2026-10-10
|
|
76
|
+
|
|
77
|
+
### Fixed
|
|
78
|
+
|
|
79
|
+
- Removing an idle Session that runs in a detached host quits it instead of refusing.
|
|
80
|
+
- Previewing a Session in the workbench marks its latest turn as seen.
|
|
81
|
+
|
|
82
|
+
## [0.1.0] - 2026-10-10
|
|
83
|
+
|
|
84
|
+
Initial release.
|
|
85
|
+
|
|
86
|
+
### Added
|
|
87
|
+
|
|
88
|
+
- `agent` tool: start, message, wait for, list, and abort Agents that run in parallel in the background and notify their owner when done.
|
|
89
|
+
- `program` tool: run JavaScript that composes tool calls and Agents, in the foreground or in the background.
|
|
90
|
+
- Messaging between Agents, including root Agents in other Pi instances.
|
|
91
|
+
- Task panel showing each Agent's and Program's live work; `/tasks` hides or shows it.
|
|
92
|
+
- `/agents` opens an Agent in the tree to watch and talk to it.
|
|
93
|
+
- `pd`: start Pi in a host that keeps the Session running after the terminal detaches (`Ctrl+Z`, `/detach`); `pd attach <session>` attaches again.
|
|
94
|
+
- `pd agents` opens the workbench, which lists all Sessions with previews.
|
package/bin/pd.mjs
CHANGED
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
// The `pd` command: runs Pi in a host, attaching this terminal to interactive Pi and connecting
|
|
3
3
|
// non-interactive Pi to the hosted Session; `--no-session` and package commands run Pi directly.
|
|
4
4
|
import { spawn } from 'node:child_process';
|
|
5
|
-
import { accessSync, constants,
|
|
5
|
+
import { accessSync, constants, existsSync, readFileSync } from 'node:fs';
|
|
6
|
+
import { homedir } from 'node:os';
|
|
6
7
|
import { createRequire } from 'node:module';
|
|
7
8
|
import { dirname, join, resolve } from 'node:path';
|
|
8
|
-
import {
|
|
9
|
+
import { pathToFileURL } from 'node:url';
|
|
9
10
|
import { styleText } from 'node:util';
|
|
10
11
|
import { attach, hostSessionFile, settledHosts, startHost } from '../src/host/client.mjs';
|
|
11
12
|
import { detachKey, detachMatcher } from '../src/host/keys.mjs';
|
|
12
13
|
import { discoverRoots, findRoot } from '../src/roots/discovery.mjs';
|
|
13
|
-
import { defaultSessionDir,
|
|
14
|
+
import { defaultSessionDir, resolvePath, sessionDir, statePaths } from '../src/roots/paths.mjs';
|
|
14
15
|
import { piPackage } from '../src/pi.mjs';
|
|
15
16
|
import { markedFiles } from '../src/daemon/sessions.mjs';
|
|
16
17
|
import { argGroups } from '../src/host/rpc-client.mjs';
|
|
@@ -154,37 +155,26 @@ function headlessMode() {
|
|
|
154
155
|
if (!interactive || parsed.print) return 'text';
|
|
155
156
|
}
|
|
156
157
|
|
|
157
|
-
/**
|
|
158
|
+
/** Opens the main Agent of the entry `--name` picks (default `main`), from any directory, or starts one in `~` if there is none. */
|
|
158
159
|
async function openMain() {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const
|
|
162
|
-
let
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
160
|
+
const name = parseArgs(args.slice(1)).name ?? 'main';
|
|
161
|
+
// `--name` picks the entry, so it does not rename the Session.
|
|
162
|
+
const options = argGroups(parseArgs, args.slice(1)).filter(([option]) => !['--name', '-n'].includes(option)).flat();
|
|
163
|
+
let file;
|
|
164
|
+
// `main.json` maps entry names to Session files; an old one holds `{ sessionFile }` for `main`.
|
|
165
|
+
try {
|
|
166
|
+
const entries = JSON.parse(readFileSync(paths.mainSession, 'utf8'));
|
|
167
|
+
file = typeof entries.sessionFile === 'string' ? { main: entries.sessionFile }[name] : entries[name];
|
|
168
|
+
} catch (failure) { if (failure.code !== 'ENOENT') throw failure; }
|
|
169
|
+
// A main Agent without messages has no file yet, but its host may be running.
|
|
170
|
+
const holds = host => hostSessionFile(host) === file;
|
|
171
|
+
if (file && (existsSync(file) || (await settledHosts(paths, holds)).some(holds))) args = ['--session', file, ...options];
|
|
172
|
+
else {
|
|
173
|
+
process.chdir(homedir());
|
|
174
|
+
// These go first, as messages may follow `--`; with `=`, `--main` takes no other argument as its value.
|
|
175
|
+
args = [`--main=${name}`, '--name', name, ...options];
|
|
168
176
|
}
|
|
169
|
-
// The link follows this copy of pi-agents; another `pd main` may be making it at the same time.
|
|
170
|
-
if (target !== instructions) {
|
|
171
|
-
try { if (target !== undefined) unlinkSync(link); }
|
|
172
|
-
catch (failure) { if (failure.code !== 'ENOENT') throw failure; }
|
|
173
|
-
try { symlinkSync(instructions, link); }
|
|
174
|
-
catch (failure) { if (failure.code !== 'EEXIST') throw failure; }
|
|
175
|
-
}
|
|
176
|
-
process.chdir(paths.main);
|
|
177
|
-
const options = args.slice(1);
|
|
178
|
-
// A running main Agent may have no file yet for `-c` to find.
|
|
179
|
-
const here = host => host.session?.cwd === process.cwd();
|
|
180
|
-
const running = (await settledHosts(paths, here)).filter(here);
|
|
181
|
-
args = running.length === 1 ? ['--session', hostSessionFile(running[0]), ...options] : ['-c', ...options];
|
|
182
177
|
parsed = parseArgs(args);
|
|
183
|
-
// A new main Agent is named, so other Agents can address it; options go first, as messages may follow `--`.
|
|
184
|
-
if (!running.length && parsed.name === undefined && !(await recentSession())) {
|
|
185
|
-
args.unshift('--name', 'main');
|
|
186
|
-
parsed = parseArgs(args);
|
|
187
|
-
}
|
|
188
178
|
}
|
|
189
179
|
|
|
190
180
|
if (args[0] === 'main') await openMain();
|
package/docs/HOSTS.md
CHANGED
|
@@ -16,7 +16,7 @@ Files live in `${XDG_STATE_HOME:-~/.local/state}/pi/agents/`:
|
|
|
16
16
|
|
|
17
17
|
- `workbench.json`: seen marks and which Sessions are listed.
|
|
18
18
|
- `host.log`, `daemon.log`: logs.
|
|
19
|
-
- `main
|
|
19
|
+
- `main.json`: which Session is the main Agent of each entry name.
|
|
20
20
|
|
|
21
21
|
Sockets and Session locks go in `$XDG_RUNTIME_DIR/pi-agents/`. macOS has no `XDG_RUNTIME_DIR`, so there they go in `runtime/` under the state directory.
|
|
22
22
|
|
|
@@ -36,7 +36,7 @@ pi install "$(npm root -g)/@tshu-w/pi-agents"
|
|
|
36
36
|
- `pd [pi options]` starts Pi interactively in a new host and attaches the terminal. If the Session is already running in a host, `pd` attaches to it instead, sending it the messages as follow-ups and ignoring other options.
|
|
37
37
|
- `pd agents` opens the workbench.
|
|
38
38
|
- `pd attach <session>` attaches to a Session, starting it first if it is not running.
|
|
39
|
-
- `pd main [pi options]` opens the main Agent, which hands work to other Agents
|
|
39
|
+
- `pd main [--name <name>] [pi options]` opens the main Agent for `<name>` (default `main`), which hands work to other Agents, from any directory; `/new` in it starts a new one.
|
|
40
40
|
- Non-interactive starts (`-p`, `--mode json`, `--mode rpc`, piped stdin) work as in Pi, on the Session in a host, so several of them and an attached terminal can use one Session at once. Their commands act on that Session, as `abort` stops whichever turn is running. Unlike Pi:
|
|
41
41
|
- Dialogs are not sent to them. A dialog waits while a terminal is attached and is cancelled otherwise.
|
|
42
42
|
- Extensions see interactive mode, so what an extension writes to stdout does not reach them; `/program` prints as with `pi -p`.
|
package/docs/PROGRAM.md
CHANGED
|
@@ -8,7 +8,7 @@ The `program` tool replaces Pi's `codemode` tool and keeps its script API, tool
|
|
|
8
8
|
|
|
9
9
|
An Agent runs a **Program** with the `program` tool. That Agent is the Program's **caller**. The Program can call the caller's tools and create Agents.
|
|
10
10
|
|
|
11
|
-
A Program runs in the **foreground** or the **background**. In the foreground, the tool call returns the Program's result. In the background, the tool call returns the Program's ID, and the caller gets the result
|
|
11
|
+
A Program runs in the **foreground** or the **background**. In the foreground, the tool call returns the Program's result. In the background, the tool call returns the Program's ID, and the caller gets the result in a notification.
|
|
12
12
|
|
|
13
13
|
A Program is shown by its short ID: the shortest prefix of its ID, at least 8 characters, that none of the caller's other Programs' IDs starts with. A target is an ID or a unique ID prefix.
|
|
14
14
|
|
|
@@ -23,11 +23,11 @@ A Program is `running` until it ends in one of three ways:
|
|
|
23
23
|
## 2. Operations
|
|
24
24
|
|
|
25
25
|
- `run` runs a Program in the foreground or background, with an optional timeout.
|
|
26
|
-
- `wait` waits for background Programs
|
|
26
|
+
- `wait` waits for background Programs to end.
|
|
27
27
|
- `list` lists the caller's background Programs.
|
|
28
28
|
- `stop` stops a running Program.
|
|
29
29
|
|
|
30
|
-
When a background Program ends
|
|
30
|
+
When a background Program ends, the caller gets a notification with the result, unless it stopped the Program. `wait` ends as in SPEC §5.
|
|
31
31
|
|
|
32
32
|
## 3. Program API
|
|
33
33
|
|
|
@@ -47,7 +47,7 @@ With `timeout`, an input that has not ended within that many seconds of `send()`
|
|
|
47
47
|
|
|
48
48
|
When a Program ends, it cancels its unfinished tool calls and aborts its Agents' unfinished inputs, and waits for its Agents to go offline. It returns its result after cleanup. Work not stopped within the cleanup timeout is abandoned and may keep running; the Program's result and `stop` report it as an error, and the Program's state is unchanged.
|
|
49
49
|
|
|
50
|
-
When the caller goes offline, its running Programs stop
|
|
50
|
+
When the caller goes offline, its running Programs stop, as if it stopped them.
|
|
51
51
|
|
|
52
52
|
## 5. Limits
|
|
53
53
|
|
|
@@ -61,7 +61,7 @@ An owned caller gives up its slot while a foreground Program runs or while it wa
|
|
|
61
61
|
|
|
62
62
|
description:
|
|
63
63
|
|
|
64
|
-
Run JavaScript that composes tool calls and Agents; only its output and return value reach the caller. `run` runs a Program in the foreground, or in the background with `background`; `wait` waits for background Programs
|
|
64
|
+
Run JavaScript that composes tool calls and Agents; only its output and return value reach the caller. `run` runs a Program in the foreground, or in the background with `background`; `wait` waits for background Programs to end; `list` lists background Programs; `stop` stops a running Program. When a background Program ends, the caller receives a notification with the result.
|
|
65
65
|
|
|
66
66
|
Program code is the body of an async function. It calls the caller's tools through `tools.*`, which excludes `program`, and creates Agents through `agent()`. These Agents belong to the Program, are isolated from other Agents, and go offline when it ends.
|
|
67
67
|
|
|
@@ -117,7 +117,7 @@ promptGuidelines:
|
|
|
117
117
|
|
|
118
118
|
- Use `program(action='run', code=...)` to call tools or Agents several times without a model turn between the calls, for example to read many files, filter large tool output, fan out Agents, or loop until a condition holds.
|
|
119
119
|
- Use `Promise.allSettled` for independent calls. Return only what the caller needs; use `text()` for progress worth reading.
|
|
120
|
-
- Use `background=true` for long Programs, and `program(action='wait', ...)` when
|
|
120
|
+
- Use `background=true` for long Programs, and `program(action='wait', ...)` when your next step or answer depends on their results.
|
|
121
121
|
|
|
122
122
|
parameters:
|
|
123
123
|
|
|
@@ -125,7 +125,7 @@ parameters:
|
|
|
125
125
|
- `code`: JavaScript async-function body for `run`.
|
|
126
126
|
- `background`: Whether `run` returns the Program ID at once (default: false).
|
|
127
127
|
- `timeout`: Maximum seconds. For `run` (default: none), expiry stops the Program. For `wait` (default: 30, min: 10, max: 3600), expiry does not stop Programs.
|
|
128
|
-
- `target`: Program ID or unique ID prefix for `wait` or `stop`. `wait` also accepts an array. When omitted for `wait`, selects running Programs
|
|
128
|
+
- `target`: Program ID or unique ID prefix for `wait` or `stop`. `wait` also accepts an array. When omitted for `wait`, selects running Programs.
|
|
129
129
|
- `limit`: Maximum Programs returned by `list` (default: 20, max: 200).
|
|
130
130
|
- `offset`: Number of Programs to skip for `list` (default: 0).
|
|
131
131
|
|
|
@@ -141,22 +141,13 @@ In these formats, `<id>` is the Program's short ID.
|
|
|
141
141
|
Program <id> started.
|
|
142
142
|
```
|
|
143
143
|
|
|
144
|
-
`wait` returns
|
|
144
|
+
`wait` returns as `agent` `wait` does (SPEC §6), naming the Programs still running:
|
|
145
145
|
|
|
146
146
|
```text
|
|
147
|
-
|
|
148
|
-
<
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
Still running: <id>, ...
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
When a steer input ended the wait, the line of SPEC §6 follows.
|
|
155
|
-
|
|
156
|
-
A single completed result, with nothing running, is returned alone. When there are no results or running Programs:
|
|
157
|
-
|
|
158
|
-
```text
|
|
159
|
-
No results.
|
|
147
|
+
Wait completed.
|
|
148
|
+
Wait timed out. Still running: <id>, ...
|
|
149
|
+
Wait interrupted by a new message. Still running: <id>, ...
|
|
150
|
+
Nothing to wait for.
|
|
160
151
|
```
|
|
161
152
|
|
|
162
153
|
`list` returns one entry per background Program, newest first:
|
|
@@ -210,18 +201,16 @@ No Program matches "<target>". Use list to find Programs.
|
|
|
210
201
|
|
|
211
202
|
truncation:
|
|
212
203
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
```text
|
|
216
|
-
[Results omitted: <id>, ... Use wait with fewer targets.]
|
|
217
|
-
```
|
|
204
|
+
A notification's result beyond Pi's output limits is truncated as in SPEC §6 and keeps its closing tag.
|
|
218
205
|
|
|
219
206
|
messages:
|
|
220
207
|
|
|
221
|
-
A notification to the caller:
|
|
208
|
+
A notification to the caller, followed by the result's images:
|
|
222
209
|
|
|
223
210
|
```text
|
|
224
|
-
|
|
211
|
+
<program-result id="<id>" status="<outcome>">
|
|
212
|
+
<result>
|
|
213
|
+
</program-result>
|
|
225
214
|
```
|
|
226
215
|
|
|
227
216
|
## 7. Human interface
|
package/docs/SPEC.md
CHANGED
|
@@ -6,7 +6,7 @@ Terms follow Pi's [Pi Durable specification](https://github.com/earendil-works/p
|
|
|
6
6
|
|
|
7
7
|
## 1. Semantic model
|
|
8
8
|
|
|
9
|
-
An **Agent** is an addressable conversation with a stable identity. The Agent that spawns another Agent is its **owner
|
|
9
|
+
An **Agent** is an addressable conversation with a stable identity. The Agent that spawns another Agent is its **owner**, unless it disowns it (§2.1). A root Agent has no owner. Ownership forms trees.
|
|
10
10
|
|
|
11
11
|
An Agent is shown by its name and **short ID**: the shortest prefix of its ID, at least 8 characters, that no other Agent visible to the reader has. An address is an ID, a name, or a unique ID prefix, tried in that order.
|
|
12
12
|
|
|
@@ -24,6 +24,8 @@ An owner can **wait** for answers to the inputs it sends to its owned Agents.
|
|
|
24
24
|
|
|
25
25
|
`spawn` creates a named, owned Agent and sends it a first input. It accepts an optional working directory, model, thinking level, and forked context, and returns the Agent's ID. Names are unique among siblings.
|
|
26
26
|
|
|
27
|
+
With `disown`, a root Agent's `spawn` starts a root Agent instead, detached in a host (HOSTS §4).
|
|
28
|
+
|
|
27
29
|
### 2.2 send
|
|
28
30
|
|
|
29
31
|
`send` sends a message to a visible Agent by address. An ambiguous or unknown address is rejected.
|
|
@@ -36,7 +38,7 @@ When the user of a root Agent clears its queued messages, messages from other Ag
|
|
|
36
38
|
|
|
37
39
|
### 2.3 wait
|
|
38
40
|
|
|
39
|
-
`wait`
|
|
41
|
+
`wait` waits until owned Agents have finished the inputs sent to them. It can target specific Agents and take a timeout.
|
|
40
42
|
|
|
41
43
|
### 2.4 list
|
|
42
44
|
|
|
@@ -50,11 +52,11 @@ When the user of a root Agent clears its queued messages, messages from other Ag
|
|
|
50
52
|
|
|
51
53
|
An Agent is `running` while a turn is in progress, `waiting` while its turn waits in `wait` (§4), `queued` while its next turn waits for a slot (§4), and `idle` otherwise. An Agent goes offline when its runtime unloads it; it stays `idle`.
|
|
52
54
|
|
|
53
|
-
`send` returns once the recipient's runtime accepts the message, waking it if needed. Acceptance does not mean the recipient has handled it
|
|
55
|
+
`send` returns once the recipient's runtime accepts the message, waking it if needed. Acceptance does not mean the recipient has handled it (§10).
|
|
54
56
|
|
|
55
57
|
An input to an offline Agent wakes it; a write to one is rejected.
|
|
56
58
|
|
|
57
|
-
Owned Agents run independently of their owner's turns
|
|
59
|
+
Owned Agents run independently of their owner's turns and go offline with it (§10). They return as `idle` when their owner is loaded again.
|
|
58
60
|
|
|
59
61
|
On abort or when going offline, work that has not stopped within a timeout is abandoned: it no longer counts as live work and may keep running until the process exits; the timeout is reported.
|
|
60
62
|
|
|
@@ -74,9 +76,9 @@ An input is outstanding from acceptance until its turn ends or it is withdrawn.
|
|
|
74
76
|
|
|
75
77
|
An input sent to an owned Agent ends as `completed` when its turn answers it, `failed` when its turn fails, or `aborted` when it is aborted or withdrawn. Its result is the answer or the error. Only inputs from the owner have results; the recipient replies to other senders with `send`.
|
|
76
78
|
|
|
77
|
-
|
|
79
|
+
When an input ends, its owner receives a notification carrying the result, unless the owner aborted it or is going offline. Inputs that a turn ends with the same outcome and result share one notification. The notification is an input delivered as `steer` (a write after the owner's turn is stopped, §2.2).
|
|
78
80
|
|
|
79
|
-
|
|
81
|
+
`wait` ends when the Agents it waits for have no pending inputs, when its timeout expires, or when a steer input arrives; a write does not end it. Their notifications are held until it ends, then delivered as one.
|
|
80
82
|
|
|
81
83
|
A recipient sees each message with its sender's name and ID.
|
|
82
84
|
|
|
@@ -86,7 +88,7 @@ A recipient sees each message with its sender's name and ID.
|
|
|
86
88
|
|
|
87
89
|
description:
|
|
88
90
|
|
|
89
|
-
Create, message, and coordinate Agents. `spawn` creates an
|
|
91
|
+
Create, message, and coordinate Agents. `spawn` creates an Agent and sends its first input; `send` sends an input or a write to a visible Agent; `wait` waits for owned Agents to finish; `list` finds visible Agents; `abort` stops an owned Agent's current turn and queued inputs while keeping it available. When an owned Agent finishes an input, its owner receives a notification with the result. Owned Agents under the same root share `${maxConcurrent}` execution slots and a limit of `${maxOutstanding}` inputs that have not ended. Inputs that start a new turn queue when all slots are busy; new inputs are rejected at the input limit.
|
|
90
92
|
|
|
91
93
|
promptSnippet:
|
|
92
94
|
|
|
@@ -96,20 +98,20 @@ promptGuidelines:
|
|
|
96
98
|
|
|
97
99
|
- Use `agent(action='spawn', name=..., message=...)` for concrete, bounded work with a clear expected result. Keep the first message self-contained; use `context='fork'` only when the Agent needs the caller's conversation.
|
|
98
100
|
- For independent work, start the Agents before waiting and partition tasks into non-overlapping responsibilities.
|
|
99
|
-
- Use `agent(action='wait', ...)` when
|
|
101
|
+
- Use `agent(action='wait', ...)` when your next step or answer depends on Agent results. Prefer one longer wait over repeated short polling.
|
|
100
102
|
|
|
101
103
|
parameters:
|
|
102
104
|
|
|
103
|
-
- `action`: Operation and applicable parameters: `spawn(name, message, cwd?, context?, model?, thinkingLevel?)`, `send(target, message, deliverAs?)`, `wait(target?,
|
|
105
|
+
- `action`: Operation and applicable parameters: `spawn(name, message, cwd?, context?, model?, thinkingLevel?, disown?)`, `send(target, message, deliverAs?)`, `wait(target?, timeout?)`, `list(query?, state?, limit?, offset?)`, or `abort(target)`.
|
|
104
106
|
- `name`: Name of the new Agent for `spawn`; unique among the caller's Agents.
|
|
105
107
|
- `message`: Non-empty text for `spawn` or `send`.
|
|
106
108
|
- `cwd`: Working directory for `spawn` (default: the caller's current working directory).
|
|
107
109
|
- `context`: Context for `spawn` (default: fresh). fresh starts without the caller's conversation; fork snapshots it.
|
|
108
110
|
- `model`: Model for `spawn` as provider/modelId (default: the caller's model).
|
|
109
111
|
- `thinkingLevel`: Thinking level for `spawn`: off, minimal, low, medium, high, xhigh, or max (default: the caller's level).
|
|
110
|
-
- `
|
|
111
|
-
- `
|
|
112
|
-
- `
|
|
112
|
+
- `disown`: For `spawn`, start a root Agent the caller does not own; it replies with send instead of returning a result (default: false).
|
|
113
|
+
- `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 inputs.
|
|
114
|
+
- `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 does not wake an idle recipient and has no result.
|
|
113
115
|
- `timeout`: Maximum seconds for `wait` (default: 30, min: 10, max: 3600). Timeout does not abort Agents.
|
|
114
116
|
- `query`: Search query for `list`, matching ID, name, cwd, summaries, and user messages. Case-insensitive; spaces mean AND; `|` means OR.
|
|
115
117
|
- `state`: State for `list`: running, waiting, queued, or idle.
|
|
@@ -148,37 +150,26 @@ followUp / steer: { id, queued }
|
|
|
148
150
|
write: { ids }
|
|
149
151
|
```
|
|
150
152
|
|
|
151
|
-
`wait` returns
|
|
153
|
+
`wait` returns how it ended; results arrive as notifications:
|
|
152
154
|
|
|
153
155
|
```text
|
|
154
|
-
|
|
155
|
-
<
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
Still pending: <name> (<id>), ...
|
|
156
|
+
Wait completed.
|
|
157
|
+
Wait timed out. Still pending: <name> (<id>), ...
|
|
158
|
+
Wait interrupted by a new message. Still pending: <name> (<id>), ...
|
|
159
159
|
```
|
|
160
160
|
|
|
161
|
-
When
|
|
161
|
+
When none of the selected Agents has pending inputs:
|
|
162
162
|
|
|
163
163
|
```text
|
|
164
|
-
|
|
165
|
-
Stopped waiting for a new message. Wait again to keep waiting.
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
A result returned again through `history` adds `history="true"`. A single unread completed result, with nothing pending, is returned as the answer alone. When there are no results or pending inputs:
|
|
169
|
-
|
|
170
|
-
```text
|
|
171
|
-
No results.
|
|
164
|
+
Nothing to wait for.
|
|
172
165
|
```
|
|
173
166
|
|
|
174
167
|
Details:
|
|
175
168
|
|
|
176
169
|
```text
|
|
177
|
-
{
|
|
170
|
+
{ pending: [{ id, name }], timedOut?, interrupted? }
|
|
178
171
|
```
|
|
179
172
|
|
|
180
|
-
`state` is the input outcome (§5); `history` marks a previously read result; `interrupted` is `true` when a steer input ended the wait.
|
|
181
|
-
|
|
182
173
|
`list` returns one entry per Agent, followed by its latest request, if any, and its time. With a query, entries are ordered by their most recent match, and a matching summary or user message shows its latest excerpt instead:
|
|
183
174
|
|
|
184
175
|
```text
|
|
@@ -257,22 +248,20 @@ Agent <name> (<id>) did not stop within the timeout and may still be running.
|
|
|
257
248
|
|
|
258
249
|
truncation:
|
|
259
250
|
|
|
260
|
-
Text beyond Pi's output limits is truncated with Pi's native notice, and the full text is saved to a file whose path appears in the notice.
|
|
261
|
-
|
|
262
|
-
```text
|
|
263
|
-
[Results omitted: <name> (<id>), ... Use wait with fewer targets.]
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
Details retain metadata, `truncation`, and `fullOutputPath`. `wait.results[].result` contains only retained text and is absent for omitted results.
|
|
251
|
+
Text beyond Pi's output limits is truncated with Pi's native notice, and the full text is saved to a file whose path appears in the notice. A notification's result is truncated the same way and keeps its closing tag. Details retain metadata, `truncation`, and `fullOutputPath`.
|
|
267
252
|
|
|
268
253
|
messages:
|
|
269
254
|
|
|
270
255
|
A notification to the owner:
|
|
271
256
|
|
|
272
257
|
```text
|
|
273
|
-
|
|
258
|
+
<agent-result name="<name>" id="<id>" status="<outcome>">
|
|
259
|
+
<answer or error>
|
|
260
|
+
</agent-result>
|
|
274
261
|
```
|
|
275
262
|
|
|
263
|
+
A notification that a `wait` held holds one such block per result, separated by blank lines.
|
|
264
|
+
|
|
276
265
|
A recipient sees each message in an `agent-message` element naming the sender. An input from a sender other than the owner also carries a note asking for a reply:
|
|
277
266
|
|
|
278
267
|
```text
|
|
@@ -326,7 +315,7 @@ A message from another Agent shows its sender after the label:
|
|
|
326
315
|
<body>
|
|
327
316
|
```
|
|
328
317
|
|
|
329
|
-
The user's input from the task viewer shows as a user message.
|
|
318
|
+
The user's input from the task viewer shows as a user message. Notifications show each result with the first lines of its body; a Program's body starts at its output:
|
|
330
319
|
|
|
331
320
|
```text
|
|
332
321
|
<name> (<id>) <state>
|
|
@@ -373,3 +362,10 @@ Invalid settings produce a startup warning, and the defaults are used.
|
|
|
373
362
|
Each Agent is a persistent Pi Session. A root Agent is an ordinary Session. An owned Agent is a child Session that records its owner and root, and runs in its owner's process.
|
|
374
363
|
|
|
375
364
|
A delivered message enters the recipient's current branch. An offline root Agent is started in a host, detached, with its saved working directory and model and the current configuration (HOSTS §4). If a root process cannot finish cleanup within a grace period on exit, it is forcibly terminated. Before shutting down an idle Session, another extension can emit `busy:query` on `pi.events` with `{ busy: false }`; it is set to `true` while the tree has live work. One runtime uses a Session at a time. If a Session is occupied at startup, Pi reports it and exits.
|
|
365
|
+
|
|
366
|
+
## 10. Limitations
|
|
367
|
+
|
|
368
|
+
In this version:
|
|
369
|
+
|
|
370
|
+
- A message accepted but not yet handled is lost if the recipient exits.
|
|
371
|
+
- When an owner goes offline, its owned Agents go offline too, from the bottom of the tree up, and their unfinished inputs end as `aborted`, as do inputs unfinished when a process exits.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tshu-w/pi-agents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Multi-agent runtime for Pi: persistent async agents, dynamic workflows, and inter-agent messaging",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"repository": {
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"files": [
|
|
17
17
|
"bin",
|
|
18
18
|
"src",
|
|
19
|
-
"docs"
|
|
19
|
+
"docs",
|
|
20
|
+
"CHANGELOG.md"
|
|
20
21
|
],
|
|
21
22
|
"keywords": [
|
|
22
23
|
"pi",
|