Skip to content

Commit fbbbc83

Browse files
committed
Re-vendor @intx/hub-sessions at a8bc06ae; retire three deltas upstream absorbed
Upstream now owns what three workbench deltas patched: fab86ca9 emits TurnUsage once per finalized turn (replacing the per-event inference.usage forward, which double-counted the cumulative usage ticks), a1d419c3 serializes collector dispatch at the registry (replacing the collector's own promise chain), and a203a057 + 9e11829f sequence prepare -> INSERT anchor -> emit with frameSent-tagged DeployFrameFailure (replacing the insert-before-frame ordering and DeployFrameNotSentError). The hub's usage sink now consumes TurnUsage, which carries tenant/session/run identity itself. Re-applied onto upstream's files: the adopted deploy front + sourceRef threading (CL-6324), the wire-projection writer (CL-6324), pack acceptance (ownsWorkflowRunRepo, anchorAddressForPackSource, decideTerminalRunFlip), malformed tool-call-name sanitization (CL-6478) and the sealed-run terminal-status backfill (CL-6595), the last now classifying through upstream's classifyTerminalEvent. Tests for the retired deltas go with them; the surviving ones stay. The still-pinned workflow-host gains a second bridging edit: its boot replay reads ownedMessageIds from scanRunsForBoot (upstream f89bb51b), since readOwnedMessageIds no longer exists.
1 parent 7110d93 commit fbbbc83

20 files changed

Lines changed: 1060 additions & 778 deletions

‎VENDORED.md‎

Lines changed: 9 additions & 45 deletions
Large diffs are not rendered by default.

‎apps/hub/src/index.ts‎

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -768,16 +768,13 @@ export async function createHub(config: HubConfig) {
768768
db: withTurnPartPersistGuard(withTurnPartWriteDefaults(db)),
769769
onTurnFinalized: (agentAddress, turn) =>
770770
artifactDeliveryHandlerRef.current?.(agentAddress, turn),
771-
// The vendored `onUsage` forward (see VENDORED.md) — the platform
772-
// collector accumulates turns per session but never persisted
773-
// `inference.usage`; this is the first point tenantId + turnId +
774-
// provider + model + tokens are all in scope at once.
775-
onUsage: (_agentAddress, tenantId, sessionId, usage) => {
771+
// Per-turn usage, emitted once when the collector finalizes a turn.
772+
onUsage: (_agentAddress, usage) => {
776773
void usageSink
777774
.handle({
778775
turnId: usage.turnId,
779-
tenantId,
780-
sessionId,
776+
tenantId: usage.tenantId,
777+
sessionId: usage.sessionId,
781778
provider: usage.provider,
782779
model: usage.model,
783780
tokens: usage.usage,
@@ -1618,9 +1615,8 @@ export async function createHub(config: HubConfig) {
16181615
// (see @corbits/insights' createDrizzleRunTraceReader) — no new storage,
16191616
// same `db` handle every other platform-table reader in this file uses.
16201617
// The sink itself is constructed earlier, alongside `eventCollectors`
1621-
// (see the vendored `onUsage` forward on `createEventCollectorRegistry`
1622-
// above), since that's the only place tenantId/turnId/model land
1623-
// together on an `inference.usage` event.
1618+
// (see the `onUsage` hook on `createEventCollectorRegistry` above),
1619+
// which reports each finalized turn's usage with its run identity.
16241620
app.route(
16251621
`${TENANT_PREFIX}/insights`,
16261622
createInsightsRoutes({

‎packages/insights/src/on-usage-wire.test.ts‎

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@ import { createUsageSink } from "./collector";
44
import { createMemoryUsageStore } from "./store";
55

66
/**
7-
* Exact `UsageForwarded` payload the vendored event-collector emits
8-
* (`vendor/intx/hub-sessions/src/event-collector.ts` `UsageForwarded`).
9-
* Hub `onUsage` (`apps/hub/src/index.ts` ~572) remaps it to `UsageEvent` as:
7+
* Exact `TurnUsage` payload `@intx/hub-sessions`' event collector emits once
8+
* per finalized turn. Hub `onUsage` (`apps/hub/src/index.ts`) remaps it to
9+
* `UsageEvent` as
1010
* `{ turnId, tenantId, sessionId, provider, model, tokens: usage.usage }`
1111
*/
12-
type HubUsageForwarded = {
12+
type HubTurnUsage = {
13+
tenantId: string;
14+
sessionId: string;
15+
runId: string;
1316
turnId: string;
1417
provider: string;
1518
model: string;
@@ -22,31 +25,30 @@ type HubUsageForwarded = {
2225
};
2326
};
2427

25-
function hubOnUsageHandleArg(
26-
tenantId: string,
27-
sessionId: string,
28-
usage: HubUsageForwarded,
29-
) {
28+
function hubOnUsageHandleArg(usage: HubTurnUsage) {
3029
return {
3130
turnId: usage.turnId,
32-
tenantId,
33-
sessionId,
31+
tenantId: usage.tenantId,
32+
sessionId: usage.sessionId,
3433
provider: usage.provider,
3534
model: usage.model,
3635
tokens: usage.usage,
3736
};
3837
}
3938

4039
describe("hub onUsage wire → createUsageSink", () => {
41-
test("inserts a row from the exact hub remapping of UsageForwarded", async () => {
40+
test("inserts a row from the exact hub remapping of TurnUsage", async () => {
4241
const store = createMemoryUsageStore();
4342
let n = 0;
4443
const sink = createUsageSink({
4544
store,
4645
generateId: () => `id-${++n}`,
4746
});
4847

49-
const forwarded: HubUsageForwarded = {
48+
const forwarded: HubTurnUsage = {
49+
tenantId: "tenant-acme",
50+
sessionId: "session-1",
51+
runId: "run-1",
5052
turnId: "turn-wire-1",
5153
provider: "anthropic",
5254
model: "claude-sonnet",
@@ -59,9 +61,7 @@ describe("hub onUsage wire → createUsageSink", () => {
5961
},
6062
};
6163

62-
const status = await sink.handle(
63-
hubOnUsageHandleArg("tenant-acme", "session-1", forwarded),
64-
);
64+
const status = await sink.handle(hubOnUsageHandleArg(forwarded));
6565

6666
expect(status).toBe("inserted");
6767
const rows = await store.listUsageByTenants(["tenant-acme"]);

‎scripts/checks/kill-dates.txt‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,16 @@
1616
apps/sidecar | sawyer | 2026-09-19
1717
vendor/intx/agent | sawyer | 2026-10-26 | d0d56d9f452b78f4b541ad8f4e89f975e8069446bb98f2c8097b90de4b020243
1818
vendor/intx/db | sawyer | 2026-09-19 | 0a4cdb9a8a6ff19d5d4713cbc4f5cc9257aad839b1fa393b2026e6d5afd828b9
19-
vendor/intx/hub-api | sawyer | 2026-09-19 | 4ee1c6b69304cdc4097a50b79b0ce02505dbdc918a36bd679b6f797e61eb5370
20-
vendor/intx/hub-sessions | sawyer | 2026-09-19 | e3344253ed6f2b0998599f9f8712d4e9b0757895b31a3e7df6a245c2bb76af3c
19+
vendor/intx/hub-api | sawyer | 2026-09-19 | f93a383cb5d6acdf50a461b43e4c8991dbdf7e13d34a4e5598e556eaee66308b
20+
vendor/intx/hub-sessions | sawyer | 2026-09-19 | 53addc3090ad9f54bc4bac8fb50ad8d567ccf46f30bb5403d447351cb16b4fb6
2121
vendor/intx/inference | sawyer | 2026-10-26 | 77fec29b078e8d03e686747c70e6b62ac1fd1434db0fb2c1e12e84b6dc71465f
2222
vendor/intx/mail-memory | sawyer | 2026-10-26 | 9f3601a7fb22e2d1c63daa976f3afccbd79af2187c155a0080c0d60c82450b92
2323
vendor/intx/mailbox | sawyer | 2026-10-26 | d36d7ffcc32018571276e4922a8c2714b7ee0bb5deb80b01e73859245975d4c6
2424
vendor/intx/mime | sawyer | 2026-10-26 | d02e5f8f1429eac7c27d3a37eec31111f8a1053c91fbfae77ac58a0d63c823ed
2525
vendor/intx/types | sawyer | 2026-10-26 | ec1de14b859007b4db137da1533d4ce79d11024ad69c8b36938017319d6d8e86
2626
vendor/intx/workflow | sawyer | 2026-09-19 | 4b51b9bd6a124cfaa0c916e2b26c04ac9170618bb092f0c8e1c312263fc84fdf
2727
vendor/intx/workflow-deploy | sawyer | 2026-10-26 | 960a2ae408223649fe8be0e3b9d63f2b0cca25259bc0ae06761bca521bb738e5
28-
vendor/intx/workflow-host | sawyer | 2026-09-19 | 48ae3e34c6f14b99a3a940ede98b119e52dfe7410a86f644d3b78cf6e7d44f4d
28+
vendor/intx/workflow-host | sawyer | 2026-09-19 | 001bea028bf1484f150fa00c9331b897a2db888f6aacd0a109f4e2739854cf74
2929

3030
packages/folded-runs | sawyer | 2026-11-01
3131

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
11
Source: https://github.com/faremeter/interchange (packages/hub-sessions)
2-
Commit: b5580a02fb918eebccc33ded7727ffee781ffbd1 (tag v0.3.0)
2+
Commit: a8bc06ae38661c5e0ed91ded8559bf09f502213d (origin/main, 2026-08-27)
33
License: LGPL-2.1-only (see vendor/intx/LICENSE)
4-
Local modifications: exports map repointed from the upstream intx-src condition to direct TypeScript source resolution (types/default -> ./src/...); dist references removed. CL-5879: event-collector.ts's inference.usage case (previously falling into the "not persisted" default) now forwards {turnId, provider, model, usage} to an optional `onUsage` callback, threaded through event-collector-registry.ts's EventCollectorRegistryConfig as `onUsage(agentAddress, tenantId, sessionId, usage)` — the collector's own turn/tenant state is the only place these identifiers meet an inference.usage event. No persistence added upstream; the app wires the callback to @corbits/insights' usage sink. Terminal-anchor pack acceptance: hub-session-lookups.ts's receiveWorkflowRunPack no longer gates the anchor lookup on liveWorkflowRunStatuses — the ownership gate is the exported pure helper ownsWorkflowRunRepo (self-anchored row with a routable address), so a terminal run can still land the inbox-enqueue and markConsumed-rejection packs that retire mail which arrived in its teardown window. Upstream's live-status gate made that pair unresolvable: pack rejected as path_violation -> ack withheld -> hub redelivers, forever. CL-6324: a third code-sourced deploy front, `deployAdoptedCodeSourcedWorkflow` (plus the `deployAdoptedWorkflowFromSource` service method and its `AdoptingWorkflowDeployer` type), deploys onto shared capacity while ADOPTING an anchor `workflow_run` row the caller already owns. Upstream's two fronts cannot: `deployWorkflowFromSource` INSERTs its anchor (a primary-key collision against a folded run's existing row) and threads no `credentialCipher`, and `deployPreparedCodeSourcedWorkflow` does both correctly but only under the allocation-ownership lock. The new front composes the same private halves (`emitSourceRefDeployFrame`, `buildInertProjectionStepSources`) and follows the prepared front's semantics minus the allocation lock: ownership is the anchor row's own tenant plus self-anchoring, checked before the frame and re-asserted on the guarded UPDATE that stamps `definitionId`/`publicKey`. See VENDORED.md and docs/revendor-inventory.md.
5-
CL-6388: `deployCodeSourcedWorkflow` now INSERTs its anchor `workflow_run` row BEFORE emitting the source-ref deploy frame (publicKey null until the ack stamps it; a failed emit deletes the row). Upstream's frame-then-insert ordering let the spawned child's first refs/heads/events pack push race the deploy ack, and receiveWorkflowRunPack fails closed (path_violation) on the missing anchor row, so every fresh deployment's first events pack was rejected and the durable event log never bootstrapped.
6-
CL-6395: CL-6388's "a failed emit deletes the row" was too broad — any rejection from `emitSourceRefDeployFrame`, including an ack-timeout or socket-drop that fires strictly AFTER the `agent.deploy` frame already reached the sidecar, deleted the anchor row and permanently orphaned an already-spawned child on the missing-anchor `path_violation` path. `ws/sidecar-handler.ts` now exports `DeployFrameNotSentError`, thrown only by a guard clause that runs before `conn.send()` or by `conn.send()` itself throwing synchronously — the sole cases that provably never reached the wire; every other deploy rejection (timeout, disconnect, reconnect takeover, ack-processing failure) is raised through the pending-deploy's `reject()`, which by construction only fires after the send. `deployCodeSourcedWorkflow` deletes the pre-inserted row only on `DeployFrameNotSentError`; any other failure keeps the row and logs one reconciliation line. Also corrects an overclaiming comment in hub-session-lookups.ts: `markTerminal`'s null return means no row in a LIVE status (`deployed` or `running`) matched, not specifically "running".
7-
CL-6478: `event-collector.ts`'s `tool_call` handling in `handleInferenceDone` now runs `block.name` through a new `sanitize-tool-name.ts` module before persisting it. `@intx/inference`'s `decodeToolName` is deliberately total — a hallucinated or provider-mangled function name is returned verbatim rather than throwing — but `encodeToolName` throws when that same name is later put back on the wire to build the next turn's outbound request, so persisting a decoded name unchecked wedged the room forever once the bad name was durable. `sanitizeToolNameForPersistence` round-trips the name through `encodeToolName` before it is written; a name that cannot be re-encoded collapses to a stable `malformed_tool_call` placeholder instead. `@intx/inference` is added to this package's own `package.json` dependencies for the check.
8-
CL-6595: `workflow-run-kind.ts`'s newly-terminal detection in `validatePush` only ever scanned a run's per-event `runs/<runId>/events/<seq>.json` blobs; `enumerateEventBlobs` explicitly skips a run whose events already live in a combined `events.jsonl` (`hasCombined` -> `continue`), so a run sealed from birth — its entire event log, including the terminal event, arriving pre-combined in a single push with no per-event blobs ever landing — was never surfaced as newly terminal and `markTerminal` never fired, leaving `workflow_run.status` stuck live forever despite the run having genuinely finished. `validatePush` now also walks `validateCombinedEventRuns`' `combinedRunIds` and reports a newly-sealed run (absent from the prior tree's combined form) as terminal by reading its combined log's last (terminal, by `checkCombinedStructure`'s own invariant) event. A new `readCommittedWorkflowRunTerminalStatus` export mirrors `readCommittedWorkflowRunLifecycle` but returns the mapped `workflow_run.status` value instead of just live/terminal/absent; `hub-session-lookups.ts`'s pack-receive path now calls it as a same-push defense-in-depth backfill (calling `markTerminal` directly) whenever the committed log proves a run terminal, independent of whether the primary per-push detection caught it.
4+
Local modifications: exports map repointed from the upstream intx-src condition to direct TypeScript source resolution (types/default -> ./src/...); dist references removed. Terminal-anchor pack acceptance: hub-session-lookups.ts's receiveWorkflowRunPack gates the anchor lookup on the exported pure helper ownsWorkflowRunRepo (self-anchored row with a routable address, no liveness requirement) instead of upstream's `status in (deployed, running)`, so a terminal run can still land the inbox-enqueue and markConsumed-rejection packs that retire mail arriving in its teardown window; CL-6361 widens the same lookup to peel a per-step pack source address back to its base run's anchor via anchorAddressForPackSource; CL-6379 classifies an accepted pack's newly-terminal runs through decideTerminalRunFlip so a section occurrence's repo-local child run (turn__<n>) is skipped quietly. CL-6324: a third code-sourced deploy front, deployAdoptedCodeSourcedWorkflow (plus the deployAdoptedWorkflowFromSource service method and its AdoptingWorkflowDeployer / DeployAdoptedWorkflowFromSourceParams types), deploys onto shared capacity while ADOPTING an anchor workflow_run row the caller already owns; it composes upstream's emitSourceRefDeployFrame and a guarded UPDATE. DeployWorkflowFromSourceParams / DeployPreparedCodeSourcedWorkflowParams gain an optional sourceRef threaded through bindAssetAttachmentResolver / bindSourceAttachmentResolver so a per-run source tree on refs/heads/runs/<runId> packs the pinned commit. CL-6324: workflow-probe-gate.ts's PersistFrozenApprovalFn carries the inert projection and createDbFrozenApprovalWriter stamps workflow_definition_version.wire_projection in the same transaction as approved_wire_hash. CL-6478: sanitize-tool-name.ts + event-collector.ts's tool_call case persist only a tool-call name encodeToolName can re-invert (anything else collapses to MALFORMED_TOOL_NAME), so one bad name fails its turn instead of wedging the room; @intx/inference is a dependency for this. CL-6595: workflow-run-kind.ts's validatePush also reports a run sealed from birth (combined events.jsonl with no per-event blobs) as newly terminal, and the new readCommittedWorkflowRunTerminalStatus export backs a same-push markTerminal backfill in hub-session-lookups.ts; both classify through upstream's classifyTerminalEvent. Retired at this pin: the per-event inference.usage forward (upstream fab86ca9 emits TurnUsage once per turn), the collector-level event serialization (upstream a1d419c3 serializes at the registry) and the anchor-before-frame ordering with DeployFrameNotSentError (upstream a203a057 + 9e11829f, isDeployFrameFailure).

‎vendor/intx/hub-sessions/package.json‎

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,22 @@
1515
}
1616
},
1717
"scripts": {
18-
"typecheck": "tsc --noEmit",
19-
"test": "bun test"
18+
"typecheck": "tsc --noEmit"
2019
},
2120
"dependencies": {
22-
"@intx/agent": "0.3.0",
21+
"@intx/agent": "workspace:*",
2322
"@intx/crypto": "0.3.0",
2423
"@intx/db": "workspace:*",
2524
"@intx/hub-common": "0.3.0",
26-
"@intx/inference": "0.3.0",
25+
"@intx/inference": "workspace:*",
2726
"@intx/log": "0.3.0",
28-
"@intx/mime": "0.3.0",
27+
"@intx/mime": "workspace:*",
2928
"@intx/pack-transport": "0.3.0",
3029
"@intx/storage-isogit": "0.3.0",
3130
"@intx/tool-packaging": "0.3.0",
32-
"@intx/types": "0.3.0",
31+
"@intx/types": "workspace:*",
3332
"@intx/workflow": "workspace:*",
34-
"@intx/workflow-deploy": "0.3.0",
33+
"@intx/workflow-deploy": "workspace:*",
3534
"arktype": "catalog:",
3635
"drizzle-orm": "catalog:",
3736
"isomorphic-git": "catalog:",

‎vendor/intx/hub-sessions/src/agent-repo.ts‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,21 @@ export type AgentRepoStore = {
122122
readonly repoStore: RepoStore;
123123
};
124124

125+
/**
126+
* Repo kinds eligible for write-path object GC. Deliberately EXCLUDES
127+
* "workflow-run". The warm-agent mailbox physically expunges `<uid>.eml`
128+
* blobs from the live tree; the raw bytes then persist only through the
129+
* parent commit in git history, and they survive a push ONLY because a
130+
* `workflow-run` repo's objects are never GC'd. Adding "workflow-run"
131+
* here -- above all with a retention other than "keep-history" -- would
132+
* make an expunged message's bytes prunable and silently destroy the
133+
* mailbox audit trail. The mailbox subtree contract in `workflow-run-kind`
134+
* documents this dependency; `agent-repo.test.ts` pins it. Do not add
135+
* "workflow-run" without replacing physical expunge with a tip-reachable
136+
* retain-bytes scheme first.
137+
*/
138+
export const DEFAULT_GC_KINDS = ["agent-state"] as const;
139+
125140
export function createAgentRepoStore(config: {
126141
dataDir: string;
127142
signingKey: { privateKey: Uint8Array; publicKey: Uint8Array };
@@ -177,7 +192,9 @@ export function createAgentRepoStore(config: {
177192
},
178193
authorize,
179194
signingCallback: () => signer,
180-
...(gc === undefined ? {} : { gc: { kinds: ["agent-state"], ...gc } }),
195+
...(gc === undefined
196+
? {}
197+
: { gc: { kinds: [...DEFAULT_GC_KINDS], ...gc } }),
181198
});
182199

183200
const hub: AgentStateHubPrincipal = { kind: "hub" };

0 commit comments

Comments
 (0)