@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.
- package/CHANGELOG.md +63 -2
- package/README.md +36 -5
- package/dist/agent/child-runtime.js +80 -0
- package/dist/agent/contracts.js +15 -24
- package/dist/agent/host.js +3 -16
- package/dist/agent/index.js +212 -192
- package/dist/agent/journal-tail.js +115 -156
- package/dist/agent/launch.js +20 -8
- package/dist/agent/legacy.js +65 -23
- package/dist/agent/notifications.js +138 -47
- package/dist/agent/observation-state.js +246 -0
- package/dist/agent/observation-view.js +258 -0
- package/dist/agent/observation.js +350 -0
- package/dist/agent/observe-card.js +243 -0
- package/dist/agent/observe-chrome.js +85 -0
- package/dist/agent/observe-client.js +285 -0
- package/dist/agent/observe-layout.js +65 -0
- package/dist/agent/observe-reading.js +84 -0
- package/dist/agent/observe-screen.js +400 -96
- package/dist/agent/observe-transcript.js +382 -0
- package/dist/agent/observe.js +250 -43
- package/dist/agent/parking.js +253 -0
- package/dist/agent/process.js +228 -28
- package/dist/agent/receipts.js +92 -20
- package/dist/agent/runner.js +229 -126
- package/dist/agent/shell.js +2 -1
- package/dist/agent/storage.js +47 -12
- package/dist/agent/tool-schemas.js +20 -0
- package/dist/agent/typebox.js +10 -5
- package/dist/agent/usage.js +0 -115
- package/dist/agent/view.js +46 -16
- package/dist/agent/wake.js +99 -0
- package/dist/build-info.js +1 -1
- package/dist/intercom-client.d.ts +26 -0
- package/dist/intercom-client.js +45 -0
- package/dist/pi-intercom/agent-dir.d.ts +1 -0
- package/dist/pi-intercom/bridge-session.js +2 -1
- package/dist/pi-intercom/broker/broker.js +62 -43
- package/dist/pi-intercom/broker/client-last.d.ts +24 -0
- package/dist/pi-intercom/broker/client-last.js +42 -5
- package/dist/pi-intercom/broker/client-park.d.ts +9 -0
- package/dist/pi-intercom/broker/client-park.js +21 -0
- package/dist/pi-intercom/broker/client.d.ts +59 -0
- package/dist/pi-intercom/broker/client.js +55 -29
- package/dist/pi-intercom/broker/connection.d.ts +29 -0
- package/dist/pi-intercom/broker/framing.d.ts +17 -0
- package/dist/pi-intercom/broker/framing.js +10 -7
- package/dist/pi-intercom/broker/guard.js +23 -0
- package/dist/pi-intercom/broker/last.d.ts +30 -0
- package/dist/pi-intercom/broker/last.js +87 -40
- package/dist/pi-intercom/broker/parked.js +107 -0
- package/dist/pi-intercom/broker/paths.d.ts +13 -0
- package/dist/pi-intercom/broker/paths.js +35 -12
- package/dist/pi-intercom/broker/pid.js +5 -1
- package/dist/pi-intercom/broker/server-connection.js +20 -1
- package/dist/pi-intercom/broker/snapshot-reader.d.ts +9 -0
- package/dist/pi-intercom/broker/snapshots.d.ts +29 -0
- package/dist/pi-intercom/broker/targets.d.ts +9 -0
- package/dist/pi-intercom/connection.js +71 -20
- package/dist/pi-intercom/identity.js +6 -5
- package/dist/pi-intercom/inbound-delivery.js +16 -60
- package/dist/pi-intercom/inbound-journal.js +83 -6
- package/dist/pi-intercom/inbound-record.js +18 -1
- package/dist/pi-intercom/inbound.js +22 -37
- package/dist/pi-intercom/index.js +41 -0
- package/dist/pi-intercom/last-message.js +4 -16
- package/dist/pi-intercom/lifecycle.js +88 -3
- package/dist/pi-intercom/outbound.js +5 -15
- package/dist/pi-intercom/reply-tracker.js +7 -6
- package/dist/pi-intercom/reply-wait.js +2 -2
- package/dist/pi-intercom/session-targets.d.ts +24 -0
- package/dist/pi-intercom/tool-registration.js +4 -1
- package/dist/pi-intercom/tool-results.js +5 -7
- package/dist/pi-intercom/tool-schemas.js +12 -26
- package/dist/pi-intercom/types.d.ts +212 -0
- package/dist/pi-intercom/types.js +54 -3
- package/dist/pi-intercom/validation.d.ts +12 -0
- package/dist/pi-intercom/validation.js +3 -0
- package/dist/shared/bounded-json.js +151 -0
- package/dist/shared/compact-cards.js +66 -0
- package/dist/shared/journal-frames.js +3 -6
- package/dist/shared/json-projection.js +267 -129
- package/dist/shared/native-journal.js +123 -56
- package/dist/shared/parking.js +12 -0
- package/dist/shared/post-exit-stdio-guard.js +120 -7
- package/dist/shared/tui-action.js +29 -0
- package/dist/shared/unknown.d.ts +9 -0
- package/docs/code-quality.md +5 -3
- package/docs/development.md +42 -3
- package/docs/intercom-bridge.md +9 -4
- package/docs/intercom.md +228 -7
- package/docs/performance.md +118 -14
- package/docs/reference.md +168 -20
- package/docs/retired-history-maintenance.md +252 -0
- package/package.json +28 -15
- package/scripts/archive-retired-history.mjs +464 -0
- package/scripts/build.mjs +48 -28
- package/scripts/measure-size.mjs +1 -1
- package/scripts/native-package-smoke.mjs +153 -12
- package/scripts/package-smoke.mjs +80 -11
- package/scripts/prepare.mjs +2 -2
- package/scripts/real-pi-live.mjs +377 -0
- package/scripts/real-pi-processes.mjs +6 -1
- package/scripts/real-pi-smoke.mjs +6 -282
- package/scripts/run-tests.mjs +4 -2
- package/scripts/typescript.mjs +35 -0
- package/skills/pi-intercom/SKILL.md +2 -1
- package/skills/pi-subagents/SKILL.md +23 -5
- package/src/agent/child-runtime.ts +105 -0
- package/src/agent/contracts.ts +18 -34
- package/src/agent/host.ts +39 -20
- package/src/agent/index.ts +234 -207
- package/src/agent/journal-tail.ts +141 -153
- package/src/agent/launch.ts +33 -9
- package/src/agent/legacy.ts +85 -31
- package/src/agent/notifications.ts +192 -67
- package/src/agent/observation-state.ts +277 -0
- package/src/agent/observation-view.ts +267 -0
- package/src/agent/observation.ts +409 -0
- package/src/agent/observe-card.ts +304 -0
- package/src/agent/observe-chrome.ts +170 -0
- package/src/agent/observe-client.ts +318 -0
- package/src/agent/observe-layout.ts +79 -0
- package/src/agent/observe-reading.ts +97 -0
- package/src/agent/observe-screen.ts +443 -129
- package/src/agent/observe-transcript.ts +434 -0
- package/src/agent/observe.ts +304 -71
- package/src/agent/parking.ts +341 -0
- package/src/agent/process.ts +280 -37
- package/src/agent/receipts.ts +114 -24
- package/src/agent/runner.ts +227 -149
- package/src/agent/shell.ts +2 -1
- package/src/agent/storage.ts +44 -11
- package/src/agent/tool-schemas.ts +33 -0
- package/src/agent/typebox.ts +12 -7
- package/src/agent/usage.ts +0 -130
- package/src/agent/view.ts +70 -20
- package/src/agent/wake.ts +107 -0
- package/src/intercom-client.ts +108 -0
- package/src/pi-intercom/bridge-session.ts +2 -2
- package/src/pi-intercom/broker/broker.ts +64 -53
- package/src/pi-intercom/broker/client-last.ts +65 -12
- package/src/pi-intercom/broker/client-park.ts +23 -0
- package/src/pi-intercom/broker/client.ts +63 -45
- package/src/pi-intercom/broker/framing.ts +11 -8
- package/src/pi-intercom/broker/guard.ts +33 -0
- package/src/pi-intercom/broker/last.ts +131 -43
- package/src/pi-intercom/broker/parked.ts +130 -0
- package/src/pi-intercom/broker/paths.ts +39 -12
- package/src/pi-intercom/broker/pid.ts +6 -1
- package/src/pi-intercom/broker/server-connection.ts +27 -2
- package/src/pi-intercom/connection.ts +103 -39
- package/src/pi-intercom/identity.ts +8 -5
- package/src/pi-intercom/inbound-delivery.ts +20 -66
- package/src/pi-intercom/inbound-journal.ts +103 -7
- package/src/pi-intercom/inbound-record.ts +21 -4
- package/src/pi-intercom/inbound.ts +32 -49
- package/src/pi-intercom/index.ts +44 -0
- package/src/pi-intercom/last-message.ts +11 -20
- package/src/pi-intercom/lifecycle.ts +117 -14
- package/src/pi-intercom/outbound.ts +7 -16
- package/src/pi-intercom/reply-tracker.ts +8 -6
- package/src/pi-intercom/reply-wait.ts +3 -2
- package/src/pi-intercom/runtime-types.ts +0 -1
- package/src/pi-intercom/tool-registration.ts +4 -1
- package/src/pi-intercom/tool-results.ts +10 -9
- package/src/pi-intercom/tool-schemas.ts +12 -44
- package/src/pi-intercom/types.ts +115 -4
- package/src/pi-intercom/validation.ts +4 -0
- package/src/shared/bounded-json.ts +162 -0
- package/src/shared/compact-cards.ts +118 -0
- package/src/shared/journal-frames.ts +3 -19
- package/src/shared/json-projection.ts +267 -147
- package/src/shared/native-journal.ts +157 -75
- package/src/shared/parking.ts +22 -0
- package/src/shared/post-exit-stdio-guard.ts +165 -7
- package/src/shared/tui-action.ts +39 -0
- package/tsconfig.client.json +8 -0
- package/tsconfig.json +1 -0
- 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
|
|
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.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
|
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
|
package/docs/performance.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
## Rebuild measurements
|
|
4
4
|
|
|
5
|
-
The baseline
|
|
6
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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.
|
|
88
|
-
|
|
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.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
102
|
-
|
|
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
|
|