stitchkit 0.99.0 → 0.100.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/CHANGELOG.md +124 -1
  2. package/dist/agent-runtime/contained-darwin.d.ts +0 -17
  3. package/dist/agent-runtime/contained-darwin.d.ts.map +1 -1
  4. package/dist/agent-runtime/contained-write.d.ts.map +1 -1
  5. package/dist/agent-runtime/invocation-attempt.d.ts +28 -0
  6. package/dist/agent-runtime/invocation-attempt.d.ts.map +1 -0
  7. package/dist/agent-runtime/invocation-failure.d.ts +6 -0
  8. package/dist/agent-runtime/invocation-failure.d.ts.map +1 -0
  9. package/dist/agent-runtime/invocation-payload.d.ts +20 -0
  10. package/dist/agent-runtime/invocation-payload.d.ts.map +1 -0
  11. package/dist/agent-runtime/invocation-read.d.ts +131 -0
  12. package/dist/agent-runtime/invocation-read.d.ts.map +1 -0
  13. package/dist/agent-runtime/invocation-schema.d.ts +378 -0
  14. package/dist/agent-runtime/invocation-schema.d.ts.map +1 -0
  15. package/dist/agent-runtime/invocations.d.ts +52 -0
  16. package/dist/agent-runtime/invocations.d.ts.map +1 -0
  17. package/dist/agent-runtime/projections.d.ts.map +1 -1
  18. package/dist/agent-runtime/run-acquisition.d.ts.map +1 -1
  19. package/dist/agent-runtime/run-execution-state.d.ts +4 -2
  20. package/dist/agent-runtime/run-execution-state.d.ts.map +1 -1
  21. package/dist/agent-runtime/run-execution.d.ts.map +1 -1
  22. package/dist/agent-runtime/run-failure.d.ts.map +1 -1
  23. package/dist/agent-runtime/run-model-attempts.d.ts.map +1 -1
  24. package/dist/agent-runtime/run-operation-lifecycle.d.ts +2 -2
  25. package/dist/agent-runtime/run-operation-lifecycle.d.ts.map +1 -1
  26. package/dist/agent-runtime/run-provider-ledger.d.ts +1 -0
  27. package/dist/agent-runtime/run-provider-ledger.d.ts.map +1 -1
  28. package/dist/agent-runtime/runtime.d.ts +11 -0
  29. package/dist/agent-runtime/runtime.d.ts.map +1 -1
  30. package/dist/agent-runtime/schedule-dispatch.d.ts +9 -2
  31. package/dist/agent-runtime/schedule-dispatch.d.ts.map +1 -1
  32. package/dist/agent-runtime/schedule-records.d.ts +8 -2
  33. package/dist/agent-runtime/schedule-records.d.ts.map +1 -1
  34. package/dist/agent-runtime/schedules.d.ts +5 -0
  35. package/dist/agent-runtime/schedules.d.ts.map +1 -1
  36. package/dist/agent-runtime/sqlite-events.d.ts.map +1 -1
  37. package/dist/agent-runtime/store-create.d.ts.map +1 -1
  38. package/dist/agent-runtime/store-driver-contract.d.ts +9 -0
  39. package/dist/agent-runtime/store-driver-contract.d.ts.map +1 -1
  40. package/dist/agent-runtime/store-event-once.d.ts +37 -0
  41. package/dist/agent-runtime/store-event-once.d.ts.map +1 -0
  42. package/dist/agent-runtime/store-memory.d.ts.map +1 -1
  43. package/dist/agent-runtime/store.d.ts +11 -0
  44. package/dist/agent-runtime/store.d.ts.map +1 -1
  45. package/dist/application/change-subscription.d.ts +36 -0
  46. package/dist/application/change-subscription.d.ts.map +1 -0
  47. package/dist/application/diagnostic-journal-contract.d.ts +3 -6
  48. package/dist/application/diagnostic-journal-contract.d.ts.map +1 -1
  49. package/dist/application/notification-delivery.d.ts +55 -0
  50. package/dist/application/notification-delivery.d.ts.map +1 -0
  51. package/dist/application/notification-outbox-state.d.ts +41 -0
  52. package/dist/application/notification-outbox-state.d.ts.map +1 -0
  53. package/dist/application/notification-outbox.d.ts +7 -1
  54. package/dist/application/notification-outbox.d.ts.map +1 -1
  55. package/dist/application/watch-hub-core.d.ts +14 -84
  56. package/dist/application/watch-hub-core.d.ts.map +1 -1
  57. package/dist/application/watch-hub-source.d.ts +98 -0
  58. package/dist/application/watch-hub-source.d.ts.map +1 -0
  59. package/dist/application/watch-hub.d.ts +10 -2
  60. package/dist/application/watch-hub.d.ts.map +1 -1
  61. package/dist/browser/session-credentials.d.ts +26 -0
  62. package/dist/browser/session-credentials.d.ts.map +1 -0
  63. package/dist/browser/session.d.ts +33 -0
  64. package/dist/browser/session.d.ts.map +1 -0
  65. package/dist/durability/events.d.ts +6 -0
  66. package/dist/durability/events.d.ts.map +1 -1
  67. package/dist/entrypoints/agent-runtime/coding-tools.js +3 -2
  68. package/dist/entrypoints/agent-runtime/harness.js +5 -3
  69. package/dist/entrypoints/agent-runtime/sandbox.js +3 -2
  70. package/dist/entrypoints/agent-runtime/sqlite/bun.js +3 -3
  71. package/dist/entrypoints/agent-runtime/sqlite/node.js +3 -3
  72. package/dist/entrypoints/agent-runtime/testing.js +3 -3
  73. package/dist/entrypoints/agent-runtime.d.ts +3 -0
  74. package/dist/entrypoints/agent-runtime.d.ts.map +1 -1
  75. package/dist/entrypoints/agent-runtime.js +65 -20
  76. package/dist/entrypoints/application/diagnostic-journal.js +2 -1
  77. package/dist/entrypoints/application.d.ts +3 -1
  78. package/dist/entrypoints/application.d.ts.map +1 -1
  79. package/dist/entrypoints/application.js +820 -294
  80. package/dist/entrypoints/files.js +9 -2
  81. package/dist/entrypoints/index.d.ts +2 -0
  82. package/dist/entrypoints/index.d.ts.map +1 -1
  83. package/dist/entrypoints/index.js +199 -0
  84. package/dist/entrypoints/live.js +78 -19
  85. package/dist/entrypoints/node.js +1 -1
  86. package/dist/entrypoints/react.d.ts +1 -1
  87. package/dist/entrypoints/react.d.ts.map +1 -1
  88. package/dist/entrypoints/react.js +59 -16
  89. package/dist/entrypoints/server.js +4 -4
  90. package/dist/{index-sp1tzwnb.js → index-10x2hbx0.js} +5 -2
  91. package/dist/{index-8p52kd5p.js → index-c6bxbsnc.js} +100 -14
  92. package/dist/{index-gxrt5h6v.js → index-cyktc4bp.js} +3 -0
  93. package/dist/{index-1m2thnq0.js → index-eaysmdtq.js} +11 -2
  94. package/dist/{index-jhx828hc.js → index-f4fx2tza.js} +948 -279
  95. package/dist/{index-px2gm2jp.js → index-fhwqwxe0.js} +17 -55
  96. package/dist/{index-e4hkgm5t.js → index-gw8bmyaj.js} +4 -2
  97. package/dist/{index-p3vsv1fb.js → index-hadmp193.js} +14 -5
  98. package/dist/{index-wt743f7f.js → index-k6gpggb9.js} +2 -1
  99. package/dist/index-q3pyb9n0.js +22 -0
  100. package/dist/{index-wsm5w4ee.js → index-vqr7f7v8.js} +51 -3
  101. package/dist/index-zjjyynm3.js +42 -0
  102. package/dist/internal/coalesced-task.d.ts +17 -0
  103. package/dist/internal/coalesced-task.d.ts.map +1 -0
  104. package/dist/internal/darwin-binding.d.ts +19 -0
  105. package/dist/internal/darwin-binding.d.ts.map +1 -0
  106. package/dist/internal/exclusive-lock.d.ts +20 -7
  107. package/dist/internal/exclusive-lock.d.ts.map +1 -1
  108. package/dist/internal/mutation-queue.d.ts +4 -0
  109. package/dist/internal/mutation-queue.d.ts.map +1 -0
  110. package/dist/internal/process-identity.d.ts +1 -1
  111. package/dist/internal/process-identity.d.ts.map +1 -1
  112. package/dist/internal/process-instance.d.ts +26 -0
  113. package/dist/internal/process-instance.d.ts.map +1 -0
  114. package/dist/internal/with-exclusive-lock.d.ts.map +1 -1
  115. package/dist/live/watch-client-state.d.ts +71 -0
  116. package/dist/live/watch-client-state.d.ts.map +1 -0
  117. package/dist/live/watch-client.d.ts +5 -1
  118. package/dist/live/watch-client.d.ts.map +1 -1
  119. package/dist/live/watch-contract.d.ts +12 -0
  120. package/dist/live/watch-contract.d.ts.map +1 -1
  121. package/dist/react/cache-bridge.d.ts +10 -0
  122. package/dist/react/cache-bridge.d.ts.map +1 -1
  123. package/dist/server/realtime.d.ts.map +1 -1
  124. package/llms/agent-runtime.2.txt +102 -0
  125. package/llms/agent-runtime.3.txt +30 -1
  126. package/llms/agent-runtime.txt +8 -3
  127. package/llms/application.2.txt +84 -2
  128. package/llms/application.txt +32 -2
  129. package/llms/files.txt +1 -1
  130. package/llms/live.txt +8 -0
  131. package/llms/react.txt +8 -0
  132. package/llms/server.3.txt +57 -0
  133. package/llms/stitchkit.2.txt +41 -0
  134. package/llms/stitchkit.3.txt +26 -0
  135. package/llms/stitchkit.txt +3 -3
  136. package/llms/upgrading-0.37.0.txt +176 -0
  137. package/llms/{upgrading-0.37.0-0.58.0.txt → upgrading-0.44.0-0.60.0.txt} +229 -169
  138. package/llms/{upgrading-0.59.0-0.71.0.txt → upgrading-0.61.0-0.73.0.txt} +122 -229
  139. package/llms/{upgrading-0.72.0-0.99.0.txt → upgrading-0.74.0-0.100.0.txt} +80 -123
  140. package/llms/upgrading.txt +4 -3
  141. package/llms-full.txt +471 -11
  142. package/llms.txt +10 -9
  143. package/native/darwin-arm64.node +0 -0
  144. package/native/darwin-x64.node +0 -0
  145. package/package.json +1 -1
  146. package/dist/agent-runtime/run-mutation-queue.d.ts +0 -21
  147. package/dist/agent-runtime/run-mutation-queue.d.ts.map +0 -1
