@fitchmultz/pi-subagents 0.50.0 → 0.52.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.
Files changed (180) hide show
  1. package/CHANGELOG.md +63 -2
  2. package/README.md +36 -5
  3. package/dist/agent/child-runtime.js +80 -0
  4. package/dist/agent/contracts.js +15 -24
  5. package/dist/agent/host.js +3 -16
  6. package/dist/agent/index.js +212 -192
  7. package/dist/agent/journal-tail.js +115 -156
  8. package/dist/agent/launch.js +20 -8
  9. package/dist/agent/legacy.js +65 -23
  10. package/dist/agent/notifications.js +138 -47
  11. package/dist/agent/observation-state.js +246 -0
  12. package/dist/agent/observation-view.js +258 -0
  13. package/dist/agent/observation.js +350 -0
  14. package/dist/agent/observe-card.js +243 -0
  15. package/dist/agent/observe-chrome.js +85 -0
  16. package/dist/agent/observe-client.js +285 -0
  17. package/dist/agent/observe-layout.js +65 -0
  18. package/dist/agent/observe-reading.js +84 -0
  19. package/dist/agent/observe-screen.js +400 -96
  20. package/dist/agent/observe-transcript.js +382 -0
  21. package/dist/agent/observe.js +250 -43
  22. package/dist/agent/parking.js +253 -0
  23. package/dist/agent/process.js +228 -28
  24. package/dist/agent/receipts.js +92 -20
  25. package/dist/agent/runner.js +229 -126
  26. package/dist/agent/shell.js +2 -1
  27. package/dist/agent/storage.js +47 -12
  28. package/dist/agent/tool-schemas.js +20 -0
  29. package/dist/agent/typebox.js +10 -5
  30. package/dist/agent/usage.js +0 -115
  31. package/dist/agent/view.js +46 -16
  32. package/dist/agent/wake.js +99 -0
  33. package/dist/build-info.js +1 -1
  34. package/dist/intercom-client.d.ts +26 -0
  35. package/dist/intercom-client.js +45 -0
  36. package/dist/pi-intercom/agent-dir.d.ts +1 -0
  37. package/dist/pi-intercom/bridge-session.js +2 -1
  38. package/dist/pi-intercom/broker/broker.js +62 -43
  39. package/dist/pi-intercom/broker/client-last.d.ts +24 -0
  40. package/dist/pi-intercom/broker/client-last.js +42 -5
  41. package/dist/pi-intercom/broker/client-park.d.ts +9 -0
  42. package/dist/pi-intercom/broker/client-park.js +21 -0
  43. package/dist/pi-intercom/broker/client.d.ts +59 -0
  44. package/dist/pi-intercom/broker/client.js +55 -29
  45. package/dist/pi-intercom/broker/connection.d.ts +29 -0
  46. package/dist/pi-intercom/broker/framing.d.ts +17 -0
  47. package/dist/pi-intercom/broker/framing.js +10 -7
  48. package/dist/pi-intercom/broker/guard.js +23 -0
  49. package/dist/pi-intercom/broker/last.d.ts +30 -0
  50. package/dist/pi-intercom/broker/last.js +87 -40
  51. package/dist/pi-intercom/broker/parked.js +107 -0
  52. package/dist/pi-intercom/broker/paths.d.ts +13 -0
  53. package/dist/pi-intercom/broker/paths.js +35 -12
  54. package/dist/pi-intercom/broker/pid.js +5 -1
  55. package/dist/pi-intercom/broker/server-connection.js +20 -1
  56. package/dist/pi-intercom/broker/snapshot-reader.d.ts +9 -0
  57. package/dist/pi-intercom/broker/snapshots.d.ts +29 -0
  58. package/dist/pi-intercom/broker/targets.d.ts +9 -0
  59. package/dist/pi-intercom/connection.js +71 -20
  60. package/dist/pi-intercom/identity.js +6 -5
  61. package/dist/pi-intercom/inbound-delivery.js +16 -60
  62. package/dist/pi-intercom/inbound-journal.js +83 -6
  63. package/dist/pi-intercom/inbound-record.js +18 -1
  64. package/dist/pi-intercom/inbound.js +22 -37
  65. package/dist/pi-intercom/index.js +41 -0
  66. package/dist/pi-intercom/last-message.js +4 -16
  67. package/dist/pi-intercom/lifecycle.js +88 -3
  68. package/dist/pi-intercom/outbound.js +5 -15
  69. package/dist/pi-intercom/reply-tracker.js +7 -6
  70. package/dist/pi-intercom/reply-wait.js +2 -2
  71. package/dist/pi-intercom/session-targets.d.ts +24 -0
  72. package/dist/pi-intercom/tool-registration.js +4 -1
  73. package/dist/pi-intercom/tool-results.js +5 -7
  74. package/dist/pi-intercom/tool-schemas.js +12 -26
  75. package/dist/pi-intercom/types.d.ts +212 -0
  76. package/dist/pi-intercom/types.js +54 -3
  77. package/dist/pi-intercom/validation.d.ts +12 -0
  78. package/dist/pi-intercom/validation.js +3 -0
  79. package/dist/shared/bounded-json.js +151 -0
  80. package/dist/shared/compact-cards.js +66 -0
  81. package/dist/shared/journal-frames.js +3 -6
  82. package/dist/shared/json-projection.js +267 -129
  83. package/dist/shared/native-journal.js +123 -56
  84. package/dist/shared/parking.js +12 -0
  85. package/dist/shared/post-exit-stdio-guard.js +120 -7
  86. package/dist/shared/tui-action.js +29 -0
  87. package/dist/shared/unknown.d.ts +9 -0
  88. package/docs/code-quality.md +5 -3
  89. package/docs/development.md +42 -3
  90. package/docs/intercom-bridge.md +9 -4
  91. package/docs/intercom.md +228 -7
  92. package/docs/performance.md +118 -14
  93. package/docs/reference.md +168 -20
  94. package/docs/retired-history-maintenance.md +252 -0
  95. package/package.json +28 -15
  96. package/scripts/archive-retired-history.mjs +464 -0
  97. package/scripts/build.mjs +48 -28
  98. package/scripts/measure-size.mjs +1 -1
  99. package/scripts/native-package-smoke.mjs +153 -12
  100. package/scripts/package-smoke.mjs +80 -11
  101. package/scripts/prepare.mjs +2 -2
  102. package/scripts/real-pi-live.mjs +377 -0
  103. package/scripts/real-pi-processes.mjs +6 -1
  104. package/scripts/real-pi-smoke.mjs +6 -282
  105. package/scripts/run-tests.mjs +4 -2
  106. package/scripts/typescript.mjs +35 -0
  107. package/skills/pi-intercom/SKILL.md +2 -1
  108. package/skills/pi-subagents/SKILL.md +23 -5
  109. package/src/agent/child-runtime.ts +105 -0
  110. package/src/agent/contracts.ts +18 -34
  111. package/src/agent/host.ts +39 -20
  112. package/src/agent/index.ts +234 -207
  113. package/src/agent/journal-tail.ts +141 -153
  114. package/src/agent/launch.ts +33 -9
  115. package/src/agent/legacy.ts +85 -31
  116. package/src/agent/notifications.ts +192 -67
  117. package/src/agent/observation-state.ts +277 -0
  118. package/src/agent/observation-view.ts +267 -0
  119. package/src/agent/observation.ts +409 -0
  120. package/src/agent/observe-card.ts +304 -0
  121. package/src/agent/observe-chrome.ts +170 -0
  122. package/src/agent/observe-client.ts +318 -0
  123. package/src/agent/observe-layout.ts +79 -0
  124. package/src/agent/observe-reading.ts +97 -0
  125. package/src/agent/observe-screen.ts +443 -129
  126. package/src/agent/observe-transcript.ts +434 -0
  127. package/src/agent/observe.ts +304 -71
  128. package/src/agent/parking.ts +341 -0
  129. package/src/agent/process.ts +280 -37
  130. package/src/agent/receipts.ts +114 -24
  131. package/src/agent/runner.ts +227 -149
  132. package/src/agent/shell.ts +2 -1
  133. package/src/agent/storage.ts +44 -11
  134. package/src/agent/tool-schemas.ts +33 -0
  135. package/src/agent/typebox.ts +12 -7
  136. package/src/agent/usage.ts +0 -130
  137. package/src/agent/view.ts +70 -20
  138. package/src/agent/wake.ts +107 -0
  139. package/src/intercom-client.ts +108 -0
  140. package/src/pi-intercom/bridge-session.ts +2 -2
  141. package/src/pi-intercom/broker/broker.ts +64 -53
  142. package/src/pi-intercom/broker/client-last.ts +65 -12
  143. package/src/pi-intercom/broker/client-park.ts +23 -0
  144. package/src/pi-intercom/broker/client.ts +63 -45
  145. package/src/pi-intercom/broker/framing.ts +11 -8
  146. package/src/pi-intercom/broker/guard.ts +33 -0
  147. package/src/pi-intercom/broker/last.ts +131 -43
  148. package/src/pi-intercom/broker/parked.ts +130 -0
  149. package/src/pi-intercom/broker/paths.ts +39 -12
  150. package/src/pi-intercom/broker/pid.ts +6 -1
  151. package/src/pi-intercom/broker/server-connection.ts +27 -2
  152. package/src/pi-intercom/connection.ts +103 -39
  153. package/src/pi-intercom/identity.ts +8 -5
  154. package/src/pi-intercom/inbound-delivery.ts +20 -66
  155. package/src/pi-intercom/inbound-journal.ts +103 -7
  156. package/src/pi-intercom/inbound-record.ts +21 -4
  157. package/src/pi-intercom/inbound.ts +32 -49
  158. package/src/pi-intercom/index.ts +44 -0
  159. package/src/pi-intercom/last-message.ts +11 -20
  160. package/src/pi-intercom/lifecycle.ts +117 -14
  161. package/src/pi-intercom/outbound.ts +7 -16
  162. package/src/pi-intercom/reply-tracker.ts +8 -6
  163. package/src/pi-intercom/reply-wait.ts +3 -2
  164. package/src/pi-intercom/runtime-types.ts +0 -1
  165. package/src/pi-intercom/tool-registration.ts +4 -1
  166. package/src/pi-intercom/tool-results.ts +10 -9
  167. package/src/pi-intercom/tool-schemas.ts +12 -44
  168. package/src/pi-intercom/types.ts +115 -4
  169. package/src/pi-intercom/validation.ts +4 -0
  170. package/src/shared/bounded-json.ts +162 -0
  171. package/src/shared/compact-cards.ts +118 -0
  172. package/src/shared/journal-frames.ts +3 -19
  173. package/src/shared/json-projection.ts +267 -147
  174. package/src/shared/native-journal.ts +157 -75
  175. package/src/shared/parking.ts +22 -0
  176. package/src/shared/post-exit-stdio-guard.ts +165 -7
  177. package/src/shared/tui-action.ts +39 -0
  178. package/tsconfig.client.json +8 -0
  179. package/tsconfig.json +1 -0
  180. package/src/shared/stream-json.d.ts +0 -10
