@llblab/pi-actors 0.30.2 → 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 +16 -0
- package/README.md +36 -12
- package/dist/lib/observability.js +17 -6
- package/dist/lib/prompts.d.ts +1 -1
- package/dist/lib/prompts.js +3 -3
- package/dist/lib/tools.js +166 -29
- package/dist/skills/actors/SKILL.md +18 -21
- package/dist/skills/swarm/SKILL.md +3 -3
- package/docs/actor-messages.md +1 -1
- package/docs/async-runs.md +2 -0
- package/docs/tool-registry.md +1 -1
- package/lib/observability.ts +18 -6
- package/lib/prompts.ts +3 -3
- package/lib/tools.ts +186 -41
- package/package.json +1 -1
- package/skills/actors/SKILL.md +18 -21
- package/skills/swarm/SKILL.md +3 -3
package/lib/tools.ts
CHANGED
|
@@ -187,12 +187,23 @@ function formatFailureCount(value: unknown): number | undefined {
|
|
|
187
187
|
return Array.isArray(value) ? value.length : undefined;
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
function actorRunNextActions(run: unknown): string[] {
|
|
191
|
+
const id = String(run ?? "").trim();
|
|
192
|
+
if (!id) return [];
|
|
193
|
+
return [
|
|
194
|
+
`inspect target=run:${id} view=status`,
|
|
195
|
+
`inspect target=run:${id} view=messages`,
|
|
196
|
+
`message to=run:${id} type=<actor.action>`,
|
|
197
|
+
];
|
|
198
|
+
}
|
|
199
|
+
|
|
190
200
|
function compactAsyncRunStatus(value: unknown): string {
|
|
191
201
|
const status = asRecord(value);
|
|
192
202
|
const progress = asRecord(status.progress);
|
|
193
203
|
const result = asRecord(status.result);
|
|
204
|
+
const run = String(status.run ?? "<unknown>");
|
|
194
205
|
const tokens = [
|
|
195
|
-
`run=${
|
|
206
|
+
`run=${run}`,
|
|
196
207
|
`status=${String(status.status ?? "unknown")}`,
|
|
197
208
|
];
|
|
198
209
|
if (status.tool) tokens.push(`tool=${String(status.tool)}`);
|
|
@@ -211,8 +222,11 @@ function compactAsyncRunStatus(value: unknown): string {
|
|
|
211
222
|
tokens.push(`failures=${failures}`);
|
|
212
223
|
if (result.code !== undefined) tokens.push(`code=${String(result.code)}`);
|
|
213
224
|
if (result.killed === true) tokens.push("killed=true");
|
|
214
|
-
|
|
215
|
-
|
|
225
|
+
const draftRecipe = status.draft_recipe ?? status.candidate_recipe;
|
|
226
|
+
if (draftRecipe) tokens.push(`draft_recipe=${String(draftRecipe)}`);
|
|
227
|
+
const nextActions = actorRunNextActions(run);
|
|
228
|
+
if (nextActions.length > 0)
|
|
229
|
+
tokens.push(`next=${nextActions.map((action) => action.replaceAll(/\s+/g, "_")).join("|")}`);
|
|
216
230
|
return `\n${tokens.join(" ")}`;
|
|
217
231
|
}
|
|
218
232
|
|
|
@@ -437,6 +451,15 @@ function compactArtifactPath(value: unknown): string {
|
|
|
437
451
|
return String(record.path ?? "<missing>");
|
|
438
452
|
}
|
|
439
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
|
+
|
|
440
463
|
function compactActorFiles(status: Record<string, unknown>): string {
|
|
441
464
|
const run = String(status.run ?? "<unknown>");
|
|
442
465
|
const artifacts = asRecord(status.artifacts);
|
|
@@ -455,7 +478,11 @@ function compactActorFiles(status: Record<string, unknown>): string {
|
|
|
455
478
|
.map(([key, value]) => `${key}:${compactArtifactPath(value)}`)
|
|
456
479
|
.join(",")}`
|
|
457
480
|
: "";
|
|
458
|
-
|
|
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}`;
|
|
459
486
|
}
|
|
460
487
|
|
|
461
488
|
function summarizeOtherSessions(
|
|
@@ -607,9 +634,43 @@ function compactRecipeDoctor(summary: Record<string, unknown>): string {
|
|
|
607
634
|
`${String(item.severity ?? "info")} kind=${String(item.kind ?? "inspect")} id=${String(item.id ?? "root")}${blocked} action=${action ?? "inspect"}`,
|
|
608
635
|
);
|
|
609
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)}`;
|
|
610
641
|
return `\n${lines.join("\n")}`;
|
|
611
642
|
}
|
|
612
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
|
+
|
|
613
674
|
function compactRecipeRegistry(summary: Record<string, unknown>): string {
|
|
614
675
|
const active = Array.isArray(summary.active) ? summary.active.length : 0;
|
|
615
676
|
const shadowed = Array.isArray(summary.shadowed)
|
|
@@ -622,13 +683,43 @@ function compactRecipeRegistry(summary: Record<string, unknown>): string {
|
|
|
622
683
|
const diagnostics = Array.isArray(summary.diagnostics)
|
|
623
684
|
? summary.diagnostics.length
|
|
624
685
|
: 0;
|
|
625
|
-
const
|
|
626
|
-
? summary.
|
|
627
|
-
:
|
|
686
|
+
const drafts = Array.isArray(summary.drafts)
|
|
687
|
+
? summary.drafts.length
|
|
688
|
+
: Array.isArray(summary.candidates)
|
|
689
|
+
? summary.candidates.length
|
|
690
|
+
: 0;
|
|
628
691
|
const recommendations = Array.isArray(summary.recommendations)
|
|
629
692
|
? summary.recommendations.length
|
|
630
693
|
: 0;
|
|
631
|
-
|
|
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);
|
|
632
723
|
}
|
|
633
724
|
|
|
634
725
|
function compactActorMessageResult(
|
|
@@ -656,6 +747,11 @@ function compactActorMessageResult(
|
|
|
656
747
|
if (result.delivery_error) {
|
|
657
748
|
tokens.push(`delivery_error=${compactPreview(result.delivery_error, 96)}`);
|
|
658
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("|")}`);
|
|
659
755
|
return `\n${tokens.join(" ")}`;
|
|
660
756
|
}
|
|
661
757
|
|
|
@@ -818,7 +914,7 @@ function writeSpawnCandidateRecipe(
|
|
|
818
914
|
const defaults = candidateRecipeDefaults(meta.values);
|
|
819
915
|
const recipe = {
|
|
820
916
|
async: true,
|
|
821
|
-
description: `
|
|
917
|
+
description: `Draft recipe captured from spawn run ${String(meta.run)}`,
|
|
822
918
|
...(meta.artifacts ? { artifacts: meta.artifacts } : {}),
|
|
823
919
|
...(defaults ? { defaults } : {}),
|
|
824
920
|
template: input.template,
|
|
@@ -942,7 +1038,7 @@ export function createSpawnToolDefinition<
|
|
|
942
1038
|
name: "spawn",
|
|
943
1039
|
label: "Spawn",
|
|
944
1040
|
description:
|
|
945
|
-
"Create an addressable actor from a recipe file or inline command template. Currently spawns run:<id> actors backed by async runs.",
|
|
1041
|
+
"Create an addressable actor from a recipe file or inline command template. Use instead of ad hoc shell backgrounding for work that may outlive this turn, needs steering/follow-up/artifacts, runs as a service, fans out, or should be inspected later. Currently spawns run:<id> actors backed by async runs.",
|
|
946
1042
|
parameters: objectSchema(
|
|
947
1043
|
{
|
|
948
1044
|
artifacts: looseObjectSchema(
|
|
@@ -1023,9 +1119,14 @@ export function createSpawnToolDefinition<
|
|
|
1023
1119
|
throw enhanceSpawnRecipeError(error, recipe);
|
|
1024
1120
|
}
|
|
1025
1121
|
const candidateRecipe = writeSpawnCandidateRecipe(input, meta);
|
|
1026
|
-
const
|
|
1027
|
-
|
|
1028
|
-
|
|
1122
|
+
const nextActions = actorRunNextActions(meta.run);
|
|
1123
|
+
const details = {
|
|
1124
|
+
...meta,
|
|
1125
|
+
...(candidateRecipe
|
|
1126
|
+
? { candidate_recipe: candidateRecipe, draft_recipe: candidateRecipe }
|
|
1127
|
+
: {}),
|
|
1128
|
+
next_actions: nextActions,
|
|
1129
|
+
};
|
|
1029
1130
|
ActorRooms.ensureDefaultRoom(meta.state_dir, String(meta.run));
|
|
1030
1131
|
ActorRooms.writeCommunicationSnapshot(meta.state_dir, String(meta.run));
|
|
1031
1132
|
return {
|
|
@@ -1067,6 +1168,33 @@ function requireContextSessionId(ctx: unknown, actor: string): string {
|
|
|
1067
1168
|
return sessionId;
|
|
1068
1169
|
}
|
|
1069
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
|
+
|
|
1070
1198
|
function assertRunAccessibleToContext(
|
|
1071
1199
|
runId: string,
|
|
1072
1200
|
ctx: unknown,
|
|
@@ -1074,18 +1202,11 @@ function assertRunAccessibleToContext(
|
|
|
1074
1202
|
const status = AsyncRuns.getRunStatus(runId);
|
|
1075
1203
|
const sessionId = getContextSessionId(ctx);
|
|
1076
1204
|
if (sessionId && status.ownerId && status.ownerId !== sessionId) {
|
|
1077
|
-
throw
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
current_session: sessionId,
|
|
1083
|
-
hint: `inspect target=session:${status.ownerId} view=status`,
|
|
1084
|
-
owner_session: status.ownerId,
|
|
1085
|
-
reason: "session_mismatch",
|
|
1086
|
-
run: runId,
|
|
1087
|
-
},
|
|
1088
|
-
);
|
|
1205
|
+
throw sessionMismatchError({
|
|
1206
|
+
currentSession: sessionId,
|
|
1207
|
+
expectedSession: String(status.ownerId),
|
|
1208
|
+
run: runId,
|
|
1209
|
+
});
|
|
1089
1210
|
}
|
|
1090
1211
|
return status;
|
|
1091
1212
|
}
|
|
@@ -1103,7 +1224,7 @@ export function createInspectToolDefinition<TContext = unknown>(
|
|
|
1103
1224
|
name: "inspect",
|
|
1104
1225
|
label: "Inspect",
|
|
1105
1226
|
description:
|
|
1106
|
-
"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.",
|
|
1107
1228
|
parameters: objectSchema(
|
|
1108
1229
|
{
|
|
1109
1230
|
lines: stringSchema("Line count for tail/messages views. Default 40."),
|
|
@@ -1111,13 +1232,13 @@ export function createInspectToolDefinition<TContext = unknown>(
|
|
|
1111
1232
|
"Optional session run filter: all, running, active, terminal, done, failed, cancelled, killed, or exited.",
|
|
1112
1233
|
),
|
|
1113
1234
|
target: stringSchema(
|
|
1114
|
-
"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.",
|
|
1115
1236
|
),
|
|
1116
1237
|
verbose: booleanSchema(
|
|
1117
1238
|
"Return full JSON instead of compact text where available.",
|
|
1118
1239
|
),
|
|
1119
1240
|
view: stringSchema(
|
|
1120
|
-
"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.",
|
|
1121
1242
|
),
|
|
1122
1243
|
},
|
|
1123
1244
|
["target", "view"],
|
|
@@ -1152,12 +1273,19 @@ export function createInspectToolDefinition<TContext = unknown>(
|
|
|
1152
1273
|
{ root: deps.packagedRecipeRoot ?? Paths.getPackagedRecipeRoot() },
|
|
1153
1274
|
]);
|
|
1154
1275
|
const recipeRoot = deps.recipeRoot ?? Paths.getRecipeRoot();
|
|
1155
|
-
const
|
|
1276
|
+
const summaryBase = {
|
|
1156
1277
|
...RecipeDiscovery.summarizeDiscovery(discovered),
|
|
1278
|
+
drafts: RecipeDiscovery.listCandidateRecipes(
|
|
1279
|
+
join(recipeRoot, "candidates"),
|
|
1280
|
+
),
|
|
1157
1281
|
candidates: RecipeDiscovery.listCandidateRecipes(
|
|
1158
1282
|
join(recipeRoot, "candidates"),
|
|
1159
1283
|
),
|
|
1160
1284
|
};
|
|
1285
|
+
const summary = {
|
|
1286
|
+
...summaryBase,
|
|
1287
|
+
next_actions: recipeRegistryNextActions(summaryBase, view),
|
|
1288
|
+
};
|
|
1161
1289
|
return {
|
|
1162
1290
|
content: [
|
|
1163
1291
|
{
|
|
@@ -1471,7 +1599,14 @@ export function createInspectToolDefinition<TContext = unknown>(
|
|
|
1471
1599
|
| undefined,
|
|
1472
1600
|
);
|
|
1473
1601
|
const details = artifactManifest
|
|
1474
|
-
? {
|
|
1602
|
+
? {
|
|
1603
|
+
...status,
|
|
1604
|
+
artifact_manifest: artifactManifest,
|
|
1605
|
+
next_actions: artifactNextActions(
|
|
1606
|
+
status.run ?? runId,
|
|
1607
|
+
asRecord(status.artifacts),
|
|
1608
|
+
),
|
|
1609
|
+
}
|
|
1475
1610
|
: status;
|
|
1476
1611
|
return {
|
|
1477
1612
|
content: [
|
|
@@ -1557,7 +1692,7 @@ export function createActorMessageToolDefinition<TContext = unknown>(
|
|
|
1557
1692
|
name: "message",
|
|
1558
1693
|
label: "Message",
|
|
1559
1694
|
description:
|
|
1560
|
-
"Send one typed addressed message.
|
|
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>.",
|
|
1561
1696
|
parameters: objectSchema(
|
|
1562
1697
|
{
|
|
1563
1698
|
body: unionSchema([
|
|
@@ -1579,7 +1714,7 @@ export function createActorMessageToolDefinition<TContext = unknown>(
|
|
|
1579
1714
|
reply_to: stringSchema("Optional message id this message replies to."),
|
|
1580
1715
|
summary: stringSchema("Optional short human-facing summary."),
|
|
1581
1716
|
to: stringSchema(
|
|
1582
|
-
"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>.",
|
|
1583
1718
|
),
|
|
1584
1719
|
type: stringSchema(
|
|
1585
1720
|
"Semantic message type, e.g. control.approve or checkpoint.needs_scope.",
|
|
@@ -1748,14 +1883,20 @@ export function createActorMessageToolDefinition<TContext = unknown>(
|
|
|
1748
1883
|
const senderStatus = assertRunAccessibleToContext(sender.value, ctx);
|
|
1749
1884
|
if (address.kind === "session") {
|
|
1750
1885
|
if (!senderStatus.ownerId) {
|
|
1751
|
-
throw
|
|
1752
|
-
|
|
1753
|
-
|
|
1886
|
+
throw sessionMismatchError({
|
|
1887
|
+
currentSession: undefined,
|
|
1888
|
+
expectedSession: address.value,
|
|
1889
|
+
run: sender.value,
|
|
1890
|
+
target: `session:${address.value}`,
|
|
1891
|
+
});
|
|
1754
1892
|
}
|
|
1755
1893
|
if (senderStatus.ownerId !== address.value) {
|
|
1756
|
-
throw
|
|
1757
|
-
|
|
1758
|
-
|
|
1894
|
+
throw sessionMismatchError({
|
|
1895
|
+
currentSession: String(senderStatus.ownerId),
|
|
1896
|
+
expectedSession: address.value,
|
|
1897
|
+
run: sender.value,
|
|
1898
|
+
target: `session:${address.value}`,
|
|
1899
|
+
});
|
|
1759
1900
|
}
|
|
1760
1901
|
}
|
|
1761
1902
|
result = AsyncRuns.appendRunOutboxEvent(sender.value, {
|
|
@@ -1779,18 +1920,22 @@ export function createActorMessageToolDefinition<TContext = unknown>(
|
|
|
1779
1920
|
`message currently supports run:<id>, branch:<run>/<branch>, room:<run>, tool:<name>, coordinator, and session:<id> destinations; unsupported destination: ${message.to}`,
|
|
1780
1921
|
);
|
|
1781
1922
|
}
|
|
1923
|
+
const nextActions = actorMessageNextActions(message, result);
|
|
1924
|
+
const resultWithNext = nextActions.length
|
|
1925
|
+
? { ...result, next_actions: nextActions }
|
|
1926
|
+
: result;
|
|
1782
1927
|
return {
|
|
1783
1928
|
content: [
|
|
1784
1929
|
{
|
|
1785
1930
|
type: "text" as const,
|
|
1786
1931
|
text: maybeJsonText(
|
|
1787
|
-
{ message, result },
|
|
1932
|
+
{ message, result: resultWithNext },
|
|
1788
1933
|
input.verbose === true,
|
|
1789
|
-
compactActorMessageResult(message,
|
|
1934
|
+
compactActorMessageResult(message, resultWithNext),
|
|
1790
1935
|
),
|
|
1791
1936
|
},
|
|
1792
1937
|
],
|
|
1793
|
-
details: { message, result },
|
|
1938
|
+
details: { message, result: resultWithNext },
|
|
1794
1939
|
};
|
|
1795
1940
|
},
|
|
1796
1941
|
};
|
package/package.json
CHANGED
package/skills/actors/SKILL.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: actors
|
|
3
|
-
description:
|
|
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)
|
|
9
9
|
|
|
10
|
-
`pi-actors` turns trusted local capabilities into addressable actors. This skill is the
|
|
10
|
+
`pi-actors` turns trusted local capabilities into addressable actors. This skill is the required practical layer for any non-trivial pi-actors use or repo change: tools, nouns, lifecycle, message protocol, recipes, and common edge cases. It is not a multi-agent strategy guide; use a swarm skill for decomposition, quorum design, reviewer lenses, and consensus methodology.
|
|
11
11
|
|
|
12
12
|
Maintain this skill as the extension's agent-facing manual. When implementation changes reveal new durable mechanics, invariants, warnings, or safer operating patterns, update this skill alongside code/docs so future agents learn the current actor model instead of rediscovering it.
|
|
13
13
|
|
|
@@ -39,15 +39,16 @@ 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
|
|
|
50
|
+
Actor-mode trigger: if work may outlive this turn, needs steering/follow-up/artifacts, runs as a service, fans out, or should be resumed/inspected later, use `spawn → message → inspect` instead of ad hoc shell backgrounding. Keep short foreground checks as normal tools.
|
|
51
|
+
|
|
51
52
|
### `spawn` — create a run actor
|
|
52
53
|
|
|
53
54
|
Use for long work, background services, subagents, fanout, pipelines, and reusable recipes.
|
|
@@ -84,8 +85,9 @@ Envelope fields:
|
|
|
84
85
|
|
|
85
86
|
- Required: `to`, `type`.
|
|
86
87
|
- Useful: `summary`, `body`, `from`, `reply_to`, `correlation_id`, `metadata`.
|
|
87
|
-
-
|
|
88
|
-
-
|
|
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>`).
|
|
89
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`.
|
|
90
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`.
|
|
91
93
|
|
|
@@ -97,12 +99,7 @@ Check `inspect view=mailbox` before domain-specific messages.
|
|
|
97
99
|
{ "target": "run:repo-health", "view": "status" }
|
|
98
100
|
{ "target": "run:repo-health", "view": "tail", "lines": "80" }
|
|
99
101
|
{ "target": "run:repo-health", "view": "messages" }
|
|
100
|
-
{ "target": "run:repo-health", "view": "communication" }
|
|
101
102
|
{ "target": "run:repo-health", "view": "artifacts" }
|
|
102
|
-
{ "target": "room:repo-health", "view": "status" }
|
|
103
|
-
{ "target": "room:repo-health", "view": "roster" }
|
|
104
|
-
{ "target": "room:repo-health", "view": "contacts" }
|
|
105
|
-
{ "target": "room:repo-health", "view": "previews" }
|
|
106
103
|
{ "target": "tool:pi-actors", "view": "status" }
|
|
107
104
|
{ "target": "tool:music_player", "view": "status" }
|
|
108
105
|
{ "target": "recipes", "view": "status" }
|
|
@@ -114,10 +111,10 @@ Views:
|
|
|
114
111
|
- `status`: lifecycle, pid, values, progress, result, compact summary.
|
|
115
112
|
- `tail`: recent stdout/stderr/log tail.
|
|
116
113
|
- `messages`: actor messages emitted by the run, or room timeline entries for `room:*`.
|
|
117
|
-
- `communication`: run/branch
|
|
118
|
-
- `roster`: room member list with address, role, parent, caps, claim, status, and last seen.
|
|
119
|
-
- `contacts`: roster-derived direct-message targets without full roster metadata.
|
|
120
|
-
- `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.
|
|
121
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.
|
|
122
119
|
- `files`: run state directory file list.
|
|
123
120
|
- `artifacts`: declared artifact paths/status.
|
|
@@ -216,13 +213,13 @@ Only matching filename ids compete. Higher priority shadows lower priority; with
|
|
|
216
213
|
Muscle-memory lens: pi-actors has two durable executable-memory layers.
|
|
217
214
|
|
|
218
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.
|
|
219
|
-
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`.
|
|
220
217
|
|
|
221
|
-
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.
|
|
222
219
|
|
|
223
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.
|
|
224
221
|
|
|
225
|
-
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.
|
|
226
223
|
|
|
227
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.
|
|
228
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.
|