package/llms-full.txt CHANGED
@@ -68,11 +68,11 @@ own, recorded as an ADR.
68
68
  | `stitchkit/geo` | server (Bun or Node) | evolving | managed GeoIP reader generations, last-known-good reload and the optional MaxMind adapter |
69
69
  | `stitchkit/oauth` | browser **and** server | evolving | provider-neutral Authorization Code + PKCE request and one-shot callback transaction mechanics |
70
70
  | `stitchkit/google` | server (Bun or Node) | evolving | optional Google code exchange and verified OIDC identity adapter |
71
- | `stitchkit/observability` | server | stable<br>_redefined in 2 of the 44 minors since 0.56.2, most recently 0.92.0_ | request/tool event projections — `createObservability`, trace context, sanitisation |
71
+ | `stitchkit/observability` | server | stable<br>_redefined in 2 of the 45 minors since 0.56.2, most recently 0.92.0_ | request/tool event projections — `createObservability`, trace context, sanitisation |
72
72
  | `stitchkit/testing` | tests on Bun or Node | stable | in-process generated clients over a real Fetch handler, plus the managed-resource conformance kit |
73
73
  | `stitchkit/declaration` | browser + build and deployment tooling (Bun or Node) | evolving | `ProjectDeclarationSchema` — the one machine-readable statement a repository makes about itself |
74
74
  | `stitchkit/react` | browser + server rendering | stable | `createCursorQuery`, `createCacheBridge`, QueryClient and `ApiError` retry policy |
75
- | `stitchkit/agent-runtime` | server | evolving<br>_redefined in 22 of the 44 minors since 0.56.2, most recently 0.97.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
75
+ | `stitchkit/agent-runtime` | server | evolving<br>_redefined in 22 of the 45 minors since 0.56.2, most recently 0.97.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
76
76
  | `stitchkit/agent-runtime/sandbox` | server | evolving | optional Linux Bubblewrap sessions, durable workspaces and a host HTTP credential gateway; see [sandbox guide](sandbox.md) |
77
77
  | `stitchkit/agent-runtime/testing` | tests on Bun or Node | evolving | credential-free replay, scripted provider faults, deterministic race controls and the agent-store conformance kit |
78
78
  | `stitchkit/agent-runtime/harness` | server | evolving | resource-aware process-local facade over the canonical Agent runtime; supervision stays outside |
@@ -82,7 +82,7 @@ own, recorded as an ADR.
82
82
  | `stitchkit/agent-runtime/sqlite/bun` | server (Bun) | evolving | durable built-in SQLite store for the agent runtime |
83
83
  | `stitchkit/agent-runtime/sqlite/node` | server (Node ≥ 22.5) | evolving | durable built-in SQLite store for the agent runtime |
84
84
  | `stitchkit-tui` | terminal (Bun) | evolving | optional official OpenTUI host over a caller-composed headless runtime |
85
- | `stitchkit/application` | browser + server | evolving<br>_redefined in 9 of the 44 minors since 0.56.2, most recently 0.96.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
85
+ | `stitchkit/application` | browser + server | evolving<br>_redefined in 10 of the 45 minors since 0.56.2, most recently 0.100.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
86
86
  | `stitchkit/application/grammy` | server | evolving | a grammY bot as application resources — configuration, then polling with updates admitted by batch — and the webhook adapter |
87
87
  | `stitchkit/application/opentelemetry` | server | evolving | maps application snapshots onto an injected OpenTelemetry `Meter` |
88
88
  | `stitchkit/application/schemas` | browser + server | evolving | the application's snapshot, health and shutdown schemas alone, without the kernel |
@@ -3245,6 +3245,47 @@ hour and drops again starts its next retry at `minDelayMs` rather than the
3245
3245
  ceiling it reached last time. Aborting the signal ends the iteration promptly,