package/docs/intercom.md CHANGED
@@ -30,6 +30,13 @@ a parent. A child uses its main's ID for both. An offline parent does not erase
30
30
  sibling membership. Presence updates cannot change the registered group. Legacy
31
31
  or remote peers without lineage are standalone mains.
32
32
 
33
+ Pi launches a child with both `PI_INTERCOM_ROOT_SESSION_ID` and
34
+ `PI_INTERCOM_PARENT_SESSION_ID` set to its main's exact native ID. With neither
35
+ set, the session is a main. Any other combination (one missing, empty, padded
36
+ with whitespace, or different values) stops the package from loading, so that
37
+ session gets neither delegation nor Intercom. Values are never trimmed into a
38
+ different identity.
39
+
33
40
  ### Identity and targeting
34
41
 
35
42
  Use a target returned by `list`. Native peer IDs are `pi-` plus the first 32
@@ -43,6 +50,61 @@ when needed to distinguish collisions. A project-filtered peer can still be
43
50
  addressed by its known target; a topology-forbidden peer cannot. A hidden peer
44
51
  does not leak through duplicate-name ambiguity or disconnect notifications.
45
52
 
53
+ ### Conditional native context targeting
54
+
55
+ Ordinary `intercom` names and asks are unchanged. Integrations can negotiate
56
+ guarded-delivery version 1 with the native broker client. Discovery then exposes
57
+ `rootSessionId`, `incarnation`, `guardedDelivery:1` and the optional opaque
58
+ `applicationGuard`. The lifecycle mints the incarnation on native startup, reload
59
+ or session replacement, not on transport reconnect. Peer IDs still belong to
60
+ the broker. Two current mains for one root make guarded sends ambiguous.
61
+
62
+ A guarded send requires an exact peer ID, a caller-recorded original message ID,
63
+ `expectsReply:false`, and
64
+ `guard:{rootSessionId,peerId,incarnation,applicationGuard}`. The broker checks
65
+ the complete binding before routing; the native recipient checks again before
66
+ initial admission, before its first checkpoint or immediate placement. Children
67
+ are not guarded command targets. Missing capability, stale context, changed
68
+ consent or an ambiguous root fails closed for new admission, never selecting a
69
+ similarly named main. A broker acceptance still does not prove
70
+ recipient admission. Lost responses remain uncertain; no automatic replay is added.
71
+ The supported non-model package client below uses these same native protocols.
72
+
73
+ Withdrawing the guard stops new admission; it does not cancel already-admitted
74
+ input. The original native session remains the recovery owner after a checkpoint,
75
+ including across reload or process recovery. A fork or new root cannot adopt its
76
+ pending inputs. Native queueing or placement is not proof of model consumption.
77
+
78
+ An original-main companion uses the existing process-local `pi.events` bus:
79
+
80
+ - Emit `pi-intercom:context-query` with `{version:1,respond(snapshot)}` for late
81
+ attachment. Unknown versions return `{error:"unsupported_version"}`.
82
+ - Observe `pi-intercom:context-state` for subsequent lifecycle, connection and
83
+ application-guard changes. Snapshots contain `version:1`, exact root, optional
84
+ parent, actual `peerId`, `incarnation`, `connected`, negotiated
85
+ `guardedDelivery:1|null`, and optional `applicationGuard`. Unavailable identities
86
+ are null. Query after subscribing; state events are not replayed.
87
+ - Emit `pi-intercom:application-guard` with
88
+ `{version:1,target:{rootSessionId,peerId,incarnation},revision,respond(result)}`.
89
+ The companion owns this opaque nonempty string revision; null withdraws it.
90
+ A stale target returns `{error:"context_changed"}`. It is cleared on context
91
+ replacement and retained on transport reconnect. This handshake is not consent
92
+ itself, does not persist a policy, and grants no human-origin authority.
93
+
94
+ Callbacks are process-local, synchronous observations. They are not socket frames
95
+ or a generic registry. Old owners have no handshake; treat them as unsupported.
96
+ Old brokers do not acknowledge guarded delivery, so updated clients refuse before
97
+ sending. Old ordinary clients and recipients continue using ordinary messages;
98
+ they are not advertised as conditional targets.
99
+
100
+ Guarded nonblocking messages may include
101
+ `replyHint:{tool,commandId,commandFingerprint?}`. Tool names are validated identifiers and command IDs
102
+ are immutable UUIDs supplied by the sender. An optional lowercase SHA-256
103
+ `commandFingerprint` is opaque to Intercom; its caller owns the fingerprint
104
+ contract. The native message renders a generic
105
+ publication hint separately from an ordinary ask. No product-specific tool is
106
+ hard-coded, no pending ask is created, and a hint does not prove a reply exists.
107
+
46
108
  ## Send, ask, and reply
