@fradser/pi-agent-teams 0.5.3 → 0.7.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/.memory/MEMORY.md +3 -1
- package/.memory/node-terminal-followups.md +3 -2
- package/.memory/team-centric-design.md +21 -0
- package/README.md +103 -63
- package/package.json +4 -4
- package/references/agent-roles.md +77 -0
- package/src/activity.ts +86 -0
- package/src/agents.ts +93 -24
- package/src/follow-up-queue.ts +94 -26
- package/src/guidance.ts +96 -61
- package/src/index.ts +114 -28
- package/src/spawner.ts +194 -350
- package/src/state.ts +252 -565
- package/src/statefile.ts +209 -46
- package/src/team-machine.ts +968 -0
- package/src/tools.ts +160 -175
- package/src/types.ts +153 -189
- package/src/ui.ts +459 -226
- package/src/worker.ts +174 -34
- package/src/worktree.ts +20 -8
- package/.memory/run-centric-design.md +0 -22
- package/agents/observer.md +0 -10
- package/agents/reviewer.md +0 -11
- package/agents/specialist.md +0 -10
- package/agents/worker.md +0 -11
- package/src/run-machine.ts +0 -606
- package/src/terminal.ts +0 -34
package/.memory/MEMORY.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
# Memory Index
|
|
2
2
|
|
|
3
|
-
- `openai_teammate_api_reference.md` — OpenAI external Teammate / Workspace Agents API reference (Workspace Agents API, Files API, file search, vector stores, Threads API). Reference only, not for Pi extension usage guidance.
|
|
3
|
+
- `openai_teammate_api_reference.md` — OpenAI external Teammate / Workspace Agents API reference (Workspace Agents API, Files API, file search, vector stores, Threads API). Reference only, not for Pi extension usage guidance.
|
|
4
|
+
|
|
5
|
+
- `team-centric-design.md` — Agent Teams current architecture: named resident RPC teammates, shared task board with atomic self-claim, direct P2P inboxes, verify-gated completion, harness-driven wake-ups. Supersedes the deleted run-centric DAG design.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: node-terminal-followups
|
|
3
|
-
description: Background Agent Teams runs notify the main session after each non-summary teammate reaches a terminal outcome,
|
|
3
|
+
description: Background Agent Teams runs notify the main session after each non-summary teammate reaches a terminal outcome, with completion shown separately from the agent report and no automatic run summary
|
|
4
4
|
type: project
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -13,5 +13,6 @@ The leader needs incremental results while independent teammates are still worki
|
|
|
13
13
|
- `run-machine.ts` sends one node follow-up for each non-summary node in a background run when it reports `completed`/`failed`, when its child process reaches a terminal outcome, or when pre-spawn setup fails (agent/state-file/worktree setup).
|
|
14
14
|
- `Node.nodeFollowUpSent` prevents duplicate follow-ups when a worker report is followed by process-close finalization; retry clears the marker.
|
|
15
15
|
- The node follow-up uses the worker's full terminal report/body and does not stop remaining teammates.
|
|
16
|
-
-
|
|
16
|
+
- Run settlement remains internal to leader state and foreground (`background=false`) runs continue to return their result inline, but background settlement does not enqueue a separate run-summary follow-up.
|
|
17
|
+
- The transport content contains only `<agent-message from="<agent>">` plus the worker's full body. The completion notice is rendered separately as `Teammate @<agent> finished.` and is never embedded in the report content.
|
|
17
18
|
- Worker turn budgets default to 100 assistant turns as a high safety cap; explicit lower budgets remain available for deliberately bounded tasks.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: teammate-resident-teams
|
|
3
|
+
description: Agent Teams (packages/agent-teams) — team-centric: named resident RPC teammates, shared task board with atomic self-claim, unified addressed messaging, verify-gated completion, harness-driven wake-ups
|
|
4
|
+
type: project
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
`packages/agent-teams` is a Claude-Code-style collaborative organization layer for Pi: the Pi session is the team leader; teammates are named resident child Pi processes in RPC mode; coordination runs through a shared local task board plus direct peer inbox messaging. It replaces the former run-centric DAG design (`teammate_run`/fanout/retry are deleted).
|
|
8
|
+
|
|
9
|
+
**Why:**
|
|
10
|
+
Exploratory / competitive-hypothesis work (multi-hypothesis debugging, three-way review with cross-challenge) cannot be represented cleanly by a static DAG because workers must exchange findings mid-task. One-step delegation avoids the old registry ceremony: `teammate_spawn(name, agent, prompt?)` carries the kickoff task and the board is optional. A later grilling decision identified tool count, conceptual overlap, and parameter breadth as separate problems: the surface was reduced from ten names to seven by making `send_message` the one addressed message primitive, registering `task_list` once for both sides, and moving model/worktree from spawn overrides into agent frontmatter.
|
|
11
|
+
|
|
12
|
+
**How to apply:**
|
|
13
|
+
1. **Seven tool names**: leader gets `teammate_spawn(name, agent, prompt?)`, `teammate_shutdown(name)`, `send_message(to, message, status?)`, `task_create`, and shared `task_list`; workers get the same `send_message`, shared `task_list`, `task_claim`, and `task_submit`. There is no `teammate_message`, cancel/retry/run/fanout, or polling tool.
|
|
14
|
+
2. **Unified messaging**: `send_message(to="leader", message, status?)` is a worker report written to the validated outbox; `send_message(to="<peer>", message)` writes the recipient inbox directly; leader-addressed teammate messages route through a running control stream or idle wake queue. `status` is invalid except for `to="leader"`. The first non-empty message line is the console title.
|
|
15
|
+
3. **Declarative role attributes**: agent frontmatter carries `model`, optional `verify`, and boolean `worktree`.Definitions live in one project directory with filename-based scopes: `<name>.local.md` is project-local (personal, never committed) and dedupes against its `<name>.md` counterpart (project scope, git-managed, team-shared); user-level sits in `~/.pi/agent/agents`; precedence is project-local > project > user > bundled and `resolveAgent` exposes scope + gitManaged for provenance. Spawn has no cwd/model/worktree override; create a role variant instead. `worktree: true` gives every instance of that role a dedicated Git worktree and diff capture at shutdown.
|
|
16
|
+
4. **Residents/wake-ups (constitution: detect and notify, never terminate)**: long-lived RPC children, 8-process session cap; wake-up sequences have NO turn-count or duration caps — no configuration may automatically terminate a working teammate. Turn counts and silence durations are telemetry only. The 500ms harness poll drains outboxes, applies intents, routes inboxes, wakes only for queued deliveries or a paced claimable-task notice, and runs the stall heartbeat: 30 minutes without any RPC output (`PI_TEAMMATE_STALL_NOTICE_MS`, 0 disables) sends the leader one actionable notice per silence episode — that notice is the LAST automatic action; continuing, steering, shutting down, or respawning a context-carrying successor (original kickoff + mailbox reports + board claims + liveText tail) belongs to the leader model alone. Any stream event or prompt delivery resets `lastOutputAt` and clears `stallNoticeSentAt`; steering a silent working teammate returns `stalledMs` so the send_message result warns delivery is uncertain.
|
|
17
|
+
5. **Board**: only leader writes `~/.pi/agent/tasks/<sessionKey>/board.json`; workers use exclusive-create claim/submission marker files. Task completion uses effective task verify or agent verify; failed verification returns stderr to the claimer. The board persists across restarts; runtime state does not.
|
|
18
|
+
6. **Reliability/UI**: per-spawn identity validation, event/message dedup, crash releases claimed tasks, role worktree diff at shutdown. Widget above the input box shows ONLY working/starting teammates as spinner rows with live activity and hides when nobody is working; idle/stopped teammates live only in the /teammate console roster; spawn prints a legacy-style `[agent] started · @name · task` transcript line; `/teammate` offers roster/board pages and message/task detail.
|
|
19
|
+
7. **Node-native TS**: relative imports use explicit `.ts` extensions (root tsconfig permits them), allowing node to strip-types modules in package tests.
|
|
20
|
+
|
|
21
|
+
**Related:** [[pi-cli-print-json-usage]] [[no-global-input-interception]] [[pi-package-conventions]]
|
package/README.md
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# Agent Teams Pi Package
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Claude-Code-style collaborative agent teams for Pi: named resident teammates, a shared local task board with self-claim, and direct peer-to-peer messaging.
|
|
4
4
|
|
|
5
5
|
**Display Name**: Agent Teams
|
|
6
6
|
|
|
7
7
|
## What This Package Does
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
The team leader (your Pi session) spawns named teammates as long-lived child Pi processes in RPC mode. Each teammate has an isolated context, a peer inbox, and access to a shared task board. The harness—not the leader model—polls for activity: it drains leader reports, routes peer mail, applies task claims and submissions, runs deterministic verify gates, and wakes idle teammates only when work or mail exists.
|
|
10
|
+
|
|
11
|
+
The message surface is deliberately singular: `send_message` is the only messaging primitive. Workers report with `to: "leader"`; teammates address peers by name; the leader addresses teammates by name. The routing implementation varies by destination (outbox, inbox, or control stream), but callers use one schema.
|
|
10
12
|
|
|
11
13
|
## Install
|
|
12
14
|
|
|
@@ -16,96 +18,134 @@ pi install /path/to/pi-packages/packages/agent-teams
|
|
|
16
18
|
|
|
17
19
|
Then run `/reload` in Pi.
|
|
18
20
|
|
|
19
|
-
## Agents
|
|
21
|
+
## Agents Are Declarative Files
|
|
20
22
|
|
|
21
|
-
Agent definitions are Markdown files with frontmatter; the body is the
|
|
23
|
+
Agent definitions are Markdown files with frontmatter; the body is the role prompt.
|
|
22
24
|
|
|
23
25
|
```markdown
|
|
24
26
|
---
|
|
25
|
-
name: security-auditor
|
|
26
|
-
description: Read-only security reviewer
|
|
27
|
+
name: isolated-security-auditor
|
|
28
|
+
description: Read-only security reviewer in an isolated worktree
|
|
27
29
|
tools: read,bash
|
|
28
|
-
model: provider/model
|
|
30
|
+
model: provider/model # optional
|
|
31
|
+
verify: npm test # optional role-default completion gate
|
|
32
|
+
worktree: true # optional role-default Git isolation
|
|
29
33
|
---
|
|
30
34
|
Review the assigned scope for exploitable security problems. Do not edit files.
|
|
31
35
|
```
|
|
32
36
|
|
|
33
|
-
Discovery precedence per name
|
|
37
|
+
Discovery precedence per name (later overrides earlier):
|
|
38
|
+
|
|
39
|
+
There are no built-in roles. When a needed role has no definition, the
|
|
40
|
+
leader creates one on demand: it derives the definition from the task using
|
|
41
|
+
the abstract role reference in `references/agent-roles.md` (definition
|
|
42
|
+
anatomy, archetype axes, invariants), writes the file, and then spawns.
|
|
43
|
+
|
|
44
|
+
| Scope | Location | Git semantics |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| user | `~/.pi/agent/agents/*.md` | system-level, never committed |
|
|
47
|
+
| project | `<cwd>/.pi/agents/<name>.md` | **git-managed** — commit for the team |
|
|
48
|
+
| project-local | `<cwd>/.pi/agents/<name>.local.md` | **local** — personal override, gitignore by convention |
|
|
34
49
|
|
|
35
|
-
|
|
50
|
+
A teammate definition is just a Markdown file you own. Shared roles live in `.pi/agents/<name>.md`; a personal tweak to that exact role is `<name>.local.md` in the SAME directory — same teammate name, local scope wins, and discovery deduplicates the pair into one entry (never two). `resolveAgent` reports each definition's `scope` and `gitManaged`; guidance lists both so the leader knows provenance.
|
|
51
|
+
|
|
52
|
+
## Build a Team
|
|
36
53
|
|
|
37
54
|
```text
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
{ id: "verify", agent: "reviewer", prompt: "Verify the fix", paths: ["packages/api"], access: "read", dependsOn: ["fix"] },
|
|
43
|
-
],
|
|
44
|
-
concurrency: 2,
|
|
45
|
-
worktree: false,
|
|
46
|
-
background: true
|
|
55
|
+
teammate_spawn({
|
|
56
|
+
name: "security",
|
|
57
|
+
agent: "reviewer",
|
|
58
|
+
prompt: "Review the auth middleware"
|
|
47
59
|
})
|
|
60
|
+
|
|
61
|
+
teammate_spawn({ name: "backend", agent: "worker" })
|
|
48
62
|
```
|
|
49
63
|
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
- `
|
|
56
|
-
- A session-wide cap of 8 worker processes applies in addition to each run's `concurrency` limit.
|
|
57
|
-
- Multi-node runs append a `__summary` node by default (`summarize=false` to skip). Single-task runs stay compact unless `summarize=true`.
|
|
58
|
-
- Completing a node hands its result to pending dependents through the spawned worker prompt. Workers have no peer-message or inbound-reply path.
|
|
64
|
+
- Spawn has exactly three parameters: `name`, `agent`, and optional kickoff `prompt`.
|
|
65
|
+
- Without a kickoff prompt, the teammate idles until messaged or until claimable work appears.
|
|
66
|
+
- Teammates stay alive between tasks and consume no model tokens while idle.
|
|
67
|
+
- A session-wide cap of 8 living teammates applies.
|
|
68
|
+
- An agent declaring `worktree: true` gets its own Git worktree; its diff is captured at shutdown.
|
|
69
|
+
- `teammate_shutdown({ name })` stops one teammate and releases its claimed task back to the board.
|
|
59
70
|
|
|
60
|
-
##
|
|
71
|
+
## Coordinate Through One Message Primitive and the Board
|
|
61
72
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
73
|
+
```text
|
|
74
|
+
send_message({
|
|
75
|
+
to: "security",
|
|
76
|
+
message: "Please challenge the backend finding against the current middleware."
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
task_create({
|
|
80
|
+
subject: "Fix auth middleware findings",
|
|
81
|
+
description: "Address the confirmed security findings in packages/api/src/auth.ts",
|
|
82
|
+
dependsOn: ["t_1"],
|
|
83
|
+
verify: "npm test"
|
|
84
|
+
})
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`send_message` has one schema everywhere:
|
|
88
|
+
|
|
89
|
+
```text
|
|
90
|
+
send_message({ to, message, status? })
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
- Worker report: `to: "leader"`; use optional `status: "completed" | "failed"` for a terminal report.
|
|
94
|
+
- Peer mail: `to: "<teammate-name>"`; `status` is invalid.
|
|
95
|
+
- Leader steering or direct assignment: `to: "<teammate-name>"`; a working recipient gets a control-stream steer, an idle recipient wakes with the message.
|
|
96
|
+
- The first non-empty line of `message` becomes the console title.
|
|
97
|
+
- Peer traffic never enters the leader model context; inspect it in `/agent-teams` instead.
|
|
98
|
+
|
|
99
|
+
Only the leader creates tasks. Idle teammates self-claim pending tasks whose dependencies are met. A task-level `verify` command overrides the claiming agent's frontmatter `verify`; zero exit completes the task, while failure returns stderr to the claimer for fix-and-resubmit.
|
|
100
|
+
|
|
101
|
+
## Tools
|
|
68
102
|
|
|
69
|
-
|
|
103
|
+
| Tool / command | Side | Description |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| `teammate_spawn(name, agent, prompt?)` | Leader | Start one named resident teammate; model/worktree come from agent frontmatter |
|
|
106
|
+
| `teammate_shutdown(name)` | Leader | Stop one teammate and release its claimed work |
|
|
107
|
+
| `send_message(to, message, status?)` | Both | The only messaging primitive; `to: "leader"` is reserved for worker reports |
|
|
108
|
+
| `task_create(subject, description?, dependsOn?, verify?)` | Leader | Add a shared board task |
|
|
109
|
+
| `task_list()` | Both | One shared board-view definition; leader view also includes the roster |
|
|
110
|
+
| `task_claim(taskId?)` | Worker | Atomically self-claim a pending, unblocked task |
|
|
111
|
+
| `task_submit(taskId, status, result?)` | Worker | Submit a claimed task outcome; completion passes through verify |
|
|
112
|
+
| `/agent-teams` | User | Management console: session teammates, persistent agent roles, board, details, shutdown |
|
|
70
113
|
|
|
71
|
-
|
|
114
|
+
That is **7 unique tool names**. There are no `teammate_run`, `teammate_fanout`, `teammate_cancel`, `teammate_retry`, or `teammate_message` tools.
|
|
72
115
|
|
|
73
|
-
## Reliability
|
|
116
|
+
## Reliability Protocol
|
|
74
117
|
|
|
75
|
-
- **Per-spawn identity validation**: every
|
|
76
|
-
- **
|
|
77
|
-
- **One
|
|
78
|
-
- **
|
|
79
|
-
-
|
|
80
|
-
- **Failure semantics**:
|
|
118
|
+
- **Per-spawn identity validation**: every leader report must match the teammate's current spawn id; stale callbacks and events cannot affect a replacement with the same name.
|
|
119
|
+
- **Sent means written**: peer `send_message` succeeds only after the recipient inbox write succeeds. The harness owns delivery into a recipient turn.
|
|
120
|
+
- **One writer per state file**: only the leader process writes runtime and board snapshots (atomic tmp+rename). Workers append leader reports to their outbox, peer mail to recipient inboxes, and task intent via exclusive-create marker files.
|
|
121
|
+
- **Completion is gated, not self-reported**: a task completes only after its effective verify gate passes when one exists; no gate means the submission itself completes it.
|
|
122
|
+
- **No caps, heartbeat only**: teammates run without turn-count or duration ceilings. The harness heartbeat tracks silence per working teammate and — after 30 minutes without any RPC output (`PI_TEAMMATE_STALL_NOTICE_MS`, 0 disables) — sends the leader one actionable notice per silence episode. The notice is the last automatic action: continuing, steering, shutting down, or respawning a context-carrying successor belongs to the leader alone. Any output or prompt delivery re-arms it, and steering a silent teammate warns that delivery is uncertain.
|
|
123
|
+
- **Failure semantics**: an unexpected crash marks the teammate stopped, reports a diagnostic, and releases its claimed tasks.
|
|
81
124
|
|
|
82
|
-
## State and
|
|
125
|
+
## State and Sessions
|
|
83
126
|
|
|
84
|
-
|
|
127
|
+
Runtime state lives in `~/.pi/agent/teammate/<sessionKey>/` and dies with the session (`state.json`, report outboxes, peer inboxes, roster). The task board lives in `~/.pi/agent/tasks/<sessionKey>/board.json` and persists for later inspection; nothing auto-deletes it. Claims held by dead teammates return to pending on reload.
|
|
85
128
|
|
|
86
129
|
## Structure
|
|
87
130
|
|
|
88
131
|
```
|
|
89
132
|
agent-teams/
|
|
90
|
-
├── index.ts
|
|
91
|
-
├──
|
|
92
|
-
├── agents/ — bundled agent definitions (worker/reviewer/specialist/observer)
|
|
133
|
+
├── index.ts — package-root extension entry point
|
|
134
|
+
├── references/ — role templates consulted when generating new agents
|
|
93
135
|
├── src/
|
|
94
|
-
│ ├── index.ts
|
|
95
|
-
│ ├── tools.ts
|
|
96
|
-
│ ├──
|
|
97
|
-
│ ├──
|
|
98
|
-
│ ├──
|
|
99
|
-
│ ├──
|
|
100
|
-
│ ├── agents.ts
|
|
101
|
-
│ ├──
|
|
102
|
-
│ ├──
|
|
103
|
-
│ ├──
|
|
104
|
-
│
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
├── features/ — BDD contract
|
|
108
|
-
└── tests/ — package E2E tests
|
|
136
|
+
│ ├── index.ts — composition root and session lifecycle
|
|
137
|
+
│ ├── tools.ts — leader tools and /agent-teams command
|
|
138
|
+
│ ├── worker.ts — worker tools plus the shared task_list registration
|
|
139
|
+
│ ├── team-machine.ts — resident lifecycle, mail routing, intents, verify, wake-ups
|
|
140
|
+
│ ├── state.ts — roster, board, leader inbox state machine
|
|
141
|
+
│ ├── spawner.ts — resident RPC process spawner (uncapped sequences)
|
|
142
|
+
│ ├── agents.ts — agent discovery and frontmatter parsing
|
|
143
|
+
│ ├── statefile.ts — runtime/board/mail IO and marker-file intents
|
|
144
|
+
│ ├── ui.ts — passive widget and /agent-teams management console
|
|
145
|
+
│ ├── guidance.ts — static leader and worker protocols
|
|
146
|
+
│ └── follow-up-queue.ts — serialized automatic report delivery
|
|
147
|
+
├── features/ — BDD contract
|
|
148
|
+
└── tests/ — package tests
|
|
109
149
|
```
|
|
110
150
|
|
|
111
151
|
## License
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fradser/pi-agent-teams",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Multi-agent team system for Pi —
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "Multi-agent team system for Pi — resident teammates, shared task board, and peer-to-peer messaging",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
7
7
|
"teammates",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"files": [
|
|
25
25
|
"index.ts",
|
|
26
26
|
"src",
|
|
27
|
-
"
|
|
27
|
+
"references",
|
|
28
28
|
".memory",
|
|
29
29
|
"README.md"
|
|
30
30
|
],
|
|
@@ -39,6 +39,6 @@
|
|
|
39
39
|
]
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@fradser/pi-kit": "0.
|
|
42
|
+
"@fradser/pi-kit": "0.3.0"
|
|
43
43
|
}
|
|
44
44
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Agent Role Reference
|
|
2
|
+
|
|
3
|
+
Abstract reference for generating new agent definitions on demand. This file
|
|
4
|
+
is NOT discoverable and CANNOT be spawned directly. Derive a concrete
|
|
5
|
+
definition from the anatomy, archetype axes, and invariants below, tailor it
|
|
6
|
+
to the task, and write it to `<cwd>/.pi/agents/<name>.md` (git-managed
|
|
7
|
+
project role) or `<name>.local.md` (personal override).
|
|
8
|
+
|
|
9
|
+
## Definition anatomy
|
|
10
|
+
|
|
11
|
+
Frontmatter:
|
|
12
|
+
|
|
13
|
+
- `name` — unique id among discovered roles.
|
|
14
|
+
- `description` — the routing contract: when the leader should choose this
|
|
15
|
+
role, phrased as "use when ...". This is the most load-bearing field.
|
|
16
|
+
- `tools` — the minimal set; follows the mutability axis (see below).
|
|
17
|
+
- optional `model`, `verify`, `worktree`.
|
|
18
|
+
|
|
19
|
+
The Markdown body is the role prompt, built from five parts:
|
|
20
|
+
|
|
21
|
+
1. Identity — "You are a `<role>` agent."
|
|
22
|
+
2. Method — the procedure to execute, the criteria to judge against, or the
|
|
23
|
+
observation targets, depending on the judgment axis.
|
|
24
|
+
3. Boundaries — what must never be touched or changed.
|
|
25
|
+
4. Evidence — what every claim or finding must carry.
|
|
26
|
+
5. Terminal report — delivery via `send_message(to="leader", message=...)`;
|
|
27
|
+
the last message to the leader MUST
|
|
28
|
+
carry status="completed" (or status="failed"); work without a terminal
|
|
29
|
+
status counts as unfinished work.
|
|
30
|
+
|
|
31
|
+
## Archetype axes
|
|
32
|
+
|
|
33
|
+
Pick a position on two axes before writing anything:
|
|
34
|
+
|
|
35
|
+
| Axis | Values |
|
|
36
|
+
|---|---|
|
|
37
|
+
| Mutability | mutating (`edit`,`write` allowed) · read-only |
|
|
38
|
+
| Judgment | execute a given procedure · evaluate against criteria · apply domain expertise · observe and report |
|
|
39
|
+
|
|
40
|
+
Read-only plus each judgment value yields the four classical shapes:
|
|
41
|
+
|
|
42
|
+
| Shape | Typical tools | Body emphasis |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| executor | `read,bash,edit,write` | implement end to end, then self-verify by running tests or commands; report changes made, verification performed, remaining risks |
|
|
45
|
+
| reviewer | `read,bash` | only confirmed findings, with severity, evidence, exact paths, and a minimal fix recommendation; when nothing is found, say so and list the checks performed |
|
|
46
|
+
| specialist | `read,bash` | deep domain analysis; state assumptions, the evidence used, and the bounds of expertise for this task |
|
|
47
|
+
| observer | `read` | snapshots or readings; summarize observed state and anomalies with timestamps and evidence; change nothing |
|
|
48
|
+
|
|
49
|
+
These shapes are starting points, not a fixed menu: a generated role may sit
|
|
50
|
+
anywhere on the axes (for example a mutating specialist with a verify gate),
|
|
51
|
+
as long as every invariant below holds.
|
|
52
|
+
|
|
53
|
+
## Invariants
|
|
54
|
+
|
|
55
|
+
Non-negotiable in every generated definition:
|
|
56
|
+
|
|
57
|
+
- Tool sets match the mutability axis: read-only roles never receive
|
|
58
|
+
`edit` or `write`.
|
|
59
|
+
- Every finding or claim carries evidence: exact paths, command output,
|
|
60
|
+
timestamps — never bare assertions.
|
|
61
|
+
- Scope is bounded: mutating roles state "do not touch files outside the
|
|
62
|
+
assigned scope"; read-only roles state the equivalent prohibition.
|
|
63
|
+
- Terminal-status discipline applies to all shapes alike.
|
|
64
|
+
|
|
65
|
+
## Skeleton
|
|
66
|
+
|
|
67
|
+
```markdown
|
|
68
|
+
---
|
|
69
|
+
name: <role-name>
|
|
70
|
+
description: <one-line capability>; use when <routing condition>
|
|
71
|
+
tools: <minimal set from the mutability axis>
|
|
72
|
+
---
|
|
73
|
+
You are a <role-name> agent. <Method: the procedure to follow, the criteria
|
|
74
|
+
to evaluate against, or the targets to observe>. <Boundary statement>.
|
|
75
|
+
Report <expected artifacts> with <evidence requirements> via
|
|
76
|
+
send_message(to="leader", message=...) with status="completed".
|
|
77
|
+
```
|
package/src/activity.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Markdown, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
2
|
+
import type { MarkdownTheme } from "@earendil-works/pi-tui";
|
|
3
|
+
import { buildMarkdownThemeCallbacks, createPiThemeStyle } from "@fradser/pi-kit";
|
|
4
|
+
import type { Teammate } from "./types.ts";
|
|
5
|
+
|
|
6
|
+
const ACTIVITY_THEME: MarkdownTheme = {
|
|
7
|
+
...buildMarkdownThemeCallbacks(createPiThemeStyle({
|
|
8
|
+
fg: (_color, text) => text,
|
|
9
|
+
})),
|
|
10
|
+
hr: () => "---",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/** Render streamed activity as one compact Markdown line for the widget. */
|
|
14
|
+
export function renderActivityMarkdown(text: string, theme: MarkdownTheme = ACTIVITY_THEME): string {
|
|
15
|
+
const markdown = new Markdown(text, 0, 0, theme);
|
|
16
|
+
return markdown.render(Math.max(1, visibleWidth(text) + 1)).join(" ").replace(/\s+/g, " ").trim();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function extractLatestLine(text: string | undefined): string | undefined {
|
|
20
|
+
if (!text) return undefined;
|
|
21
|
+
const lines = text.split("\n");
|
|
22
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
23
|
+
const trimmed = lines[i].replace(/\s+/g, " ").trim();
|
|
24
|
+
if (trimmed.length > 0 && !trimmed.startsWith("... [truncated") && !trimmed.startsWith("…[truncated")) {
|
|
25
|
+
return trimmed;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Extract the current teammate activity without imposing a display width. */
|
|
32
|
+
export function runningTeammateActivity(teammate: Teammate): string {
|
|
33
|
+
const tool = teammate.activeTool?.replace(/\s+/g, " ").trim();
|
|
34
|
+
if (tool) return tool;
|
|
35
|
+
|
|
36
|
+
return extractLatestLine(teammate.liveThinking)
|
|
37
|
+
?? extractLatestLine(teammate.liveText)
|
|
38
|
+
?? "Working...";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface TeammateRowWidths {
|
|
42
|
+
lineWidth: number;
|
|
43
|
+
nameWidth: number;
|
|
44
|
+
activityWidth: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Reserve enough width for the live activity before truncating the identity. */
|
|
48
|
+
export function teammateRowWidths(spinner: string, agent: string, width: number): TeammateRowWidths {
|
|
49
|
+
const lineWidth = Math.max(10, width - 1);
|
|
50
|
+
const fixedWidth = visibleWidth(` ${spinner} · `);
|
|
51
|
+
const availableWidth = Math.max(2, lineWidth - fixedWidth);
|
|
52
|
+
const nameWidth = Math.max(1, Math.min(visibleWidth(agent), Math.floor(availableWidth * 0.4)));
|
|
53
|
+
return {
|
|
54
|
+
lineWidth,
|
|
55
|
+
nameWidth,
|
|
56
|
+
activityWidth: Math.max(1, availableWidth - nameWidth),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Fit one widget row to its terminal width while keeping it on one line. */
|
|
61
|
+
export function fitTeammateRow(
|
|
62
|
+
spinner: string,
|
|
63
|
+
agent: string,
|
|
64
|
+
activity: string,
|
|
65
|
+
width: number,
|
|
66
|
+
formatActivity: (text: string) => string = (text) => text,
|
|
67
|
+
markdownTheme: MarkdownTheme = ACTIVITY_THEME,
|
|
68
|
+
): string {
|
|
69
|
+
const sizes = teammateRowWidths(spinner, agent, width);
|
|
70
|
+
const nameText = truncateToWidth(agent, sizes.nameWidth);
|
|
71
|
+
const activityText = formatActivity(truncateToWidth(renderActivityMarkdown(activity, markdownTheme), sizes.activityWidth));
|
|
72
|
+
const line = ` ${spinner} ${nameText} · ${activityText}`;
|
|
73
|
+
return truncateToWidth(line, sizes.lineWidth);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Fit a console status label to the requested activity width. */
|
|
77
|
+
export function formatTeammateLabel(
|
|
78
|
+
spinner: string,
|
|
79
|
+
activity: string,
|
|
80
|
+
maxActivityWidth?: number,
|
|
81
|
+
markdownTheme: MarkdownTheme = ACTIVITY_THEME,
|
|
82
|
+
): string {
|
|
83
|
+
if (maxActivityWidth === undefined) return `${spinner} ${renderActivityMarkdown(activity, markdownTheme)}`;
|
|
84
|
+
if (maxActivityWidth <= 0) return spinner;
|
|
85
|
+
return `${spinner} ${truncateToWidth(renderActivityMarkdown(activity, markdownTheme), maxActivityWidth)}`;
|
|
86
|
+
}
|