3246
3246
  including in the middle of a wait.
3247
3247
 
3248
+ ## Operations belong to a login lifetime
3249
+
3250
+ Use `createSessionScope` when a client can log out or switch accounts. The scope is
3251
+ host-owned and context is immutable. Replace it for **every** login, even to the
3252
+ same account. Capture once for an entire compound operation:
3253
+
3254
+ ```ts
3255
+ import { createClient, createSessionScope } from 'stitchkit'
3256
+
3257
+ const sessions = createSessionScope<{ accountId: string }>()
3258
+ const operation = sessions.replace({ accountId: 'account-a' })
3259
+ const client = createClient(contract, {
3260
+ baseUrl,
3261
+ fetch: operation.bindFetch(fetch, () => ({ Authorization: tokenFor(operation) })),
3262
+ })
3263
+ const value = await operation.run(() => client.read())
3264
+ operation.deliver(() => applyResult(value))
3265
+ ```
3266
+
3267
+ Capture before the first request, keep the same operation for a second request or
3268
+ one explicitly allowed auth retry, and guard the **actual** final transport send
3269
+ or local state change. An async delivery callback needs another check after its
3270
+ await; `deliver` itself is synchronous. Guard error delivery too. Cancellation is
3271
+ additional protection and cannot undo server effects. `SessionExpiredError` means
3272
+ discard the stale operation, not refresh a new user's credentials. For raw/stream
3273
+ responses, guard each consumed chunk and final delivery; `run` returning a stream
3274
+ does not mean its future contents have been consumed.
3275
+
3276
+ `createSessionCredentials({ scope, refresh, storage })` coordinates the same scope.
3277
+ `login(context, credentials)` creates a generation, `read(operation)` resolves only
3278
+ that generation's credentials, and concurrent `refresh(operation)` shares one
3279
+ flight. `logout()` invalidates memory immediately and resolves after its ordered
3280
+ storage clear (or a newer login superseding that clear). Handle storage and capacity
3281
+ errors explicitly; a rejected write is not proof of durable login/logout. Refresh
3282
+ network work never holds the storage queue. Callers decide whether a failed command
3283
+ may be retried once; do not replay a multi-step callback or treat any `403` as expiry.
3284
+
3285
+ This is per-instance coordination. Route all writes through it; shared tabs/processes
3286
+ need storage-level locking. Replace the local session counter and refresh queue when
3287
+ adopting these primitives instead of retaining two independent lifetimes.
3288
+
3248
3289
 
3249
3290
  ==============================================================================
3250
3291
  # Guide: MCP & agents (docs/guide/mcp-and-agents.md)
@@ -4847,7 +4888,7 @@ description: Configure Stitchkit's optional durable history, stream loop, run co
4847
4888
  type: architecture
4848
4889
  status: active
4849
4890
  created: 2026-08-22
4850
- updated: 2026-09-26 16:00 +07:00
4891
+ updated: 2026-09-28 18:39 +07:00
4851
4892
  ---
4852
4893
 
4853
4894
  # Agent application runtime
@@ -5464,9 +5505,14 @@ has invoked a tool is never automatically replayed.
5464
5505
  `createAgentScheduleService` persists `at`, `after` and `every` input. Repeating
5465
5506
  schedules require an explicit IANA time zone. Dispatch uses a stable
5466
5507
  `schedule:<id>:<occurrence>` idempotency key, and restart lateness is recorded.
5467
- Resolve `void` after durable admission; return `{ status: 'retry', reason }` for a
5508
+ Resolve after durable admission; return `{ status: 'retry', reason }` for a
5468
5509
  transient refusal or `{ status: 'terminal', reason }` to stop the entire schedule in
5469
- `failed` state. Throws retry too. Backoff is 1, 2, 4, 8, 16, 32, then 60 seconds,
5510
+ `failed` state. Only those two shapes are failures: any other returned value — a
5511
+ queue length, a receipt — means the callback returned, so it delivered. Throws retry
5512
+ too. `maxAttempts` (default 1000, about sixteen hours at the retry ceiling) bounds
5513
+ one occurrence: a one-off schedule then becomes `failed`, a recurring one gives up
5514
+ that occurrence (`schedule/failed` with `skipped: true` and the `nextAt` it moves
5515
+ to) and keeps its cadence. A failure reason is kept to 1000 characters. Backoff is 1, 2, 4, 8, 16, 32, then 60 seconds,
5470
5516
  capped at 60 seconds and persisted across restart. `retryAt`, `attempts` and `lastError`
5471
5517
  show retry diagnostics; success resets them. `nextAt` and `lateByMs` retain the original
5472
5518
  occurrence time. Recurring schedules fire once after an idle stretch and advance to the
@@ -6273,6 +6319,108 @@ can provision those parents; the optional `cleanup(context)` removes them again,
6273
6319
  whether the scenario passed or failed. A factory that owns no fixture state ignores the argument and
6274
6320
  keeps working unchanged.
6275
6321
 