47
109
 
48
110
  ```js
@@ -56,7 +118,9 @@ intercom({
56
118
  });
57
119
  ```
58
120
 
59
- - Default **steer** wakes an idle recipient or reaches busy work at a tool boundary.
121
+ - Default **steer**, for both sends and incoming asks, wakes an idle recipient or
122
+ reaches busy work at a tool boundary. Print-mode recipients use the same rule;
123
+ they do not replace an ordinary ask with a canned busy reply.
60
124
  - Explicit **queue** defers delivery. Optional `queueMode:"replace"` with a nonempty
61
125
  `threadId` coalesces still-staged updates for that thread; messages already handed
62
126
  to Pi's native queue cannot be keyed-replaced.
@@ -67,10 +131,13 @@ recipient journal persistence, model consumption, reply, or task completion. A
67
131
  lost transport response can be uncertain; do not blindly replay side effects.
68
132
 
69
133
  `ask` waits for a correlated reply and has a bounded timeout. Prefer `send` unless
70
- the sender genuinely must remain alive waiting for a decision. A busy peer may
71
- report that it cannot answer immediately. `reply` uses the active or sole pending
72
- ask when unambiguous; otherwise specify `replyTo` and the intended sender. Inspect
73
- `pending` for the exact correlation rather than guessing from text.
134
+ the sender genuinely must remain alive waiting for a decision. If an ordinary ask
135
+ finds a peer advertising `acceptsAsks:false`, the sender still reports `peer_busy`
136
+ without waiting; recipient steering does not claim that a reply has arrived.
137
+ Explicit steer/queue asks retain their bounded correlated wait. `reply` uses the
138
+ active or sole pending ask when unambiguous; otherwise specify `replyTo` and the
139
+ intended sender. Inspect `pending` for the exact correlation rather than guessing
140
+ from text.
74
141
 
