@gavin-hu/my-pi-agent 0.17.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -45
- package/README.zh-CN.md +54 -42
- package/docs/extensions/README.md +34 -0
- package/docs/extensions/board.md +344 -0
- package/{extensions/bytefly/README.md → docs/extensions/bytefly.md} +465 -416
- package/{extensions/check/README.md → docs/extensions/check.md} +83 -68
- package/{extensions/context/README.md → docs/extensions/context.md} +40 -23
- package/docs/extensions/file-browse.md +554 -0
- package/{extensions/file-open/README.md → docs/extensions/file-open.md} +31 -23
- package/{extensions/file-read/README.md → docs/extensions/file-read.md} +47 -55
- package/{extensions/goal/README.md → docs/extensions/goal.md} +115 -131
- package/{extensions/job/README.md → docs/extensions/job.md} +66 -53
- package/{extensions/memory/README.md → docs/extensions/memory.md} +74 -63
- package/docs/extensions/plan.md +976 -0
- package/{extensions/question/README.md → docs/extensions/question.md} +54 -28
- package/{extensions/rewind/README.md → docs/extensions/rewind.md} +115 -122
- package/docs/extensions/session-bus.md +285 -0
- package/{extensions/subagent/README.md → docs/extensions/subagent.md} +166 -119
- package/docs/extensions/team.md +226 -0
- package/{extensions/todo/README.md → docs/extensions/todo.md} +85 -88
- package/{extensions/watch/README.md → docs/extensions/watch.md} +60 -38
- package/{extensions/web-access/README.md → docs/extensions/web-access.md} +93 -50
- package/{extensions/wechat/README.md → docs/extensions/wechat.md} +275 -192
- package/{extensions/worktree/README.md → docs/extensions/worktree.md} +115 -72
- package/extensions/board/commands.ts +87 -0
- package/extensions/board/config.ts +36 -0
- package/extensions/board/format.ts +72 -0
- package/extensions/board/index.ts +40 -0
- package/extensions/board/input.ts +54 -0
- package/extensions/board/join.ts +42 -0
- package/extensions/board/page.ts +312 -0
- package/extensions/board/result.ts +99 -0
- package/extensions/board/rows.ts +48 -0
- package/extensions/board/schema.ts +75 -0
- package/extensions/board/seat.ts +29 -0
- package/extensions/board/snapshot.ts +41 -0
- package/extensions/board/tools.ts +279 -0
- package/extensions/board/transport/router.ts +172 -0
- package/extensions/board/transport/server.ts +149 -0
- package/extensions/board/types.ts +54 -0
- package/extensions/board/view/actions.ts +306 -0
- package/extensions/board/view/client.ts +41 -0
- package/extensions/board/view/keys.ts +121 -0
- package/extensions/board/view/live.ts +397 -0
- package/extensions/board/view/page.ts +158 -0
- package/extensions/board/view/render.ts +496 -0
- package/extensions/board/view/styles.ts +211 -0
- package/extensions/board/view/team.ts +305 -0
- package/extensions/board/writes.ts +99 -0
- package/extensions/bytefly/notify/types.ts +1 -1
- package/extensions/bytefly/panel/commands.ts +40 -7
- package/extensions/bytefly/panel/settings-runtime.ts +0 -6
- package/extensions/bytefly/panel/settings.ts +11 -21
- package/extensions/bytefly/panel/tui/settings-view.ts +1 -1
- package/extensions/bytefly/picker/discovery.ts +0 -2
- package/extensions/bytefly/picker/types.ts +0 -2
- package/extensions/bytefly/update/check.ts +39 -0
- package/extensions/bytefly/update/format.ts +15 -0
- package/extensions/bytefly/update/register.ts +73 -34
- package/extensions/bytefly/update/state.ts +6 -3
- package/extensions/context/tui.ts +1 -1
- package/extensions/file-browse/{config.ts → config/config.ts} +13 -4
- package/extensions/file-browse/{files.ts → files/files.ts} +1 -14
- package/extensions/file-browse/{paths.ts → files/paths.ts} +1 -1
- package/extensions/file-browse/{git.ts → git/git.ts} +10 -3
- package/extensions/file-browse/index.ts +4 -4
- package/extensions/file-browse/{live.ts → live/live.ts} +54 -82
- package/extensions/file-browse/live/rev.ts +42 -0
- package/extensions/file-browse/{router.ts → transport/router.ts} +44 -17
- package/extensions/file-browse/transport/server.ts +110 -0
- package/extensions/file-browse/view/chrome.ts +83 -0
- package/extensions/file-browse/{client.ts → view/client.ts} +63 -1
- package/extensions/file-browse/view/diff-html.ts +94 -0
- package/extensions/file-browse/{html.ts → view/html.ts} +32 -158
- package/extensions/file-browse/{icons.ts → view/icons.ts} +1 -1
- package/extensions/file-browse/{live-client.ts → view/live-client.ts} +47 -1
- package/extensions/file-browse/{markdown.ts → view/markdown.ts} +7 -7
- package/extensions/file-browse/{raw-html.ts → view/raw-html.ts} +1 -1
- package/extensions/file-browse/{styles.ts → view/styles.ts} +31 -5
- package/extensions/file-browse/{tree.ts → view/tree.ts} +1 -1
- package/extensions/file-browse/{urls.ts → view/urls.ts} +1 -1
- package/extensions/file-read/schema.ts +2 -1
- package/extensions/goal/plan-route.ts +8 -2
- package/extensions/job/index.ts +15 -7
- package/extensions/memory/store.ts +12 -5
- package/extensions/plan/blocks.ts +23 -2
- package/extensions/plan/commands.ts +77 -21
- package/extensions/plan/feedback.ts +409 -63
- package/extensions/plan/index.ts +7 -5
- package/extensions/plan/plan-index.ts +44 -10
- package/extensions/plan/plan-text.ts +51 -24
- package/extensions/plan/plans.ts +162 -57
- package/extensions/plan/policy.ts +20 -12
- package/extensions/plan/review-prompt.ts +93 -0
- package/extensions/plan/review.ts +51 -95
- package/extensions/plan/runtime.ts +22 -10
- package/extensions/plan/sections.ts +112 -0
- package/extensions/plan/steps.ts +63 -95
- package/extensions/plan/tools/enter.ts +8 -3
- package/extensions/plan/tools/exit.ts +13 -19
- package/extensions/plan/tools/write.ts +8 -3
- package/extensions/plan/tools.ts +1 -1
- package/extensions/plan/tui.ts +29 -15
- package/extensions/plan/types.ts +29 -3
- package/extensions/plan/web-chrome.ts +2 -1
- package/extensions/plan/web-client.ts +122 -178
- package/extensions/plan/web-collab.ts +207 -0
- package/extensions/plan/web-comments.ts +269 -0
- package/extensions/plan/web-page.ts +96 -17
- package/extensions/plan/web-router.ts +109 -47
- package/extensions/plan/web-share-session.ts +191 -0
- package/extensions/plan/web-share.ts +169 -0
- package/extensions/plan/web-streams.ts +124 -0
- package/extensions/plan/web-tokens.ts +63 -0
- package/extensions/plan/web.ts +245 -151
- package/extensions/question/dialogs.ts +91 -1
- package/extensions/question/tools.ts +2 -1
- package/extensions/rewind/{commands.ts → commands/commands.ts} +5 -5
- package/extensions/rewind/{review.ts → commands/review.ts} +13 -13
- package/extensions/rewind/{rewind.ts → commands/rewind.ts} +6 -6
- package/extensions/rewind/{config.ts → config/config.ts} +1 -1
- package/extensions/rewind/index.ts +3 -3
- package/extensions/rewind/{policy.ts → state/policy.ts} +4 -4
- package/extensions/rewind/{runtime.ts → state/runtime.ts} +11 -11
- package/extensions/rewind/{snapshot-run.ts → state/snapshot-run.ts} +1 -1
- package/extensions/rewind/{timeline.ts → state/timeline.ts} +2 -2
- package/extensions/rewind/{format.ts → text/format.ts} +3 -3
- package/extensions/rewind/{git.ts → transport/git.ts} +2 -2
- package/extensions/rewind/{restore.ts → transport/restore.ts} +2 -2
- package/extensions/rewind/{snapshot.ts → transport/snapshot.ts} +1 -1
- package/extensions/rewind/{store.ts → transport/store.ts} +1 -1
- package/extensions/rewind/{checklist.ts → view/checklist.ts} +6 -6
- package/extensions/rewind/{hunk-picker.ts → view/hunk-picker.ts} +8 -8
- package/extensions/rewind/{path-picker.ts → view/path-picker.ts} +7 -7
- package/extensions/rewind/{review-tui.ts → view/review-tui.ts} +11 -11
- package/extensions/rewind/{rewind-tui.ts → view/rewind-tui.ts} +17 -11
- package/extensions/rewind/{status.ts → view/status.ts} +4 -4
- package/extensions/session-bus/commands.ts +113 -0
- package/extensions/session-bus/format.ts +101 -0
- package/extensions/session-bus/index.ts +75 -0
- package/extensions/session-bus/join-request.ts +40 -0
- package/extensions/session-bus/mail-watch.ts +134 -0
- package/extensions/session-bus/notice.ts +86 -0
- package/extensions/session-bus/overlap.ts +36 -0
- package/extensions/session-bus/protocol.ts +104 -0
- package/extensions/session-bus/resolve.ts +67 -0
- package/extensions/session-bus/result.ts +208 -0
- package/extensions/session-bus/rows.ts +171 -0
- package/extensions/session-bus/runtime.ts +466 -0
- package/extensions/session-bus/store.ts +365 -0
- package/extensions/session-bus/tools.ts +192 -0
- package/extensions/session-bus/tui.ts +394 -0
- package/extensions/session-bus/types.ts +98 -0
- package/extensions/subagent/agents.ts +9 -8
- package/extensions/subagent/invocation.ts +4 -2
- package/extensions/team/commands.ts +112 -0
- package/extensions/team/dispatch.ts +133 -0
- package/extensions/team/format.ts +105 -0
- package/extensions/team/index.ts +33 -0
- package/extensions/team/schema.ts +29 -0
- package/extensions/team/tools.ts +246 -0
- package/extensions/team/types.ts +48 -0
- package/extensions/watch/index.ts +15 -9
- package/extensions/web-access/fetch/page.ts +4 -1
- package/extensions/web-access/fetch/structured.ts +447 -0
- package/extensions/web-access/fetch/tool.ts +2 -1
- package/extensions/web-access/search/tool.ts +2 -1
- package/extensions/wechat/bridge.ts +76 -92
- package/extensions/wechat/{commands.ts → commands/commands.ts} +26 -18
- package/extensions/wechat/{doctor.ts → commands/doctor.ts} +5 -5
- package/extensions/wechat/{history.ts → commands/history.ts} +1 -1
- package/extensions/wechat/{login.ts → commands/login.ts} +3 -3
- package/extensions/wechat/{slash.ts → commands/slash.ts} +38 -2
- package/extensions/wechat/dialog/channels.ts +136 -0
- package/extensions/wechat/{interaction.ts → dialog/interaction.ts} +66 -51
- package/extensions/wechat/{prompt.ts → dialog/prompt.ts} +40 -25
- package/extensions/wechat/{remote-ui.ts → dialog/remote-ui.ts} +1 -1
- package/extensions/wechat/index.ts +10 -10
- package/extensions/wechat/{admission.ts → peer/admission.ts} +2 -2
- package/extensions/wechat/{peer-session.ts → peer/peer-session.ts} +2 -2
- package/extensions/wechat/{peer-worker.ts → peer/peer-worker.ts} +119 -56
- package/extensions/wechat/{presence.ts → peer/presence.ts} +11 -11
- package/extensions/wechat/{reply-pipeline.ts → peer/reply-pipeline.ts} +13 -5
- package/extensions/wechat/{credentials.ts → state/credentials.ts} +2 -2
- package/extensions/wechat/{state.ts → state/state.ts} +14 -2
- package/extensions/wechat/{format.ts → text/format.ts} +3 -3
- package/extensions/wechat/{notices.ts → text/notices.ts} +15 -7
- package/extensions/wechat/tools.ts +29 -12
- package/extensions/wechat/{client.ts → transport/client.ts} +26 -2
- package/extensions/wechat/{deliver.ts → transport/deliver.ts} +104 -40
- package/extensions/wechat/{inbound.ts → transport/inbound.ts} +5 -5
- package/extensions/wechat/{media.ts → transport/media.ts} +11 -5
- package/extensions/wechat/transport/outbound.ts +142 -0
- package/extensions/wechat/{poll.ts → transport/poll.ts} +3 -3
- package/extensions/wechat/types.ts +10 -1
- package/extensions/wechat/{login-screen.ts → view/login-screen.ts} +4 -4
- package/extensions/wechat/{status-screen.ts → view/status-screen.ts} +31 -26
- package/extensions/worktree/git.ts +5 -0
- package/extensions/worktree/guard.ts +3 -5
- package/extensions/worktree/registry.ts +11 -5
- package/extensions/worktree/status.ts +1 -3
- package/lib/README.md +12 -6
- package/lib/board/card.ts +290 -0
- package/lib/board/links.ts +117 -0
- package/lib/board/page-pointer.ts +256 -0
- package/lib/board/round-text.ts +72 -0
- package/lib/board/round.ts +89 -0
- package/lib/board/rules.ts +304 -0
- package/lib/board/session.ts +105 -0
- package/lib/board/store.ts +310 -0
- package/lib/board/view.ts +150 -0
- package/lib/errors.ts +12 -4
- package/lib/format.ts +13 -0
- package/lib/http/guard.ts +112 -0
- package/lib/http/index.ts +16 -5
- package/lib/http/router.ts +40 -13
- package/lib/http/serve.ts +234 -0
- package/lib/http/server.ts +29 -13
- package/lib/http/sink.ts +110 -0
- package/lib/http/token.ts +38 -0
- package/lib/interaction.ts +10 -0
- package/lib/notice.ts +29 -2
- package/lib/plan-entry.ts +7 -2
- package/lib/policy.ts +12 -2
- package/lib/qr.ts +64 -0
- package/lib/repo-key.ts +74 -0
- package/lib/report.ts +52 -33
- package/lib/requirements.ts +7 -9
- package/lib/session-label.ts +64 -0
- package/lib/session-roster.ts +130 -0
- package/lib/session-tool.ts +184 -0
- package/lib/tool-names.ts +25 -2
- package/lib/tui/README.md +16 -2
- package/lib/tui/dock-screen.ts +85 -17
- package/lib/tui/glyphs.ts +2 -0
- package/lib/tui/line-pane.ts +15 -4
- package/lib/tui/tool.ts +23 -4
- package/lib/web/README.md +5 -2
- package/lib/web/components.ts +6 -1
- package/lib/web/runtime.ts +7 -1
- package/lib/web/tokens.ts +25 -2
- package/package.json +27 -14
- package/skills/init/SKILL.md +80 -45
- package/skills/init/references/agents-md-quality.md +26 -2
- package/skills/init/references/interview-questions.md +36 -14
- package/skills/next/SKILL.md +77 -51
- package/skills/project-docs/SKILL.md +133 -0
- package/skills/project-docs/references/audit.md +131 -0
- package/skills/project-docs/references/changelog.md +105 -0
- package/skills/project-docs/references/discovery.md +127 -0
- package/skills/project-docs/references/readme.md +80 -0
- package/skills/project-docs/references/release.md +84 -0
- package/skills/project-docs/references/roadmap.md +93 -0
- package/skills/project-docs/scripts/state.ts +316 -0
- package/skills/refactor/SKILL.md +100 -125
- package/skills/refactor/references/handoff.md +35 -0
- package/skills/refactor/references/signals.md +54 -0
- package/skills/refactor/scripts/inventory.ts +587 -0
- package/extensions/file-browse/README.md +0 -467
- package/extensions/file-browse/server.ts +0 -134
- package/extensions/plan/README.md +0 -709
- package/extensions/plan/config.ts +0 -27
- package/extensions/wechat/outbound.ts +0 -100
- package/extensions/wechat/qrcode.ts +0 -46
- package/skills/changelog/SKILL.md +0 -148
- /package/extensions/file-browse/{diff.ts → text/diff.ts} +0 -0
- /package/extensions/file-browse/{fuzzy.ts → text/fuzzy.ts} +0 -0
- /package/extensions/file-browse/{palette.ts → view/palette.ts} +0 -0
- /package/extensions/rewind/{patch.ts → text/patch.ts} +0 -0
- /package/extensions/rewind/{paths.ts → text/paths.ts} +0 -0
- /package/extensions/rewind/{queue.ts → transport/queue.ts} +0 -0
- /package/extensions/rewind/{temp-index.ts → transport/temp-index.ts} +0 -0
- /package/extensions/wechat/{lock.ts → state/lock.ts} +0 -0
- /package/extensions/wechat/{channel.ts → text/channel.ts} +0 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
# session-bus — coordinate parallel Pi sessions
|
|
2
|
+
|
|
3
|
+
A local coordination bus for Pi sessions on the same machine and repository:
|
|
4
|
+
sessions discover each other, exchange short messages and events, consume an
|
|
5
|
+
inbox, and claim paths before editing. A session joins explicitly, so the
|
|
6
|
+
board opens empty and a peer never appears because the package loaded. Delivery is pull-based, and a session that
|
|
7
|
+
joined listens for new mail — a report at its next turn, or,
|
|
8
|
+
with `wake`, one raised turn as soon as the agent is idle, including right after
|
|
9
|
+
a running turn settles. There is no network service and no hidden context.
|
|
10
|
+
|
|
11
|
+
<img src="../../assets/screens/session.png" alt="The /sessions screen: a counts summary, session rows with unread and claim counts, and the focused session's cwd, claims, and identity" width="100%">
|
|
12
|
+
|
|
13
|
+
## Capabilities
|
|
14
|
+
|
|
15
|
+
Status records what a reader can rely on: **Shipped** is in a tagged release, **Unreleased** is complete in the current tree but not yet tagged, and **Partial** has one released part and one unreleased part. Rows are ordered by user value, highest first.
|
|
16
|
+
|
|
17
|
+
| Capability | How | What you get | Status |
|
|
18
|
+
|---|---|---|---|
|
|
19
|
+
| Membership | `session join [label]`, `/team join [<label>]`, the board's `w` | The join: this session is on the bus under a stable label peers address, and listens for mail unless told not to. A label given to `session join` or `/team join` is also Pi's session name, so the footer and `/resume` agree with the record; `w` joins under the name the bus derives and writes nothing to Pi. | Unreleased |
|
|
20
|
+
| Peer discovery | `session status` | Every active session with its unread count and claims, plus this session's identity, in one read. | Shipped |
|
|
21
|
+
| Live board | `/sessions` | Sessions, this session's unread mail, and every claim held on the repository, on one screen. | Shipped |
|
|
22
|
+
| Board actions | `/sessions` (`w`, `x`) | Start or stop listening from the board, or release an own claim behind a confirmation, without leaving it. | Shipped |
|
|
23
|
+
| Short messages | `session send` | One message per recipient, addressed by label or id, with a broadcast for events. | Shipped |
|
|
24
|
+
| Message kinds | `session send` with `kind` | A `message`, `event`, `ask`, or `handoff` keeps its meaning; `ask` does not block the sender, and a `handoff` is named as the work it assigns. | Shipped |
|
|
25
|
+
| Message paths | `session send` with `paths` | A message can name the repository paths it concerns, echoed in the inbox row. | Shipped |
|
|
26
|
+
| Inbox on demand | `session inbox` | Up to `limit` messages, oldest first, consumed only when the model asks. | Shipped |
|
|
27
|
+
| Listening | `session join` | New mail reported at the next turn; `join({wake: true})` raises one turn once the agent is idle, and `join({listen: false})` stops it. | Unreleased |
|
|
28
|
+
| Path claims | `session claim`, `session release` | A file or directory prefix held by one session at a time. | Shipped |
|
|
29
|
+
|
|
30
|
+
## Quickstart
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pi --extension ./extensions/session-bus # load just this extension
|
|
34
|
+
pi -e . # load the whole package
|
|
35
|
+
pi install ./ # install the package for a user
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```text
|
|
39
|
+
/sessions # open the board: empty until this session joins
|
|
40
|
+
session({action:"join", label:"billing-core"}) # join, name this session for Pi, and listen for mail
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Nothing joins on `session_start`, in any mode: the board and `status` only read, so
|
|
44
|
+
a fresh session sees an empty board and a peer's session is not listed until it
|
|
45
|
+
joins. `join` is the join — it puts this session on the bus, names it when a label
|
|
46
|
+
is given, and starts listening — and so is the first action that needs you to be
|
|
47
|
+
addressable: `send`, `inbox`, `claim`, or `release`.
|
|
48
|
+
|
|
49
|
+
## Tool
|
|
50
|
+
|
|
51
|
+
`session` — `exposure: "direct"` (the default), `executionMode: "sequential"`,
|
|
52
|
+
read-write and closed-world. One action-shaped tool keeps the prompt surface
|
|
53
|
+
small: each action takes only the parameters it needs, and the result is one
|
|
54
|
+
collapsed line that expands to the board's rows.
|
|
55
|
+
|
|
56
|
+
| Action | Inputs | Result |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| `status` | — | Active sessions with unread counts, claims, and this session's identity. Prunes stale records. A read: it never adds this session to the bus. |
|
|
59
|
+
| `send` | `to`, `text`, optional `kind`, optional `paths` | Writes one message per recipient. `to: "*"` is only valid with `kind: "event"`. |
|
|
60
|
+
| `inbox` | optional `limit` | Returns and deletes up to `limit` messages, oldest first (default 20, max 50). |
|
|
61
|
+
| `claim` | `paths` | Claims repository-relative files or directory prefixes; returns conflicts with their owner. |
|
|
62
|
+
| `release` | `paths` | Removes claims owned by this session; reports paths owned by someone else. |
|
|
63
|
+
| `join` | optional `label`, `wake`, `listen` | Joins the bus, names this session on it and in Pi when a label is given — the same path and uniqueness rule as `/team join`, so the footer and `/resume` agree with the record peers address — and starts listening for mail. `wake: true` raises one turn per burst once the agent is idle; `wake: false` reports at the next turn; `listen: false` joins without listening. A label a live session holds is refused with its owner named, and the refusal leaves both names and the watch alone. The result reports the label the bus settled on and whether mail will reach this session. |
|
|
64
|
+
|
|
65
|
+
A claim is all-or-nothing: if one path conflicts, nothing is claimed. A claim on
|
|
66
|
+
a directory conflicts with claims below it and above it, so `src/api` and
|
|
67
|
+
`src/api/routes.ts` cannot be held by different sessions. There is no `force`
|
|
68
|
+
takeover; wait for the owner, ask it to release, or let the session expire.
|
|
69
|
+
|
|
70
|
+
Paths resolve against the caller's working directory and are stored relative to
|
|
71
|
+
its repository root; a path outside the repository is refused.
|
|
72
|
+
|
|
73
|
+
`wake` is the mode, not an arm-time switch: `join({ wake: true })` raises a turn
|
|
74
|
+
for a session that was reporting at the next turn, and `join({ wake: false })` —
|
|
75
|
+
or a plain `join` — sets it back. The board's `w` starts and stops listening,
|
|
76
|
+
and it starts a plain watch; set `wake` with the tool. Nothing joins or listens on
|
|
77
|
+
`session_start`; a session that never asks stays pull-only and off the bus.
|
|
78
|
+
|
|
79
|
+
## Commands
|
|
80
|
+
|
|
81
|
+
`/sessions` is the human surface: in `tui` mode it opens the live board described
|
|
82
|
+
in [the board](#the-sessions-board), and every other mode prints the compact
|
|
83
|
+
listing. It takes no argument, and it joins nothing — the board's `w` is the one
|
|
84
|
+
act there that puts this session on the bus.
|
|
85
|
+
|
|
86
|
+
| Command | Effect |
|
|
87
|
+
|---|---|
|
|
88
|
+
| `/sessions` | Open the board: `Tab` cycles Sessions, Inbox, and Claims. `Enter` opens the focused row, `w` starts or stops listening, and in Claims `x` releases a claim you hold after a y/N confirmation. A non-TUI caller prints the listing. |
|
|
89
|
+
|
|
90
|
+
Labels are how another session addresses you. Name one by the outcome it owns
|
|
91
|
+
(`billing-core`, `integrator`, `verify-billing`), not by a layer or a role;
|
|
92
|
+
duplicate labels get a short id suffix automatically.
|
|
93
|
+
|
|
94
|
+
A label is also Pi's session name, so the footer's `[label]` and `/resume` agree
|
|
95
|
+
with the bus. Because the bus derives a joining session's label from that name, a
|
|
96
|
+
named session comes back under the same label after a resume.
|
|
97
|
+
|
|
98
|
+
The tool's `join` action is the same join from the model's side, so a `board` or a
|
|
99
|
+
`team` call can put a session on the bus without the user naming it, and
|
|
100
|
+
`/team join [<label>]` is that join from the terminal.
|
|
101
|
+
|
|
102
|
+
## Behaviour by mode
|
|
103
|
+
|
|
104
|
+
| Mode | Behaviour |
|
|
105
|
+
|---|---|
|
|
106
|
+
| `tui` | Does not join on start. `/sessions` opens the board: sessions, this session's unread mail, and every claim, with `Tab` between them, and lists nobody in this session until it joins. |
|
|
107
|
+
| `rpc`, `json`, `print` | Same rule: `/sessions` and `status` are reads, and the first addressable action joins. This is how a subagent or an RPC-driven session opts in. |
|
|
108
|
+
| WeChat peer | Same as `rpc`: the peer is a separate AgentSession in the bridge process and joins only on explicit use. Messages are read by that peer’s model; the bridge does not forward them to the phone. |
|
|
109
|
+
|
|
110
|
+
Every mode can join and listen; the notice is a visible transcript message, so
|
|
111
|
+
a `wake` raises that session’s own turn, not a phone notification.
|
|
112
|
+
|
|
113
|
+
## Configuration
|
|
114
|
+
|
|
115
|
+
None in v1. The heartbeat (10 s), watch poll (2 s), session TTL (10 min), message
|
|
116
|
+
TTL (24 h), text cap, inbox limit, and preview cap are fixed, not configurable.
|
|
117
|
+
The bus lives under the agent directory, not the project, so it is never committed.
|
|
118
|
+
|
|
119
|
+
## Parallel workflow
|
|
120
|
+
|
|
121
|
+
1. The user joins each session (`/team join <label>`, or `w` on the board), and the
|
|
122
|
+
contract (OpenAPI, schema, or types) is written before parallel work starts.
|
|
123
|
+
2. Each session claims its paths before touching them, then implements its side.
|
|
124
|
+
3. A contract, build, or test change goes out as an event:
|
|
125
|
+
`session({action:"send", to:"*", kind:"event", text:"contract v2: ..."})`.
|
|
126
|
+
4. Other sessions consume `inbox` at the start of their next turn, or join with
|
|
127
|
+
`session({action:"join", wake:true})` so a message raises their turn as soon
|
|
128
|
+
as they are idle; a wake that arrives mid-turn is raised once that run
|
|
129
|
+
settles, never pushed into the running turn.
|
|
130
|
+
5. Whoever integrates merges the worktrees and runs the project's full check.
|
|
131
|
+
|
|
132
|
+
The bus carries coordination, not authority. The plan, todo list, contract, and
|
|
133
|
+
git branch remain the source of truth.
|
|
134
|
+
|
|
135
|
+
## The /sessions board
|
|
136
|
+
|
|
137
|
+
The TUI opens the board; RPC, JSON, and print print the compact listing. `Tab`
|
|
138
|
+
cycles Sessions, Inbox, and Claims, `Enter` opens the focused row, and `Esc` goes
|
|
139
|
+
back from a row's pane. `↑`/`↓` and `PgUp`/`PgDn` scroll a list; a pane adds
|
|
140
|
+
`g`/`G` for its ends.
|
|
141
|
+
|
|
142
|
+
The board changes only this session's own state: `w` starts or stops listening and
|
|
143
|
+
`x` releases a claim it holds. Mail is previewed, not consumed — `session inbox`
|
|
144
|
+
consumes it — and no view shows another session's inbox or releases its claims.
|
|
145
|
+
|
|
146
|
+
## Message kinds
|
|
147
|
+
|
|
148
|
+
Message kinds are `message` (default), `event`, `ask`, and `handoff`. An `ask` is
|
|
149
|
+
still a message: it does not block or wait for a reply. Text is capped at 4,000
|
|
150
|
+
characters.
|
|
151
|
+
|
|
152
|
+
A `handoff` is the one kind that assigns work, so it is the one kind the notice
|
|
153
|
+
treats as an instruction: the waking turn is headlined `1 handoff`, the preview
|
|
154
|
+
keeps 800 characters so the card, its paths, and the next step survive, and the
|
|
155
|
+
hint says the work is yours — read it with `session inbox`, record the plan, and
|
|
156
|
+
start. Nothing else changes: a handoff still waits in the inbox, and the recipient's
|
|
157
|
+
listening watch is what raises the turn.
|
|
158
|
+
|
|
159
|
+
## Limitations
|
|
160
|
+
|
|
161
|
+
- **Same machine, same OS user.** Any process with that user’s file access can
|
|
162
|
+
read or write the bus; there is no authentication between sessions.
|
|
163
|
+
- **Pull-based by default.** A message does not reach a session that has not
|
|
164
|
+
joined and is not listening. Agents must call `inbox` at coordination points, and
|
|
165
|
+
joining listens unless `join({ listen: false })` says otherwise.
|
|
166
|
+
- **A reader is not a member.** `/sessions` and `session status` never add this
|
|
167
|
+
session to the bus, so an unjoined session reads a board it is not on until it
|
|
168
|
+
joins or acts.
|
|
169
|
+
- **Membership is per session and session-only.** It survives neither a resume nor a
|
|
170
|
+
new process; unread mail is previewed once more after `/resume`, and the record
|
|
171
|
+
goes when the session stops.
|
|
172
|
+
- **The board acts on your own state only.** `w` starts or stops listening and `x`
|
|
173
|
+
releases a claim it holds. Mail is a preview, not a consume: the board never
|
|
174
|
+
deletes a message, never marks one reported, and never touches a peer's inbox or
|
|
175
|
+
claim.
|
|
176
|
+
- **Two listening peers can ping-pong.** Each reply wakes the other as soon as it is
|
|
177
|
+
idle. A wake raises one turn per burst, and `inbox` consumes, so the loop needs
|
|
178
|
+
both sides to keep replying; the answer is to stop listening on one of them.
|
|
179
|
+
- **No broadcast history.** A broadcast reaches the sessions active at send time;
|
|
180
|
+
a session that joins later does not receive it.
|
|
181
|
+
- **No forced claims.** A crashed owner blocks its paths until the session TTL
|
|
182
|
+
expires. This is deliberate; stealing a claim is worse than waiting.
|
|
183
|
+
- **Bounded by TTL.** An idle session with a stopped heartbeat is pruned after
|
|
184
|
+
ten minutes; its record, its claims, and an inbox directory left empty go with
|
|
185
|
+
it, and its unread mail expires after 24 hours. A claim belongs to a record
|
|
186
|
+
that was read from disk, not to the reader's idea of who is active, so a peer
|
|
187
|
+
that claimed in the middle of someone else's prune keeps it.
|
|
188
|
+
- **One session id, one record.** Resuming a session in a second pane gives two
|
|
189
|
+
processes the same id, and the record names the process that owns it: the older
|
|
190
|
+
pane's shutdown leaves the record and the claims to the newer one instead of
|
|
191
|
+
releasing them. Listening is still per process, so the survivor's watch is the
|
|
192
|
+
survivor's.
|
|
193
|
+
- **A message file that does not parse is dropped.** The board's read never
|
|
194
|
+
consumes mail, but it does delete a file it cannot validate, so a truncated
|
|
195
|
+
message cannot linger on the bus.
|
|
196
|
+
- **No network.** It is not a distributed bus and does not synchronize machines.
|
|
197
|
+
|
|
198
|
+
## Pi integration
|
|
199
|
+
|
|
200
|
+
| Point | Detail |
|
|
201
|
+
|---|---|
|
|
202
|
+
| Tools | `session` — one action-shaped tool; `readOnlyHint: false`, `destructiveHint: true`, `openWorldHint: false`, `executionMode: "sequential"`. |
|
|
203
|
+
| Commands | `/sessions` — the board in `tui`, the listing elsewhere; no arguments and no sub-commands. |
|
|
204
|
+
| UI | `tui` mounts `SessionListComponent` through `withPolledScreen` (rails suppressed, a 1 s read-only poll of `snapshot()` + `peek()`); every other mode prints through `ctx.ui.notify`. No status chip, so listening never changes the footer. |
|
|
205
|
+
| Events | `session_start` resolves the repository and arms the mail trigger, without joining; `before_agent_start` reports queued non-wake mail at the next turn, `agent_settled` retries a wake the run suppressed, and `context` prunes stale notices; `session_shutdown` clears the heartbeat, the watch ticker, and the record. The seven hooks are `lib/report.ts`'s `registerPendingChannel`, not a local copy. |
|
|
206
|
+
| State | Files under `<agent-dir>/session-bus/<repo-key>/`; nothing in the session branch. Listening is session-only. `/resume` re-registers the same session id. |
|
|
207
|
+
| Lifecycle | The factory only registers. The runtime starts on `session_start`; heartbeat, watch ticker, and cleanup belong to it. Disabling `session-bus` through `PI_DISABLED_EXTENSIONS` registers nothing. |
|
|
208
|
+
| Mode | Uses `ctx.mode` to decide whether `/sessions` mounts the board and how a label is derived; nothing joins at start in any mode; `askHuman` is not involved and no status chip is published. |
|
|
209
|
+
|
|
210
|
+
## Design notes
|
|
211
|
+
|
|
212
|
+
- **Common git dir as the key.** `git rev-parse --git-common-dir` gives all
|
|
213
|
+
worktrees of one repository the same bus; `--show-toplevel` normalizes claim
|
|
214
|
+
paths within each worktree. Non-repository sessions fall back to their cwd.
|
|
215
|
+
- **One path, one claim.** The cwd is canonicalized at join and every claim or
|
|
216
|
+
release is re-keyed by its real target, so a working directory reached through
|
|
217
|
+
a symlink still resolves inside its own repository, a link inside the repository
|
|
218
|
+
and the file it names are the same claim, and a link that leaves the repository
|
|
219
|
+
is refused. The automatic label is the real basename of the cwd, so a session
|
|
220
|
+
started through a link named differently is named after the directory it is
|
|
221
|
+
actually in. An absolute path argument that sits outside the repository is
|
|
222
|
+
refused before it is re-keyed, and a relative one whose target leaves the
|
|
223
|
+
repository is refused by the re-keying itself.
|
|
224
|
+
- **One file per item.** Sessions never append to a shared registry: each session
|
|
225
|
+
writes its own record, each message is its own file, and a claim is a temp file
|
|
226
|
+
hard-linked into place — an exclusive create no reader can see half-written.
|
|
227
|
+
This avoids a cross-process lock for the common path.
|
|
228
|
+
- **Lazy join.** Loading a package extension must not make every subagent and
|
|
229
|
+
WeChat peer a team member. Nothing joins at start, in any mode; a session opts
|
|
230
|
+
in by joining, or by the first action that needs it to be addressable.
|
|
231
|
+
- **Listening is on the record.** `join` writes this session's watch state onto its
|
|
232
|
+
own record, so a peer's `status` read says whether mail will reach it without
|
|
233
|
+
asking it anything. The field is absent when the session is not listening — which
|
|
234
|
+
is also how a record written before the field existed reads, and neither is a
|
|
235
|
+
claim about a peer that has since gone quiet.
|
|
236
|
+
- **Sanitized at the boundary.** Messages are model-authored and are sanitized
|
|
237
|
+
before rendering, like other untrusted text in the transcript.
|
|
238
|
+
- **Poll, not `fs.watch`.** The listening watch reads this session's inbox directory
|
|
239
|
+
with one `readdir` every 2 s, the same trade `watch` makes for a file tail, so
|
|
240
|
+
it does not depend on a platform's event API or a filename in the event. Only
|
|
241
|
+
a listening session polls.
|
|
242
|
+
- **Read the board, prune on the next call.** The open board polls a read-only
|
|
243
|
+
`snapshot()` and `peek()` that hide a session past the TTL, a claim held by one,
|
|
244
|
+
and consume nothing. The physical prune, and the claim release that goes with
|
|
245
|
+
it, happen on the next `status` or `claim` call — which is the call that reader
|
|
246
|
+
is about to make.
|
|
247
|
+
- **Release by the stored path.** A claim is stored relative to the repo root of
|
|
248
|
+
the session that took it, so releasing one resolves nothing: re-resolving it
|
|
249
|
+
against a cwd below that root would target `src/src/api`. The screen refuses a
|
|
250
|
+
peer's claim before asking the store, which checks the owner again.
|
|
251
|
+
- **A pane is a log or prose.** `DetailContents.text` wraps at the render width
|
|
252
|
+
and `tail: false` opens it at the top, so a message reads as prose while a job
|
|
253
|
+
log keeps following its tail.
|
|
254
|
+
- **The notice reuses the shared report channel.** `lib/report.ts` already owns
|
|
255
|
+
whether an outcome may raise a turn; the bus supplies the pending mail and the
|
|
256
|
+
text, and bytefly renders the notice like a `job` or `watch` one.
|
|
257
|
+
|
|
258
|
+
## Files
|
|
259
|
+
|
|
260
|
+
| File | Responsibility |
|
|
261
|
+
|---|---|
|
|
262
|
+
| `index.ts` | Disable guard, registration, the pending-report channel, and the session hooks. |
|
|
263
|
+
| `join-request.ts` | The `board`/`team` join request: one `bus.join` answer, and the label it settles becomes Pi's session name. |
|
|
264
|
+
| `runtime.ts` | Lifecycle, repo key, joining, heartbeat, and the session's own reads and writes. |
|
|
265
|
+
| `mail-watch.ts` | The armed inbox watch: the poll, the not-yet-reported queue, and its state. |
|
|
266
|
+
| `store.ts` | Atomic JSON files, inbox, peek, claims, pruning, and the one release loop. |
|
|
267
|
+
| `protocol.ts` | Pure labels, targets, paths, validation, conflict rules. |
|
|
268
|
+
| `overlap.ts` | The one "do these paths collide?" predicate: `claimsConflict`, which normalizes both spellings of a claim path first. |
|
|
269
|
+
| `resolve.ts` | `resolveClaimPaths` and `claimPathMessage`: a claim path made repository-relative, posix, and canonical through its real target, or the refusal naming why. |
|
|
270
|
+
| `types.ts` | The stored shapes — session record, message, claim — and the tool's `details`. |
|
|
271
|
+
| `tui.ts` | The `/sessions` board, its scopes, and the pane over one row; the row grammar is in `rows.ts`. |
|
|
272
|
+
| `rows.ts` | The session/claim/message row builders and summaries shared by the board and the transcript. |
|
|
273
|
+
| `result.ts` | The `session` tool's expanded result block and its collapsed line. |
|
|
274
|
+
| `notice.ts` | The mail notice's model text and renderer details. |
|
|
275
|
+
| `tools.ts` | The `session` tool and its renderer. |
|
|
276
|
+
| `commands.ts` | `/sessions`. |
|
|
277
|
+
| `format.ts` | Model-facing listing and result text. |
|
|
278
|
+
|
|
279
|
+
## Testing
|
|
280
|
+
|
|
281
|
+
The capability suites — `format`, `protocol`, `store`, `runtime`, `tui`, `tools`,
|
|
282
|
+
`overlap`, `resolve`, and `extension` — are declared in `test/suite-manifest.ts`.
|
|
283
|
+
They build a bus in a temp directory with a fixed clock, so none needs a live peer; `runtime.test.ts`
|
|
284
|
+
puts its real-repository cases behind `runSlowSuites`, which skips unless `CI` or
|
|
285
|
+
`PI_SLOW_TESTS=1` is set.
|
|
@@ -6,49 +6,52 @@ exploration or parallel work does not fill the main conversation. A single run
|
|
|
6
6
|
can also start in the background and be inspected, collected, or cancelled with
|
|
7
7
|
`/agents`.
|
|
8
8
|
|
|
9
|
+
## Capabilities
|
|
10
|
+
|
|
11
|
+
Status records what a reader can rely on: **Shipped** is in a tagged release, **Unreleased** is complete in the current tree but not yet tagged, and **Partial** has one released part and one unreleased part. Rows are ordered by user value, highest first.
|
|
12
|
+
|
|
13
|
+
| Capability | How | What you get | Status |
|
|
14
|
+
|---|---|---|---|
|
|
15
|
+
| Isolated context | `subagent` | Each task runs in its own process with its own transcript, and only its final reply returns to the caller. | Shipped |
|
|
16
|
+
| Explicit tool allowlist | `--tools` for every child | An agent gets only its declared tools, not the parent's ambient extensions; `worker` cannot recurse into `subagent`. | Shipped |
|
|
17
|
+
| Read-only delegation | `subagent` (`readOnly: true`) | Every spawned agent runs with a reader-only tool set, enforced at the process boundary. | Shipped |
|
|
18
|
+
| Plan-mode guard | `subagent`, plan mode | Plan mode forces every call read-only and refuses `/agents` until the plan is approved. | Shipped |
|
|
19
|
+
| Project-agent trust gate | `agentScope` with the trust gate | A `.pi/agents` file loads only when the project is trusted or confirmed, so a repository cannot inject a prompt by adding a file. | Shipped |
|
|
20
|
+
| Three delegation modes | `subagent` (`agent` + `task`, `tasks[]`, `chain[]`) | One agent, an independent batch, or an ordered chain whose steps receive `{previous}`. | Shipped |
|
|
21
|
+
| Parallel and chain bounds | `tasks[]`, `chain[]` | Parallel accepts up to 8 tasks with 4 running at once; a chain accepts up to 8 steps and stops at the first failure. | Shipped |
|
|
22
|
+
| Per-step working directory | `cwd` | A single run, each parallel task, or each chain step can name its working directory; a relative path resolves against the active root. | Shipped |
|
|
23
|
+
| Built-in agents | `subagent` (`agent`) | Recon, planning, review, implementation, research, testing, debugging, and docs work without writing an agent file. | Shipped |
|
|
24
|
+
| External agents | `subagent` (`agentScope`) | Markdown agents from the user directory and the nearest `.pi/agents`, opt-in per scope and never replacing a built-in silently. | Shipped |
|
|
25
|
+
| Background runs | `subagent` (`background: true`), `/agents` | A single run continues after the call returns and is inspected, collected, or cancelled from the board. | Shipped |
|
|
26
|
+
| Background concurrency cap | `backgroundMax` | At most four background children run at once; finished runs release their slot. | Shipped |
|
|
27
|
+
| Session-owned background run | `background: true` | `/tree` does not move the run and a restart cannot resume it; an unfinished start shows as `interrupted`. | Shipped |
|
|
28
|
+
| Live progress | `subagent` | Tool calls and progress update in the transcript while the child runs, with a `↳ N` footer chip and a finish notice. | Shipped |
|
|
29
|
+
| Bounded runs | `timeoutMs` | A child that hangs is killed at the deadline and reported as a failed run rather than rejecting the whole call. | Shipped |
|
|
30
|
+
| Optional transcripts | `transcripts` | Per-run streams under `.pi/subagents/`, written by the parent and pruned by age and count. | Shipped |
|
|
31
|
+
| Inherited model | `subagent` | Children use the dispatching session's model and thinking level unless an agent overrides them. | Shipped |
|
|
32
|
+
|
|
33
|
+
## Quickstart
|
|
34
|
+
|
|
9
35
|
```bash
|
|
10
36
|
pi --extension ./extensions/subagent # load just this extension
|
|
11
37
|
pi -e . # load the whole @gavin-hu/my-pi-agent package
|
|
12
|
-
pi install ./ # install the package
|
|
38
|
+
pi install ./ # install the package for a user
|
|
13
39
|
```
|
|
14
40
|
|
|
15
|
-
|
|
41
|
+
```text
|
|
42
|
+
subagent({ agent: "explorer", task: "Find every place the HTTP client is retried" })
|
|
43
|
+
/agents # inspect, collect, or cancel background runs
|
|
44
|
+
```
|
|
16
45
|
|
|
17
|
-
|
|
46
|
+
## Tool
|
|
18
47
|
|
|
19
48
|
<img src="../../assets/tools/subagent.png" alt="The subagent tool's call and result: the call line and an indented body with the two tasks, each agent's section rule, its summary, and the usage total" width="100%">
|
|
20
49
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
- **Isolated context.** Each subagent is a separate `pi` process with its own
|
|
28
|
-
transcript; only its final reply is returned to the caller.
|
|
29
|
-
- **Built-in agents.** `explorer`, `planner`, `reviewer`, `worker`,
|
|
30
|
-
`researcher`, `tester`, `debugger`, and `documenter` ship with the extension.
|
|
31
|
-
- **External agents.** Optionally load markdown agents from `~/.pi/agents`
|
|
32
|
-
(user) and the nearest `.pi/agents` (project). They are opt-in per scope and
|
|
33
|
-
never replace the built-ins silently.
|
|
34
|
-
- **Three modes, plus background.** One agent, a parallel batch, or a sequential
|
|
35
|
-
chain; a single run can also continue after the tool returns and be collected
|
|
36
|
-
from `/agents`. A running helper adds a `↳ N` footer chip, and a finish notice
|
|
37
|
-
points back at `/agents`.
|
|
38
|
-
- **Streaming.** Tool calls and progress update live in the transcript while the
|
|
39
|
-
subagent runs; the `app.tools.expand` key (`ctrl+o` by default) expands the
|
|
40
|
-
full task, tool calls, Markdown output, and usage.
|
|
41
|
-
- **Bounded runs.** A per-run deadline kills a child that hangs, and reports it
|
|
42
|
-
as a failed run instead of rejecting the whole call.
|
|
43
|
-
- **Optional transcripts.** Opt-in per-run stream under `.pi/subagents/`,
|
|
44
|
-
written by the parent and pruned by age and count.
|
|
45
|
-
- **Inherited model.** Subagents use the dispatching session's model and thinking
|
|
46
|
-
level unless an agent overrides them (none do).
|
|
47
|
-
|
|
48
|
-
## Tool
|
|
49
|
-
|
|
50
|
-
`subagent` takes exactly one mode. Every call also accepts `agentScope` and
|
|
51
|
-
`readOnly`.
|
|
50
|
+
`subagent` — `exposure: "direct"` (the default), read-write, open-world,
|
|
51
|
+
destructive, and parallel across calls: only a chain orders itself. A call takes
|
|
52
|
+
exactly one mode, plus `agentScope`, `readOnly`, and `cwd`. A call with no mode,
|
|
53
|
+
or with two, is refused before any process starts, and an unknown agent name is
|
|
54
|
+
refused with the resolved roster.
|
|
52
55
|
|
|
53
56
|
| Field | Value |
|
|
54
57
|
|---|---|
|
|
@@ -59,78 +62,38 @@ How the `subagent` call and its result look in the transcript — the call line
|
|
|
59
62
|
| `cwd` | Optional working directory for the spawned process (single mode; per-step for the others). A relative path resolves against the active root. |
|
|
60
63
|
| `agentScope` | `user` (default), `project`, or `both` — which external agent directories to load. |
|
|
61
64
|
| `readOnly` | `true` forces every spawned agent to a reader-only tool set; plan mode sets it automatically. |
|
|
62
|
-
| `exposure` | `direct`, active by default. |
|
|
63
|
-
| `annotations` | `readOnlyHint: false`, `openWorldHint: true`. |
|
|
64
|
-
|
|
65
|
-
## Behaviour by mode
|
|
66
|
-
|
|
67
|
-
### Single
|
|
68
|
-
|
|
69
|
-
```json
|
|
70
|
-
{ "agent": "explorer", "task": "Find every place the HTTP client is retried", "cwd": "." }
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### Background
|
|
74
|
-
|
|
75
|
-
```json
|
|
76
|
-
{ "agent": "explorer", "task": "Map the auth module", "background": true }
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
The tool returns immediately with a run id such as `a2`. `/agents` lists every
|
|
80
|
-
run with its status, elapsed time, and usage; `Enter` opens the run, `c` collects
|
|
81
|
-
a finished result into the conversation, and `x` cancels a running one. While a
|
|
82
|
-
run is active, its detail pane follows a bounded preview of the latest output,
|
|
83
|
-
and a `↳ N` chip in the footer counts running helpers. A finish notice points
|
|
84
|
-
back at `/agents`; the finalized result replaces the preview when the run
|
|
85
|
-
finishes. A background run is session-owned and is bounded by the same timeout
|
|
86
|
-
and transcript settings as a foreground run.
|
|
87
|
-
|
|
88
|
-
### Parallel
|
|
89
|
-
|
|
90
|
-
Runs up to 8 tasks, at most 4 at once. Independent tasks only.
|
|
91
|
-
|
|
92
|
-
```json
|
|
93
|
-
{
|
|
94
|
-
"tasks": [
|
|
95
|
-
{ "agent": "explorer", "task": "Map the auth module" },
|
|
96
|
-
{ "agent": "explorer", "task": "Map the billing module" }
|
|
97
|
-
]
|
|
98
|
-
}
|
|
99
|
-
```
|
|
100
65
|
|
|
101
|
-
|
|
102
|
-
output remains in the tool details.
|
|
103
|
-
|
|
104
|
-
### Chain
|
|
105
|
-
|
|
106
|
-
Runs steps in order. `{previous}` in a step's `task` is replaced by the prior
|
|
107
|
-
step's final output. The chain stops at the first failing step, and a chain has
|
|
108
|
-
at most 8 steps (like the parallel task cap).
|
|
109
|
-
|
|
110
|
-
```json
|
|
111
|
-
{
|
|
112
|
-
"chain": [
|
|
113
|
-
{ "agent": "explorer", "task": "Find the config loader" },
|
|
114
|
-
{ "agent": "planner", "task": "Using this context, plan a refactor:\n\n{previous}" },
|
|
115
|
-
{ "agent": "worker", "task": "Implement the plan:\n\n{previous}" }
|
|
116
|
-
]
|
|
117
|
-
}
|
|
118
|
-
```
|
|
66
|
+
## Commands
|
|
119
67
|
|
|
120
|
-
|
|
68
|
+
<img src="../../assets/screens/agents.png" alt="The /agents screen: running and finished background helpers with status, task, elapsed time, and usage" width="100%">
|
|
121
69
|
|
|
122
70
|
| Command | What it does |
|
|
123
71
|
|---|---|
|
|
124
72
|
| `/agents` | Open the background-run board: inspect, collect, or cancel. |
|
|
125
73
|
|
|
126
74
|
The board is TUI-only for actions; RPC, print, and JSON print the run list.
|
|
127
|
-
|
|
75
|
+
`Enter` opens the run, `c` collects a finished result into the conversation, and
|
|
76
|
+
`x` cancels a running one. Collecting sends the result as a user message, which
|
|
77
|
+
starts the next turn. Plan mode refuses the command until the plan is approved.
|
|
78
|
+
|
|
79
|
+
## Behaviour by mode
|
|
80
|
+
|
|
81
|
+
The tool and its background runs work in every mode; only the run board is a
|
|
82
|
+
terminal surface.
|
|
83
|
+
|
|
84
|
+
- In `tui` mode `/agents` opens the board, the footer carries a `↳ N` chip while
|
|
85
|
+
helpers run, and a finish notice points back at `/agents`.
|
|
86
|
+
- In RPC, print, and JSON `/agents` reports the same runs as a notification — a
|
|
87
|
+
count, then one line per run with agent, task, status, elapsed time, and usage
|
|
88
|
+
— and there is no board and no action.
|
|
89
|
+
- In plan mode `subagent` stays active but every call is forced read-only, and
|
|
90
|
+
`/agents` refuses until the plan is approved.
|
|
128
91
|
|
|
129
92
|
## Configuration
|
|
130
93
|
|
|
131
94
|
Read from the shared `bytefly.json` section `subagent` (global then project),
|
|
132
95
|
once per session. Keys and bounds live in the
|
|
133
|
-
[configuration page](
|
|
96
|
+
[configuration page](../configuration.md#subagent--delegated-runs);
|
|
134
97
|
the behaviour is here.
|
|
135
98
|
|
|
136
99
|
- **`timeoutMs`** (default ten minutes, `0` disables) bounds one child run. The
|
|
@@ -186,18 +149,75 @@ confirmation before running them in an interactive session; without a UI it
|
|
|
186
149
|
refuses instead of running them silently. The gate is driven only by the
|
|
187
150
|
project's trusted state — the model cannot turn it off.
|
|
188
151
|
|
|
152
|
+
## Delegation modes
|
|
153
|
+
|
|
154
|
+
One mode per call, with a single `background` run available on the single-agent
|
|
155
|
+
shape.
|
|
156
|
+
|
|
157
|
+
### Single
|
|
158
|
+
|
|
159
|
+
```json
|
|
160
|
+
{ "agent": "explorer", "task": "Find every place the HTTP client is retried", "cwd": "." }
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Background
|
|
164
|
+
|
|
165
|
+
```json
|
|
166
|
+
{ "agent": "explorer", "task": "Map the auth module", "background": true }
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
The tool returns immediately with a run id such as `a2`. `/agents` lists every
|
|
170
|
+
run with its status, elapsed time, and usage. While a run is active, its detail
|
|
171
|
+
pane follows a bounded preview of the latest output, and a `↳ N` chip in the
|
|
172
|
+
footer counts running helpers. A finish notice points back at `/agents`; the
|
|
173
|
+
finalized result replaces the preview when the run finishes. A background run is
|
|
174
|
+
session-owned and is bounded by the same timeout and transcript settings as a
|
|
175
|
+
foreground run.
|
|
176
|
+
|
|
177
|
+
### Parallel
|
|
178
|
+
|
|
179
|
+
Runs up to 8 tasks, at most 4 at once. Independent tasks only.
|
|
180
|
+
|
|
181
|
+
```json
|
|
182
|
+
{
|
|
183
|
+
"tasks": [
|
|
184
|
+
{ "agent": "explorer", "task": "Map the auth module" },
|
|
185
|
+
{ "agent": "explorer", "task": "Map the billing module" }
|
|
186
|
+
]
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Each task's output is capped at 50 KiB in the model-facing reply; the complete
|
|
191
|
+
output remains in the tool details.
|
|
192
|
+
|
|
193
|
+
### Chain
|
|
194
|
+
|
|
195
|
+
Runs steps in order. `{previous}` in a step's `task` is replaced by the prior
|
|
196
|
+
step's final output. The chain stops at the first failing step, and a chain has
|
|
197
|
+
at most 8 steps (like the parallel task cap).
|
|
198
|
+
|
|
199
|
+
```json
|
|
200
|
+
{
|
|
201
|
+
"chain": [
|
|
202
|
+
{ "agent": "explorer", "task": "Find the config loader" },
|
|
203
|
+
{ "agent": "planner", "task": "Using this context, plan a refactor:\n\n{previous}" },
|
|
204
|
+
{ "agent": "worker", "task": "Implement the plan:\n\n{previous}" }
|
|
205
|
+
]
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
189
209
|
## Built-in agents
|
|
190
210
|
|
|
191
211
|
| Agent | Tools | Purpose |
|
|
192
212
|
|---|---|---|
|
|
193
|
-
| `explorer` | `read, grep, find, ls` | Fast codebase recon; returns files + line ranges, key code, architecture, and where to start. |
|
|
194
|
-
| `planner` | `read, grep, find, ls` | Delegated planning in an isolated context (headless/chain); never edits. |
|
|
195
|
-
| `reviewer` | `read, grep, find, ls, bash` | Reviews for bugs, security, and maintainability; bash limited to read-only `git diff/log/show`. |
|
|
196
|
-
| `worker` | `read, write, edit, bash, grep, find, ls` | General-purpose executor; reports what changed. |
|
|
197
|
-
| `researcher` | `read, grep, find, ls, web_search, web_fetch` | Investigates external questions and returns sourced findings with confidence. |
|
|
198
|
-
| `tester` | `read, write, edit, grep, find, ls, bash` | Adds or updates tests and runs them; never weakens assertions to pass. |
|
|
199
|
-
| `debugger` | `read, grep, find, ls, bash` | Root-causes a failure from evidence; read-only plus running tests/builds. |
|
|
200
|
-
| `documenter` | `read, write, edit, grep, find, ls, bash` | Updates READMEs, docs, and changelogs to match the code. |
|
|
213
|
+
| `explorer` | `read, grep, find, ls, read_doc` | Fast codebase recon; returns files + line ranges, key code, architecture, and where to start. |
|
|
214
|
+
| `planner` | `read, grep, find, ls, read_doc` | Delegated planning in an isolated context (headless/chain); never edits. |
|
|
215
|
+
| `reviewer` | `read, grep, find, ls, read_doc, bash` | Reviews for bugs, security, and maintainability; bash limited to read-only `git diff/log/show`. |
|
|
216
|
+
| `worker` | `read, write, edit, bash, grep, find, ls, read_doc` | General-purpose executor; reports what changed. |
|
|
217
|
+
| `researcher` | `read, grep, find, ls, read_doc, web_search, web_fetch` | Investigates external questions and returns sourced findings with confidence. |
|
|
218
|
+
| `tester` | `read, write, edit, grep, find, ls, read_doc, bash` | Adds or updates tests and runs them; never weakens assertions to pass. |
|
|
219
|
+
| `debugger` | `read, grep, find, ls, read_doc, bash` | Root-causes a failure from evidence; read-only plus running tests/builds. |
|
|
220
|
+
| `documenter` | `read, write, edit, grep, find, ls, read_doc, bash` | Updates READMEs, docs, and changelogs to match the code. |
|
|
201
221
|
|
|
202
222
|
Each agent's system prompt is appended to the subprocess, and its tool list is
|
|
203
223
|
passed as `--tools`. Every built-in has an explicit allowlist, so an agent never
|
|
@@ -210,7 +230,7 @@ for another agent (typically `worker`) to execute, and it works headlessly. It
|
|
|
210
230
|
returns the plan as text and writes nothing. For **interactive** planning where
|
|
211
231
|
the user reviews and approves a saved plan, use the plan-mode extension instead
|
|
212
232
|
(`enter_plan_mode` / `write_plan` / `exit_plan_mode`), which operates on the
|
|
213
|
-
main session and persists to
|
|
233
|
+
main session and persists to the agent directory. Plan mode keeps `subagent` available
|
|
214
234
|
for exploration but forces it read-only, so delegated recon is safe mid-plan.
|
|
215
235
|
|
|
216
236
|
## External agents
|
|
@@ -259,9 +279,9 @@ is the safe way to delegate exploration, and plan mode sets it automatically.
|
|
|
259
279
|
|
|
260
280
|
The child is restricted at the process boundary, not by its prompt: the tool
|
|
261
281
|
list passed as `--tools` is the intersection of the agent's tools with
|
|
262
|
-
`read`, `grep`, `find`, `ls`, `web_search`, and `web_fetch`. An agent
|
|
263
|
-
reader tools (or no `tools` declaration) gets the full reader set
|
|
264
|
-
`worker` cannot write under `readOnly`, and an external agent file that
|
|
282
|
+
`read`, `grep`, `find`, `ls`, `read_doc`, `web_search`, and `web_fetch`. An agent
|
|
283
|
+
with no reader tools (or no `tools` declaration) gets the full reader set
|
|
284
|
+
instead. So `worker` cannot write under `readOnly`, and an external agent file that
|
|
265
285
|
customizes a built-in cannot widen it. A short read-only note is appended to the
|
|
266
286
|
child's system prompt for behavior; the tool list is the enforcement.
|
|
267
287
|
|
|
@@ -314,6 +334,19 @@ with `{previous}` left in place, rather than the substituted prior output that
|
|
|
314
334
|
was actually sent to the child; that output is already shown as the previous
|
|
315
335
|
step's `Output`.
|
|
316
336
|
|
|
337
|
+
## Limitations
|
|
338
|
+
|
|
339
|
+
- Background runs are single-mode only. Parallel and chain calls wait as before.
|
|
340
|
+
- A background run is session-owned: `/tree` does not move it, and a process
|
|
341
|
+
restart cannot resume it. A start entry without a finish is shown as
|
|
342
|
+
`interrupted`.
|
|
343
|
+
- The collected output is capped at 50 KiB; the optional transcript keeps the
|
|
344
|
+
full stream. The `/agents` detail keeps only a bounded live preview, and a
|
|
345
|
+
finished run keeps its bounded output in the session record rather than the
|
|
346
|
+
child's full result.
|
|
347
|
+
- A finish notice appears in the TUI, but there is no OS notification; the run
|
|
348
|
+
remains listed in `/agents`.
|
|
349
|
+
|
|
317
350
|
## Non-goals
|
|
318
351
|
|
|
319
352
|
- **No ambient external agents.** User agents load only when an explicit
|
|
@@ -340,29 +373,19 @@ step's `Output`.
|
|
|
340
373
|
per process: a resumed session starts from zero, and a run that never produced
|
|
341
374
|
an assistant message publishes nothing.
|
|
342
375
|
|
|
343
|
-
## Limitations
|
|
344
|
-
|
|
345
|
-
- Background runs are single-mode only. Parallel and chain calls wait as before.
|
|
346
|
-
- A background run is session-owned: `/tree` does not move it, and a process
|
|
347
|
-
restart cannot resume it. A start entry without a finish is shown as
|
|
348
|
-
`interrupted`.
|
|
349
|
-
- The collected output is capped at 50 KiB; the optional transcript keeps the
|
|
350
|
-
full stream. The `/agents` detail keeps only a bounded live preview, and a
|
|
351
|
-
finished run keeps its bounded output in the session record rather than the
|
|
352
|
-
child's full result.
|
|
353
|
-
- A finish notice appears in the TUI, but there is no OS notification; the run
|
|
354
|
-
remains listed in `/agents`.
|
|
355
|
-
|
|
356
376
|
## Pi integration
|
|
357
377
|
|
|
358
378
|
| Aspect | Detail |
|
|
359
379
|
|---|---|
|
|
360
380
|
| Tool | `subagent` |
|
|
361
381
|
| Exposure | `direct`, active by default |
|
|
362
|
-
|
|
|
382
|
+
| Execution mode | none: sibling calls are independent, each spawning its own child, so they stay parallel |
|
|
383
|
+
| Annotations | `readOnlyHint: false`, `destructiveHint: true`, `openWorldHint: true` |
|
|
363
384
|
| Output schema | none; the full result lives in tool-result `details` |
|
|
364
385
|
| Execution | spawns `pi --mode json` subprocesses; external agents discovered from `<agent-dir>/agents` and `.pi/agents` |
|
|
386
|
+
| Command | `/agents` |
|
|
365
387
|
| State storage | tool-result `details`; optional per-run transcripts under `.pi/subagents/`; background start/finish entries (`subagent-background`) on the session branch |
|
|
388
|
+
| Event | `my-pi-agent/subagent-finished` (`SUBAGENT_FINISHED_CHANNEL` in `lib/events.ts`) once per finished child |
|
|
366
389
|
| Lifecycle | `session_start` loads the config, prunes expired transcripts, and attaches the footer chip; `session_shutdown` aborts every live background child and clears the chip |
|
|
367
390
|
|
|
368
391
|
## Design notes
|
|
@@ -442,6 +465,30 @@ step's `Output`.
|
|
|
442
465
|
| `background-tui.ts` | The `/agents` board: a `DockListScreen` with a live output pane and width-aware facts. |
|
|
443
466
|
| `commands.ts` | The `/agents` command. |
|
|
444
467
|
| `ui.ts` | The running-count footer chip and finish notices. |
|
|
468
|
+
| `accounting.ts` | The `subagent-finished` event payload built from a result (pure). |
|
|
445
469
|
| `format.ts` | Token/usage/total line and tool-call formatting (pure, money-free). |
|
|
446
470
|
| `render.ts` | `renderCall` / `renderResult` transcript rendering. |
|
|
447
471
|
| `types.ts` | Shared result and process types. |
|
|
472
|
+
|
|
473
|
+
## Testing
|
|
474
|
+
|
|
475
|
+
```bash
|
|
476
|
+
bun test extensions/subagent
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
Four co-located suites, declared in `test/suite-manifest.ts`:
|
|
480
|
+
|
|
481
|
+
- `agents.test.ts` — the built-in registry, markdown discovery and pool
|
|
482
|
+
resolution, config normalisation, the parameter schema, `resolveMode`,
|
|
483
|
+
subprocess argument construction, invocation, and the finished event.
|
|
484
|
+
- `run.test.ts` — the stream helpers, the concurrency limiter, `runSingleAgent`
|
|
485
|
+
against a scripted child, transcripts and pruning, the background runtime, and
|
|
486
|
+
the board component.
|
|
487
|
+
- `render.test.ts` — the call and result blocks in every mode, collapsed and
|
|
488
|
+
expanded, the failure text, and component reuse.
|
|
489
|
+
- `extension.test.ts` — registration, run settings, mode validation, all three
|
|
490
|
+
modes, the project-agent gate, accounting, `/agents`, the footer chip, and the
|
|
491
|
+
formatters.
|
|
492
|
+
|
|
493
|
+
`runSingleAgent` takes an injectable `SpawnFn`, so no suite launches `pi` or
|
|
494
|
+
spends tokens.
|