6322
+ ## Plain completions and shared invocation receipts
6323
+
6324
+ Use `createModelInvocationLedger` from `stitchkit/agent-runtime` when a direct text
6325
+ completion and an agent must share causal provider evidence. It uses the existing
6326
+ runtime store, not a second database. The completion path sends exactly one user
6327
+ prompt, no framework instructions and no tools. It never starts an agent loop.
6328
+
6329
+ ```ts
6330
+ import { createModelInvocationLedger } from 'stitchkit/agent-runtime';
6331
+
6332
+ const invocations = createModelInvocationLedger({
6333
+ store, // the same AgentRuntimeStore object passed to createAgentRuntime
6334
+ models, // an existing defineModelRegistry result
6335
+ payloadKey, // 32 bytes from the host's secret store; retain separately from archives
6336
+ authorize: async ({ context, conversationId, action, trace }) => {
6337
+ const session = await authenticate(context);
6338
+ await authorizeConversation(session, conversationId, action, trace);
6339
+ return { subject: session.subject }; // verified identity, never a body field
6340
+ },
6341
+ });
6342
+
6343
+ const completion = await invocations.complete({
6344
+ conversationId: 'evaluation-42',
6345
+ idempotencyKey: 'summary-case-3',
6346
+ trace: {
6347
+ operationId: 'summary-42', purpose: 'summarize', project: 'example',
6348
+ parent: { traceId: 'trace-42' }, experiment: 'evaluation', case: 'case-3',
6349
+ },
6350
+ prompt: 'Summarize these supplied facts.',
6351
+ models: ['primary', 'fallback'], // registry keys; repeat a key for an explicit retry
6352
+ settings: { maxOutputTokens: 500 },
6353
+ timeoutMs: 30_000,
6354
+ }, authenticatedContext);
6355
+
6356
+ // Add to the existing createAgentRuntime configuration:
6357
+ const receiptConfig = {
6358
+ ledger: invocations,
6359
+ trace: ({ run }) => ({
6360
+ operationId: run.id, purpose: 'assistant-turn', project: 'example',
6361
+ }),
6362
+ };
6363
+ // createAgentRuntime({ ...existingConfig, invocations: receiptConfig });
6364
+
6365
+ const page = await invocations.read({
6366
+ conversationId: 'evaluation-42', limit: 100,
6367
+ }, authenticatedContext);
6368
+ // Continue with fromSeq: page.nextSeq while it is defined.
6369
+ ```
6370
+
6371
+ `complete` returns `invocationId`, `outcome` and, on success, `text`. `duplicate`
6372
+ returns the existing ID without provider execution or replaying plaintext. Changed
6373
+ input, trace or caller under the same key is a conflict. Model attempts are explicit
6374
+ and bounded to eight; SDK retries are disabled. Only provider failures trigger the
6375
+ next attempt. Abort, audit refusal and configuration errors do not. An admitted call
6376
+ whose process died stays claimed: inspect the receipt before choosing a new key.
6377
+
6378
+ The agent retains its existing prompt, tools and loop. Both modes produce
6379
+ `invocation/started`, `provider/request`, `provider/response` and
6380
+ `invocation/finished` records through `read`. Each attempt has its own ID; response
6381
+ usage is counted once and keeps `unavailable` distinct from measured zero. Requested
6382
+ registry selection, sent adapter identity and effective provider identity are
6383
+ separate. Effective identity is null when not reported. A gateway's hidden retries
6384
+ are not visible as additional attempts.
6385
+
6386
+ `currentModelInvocationAttempt()` returns the causal context inside a provider's
6387
+ `fetch` callback or streamed read. Use it to join a host transport audit to
6388
+ `operationId`, `invocationId`, `attemptId`, optional `runId` and `step`.
6389
+ `onAttempt` is an awaited admission hook; a rejected hook prevents the provider call.
6390
+ SDK payload hashes are not represented as hashes of HTTP wire bytes.
6391
+
6392
+ Prompts and output are AES-256-GCM payload artifacts. `read` returns references and
6393
+ hashes; `readPayload({ conversationId, artifactId }, context)` separately authorizes
6394
+ `read-payload` and checks authentication tags and hashes. `payloadKey` must be 32
6395
+ bytes and is copied at construction. Keep it outside the event store/backups; retain
6396
+ the appropriate key when restoring an archive. `maxPayloadBytes` defaults to 4 MiB
6397
+ (range 1 KiB–64 MiB). Streaming output evidence keeps parts up to half of it; past
6398
+ that the evidence carries `truncated: { parts, sha256 }` over the whole stream, and
6399
+ the answer itself is never cut. A receipt that cannot be written never changes an
6400
+ outcome: `complete()` returns it with `receiptError` beside it, and a succeeded
6401
+ answer keeps its `text`. Request headers
6402
+ are excluded. Protect existing agent history independently: enabling receipts does
6403
+ not encrypt its ordinary conversation records.
6404
+
6405
+ The built-in memory and SQLite stores implement `appendEventOnce`. Custom stores
6406
+ must implement its once-only check and append atomically, returning the original
6407
+ event on a duplicate key scoped by conversation and kind. A factory without this
6408
+ capability refuses receipt setup. The driver-backed implementation finds the keyed
6409
+ event by its identity through the driver's optional `events.find` — one index probe
6410
+ in SQLite — so admission costs the same on a long conversation as on a new one; a
6411
+ custom driver without `find` falls back to paging the log in one transaction.
6412
+ `store.findEventOnce({ conversationId, kind, key })` reads the same event back.
6413
+ Invocation payload artifacts are keyed by their artifact id the same way.
6414
+
6415
+ ### Migration from direct SDK completions
6416
+
6417
+ **Who must act:** applications opting into shared completion/agent receipts.
6418
+ Replace the direct `generateText` call with `invocations.complete`, configure host
6419
+ authorization and a retained payload key, and pass the same ledger in the agent's
6420
+ `invocations` option. Use the returned invocation ID and the paginated `read` method
6421
+ for receipt queries; do not import runtime internals. Available since 0.100.0;
6422
+ existing runtime configurations need no migration.
6423
+
6276
6424
 
6277
6425
  ==============================================================================
6278
6426
  # Guide: Managed application kernel (docs/guide/application-kernel.md)
@@ -6922,7 +7070,12 @@ refusals are explicit; accepted ordered frames are never evicted.
6922
7070
  The absolute path's parent must already exist and be operator-controlled. One manager owns it via
6923
7071
  an exclusive `.lock`; new files use mode `0600` by default. `maxFiles` includes the active file,
6924
7072
  and a non-newline startup tail is rotated intact rather than guessed or repaired. An abrupt process
6925
- death may leave the lock for an operator to remove only after proving the former owner is gone.
7073
+ death may leave a lock. The default `refuse` policy leaves recovery to the operator;
7074
+ `lock: 'reclaim-stale'` compares machine, boot and process birth identity before
7075
+ recovery. A reused PID cannot impersonate a modern owner. Legacy records with a
7076
+ live PID, foreign namespaces and unavailable identity refuse automatic recovery;
7077
+ see the upgrading guide for quiescent legacy recovery. The same mechanism owns the
7078
+ short-lived reclaim guard.
6926
7079
 
6927
7080
  `flush()` means every accepted append through that call's boundary settled. It is not `fsync`, a
6928
7081
  durable receipt, exactly-once execution or remote delivery. Timeout/cancellation bound only the
@@ -7253,6 +7406,62 @@ holder — after which the heartbeat wins and the lock is abandoned. Temporary f
7253
7406
  the store's first update. Ledger corruption may be declared reconstructable;
7254
7407
  an outbox must fail closed rather than silently discard pending delivery.
7255
7408
 
7409
+ ## Recovering change subscriptions
7410
+
7411
+ `changeSubscriptionResource` is an optional resource over a consumer-owned driver.
7412
+ It uses a finite declared key set, subscribes before reading, reconciles after
7413
+ reconnect and periodically repairs lost hints. `reconcileIntervalMs` is explicit;
7414
+ notifications alone cannot prove freshness. Within a key, hints coalesce into one
7415
+ trailing read and a failed read retries with backoff. Use the supplied
7416
+ `context.commit(() => publish(value))` after awaits to reject an obsolete connection.
7417
+
7418
+ Drivers implement `connect(signal)` and return `subscribe({ signal, hint,
7419
+ disconnected })` plus `close()`. Subscribe must establish the notification boundary
7420
+ before resolving. A database LISTEN driver owns its connection, channels, decoding
7421
+ and transaction ordering; no SQL or database driver is included here. The resource
7422
+ owns reconnect, deadlines and shutdown. Drivers must honor abort. If one ignores it,
7423
+ its slot remains occupied until settlement; deadlines cannot safely manufacture
7424
+ successful cleanup or permit an overlapping connection.
7425
+
7426
+ For watched reads, set `reconcileIntervalMs` on the existing `createWatchHub` rather
7427
+ than building a second read loop. Its per-key machinery is shared with managed
7428
+ subscriptions. Limits default to 1024 sources and 1024 subscribers per hub; set
7429
+ explicit finite values for larger workloads. Supply a server-verified admission
7430
+ scope to `attach` and abort it on logout, permission revocation or session replacement.
7431
+ The read callback receives the trusted scope as its third argument. A client owner
7432
+ ID in arguments is not authentication.
7433
+
7434
+ ## Multi-action notification delivery
7435
+
7436
+ Opt into `delivery: { version, actions, project }` on `createNotificationOutbox`.
7437
+ The pure `actions(payload)` selector runs at enqueue; the versioned plan remains
7438
+ fixed throughout retry. Each `send` receives its action and a stable idempotency
7439
+ key; return a small JSON receipt such as a remote message or uploaded object ID.
7440
+ An action that returned is confirmed and is never sent again: a result that is not
7441
+ JSON, or does not fit the state's byte budget, is kept as `receipt: null` with
7442
+ `unrecorded: 'not-json' | 'too-large'` and reported to `onError`.
7443
+ `project(notification, receipts)` applies all confirmed results locally and must
7444
+ be idempotent. Checkpoints and the final projection timestamp live in the existing
7445
+ atomic StateStore; completed receipts retain them within retention/byte limits.
7446
+
7447
+ This supports, for example, text plus attachment delivery with a local timeline
7448
+ projection, or upload plus publication with a local asset index. These are protocol
7449
+ examples, not claims of deployed consumer adoption. A failure in projection does
7450
+ not repeat confirmed sends. A crash after remote success but before receipt commit
7451
+ is ambiguous: the provider must deduplicate the action key or reconcile its result.
7452
+ A projection can also repeat if its completion checkpoint is lost.
7453
+
7454
+ A plan keeps its actions from enqueue, so an older version needs only its
7455
+ projection to finish: a new executor lists it in `retiredVersions: { v1: project }`
7456
+ until the queue has drained. A plan version the executor does not know is reported
7457
+ to `onError` as `NotificationPlanVersionError` and waits — at least a poll interval,
7458
+ spending no attempt, without `classify` — for a process that knows it: the other
7459
+ side of a rolling deploy, the version a rollback returns to, or this one once it
7460
+ lists the version. Whole-state file and memory stores
7461
+ remain supported. Atomic enqueue with an unrelated business transaction is not
7462
+ provided by this contract; retain the application's transactional adapter when that
7463
+ boundary is required. Send and projection callbacks own their external deadlines.
7464
+
7256
7465
 