75
142
  This ordinary request/reply mechanism does not recreate the retired durable
76
143
  supervisor approval/revival workflow. An expired wait is not an answered question
@@ -99,6 +166,140 @@ identity. Reads settle on response, disconnect, or timeout. Old peers/brokers
99
166
  without the capability report unsupported reads instead of receiving unknown
100
167
  message frames. Broker correlation does not grant access to another group.
101
168
 
169
+ ## Native-owned receipts and publication provenance
170
+
171
+ Receipt reads are non-waking and distinct from `last`. The native client
172
+ negotiates `receiptReads:1` with the broker, which also checks the recipient's
173
+ capability before forwarding `{rootSessionId,messageId}`. Old brokers or owners
174
+ are unavailable, not a reason to send an unknown frame or parse their journals.
175
+ Receipt and last reads share the broker's private token, answering-peer,
176
+ timeout and disconnect owner. A receipt response must match the exact requested
177
+ native root and message ID. No model-facing receipt action is added.
178
+
179
+ A companion in the original main emits `pi-intercom:provenance-query` on
180
+ `pi.events` with `{version:1,rootSessionId,messageId,respond(result)}`. The loaded
181
+ Intercom owner reads its own readonly native SessionManager and responds
182
+ synchronously with `{receipt}` or `{error}`. Unknown versions, an inactive owner
183
+ or a different root return `publication_unavailable`. An absent checkpoint
184
+ returns `receipt_not_found`, which proves neither no effect nor task success.
185
+ Missing handlers are unsupported; there is no packaged parser fallback.
186
+
187
+ The version-1 receipt contains the exact root and message ID, original sender
188
+ peer/root/incarnation where recorded, requested delivery, received timestamp,
189
+ checkpoint entry ID, selected guard and publication-hint fields, and
190
+ `textSha256`. That hash is lowercase SHA-256 of the exact raw sender text encoded
191
+ as UTF-8, before banners, hints, or newline/Unicode normalization. It is computed
192
+ independently of the caller's opaque `commandFingerprint`. No input text,
193
+ attachments, thoughts, cwd, model settings or native journal path is returned.
194
+
195
+ Facts remain separate:
196
+
197
+ - `checkpointEntryId` observes the original owner's appended delivery record.
198
+ It is not a per-entry fsync or power-loss durability receipt.
199
+ - `queued` records an observed extension staging checkpoint.
200
+ - `nativeHandoff` records the native-handoff checkpoint, written before calling
201
+ Pi's send API. It does not prove that call succeeded or the provider accepted
202
+ input.
203
+ - `placementEntryId` identifies the actual native custom-message entry, or null
204
+ when no placement was observed. Placement is not semantic model consumption.
205
+ - `discarded` records an explicit discard checkpoint when one exists.
206
+
207
+ These are historical positive observations, not a claim that an input is still
208
+ queued. Provider-input admission and model consumption remain unknown. Finals,
209
+ settlement and ordinary replies are not fabricated command publications.
210
+
211
+ Receipt reads inspect the exact original session's journal, including its other
212
+ branches. They retain the immutable original sender after ordinary departure,
213
+ reload, compaction and cold reopen. They never depend on a live pending ask.
214
+ Copied history in a fork/new root cannot attest the original root's checkpoint.
215
+ This is separate from `last`, which remains active-branch-only and keeps pinned
216
+ entry paging semantics.
217
+
218
+ ## Supported non-model client
219
+
220
+ Install this package as a normal Node.js dependency and import its supported
221
+ subpath. Pi and TypeBox peers are optional; a companion needs neither them nor
222
+ provider credentials. Importing this entry does not load the extension, CLI,
223
+ TUI, session manager, journal parser or model SDK.
224
+
225
+ ```js
226
+ import { connectIntercom } from "@fitchmultz/pi-subagents/intercom-client";
227
+
228
+ const client = await connectIntercom({
229
+ spawn: false,
230
+ session: {
231
+ name: "my-companion",
232
+ cwd: process.cwd(),
233
+ model: "non-model-client", // A registration label, not a model to initialize.
234
+ rootSessionId: savedSenderRoot,
235
+ incarnation: savedSenderIncarnation,
236
+ },
237
+ });
238
+ try {
239
+ const peers = await client.listSessions();
240
+ // Join selected consent to one exact advertised main; do not select by name.
241
+ const target = peers.find((peer) => peer.id === savedTarget.peerId);
242
+ if (!target) throw new Error("Original target unavailable");
243
+ const result = await client.send(savedTarget.peerId, {
244
+ messageId: savedMessageId,
245
+ text: savedText,
246
+ expectsReply: false,
247
+ delivery: "steer",
248
+ guard: savedTarget, // rootSessionId, peerId, incarnation, applicationGuard
249
+ replyHint: {
250
+ tool: "my_publication_tool",
251
+ commandId: savedCommandId,
252
+ commandFingerprint: savedCommandFingerprint,
253
+ },
254
+ });
255
+ // result describes broker acceptance, not native or provider admission.
256
+ const receipt = await client.receipt(savedTarget.peerId, {
257
+ rootSessionId: savedTarget.rootSessionId,
258
+ messageId: savedMessageId,
259
+ });
260
+ const page = await client.last(savedTarget.peerId, { limit: 16384 });
261
+ // Keep page.entryId and page.nextOffset for subsequent pages.
262
+ } finally {
263
+ await client.disconnect();
264
+ }
265
+ ```
266
+
267
+ The example's saved values belong to the caller's immutable command record, not
268
+ to this client. Persist that record and the actual `client.sessionId` before the
269
+ attempt. `requestedId` is optional and can collide; only the returned current
270
+ identity attests this connection. The client creates no ledger, consent, second
271
+ session or model loop. Registration does not grant human-origin authority.
272
+
273
+ `connectIntercom` requires literal `spawn:false` and never starts a missing or
274
+ old broker. It uses the normal same-user namespace or the caller's explicit
275
+ `PI_INTERCOM_RUNTIME_DIR`; it does not change global Pi configuration or infer a
276
+ fallback namespace. Connection failure rejects. `sendTimeoutMs` and
277
+ `listTimeoutMs` bound native requests; optional `onError(error)` observes socket
278
+ errors. Outstanding operations reject on loss. `isConnected()` and `sessionId`
279
+ report current transport state, not recipient activity or successful work.
280
+ `disconnect()` closes this socket and joins its cleanup; reconnect explicitly
281
+ with a new `connectIntercom` call. There are no reconnect or resend timers.
282
+
283
+ Public sends require an original message ID, explicit delivery, `expectsReply:false`
284
+ and the complete exact guard. Concurrent sends with one pending ID reject rather
285
+ than replacing its acknowledgement waiter. Invalid inputs, old capability
286
+ negotiation, stale consent/context and ambiguous roots never downgrade to an
287
+ ordinary send. The generic hint and opaque fingerprint are optional native
288
+ metadata, not an ordinary ask or a guaranteed reply.
289
+
290
+ After any unconfirmed send, keep the original ID and target. Reconcile only with
291
+ `receipt`; never generate another UUID, reroute or assume that missing means
292
+ unsent. A later explicit new operation is the caller's decision, not an automatic
293
+ retry. Receipt facts retain the original sender after departure. Unsupported old
294
+ brokers or owners fail closed; `publication_unavailable` and `receipt_not_found`
295
+ do not establish no effects. `last` is separately non-waking and pinned by entry
296
+ ID; it is not a correlated command reply. Ordinary local Intercom name resolution,
297
+ asks and topology are unchanged.
298
+
299
+ Only `./intercom-client` and `./package.json` are public package subpaths.
300
+ Private transport, broker, extension and parser paths are not supported imports.
301
+ The Pi resource manifest still loads its compiled extension independently.
302
+
102
303
  ## Persistence and lifecycle
