@llblab/pi-actors 0.26.3 → 0.27.1

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/BACKLOG.md CHANGED
@@ -225,12 +225,78 @@ The backlog is intentionally pruned to the 20% of work most likely to deliver 80
225
225
  - Packaged recipes declare `stop`/`cancel` only when the actor-specific behavior is meaningful.
226
226
  - Tests assert that generic mailbox-loop termination is not triggered by `control.stop` or `control.cancel`.
227
227
 
228
+ ### M-12 Runtime and Session Observability UX
229
+
230
+ - Priority: High.
231
+ - Status: Done.
232
+ - Goal: Make reload/session/runtime mismatches visible without changing ownership or lifecycle policy.
233
+ - Why now: 0.26 dogfood showed that actors, mailbox workers, and hotfixes work after a full Pi restart, but ordinary reloads can leave operators unsure which extension code is live. Session ownership mismatches also surface as terse strings instead of structured diagnostics or navigation hints.
234
+ - Direction:
235
+ - Add an intentional runtime/version inspection surface that reports loaded package version, entrypoint path, source/dist mode, package root, recipe roots, and git commit when available.
236
+ - Make session ownership denials structured in tool details with compact `reason=session_mismatch owner_session=... current_session=...` text and hints to inspect the owning session.
237
+ - Make coordinator/session status show when other sessions or other-session runs exist so `runs=0` is not misleading after reload or resume.
238
+ - Document reload vs full restart verification in the actors/swarm guidance.
239
+ - Opportunistically improve invalid shadowing recipe launch hints only if it stays diagnostic-only.
240
+ - Non-goals:
241
+ - No cross-session force kill.
242
+ - No session attach/adopt/reparent policy.
243
+ - No relaxation of current ownership gates.
244
+ - Acceptance:
245
+ - Operators can verify the loaded pi-actors version/path from an inspect/tool surface after reload.
246
+ - Session mismatch responses carry structured details and a compact human hint.
247
+ - Coordinator/session status exposes other-session counts without leaking unrelated run details by default.
248
+ - Tests cover version inspection, session mismatch shape, and other-session count summaries.
249
+
250
+ ### M-13 Shadowed Recipe Launch UX
251
+
252
+ - Priority: High.
253
+ - Status: Planned.
254
+ - Goal: Make broken user recipes that shadow packaged/ad hoc candidates obvious at launch time.
255
+ - Why now: 0.26 dogfood found a broken `~/.pi/agent/recipes/actor-worker.json` shadowing the packaged `actor-worker`; Recipe Doctor exposed the evidence, but the launch path did not provide a direct hint.
256
+ - Direction:
257
+ - When recipe resolution or launch fails because the active user recipe is invalid/disabled and a lower-priority candidate exists, surface `reason=shadowed_invalid` or `reason=shadowed_disabled` where practical.
258
+ - Include active path, blocked candidate path, and compact hint: `inspect target=recipes view=doctor`.
259
+ - Keep remediation advisory only; do not auto-disable, delete, or rewrite user recipes.
260
+ - Acceptance:
261
+ - Launch failures caused by shadowing include a compact actionable hint.
262
+ - Verbose details expose the active broken recipe and blocked fallback candidate.
263
+ - Tests cover invalid and disabled user recipes shadowing a packaged candidate.
264
+
265
+ ### M-14 Session Mismatch Follow-through
266
+
267
+ - Priority: Medium.
268
+ - Status: Planned.
269
+ - Goal: Extend 0.27 structured session diagnostics consistently across room, branch, run, coordinator, and session workflows.
270
+ - Why now: M-12 established the shape; dogfood should now make every ownership denial equally actionable without relaxing ownership gates.
271
+ - Direction:
272
+ - Audit all session mismatch errors for consistent `reason`, owner/current session fields, and inspect-session hints.
273
+ - Keep read/write ownership policy unchanged.
274
+ - Update docs with session mismatch examples and recovery inspection paths.
275
+ - Acceptance:
276
+ - Room, branch, run, coordinator, and session denials share the same compact/verbose shape.
277
+ - Tests cover representative inspect and message paths.
278
+
279
+ ### M-15 Worker Stale-Claim Dogfood
280
+
281
+ - Priority: Medium.
282
+ - Status: Planned.
283
+ - Goal: Validate and harden actor-worker v2 stale-claim visibility under intentionally stale claimed branch messages.
284
+ - Why now: M-09 exposed `stale_claims`; real dogfood should verify the operator can diagnose stuck claimed work before adding recovery policy.
285
+ - Direction:
286
+ - Create deterministic stale claimed branch inbox fixtures or smoke tests.
287
+ - Verify `worker-status.json`, room events, and inspect surfaces make stale claims visible.
288
+ - Defer auto-recovery unless workflow evidence proves it is safe.
289
+ - Acceptance:
290
+ - Stale claims are reproducible and visible in worker status.
291
+ - Tests cover stale-claim counting without adding scheduler/broker policy.
292
+
228
293
  ## Explicitly Deferred
229
294
 
230
295
  These are valid ideas but not current focus. Reintroduce only with concrete evidence from real actor workflows.
231
296
 
232
297
  - Spawn preflight mode: useful later, but lower value than resilient inspect and mailbox-loop consolidation.
233
298
  - Run restart/reattach policy: risky for isolation; defer until corruption recovery and protocol fixtures are stronger.
299
+ - Cross-session force kill or attach/adopt/reparent: useful later, but ownership policy should not change until observability makes current boundaries clear.
234
300
  - Actor address helper CLI: keep diagnostics improving opportunistically inside existing parser/tests.
235
301
  - Documentation refactor: defer until the canonical mailbox loop and worker recipe exist; avoid rewriting docs twice.
236
302
  - Host-level tool unregistration: blocked on host API support.
@@ -240,5 +306,5 @@ These are valid ideas but not current focus. Reintroduce only with concrete evid
240
306
  ## Suggested Milestone Order
241
307
 
242
308
  ```text
243
- Next milestone: choose from deferred items only after concrete actor workflow evidence appears.
309
+ Next milestone: M-13 Shadowed Recipe Launch UX.
244
310
  ```
package/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.27.1: Changelog and Backlog Hotfix
6
+
7
+ - `[Changelog]` Moved the 0.27.0 runtime/session observability notes out of `Unreleased` into a proper release section so published package history matches the npm/tag release.
8
+ - `[Backlog]` Marked M-12 complete and added the next evidence-backed candidates for shadowed recipe launch UX, session mismatch follow-through, and worker stale-claim dogfood.
9
+
10
+ ## 0.27.0: Runtime and Session Observability UX
11
+
12
+ - `[Inspect]` Added `inspect target=tool:pi-actors view=status` as a runtime verification surface with loaded version, package root, source/dist mode, entrypoint path, recipe roots, and git commit when available.
13
+ - `[Sessions]` Started structured session mismatch diagnostics with `reason=session_mismatch`, owner/current session fields, and compact inspect-session hints while preserving current ownership gates.
14
+ - `[Sessions]` Added other-session run counts to coordinator/session status summaries so reload/resume states do not misleadingly report only `runs=0` without nearby context.
15
+
5
16
  ## 0.26.3: Branch Message Delivery UX Hotfix
6
17
 
7
18
  - `[Actor Messages]` Treat branch mailbox persistence as a successful queued outcome even when the parent run control endpoint is unavailable, returning compact `queued=true` delivery diagnostics instead of throwing an unframed tool error.
package/dist/lib/tools.js CHANGED
@@ -3,6 +3,9 @@
3
3
  * Zones: pi tools, registry tools, async run launchers
4
4
  * Owns generated runtime tool schemas and the register_tool management tool schema
5
5
  */
6
+ import { execFileSync } from "node:child_process";
7
+ import { existsSync, readFileSync } from "node:fs";
8
+ import { dirname, join } from "node:path";
6
9
  import * as ActorMessages from "./actor-messages.js";