7257
7466
  ==============================================================================
7258
7467
  # Guide: Optional process sandbox (docs/guide/sandbox.md)
@@ -9728,6 +9937,63 @@ through `emitTo`; a broadcast through `realtime.to(room).emit` reaches the
9728
9937
  socket immediately, replay or not, so keep room traffic whose order matters
9729
9938
  relative to the snapshot on the registry.
9730
9939
 
9940
+ ## Session-scoped watches and query cache
9941
+
9942
+ Pass the captured login operation as `createWatchClient(contract, { transport,
9943
+ session })`. It adds a lifetime nonce to watch keys and detaches on invalidation;
9944
+ a late frame for a previous login cannot match a new client's key on the same socket.
9945
+ Both client and server must support the new optional `WatchKey.instance` field.
9946
+ The nonce is not authorization: the server still admits a verified identity and
9947
+ uses `hub.attach(subscriber, { key: verifiedScope, signal: revocationSignal })`.
9948
+ The scope key must change with session and permission boundaries.
9949
+
9950
+ One scope and one question share one source and one read, however many client
9951
+ instances watch it: the hub routes each frame to every instance under its own
9952
+ key. A new tab of the same session costs a route, not a read.
9953
+
9954
+ ### Capacity
9955
+
9956
+ A hub admits at most `maxSources` sources (1024) and `maxSubscribers` attached
9957
+ subscribers (1024); a client holds at most `maxKeys` keys (1024) and
9958
+ `maxListenersPerKey` listeners per key (128). Every excess is a refusal, never
9959
+ unbounded state: `open` answers `{ accepted: false, reason }` — including for a
9960
+ subscriber attached over capacity, whose `attach` returns a watcher that refuses
9961
+ every `open` — and a client's `subscribe` throws. A client handle takes its key
9962
+ when it subscribes, so handles a render creates and never subscribes cost
9963
+ nothing. A connection handler that throws closes that connection and is logged;
9964
+ the server goes on. A subscriber attached over capacity is not tracked: its
9965
+ refusals stand until it reconnects.
9966
+
9967
+ A read that fails sends its subscribers `unavailable` / `source-error` with the
9968
+ `code` and `message` of an `AppError`, which is written for its caller. Any other
9969
+ error reaches only the hub's `logger`; the browser reads `The watched read failed`. Set larger finite limits for larger workloads, and
9970
+ `reconcileIntervalMs` when a lost invalidation must recover without a reconnect.
9971
+
9972
+ `createCacheBridge` can bind watched handles directly:
9973
+
9974
+ ```ts
9975
+ const bridge = createCacheBridge({
9976
+ socket, queryClient, handlers: {}, session,
9977
+ watched: [{
9978
+ handle: watches.list({ folderId }),
9979
+ queryKey: ['items', session.id, folderId],
9980
+ state: frame => showWatchState(frame),
9981
+ }],
9982
+ })
9983
+ bridge.connect()
9984
+ ```
9985
+
9986
+ A value updates that exact query; non-live state marks it stale while retaining its
9987
+ value. The existing phases (`opening`, `live`, `unavailable`, `resync-required`)
9988
+ remain the status model. Do not replace unavailable data with an empty collection.
9989
+ Session invalidation removes bound queries. Include the session identity in query
9990
+ keys and guard any application-level side effects with that same operation.
9991
+
9992
+ Use existing shared watches for paginated lists and selected-detail queries; bind
9993
+ each exact argument set to its query key. No global domain/query registry or new
9994
+ UI components are needed. Reconnect and revision/delta recovery remain owned by the
9995
+ watch client; silent lost notifications are repaired by hub reconciliation.
9996
+
9731
9997
 
9732
9998
  ==============================================================================
9733
9999
  # Guide: Live data (docs/guide/live.md)
@@ -13035,6 +13301,75 @@ analytics policy belong to the application.
13035
13301
 
13036
13302
  # Upgrading stitchkit
13037
13303
 
