@llblab/pi-actors 0.34.0 → 0.34.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
@@ -53,37 +53,6 @@ No open hotfix items.
53
53
 
54
54
  The backlog is intentionally pruned to the 20% of work most likely to deliver 80% of value for `pi-actors` as a local actor kernel. Bias toward consolidation, smaller public surface area, and reliability over new feature breadth.
55
55
 
56
- ### M-15 Worker Stale-Claim Dogfood
57
-
58
- - Priority: Medium.
59
- - Status: Planned.
60
- - Goal: Validate and harden actor-worker v2 stale-claim visibility under intentionally stale claimed branch messages.
61
- - Why now: M-09 exposed `stale_claims`; real dogfood should verify the operator can diagnose stuck claimed work before adding recovery policy.
62
- - Direction:
63
- - Create deterministic stale claimed branch inbox fixtures or smoke tests.
64
- - Verify `worker-status.json`, room events, and inspect surfaces make stale claims visible.
65
- - Defer auto-recovery unless workflow evidence proves it is safe.
66
- - Acceptance:
67
- - Stale claims are reproducible and visible in worker status.
68
- - Tests cover stale-claim counting without adding scheduler/broker policy.
69
-
70
- ### M-17 Message Delivery Outcome Contract
71
-
72
- - Priority: High.
73
- - Status: Planned.
74
- - Goal: Normalize `message` results so operators can distinguish delivered, queued, persisted, forwarded, unsupported, and ownership-denied outcomes.
75
- - Why now: Branch message UX already treats durable branch mailbox persistence as a successful queued outcome when a parent endpoint is unavailable; that local fix should become a consistent message-result membrane.
76
- - Direction:
77
- - Define compact delivery fields: `queued`, `delivered`, `persisted`, `forwarded`, `consumer`, `reason`, and `hint`.
78
- - Apply the shape to `run:<id>`, `branch:<run>/<branch>`, `room:<run>`, `coordinator`, `session:`, and `tool:<name>` where meaningful.
79
- - Reuse M-14 session mismatch shape for ownership-denied outcomes.
80
- - Do not claim guaranteed live consumption unless a known consumer exists.
81
- - Do not add a broker, distributed delivery semantics, or a new public noun.
82
- - Acceptance:
83
- - Branch messages clearly report queued/persisted state and known worker-consumer state where available.
84
- - Room messages distinguish timeline append success from forwarded branch-targeted copies.
85
- - Tests cover at least run, branch, room, coordinator, and ownership-denied outcomes.
86
-
87
56
  ### M-18 Draft Recipe Promotion UX
88
57
 
89
58
  - Priority: High.
@@ -185,7 +154,7 @@ These are valid ideas but not current focus. Reintroduce only with concrete evid
185
154
  ## Suggested Milestone Order
186
155
 
187
156
  ```text
188
- Next milestone: M-15 Worker Stale-Claim Dogfood.
189
- Then: M-17 Message Delivery Outcome Contract → M-18 Draft Recipe Promotion UX.
157
+ Next milestone: M-18 Draft Recipe Promotion UX.
158
+ Then: M-19 Recipe Doctor Risk Labels v2.
190
159
  Small cleanup lane: continue opportunistic domain polish only when a real ownership boundary appears.
191
160
  ```
package/CHANGELOG.md CHANGED
@@ -1,27 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 0.34.1: Message Delivery Outcome Hotfix
6
+
7
+ - `[Dogfood]` Added a deterministic actor-worker stale-claim smoke covering an intentionally claimed branch inbox record; the worker now reports stale claim counts in both `worker-status.json` and its awaiting-assignment room event without adding auto-recovery or scheduler policy.
8
+ - `[Messages]` Completed the M-17 message delivery outcome contract by normalizing public message results with `delivered`, `persisted`, `queued`, `forwarded`, `consumer`, and `reason` fields across run, branch, room, coordinator, session, and tool destinations; room multicast now also exposes per-recipient branch delivery outcomes.
9
+ - `[Backlog]` Closed M-15 Worker Stale-Claim Dogfood and M-17 Message Delivery Outcome Contract after adding delivery-result coverage for run, branch, room, coordinator, session, tool, and ownership-denied paths.
10
+
3
11
  ## 0.34.0: Actor Kernel Domain Compression