7
10
  import * as ActorRooms from "./actor-rooms.js";
8
11
  import * as AsyncRuns from "./async-runs.js";
@@ -345,10 +348,21 @@ function compactActorFiles(status) {
345
348
  : "";
346
349
  return `\nrun=${run}${artifactText}${files.length ? ` files=${files.join(",")}` : ""}`;
347
350
  }
348
- function compactSessionRuns(session, runs) {
351
+ function summarizeOtherSessions(currentSession, allRuns) {
352
+ const otherRuns = allRuns.filter((run) => run.ownerId && run.ownerId !== currentSession);
353
+ return {
354
+ other_runs: otherRuns.length,
355
+ other_sessions: new Set(otherRuns.map((run) => run.ownerId)).size,
356
+ };
357
+ }
358
+ function compactSessionRuns(session, runs, summary = {}) {
359
+ const suffix = [
360
+ summary.other_sessions !== undefined ? `other_sessions=${String(summary.other_sessions)}` : "",
361
+ summary.other_runs !== undefined ? `other_runs=${String(summary.other_runs)}` : "",
362
+ ].filter(Boolean).join(" ");
349
363
  if (runs.length === 0)
350
- return `\nsession=${session} runs=0`;
351
- return `\nsession=${session} runs=${runs.length}\n${runs
364
+ return `\nsession=${session} runs=0${suffix ? ` ${suffix}` : ""}`;
365
+ return `\nsession=${session} runs=${runs.length}${suffix ? ` ${suffix}` : ""}\n${runs
352
366
  .map((run) => {
353
367
  const tokens = [
354
368
  `run=${String(run.run ?? "")}`,
@@ -362,6 +376,38 @@ function compactSessionRuns(session, runs) {
362
376
  })
363
377
  .join("\n")}`;
364
378
  }
379
+ function getPiActorsRuntimeStatus() {
380
+ const packagedRecipeRoot = Paths.getPackagedRecipeRoot();
381
+ const packageRoot = dirname(packagedRecipeRoot);
382
+ const packageJsonPath = join(packageRoot, "package.json");
383
+ const packageJson = existsSync(packageJsonPath)
384
+ ? JSON.parse(readFileSync(packageJsonPath, "utf8"))
385
+ : {};
386
+ let git_commit;
387
+ try {
388
+ git_commit = execFileSync("git", ["-C", packageRoot, "rev-parse", "--short", "HEAD"], {
389
+ encoding: "utf8",
390
+ stdio: ["ignore", "pipe", "ignore"],
391
+ }).trim();
392
+ }
393
+ catch {
394
+ git_commit = undefined;
395
+ }
396
+ const entrypoint = new URL(import.meta.url).pathname;
397
+ return {
398
+ entrypoint,
399
+ git_commit,
400
+ mode: entrypoint.includes("/dist/") ? "dist" : "source",
401
+ package_name: packageJson.name ?? "@llblab/pi-actors",
402
+ package_root: packageRoot,
403
+ recipe_root: Paths.getRecipeRoot(),
404
+ packaged_recipe_root: packagedRecipeRoot,
405
+ version: packageJson.version ?? "unknown",
406
+ };
407
+ }
408
+ function compactPiActorsRuntimeStatus(status) {
409
+ return `\npi-actors version=${String(status.version)} mode=${String(status.mode)} path=${String(status.package_root)} entrypoint=${String(status.entrypoint)}${status.git_commit ? ` git=${String(status.git_commit)}` : ""}`;
410
+ }
365
411
  function compactToolActor(name, tool) {
366
412
  const parameters = asRecord(tool.parameters);
367
413
  const required = Array.isArray(parameters.required)
@@ -660,7 +706,13 @@ function assertRunAccessibleToContext(runId, ctx) {
660
706
  const status = AsyncRuns.getRunStatus(runId);
661
707
  const sessionId = getContextSessionId(ctx);
662
708
  if (sessionId && status.ownerId && status.ownerId !== sessionId) {
663
- throw new Error(`run:${runId} is owned by session:${status.ownerId}; current session is ${sessionId}.`);
709
+ throw Object.assign(new Error(`run:${runId} reason=session_mismatch owner_session=${status.ownerId} current_session=${sessionId} hint=inspect_session:${status.ownerId}`), {
710
+ current_session: sessionId,
711
+ hint: `inspect target=session:${status.ownerId} view=status`,
712
+ owner_session: status.ownerId,
713
+ reason: "session_mismatch",
714
+ run: runId,
715
+ });
664
716
  }
665
717
  return status;
666
718
  }
@@ -716,37 +768,54 @@ export function createInspectToolDefinition(deps = {}) {
716
768
  throw new Error("inspect coordinator supports view=status or view=runs.");
717
769
  }
718
770
  const session = requireContextSessionId(ctx, "inspect coordinator");
719
- const runs = AsyncRuns.listRuns(undefined, typeof input.status === "string" ? input.status : undefined)
720
- .map((run) => AsyncRuns.getRunStatus(String(run.state_dir)))
721
- .filter((run) => run.ownerId === session);
771
+ const allRuns = AsyncRuns.listRuns(undefined, typeof input.status === "string" ? input.status : undefined).map((run) => AsyncRuns.getRunStatus(String(run.state_dir)));
772
+ const runs = allRuns.filter((run) => run.ownerId === session);
773
+ const sessionSummary = summarizeOtherSessions(session, allRuns);
722
774
  return {
723
775
  content: [
724
776
  {
725
777
  type: "text",
726
- text: maybeJsonText({ session, runs }, input.verbose === true, compactSessionRuns(session, runs)),
778
+ text: maybeJsonText({ session, runs, ...sessionSummary }, input.verbose === true, compactSessionRuns(session, runs, sessionSummary)),
727
779
  },
728
780
  ],
729
- details: { session, runs },
781
+ details: { session, runs, ...sessionSummary },
730
782
  };
731
783
  }
732
784
  if (address.kind === "session") {
733
785
  if (view !== "status" && view !== "runs") {
734
786
  throw new Error("inspect session:<id> supports view=status or view=runs.");
735
787
  }
736
- const runs = AsyncRuns.listRuns(undefined, typeof input.status === "string" ? input.status : undefined)
737
- .map((run) => AsyncRuns.getRunStatus(String(run.state_dir)))
738
- .filter((run) => address.value === "all" || run.ownerId === address.value);
788
+ const allRuns = AsyncRuns.listRuns(undefined, typeof input.status === "string" ? input.status : undefined).map((run) => AsyncRuns.getRunStatus(String(run.state_dir)));
789
+ const runs = allRuns.filter((run) => address.value === "all" || run.ownerId === address.value);
790
+ const sessionSummary = address.value === "all"
791
+ ? {}
792
+ : summarizeOtherSessions(address.value || "", allRuns);
739
793
  return {
740
794
  content: [
741
795
  {
742
796
  type: "text",
743
- text: maybeJsonText({ session: address.value, runs }, input.verbose === true, compactSessionRuns(address.value || "", runs)),
797
+ text: maybeJsonText({ session: address.value, runs, ...sessionSummary }, input.verbose === true, compactSessionRuns(address.value || "", runs, sessionSummary)),
744
798
  },
745
799
  ],
746
- details: { session: address.value, runs },
800
+ details: { session: address.value, runs, ...sessionSummary },
747
801
  };
748
802
  }
749
803
  if (address.kind === "tool" && address.value) {
804
+ if (address.value === "pi-actors") {
805
+ if (view !== "status") {
806
+ throw new Error("inspect tool:pi-actors supports view=status.");
807
+ }
808
+ const details = getPiActorsRuntimeStatus();
809
+ return {
810
+ content: [
811
+ {
812
+ type: "text",
813
+ text: maybeJsonText(details, input.verbose === true, compactPiActorsRuntimeStatus(details)),
814
+ },
815
+ ],
816
+ details,
817
+ };
818
+ }
750
819
  if (view !== "status" && view !== "schema") {
751
820
  throw new Error("inspect tool:<name> supports view=status or view=schema.");
752
821
  }
@@ -2,7 +2,7 @@
2
2
  name: actors
3
3
  description: Highest-density practical guide for pi-actors. Read this skill whenever prompt and tools are not enough for spawn, message, inspect, actor runs, tools, recipes, command templates, async lifecycle, mailboxes, artifacts, and local orchestration mechanics.
4
4
  metadata:
5
- version: 0.26.3
5
+ version: 0.27.1
6
6
  ---
7
7
 
8
8
  # Actors (pi-actors)
@@ -41,7 +41,7 @@ Trusted local capability
41
41
  - **Recipe**: saved JSON definition wrapping a template with args, defaults, imports, mailbox, artifacts, metadata, and optional `async: true`.
42
42
  - **Run actor**: one detached execution instance addressable as `run:<id>` with status, logs, messages, mailbox metadata, files, communication snapshot, and artifacts.
43
43
  - **Room actor**: shared timeline + roster endpoint addressable as `room:<run>`; every spawned run gets `room:<run>`.
44
- - **Tool actor**: registered persistent capability addressable as `tool:<name>` and callable through the generated tool or `message`.
44
+ - **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
45
  - **Coordinator/session**: the current pi session endpoint that receives bounded actor follow-ups.
46
46
  - **Mailbox**: public interaction contract: message types the actor accepts/emits.
47
47
  - **Artifact**: named durable output path declared by a recipe/run.
@@ -102,6 +102,7 @@ Check `inspect view=mailbox` before domain-specific messages.
102
102
  { "target": "room:repo-health", "view": "roster" }
103
103
  { "target": "room:repo-health", "view": "contacts" }
104
104
  { "target": "room:repo-health", "view": "previews" }
105
+ { "target": "tool:pi-actors", "view": "status" }
105
106
  { "target": "tool:music_player", "view": "status" }
106
107
  { "target": "recipes", "view": "status" }
107
108
  { "target": "coordinator", "view": "status" }
@@ -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.26.3
5
+ version: 0.27.1
6
6
  ---
7
7
 
8
8
  # Swarm
@@ -19,15 +19,18 @@ Because the user recipe directory is sticky agent muscle memory, runtime launche
19
19
 
20
20
  `register_tool` is the preferred agent-facing mutation API. It creates, updates, and deletes recipe files in `~/.pi/agent/recipes`; agents do not need to edit the files directly for normal registration. Direct file edits are still valid for operators and advanced agents. Runtime behavior is reactive: file creation, deletion, or edits in the user recipe root trigger validation and tool-set refresh, with invalid recipes surfaced as diagnostics rather than silently ignored.
21
21
 
22
- Inspect the discovered registry with:
22
+ Inspect the loaded pi-actors runtime and discovered registry with:
23
23
 
24
24
  ```text
25
+ inspect target=tool:pi-actors view=status
25
26
  inspect target=recipes view=status
26
27
  inspect target=recipes view=doctor
27
28
  inspect target=recipes view=summary verbose=true
28
29
  ```
29
30
 
30
- The summary reports active, shadowed, invalid, disabled, and diagnostic entries so operators can answer why a tool is present, hidden, broken, or disabled. The doctor view keeps the same registry evidence but promotes an advisory action surface: compact output includes the highest-priority `top` remediation plus ordered actions for invalid/blocking, disabled, risky shell-boundary, and shadowed recipes. Verbose inspection keeps the structured `remediations`, `top_action`, diagnostic details, and blocked lower-priority candidate paths when a broken or disabled higher-priority recipe masks a fallback.
31
+ `tool:pi-actors` is a reserved runtime-status actor: it reports the loaded package version, package root, source/dist mode, entrypoint path, recipe roots, and git commit when available. Use it after reloads to confirm which extension code is actually live.
32
+
33
+ The recipe summary reports active, shadowed, invalid, disabled, and diagnostic entries so operators can answer why a tool is present, hidden, broken, or disabled. The doctor view keeps the same registry evidence but promotes an advisory action surface: compact output includes the highest-priority `top` remediation plus ordered actions for invalid/blocking, disabled, risky shell-boundary, and shadowed recipes. Verbose inspection keeps the structured `remediations`, `top_action`, diagnostic details, and blocked lower-priority candidate paths when a broken or disabled higher-priority recipe masks a fallback.
31
34
 
32
35
  ## Registering Tools
33
36
 
package/lib/tools.ts CHANGED
@@ -4,6 +4,10 @@
4
4
  * Owns generated runtime tool schemas and the register_tool management tool schema
5
5
  */
6
6
 
7
+ import { execFileSync } from "node:child_process";
8
+ import { existsSync, readFileSync } from "node:fs";
9
+ import { dirname, join } from "node:path";
10
+
7
11
  import * as ActorMessages from "./actor-messages.ts";
8
12
  import * as ActorRooms from "./actor-rooms.ts";
9
13
  import * as AsyncRuns from "./async-runs.ts";
@@ -424,12 +428,30 @@ function compactActorFiles(status: Record<string, unknown>): string {
424
428
  return `\nrun=${run}${artifactText}${files.length ? ` files=${files.join(",")}` : ""}`;
425
429
  }
426
430
 
431
+ function summarizeOtherSessions(
432
+ currentSession: string,
433
+ allRuns: Array<Record<string, unknown>>,
434
+ ): Record<string, unknown> {
435
+ const otherRuns = allRuns.filter(
436
+ (run) => run.ownerId && run.ownerId !== currentSession,
437
+ );
438
+ return {
439
+ other_runs: otherRuns.length,
440
+ other_sessions: new Set(otherRuns.map((run) => run.ownerId)).size,
441
+ };
442
+ }
443
+
427
444
  function compactSessionRuns(
428
445
  session: string,
429
446
  runs: Array<Record<string, unknown>>,
447
+ summary: Record<string, unknown> = {},
430
448
  ): string {
431
- if (runs.length === 0) return `\nsession=${session} runs=0`;
432
- return `\nsession=${session} runs=${runs.length}\n${runs
449
+ const suffix = [
450
+ summary.other_sessions !== undefined ? `other_sessions=${String(summary.other_sessions)}` : "",
451
+ summary.other_runs !== undefined ? `other_runs=${String(summary.other_runs)}` : "",
452
+ ].filter(Boolean).join(" ");
453
+ if (runs.length === 0) return `\nsession=${session} runs=0${suffix ? ` ${suffix}` : ""}`;
454
+ return `\nsession=${session} runs=${runs.length}${suffix ? ` ${suffix}` : ""}\n${runs
433
455
  .map((run) => {
434
456
  const tokens = [
435
457
  `run=${String(run.run ?? "")}`,
@@ -443,6 +465,39 @@ function compactSessionRuns(
443
465
  .join("\n")}`;
444
466
  }
445
467
 
468
+ function getPiActorsRuntimeStatus(): Record<string, unknown> {
469
+ const packagedRecipeRoot = Paths.getPackagedRecipeRoot();
470
+ const packageRoot = dirname(packagedRecipeRoot);
471
+ const packageJsonPath = join(packageRoot, "package.json");
472
+ const packageJson = existsSync(packageJsonPath)
473
+ ? JSON.parse(readFileSync(packageJsonPath, "utf8")) as Record<string, unknown>
474
+ : {};
475
+ let git_commit: string | undefined;
476
+ try {
477
+ git_commit = execFileSync("git", ["-C", packageRoot, "rev-parse", "--short", "HEAD"], {
478
+ encoding: "utf8",
479
+ stdio: ["ignore", "pipe", "ignore"],
480
+ }).trim();
481
+ } catch {
482
+ git_commit = undefined;
483
+ }
484
+ const entrypoint = new URL(import.meta.url).pathname;
485
+ return {
486
+ entrypoint,
487
+ git_commit,
488
+ mode: entrypoint.includes("/dist/") ? "dist" : "source",
489
+ package_name: packageJson.name ?? "@llblab/pi-actors",
490
+ package_root: packageRoot,
491
+ recipe_root: Paths.getRecipeRoot(),
492
+ packaged_recipe_root: packagedRecipeRoot,
493
+ version: packageJson.version ?? "unknown",
494
+ };
495
+ }
496
+
497
+ function compactPiActorsRuntimeStatus(status: Record<string, unknown>): string {
498
+ return `\npi-actors version=${String(status.version)} mode=${String(status.mode)} path=${String(status.package_root)} entrypoint=${String(status.entrypoint)}${status.git_commit ? ` git=${String(status.git_commit)}` : ""}`;
499
+ }
500
+
446
501
  function compactToolActor(name: string, tool: Record<string, unknown>): string {
447
502
  const parameters = asRecord(tool.parameters);
448
503
  const required = Array.isArray(parameters.required)
@@ -871,8 +926,17 @@ function assertRunAccessibleToContext(
871
926
  const status = AsyncRuns.getRunStatus(runId);
872
927
  const sessionId = getContextSessionId(ctx);
873
928
  if (sessionId && status.ownerId && status.ownerId !== sessionId) {
874
- throw new Error(
875
- `run:${runId} is owned by session:${status.ownerId}; current session is ${sessionId}.`,
929
+ throw Object.assign(
930
+ new Error(
931
+ `run:${runId} reason=session_mismatch owner_session=${status.ownerId} current_session=${sessionId} hint=inspect_session:${status.ownerId}`,
932
+ ),
933
+ {
934
+ current_session: sessionId,
935
+ hint: `inspect target=session:${status.ownerId} view=status`,
936
+ owner_session: status.ownerId,
937
+ reason: "session_mismatch",
938
+ run: runId,
939
+ },
876
940
  );
877
941
  }
878
942
  return status;
@@ -961,24 +1025,24 @@ export function createInspectToolDefinition<TContext = unknown>(
961
1025
  );
962
1026
  }
963
1027
  const session = requireContextSessionId(ctx, "inspect coordinator");
964
- const runs = AsyncRuns.listRuns(
1028
+ const allRuns = AsyncRuns.listRuns(
965
1029
  undefined,
966
1030
  typeof input.status === "string" ? input.status : undefined,
967
- )
968
- .map((run) => AsyncRuns.getRunStatus(String(run.state_dir)))
969
- .filter((run) => run.ownerId === session);
1031
+ ).map((run) => AsyncRuns.getRunStatus(String(run.state_dir)));
1032
+ const runs = allRuns.filter((run) => run.ownerId === session);
1033
+ const sessionSummary = summarizeOtherSessions(session, allRuns);
970
1034
  return {
971
1035
  content: [
972
1036
  {
973
1037
  type: "text" as const,
974
1038
  text: maybeJsonText(
975
- { session, runs },
1039
+ { session, runs, ...sessionSummary },
976
1040
  input.verbose === true,
977
- compactSessionRuns(session, runs),
1041
+ compactSessionRuns(session, runs, sessionSummary),
978
1042
  ),
979
1043
  },
980
1044
  ],
981
- details: { session, runs },
1045
+ details: { session, runs, ...sessionSummary },
982
1046
  };
983
1047
  }
984
1048
  if (address.kind === "session") {
@@ -987,29 +1051,50 @@ export function createInspectToolDefinition<TContext = unknown>(
987
1051
  "inspect session:<id> supports view=status or view=runs.",
988
1052
  );
989
1053
  }
990
- const runs = AsyncRuns.listRuns(
1054
+ const allRuns = AsyncRuns.listRuns(
991
1055
  undefined,
992
1056
  typeof input.status === "string" ? input.status : undefined,
993
- )
994
- .map((run) => AsyncRuns.getRunStatus(String(run.state_dir)))
995
- .filter(
996
- (run) => address.value === "all" || run.ownerId === address.value,
997
- );
1057
+ ).map((run) => AsyncRuns.getRunStatus(String(run.state_dir)));
1058
+ const runs = allRuns.filter(
1059
+ (run) => address.value === "all" || run.ownerId === address.value,
1060
+ );
1061
+ const sessionSummary = address.value === "all"
1062
+ ? {}
1063
+ : summarizeOtherSessions(address.value || "", allRuns);
998
1064
  return {
999
1065
  content: [
1000
1066
  {
1001
1067
  type: "text" as const,
1002
1068
  text: maybeJsonText(
1003
- { session: address.value, runs },
1069
+ { session: address.value, runs, ...sessionSummary },
1004
1070
  input.verbose === true,
1005
- compactSessionRuns(address.value || "", runs),
1071
+ compactSessionRuns(address.value || "", runs, sessionSummary),
1006
1072
  ),
1007
1073
  },
1008
1074
  ],
1009
- details: { session: address.value, runs },
1075
+ details: { session: address.value, runs, ...sessionSummary },
1010
1076
  };
1011
1077
  }
1012
1078
  if (address.kind === "tool" && address.value) {
1079
+ if (address.value === "pi-actors") {
1080
+ if (view !== "status") {
1081
+ throw new Error("inspect tool:pi-actors supports view=status.");
1082
+ }
1083
+ const details = getPiActorsRuntimeStatus();
1084
+ return {
1085
+ content: [
1086
+ {
1087
+ type: "text" as const,
1088
+ text: maybeJsonText(
1089
+ details,
1090
+ input.verbose === true,
1091
+ compactPiActorsRuntimeStatus(details),
1092
+ ),
1093
+ },
1094
+ ],
1095
+ details,
1096
+ };
1097
+ }
1013
1098
  if (view !== "status" && view !== "schema") {
1014
1099
  throw new Error(
1015
1100
  "inspect tool:<name> supports view=status or view=schema.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-actors",
3
- "version": "0.26.3",
3
+ "version": "0.27.1",
4
4
  "private": false,
5
5
  "description": "Local Actor Kernel for Pi",
6
6
  "keywords": [
@@ -2,7 +2,7 @@
2
2
  name: actors
3
3
  description: Highest-density practical guide for pi-actors. Read this skill whenever prompt and tools are not enough for spawn, message, inspect, actor runs, tools, recipes, command templates, async lifecycle, mailboxes, artifacts, and local orchestration mechanics.
4
4
  metadata:
5
- version: 0.26.3
5
+ version: 0.27.1
6
6
  ---
7
7
 
8
8
  # Actors (pi-actors)
@@ -41,7 +41,7 @@ Trusted local capability
41
41
  - **Recipe**: saved JSON definition wrapping a template with args, defaults, imports, mailbox, artifacts, metadata, and optional `async: true`.
42
42
  - **Run actor**: one detached execution instance addressable as `run:<id>` with status, logs, messages, mailbox metadata, files, communication snapshot, and artifacts.
43
43
  - **Room actor**: shared timeline + roster endpoint addressable as `room:<run>`; every spawned run gets `room:<run>`.
44
- - **Tool actor**: registered persistent capability addressable as `tool:<name>` and callable through the generated tool or `message`.
44
+ - **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
45
  - **Coordinator/session**: the current pi session endpoint that receives bounded actor follow-ups.
46
46
  - **Mailbox**: public interaction contract: message types the actor accepts/emits.
47
47
  - **Artifact**: named durable output path declared by a recipe/run.
@@ -102,6 +102,7 @@ Check `inspect view=mailbox` before domain-specific messages.
102
102
  { "target": "room:repo-health", "view": "roster" }
103
103
  { "target": "room:repo-health", "view": "contacts" }
104
104
  { "target": "room:repo-health", "view": "previews" }
105
+ { "target": "tool:pi-actors", "view": "status" }
105
106
  { "target": "tool:music_player", "view": "status" }
106
107
  { "target": "recipes", "view": "status" }
107
108
  { "target": "coordinator", "view": "status" }
@@ -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.26.3
5
+ version: 0.27.1
6
6
  ---
7
7
 
8
8
  # Swarm