13304
+ ## Released migration: 0.100.0
13305
+
13306
+ ### watch capacity and session/live/delivery adoption
13307
+
13308
+ **Who must act:** watch consumers exceeding 1024 hub sources, 1024 hub subscribers,
13309
+ 1024 client keys or 128 listeners per key. These previously unbounded collections
13310
+ now refuse excess admission. Choose explicit positive finite limits, for example:
13311
+
13312
+ ```ts
13313
+ // Before: no hub-wide capacity bounds.
13314
+ createWatchHub({ read, watchable });
13315
+ // After: explicitly budget a larger workload.
13316
+ createWatchHub({ read, watchable, maxSources: 4096, maxSubscribers: 4096 });
13317
+ // Client limits are configured independently.
13318
+ createWatchClient({ ...clientConfig, maxKeys: 2048, maxListenersPerKey: 256 });
13319
+ ```
13320
+
13321
+ The remaining steps apply only to applications opting into session lifetimes,
13322
+ scoped watched reads, managed subscriptions or multi-action notification delivery.
13323
+ Existing clients within the limits and single-send outboxes need no migration.
13324
+
13325
+ Replace local session generation counters with one `createSessionScope`; share it
13326
+ with `createSessionCredentials`. Guard transport attempts, complete asynchronous
13327
+ operations and final synchronous delivery. Route credential persistence through
13328
+ one coordinator. Keep provider-specific error classification and bounded command
13329
+ retry in the application. Coordination is per client instance.
13330
+
13331
+ Upgrade both watch endpoints before enabling session-scoped watches: strict older
13332
+ servers reject the new optional `WatchKey.instance`. Server-verified scope controls
13333
+ sharing; the client instance is only routing identity. Bind exact watched handles
13334
+ to session-qualified cache keys. Existing delta/reconnect behavior stays in place.
13335
+ For applications needing more than 1024 hub sources/subscribers or client keys, set
13336
+ explicit finite capacity options. Enable a reconciliation interval when lost hints
13337
+ must recover without a reconnect.
13338
+
13339
+ For multi-action outboxes, enqueue new items with a versioned delivery plan and
13340
+ return small JSON receipts from `send`. Existing queued single-send items should
13341
+ be drained before enabling a delivery executor on that queue, or migrated under
13342
+ the store's atomic transition. When a new executor version ships, list the old
13343
+ version's projection in `delivery.retiredVersions` until its queued items drain;
13344
+ a plan version with no executor waits, reported to `onError` as
13345
+ `NotificationPlanVersionError` (from 0.100.1).
13346
+ Make projection idempotent. Provider idempotency/reconciliation remains necessary
13347
+ for a crash between remote success and checkpoint. These APIs are available starting with 0.100.0.
13348
+
13349
+ ### exclusive lock recovery
13350
+
13351
+ New lock and reclaim-guard records include `owner.process`: kernel boot identity,
13352
+ process birth token and namespace. `readDiagnosticJournalLockDiagnosis(error)`
13353
+ adds `identity` to distinguish matched lifetime evidence, PID reuse, a different
13354
+ boot, an absent PID, a legacy record or unavailable identity. Legacy `alive` only
13355
+ means the PID is present; it does not prove that the recorded writer still exists.
13356
+
13357
+ **Who must act:** deployments recovering a legacy lock with a reused live PID,
13358
+ or bundling file-lock consumers into a standalone Darwin executable. Legacy
13359
+ records cannot safely infer their missing boot/start identity from `acquiredAt`.
13360
+ Stop all writers to that resource, verify exclusive ownership, preserve the old
13361
+ lock for incident evidence outside the active lock path, and restart with the
13362
+ new package. Never delete a lock merely because it is old. New acquisitions
13363
+ write the modern record; no journal data migration is needed.
13364
+
13365
+ Darwin distributions must retain the package's architecture-matched native
13366
+ `.node` asset for process birth measurement, as for contained filesystem access.
13367
+ If native/proc identity cannot be read, new locks explicitly record null and
13368
+ recovery refuses rather than treating unknown ownership as death. From 0.100.1 a
13369
+ Linux kernel without time namespaces (before 5.6) records the PID namespace alone
13370
+ instead of null, so its locks recover like any other. Existing
13371
+ `refuse` policy and the separately configured ownerless-file grace are unchanged.
13372
+
13038
13373
  ## Released migration: 0.99.0
13039
13374
 
13040
13375
  ### structural alternatives in flattened tool schemas
@@ -13050,13 +13385,20 @@ Colliding object/array fields retain their complete structural alternatives:
13050
13385
  { type: ['object', 'array'] }
13051
13386
  // After: structural alternatives live inside the property schema.
13052
13387
  { anyOf: [
13053
- { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] },
13054
13388
  { type: 'array', items: {
13055
13389
  type: 'object', properties: { id: { type: 'string' } }, required: ['id'],
13056
13390
  } },
13391
+ { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] },
13057
13392
  ] }
13058
13393
  ```
13059
13394
 
13395
+ Alternatives come in kind order, independent of variant order. The same shape
13396
+ covers every collision of different kinds, scalars included: `string` against
13397
+ `string[]` becomes `{ anyOf: [{ type: 'array', items: { type: 'string' } }, { type:
13398
+ 'string' }] }`. A nullable field keeps `null` in each alternative's `type`, and
13399
+ differing nested object unions remain nested `anyOf` inside the alternatives. Update
13400
+ snapshots to this order.
13401
+
13060
13402
  Traverse each nested alternative when inspecting schemas and regenerate schema
13061
13403
  snapshots. Verify the actual manifest with the chosen provider. Keep flatten
13062
13404
  enabled when a top-level discriminated union needs to be advertised as an
@@ -17000,6 +17342,32 @@ can emit / propagate a `traceparent` (see `HttpClientConfig.trace`).
17000
17342
 
17001
17343
  ---
17002
17344
 
17345
+ ### Session lifetime and credential coordination
17346
+
17347
+ `createSessionScope` (`stitchkit`) returns the generic `SessionScope`: `replace(context)`
17348
+ creates a new login lifetime (including same-account relogin), `capture()` returns
17349
+ its `SessionOperation<T>`, `clear()` invalidates it, `stop()` permanently closes
17350
+ the host, and `owns(operation)` answers whether an operation is this scope's current
17351
+ one — `current()` alone cannot tell an operation of another scope. Operations expose `id`, `generation`, `context`, `signal`, `current()` and
17352
+ `assertCurrent()`. `run(callback)` checks both success and failure across an await;
17353
+ `deliver(callback)` admits a synchronous final send/state change or returns false.
17354
+ `bindFetch(fetch, headers?)` guards every attempt and scopes its abort signal. Wrap
17355
+ the complete client call in `run` too: headers are not the response body boundary.
17356
+ Expired work throws `SessionExpiredError`, not a refresh instruction; when the work
17357
+ itself failed, that failure is its `cause`.
17358
+
17359
+ `createSessionCredentials` accepts `SessionCredentialsConfig`:
17360
+ `scope`, `refresh(credentials, signal)`, `storage.write(session, credentials)`,
17361
+ `storage.clear()`, `maxPendingWrites` (128), `refreshTimeoutMs` (30000, at most one
17362
+ hour) and `storageTimeoutMs` (30000, at most one hour): a storage call past it
17363
+ rejects and frees the queue, so `logout()` still clears; when the late call settles,
17364
+ storage is written again from memory, or cleared when nobody is logged in. `SessionCredentials` provides `login`, `logout`, `read(operation)` and
17365
+ `refresh(operation)`. Concurrent refresh calls in one generation share the same
17366
+ promise. Logout invalidates memory before its returned persistence promise settles.
17367
+ Storage errors and queue-capacity refusals reject; callers must handle them. All
17368
+ writes must pass through the same instance. There is no cross-process/tab lock,
17369
+ provider error classifier or automatic retry of arbitrary application callbacks.
17370
+
17003
17371
  ## `stitchkit/contract`
17004
17372
 
17005
17373
  The contract layer alone — browser-and-server safe. All of this is also exported
@@ -17140,6 +17508,14 @@ realtime contract from `stitchkit`, and the server halves live in `stitchkit/app
17140
17508
 
17141
17509
  ---
17142
17510
 
17511
+ ### Session-scoped watch lifetime
17512
+
17513
+ `WatchClientConfig.session` captures a `SessionOperation`; `maxKeys` (1024) and
17514
+ `maxListenersPerKey` (128) bound retained subscriptions; a handle takes its key on
17515
+ `subscribe`, which is where capacity refuses. Its lifetime nonce travels
17516
+ in optional `WatchKey.instance`, preventing old-session frames from matching a new
17517
+ client on the same socket. This is routing identity, never authorization.
17518
+
17143
17519
  ## `stitchkit/primitives`
17144
17520
 
17145
17521
  Browser-and-server-safe declarations for facts an application wants to define once. See the