4
12
 
5
- - `[Context]` Added a critical tools-domain split track and durable naming guidance: split broad buckets proactively, use `tool-<verb>.ts` for independent public agent-tool domains when deleting `tools.ts`, reserve `tools-<part>.ts` only for a retained `tools.ts` aggregate-root architecture, keep non-tool domains unprefixed, and avoid new `utils` or compatibility barrels when direct owned-domain imports work.
6
- - `[Domains]` Started the `tools.ts` split by moving JSON schema builders into `schema.ts` and compact response helpers into the tool-family response path, reducing the remaining tool bucket to public tool composition rather than generic schema ownership.
7
- - `[Domains]` Moved the public `message` actor tool behavior into `tools-message.ts`, including run controls, branch/room delivery, tool actor invocation, and compact delivery next actions.
8
- - `[Domains]` Moved shared session ownership guards and normalized mismatch diagnostics into `tools-access.ts` so public tools can share one access contract without copying ownership checks.
9
- - `[Domains]` Moved mailbox contract normalization and accepted/emitted type extraction into `tools-mailbox.ts`, removing duplicated mailbox metadata handling from message and inspect tool paths.
10
- - `[Domains]` Added `tools-response.ts` for compact public tool responses and next-action rendering shared by spawn, inspect, and registered tool paths; moved recipe registry compact summaries and doctor/import next-action rendering into it.
11
- - `[Domains]` Moved the public `inspect` actor tool behavior into `tools-inspect.ts`, including recipe registry inspection, room/session/tool/run views, and inspect-specific compact observation formatting.
12
- - `[Domains]` Moved the public `spawn` actor tool behavior into `tools-spawn.ts`, including actor launch, draft recipe capture, shadowed recipe launch diagnostics, and spawn next-action feedback.
13
- - `[Domains]` Removed the over-thin `tool-context.ts` extraction and kept tool execution context types local to the domains that use them.
14
- - `[Domains]` Moved saved local capability execution into `tools-local.ts`, including generated schemas, argument usage hints, runtime value normalization, and async recipe launch behavior.
15
- - `[Domains]` Moved public `register_tool` behavior into `tools-register.ts`, including the register schema and registry mutation bridge for persisted local capabilities.
16
- - `[Domains]` Kept `tools.ts` as the public tool-family composition owner, moved decomposed behavior into `tools-*` subdomains, removed the redundant `actor-tools.ts` prefix because this package is already actor-scoped, and documented that any `tools-*` helper reused by non-tools domains must drop the prefix in the same slice.
17
- - `[Domains]` Removed redundant internal `actor-` prefixes from core library domains: `inspector.ts`, `messages.ts`, `recipes-context.ts`, and `rooms.ts`; public recipe/script/docs names that intentionally expose actor terminology stay unchanged.
18
- - `[Scripts]` Collapsed the one-off `run-executor.ts`, `worker.ts`, `validate-recipe.ts`, `recipe-utils.ts`, `locker.ts`, and `coordinator.ts` domains into their owning scripts; reusable lifecycle, room, mailbox, and recipe-reference primitives stay in `lib/`, while public scripts own their executable control loops directly.
19
- - `[Domains]` Started decomposing the oversized `async-runs.ts` lifecycle aggregate into `runs-*` subdomains, moving artifact manifest handling to `runs-artifacts.ts`, durable run inbox locking/claim handling to `runs-mailbox.ts`, process-control signal helpers to `runs-control.ts`, outbox event parsing/payload formatting to `runs-outbox.ts`, run-state index discovery/rebuild logic to `runs-index.ts`, id normalization to `runs-identity.ts`, archive/prune retention behavior to `runs-retention.ts`, process identity/liveness checks to `runs-process.ts`, run message delivery to `runs-messages.ts`, status/log derivation to `runs-status.ts`, and start lock/reuse guards to `runs-start.ts` while preserving the `async-runs.ts` facade.
20
- - `[Context]` Reversed the thin-script default: `scripts/*.mjs` should own script-only executable behavior, and behavior should move into `lib/` only for real non-script reuse or existing reusable domain ownership; packaging/tests/shim neatness alone no longer justify a lib domain.
21
- - `[Context]` Polished domain ownership headers after the file moves so command templates, actor messages, rooms, recipe context, inspector previews, and mailbox loops describe their actual reasons to change without generic helper wording.
22
- - `[Domains]` Renamed generic `output.ts` to `execution-output.ts` so registered-tool stdout/stderr truncation and temp artifact formatting are tied to the execution domain instead of a broad output bucket.
23
- - `[Domains]` Renamed the recipe domain family from singular `recipe-*` to plural `recipes-*` (`recipes-references.ts`, `recipes-discovery.ts`, `recipes-usage.ts`, `recipes-context.ts`) to match the `tools-*` and `runs-*` family convention.
24
- - `[Tests]` Extended installed-package contract coverage to assert stale renamed lib domains are absent from `dist/lib`, keeping packaged JS output aligned with the current domain names.
13
+ - `[Domains]` Compressed the actor kernel into explicit domain families: `tools.ts` remains the public tool-family owner while `tools-*` owns message, inspect, spawn, register, local execution, response, access, and mailbox-contract behavior; `async-runs.ts` remains the lifecycle facade while `runs-*` owns artifacts, mailbox, process control, delivery, outbox, index, retention, status, start guards, and identity internals.
14
+ - `[Domains]` Removed redundant internal `actor-` prefixes and renamed the recipe family to plural `recipes-*`, leaving public actor-named recipe/script/docs surfaces intact while making core library ownership match the `tools-*` and `runs-*` convention.
15
+ - `[Scripts]` Collapsed script-only runner, worker, validator, recipe-utils, locker, and coordinator library shims back into their owning `scripts/*.mjs` entrypoints; reusable lifecycle, room, mailbox-loop, command-template, and recipe-reference primitives remain in `lib/`.
16
+ - `[Context]` Reversed the thin-script default, polished ownership headers, and kept completed script-autonomy/domain-compression work in the changelog instead of the backlog.
17
+ - `[Tests]` Mirrored renamed domains in test filenames and extended installed-package contract coverage to ensure stale renamed lib domains are absent from `dist/lib` while packaged JS-only script execution still works.
25
18
 
