@llblab/pi-actors 0.31.0 → 0.32.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/AGENTS.md +3 -0
- package/BACKLOG.md +14 -27
- package/CHANGELOG.md +8 -0
- package/README.md +16 -11
- package/dist/lib/observability.js +17 -6
- package/dist/lib/tools.js +145 -25
- package/dist/skills/actors/SKILL.md +14 -19
- package/dist/skills/swarm/SKILL.md +3 -3
- package/docs/actor-messages.md +1 -1
- package/docs/tool-registry.md +1 -1
- package/lib/observability.ts +18 -6
- package/lib/tools.ts +165 -37
- package/package.json +1 -1
- package/skills/actors/SKILL.md +14 -19
- package/skills/swarm/SKILL.md +3 -3
package/AGENTS.md
CHANGED
|
@@ -61,6 +61,7 @@ Pi host
|
|
|
61
61
|
- Prefer explicit operator action over silent user-config rewrites.
|
|
62
62
|
- Keep published documentation portable: use `~`, `<repo>`, or relative paths instead of machine-local absolute paths.
|
|
63
63
|
- Preserve runtime output discipline because tool output flows directly into agent context.
|
|
64
|
+
- Optimize every actor-facing surface for signal over volume: prefer compact state-backed hints and fewer concepts over broad explanatory prose or speculative guidance.
|
|
64
65
|
- Keep the project lens local-first and cybernetic: agents wrap durable local capabilities as actors, then use semantic tools and messages instead of repeatedly reconstructing shell commands.
|
|
65
66
|
- Design recipes as agent-callable tools: make prompts, scopes, paths, models, and policy knobs public args/defaults when the caller should decide them at invocation time.
|
|
66
67
|
- Decompose oversized bullets into sublists or hierarchy; long flat list items are a context-smell.
|
|
@@ -80,6 +81,7 @@ Pi host
|
|
|
80
81
|
## Public Actor Model
|
|
81
82
|
|
|
82
83
|
- Preserve the public verbs: `spawn`, `message`, `inspect`.
|
|
84
|
+
- Keep the model-facing concept ladder minimal: core is run actors, typed messages, intentional inspection, artifacts, and recipe/tool memory; group messaging, roster, branches, sessions, and diagnostics are advanced surfaces.
|
|
83
85
|
- Prefer one typed actor-message envelope for upward, downward, lateral, parent/branch, and branch/parent messages.
|
|
84
86
|
- Prefer actor addresses and inspect views over exposing FIFO, outbox, or status mechanics as public concepts.
|
|
85
87
|
- Keep route and semantic type separate: delivery behavior comes from `to`, while `type` describes intent.
|
|
@@ -116,6 +118,7 @@ Pi host
|
|
|
116
118
|
## State, IO, And Safety
|
|
117
119
|
|
|
118
120
|
- Tool stdout and temp state must stay bounded and local.
|
|
121
|
+
- Feedback hints must be evidence-backed, bounded, and action-shaped; prefer `next_actions` pointing to existing verbs over prose, and avoid hints when no concrete next step is justified.
|
|
119
122
|
- Keep tail truncation, full-output temp files, failure formatting, and centralized limits intact.
|
|
120
123
|
- Published docs must not include machine-local absolute paths.
|
|
121
124
|
- Any view scanning run directories must apply coordinator/session ownership filters before exposing summaries or previews.
|
package/BACKLOG.md
CHANGED
|
@@ -47,25 +47,12 @@ No open hotfix items.
|
|
|
47
47
|
- File length alone is not a domain-split trigger: ~1000-line cohesive domain files are acceptable when ownership is clear.
|
|
48
48
|
- Consider splitting only when a file crosses roughly 2000 lines, mixes real ownership zones, or hides a clearer domain boundary.
|
|
49
49
|
- Prefer semantic compression before file splitting: fewer public nouns, consistent outcomes, compact diagnostics, and domain-owned constants/helpers.
|
|
50
|
+
- Preserve signal/noise balance: feedback should be state-backed, compact, and action-shaped; do not add advisory prose just because a surface exists.
|
|
50
51
|
|
|
51
52
|
## Minor Backlog
|
|
52
53
|
|
|
53
54
|
The backlog is intentionally pruned to the 20% of work most likely to deliver 80% of value for `pi-actors` as a local actor kernel. Bias toward consolidation, smaller public surface area, and reliability over new feature breadth.
|
|
54
55
|
|
|
55
|
-
### M-14 Session Mismatch Follow-through
|
|
56
|
-
|
|
57
|
-
- Priority: Medium.
|
|
58
|
-
- Status: Planned.
|
|
59
|
-
- Goal: Extend 0.27 structured session diagnostics consistently across room, branch, run, coordinator, and session workflows.
|
|
60
|
-
- Why now: M-12 established the shape; dogfood should now make every ownership denial equally actionable without relaxing ownership gates.
|
|
61
|
-
- Direction:
|
|
62
|
-
- Audit all session mismatch errors for consistent `reason`, owner/current session fields, and inspect-session hints.
|
|
63
|
-
- Keep read/write ownership policy unchanged.
|
|
64
|
-
- Update docs with session mismatch examples and recovery inspection paths.
|
|
65
|
-
- Acceptance:
|
|
66
|
-
- Room, branch, run, coordinator, and session denials share the same compact/verbose shape.
|
|
67
|
-
- Tests cover representative inspect and message paths.
|
|
68
|
-
|
|
69
56
|
### M-15 Worker Stale-Claim Dogfood
|
|
70
57
|
|
|
71
58
|
- Priority: Medium.
|
|
@@ -111,23 +98,23 @@ The backlog is intentionally pruned to the 20% of work most likely to deliver 80
|
|
|
111
98
|
- Room messages distinguish timeline append success from forwarded branch-targeted copies.
|
|
112
99
|
- Tests cover at least run, branch, room, coordinator, and ownership-denied outcomes.
|
|
113
100
|
|
|
114
|
-
### M-18
|
|
101
|
+
### M-18 Draft Recipe Promotion UX
|
|
115
102
|
|
|
116
103
|
- Priority: High.
|
|
117
104
|
- Status: Planned.
|
|
118
|
-
- Goal: Make successful ad hoc actor patterns easy to promote manually from
|
|
119
|
-
- Why now:
|
|
105
|
+
- Goal: Make successful ad hoc actor patterns easy to promote manually from draft memory into active user recipe memory.
|
|
106
|
+
- Why now: Draft recipes under `~/.pi/agent/recipes/candidates` are replayable but intentionally not active tools; the directory name is retained for compatibility, and the two-stage memory model now needs an explicit operator-gated promotion path.
|
|
120
107
|
- Direction:
|
|
121
|
-
- List
|
|
122
|
-
- Promote a selected
|
|
108
|
+
- List draft recipes with source run, timestamp, fingerprint, description/template preview, and validation status.
|
|
109
|
+
- Promote a selected draft to `~/.pi/agent/recipes/<name>.json` only through an explicit action or explicit tool argument.
|
|
123
110
|
- Run recipe validation/doctor before writing and expose collision/shadowing diagnostics.
|
|
124
|
-
- Preserve
|
|
111
|
+
- Preserve draft files unless deletion is explicitly requested.
|
|
125
112
|
- Prefer extending existing registry/tool surfaces over adding a new public noun.
|
|
126
113
|
- Acceptance:
|
|
127
|
-
-
|
|
114
|
+
- Draft recipes remain non-tools until promotion.
|
|
128
115
|
- Promotion writes atomically and never auto-promotes.
|
|
129
|
-
- Tests cover valid promotion, invalid
|
|
130
|
-
- Docs explain
|
|
116
|
+
- Tests cover valid promotion, invalid draft, name collision, and packaged-recipe shadowing.
|
|
117
|
+
- Docs explain draft memory vs active tool memory in one compact section.
|
|
131
118
|
|
|
132
119
|
### M-24 Registry Path Naming Cleanup
|
|
133
120
|
|
|
@@ -166,10 +153,10 @@ The backlog is intentionally pruned to the 20% of work most likely to deliver 80
|
|
|
166
153
|
- Priority: Medium.
|
|
167
154
|
- Status: Planned.
|
|
168
155
|
- Goal: Add one compact operator triage view that answers what needs attention right now without performing repairs.
|
|
169
|
-
- Why now: Runtime status, recipe doctor,
|
|
156
|
+
- Why now: Runtime status, recipe doctor, drafts, stale claims, session mismatches, failed runs, and other-session counts are currently separate bounded surfaces.
|
|
170
157
|
- Direction:
|
|
171
158
|
- Add `inspect target=tool:pi-actors view=triage` or an equivalent existing inspect surface.
|
|
172
|
-
- Summarize runtime version/mode, active runs, other-session runs, invalid or blocking recipes, high-risk recipes,
|
|
159
|
+
- Summarize runtime version/mode, active runs, other-session runs, invalid or blocking recipes, high-risk recipes, draft recipes, stale worker claims, recent failed runs, attention messages, and suggested next inspect actions.
|
|
173
160
|
- Keep every warning tied to a next inspect/action hint.
|
|
174
161
|
- Do not auto-repair, auto-prune, relax ownership, or hide detailed source-of-truth views.
|
|
175
162
|
- Acceptance:
|
|
@@ -226,7 +213,7 @@ These are valid ideas but not current focus. Reintroduce only with concrete evid
|
|
|
226
213
|
## Suggested Milestone Order
|
|
227
214
|
|
|
228
215
|
```text
|
|
229
|
-
Next milestone: M-
|
|
230
|
-
Then: M-
|
|
216
|
+
Next milestone: M-15 Worker Stale-Claim Dogfood.
|
|
217
|
+
Then: M-17 Message Delivery Outcome Contract → M-18 Draft Recipe Promotion UX.
|
|
231
218
|
Small cleanup lane: M-23 Tool Boundary Type Tightening → M-24 Registry Path Naming Cleanup.
|
|
232
219
|
```
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.32.0: Actor Surface Minimization
|
|
6
|
+
|
|
7
|
+
- `[Context]` Added durable signal/noise guidance for actor-facing surfaces: keep the model-facing concept ladder minimal, make feedback hints state-backed and action-shaped, and avoid speculative advisory prose.
|
|
8
|
+
- `[Backlog]` Added critical concept-surface compression and actor feedback-loop strengthening tracks to guide the next minimization-focused development cycle.
|
|
9
|
+
- `[Concepts]` Compressed model-facing language by presenting captured inline-spawn recipes as drafts, treating `room:<run>` as advanced group messaging plus roster, demoting coordinator/session/debug views from golden-path guidance, and keeping compatibility names/paths as storage details rather than core onboarding nouns.
|
|
10
|
+
- `[Feedback]` Added bounded next-action hints to recipe registry/doctor inspection, artifact inspection, delivery-fallback message results, and terminal run follow-ups so state-backed surfaces point back to `inspect`, `message`, `spawn`, or draft promotion without polling or auto-repair.
|
|
11
|
+
- `[Sessions]` Normalized session-directed message ownership failures onto the same structured `reason=session_mismatch`, owner/current session, and inspect-session hint shape used by run, branch, room, and coordinator ownership denials.
|
|
12
|
+
|
|
5
13
|
## 0.31.0: Agent Adoption Ergonomics
|
|
6
14
|
|
|
7
15
|
- `[Adoption]` Added a compact actor-mode trigger rule to the injected prompt, actors skill, README, and async-run docs so models prefer `spawn → message → inspect` for long-lived, stateful, follow-up, artifact, service, fanout, and resumable work while keeping short foreground checks as ordinary tools.
|
package/README.md
CHANGED
|
@@ -66,16 +66,21 @@ The npm package is dist-first for JavaScript-only runtimes: default Pi metadata
|
|
|
66
66
|
|
|
67
67
|
## Address Surface
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
Core actor addresses stay small:
|
|
70
70
|
|
|
71
71
|
```text
|
|
72
|
-
run:<id>
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
run:<id> one detached actor run
|
|
73
|
+
tool:<name> executable registered tool actor
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Advanced coordination/debug addresses are available when a recipe or workflow needs them:
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
branch:<run>/<branch> branch-local worker endpoint
|
|
80
|
+
room:<run> group-message timeline plus roster for one run
|
|
81
|
+
coordinator compatibility alias for the current session coordination path
|
|
76
82
|
session: current session actor surface
|
|
77
|
-
session:all cross-session inventory surface
|
|
78
|
-
tool:<name> executable registered tool
|
|
83
|
+
session:all cross-session inventory surface for diagnostics
|
|
79
84
|
```
|
|
80
85
|
|
|
81
86
|
Actor messages use one envelope shape:
|
|
@@ -140,9 +145,9 @@ message to=run:docs_review type=control.continue body=continue
|
|
|
140
145
|
message to=run:docs_review type=control.kill body=stop
|
|
141
146
|
```
|
|
142
147
|
|
|
143
|
-
##
|
|
148
|
+
## Group Messaging And Roster
|
|
144
149
|
|
|
145
|
-
Every spawned run can have
|
|
150
|
+
Every spawned run can have advanced group messaging at `room:<run>`. Treat this as a run-local timeline plus roster for coordinated actors, not as a core chat/broker concept.
|
|
146
151
|
|
|
147
152
|
Actors can join, post, leave, and discover peers:
|
|
148
153
|
|
|
@@ -155,7 +160,7 @@ message \
|
|
|
155
160
|
body='{"role":"reviewer","caps":["security-review"],"claim":"Review auth boundary risks"}'
|
|
156
161
|
```
|
|
157
162
|
|
|
158
|
-
Inspect
|
|
163
|
+
Inspect group messages and roster intentionally:
|
|
159
164
|
|
|
160
165
|
```text
|
|
161
166
|
inspect target=room:review view=status
|
|
@@ -165,7 +170,7 @@ inspect target=room:review view=contacts
|
|
|
165
170
|
inspect target=room:review view=messages
|
|
166
171
|
```
|
|
167
172
|
|
|
168
|
-
|
|
173
|
+
Group posts require a same-run sender, so unrelated runs do not pollute the roster. Direct messages and group messages use the same envelope; only the address changes. Direct `branch:<run>/<branch>` messages are private: they are forwarded through the parent run mailbox and recorded in the recipient branch inbox for worker protocols that consume queued branch work. For selected-recipient multicast, send to `room:<run>` with `metadata.recipients` set to same-run `branch:<run>/<branch>` addresses; this keeps one visible transcript entry while forwarding branch-targeted copies.
|
|
169
174
|
|
|
170
175
|
## Actor Inspector
|
|
171
176
|
|
|
@@ -721,19 +721,30 @@ function formatRecipePersistenceSuggestion(transition) {
|
|
|
721
721
|
}
|
|
722
722
|
return `\nAgent note: this actor was spawned directly and completed successfully. If this pattern fits this machine's recurring workflow, ask the operator whether to save it as a durable recipe/tool under ~/.pi/agent/recipes with register_tool. Do not auto-save without confirmation.`;
|
|
723
723
|
}
|
|
724
|
+
function formatTransitionNextActions(transition) {
|
|
725
|
+
const actions = [
|
|
726
|
+
`inspect target=run:${transition.run} view=status`,
|
|
727
|
+
transition.to === "done" && Object.keys(transition.artifacts ?? {}).length > 0
|
|
728
|
+
? `inspect target=run:${transition.run} view=artifacts`
|
|
729
|
+
: `inspect target=run:${transition.run} view=tail`,
|
|
730
|
+
`inspect target=run:${transition.run} view=messages`,
|
|
731
|
+
].filter(Boolean);
|
|
732
|
+
return `\nNext actions: ${actions.join(" | ")}`;
|
|
733
|
+
}
|
|
724
734
|
export function formatRunTransitionMessage(transition) {
|
|
725
735
|
const artifacts = formatNamedArtifacts(transition.artifacts);
|
|
726
736
|
const runFiles = formatRunFileList(getRunArtifacts(transition));
|
|
727
737
|
const persistenceSuggestion = formatRecipePersistenceSuggestion(transition);
|
|
738
|
+
const nextActions = formatTransitionNextActions(transition);
|
|
728
739
|
if (transition.to === "done")
|
|
729
|
-
return `Run ${transition.run} completed successfully.${artifacts}${runFiles}
|
|
740
|
+
return `Run ${transition.run} completed successfully.${artifacts}${runFiles}${nextActions}${persistenceSuggestion}`;
|
|
730
741
|
if (transition.to === "failed")
|
|
731
|
-
return `Run ${transition.run} failed.${artifacts}${runFiles}
|
|
742
|
+
return `Run ${transition.run} failed.${artifacts}${runFiles}${nextActions}`;
|
|
732
743
|
if (transition.to === "cancelled")
|
|
733
|
-
return `Run ${transition.run} was cancelled
|
|
744
|
+
return `Run ${transition.run} was cancelled.${nextActions}`;
|
|
734
745
|
if (transition.to === "killed")
|
|
735
|
-
return `Run ${transition.run} was force-killed
|
|
746
|
+
return `Run ${transition.run} was force-killed.${nextActions}`;
|
|
736
747
|
if (transition.to === "exited")
|
|
737
|
-
return `Run ${transition.run} exited before writing a result
|
|
738
|
-
return `Run ${transition.run} finished with status ${transition.to}
|
|
748
|
+
return `Run ${transition.run} exited before writing a result.${nextActions}`;
|
|
749
|
+
return `Run ${transition.run} finished with status ${transition.to}.${nextActions}`;
|
|
739
750
|
}
|
package/dist/lib/tools.js
CHANGED
|
@@ -168,8 +168,9 @@ function compactAsyncRunStatus(value) {
|
|
|
168
168
|
tokens.push(`code=${String(result.code)}`);
|
|
169
169
|
if (result.killed === true)
|
|
170
170
|
tokens.push("killed=true");
|
|
171
|
-
|
|
172
|
-
|
|
171
|
+
const draftRecipe = status.draft_recipe ?? status.candidate_recipe;
|
|
172
|
+
if (draftRecipe)
|
|
173
|
+
tokens.push(`draft_recipe=${String(draftRecipe)}`);
|
|
173
174
|
const nextActions = actorRunNextActions(run);
|
|
174
175
|
if (nextActions.length > 0)
|
|
175
176
|
tokens.push(`next=${nextActions.map((action) => action.replaceAll(/\s+/g, "_")).join("|")}`);
|
|
@@ -357,6 +358,15 @@ function compactArtifactPath(value) {
|
|
|
357
358
|
const record = asRecord(value);
|
|
358
359
|
return String(record.path ?? "<missing>");
|
|
359
360
|
}
|
|
361
|
+
function artifactNextActions(run, artifacts) {
|
|
362
|
+
const id = String(run ?? "").trim();
|
|
363
|
+
if (!id || Object.keys(artifacts).length === 0)
|
|
364
|
+
return [];
|
|
365
|
+
return [
|
|
366
|
+
`inspect target=run:${id} view=artifacts verbose=true`,
|
|
367
|
+
`inspect target=run:${id} view=messages`,
|
|
368
|
+
];
|
|
369
|
+
}
|
|
360
370
|
function compactActorFiles(status) {
|
|
361
371
|
const run = String(status.run ?? "<unknown>");
|
|
362
372
|
const artifacts = asRecord(status.artifacts);
|
|
@@ -375,7 +385,11 @@ function compactActorFiles(status) {
|
|
|
375
385
|
.map(([key, value]) => `${key}:${compactArtifactPath(value)}`)
|
|
376
386
|
.join(",")}`
|
|
377
387
|
: "";
|
|
378
|
-
|
|
388
|
+
const nextActions = artifactNextActions(run, artifacts);
|
|
389
|
+
const nextText = nextActions.length
|
|
390
|
+
? ` next=${nextActions.map((action) => action.replaceAll(/\s+/g, "_")).join("|")}`
|
|
391
|
+
: "";
|
|
392
|
+
return `\nrun=${run}${artifactText}${files.length ? ` files=${files.join(",")}` : ""}${nextText}`;
|
|
379
393
|
}
|
|
380
394
|
function summarizeOtherSessions(currentSession, allRuns) {
|
|
381
395
|
const otherRuns = allRuns.filter((run) => run.ownerId && run.ownerId !== currentSession);
|
|
@@ -495,8 +509,42 @@ function compactRecipeDoctor(summary) {
|
|
|
495
509
|
: "";
|
|
496
510
|
lines.push(`${String(item.severity ?? "info")} kind=${String(item.kind ?? "inspect")} id=${String(item.id ?? "root")}${blocked} action=${action ?? "inspect"}`);
|
|
497
511
|
}
|
|
512
|
+
const nextActions = Array.isArray(summary.next_actions)
|
|
513
|
+
? summary.next_actions
|
|
514
|
+
: [];
|
|
515
|
+
if (nextActions.length > 0)
|
|
516
|
+
lines[0] = `${lines[0]}${compactNextActions(nextActions)}`;
|
|
498
517
|
return `\n${lines.join("\n")}`;
|
|
499
518
|
}
|
|
519
|
+
function recipeRegistryNextActions(summary, view) {
|
|
520
|
+
const actions = [];
|
|
521
|
+
const drafts = Array.isArray(summary.drafts)
|
|
522
|
+
? summary.drafts
|
|
523
|
+
: [];
|
|
524
|
+
const invalid = Array.isArray(summary.invalid) ? summary.invalid.length : 0;
|
|
525
|
+
const diagnostics = Array.isArray(summary.diagnostics)
|
|
526
|
+
? summary.diagnostics.length
|
|
527
|
+
: 0;
|
|
528
|
+
const topAction = asRecord(summary.top_action);
|
|
529
|
+
if (view !== "doctor" && (invalid > 0 || diagnostics > 0)) {
|
|
530
|
+
actions.push("inspect target=recipes view=doctor");
|
|
531
|
+
}
|
|
532
|
+
if (view === "doctor" && typeof topAction.action === "string") {
|
|
533
|
+
actions.push(String(topAction.action));
|
|
534
|
+
}
|
|
535
|
+
if (drafts.length > 0) {
|
|
536
|
+
actions.push("inspect target=recipes view=summary verbose=true");
|
|
537
|
+
const firstPath = typeof drafts[0]?.path === "string" ? drafts[0].path : undefined;
|
|
538
|
+
if (firstPath)
|
|
539
|
+
actions.push(`spawn file=${firstPath}`);
|
|
540
|
+
}
|
|
541
|
+
return [...new Set(actions)].slice(0, 4);
|
|
542
|
+
}
|
|
543
|
+
function compactNextActions(actions) {
|
|
544
|
+
return actions.length
|
|
545
|
+
? ` next=${actions.map((action) => action.replaceAll(/\s+/g, "_")).join("|")}`
|
|
546
|
+
: "";
|
|
547
|
+
}
|
|
500
548
|
function compactRecipeRegistry(summary) {
|
|
501
549
|
const active = Array.isArray(summary.active) ? summary.active.length : 0;
|
|
502
550
|
const shadowed = Array.isArray(summary.shadowed)
|
|
@@ -509,13 +557,41 @@ function compactRecipeRegistry(summary) {
|
|
|
509
557
|
const diagnostics = Array.isArray(summary.diagnostics)
|
|
510
558
|
? summary.diagnostics.length
|
|
511
559
|
: 0;
|
|
512
|
-
const
|
|
513
|
-
? summary.
|
|
514
|
-
:
|
|
560
|
+
const drafts = Array.isArray(summary.drafts)
|
|
561
|
+
? summary.drafts.length
|
|
562
|
+
: Array.isArray(summary.candidates)
|
|
563
|
+
? summary.candidates.length
|
|
564
|
+
: 0;
|
|
515
565
|
const recommendations = Array.isArray(summary.recommendations)
|
|
516
566
|
? summary.recommendations.length
|
|
517
567
|
: 0;
|
|
518
|
-
|
|
568
|
+
const nextActions = Array.isArray(summary.next_actions)
|
|
569
|
+
? summary.next_actions
|
|
570
|
+
: [];
|
|
571
|
+
return `\nrecipes active=${active} drafts=${drafts} shadowed=${shadowed} invalid=${invalid} disabled=${disabled} recommendations=${recommendations} diagnostics=${diagnostics}${compactNextActions(nextActions)}`;
|
|
572
|
+
}
|
|
573
|
+
function actorMessageNextActions(message, result) {
|
|
574
|
+
const actions = [];
|
|
575
|
+
const address = ActorMessages.parseActorAddress(message.to);
|
|
576
|
+
if (result.delivery_error || result.sent === false) {
|
|
577
|
+
if (address.kind === "run" && address.value) {
|
|
578
|
+
actions.push(`inspect target=run:${address.value} view=status`);
|
|
579
|
+
actions.push(`inspect target=run:${address.value} view=mailbox`);
|
|
580
|
+
}
|
|
581
|
+
else if (address.kind === "branch" && address.value) {
|
|
582
|
+
actions.push(`inspect target=branch:${address.value}/${address.branch ?? "main"} view=mailbox`);
|
|
583
|
+
actions.push(`inspect target=run:${address.value} view=status`);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
if (result.queued === true) {
|
|
587
|
+
if (address.kind === "branch" && address.value) {
|
|
588
|
+
actions.push(`inspect target=branch:${address.value}/${address.branch ?? "main"} view=mailbox`);
|
|
589
|
+
}
|
|
590
|
+
else if (address.kind === "run" && address.value) {
|
|
591
|
+
actions.push(`inspect target=run:${address.value} view=mailbox`);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return [...new Set(actions)].slice(0, 3);
|
|
519
595
|
}
|
|
520
596
|
function compactActorMessageResult(message, result) {
|
|
521
597
|
const tokens = [
|
|
@@ -548,6 +624,11 @@ function compactActorMessageResult(message, result) {
|
|
|
548
624
|
if (result.delivery_error) {
|
|
549
625
|
tokens.push(`delivery_error=${compactPreview(result.delivery_error, 96)}`);
|
|
550
626
|
}
|
|
627
|
+
const nextActions = Array.isArray(result.next_actions)
|
|
628
|
+
? result.next_actions
|
|
629
|
+
: actorMessageNextActions(message, result);
|
|
630
|
+
if (nextActions.length > 0)
|
|
631
|
+
tokens.push(`next=${nextActions.map((action) => action.replaceAll(/\s+/g, "_")).join("|")}`);
|
|
551
632
|
return `\n${tokens.join(" ")}`;
|
|
552
633
|
}
|
|
553
634
|
function maybeJsonText(value, verbose, compact) {
|
|
@@ -649,7 +730,7 @@ function writeSpawnCandidateRecipe(input, meta) {
|
|
|
649
730
|
const defaults = candidateRecipeDefaults(meta.values);
|
|
650
731
|
const recipe = {
|
|
651
732
|
async: true,
|
|
652
|
-
description: `
|
|
733
|
+
description: `Draft recipe captured from spawn run ${String(meta.run)}`,
|
|
653
734
|
...(meta.artifacts ? { artifacts: meta.artifacts } : {}),
|
|
654
735
|
...(defaults ? { defaults } : {}),
|
|
655
736
|
template: input.template,
|
|
@@ -787,7 +868,9 @@ export function createSpawnToolDefinition() {
|
|
|
787
868
|
const nextActions = actorRunNextActions(meta.run);
|
|
788
869
|
const details = {
|
|
789
870
|
...meta,
|
|
790
|
-
...(candidateRecipe
|
|
871
|
+
...(candidateRecipe
|
|
872
|
+
? { candidate_recipe: candidateRecipe, draft_recipe: candidateRecipe }
|
|
873
|
+
: {}),
|
|
791
874
|
next_actions: nextActions,
|
|
792
875
|
};
|
|
793
876
|
ActorRooms.ensureDefaultRoom(meta.state_dir, String(meta.run));
|
|
@@ -814,15 +897,29 @@ function requireContextSessionId(ctx, actor) {
|
|
|
814
897
|
}
|
|
815
898
|
return sessionId;
|
|
816
899
|
}
|
|
900
|
+
function sessionMismatchError(input) {
|
|
901
|
+
const ownerSession = input.expectedSession ?? "none";
|
|
902
|
+
const currentSession = input.currentSession ?? "none";
|
|
903
|
+
const actor = input.run ? `run:${input.run}` : (input.target ?? "session");
|
|
904
|
+
const hintTarget = input.expectedSession
|
|
905
|
+
? `session:${input.expectedSession}`
|
|
906
|
+
: "session:all";
|
|
907
|
+
return Object.assign(new Error(`${actor} reason=session_mismatch owner_session=${ownerSession} current_session=${currentSession} hint=inspect_session:${input.expectedSession ?? "all"}`), {
|
|
908
|
+
current_session: input.currentSession,
|
|
909
|
+
hint: `inspect target=${hintTarget} view=status`,
|
|
910
|
+
owner_session: input.expectedSession,
|
|
911
|
+
reason: "session_mismatch",
|
|
912
|
+
run: input.run,
|
|
913
|
+
target: input.target,
|
|
914
|
+
});
|
|
915
|
+
}
|
|
817
916
|
function assertRunAccessibleToContext(runId, ctx) {
|
|
818
917
|
const status = AsyncRuns.getRunStatus(runId);
|
|
819
918
|
const sessionId = getContextSessionId(ctx);
|
|
820
919
|
if (sessionId && status.ownerId && status.ownerId !== sessionId) {
|
|
821
|
-
throw
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
owner_session: status.ownerId,
|
|
825
|
-
reason: "session_mismatch",
|
|
920
|
+
throw sessionMismatchError({
|
|
921
|
+
currentSession: sessionId,
|
|
922
|
+
expectedSession: String(status.ownerId),
|
|
826
923
|
run: runId,
|
|
827
924
|
});
|
|
828
925
|
}
|
|
@@ -835,13 +932,13 @@ export function createInspectToolDefinition(deps = {}) {
|
|
|
835
932
|
return {
|
|
836
933
|
name: "inspect",
|
|
837
934
|
label: "Inspect",
|
|
838
|
-
description: "Intentionally inspect
|
|
935
|
+
description: "Intentionally inspect actors at decision points, after follow-ups, or during diagnosis instead of polling. Core targets are run:<id> and tool:<name>; advanced targets include branch:<run>/<branch>, room:<run>, coordinator, session:<id>, and session:all.",
|
|
839
936
|
parameters: objectSchema({
|
|
840
937
|
lines: stringSchema("Line count for tail/messages views. Default 40."),
|
|
841
938
|
status: stringSchema("Optional session run filter: all, running, active, terminal, done, failed, cancelled, killed, or exited."),
|
|
842
|
-
target: stringSchema("Actor address to inspect, e.g. run:<id>, room:<run>, coordinator, session:<id>, session:all
|
|
939
|
+
target: stringSchema("Actor address to inspect, e.g. run:<id> or tool:<name>; advanced: branch:<run>/<branch>, room:<run>, coordinator, session:<id>, session:all."),
|
|
843
940
|
verbose: booleanSchema("Return full JSON instead of compact text where available."),
|
|
844
|
-
view: stringSchema("Inspection view: status, tail, messages, artifacts, files, mailbox
|
|
941
|
+
view: stringSchema("Inspection view. Core run views: status, tail, messages, artifacts, files, mailbox. Advanced views include communication, roster, and contacts."),
|
|
845
942
|
}, ["target", "view"]),
|
|
846
943
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
847
944
|
const input = asRecord(params);
|
|
@@ -863,10 +960,15 @@ export function createInspectToolDefinition(deps = {}) {
|
|
|
863
960
|
{ root: deps.packagedRecipeRoot ?? Paths.getPackagedRecipeRoot() },
|
|
864
961
|
]);
|
|
865
962
|
const recipeRoot = deps.recipeRoot ?? Paths.getRecipeRoot();
|
|
866
|
-
const
|
|
963
|
+
const summaryBase = {
|
|
867
964
|
...RecipeDiscovery.summarizeDiscovery(discovered),
|
|
965
|
+
drafts: RecipeDiscovery.listCandidateRecipes(join(recipeRoot, "candidates")),
|
|
868
966
|
candidates: RecipeDiscovery.listCandidateRecipes(join(recipeRoot, "candidates")),
|
|
869
967
|
};
|
|
968
|
+
const summary = {
|
|
969
|
+
...summaryBase,
|
|
970
|
+
next_actions: recipeRegistryNextActions(summaryBase, view),
|
|
971
|
+
};
|
|
870
972
|
return {
|
|
871
973
|
content: [
|
|
872
974
|
{
|
|
@@ -1084,7 +1186,11 @@ export function createInspectToolDefinition(deps = {}) {
|
|
|
1084
1186
|
const status = assertRunAccessibleToContext(runId, ctx);
|
|
1085
1187
|
const artifactManifest = AsyncRuns.resolveArtifactManifest(status.artifacts);
|
|
1086
1188
|
const details = artifactManifest
|
|
1087
|
-
? {
|
|
1189
|
+
? {
|
|
1190
|
+
...status,
|
|
1191
|
+
artifact_manifest: artifactManifest,
|
|
1192
|
+
next_actions: artifactNextActions(status.run ?? runId, asRecord(status.artifacts)),
|
|
1193
|
+
}
|
|
1088
1194
|
: status;
|
|
1089
1195
|
return {
|
|
1090
1196
|
content: [
|
|
@@ -1139,7 +1245,7 @@ export function createActorMessageToolDefinition(deps = {}) {
|
|
|
1139
1245
|
return {
|
|
1140
1246
|
name: "message",
|
|
1141
1247
|
label: "Message",
|
|
1142
|
-
description: "Send one typed addressed message to steer an existing actor instead of restarting it.
|
|
1248
|
+
description: "Send one typed addressed message to steer an existing actor instead of restarting it. Core routes are run:<id> and tool:<name>; advanced routes include branch:<run>/<branch>, room:<run> group timelines, coordinator, and session:<id>.",
|
|
1143
1249
|
parameters: objectSchema({
|
|
1144
1250
|
body: unionSchema([
|
|
1145
1251
|
stringSchema("Message body. For run:<id>, this is the run-local command line."),
|
|
@@ -1151,7 +1257,7 @@ export function createActorMessageToolDefinition(deps = {}) {
|
|
|
1151
1257
|
metadata: looseObjectSchema("Optional structured metadata for routing or domain hints."),
|
|
1152
1258
|
reply_to: stringSchema("Optional message id this message replies to."),
|
|
1153
1259
|
summary: stringSchema("Optional short human-facing summary."),
|
|
1154
|
-
to: stringSchema("Destination actor address, e.g. run:<id
|
|
1260
|
+
to: stringSchema("Destination actor address, e.g. run:<id> or tool:<name>; advanced: branch:<run>/<branch>, room:<run>, coordinator, session:<id>."),
|
|
1155
1261
|
type: stringSchema("Semantic message type, e.g. control.approve or checkpoint.needs_scope."),
|
|
1156
1262
|
verbose: booleanSchema("Return full JSON instead of compact text."),
|
|
1157
1263
|
}, ["to", "type"]),
|
|
@@ -1266,10 +1372,20 @@ export function createActorMessageToolDefinition(deps = {}) {
|
|
|
1266
1372
|
const senderStatus = assertRunAccessibleToContext(sender.value, ctx);
|
|
1267
1373
|
if (address.kind === "session") {
|
|
1268
1374
|
if (!senderStatus.ownerId) {
|
|
1269
|
-
throw
|
|
1375
|
+
throw sessionMismatchError({
|
|
1376
|
+
currentSession: undefined,
|
|
1377
|
+
expectedSession: address.value,
|
|
1378
|
+
run: sender.value,
|
|
1379
|
+
target: `session:${address.value}`,
|
|
1380
|
+
});
|
|
1270
1381
|
}
|
|
1271
1382
|
if (senderStatus.ownerId !== address.value) {
|
|
1272
|
-
throw
|
|
1383
|
+
throw sessionMismatchError({
|
|
1384
|
+
currentSession: String(senderStatus.ownerId),
|
|
1385
|
+
expectedSession: address.value,
|
|
1386
|
+
run: sender.value,
|
|
1387
|
+
target: `session:${address.value}`,
|
|
1388
|
+
});
|
|
1273
1389
|
}
|
|
1274
1390
|
}
|
|
1275
1391
|
result = AsyncRuns.appendRunOutboxEvent(sender.value, {
|
|
@@ -1291,14 +1407,18 @@ export function createActorMessageToolDefinition(deps = {}) {
|
|
|
1291
1407
|
else {
|
|
1292
1408
|
throw new Error(`message currently supports run:<id>, branch:<run>/<branch>, room:<run>, tool:<name>, coordinator, and session:<id> destinations; unsupported destination: ${message.to}`);
|
|
1293
1409
|
}
|
|
1410
|
+
const nextActions = actorMessageNextActions(message, result);
|
|
1411
|
+
const resultWithNext = nextActions.length
|
|
1412
|
+
? { ...result, next_actions: nextActions }
|
|
1413
|
+
: result;
|
|
1294
1414
|
return {
|
|
1295
1415
|
content: [
|
|
1296
1416
|
{
|
|
1297
1417
|
type: "text",
|
|
1298
|
-
text: maybeJsonText({ message, result }, input.verbose === true, compactActorMessageResult(message,
|
|
1418
|
+
text: maybeJsonText({ message, result: resultWithNext }, input.verbose === true, compactActorMessageResult(message, resultWithNext)),
|
|
1299
1419
|
},
|
|
1300
1420
|
],
|
|
1301
|
-
details: { message, result },
|
|
1421
|
+
details: { message, result: resultWithNext },
|
|
1302
1422
|
};
|
|
1303
1423
|
},
|
|
1304
1424
|
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: actors
|
|
3
3
|
description: Required practical guide for non-trivial pi-actors use. Read before using or changing spawn, message, inspect, actor runs, tools, recipes, command templates, async lifecycle, mailboxes, artifacts, and local orchestration mechanics.
|
|
4
4
|
metadata:
|
|
5
|
-
version: 0.
|
|
5
|
+
version: 0.32.0
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Actors (pi-actors)
|
|
@@ -39,12 +39,11 @@ Trusted local capability
|
|
|
39
39
|
|
|
40
40
|
- **Command template**: portable execution graph. String leaf, sequence array, or object node with controls.
|
|
41
41
|
- **Recipe**: saved JSON definition wrapping a template with args, defaults, imports, mailbox, artifacts, metadata, and optional `async: true`.
|
|
42
|
-
- **Run actor**: one detached execution instance addressable as `run:<id>` with status, logs, messages, mailbox metadata, files,
|
|
43
|
-
- **Room actor**: shared timeline + roster endpoint addressable as `room:<run>`; every spawned run gets `room:<run>`.
|
|
42
|
+
- **Run actor**: one detached execution instance addressable as `run:<id>` with status, logs, messages, mailbox metadata, files, and artifacts.
|
|
44
43
|
- **Tool actor**: registered persistent capability addressable as `tool:<name>` and callable through the generated tool or `message`. `tool:pi-actors` is reserved for runtime/status inspection.
|
|
45
|
-
- **Coordinator/session**: the current pi session endpoint that receives bounded actor follow-ups.
|
|
46
|
-
- **Mailbox**: public interaction contract: message types the actor accepts/emits.
|
|
47
44
|
- **Artifact**: named durable output path declared by a recipe/run.
|
|
45
|
+
- **Mailbox**: interaction contract: message types the actor accepts/emits.
|
|
46
|
+
- **Advanced group/coordination surfaces**: `branch:<run>/<branch>`, `room:<run>`, `coordinator`, `session:<id>`, and communication snapshots exist for multi-actor workflows and diagnostics; do not make them the default mental model.
|
|
48
47
|
|
|
49
48
|
## Three Verbs
|
|
50
49
|
|
|
@@ -86,8 +85,9 @@ Envelope fields:
|
|
|
86
85
|
|
|
87
86
|
- Required: `to`, `type`.
|
|
88
87
|
- Useful: `summary`, `body`, `from`, `reply_to`, `correlation_id`, `metadata`.
|
|
89
|
-
-
|
|
90
|
-
-
|
|
88
|
+
- Core addresses: `run:<id>`, `tool:<name>`.
|
|
89
|
+
- Advanced addresses: `branch:<run>/<branch>`, `room:<run>` for group timeline/roster, `coordinator`, `session:<id>`.
|
|
90
|
+
- Group posts to `room:<run>` require `from` from the same run (`run:<run>` or `branch:<run>/<branch>`).
|
|
91
91
|
- Runtime termination message: `control.kill` is the only documented actor message that kills a run. `control.stop` and `control.cancel` are actor-local mailbox vocabulary only when a recipe declares and handles them. Terminal retention messages: `control.archive`, `control.prune`.
|
|
92
92
|
- Long-lived child processes should remain in the run-owned process group unless the recipe implements an explicit daemon termination bridge; do not leave unowned detached services behind `control.kill`.
|
|
93
93
|
|
|
@@ -99,12 +99,7 @@ Check `inspect view=mailbox` before domain-specific messages.
|
|
|
99
99
|
{ "target": "run:repo-health", "view": "status" }
|
|
100
100
|
{ "target": "run:repo-health", "view": "tail", "lines": "80" }
|
|
101
101
|
{ "target": "run:repo-health", "view": "messages" }
|
|
102
|
-
{ "target": "run:repo-health", "view": "communication" }
|
|
103
102
|
{ "target": "run:repo-health", "view": "artifacts" }
|
|
104
|
-
{ "target": "room:repo-health", "view": "status" }
|
|
105
|
-
{ "target": "room:repo-health", "view": "roster" }
|
|
106
|
-
{ "target": "room:repo-health", "view": "contacts" }
|
|
107
|
-
{ "target": "room:repo-health", "view": "previews" }
|
|
108
103
|
{ "target": "tool:pi-actors", "view": "status" }
|
|
109
104
|
{ "target": "tool:music_player", "view": "status" }
|
|
110
105
|
{ "target": "recipes", "view": "status" }
|
|
@@ -116,10 +111,10 @@ Views:
|
|
|
116
111
|
- `status`: lifecycle, pid, values, progress, result, compact summary.
|
|
117
112
|
- `tail`: recent stdout/stderr/log tail.
|
|
118
113
|
- `messages`: actor messages emitted by the run, or room timeline entries for `room:*`.
|
|
119
|
-
- `communication`: run/branch
|
|
120
|
-
- `roster`: room member list with address, role, parent, caps, claim, status, and last seen.
|
|
121
|
-
- `contacts`: roster-derived direct-message targets without full roster metadata.
|
|
122
|
-
- `previews`: TUI-ready bounded
|
|
114
|
+
- Advanced `communication`: run/branch group-coordination snapshot with self/root/default-room/member/contact hints.
|
|
115
|
+
- Advanced `roster`: room member list with address, role, parent, caps, claim, status, and last seen.
|
|
116
|
+
- Advanced `contacts`: roster-derived direct-message targets without full roster metadata.
|
|
117
|
+
- Advanced `previews`: TUI-ready bounded group-message previews with timestamp/from/to/type/summary/body_preview.
|
|
123
118
|
- `mailbox`: declared accepts/emits contract for runs; queued direct branch inbox messages for `branch:<run>/<branch>` with `id`, status, route/type, and queue/handling timestamps.
|
|
124
119
|
- `files`: run state directory file list.
|
|
125
120
|
- `artifacts`: declared artifact paths/status.
|
|
@@ -218,13 +213,13 @@ Only matching filename ids compete. Higher priority shadows lower priority; with
|
|
|
218
213
|
Muscle-memory lens: pi-actors has two durable executable-memory layers.
|
|
219
214
|
|
|
220
215
|
1. `~/.pi/agent/recipes/*.json` and `*.md` are the agent's active capability memory. Every recipe in that directory becomes an easy-to-call tool automatically and survives into later sessions. Descriptions matter here because they become the tool's operator-facing title/context.
|
|
221
|
-
2. `~/.pi/agent/recipes/candidates/*.json` is
|
|
216
|
+
2. `~/.pi/agent/recipes/candidates/*.json` is draft memory captured from successful inline `spawn template=...` runs. The directory name is retained for compatibility; treat these as drafts, not active tools. Drafts do not enter the injected tool surface. They remain reusable by explicit path, e.g. `spawn file="~/.pi/agent/recipes/candidates/<name>.json"`, and can be promoted by moving or copying one level up into `~/.pi/agent/recipes`.
|
|
222
217
|
|
|
223
|
-
Agents grow active memory by calling `register_tool` or by deliberate recipe-file edits. They grow
|
|
218
|
+
Agents grow active memory by calling `register_tool` or by deliberate recipe-file edits. They grow draft memory by trying ad hoc actors successfully. Treat both as executable habits: drafts are the workbench/proving ground; root recipes are promoted muscle memory.
|
|
224
219
|
|
|
225
220
|
Usage lens: user recipes may carry extension-maintained launch metadata such as `usage.calls` and `usage.last_called`. The extension increments the counter when it starts that concrete recipe; agents should not hand-edit counters as part of normal recipe maintenance. Treat usage as evidence for usefulness analysis: heavily used recipes are good candidates for promotion, documentation, or stronger tests; unused recipes are cleanup candidates. Do not use failure counts as a primary usefulness signal because failures may reflect bad caller judgment rather than bad recipes. Do not delete or demote solely from counters without operator approval.
|
|
226
221
|
|
|
227
|
-
Promotion lens: successful transient/ad hoc actor runs are evidence, not commands. Inline spawns leave
|
|
222
|
+
Promotion lens: successful transient/ad hoc actor runs are evidence, not commands. Inline spawns leave draft recipes as replayable evidence, not active tools. If a draft is repeatable, parameterized, safe enough, and likely useful later, the agent may promote it by moving/copying it into `~/.pi/agent/recipes` or by calling `register_tool` with a concise name, typed args/defaults, and a reviewed template or recipe path. Do not auto-register every success; do not promote temp paths, secrets, one-off prompts, or project-private assumptions without normalization and approval.
|
|
228
223
|
|
|
229
224
|
Cleanup rule: periodically inspect `~/.pi/agent/recipes` as the live muscle-memory set. For each stale, duplicate, too-specific, or low-value recipe, choose one explicit action: keep as a tool, move it out of the agent recipe root to retain recipe-only memory, merge into a better recipe, or delete/archive the file. Prefer moving over deletion when the recipe may still be useful as a component. Never silently remove tools during unrelated work.
|
|
230
225
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: swarm
|
|
3
3
|
description: Subagent orchestration with scoped locks and quorum consensus. Use for multi-model review, parallel scoped work, delegated audit, and coordinated subagent execution.
|
|
4
4
|
metadata:
|
|
5
|
-
version: 0.
|
|
5
|
+
version: 0.32.0
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Swarm
|
|
@@ -30,8 +30,8 @@ Maintain this skill as a living orchestration standard. When real swarm work exp
|
|
|
30
30
|
- `Async Run`: A local lifecycle envelope around a command-template swarm composer or utility. It owns state, logs, status, cancellation, and observability, not swarm semantics.
|
|
31
31
|
- `Lens`: A deliberately narrow cognitive role assigned to one subagent, such as security, tests, architecture, economics, or operator UX.
|
|
32
32
|
- `Task Card`: A bounded implementation assignment with goal, allowed files, avoided files, expected output, and validation gates.
|
|
33
|
-
- `Component Capability`: An abstract adapter operation such as launcher, reviewer, verifier, merger, quorum, checkpoint, follow-up, judge, or normalizer. Swarm may target these capabilities, but local adapters bind them to concrete tools, recipes,
|
|
34
|
-
- `
|
|
33
|
+
- `Component Capability`: An abstract adapter operation such as launcher, reviewer, verifier, merger, quorum, checkpoint, follow-up, judge, or normalizer. Swarm may target these capabilities, but local adapters bind them to concrete tools, recipes, draft recipes, command templates, async runs, or services.
|
|
34
|
+
- `Draft Recipe`: A reusable but non-registered recipe captured from a successful inline actor spawn. It can be replayed by explicit file path and later promoted into the active tool recipe root after enough dogfood. Its compatibility storage path may still include `recipes/candidates`.
|
|
35
35
|
- `Coordinator Checkpoint`: A deliberate subagent pause where the subagent preserves its working context, sends a bounded question or status to the orchestrator, receives a coordinator reply, and continues in the same subagent context.
|
|
36
36
|
- `Evidence Checkpoint`: A deliberate stop where a subagent records sources, assumptions, confidence, contradictions, or blocking evidence gaps before synthesis.
|
|
37
37
|
- `Integrator`: The human or agent that merges isolated branches/worktrees into the shared target and owns conflict resolution.
|
package/docs/actor-messages.md
CHANGED
|
@@ -199,7 +199,7 @@ Recipes can declare their conversational surface:
|
|
|
199
199
|
}
|
|
200
200
|
```
|
|
201
201
|
|
|
202
|
-
The implementation supports `status`, `tail`, `messages`, `artifacts`, `files`, `mailbox`, and `communication` for `run:<id>` actors, `status`, `messages`, `previews`, `roster`, and `contacts` for `room:<run>` actors, `status`/`runs` for `coordinator`, `session:<id>`, and `session:all` actors with optional status filtering, and `status`/`schema` for registered `tool:<name>` actors. Run mailbox inspection shows recipe-declared mailbox metadata plus recent durable run inbox entries; branch mailbox inspection shows branch-local queued/claimed/handled records. Room `status` returns compact message/roster counts plus `last_message_at`, `last_message_from`, `last_message_type`, and `last_message_summary` when available, without parsing the full timeline into actor envelopes. Use `messages` for actor-envelope inspection. `inspect target=coordinator` requires a current coordinator session; use `session:<id>` or `session:all` when the session is intentionally explicit. Direct `run:<id>` and `room:<run>` inspection respects coordinator-session ownership when the current session is known. `inspect` is for decision points and diagnosis only; examples must not teach sleep-then-inspect polling.
|
|
202
|
+
The implementation supports `status`, `tail`, `messages`, `artifacts`, `files`, `mailbox`, and `communication` for `run:<id>` actors, `status`, `messages`, `previews`, `roster`, and `contacts` for `room:<run>` actors, `status`/`runs` for `coordinator`, `session:<id>`, and `session:all` actors with optional status filtering, and `status`/`schema` for registered `tool:<name>` actors. Run mailbox inspection shows recipe-declared mailbox metadata plus recent durable run inbox entries; branch mailbox inspection shows branch-local queued/claimed/handled records. Room `status` returns compact message/roster counts plus `last_message_at`, `last_message_from`, `last_message_type`, and `last_message_summary` when available, without parsing the full timeline into actor envelopes. Use `messages` for actor-envelope inspection. `inspect target=coordinator` requires a current coordinator session; use `session:<id>` or `session:all` when the session is intentionally explicit. Direct `run:<id>` and `room:<run>` inspection respects coordinator-session ownership when the current session is known. Ownership denials use `reason=session_mismatch owner_session=<id> current_session=<id> hint=inspect_session:<id>`; recover by inspecting the hinted `session:<id>` instead of forcing cross-session control. `inspect` is for decision points and diagnosis only; examples must not teach sleep-then-inspect polling.
|
|
203
203
|
|
|
204
204
|
## Runtime Direction
|
|
205
205
|
|
package/docs/tool-registry.md
CHANGED
|
@@ -10,7 +10,7 @@ The registry source is location-discovered recipes, not a live tool-only JSON fi
|
|
|
10
10
|
|
|
11
11
|
- `~/.pi/agent/recipes/*.json` and `*.md` are the highest-priority user recipe root and the operator-managed tool set.
|
|
12
12
|
- Recipes in that root are tools by location.
|
|
13
|
-
- `~/.pi/agent/recipes/candidates/*.json`
|
|
13
|
+
- `~/.pi/agent/recipes/candidates/*.json` stores captured inline-spawn draft recipes, not registered tools. The directory name is retained for compatibility; model-facing output calls them drafts. Promote one by moving or copying it up one level into `~/.pi/agent/recipes`. `inspect target=recipes view=summary` reports their count, and verbose output lists their paths/descriptions for explicit replay by file path.
|
|
14
14
|
- Packaged pi-actors recipes are the lower-priority standard library of declarative actor config components, not automatically registered tools.
|
|
15
15
|
- Ad hoc recipe files outside the user recipe root are components unless explicitly registered/copied into `~/.pi/agent/recipes`.
|
|
16
16
|
- Recipe identity is the filename basename; `~/.pi/agent/recipes/docs_review.json` and `docs_review.md` both have id/tool name `docs_review`.
|
package/lib/observability.ts
CHANGED
|
@@ -1021,19 +1021,31 @@ function formatRecipePersistenceSuggestion(transition: RunTransition): string {
|
|
|
1021
1021
|
return `\nAgent note: this actor was spawned directly and completed successfully. If this pattern fits this machine's recurring workflow, ask the operator whether to save it as a durable recipe/tool under ~/.pi/agent/recipes with register_tool. Do not auto-save without confirmation.`;
|
|
1022
1022
|
}
|
|
1023
1023
|
|
|
1024
|
+
function formatTransitionNextActions(transition: RunTransition): string {
|
|
1025
|
+
const actions = [
|
|
1026
|
+
`inspect target=run:${transition.run} view=status`,
|
|
1027
|
+
transition.to === "done" && Object.keys(transition.artifacts ?? {}).length > 0
|
|
1028
|
+
? `inspect target=run:${transition.run} view=artifacts`
|
|
1029
|
+
: `inspect target=run:${transition.run} view=tail`,
|
|
1030
|
+
`inspect target=run:${transition.run} view=messages`,
|
|
1031
|
+
].filter(Boolean);
|
|
1032
|
+
return `\nNext actions: ${actions.join(" | ")}`;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1024
1035
|
export function formatRunTransitionMessage(transition: RunTransition): string {
|
|
1025
1036
|
const artifacts = formatNamedArtifacts(transition.artifacts);
|
|
1026
1037
|
const runFiles = formatRunFileList(getRunArtifacts(transition));
|
|
1027
1038
|
const persistenceSuggestion = formatRecipePersistenceSuggestion(transition);
|
|
1039
|
+
const nextActions = formatTransitionNextActions(transition);
|
|
1028
1040
|
if (transition.to === "done")
|
|
1029
|
-
return `Run ${transition.run} completed successfully.${artifacts}${runFiles}
|
|
1041
|
+
return `Run ${transition.run} completed successfully.${artifacts}${runFiles}${nextActions}${persistenceSuggestion}`;
|
|
1030
1042
|
if (transition.to === "failed")
|
|
1031
|
-
return `Run ${transition.run} failed.${artifacts}${runFiles}
|
|
1043
|
+
return `Run ${transition.run} failed.${artifacts}${runFiles}${nextActions}`;
|
|
1032
1044
|
if (transition.to === "cancelled")
|
|
1033
|
-
return `Run ${transition.run} was cancelled
|
|
1045
|
+
return `Run ${transition.run} was cancelled.${nextActions}`;
|
|
1034
1046
|
if (transition.to === "killed")
|
|
1035
|
-
return `Run ${transition.run} was force-killed
|
|
1047
|
+
return `Run ${transition.run} was force-killed.${nextActions}`;
|
|
1036
1048
|
if (transition.to === "exited")
|
|
1037
|
-
return `Run ${transition.run} exited before writing a result
|
|
1038
|
-
return `Run ${transition.run} finished with status ${transition.to}
|
|
1049
|
+
return `Run ${transition.run} exited before writing a result.${nextActions}`;
|
|
1050
|
+
return `Run ${transition.run} finished with status ${transition.to}.${nextActions}`;
|
|
1039
1051
|
}
|
package/lib/tools.ts
CHANGED
|
@@ -222,8 +222,8 @@ function compactAsyncRunStatus(value: unknown): string {
|
|
|
222
222
|
tokens.push(`failures=${failures}`);
|
|
223
223
|
if (result.code !== undefined) tokens.push(`code=${String(result.code)}`);
|
|
224
224
|
if (result.killed === true) tokens.push("killed=true");
|
|
225
|
-
|
|
226
|
-
|
|
225
|
+
const draftRecipe = status.draft_recipe ?? status.candidate_recipe;
|
|
226
|
+
if (draftRecipe) tokens.push(`draft_recipe=${String(draftRecipe)}`);
|
|
227
227
|
const nextActions = actorRunNextActions(run);
|
|
228
228
|
if (nextActions.length > 0)
|
|
229
229
|
tokens.push(`next=${nextActions.map((action) => action.replaceAll(/\s+/g, "_")).join("|")}`);
|
|
@@ -451,6 +451,15 @@ function compactArtifactPath(value: unknown): string {
|
|
|
451
451
|
return String(record.path ?? "<missing>");
|
|
452
452
|
}
|
|
453
453
|
|
|
454
|
+
function artifactNextActions(run: unknown, artifacts: Record<string, unknown>): string[] {
|
|
455
|
+
const id = String(run ?? "").trim();
|
|
456
|
+
if (!id || Object.keys(artifacts).length === 0) return [];
|
|
457
|
+
return [
|
|
458
|
+
`inspect target=run:${id} view=artifacts verbose=true`,
|
|
459
|
+
`inspect target=run:${id} view=messages`,
|
|
460
|
+
];
|
|
461
|
+
}
|
|
462
|
+
|
|
454
463
|
function compactActorFiles(status: Record<string, unknown>): string {
|
|
455
464
|
const run = String(status.run ?? "<unknown>");
|
|
456
465
|
const artifacts = asRecord(status.artifacts);
|
|
@@ -469,7 +478,11 @@ function compactActorFiles(status: Record<string, unknown>): string {
|
|
|
469
478
|
.map(([key, value]) => `${key}:${compactArtifactPath(value)}`)
|
|
470
479
|
.join(",")}`
|
|
471
480
|
: "";
|
|
472
|
-
|
|
481
|
+
const nextActions = artifactNextActions(run, artifacts);
|
|
482
|
+
const nextText = nextActions.length
|
|
483
|
+
? ` next=${nextActions.map((action) => action.replaceAll(/\s+/g, "_")).join("|")}`
|
|
484
|
+
: "";
|
|
485
|
+
return `\nrun=${run}${artifactText}${files.length ? ` files=${files.join(",")}` : ""}${nextText}`;
|
|
473
486
|
}
|
|
474
487
|
|
|
475
488
|
function summarizeOtherSessions(
|
|
@@ -621,9 +634,43 @@ function compactRecipeDoctor(summary: Record<string, unknown>): string {
|
|
|
621
634
|
`${String(item.severity ?? "info")} kind=${String(item.kind ?? "inspect")} id=${String(item.id ?? "root")}${blocked} action=${action ?? "inspect"}`,
|
|
622
635
|
);
|
|
623
636
|
}
|
|
637
|
+
const nextActions = Array.isArray(summary.next_actions)
|
|
638
|
+
? (summary.next_actions as string[])
|
|
639
|
+
: [];
|
|
640
|
+
if (nextActions.length > 0) lines[0] = `${lines[0]}${compactNextActions(nextActions)}`;
|
|
624
641
|
return `\n${lines.join("\n")}`;
|
|
625
642
|
}
|
|
626
643
|
|
|
644
|
+
function recipeRegistryNextActions(summary: Record<string, unknown>, view: string): string[] {
|
|
645
|
+
const actions: string[] = [];
|
|
646
|
+
const drafts = Array.isArray(summary.drafts)
|
|
647
|
+
? (summary.drafts as Array<Record<string, unknown>>)
|
|
648
|
+
: [];
|
|
649
|
+
const invalid = Array.isArray(summary.invalid) ? summary.invalid.length : 0;
|
|
650
|
+
const diagnostics = Array.isArray(summary.diagnostics)
|
|
651
|
+
? summary.diagnostics.length
|
|
652
|
+
: 0;
|
|
653
|
+
const topAction = asRecord(summary.top_action);
|
|
654
|
+
if (view !== "doctor" && (invalid > 0 || diagnostics > 0)) {
|
|
655
|
+
actions.push("inspect target=recipes view=doctor");
|
|
656
|
+
}
|
|
657
|
+
if (view === "doctor" && typeof topAction.action === "string") {
|
|
658
|
+
actions.push(String(topAction.action));
|
|
659
|
+
}
|
|
660
|
+
if (drafts.length > 0) {
|
|
661
|
+
actions.push("inspect target=recipes view=summary verbose=true");
|
|
662
|
+
const firstPath = typeof drafts[0]?.path === "string" ? drafts[0].path : undefined;
|
|
663
|
+
if (firstPath) actions.push(`spawn file=${firstPath}`);
|
|
664
|
+
}
|
|
665
|
+
return [...new Set(actions)].slice(0, 4);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
function compactNextActions(actions: string[]): string {
|
|
669
|
+
return actions.length
|
|
670
|
+
? ` next=${actions.map((action) => action.replaceAll(/\s+/g, "_")).join("|")}`
|
|
671
|
+
: "";
|
|
672
|
+
}
|
|
673
|
+
|
|
627
674
|
function compactRecipeRegistry(summary: Record<string, unknown>): string {
|
|
628
675
|
const active = Array.isArray(summary.active) ? summary.active.length : 0;
|
|
629
676
|
const shadowed = Array.isArray(summary.shadowed)
|
|
@@ -636,13 +683,43 @@ function compactRecipeRegistry(summary: Record<string, unknown>): string {
|
|
|
636
683
|
const diagnostics = Array.isArray(summary.diagnostics)
|
|
637
684
|
? summary.diagnostics.length
|
|
638
685
|
: 0;
|
|
639
|
-
const
|
|
640
|
-
? summary.
|
|
641
|
-
:
|
|
686
|
+
const drafts = Array.isArray(summary.drafts)
|
|
687
|
+
? summary.drafts.length
|
|
688
|
+
: Array.isArray(summary.candidates)
|
|
689
|
+
? summary.candidates.length
|
|
690
|
+
: 0;
|
|
642
691
|
const recommendations = Array.isArray(summary.recommendations)
|
|
643
692
|
? summary.recommendations.length
|
|
644
693
|
: 0;
|
|
645
|
-
|
|
694
|
+
const nextActions = Array.isArray(summary.next_actions)
|
|
695
|
+
? (summary.next_actions as string[])
|
|
696
|
+
: [];
|
|
697
|
+
return `\nrecipes active=${active} drafts=${drafts} shadowed=${shadowed} invalid=${invalid} disabled=${disabled} recommendations=${recommendations} diagnostics=${diagnostics}${compactNextActions(nextActions)}`;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
function actorMessageNextActions(
|
|
701
|
+
message: ActorMessages.ActorMessage,
|
|
702
|
+
result: Record<string, unknown>,
|
|
703
|
+
): string[] {
|
|
704
|
+
const actions: string[] = [];
|
|
705
|
+
const address = ActorMessages.parseActorAddress(message.to);
|
|
706
|
+
if (result.delivery_error || result.sent === false) {
|
|
707
|
+
if (address.kind === "run" && address.value) {
|
|
708
|
+
actions.push(`inspect target=run:${address.value} view=status`);
|
|
709
|
+
actions.push(`inspect target=run:${address.value} view=mailbox`);
|
|
710
|
+
} else if (address.kind === "branch" && address.value) {
|
|
711
|
+
actions.push(`inspect target=branch:${address.value}/${address.branch ?? "main"} view=mailbox`);
|
|
712
|
+
actions.push(`inspect target=run:${address.value} view=status`);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
if (result.queued === true) {
|
|
716
|
+
if (address.kind === "branch" && address.value) {
|
|
717
|
+
actions.push(`inspect target=branch:${address.value}/${address.branch ?? "main"} view=mailbox`);
|
|
718
|
+
} else if (address.kind === "run" && address.value) {
|
|
719
|
+
actions.push(`inspect target=run:${address.value} view=mailbox`);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
return [...new Set(actions)].slice(0, 3);
|
|
646
723
|
}
|
|
647
724
|
|
|
648
725
|
function compactActorMessageResult(
|
|
@@ -670,6 +747,11 @@ function compactActorMessageResult(
|
|
|
670
747
|
if (result.delivery_error) {
|
|
671
748
|
tokens.push(`delivery_error=${compactPreview(result.delivery_error, 96)}`);
|
|
672
749
|
}
|
|
750
|
+
const nextActions = Array.isArray(result.next_actions)
|
|
751
|
+
? (result.next_actions as string[])
|
|
752
|
+
: actorMessageNextActions(message, result);
|
|
753
|
+
if (nextActions.length > 0)
|
|
754
|
+
tokens.push(`next=${nextActions.map((action) => action.replaceAll(/\s+/g, "_")).join("|")}`);
|
|
673
755
|
return `\n${tokens.join(" ")}`;
|
|
674
756
|
}
|
|
675
757
|
|
|
@@ -832,7 +914,7 @@ function writeSpawnCandidateRecipe(
|
|
|
832
914
|
const defaults = candidateRecipeDefaults(meta.values);
|
|
833
915
|
const recipe = {
|
|
834
916
|
async: true,
|
|
835
|
-
description: `
|
|
917
|
+
description: `Draft recipe captured from spawn run ${String(meta.run)}`,
|
|
836
918
|
...(meta.artifacts ? { artifacts: meta.artifacts } : {}),
|
|
837
919
|
...(defaults ? { defaults } : {}),
|
|
838
920
|
template: input.template,
|
|
@@ -1040,7 +1122,9 @@ export function createSpawnToolDefinition<
|
|
|
1040
1122
|
const nextActions = actorRunNextActions(meta.run);
|
|
1041
1123
|
const details = {
|
|
1042
1124
|
...meta,
|
|
1043
|
-
...(candidateRecipe
|
|
1125
|
+
...(candidateRecipe
|
|
1126
|
+
? { candidate_recipe: candidateRecipe, draft_recipe: candidateRecipe }
|
|
1127
|
+
: {}),
|
|
1044
1128
|
next_actions: nextActions,
|
|
1045
1129
|
};
|
|
1046
1130
|
ActorRooms.ensureDefaultRoom(meta.state_dir, String(meta.run));
|
|
@@ -1084,6 +1168,33 @@ function requireContextSessionId(ctx: unknown, actor: string): string {
|
|
|
1084
1168
|
return sessionId;
|
|
1085
1169
|
}
|
|
1086
1170
|
|
|
1171
|
+
function sessionMismatchError(input: {
|
|
1172
|
+
currentSession?: string;
|
|
1173
|
+
expectedSession?: string;
|
|
1174
|
+
run?: string;
|
|
1175
|
+
target?: string;
|
|
1176
|
+
}): Error {
|
|
1177
|
+
const ownerSession = input.expectedSession ?? "none";
|
|
1178
|
+
const currentSession = input.currentSession ?? "none";
|
|
1179
|
+
const actor = input.run ? `run:${input.run}` : (input.target ?? "session");
|
|
1180
|
+
const hintTarget = input.expectedSession
|
|
1181
|
+
? `session:${input.expectedSession}`
|
|
1182
|
+
: "session:all";
|
|
1183
|
+
return Object.assign(
|
|
1184
|
+
new Error(
|
|
1185
|
+
`${actor} reason=session_mismatch owner_session=${ownerSession} current_session=${currentSession} hint=inspect_session:${input.expectedSession ?? "all"}`,
|
|
1186
|
+
),
|
|
1187
|
+
{
|
|
1188
|
+
current_session: input.currentSession,
|
|
1189
|
+
hint: `inspect target=${hintTarget} view=status`,
|
|
1190
|
+
owner_session: input.expectedSession,
|
|
1191
|
+
reason: "session_mismatch",
|
|
1192
|
+
run: input.run,
|
|
1193
|
+
target: input.target,
|
|
1194
|
+
},
|
|
1195
|
+
);
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1087
1198
|
function assertRunAccessibleToContext(
|
|
1088
1199
|
runId: string,
|
|
1089
1200
|
ctx: unknown,
|
|
@@ -1091,18 +1202,11 @@ function assertRunAccessibleToContext(
|
|
|
1091
1202
|
const status = AsyncRuns.getRunStatus(runId);
|
|
1092
1203
|
const sessionId = getContextSessionId(ctx);
|
|
1093
1204
|
if (sessionId && status.ownerId && status.ownerId !== sessionId) {
|
|
1094
|
-
throw
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
current_session: sessionId,
|
|
1100
|
-
hint: `inspect target=session:${status.ownerId} view=status`,
|
|
1101
|
-
owner_session: status.ownerId,
|
|
1102
|
-
reason: "session_mismatch",
|
|
1103
|
-
run: runId,
|
|
1104
|
-
},
|
|
1105
|
-
);
|
|
1205
|
+
throw sessionMismatchError({
|
|
1206
|
+
currentSession: sessionId,
|
|
1207
|
+
expectedSession: String(status.ownerId),
|
|
1208
|
+
run: runId,
|
|
1209
|
+
});
|
|
1106
1210
|
}
|
|
1107
1211
|
return status;
|
|
1108
1212
|
}
|
|
@@ -1120,7 +1224,7 @@ export function createInspectToolDefinition<TContext = unknown>(
|
|
|
1120
1224
|
name: "inspect",
|
|
1121
1225
|
label: "Inspect",
|
|
1122
1226
|
description:
|
|
1123
|
-
"Intentionally inspect
|
|
1227
|
+
"Intentionally inspect actors at decision points, after follow-ups, or during diagnosis instead of polling. Core targets are run:<id> and tool:<name>; advanced targets include branch:<run>/<branch>, room:<run>, coordinator, session:<id>, and session:all.",
|
|
1124
1228
|
parameters: objectSchema(
|
|
1125
1229
|
{
|
|
1126
1230
|
lines: stringSchema("Line count for tail/messages views. Default 40."),
|
|
@@ -1128,13 +1232,13 @@ export function createInspectToolDefinition<TContext = unknown>(
|
|
|
1128
1232
|
"Optional session run filter: all, running, active, terminal, done, failed, cancelled, killed, or exited.",
|
|
1129
1233
|
),
|
|
1130
1234
|
target: stringSchema(
|
|
1131
|
-
"Actor address to inspect, e.g. run:<id>, room:<run>, coordinator, session:<id>, session:all
|
|
1235
|
+
"Actor address to inspect, e.g. run:<id> or tool:<name>; advanced: branch:<run>/<branch>, room:<run>, coordinator, session:<id>, session:all.",
|
|
1132
1236
|
),
|
|
1133
1237
|
verbose: booleanSchema(
|
|
1134
1238
|
"Return full JSON instead of compact text where available.",
|
|
1135
1239
|
),
|
|
1136
1240
|
view: stringSchema(
|
|
1137
|
-
"Inspection view: status, tail, messages, artifacts, files, mailbox
|
|
1241
|
+
"Inspection view. Core run views: status, tail, messages, artifacts, files, mailbox. Advanced views include communication, roster, and contacts.",
|
|
1138
1242
|
),
|
|
1139
1243
|
},
|
|
1140
1244
|
["target", "view"],
|
|
@@ -1169,12 +1273,19 @@ export function createInspectToolDefinition<TContext = unknown>(
|
|
|
1169
1273
|
{ root: deps.packagedRecipeRoot ?? Paths.getPackagedRecipeRoot() },
|
|
1170
1274
|
]);
|
|
1171
1275
|
const recipeRoot = deps.recipeRoot ?? Paths.getRecipeRoot();
|
|
1172
|
-
const
|
|
1276
|
+
const summaryBase = {
|
|
1173
1277
|
...RecipeDiscovery.summarizeDiscovery(discovered),
|
|
1278
|
+
drafts: RecipeDiscovery.listCandidateRecipes(
|
|
1279
|
+
join(recipeRoot, "candidates"),
|
|
1280
|
+
),
|
|
1174
1281
|
candidates: RecipeDiscovery.listCandidateRecipes(
|
|
1175
1282
|
join(recipeRoot, "candidates"),
|
|
1176
1283
|
),
|
|
1177
1284
|
};
|
|
1285
|
+
const summary = {
|
|
1286
|
+
...summaryBase,
|
|
1287
|
+
next_actions: recipeRegistryNextActions(summaryBase, view),
|
|
1288
|
+
};
|
|
1178
1289
|
return {
|
|
1179
1290
|
content: [
|
|
1180
1291
|
{
|
|
@@ -1488,7 +1599,14 @@ export function createInspectToolDefinition<TContext = unknown>(
|
|
|
1488
1599
|
| undefined,
|
|
1489
1600
|
);
|
|
1490
1601
|
const details = artifactManifest
|
|
1491
|
-
? {
|
|
1602
|
+
? {
|
|
1603
|
+
...status,
|
|
1604
|
+
artifact_manifest: artifactManifest,
|
|
1605
|
+
next_actions: artifactNextActions(
|
|
1606
|
+
status.run ?? runId,
|
|
1607
|
+
asRecord(status.artifacts),
|
|
1608
|
+
),
|
|
1609
|
+
}
|
|
1492
1610
|
: status;
|
|
1493
1611
|
return {
|
|
1494
1612
|
content: [
|
|
@@ -1574,7 +1692,7 @@ export function createActorMessageToolDefinition<TContext = unknown>(
|
|
|
1574
1692
|
name: "message",
|
|
1575
1693
|
label: "Message",
|
|
1576
1694
|
description:
|
|
1577
|
-
"Send one typed addressed message to steer an existing actor instead of restarting it.
|
|
1695
|
+
"Send one typed addressed message to steer an existing actor instead of restarting it. Core routes are run:<id> and tool:<name>; advanced routes include branch:<run>/<branch>, room:<run> group timelines, coordinator, and session:<id>.",
|
|
1578
1696
|
parameters: objectSchema(
|
|
1579
1697
|
{
|
|
1580
1698
|
body: unionSchema([
|
|
@@ -1596,7 +1714,7 @@ export function createActorMessageToolDefinition<TContext = unknown>(
|
|
|
1596
1714
|
reply_to: stringSchema("Optional message id this message replies to."),
|
|
1597
1715
|
summary: stringSchema("Optional short human-facing summary."),
|
|
1598
1716
|
to: stringSchema(
|
|
1599
|
-
"Destination actor address, e.g. run:<id
|
|
1717
|
+
"Destination actor address, e.g. run:<id> or tool:<name>; advanced: branch:<run>/<branch>, room:<run>, coordinator, session:<id>.",
|
|
1600
1718
|
),
|
|
1601
1719
|
type: stringSchema(
|
|
1602
1720
|
"Semantic message type, e.g. control.approve or checkpoint.needs_scope.",
|
|
@@ -1765,14 +1883,20 @@ export function createActorMessageToolDefinition<TContext = unknown>(
|
|
|
1765
1883
|
const senderStatus = assertRunAccessibleToContext(sender.value, ctx);
|
|
1766
1884
|
if (address.kind === "session") {
|
|
1767
1885
|
if (!senderStatus.ownerId) {
|
|
1768
|
-
throw
|
|
1769
|
-
|
|
1770
|
-
|
|
1886
|
+
throw sessionMismatchError({
|
|
1887
|
+
currentSession: undefined,
|
|
1888
|
+
expectedSession: address.value,
|
|
1889
|
+
run: sender.value,
|
|
1890
|
+
target: `session:${address.value}`,
|
|
1891
|
+
});
|
|
1771
1892
|
}
|
|
1772
1893
|
if (senderStatus.ownerId !== address.value) {
|
|
1773
|
-
throw
|
|
1774
|
-
|
|
1775
|
-
|
|
1894
|
+
throw sessionMismatchError({
|
|
1895
|
+
currentSession: String(senderStatus.ownerId),
|
|
1896
|
+
expectedSession: address.value,
|
|
1897
|
+
run: sender.value,
|
|
1898
|
+
target: `session:${address.value}`,
|
|
1899
|
+
});
|
|
1776
1900
|
}
|
|
1777
1901
|
}
|
|
1778
1902
|
result = AsyncRuns.appendRunOutboxEvent(sender.value, {
|
|
@@ -1796,18 +1920,22 @@ export function createActorMessageToolDefinition<TContext = unknown>(
|
|
|
1796
1920
|
`message currently supports run:<id>, branch:<run>/<branch>, room:<run>, tool:<name>, coordinator, and session:<id> destinations; unsupported destination: ${message.to}`,
|
|
1797
1921
|
);
|
|
1798
1922
|
}
|
|
1923
|
+
const nextActions = actorMessageNextActions(message, result);
|
|
1924
|
+
const resultWithNext = nextActions.length
|
|
1925
|
+
? { ...result, next_actions: nextActions }
|
|
1926
|
+
: result;
|
|
1799
1927
|
return {
|
|
1800
1928
|
content: [
|
|
1801
1929
|
{
|
|
1802
1930
|
type: "text" as const,
|
|
1803
1931
|
text: maybeJsonText(
|
|
1804
|
-
{ message, result },
|
|
1932
|
+
{ message, result: resultWithNext },
|
|
1805
1933
|
input.verbose === true,
|
|
1806
|
-
compactActorMessageResult(message,
|
|
1934
|
+
compactActorMessageResult(message, resultWithNext),
|
|
1807
1935
|
),
|
|
1808
1936
|
},
|
|
1809
1937
|
],
|
|
1810
|
-
details: { message, result },
|
|
1938
|
+
details: { message, result: resultWithNext },
|
|
1811
1939
|
};
|
|
1812
1940
|
},
|
|
1813
1941
|
};
|
package/package.json
CHANGED
package/skills/actors/SKILL.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: actors
|
|
3
3
|
description: Required practical guide for non-trivial pi-actors use. Read before using or changing spawn, message, inspect, actor runs, tools, recipes, command templates, async lifecycle, mailboxes, artifacts, and local orchestration mechanics.
|
|
4
4
|
metadata:
|
|
5
|
-
version: 0.
|
|
5
|
+
version: 0.32.0
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Actors (pi-actors)
|
|
@@ -39,12 +39,11 @@ Trusted local capability
|
|
|
39
39
|
|
|
40
40
|
- **Command template**: portable execution graph. String leaf, sequence array, or object node with controls.
|
|
41
41
|
- **Recipe**: saved JSON definition wrapping a template with args, defaults, imports, mailbox, artifacts, metadata, and optional `async: true`.
|
|
42
|
-
- **Run actor**: one detached execution instance addressable as `run:<id>` with status, logs, messages, mailbox metadata, files,
|
|
43
|
-
- **Room actor**: shared timeline + roster endpoint addressable as `room:<run>`; every spawned run gets `room:<run>`.
|
|
42
|
+
- **Run actor**: one detached execution instance addressable as `run:<id>` with status, logs, messages, mailbox metadata, files, and artifacts.
|
|
44
43
|
- **Tool actor**: registered persistent capability addressable as `tool:<name>` and callable through the generated tool or `message`. `tool:pi-actors` is reserved for runtime/status inspection.
|
|
45
|
-
- **Coordinator/session**: the current pi session endpoint that receives bounded actor follow-ups.
|
|
46
|
-
- **Mailbox**: public interaction contract: message types the actor accepts/emits.
|
|
47
44
|
- **Artifact**: named durable output path declared by a recipe/run.
|
|
45
|
+
- **Mailbox**: interaction contract: message types the actor accepts/emits.
|
|
46
|
+
- **Advanced group/coordination surfaces**: `branch:<run>/<branch>`, `room:<run>`, `coordinator`, `session:<id>`, and communication snapshots exist for multi-actor workflows and diagnostics; do not make them the default mental model.
|
|
48
47
|
|
|
49
48
|
## Three Verbs
|
|
50
49
|
|
|
@@ -86,8 +85,9 @@ Envelope fields:
|
|
|
86
85
|
|
|
87
86
|
- Required: `to`, `type`.
|
|
88
87
|
- Useful: `summary`, `body`, `from`, `reply_to`, `correlation_id`, `metadata`.
|
|
89
|
-
-
|
|
90
|
-
-
|
|
88
|
+
- Core addresses: `run:<id>`, `tool:<name>`.
|
|
89
|
+
- Advanced addresses: `branch:<run>/<branch>`, `room:<run>` for group timeline/roster, `coordinator`, `session:<id>`.
|
|
90
|
+
- Group posts to `room:<run>` require `from` from the same run (`run:<run>` or `branch:<run>/<branch>`).
|
|
91
91
|
- Runtime termination message: `control.kill` is the only documented actor message that kills a run. `control.stop` and `control.cancel` are actor-local mailbox vocabulary only when a recipe declares and handles them. Terminal retention messages: `control.archive`, `control.prune`.
|
|
92
92
|
- Long-lived child processes should remain in the run-owned process group unless the recipe implements an explicit daemon termination bridge; do not leave unowned detached services behind `control.kill`.
|
|
93
93
|
|
|
@@ -99,12 +99,7 @@ Check `inspect view=mailbox` before domain-specific messages.
|
|
|
99
99
|
{ "target": "run:repo-health", "view": "status" }
|
|
100
100
|
{ "target": "run:repo-health", "view": "tail", "lines": "80" }
|
|
101
101
|
{ "target": "run:repo-health", "view": "messages" }
|
|
102
|
-
{ "target": "run:repo-health", "view": "communication" }
|
|
103
102
|
{ "target": "run:repo-health", "view": "artifacts" }
|
|
104
|
-
{ "target": "room:repo-health", "view": "status" }
|
|
105
|
-
{ "target": "room:repo-health", "view": "roster" }
|
|
106
|
-
{ "target": "room:repo-health", "view": "contacts" }
|
|
107
|
-
{ "target": "room:repo-health", "view": "previews" }
|
|
108
103
|
{ "target": "tool:pi-actors", "view": "status" }
|
|
109
104
|
{ "target": "tool:music_player", "view": "status" }
|
|
110
105
|
{ "target": "recipes", "view": "status" }
|
|
@@ -116,10 +111,10 @@ Views:
|
|
|
116
111
|
- `status`: lifecycle, pid, values, progress, result, compact summary.
|
|
117
112
|
- `tail`: recent stdout/stderr/log tail.
|
|
118
113
|
- `messages`: actor messages emitted by the run, or room timeline entries for `room:*`.
|
|
119
|
-
- `communication`: run/branch
|
|
120
|
-
- `roster`: room member list with address, role, parent, caps, claim, status, and last seen.
|
|
121
|
-
- `contacts`: roster-derived direct-message targets without full roster metadata.
|
|
122
|
-
- `previews`: TUI-ready bounded
|
|
114
|
+
- Advanced `communication`: run/branch group-coordination snapshot with self/root/default-room/member/contact hints.
|
|
115
|
+
- Advanced `roster`: room member list with address, role, parent, caps, claim, status, and last seen.
|
|
116
|
+
- Advanced `contacts`: roster-derived direct-message targets without full roster metadata.
|
|
117
|
+
- Advanced `previews`: TUI-ready bounded group-message previews with timestamp/from/to/type/summary/body_preview.
|
|
123
118
|
- `mailbox`: declared accepts/emits contract for runs; queued direct branch inbox messages for `branch:<run>/<branch>` with `id`, status, route/type, and queue/handling timestamps.
|
|
124
119
|
- `files`: run state directory file list.
|
|
125
120
|
- `artifacts`: declared artifact paths/status.
|
|
@@ -218,13 +213,13 @@ Only matching filename ids compete. Higher priority shadows lower priority; with
|
|
|
218
213
|
Muscle-memory lens: pi-actors has two durable executable-memory layers.
|
|
219
214
|
|
|
220
215
|
1. `~/.pi/agent/recipes/*.json` and `*.md` are the agent's active capability memory. Every recipe in that directory becomes an easy-to-call tool automatically and survives into later sessions. Descriptions matter here because they become the tool's operator-facing title/context.
|
|
221
|
-
2. `~/.pi/agent/recipes/candidates/*.json` is
|
|
216
|
+
2. `~/.pi/agent/recipes/candidates/*.json` is draft memory captured from successful inline `spawn template=...` runs. The directory name is retained for compatibility; treat these as drafts, not active tools. Drafts do not enter the injected tool surface. They remain reusable by explicit path, e.g. `spawn file="~/.pi/agent/recipes/candidates/<name>.json"`, and can be promoted by moving or copying one level up into `~/.pi/agent/recipes`.
|
|
222
217
|
|
|
223
|
-
Agents grow active memory by calling `register_tool` or by deliberate recipe-file edits. They grow
|
|
218
|
+
Agents grow active memory by calling `register_tool` or by deliberate recipe-file edits. They grow draft memory by trying ad hoc actors successfully. Treat both as executable habits: drafts are the workbench/proving ground; root recipes are promoted muscle memory.
|
|
224
219
|
|
|
225
220
|
Usage lens: user recipes may carry extension-maintained launch metadata such as `usage.calls` and `usage.last_called`. The extension increments the counter when it starts that concrete recipe; agents should not hand-edit counters as part of normal recipe maintenance. Treat usage as evidence for usefulness analysis: heavily used recipes are good candidates for promotion, documentation, or stronger tests; unused recipes are cleanup candidates. Do not use failure counts as a primary usefulness signal because failures may reflect bad caller judgment rather than bad recipes. Do not delete or demote solely from counters without operator approval.
|
|
226
221
|
|
|
227
|
-
Promotion lens: successful transient/ad hoc actor runs are evidence, not commands. Inline spawns leave
|
|
222
|
+
Promotion lens: successful transient/ad hoc actor runs are evidence, not commands. Inline spawns leave draft recipes as replayable evidence, not active tools. If a draft is repeatable, parameterized, safe enough, and likely useful later, the agent may promote it by moving/copying it into `~/.pi/agent/recipes` or by calling `register_tool` with a concise name, typed args/defaults, and a reviewed template or recipe path. Do not auto-register every success; do not promote temp paths, secrets, one-off prompts, or project-private assumptions without normalization and approval.
|
|
228
223
|
|
|
229
224
|
Cleanup rule: periodically inspect `~/.pi/agent/recipes` as the live muscle-memory set. For each stale, duplicate, too-specific, or low-value recipe, choose one explicit action: keep as a tool, move it out of the agent recipe root to retain recipe-only memory, merge into a better recipe, or delete/archive the file. Prefer moving over deletion when the recipe may still be useful as a component. Never silently remove tools during unrelated work.
|
|
230
225
|
|
package/skills/swarm/SKILL.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: swarm
|
|
3
3
|
description: Subagent orchestration with scoped locks and quorum consensus. Use for multi-model review, parallel scoped work, delegated audit, and coordinated subagent execution.
|
|
4
4
|
metadata:
|
|
5
|
-
version: 0.
|
|
5
|
+
version: 0.32.0
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Swarm
|
|
@@ -30,8 +30,8 @@ Maintain this skill as a living orchestration standard. When real swarm work exp
|
|
|
30
30
|
- `Async Run`: A local lifecycle envelope around a command-template swarm composer or utility. It owns state, logs, status, cancellation, and observability, not swarm semantics.
|
|
31
31
|
- `Lens`: A deliberately narrow cognitive role assigned to one subagent, such as security, tests, architecture, economics, or operator UX.
|
|
32
32
|
- `Task Card`: A bounded implementation assignment with goal, allowed files, avoided files, expected output, and validation gates.
|
|
33
|
-
- `Component Capability`: An abstract adapter operation such as launcher, reviewer, verifier, merger, quorum, checkpoint, follow-up, judge, or normalizer. Swarm may target these capabilities, but local adapters bind them to concrete tools, recipes,
|
|
34
|
-
- `
|
|
33
|
+
- `Component Capability`: An abstract adapter operation such as launcher, reviewer, verifier, merger, quorum, checkpoint, follow-up, judge, or normalizer. Swarm may target these capabilities, but local adapters bind them to concrete tools, recipes, draft recipes, command templates, async runs, or services.
|
|
34
|
+
- `Draft Recipe`: A reusable but non-registered recipe captured from a successful inline actor spawn. It can be replayed by explicit file path and later promoted into the active tool recipe root after enough dogfood. Its compatibility storage path may still include `recipes/candidates`.
|
|
35
35
|
- `Coordinator Checkpoint`: A deliberate subagent pause where the subagent preserves its working context, sends a bounded question or status to the orchestrator, receives a coordinator reply, and continues in the same subagent context.
|
|
36
36
|
- `Evidence Checkpoint`: A deliberate stop where a subagent records sources, assumptions, confidence, contradictions, or blocking evidence gaps before synthesis.
|
|
37
37
|
- `Integrator`: The human or agent that merges isolated branches/worktrees into the shared target and owns conflict resolution.
|