@@ -17528,7 +17904,7 @@ cutovers are covered by the executable
17528
17904
  | `DiagnosticJournalConfig` / `DiagnosticJournal` | _type_ | owner schema/path/limits/failure observer and the synchronous `submit`, bounded-wait `flush`/`close`, status handle |
17529
17905
  | `DiagnosticJournalLimitsSchema` / `DiagnosticJournalLimits` | schema / _type_ | positive event, pending-item, pending-byte, file-byte and retained-file limits |
17530
17906
  | `DiagnosticJournalLockPolicySchema` / `DiagnosticJournalLockPolicy` | schema / _type_ | `refuse` (default) or `reclaim-stale`, which reclaims only a lock whose recorded owner is provably gone |
17531
- | `readDiagnosticJournalLockDiagnosis` / `DiagnosticJournalLockDiagnosis` | function / _type_ | why a `reclaim-stale` acquisition refused — owner alive, another machine, or a lock this host cannot attribute — read off the thrown `EEXIST` |
17907
+ | `readDiagnosticJournalLockDiagnosis` / `DiagnosticJournalLockDiagnosis` | function / _type_ | why a `reclaim-stale` acquisition refused — attribution, PID liveness and optional process `identity` evidence (`matched`, `different-boot`, `reused-pid`, `pid-gone`, `legacy`, `unavailable`) — read off the thrown `EEXIST` |
17532
17908
  | `DiagnosticJournalSubmitResultSchema` / `DiagnosticJournalSubmitResult` | schema / _type_ | accepted epoch/sequence or explicit invalid, oversized, capacity, closed or failed refusal |
17533
17909
  | `DiagnosticJournalStatusSchema` / `DiagnosticJournalStatus` | schema / _type_ | state, limits, exact admission/write/failure counters, pending ownership, rotations, partial tails and last safe sequences |
17534
17910
  | `DiagnosticJournalFrameSchema` / `DiagnosticJournalFrame` | schema / _type_ | version-1 JSONL frame carrying process epoch, contiguous accepted sequence and schema-validated JSON event |
@@ -17631,6 +18007,53 @@ vocabulary with an event topic declared `mode: 'decision'`.
17631
18007
  → [ADR 0152](../decisions/0152-a-keyspace-is-memory-that-nothing-reaches-before-it-is-durable.md),
17632
18008
  [ADR 0153](../decisions/0153-a-watched-read-is-one-read-per-question.md).
17633
18009
 
18010
+ ### Managed change subscriptions and scoped watches
18011
+
18012
+ `changeSubscriptionResource` (`stitchkit/application`) accepts
18013
+ `ChangeSubscriptionConfig`: `id`, unique `keys` (1–128, at most 256 characters each),
18014
+ `connect(signal)`, `reconcile(key, context)`, required `reconcileIntervalMs` (10 ms–1 h),
18015
+ optional `timeoutMs` (30000; 1 ms–1 h), `backoff`, and `onError`.
18016
+ `ChangeConnection` supplies `subscribe({ signal, hint, disconnected })`, which completes subscription
18017
+ before the first read; `close()` releases it. `ChangeReconciliation` exposes `signal`
18018
+ and a synchronous `commit(callback)` guard against late publication. Drivers must
18019
+ honor cancellation. Uncooperative work retains its slot until settled instead of
18020
+ allowing overlapping connections/reads. Errors reach `onError`; no raw payload or
18021
+ connection string is logged by this resource. Recovery runs after reconnect and
18022
+ periodically even when no notification arrives.
18023
+
18024
+ `WatchHubConfig` adds `maxSources` (1024), `maxSubscribers` (1024), and optional
18025
+ `reconcileIntervalMs` (10 ms–1 h). `hub.attach(subscriber, scope?)` accepts a
18026
+ `WatchAdmissionScope` containing a **server-verified** `key` (1–512 characters) and
18027
+ revocation `signal`. The key separates sharing and is the optional third argument
18028
+ of `read(operation, args, scope)`; a key's `instance` routes frames and never
18029
+ separates sources. Attached over `maxSubscribers`, a subscriber gets a watcher whose
18030
+ `open` refuses with `Watch subscriber capacity exceeded`; `attach` does not throw
18031
+ for capacity. It must represent account, session and permissions;
18032
+ never trust a client-supplied owner ID as that scope.
18033
+
18034
+
18035
+
18036
+ ### Notification action and projection receipts
18037
+
18038
+ `NotificationOutboxConfig.delivery` accepts `NotificationDeliveryConfig`:
18039
+ immutable executor `version`, pure `actions(payload)` plan selector (1–128 unique
18040
+ names, each at most 128 characters), and idempotent `project(notification, receipts)`.
18041
+ `send` receives optional `action` and stable `idempotencyKey`, and returns a JSON
18042
+ remote receipt (void becomes null). A result that is not JSON or does not fit the
18043
+ byte budget is kept as `receipt: null` with `unrecorded: 'not-json' | 'too-large'`;
18044
+ the action stays confirmed and is not sent again. `retiredVersions` maps earlier
18045
+ plan versions to their `NotificationProjection` so queued items drain after an
18046
+ executor upgrade; an unknown version is reported to `onError` as
18047
+ `NotificationPlanVersionError` and waits, spending no attempt and not consulting
18048
+ `classify`, for an executor that knows it. `NotificationDeliveryStateSchema` validates the
18049
+ optional `NotificationDeliveryState` on queued items and completed receipts:
18050
+ `version`, `actions`, `receipts` and optional `projectedAt`. Checkpoints share the
18051
+ existing atomic StateStore and lease fence. Receipt retention and serialized byte
18052
+ budgets still apply. Single-send configuration remains valid without `delivery`.
18053
+ A remote result lost before its checkpoint may be sent again; provider idempotency
18054
+ or reconciliation is required. A projection may repeat after a crash before its
18055
+ checkpoint. Business-transaction enqueue and durable quarantine are not implied.
18056
+
17634
18057
  ## `stitchkit/application/grammy`
17635
18058
 
17636
18059
  Isolated optional-peer lifecycle adapters. Importing `stitchkit/application`
@@ -17808,7 +18231,7 @@ Server-only optional application runtime. See the
17808
18231
  | `AgentSandboxProcess` / `AgentSandboxOutputStream` | _types_ | structural child/output interface for optional lifecycle-owned `AgentProcessSandbox.spawn`, without Node ambient type dependencies |
17809
18232
  | `AgentEventSearchResultSchema` / `AgentEventSearchResult` / `createSqliteAgentEventSearch` / `createAgentEventSearchTools` | schema / _type_ / functions | authorized FTS5 search with exact event addresses and `session_*` tools |
17810
18233
  | `createSqliteAgentSpillStore` | function | durable content-address-checked artifact storage, bounded read/search, retention facts and archive participation |