26
19
  ## 0.33.0: Signal-First Compatibility Pruning
27
20
 
@@ -101,6 +101,73 @@ function getRoomMulticastRecipients(message, run) {
101
101
  return Messages.formatActorAddress(parsed);
102
102
  });
103
103
  }
104
+ function normalizeDeliveryOutcome(address, result) {
105
+ if (result.reason)
106
+ return result;
107
+ if (address.kind === "run") {
108
+ if (result.stopped === true) {
109
+ return {
110
+ ...result,
111
+ consumer: "run-control",
112
+ delivered: true,
113
+ persisted: true,
114
+ reason: "control_applied",
115
+ };
116
+ }
117
+ return {
118
+ ...result,
119
+ consumer: result.control_type ?? result.control ?? "run-control",
120
+ delivered: result.sent === true && result.queued !== true,
121
+ persisted: true,
122
+ reason: result.delivery_error
123
+ ? "delivery_failed_persisted"
124
+ : result.queued === true
125
+ ? "queued_mailbox"
126
+ : "delivered",
127
+ };
128
+ }
129
+ if (address.kind === "branch") {
130
+ return {
131
+ ...result,
132
+ consumer: "branch-mailbox",
133
+ delivered: result.sent === true,
134
+ persisted: true,
135
+ reason: result.delivery_error
136
+ ? "branch_persisted_parent_unavailable"
137
+ : "branch_persisted_forwarded",
138
+ };
139
+ }
140
+ if (address.kind === "room") {
141
+ return {
142
+ ...result,
143
+ consumer: "room-timeline",
144
+ delivered: true,
145
+ forwarded: Number(result.multicast_count ?? 0) > 0,
146
+ persisted: true,
147
+ reason: "room_persisted",
148
+ };
149
+ }
150
+ if (address.kind === "tool") {
151
+ return {
152
+ ...result,
153
+ consumer: "tool",
154
+ delivered: true,
155
+ persisted: false,
156
+ reason: "tool_invoked",
157
+ };
158
+ }
159
+ if (address.kind === "coordinator" || address.kind === "session") {
160
+ return {
161
+ ...result,
162
+ consumer: "run-outbox",
163
+ delivered: false,
164
+ persisted: true,
165
+ queued: true,
166
+ reason: `${address.kind}_outbox_persisted`,
167
+ };
168
+ }
169
+ return result;
170
+ }
104
171
  function actorMessageNextActions(message, result) {
105
172
  const actions = [];
106
173
  const address = Messages.parseActorAddress(message.to);
@@ -132,8 +199,18 @@ function compactActorMessageResult(message, result) {
132
199
  ];
133
200
  if (result.bytes !== undefined)
134
201
  tokens.push(`bytes=${String(result.bytes)}`);
202
+ if (result.delivered !== undefined)
203
+ tokens.push(`delivered=${String(result.delivered)}`);
135
204
  if (result.queued === true)
136
205
  tokens.push("queued=true");
206
+ if (result.persisted !== undefined)
207
+ tokens.push(`persisted=${String(result.persisted)}`);
208
+ if (result.forwarded !== undefined)
209
+ tokens.push(`forwarded=${String(result.forwarded)}`);
210
+ if (result.consumer)
211
+ tokens.push(`consumer=${String(result.consumer)}`);
212
+ if (result.reason)
213
+ tokens.push(`reason=${String(result.reason)}`);
137
214
  if (result.control)
138
215
  tokens.push(`control=${String(result.control)}`);
139
216
  if (result.outbox)
@@ -252,13 +329,17 @@ export function createActorMessageToolDefinition(deps = {}) {
252
329
  throw new Error(`${message.to} has no run state directory.`);
253
330
  const recipients = getRoomMulticastRecipients(message, runId);
254
331
  const roomResult = Rooms.appendRoomMessage(stateDir, address.room, message);
255
- await Promise.all(recipients.map((recipient) => routeBranchEnvelope(stateDir, runId, recipient, message, {
332
+ const multicastResults = await Promise.all(recipients.map(async (recipient) => normalizeDeliveryOutcome({ kind: "branch", branch: recipient.split("/").at(-1), value: runId }, await routeBranchEnvelope(stateDir, runId, recipient, message, {
256
333
  source: "room-multicast",
257
- })));
334
+ }))));
258
335
  result = {
259
336
  ...roomResult,
260
337
  ...(recipients.length > 0
261
- ? { multicast: recipients, multicast_count: recipients.length }
338
+ ? {
339
+ multicast: recipients,
340
+ multicast_count: recipients.length,
341
+ multicast_results: multicastResults,
342
+ }
262
343
  : {}),
263
344
  };
264
345
  }
@@ -328,6 +409,7 @@ export function createActorMessageToolDefinition(deps = {}) {
328
409
  else {
329
410
  throw new Error(`message currently supports run:<id>, branch:<run>/<branch>, room:<run>, tool:<name>, coordinator, and session:<id> destinations; unsupported destination: ${message.to}`);
330
411
  }
412
+ result = normalizeDeliveryOutcome(address, result);
331
413
  const nextActions = actorMessageNextActions(message, result);
332
414
  const resultWithNext = nextActions.length
333
415
  ? { ...result, next_actions: nextActions }
@@ -137,7 +137,11 @@ export async function runActorWorker(argv = process.argv.slice(2)) {
137
137
  { display: branch, role: "worker", status: "present" },
138
138
  `${branch} joined as mailbox worker`,
139
139
  );
140
- room("awaiting_assignment", `${branch} awaiting assignment`, { branch });
140
+ room("awaiting_assignment", `${branch} awaiting assignment`, {
141
+ branch,
142
+ stale_claim_ms: staleClaimMs,
143
+ stale_claims: staleClaims(),
144
+ });
141
145
  journal("worker.started", {
142
146
  artifact_dir: artifactDir,
143
147
  branch,
@@ -2,7 +2,7 @@
2
2
  name: actors
3
3
  description: Required practical guide for non-trivial pi-actors use. Read before using or changing spawn, message, inspect, actor runs, tools, recipes, command templates, async lifecycle, mailboxes, artifacts, and local orchestration mechanics.
4
4
  metadata:
5
- version: 0.34.0
5
+ version: 0.34.1
6
6
  ---
7
7
 
8
8
  # Actors (pi-actors)
@@ -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.34.0
5
+ version: 0.34.1
6
6
  ---
7
7
 
8
8
  # Swarm
@@ -144,6 +144,76 @@ function getRoomMulticastRecipients(
144
144
  });
145
145
  }
146
146
 
147
+ function normalizeDeliveryOutcome(
148
+ address: Messages.ActorAddress,
149
+ result: Record<string, unknown>,
150
+ ): Record<string, unknown> {
151
+ if (result.reason) return result;
152
+ if (address.kind === "run") {
153
+ if (result.stopped === true) {
154
+ return {
155
+ ...result,
156
+ consumer: "run-control",
157
+ delivered: true,
158
+ persisted: true,
159
+ reason: "control_applied",
160
+ };
161
+ }
162
+ return {
163
+ ...result,
164
+ consumer: result.control_type ?? result.control ?? "run-control",
165
+ delivered: result.sent === true && result.queued !== true,
166
+ persisted: true,
167
+ reason: result.delivery_error
168
+ ? "delivery_failed_persisted"
169
+ : result.queued === true
170
+ ? "queued_mailbox"
171
+ : "delivered",
172
+ };
173
+ }
174
+ if (address.kind === "branch") {
175
+ return {
176
+ ...result,
177
+ consumer: "branch-mailbox",
178
+ delivered: result.sent === true,
179
+ persisted: true,
180
+ reason: result.delivery_error
181
+ ? "branch_persisted_parent_unavailable"
182
+ : "branch_persisted_forwarded",
183
+ };
184
+ }
185
+ if (address.kind === "room") {
186
+ return {
187
+ ...result,
188
+ consumer: "room-timeline",
189
+ delivered: true,
190
+ forwarded: Number(result.multicast_count ?? 0) > 0,
191
+ persisted: true,
192
+ reason: "room_persisted",
193
+ };
194
+ }
195
+ if (address.kind === "tool") {
196
+ return {
197
+ ...result,
198
+ consumer: "tool",
199
+ delivered: true,
200
+ persisted: false,
201
+ reason: "tool_invoked",
202
+ };
203
+ }
204
+ if (address.kind === "coordinator" || address.kind === "session") {
205
+ return {
206
+ ...result,
207
+ consumer: "run-outbox",
208
+ delivered: false,
209
+ persisted: true,
210
+ queued: true,
211
+ reason: `${address.kind}_outbox_persisted`,
212
+ };
213
+ }
214
+ return result;
215
+ }
216
+
147
217
  function actorMessageNextActions(
148
218
  message: Messages.ActorMessage,
149
219
  result: Record<string, unknown>,
@@ -183,7 +253,15 @@ function compactActorMessageResult(
183
253
  `message=${result.sent === true || result.stopped === true ? "sent" : "not_sent"}`,
184
254
  ];
185
255
  if (result.bytes !== undefined) tokens.push(`bytes=${String(result.bytes)}`);
256
+ if (result.delivered !== undefined)
257
+ tokens.push(`delivered=${String(result.delivered)}`);
186
258
  if (result.queued === true) tokens.push("queued=true");
259
+ if (result.persisted !== undefined)
260
+ tokens.push(`persisted=${String(result.persisted)}`);
261
+ if (result.forwarded !== undefined)
262
+ tokens.push(`forwarded=${String(result.forwarded)}`);
263
+ if (result.consumer) tokens.push(`consumer=${String(result.consumer)}`);
264
+ if (result.reason) tokens.push(`reason=${String(result.reason)}`);
187
265
  if (result.control) tokens.push(`control=${String(result.control)}`);
188
266
  if (result.outbox) tokens.push(`messages=${String(result.outbox)}`);
189
267
  if (result.message_count !== undefined)
@@ -364,17 +442,24 @@ export function createActorMessageToolDefinition<TContext = unknown>(
364
442
  address.room,
365
443
  message,
366
444
  );
367
- await Promise.all(
368
- recipients.map((recipient) =>
369
- routeBranchEnvelope(stateDir, runId, recipient, message, {
370
- source: "room-multicast",
371
- }),
445
+ const multicastResults = await Promise.all(
446
+ recipients.map(async (recipient) =>
447
+ normalizeDeliveryOutcome(
448
+ { kind: "branch", branch: recipient.split("/").at(-1), value: runId },
449
+ await routeBranchEnvelope(stateDir, runId, recipient, message, {
450
+ source: "room-multicast",
451
+ }),
452
+ ),
372
453
  ),
373
454
  );
374
455
  result = {
375
456
  ...roomResult,
376
457
  ...(recipients.length > 0
377
- ? { multicast: recipients, multicast_count: recipients.length }
458
+ ? {
459
+ multicast: recipients,
460
+ multicast_count: recipients.length,
461
+ multicast_results: multicastResults,
462
+ }
378
463
  : {}),
379
464
  };
380
465
  } else if (address.kind === "tool" && address.value) {
@@ -461,6 +546,7 @@ export function createActorMessageToolDefinition<TContext = unknown>(
461
546
  `message currently supports run:<id>, branch:<run>/<branch>, room:<run>, tool:<name>, coordinator, and session:<id> destinations; unsupported destination: ${message.to}`,
462
547
  );
463
548
  }
549
+ result = normalizeDeliveryOutcome(address, result);
464
550
  const nextActions = actorMessageNextActions(message, result);
465
551
  const resultWithNext = nextActions.length
466
552
  ? { ...result, next_actions: nextActions }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-actors",
3
- "version": "0.34.0",
3
+ "version": "0.34.1",
4
4
  "private": false,
5
5
  "description": "Local Actor Kernel for Pi",
6
6
  "keywords": [
@@ -137,7 +137,11 @@ export async function runActorWorker(argv = process.argv.slice(2)) {
137
137
  { display: branch, role: "worker", status: "present" },
138
138
  `${branch} joined as mailbox worker`,
139
139
  );
140
- room("awaiting_assignment", `${branch} awaiting assignment`, { branch });
140
+ room("awaiting_assignment", `${branch} awaiting assignment`, {
141
+ branch,
142
+ stale_claim_ms: staleClaimMs,
143
+ stale_claims: staleClaims(),
144
+ });
141
145
  journal("worker.started", {
142
146
  artifact_dir: artifactDir,
143
147
  branch,
@@ -2,7 +2,7 @@
2
2
  name: actors
3
3
  description: Required practical guide for non-trivial pi-actors use. Read before using or changing spawn, message, inspect, actor runs, tools, recipes, command templates, async lifecycle, mailboxes, artifacts, and local orchestration mechanics.
4
4
  metadata:
5
- version: 0.34.0
5
+ version: 0.34.1
6
6
  ---
7
7
 
8
8
  # Actors (pi-actors)
@@ -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.34.0
5
+ version: 0.34.1
6
6
  ---
7
7
 
8
8
  # Swarm