103
304
 
104
305
  Inbound state is checkpointed in the recipient's native session journal. Native
@@ -107,6 +308,19 @@ Queued, admitted, consumed, and replied are distinct states. Reload, compaction,
107
308
  branch changes, shutdown, and fresh-process recovery must not fabricate receipts
108
309
  or adopt another native session's pending messages.
109
310
 
311
+ An already-admitted message ID cannot publish a second copy or revive an answered
312
+ ask. The same applies to replies received by a blocking ask and to deliberately
313
+ replaced/discarded queued messages, including native journal restoration. A new ID
314
+ is a new input even if its text is identical. This recipient-side deduplication is
315
+ not an end-to-end exactly-once guarantee for arbitrary tool side effects.
316
+
317
+ Idle recovery preserves admission order within the non-passive backlog and wakes
318
+ once on its last item; passive breadcrumbs are appended before that waking work.
319
+ The first eligible ask remains the implicit reply target, independently of which
320
+ message triggers the turn. A busy recipient's steer is handed to Pi's native
321
+ queue and consumed at its next tool boundary; no extension probe asks another
322
+ tool to give up the turn.
323
+
110
324
  Shutdown joins admitted inbound work before detaching its native owner. Stale
111
325
  runtime generations must not checkpoint or deliver into a replacement session.
112
326
  The native tests own these contracts; neither a timer nor a source-string test is
@@ -120,9 +334,16 @@ Default files on macOS/Linux:
120
334
  - `/tmp/pi-intercom-<uid>/broker.pid`
121
335
  - `/tmp/pi-intercom-<uid>/broker.spawn.lock`
122
336
 
337
+ Native Android/Termux uses `$PREFIX/tmp/pi-intercom-<uid>` instead of `/tmp`.
338
+ This is shared across the app's sessions, not derived from a session's HOME,
339
+ agent directory, or TMPDIR. Android without a Termux PREFIX uses Node's native
340
+ temporary directory. The separate read-only child observation endpoints use
341
+ the same runtime-root policy.
342
+
123
343
  The runtime directory is owned by the OS user, nonsymlink, and mode 0700; the
124
344
  socket is mode 0600. The broker starts on first use and exits after five seconds
125
- without connected sessions. Concurrent startup uses the shared spawn lock.
345
+ without connected sessions, unless a parked child still holds admitted input; that
346
+ input waits for its main to return and wake the child. Concurrent startup uses the shared spawn lock.
126
347
 
127
348
  `PI_INTERCOM_RUNTIME_DIR` explicitly selects an isolated deployment. It must be
128
349
  an absolute private directory with a socket path within 100 UTF-8 bytes. Invalid
@@ -142,7 +363,7 @@ then verify the expected exact peers from both mains and the helper. The warning
142
363
  only detects old brokers for the current agent directory; it cannot certify that
143
364
  all old sessions elsewhere have migrated.
144
365
 
145
- The predictable `/tmp` namespace is independent of caller HOME/TMPDIR, which is
366
+ The predictable macOS/Linux `/tmp` namespace is independent of caller HOME/TMPDIR, which is
146
367
  needed for same-user discovery across differently configured sessions. A foreign
147
368
  user who pre-creates that path can deny availability, but cannot be adopted as the
148
369
  broker owner: startup refuses foreign or symlink directories. It must not delete
@@ -2,8 +2,8 @@
2
2
 
3
3
  ## Rebuild measurements
4
4
 
5
- The baseline and enforced size ceilings are in
6
- [the rebuild evidence](single-agent-rebuild.md). `node scripts/measure-size.mjs`
5
+ The baseline is in [the rebuild evidence](single-agent-rebuild.md); the only
6
+ enforced ceiling is the 1 MiB total production bytes. `node scripts/measure-size.mjs`
7
7
  measures production source, schemas, generated code and scripts; tests are separate.
8
8
  Line/byte reductions are not measurements of latency, resident memory, provider
9
9
  cost, or throughput.
@@ -33,9 +33,16 @@ These measure extension factory loading, not interactive startup, completion
33
33
  latency, or steady-state memory. The minimum decreased, but the delta is smaller
34
34
  than the baseline's observed first-run spread: this small sample is not a general
35
35
  speedup claim. The long-lived costs remain explicit: a main reconciles receipts
36
- on a 200 ms timer, native actors poll their durable command/attachment state, and
37
- the broker maintains connections/presence. Completed actors stay messageable
38
- while attached; observed owner detachment ends their lifetime. Native lifecycle
36
+ when watched completion inputs change and stats those inputs each second, native
37
+ actors check their parent each second and scan durable commands on change, and the
38
+ broker maintains connections/presence. Completed actors stay messageable
39
+ while attached; observed owner detachment ends their lifetime. On the fork, when native
40
+ working-session capture and broker parking support are both available, a child
41
+ idle for five minutes parks its exact working state and exits, releasing its
42
+ process and heap. On official Pi, or with an older broker that lacks parking
43
+ support, idle children stay resident. A genuine message or continuation restores that exact
44
+ state at the cost of one cold actor start. No retained measurement quantifies
45
+ the memory released or the wake latency. Native lifecycle
39
46
  and live acceptance checks verify disposal/exit, rather than inferring cleanup
40
47
  from a timer or package size.
41
48
 
@@ -71,6 +78,93 @@ conversation per scan, not for completed idle actors. The open watch polls at
71
78
  display lines. Those are display limits, not truncation of the native journal.
72
79
  Children and print/RPC sessions do not install the watcher.
73
80
 
81
+ ### TypeBox host-instance startup (#234)
82
+
83
+ In runtime-only installs without a resolvable package-local or ancestor TypeBox,
84
+ the extension now uses Pi's aliased/virtual `typebox` and `typebox/value` modules
85
+ in the host. Only detached runners resolve absolute imports from the selected
86
+ host package. The Type/Assert export contracts are unchanged; there is no
87
+ package-local runtime TypeBox dependency.
88
+
89
+ An isolated Linux x86_64 RPC probe compared `ae4c363713a6c4b75092b5f15927957e915cf050`
90
+ with the same tree plus the conditional imports in `src/agent/typebox.ts`. Both
91
+ full package copies contained compiled `dist` and only the production stream
92
+ dependencies, with no TypeBox/Pi in their own `node_modules` or ancestors. The
93
+ host was the lockfile's official npm Pi 1.4.0, bundled `dist/bundle/cli.js` SHA-256
94
+ `e79626f2dd6f94aa45d30f3fa63cd84319a6eefcd150b353cfaf274366926774`, using
95
+ `/usr/local/bin/node` v24.22.0. This is not a measurement of the maintained fork.
96
+
97
+ Each fresh process had a private empty HOME/agent/runtime directory, offline
98
+ mode, telemetry disabled, resource discovery disabled, and `PI_TIMING=1`.
99
+ The CLI arguments were `--mode rpc --no-session -ne -ns -np -nc --no-themes
100
+ --approve --offline --tools delegate,agent_runs,intercom -e <package>/dist/index.js
101
+ -e <observer.mjs>`. The observer recorded the active tools and shut down at
102
+ `session_start`; the harness asserted all three tools were registered. No
103
+ subagent or model request was launched. After warming each
104
+ snapshot once, seven A/B rounds alternated execution order (B/A, A/B, ...).
105
+
106
+ | Metric (median; observed range) | Before | After |
107
+ | ------------------------------------------ | ---------------------- | ---------------------- |
108
+ | `dist/index.js` module import, `PI_TIMING` | 241 ms (227–267) | 86 ms (76–102) |
109
+ | Whole-process wall clock | 730.75 ms (617–754) | 571.10 ms (546–595) |
110
+ | Whole-process user + system CPU | 803.89 ms (771–834) | 610.57 ms (581–627) |
111
+ | Peak RSS (`wait4`, MiB) | 141.87 (137.50–146.20) | 127.63 (120.89–129.46) |
112
+
113
+ Individual import times were before `239, 236, 251, 227, 267, 241, 259` ms and
114
+ after `78, 87, 76, 87, 81, 102, 86` ms. The median import dropped 155 ms (64%);
115
+ these are local warm-cache startup observations, not cold-cache, interactive,
116
+ model-latency, or universal performance guarantees. The package smoke separately
117
+ checks host Type/Assert identity through Pi's loader, detached Type/Assert
118
+ identity against the selected host in a production-only install, and actual
119
+ native child execution. The bundled-host identity check rejects the pre-fix
120
+ adapter. Native-loaded development or consumer graphs with independently
121
+ installed or hoisted TypeBox can resolve that copy before Jiti applies aliases;
122
+ this change does not force aliasing in those layouts. Their parent-side adapter
123
+ can now use that copy instead of the host's on-disk copy. Those graphs are outside
124
+ this production-only measurement; the full checkout functional smoke still runs.
125
+
126
+ ### Session attach inventory (#248)
127
+
128
+ Session attachment no longer scans retained completions before returning. It still
129
+ publishes the same durable process identity and generation before ordinary SDK or
130
+ RPC startup completes. A one-shot recovery scan and explicit reconciliation
131
+ paths perform recovery afterward. Managed launcher attachment remains gated on
132
+ accepted host readiness.
133
+
134
+ An isolated official Pi 1.4.0 CLI probe on Linux compared current-main source
135
+ `4906e67` with this change. Five alternating pairs switched into a synthetic
136
+ 42.6 MB, 5,180-entry journal with 1,000 completed runs. The median wait from the
137
+ switch request through an immediate successful `get_state` response changed from
138
+ 755.43 ms to 189.56 ms. The first `agent_runs list` itself became slower because
139
+ it now pays for cold receipt indexing. This is a change to when recovery work
140
+ runs, not an installed macOS or general delegation-latency claim.
141
+
142
+ The [issue 248 evidence](evidence/issue-248/README.md) includes both waits,
143
+ switch-to-first-tool measurements, ranges, profiler results, exact runtime and
144
+ build identities, reproducible commands, and retained synthetic RPC transcripts.
145
+
146
+ ### Empty-owner idle CPU (#238)
147
+
148
+ The issue 238 idle ceiling of 30 ms CPU per 10 s still fails against the
149
+ observer-only floor on official Pi 1.4.0 and the maintained fork. No runtime
150
+ change or waiver is claimed. In the sampled configurations, most of the
151
+ increment is a one-shot pair of V8 memory-reducer collections about 8 s after
152
+ `session_start`. An isolated package that only imports `typebox` showed a
153
+ 128,968-byte C++ heap reading at `session_start`, followed by the same pair of
154
+ collections. That is consistent with Pi's bundled Jiti 2.7.0 loader compiling it
155
+ through `vm.runInThisContext`, which allocates on the C++ heap. Jiti
156
+ can try a native ESM import first when no transformation is needed, so this is
157
+ not a rule for every extension or host import. That two-line package added
158
+ 38–41 ms in the same campaign. Against it,
159
+ the measured `bb38f682` artifact (build `fc084c36`) added 20.7 ms on official
160
+ Pi and 15.0 ms on the fork. Most of that difference is one-shot work right
161
+ after `session_start`, chiefly the intercom broker and owner attachment. In two
162
+ official-host 40 s floor/`0b6da9b` pairs, the later windows of about 10 s each
163
+ stayed within 1.1 ms of the paired floor. There is no fork long run, and this
164
+ is not an indefinite-idle bound. The
165
+ [issue 238 evidence](evidence/issue-238-idle/README.md) has the samples, host
166
+ hashes, native causal chain and limits.
167
+
74
168
  ## Necessary costs