17811
- | `AgentScheduleSchema` / `AgentSchedule` / `AgentScheduleService` / `AgentScheduleDeliveryOutcomeSchema` / `AgentScheduleDeliveryOutcome` / `createAgentScheduleService` / `createAgentScheduleTools` | schemas / _types_ / functions | durable `at`/`after`/timezone-explicit `every`, indexed retry eligibility, typed retry/terminal failure, bounded dispatch and fenced stable occurrence identity (ADR 0202) |
18234
+ | `AgentScheduleSchema` / `AgentSchedule` / `AgentScheduleService` / `AgentScheduleDeliveryOutcomeSchema` / `AgentScheduleDeliveryOutcome` / `createAgentScheduleService` / `createAgentScheduleTools` | schemas / _types_ / functions | durable `at`/`after`/timezone-explicit `every`, indexed retry eligibility, typed retry/terminal failure (any other returned value delivers), `maxAttempts` (default 1000) per occurrence — a one-off ends `failed`, a recurring schedule skips to its next slot — bounded dispatch and fenced stable occurrence identity (ADR 0202) |
17812
18235
  | `AgentChildBudgetSchema` / `AgentChildBudget` / `AgentChildStateSchema` / `AgentChildState` / `AgentChildRecordSchema` / `AgentChildRecord` | schema / _type_ | durable child graph, bounded seed and measured budget state |
17813
18236
  | `AgentChildBlockingKindSchema` / `AgentChildBlockingKind` / `AgentChildBlockingSourceSchema` / `AgentChildBlockingSource` / `AgentChildBlockingEventSchema` / `AgentChildBlockingEvent` / `AgentChildBlockingDecision` | schemas / _types_ | parent-owned request identities; responses discriminate approval decisions from JSON input values |
17814
18237
  | `AgentChildHandle` / `AgentChildManager` / `SqliteAgentChildManagerConfig` / `createSqliteAgentChildManager` / `createAgentChildTools` / `agentChildBudgetStopPolicy` | _type_ / functions | host execution port, child lifecycle, cascade (given to `createAgentRuntime` as `children`), messaging, Agent tools, and the child runtime's own budget stop policy — `recordStepUsage` at every step boundary, `policy_stop` as `child-budget` when spent |
@@ -17963,6 +18386,35 @@ Managed effects and operator telemetry additionally export `AgentToolFenceConfig
17963
18386
  may accompany checkpoint/terminal writes and tool context; internal causes are redacted unless an
17964
18387
  operator-only observability sink explicitly opts in.
17965
18388
 
18389
+ ### Completion and agent invocation receipts
18390
+
18391
+ `createModelInvocationLedger` constructs a `ModelInvocationLedger` from
18392
+ `ModelInvocationConfig`: the existing store and model registry, a 32-byte payload
18393
+ key, required host authorization, optional bounded payload size and `onAttempt`.
18394
+ `complete(input, context, signal?)` executes only the supplied prompt, with no tools
18395
+ or framework instructions; repeated keys return the existing invocation ID. It
18396
+ resolves `{ invocationId, outcome, text?, receiptError? }`: `receiptError` says a
18397
+ receipt could not be written and never changes `outcome` or drops `text`.
18398
+ `read(input, context)` returns a bounded event page shared by agent and completion;
18399
+ `readPayload(input, context)` requires a separate authorization action to decrypt
18400
+ one protected artifact. See the [guide and migration example](../guide/agent-runtime.md#plain-completions-and-shared-invocation-receipts).
18401
+
18402
+ | Export | Kind | Purpose |
18403
+ | --- | --- | --- |
18404
+ | `CompletionInvocationInputSchema` / `CompletionInvocationInput` | schema/type | strict prompt, model-attempt list, deadline, idempotency key and trace input |
18405
+ | `ModelInvocationTraceSchema` / `ModelInvocationTrace` | schema/type | operation, purpose, project, parent trace and optional experiment/case/profile |
18406
+ | `ModelInvocationRecordSchema` / `ModelInvocationRecord` | schema/type | discriminated invocation start/finish and per-attempt requested/sent/effective evidence |
18407
+ | `ModelInvocationAttemptContext` | type | conversation, operation, invocation, attempt, step and optional agent run identity |
18408
+ | `currentModelInvocationAttempt` | function | returns causal IDs inside the provider transport; undefined outside an invocation |
18409
+
18410
+ The runtime `invocations` option takes this ledger and a trace callback; it requires
18411
+ the same store and preserves the existing tool loop. `AgentRuntimeStore.appendEventOnce`
18412
+ is an optional atomic capability required by receipt admission, and
18413
+ `AgentRuntimeStore.findEventOnce` reads the event it wrote by kind and key. Built-in
18414
+ stores supply both; custom adapters must honor the once-only transaction contract.
18415
+ A driver's optional `events.find(transaction, { conversationId, eventId })` makes
18416
+ both one lookup instead of a scan of the conversation log.
18417
+
17966
18418
  ## `stitchkit/agent-runtime/testing`
17967
18419
 
17968
18420
  Credential-free provider and concurrency fixtures for Bun and Node tests.
@@ -18639,7 +19091,7 @@ available from `stitchkit/contract`.
18639
19091
  | `ManagedFileInspector` | _type_ | bounded-prefix read/write inspection callback with a finite cancellation signal that cannot own path or size |
18640
19092
  | `ManagedFileInspectionInput` / `ManagedFileInspection` | _type_ | inspector prefix/name/declared media/signal input and validated metadata-only result |
18641
19093
  | `writeFileAtomic` / `writeFileAtomicSync` / `WriteFileAtomicOptions` | function / _type_ | replace a file atomically: a random staging name created exclusively (never through a planted link), the mode set on the descriptor before the file is visible (default `0o600`, not masked by the umask), `fsync`, rename; a failure leaves the target and no staging file. The asynchronous form keeps the event loop running |
18642
- | `withExclusiveLock` / `ExclusiveLockOptions` / `ExclusiveLock` / `ExclusiveLockOwner` | function / _type_ | run work under an exclusive lock between processes — a file recording its owner (pid, host, machine identity, time); waits up to `timeoutMs` (default 10 s) and stops on `signal`; a dead owner on this machine is taken over, a live, slow or foreign one never is; an ownerless lock only after `ownerlessGraceMs` |
19094
+ | `withExclusiveLock` / `ExclusiveLockOptions` / `ExclusiveLock` / `ExclusiveLockOwner` | function / _type_ | run work under an exclusive lock between processes — a file recording its owner (pid, host, machine identity, time and nullable kernel process identity); waits up to `timeoutMs` (default 10 s) and stops on `signal`; a dead owner on this machine is taken over, a live, slow or foreign one never is; an ownerless lock only after `ownerlessGraceMs` |
18643
19095
  | `ExclusiveLockError` | class | `LOCK_TIMEOUT` naming the resource and its holder, or `LOCK_ABORTED` with the signal's reason |
18644
19096
 
18645
19097
  ---
@@ -19019,6 +19471,14 @@ For the rationale behind these APIs — why `Bun.serve` and not a framework, why
19019
19471
  two context types, why thin wrappers — see the
19020
19472
  [Architecture Decisions](../decisions/).
19021
19473
 
19474
+ ### Watched handles in the query cache
19475
+
19476
+ `CacheBridgeConfig.watched` accepts `WatchCacheBinding` entries with `handle`,
19477
+ `queryKey` and optional `state(frame)` observer. Include the session in query keys.
19478
+ `CacheBridgeConfig.session` fences events and removes bound queries when that login
19479
+ ends. Watch loss marks a query stale without triggering an independent refetch;
19480
+ recovery belongs to the watched read.
19481
+
19022
19482
  ## `stitchkit/agent-runtime/sandbox`
19023
19483
 
19024
19484
  Optional server-only namespace sandbox. Exports: