@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/dist/lib/tools.js
CHANGED
|
@@ -128,12 +128,23 @@ function asRecord(value) {
|
|
|
128
128
|
function formatFailureCount(value) {
|
|
129
129
|
return Array.isArray(value) ? value.length : undefined;
|
|
130
130
|
}
|
|
131
|
+
function actorRunNextActions(run) {
|
|
132
|
+
const id = String(run ?? "").trim();
|
|
133
|
+
if (!id)
|
|
134
|
+
return [];
|
|
135
|
+
return [
|
|
136
|
+
`inspect target=run:${id} view=status`,
|
|
137
|
+
`inspect target=run:${id} view=messages`,
|
|
138
|
+
`message to=run:${id} type=<actor.action>`,
|
|
139
|
+
];
|
|
140
|
+
}
|
|
131
141
|
function compactAsyncRunStatus(value) {
|
|
132
142
|
const status = asRecord(value);
|
|
133
143
|
const progress = asRecord(status.progress);
|
|
134
144
|
const result = asRecord(status.result);
|
|
145
|
+
const run = String(status.run ?? "<unknown>");
|
|
135
146
|
const tokens = [
|
|
136
|
-
`run=${
|
|
147
|
+
`run=${run}`,
|
|
137
148
|
`status=${String(status.status ?? "unknown")}`,
|
|
138
149
|
];
|
|
139
150
|
if (status.tool)
|
|
@@ -157,8 +168,12 @@ function compactAsyncRunStatus(value) {
|
|
|
157
168
|
tokens.push(`code=${String(result.code)}`);
|
|
158
169
|
if (result.killed === true)
|
|
159
170
|
tokens.push("killed=true");
|
|
160
|
-
|
|
161
|
-
|
|
171
|
+
const draftRecipe = status.draft_recipe ?? status.candidate_recipe;
|
|
172
|
+
if (draftRecipe)
|
|
173
|
+
tokens.push(`draft_recipe=${String(draftRecipe)}`);
|
|
174
|
+
const nextActions = actorRunNextActions(run);
|
|
175
|
+
if (nextActions.length > 0)
|
|
176
|
+
tokens.push(`next=${nextActions.map((action) => action.replaceAll(/\s+/g, "_")).join("|")}`);
|
|
162
177
|
return `\n${tokens.join(" ")}`;
|
|
163
178
|
}
|
|
164
179
|
function compactRunMessages(messages) {
|
|
@@ -343,6 +358,15 @@ function compactArtifactPath(value) {
|
|
|
343
358
|
const record = asRecord(value);
|
|
344
359
|
return String(record.path ?? "<missing>");
|
|
345
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
|
+
}
|
|
346
370
|
function compactActorFiles(status) {
|
|
347
371
|
const run = String(status.run ?? "<unknown>");
|
|
348
372
|
const artifacts = asRecord(status.artifacts);
|
|
@@ -361,7 +385,11 @@ function compactActorFiles(status) {
|
|
|
361
385
|
.map(([key, value]) => `${key}:${compactArtifactPath(value)}`)
|
|
362
386
|
.join(",")}`
|
|
363
387
|
: "";
|
|
364
|
-
|
|
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}`;
|
|
365
393
|
}
|
|
366
394
|
function summarizeOtherSessions(currentSession, allRuns) {
|
|
367
395
|
const otherRuns = allRuns.filter((run) => run.ownerId && run.ownerId !== currentSession);
|
|
@@ -481,8 +509,42 @@ function compactRecipeDoctor(summary) {
|
|
|
481
509
|
: "";
|
|
482
510
|
lines.push(`${String(item.severity ?? "info")} kind=${String(item.kind ?? "inspect")} id=${String(item.id ?? "root")}${blocked} action=${action ?? "inspect"}`);
|
|
483
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)}`;
|
|
484
517
|
return `\n${lines.join("\n")}`;
|
|
485
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
|
+
}
|
|
486
548
|
function compactRecipeRegistry(summary) {
|
|
487
549
|
const active = Array.isArray(summary.active) ? summary.active.length : 0;
|
|
488
550
|
const shadowed = Array.isArray(summary.shadowed)
|
|
@@ -495,13 +557,41 @@ function compactRecipeRegistry(summary) {
|
|
|
495
557
|
const diagnostics = Array.isArray(summary.diagnostics)
|
|
496
558
|
? summary.diagnostics.length
|
|
497
559
|
: 0;
|
|
498
|
-
const
|
|
499
|
-
? summary.
|
|
500
|
-
:
|
|
560
|
+
const drafts = Array.isArray(summary.drafts)
|
|
561
|
+
? summary.drafts.length
|
|
562
|
+
: Array.isArray(summary.candidates)
|
|
563
|
+
? summary.candidates.length
|
|
564
|
+
: 0;
|
|
501
565
|
const recommendations = Array.isArray(summary.recommendations)
|
|
502
566
|
? summary.recommendations.length
|
|
503
567
|
: 0;
|
|
504
|
-
|
|
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);
|
|
505
595
|
}
|
|
506
596
|
function compactActorMessageResult(message, result) {
|
|
507
597
|
const tokens = [
|
|
@@ -534,6 +624,11 @@ function compactActorMessageResult(message, result) {
|
|
|
534
624
|
if (result.delivery_error) {
|
|
535
625
|
tokens.push(`delivery_error=${compactPreview(result.delivery_error, 96)}`);
|
|
536
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("|")}`);
|
|
537
632
|
return `\n${tokens.join(" ")}`;
|
|
538
633
|
}
|
|
539
634
|
function maybeJsonText(value, verbose, compact) {
|
|
@@ -635,7 +730,7 @@ function writeSpawnCandidateRecipe(input, meta) {
|
|
|
635
730
|
const defaults = candidateRecipeDefaults(meta.values);
|
|
636
731
|
const recipe = {
|
|
637
732
|
async: true,
|
|
638
|
-
description: `
|
|
733
|
+
description: `Draft recipe captured from spawn run ${String(meta.run)}`,
|
|
639
734
|
...(meta.artifacts ? { artifacts: meta.artifacts } : {}),
|
|
640
735
|
...(defaults ? { defaults } : {}),
|
|
641
736
|
template: input.template,
|
|
@@ -720,7 +815,7 @@ export function createSpawnToolDefinition() {
|
|
|
720
815
|
return {
|
|
721
816
|
name: "spawn",
|
|
722
817
|
label: "Spawn",
|
|
723
|
-
description: "Create an addressable actor from a recipe file or inline command template. Currently spawns run:<id> actors backed by async runs.",
|
|
818
|
+
description: "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.",
|
|
724
819
|
parameters: objectSchema({
|
|
725
820
|
artifacts: looseObjectSchema("Optional named artifact paths for the spawned actor."),
|
|
726
821
|
as: stringSchema("Optional actor address for the spawned run, e.g. run:<id>."),
|
|
@@ -770,9 +865,14 @@ export function createSpawnToolDefinition() {
|
|
|
770
865
|
throw enhanceSpawnRecipeError(error, recipe);
|
|
771
866
|
}
|
|
772
867
|
const candidateRecipe = writeSpawnCandidateRecipe(input, meta);
|
|
773
|
-
const
|
|
774
|
-
|
|
775
|
-
|
|
868
|
+
const nextActions = actorRunNextActions(meta.run);
|
|
869
|
+
const details = {
|
|
870
|
+
...meta,
|
|
871
|
+
...(candidateRecipe
|
|
872
|
+
? { candidate_recipe: candidateRecipe, draft_recipe: candidateRecipe }
|
|
873
|
+
: {}),
|
|
874
|
+
next_actions: nextActions,
|
|
875
|
+
};
|
|
776
876
|
ActorRooms.ensureDefaultRoom(meta.state_dir, String(meta.run));
|
|
777
877
|
ActorRooms.writeCommunicationSnapshot(meta.state_dir, String(meta.run));
|
|
778
878
|
return {
|
|
@@ -797,15 +897,29 @@ function requireContextSessionId(ctx, actor) {
|
|
|
797
897
|
}
|
|
798
898
|
return sessionId;
|
|
799
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
|
+
}
|
|
800
916
|
function assertRunAccessibleToContext(runId, ctx) {
|
|
801
917
|
const status = AsyncRuns.getRunStatus(runId);
|
|
802
918
|
const sessionId = getContextSessionId(ctx);
|
|
803
919
|
if (sessionId && status.ownerId && status.ownerId !== sessionId) {
|
|
804
|
-
throw
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
owner_session: status.ownerId,
|
|
808
|
-
reason: "session_mismatch",
|
|
920
|
+
throw sessionMismatchError({
|
|
921
|
+
currentSession: sessionId,
|
|
922
|
+
expectedSession: String(status.ownerId),
|
|
809
923
|
run: runId,
|
|
810
924
|
});
|
|
811
925
|
}
|
|
@@ -818,13 +932,13 @@ export function createInspectToolDefinition(deps = {}) {
|
|
|
818
932
|
return {
|
|
819
933
|
name: "inspect",
|
|
820
934
|
label: "Inspect",
|
|
821
|
-
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.",
|
|
822
936
|
parameters: objectSchema({
|
|
823
937
|
lines: stringSchema("Line count for tail/messages views. Default 40."),
|
|
824
938
|
status: stringSchema("Optional session run filter: all, running, active, terminal, done, failed, cancelled, killed, or exited."),
|
|
825
|
-
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."),
|
|
826
940
|
verbose: booleanSchema("Return full JSON instead of compact text where available."),
|
|
827
|
-
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."),
|
|
828
942
|
}, ["target", "view"]),
|
|
829
943
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
830
944
|
const input = asRecord(params);
|
|
@@ -846,10 +960,15 @@ export function createInspectToolDefinition(deps = {}) {
|
|
|
846
960
|
{ root: deps.packagedRecipeRoot ?? Paths.getPackagedRecipeRoot() },
|
|
847
961
|
]);
|
|
848
962
|
const recipeRoot = deps.recipeRoot ?? Paths.getRecipeRoot();
|
|
849
|
-
const
|
|
963
|
+
const summaryBase = {
|
|
850
964
|
...RecipeDiscovery.summarizeDiscovery(discovered),
|
|
965
|
+
drafts: RecipeDiscovery.listCandidateRecipes(join(recipeRoot, "candidates")),
|
|
851
966
|
candidates: RecipeDiscovery.listCandidateRecipes(join(recipeRoot, "candidates")),
|
|
852
967
|
};
|
|
968
|
+
const summary = {
|
|
969
|
+
...summaryBase,
|
|
970
|
+
next_actions: recipeRegistryNextActions(summaryBase, view),
|
|
971
|
+
};
|
|
853
972
|
return {
|
|
854
973
|
content: [
|
|
855
974
|
{
|
|
@@ -1067,7 +1186,11 @@ export function createInspectToolDefinition(deps = {}) {
|
|
|
1067
1186
|
const status = assertRunAccessibleToContext(runId, ctx);
|
|
1068
1187
|
const artifactManifest = AsyncRuns.resolveArtifactManifest(status.artifacts);
|
|
1069
1188
|
const details = artifactManifest
|
|
1070
|
-
? {
|
|
1189
|
+
? {
|
|
1190
|
+
...status,
|
|
1191
|
+
artifact_manifest: artifactManifest,
|
|
1192
|
+
next_actions: artifactNextActions(status.run ?? runId, asRecord(status.artifacts)),
|
|
1193
|
+
}
|
|
1071
1194
|
: status;
|
|
1072
1195
|
return {
|
|
1073
1196
|
content: [
|
|
@@ -1122,7 +1245,7 @@ export function createActorMessageToolDefinition(deps = {}) {
|
|
|
1122
1245
|
return {
|
|
1123
1246
|
name: "message",
|
|
1124
1247
|
label: "Message",
|
|
1125
|
-
description: "Send one typed addressed message.
|
|
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>.",
|
|
1126
1249
|
parameters: objectSchema({
|
|
1127
1250
|
body: unionSchema([
|
|
1128
1251
|
stringSchema("Message body. For run:<id>, this is the run-local command line."),
|
|
@@ -1134,7 +1257,7 @@ export function createActorMessageToolDefinition(deps = {}) {
|
|
|
1134
1257
|
metadata: looseObjectSchema("Optional structured metadata for routing or domain hints."),
|
|
1135
1258
|
reply_to: stringSchema("Optional message id this message replies to."),
|
|
1136
1259
|
summary: stringSchema("Optional short human-facing summary."),
|
|
1137
|
-
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>."),
|
|
1138
1261
|
type: stringSchema("Semantic message type, e.g. control.approve or checkpoint.needs_scope."),
|
|
1139
1262
|
verbose: booleanSchema("Return full JSON instead of compact text."),
|
|
1140
1263
|
}, ["to", "type"]),
|
|
@@ -1249,10 +1372,20 @@ export function createActorMessageToolDefinition(deps = {}) {
|
|
|
1249
1372
|
const senderStatus = assertRunAccessibleToContext(sender.value, ctx);
|
|
1250
1373
|
if (address.kind === "session") {
|
|
1251
1374
|
if (!senderStatus.ownerId) {
|
|
1252
|
-
throw
|
|
1375
|
+
throw sessionMismatchError({
|
|
1376
|
+
currentSession: undefined,
|
|
1377
|
+
expectedSession: address.value,
|
|
1378
|
+
run: sender.value,
|
|
1379
|
+
target: `session:${address.value}`,
|
|
1380
|
+
});
|
|
1253
1381
|
}
|
|
1254
1382
|
if (senderStatus.ownerId !== address.value) {
|
|
1255
|
-
throw
|
|
1383
|
+
throw sessionMismatchError({
|
|
1384
|
+
currentSession: String(senderStatus.ownerId),
|
|
1385
|
+
expectedSession: address.value,
|
|
1386
|
+
run: sender.value,
|
|
1387
|
+
target: `session:${address.value}`,
|
|
1388
|
+
});
|
|
1256
1389
|
}
|
|
1257
1390
|
}
|
|
1258
1391
|
result = AsyncRuns.appendRunOutboxEvent(sender.value, {
|
|
@@ -1274,14 +1407,18 @@ export function createActorMessageToolDefinition(deps = {}) {
|
|
|
1274
1407
|
else {
|
|
1275
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}`);
|
|
1276
1409
|
}
|
|
1410
|
+
const nextActions = actorMessageNextActions(message, result);
|
|
1411
|
+
const resultWithNext = nextActions.length
|
|
1412
|
+
? { ...result, next_actions: nextActions }
|
|
1413
|
+
: result;
|
|
1277
1414
|
return {
|
|
1278
1415
|
content: [
|
|
1279
1416
|
{
|
|
1280
1417
|
type: "text",
|
|
1281
|
-
text: maybeJsonText({ message, result }, input.verbose === true, compactActorMessageResult(message,
|
|
1418
|
+
text: maybeJsonText({ message, result: resultWithNext }, input.verbose === true, compactActorMessageResult(message, resultWithNext)),
|
|
1282
1419
|
},
|
|
1283
1420
|
],
|
|
1284
|
-
details: { message, result },
|
|
1421
|
+
details: { message, result: resultWithNext },
|
|
1285
1422
|
};
|
|
1286
1423
|
},
|
|
1287
1424
|
};
|
|
@@ -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
|
|
|
@@ -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/async-runs.md
CHANGED
|
@@ -6,6 +6,8 @@ Async runs are detached executions of a template recipe or inline command templa
|
|
|
6
6
|
|
|
7
7
|
**Scope:** run id, state path, runner pid, process-group cancellation, logs, status, tail, list, script-authored actor messages, run-local control messages, cancel, force-kill, terminal result state, ambient activity indicators, and extension-owned temp storage. No scheduler, queue daemon, workflow DSL, distributed worker, or second execution language.
|
|
8
8
|
|
|
9
|
+
Actor-mode trigger: choose an async run when work may outlive the current turn, needs later steering or inspection, produces artifacts/follow-ups, runs as a service, fans out, or should become repeatable recipe memory. Keep short foreground checks in ordinary tools/templates.
|
|
10
|
+
|
|
9
11
|
Layer boundary: async-run configuration may inject lifecycle values such as `{run_id}` and `{state_dir}` and may choose detached execution through `async: true`, but it does not add command-template graph syntax. Recipe imports and recipe-local references belong to the template-recipe layer; status, control messages, actor messages, cancel, and kill belong to the async-run layer.
|
|
10
12
|
|
|
11
13
|
---
|
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/prompts.ts
CHANGED
|
@@ -27,12 +27,12 @@ export const ONBOARDING_SYSTEM_PROMPT = `pi-actors quick model:
|
|
|
27
27
|
- ~/.pi/agent/recipes/*.json is actor muscle memory: every recipe there is auto-registered as an agent tool across sessions; register_tool writes there.
|
|
28
28
|
- Recipes own template directly and may declare metadata/defaults/imports/mailbox/artifacts; files >1 MiB or import depth >32 fail closed.
|
|
29
29
|
- Recipe imports are local variables; imported recipes are definitions, not nested async runs; parent async:true creates one run.
|
|
30
|
-
-
|
|
30
|
+
- Actor-mode trigger: if work may outlive this turn, need steering/follow-up/artifacts, run as a service, fan out, or be resumed/inspected later, use spawn -> message -> inspect instead of ad hoc shell backgrounding.
|
|
31
|
+
- Use spawn/message/inspect for actor-level start/send/observe; short foreground checks can stay ordinary tools/templates; avoid runtime/FIFO/outbox vocabulary in public guidance.
|
|
31
32
|
- Run state lives under ~/.pi/agent/tmp/pi-actors/runs; inspect status/tail/messages/mailbox/files/artifacts intentionally and avoid busy-polling.
|
|
32
33
|
- Maintain ~/.pi/agent/recipes like MEMORY.md for capabilities: keep useful tools, curate stale ones, and fix/remove/disable invalid recipes flagged by registry warnings; packaged/ad hoc recipes are lower-priority components; offer to save successful recurring patterns only after confirmation.
|
|
33
|
-
- Foreground tools/templates fit short work; async recipes/runs fit subagents, services, fanout, media, and long pipelines.
|
|
34
34
|
- Long fanout = parent async recipe wrapping template(parallel:true) and imports; packaged fanout recipes bubble branch completion messages; grow recurring multi-agent workflows as packaged recipes/pipelines, not ad hoc external scripts.
|
|
35
|
-
- For
|
|
35
|
+
- For any non-trivial actor use or pi-actors change, read the bundled actors skill first; for deeper guidance, inspect installed extension sources/docs/recipes because README/docs are not automatically in context.`;
|
|
36
36
|
|
|
37
37
|
export const REGISTER_TOOL_PARAM_DESCRIPTIONS = {
|
|
38
38
|
name: "Tool name in snake_case (e.g., 'transcribe')",
|