75
169
 
76
170
  - Native Pi owns context loading and model execution. The extension cannot remove
@@ -84,22 +178,32 @@ Children and print/RPC sessions do not install the watcher.
84
178
  with the parent's inference; there is no implicit daily spend limit.
85
179
  - Completed answers and inbound messages can enlarge later prompts. Passive
86
180
  messages and out-of-band `last` reads do not request model inference.
87
- - Owner recovery must verify journal publication and process identity. Stat-only
88
- caches, elapsed time, and mere queue admission cannot replace those checks.
181
+ - Owner recovery must verify journal publication and process identity. Elapsed
182
+ time and mere queue admission cannot replace those checks. Completion-receipt
183
+ and child-usage indexes parse physical LF-published bytes. For native v3
184
+ journals, same-inode growth follows the native append-only writer contract and
185
+ reads from the previous LF boundary; it does not revalidate old bytes. An outside
186
+ edit to old bytes followed by an append on the same inode between observations
187
+ is outside that contract. Changed-stamp same-size edits, shrink, inode replacement,
188
+ and legacy/unclassified headers rebuild the index. Failed scans discard cached
189
+ state. Native load-time migration is covered separately: official Pi 1.4.0
190
+ rewrites in place, whereas fork core `7bc005fa` replaces the inode atomically.
89
191
  - Paged output bounds each response, not all storage or history work. Keep the
90
192
  returned cursor/offset when reading large responses rather than assuming a
91
193
  preview is the full result.
92
194
 
93
195
  ## Accounting
94
196
 
95
- Native child journals are the underlying execution record. Parent attribution
96
- must use finalized native tool-result usage receipts, excluding inherited launch
97
- baseline entries and already attributed child contributions. Read-only inspection
98
- and custom completion notifications are not finalized nested-model charges.
99
- Preparing or discarding a result must not reserve usage as if it were published.
197
+ Native child journals are the underlying execution record. Each child result
198
+ records its direct native contributions once, excluding inherited launch baseline
199
+ entries and contributions already recorded by a predecessor. New calls do not
200
+ attribute child usage to the parent: completion notices, inspection and reload
201
+ are observations, not nested-model charges.
100
202
 
101
- Do not sum a parent's attributed child usage and the same child's journal as two
102
- independent provider requests. An incomplete accounting record does not erase a
203
+ Historical parent journals can retain child usage forwarded by the retired
204
+ foreground and `wait` collectors. When calculating workflow spend, count shared
205
+ continuation journals once and exclude child contributions already included in
206
+ historical parent tool results. An incomplete accounting record does not erase a
103
207
  real completed answer or authorize fabricated usage. Preserve optional reasoning
104
208
  and cache fields according to native Pi semantics rather than adding them twice.
105
209