@qvac/harness 0.7.1 → 1.0.0-rc.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 (201) hide show
  1. package/README.md +224 -0
  2. package/dist-lib/index.d.ts +55 -3
  3. package/dist-lib/index.js +721 -236
  4. package/dist-lib/lib/capabilities/completion-engine/abort-polyfill.d.ts +1 -0
  5. package/dist-lib/lib/capabilities/completion-engine/abort-polyfill.js +10 -0
  6. package/dist-lib/lib/capabilities/completion-engine/actors/answering.d.ts +3 -0
  7. package/dist-lib/lib/capabilities/completion-engine/actors/answering.js +17 -0
  8. package/dist-lib/lib/capabilities/completion-engine/actors/approval.d.ts +4 -0
  9. package/dist-lib/lib/capabilities/completion-engine/actors/approval.js +29 -0
  10. package/dist-lib/lib/capabilities/completion-engine/actors/direct-search.d.ts +4 -0
  11. package/dist-lib/lib/capabilities/completion-engine/actors/direct-search.js +73 -0
  12. package/dist-lib/lib/capabilities/completion-engine/actors/execute.d.ts +41 -0
  13. package/dist-lib/lib/capabilities/completion-engine/actors/execute.js +49 -0
  14. package/dist-lib/lib/capabilities/completion-engine/actors/executing.d.ts +3 -0
  15. package/dist-lib/lib/capabilities/completion-engine/actors/executing.js +102 -0
  16. package/dist-lib/lib/capabilities/completion-engine/actors/execution-plan.d.ts +47 -0
  17. package/dist-lib/lib/capabilities/completion-engine/actors/execution-plan.js +87 -0
  18. package/dist-lib/lib/capabilities/completion-engine/actors/finishing.d.ts +2 -0
  19. package/dist-lib/lib/capabilities/completion-engine/actors/finishing.js +32 -0
  20. package/dist-lib/lib/capabilities/completion-engine/actors/inspect-preload.d.ts +3 -0
  21. package/dist-lib/lib/capabilities/completion-engine/actors/inspect-preload.js +75 -0
  22. package/dist-lib/lib/capabilities/completion-engine/actors/inspecting.d.ts +3 -0
  23. package/dist-lib/lib/capabilities/completion-engine/actors/inspecting.js +152 -0
  24. package/dist-lib/lib/capabilities/completion-engine/actors/loading-skill.d.ts +3 -0
  25. package/dist-lib/lib/capabilities/completion-engine/actors/loading-skill.js +94 -0
  26. package/dist-lib/lib/capabilities/completion-engine/actors/preload-failure.d.ts +9 -0
  27. package/dist-lib/lib/capabilities/completion-engine/actors/preload-failure.js +11 -0
  28. package/dist-lib/lib/capabilities/completion-engine/actors/preloading.d.ts +3 -0
  29. package/dist-lib/lib/capabilities/completion-engine/actors/preloading.js +114 -0
  30. package/dist-lib/lib/capabilities/completion-engine/actors/preparing.d.ts +3 -0
  31. package/dist-lib/lib/capabilities/completion-engine/actors/preparing.js +198 -0
  32. package/dist-lib/lib/capabilities/completion-engine/actors/rejecting.d.ts +3 -0
  33. package/dist-lib/lib/capabilities/completion-engine/actors/rejecting.js +71 -0
  34. package/dist-lib/lib/capabilities/completion-engine/actors/rejection.d.ts +18 -0
  35. package/dist-lib/lib/capabilities/completion-engine/actors/rejection.js +70 -0
  36. package/dist-lib/lib/capabilities/completion-engine/actors/resolving.d.ts +3 -0
  37. package/dist-lib/lib/capabilities/completion-engine/actors/resolving.js +60 -0
  38. package/dist-lib/lib/capabilities/completion-engine/actors/stream.d.ts +21 -0
  39. package/dist-lib/lib/capabilities/completion-engine/actors/stream.js +48 -0
  40. package/dist-lib/lib/capabilities/completion-engine/actors/streaming.d.ts +3 -0
  41. package/dist-lib/lib/capabilities/completion-engine/actors/streaming.js +156 -0
  42. package/dist-lib/lib/capabilities/completion-engine/actors/wrap-up.d.ts +7 -0
  43. package/dist-lib/lib/capabilities/completion-engine/actors/wrap-up.js +61 -0
  44. package/dist-lib/lib/capabilities/completion-engine/driver.d.ts +25 -0
  45. package/dist-lib/lib/capabilities/completion-engine/driver.js +553 -0
  46. package/dist-lib/lib/capabilities/completion-engine/durable.d.ts +61 -0
  47. package/dist-lib/lib/capabilities/completion-engine/durable.js +150 -0
  48. package/dist-lib/lib/capabilities/completion-engine/operations.d.ts +6 -0
  49. package/dist-lib/lib/capabilities/completion-engine/operations.js +239 -0
  50. package/dist-lib/lib/capabilities/completion-engine/ports.d.ts +3 -0
  51. package/dist-lib/lib/capabilities/completion-engine/ports.js +267 -0
  52. package/dist-lib/lib/capabilities/completion-engine/session.d.ts +76 -0
  53. package/dist-lib/lib/capabilities/completion-engine/session.js +70 -0
  54. package/dist-lib/lib/capabilities/completion-loop/index.d.ts +80 -0
  55. package/dist-lib/lib/capabilities/completion-loop/index.js +158 -0
  56. package/dist-lib/lib/capabilities/completion-loop/metrics.d.ts +5 -0
  57. package/dist-lib/lib/capabilities/completion-loop/metrics.js +29 -0
  58. package/dist-lib/lib/capabilities/completion-loop/round.d.ts +33 -0
  59. package/dist-lib/lib/capabilities/completion-loop/round.js +353 -0
  60. package/dist-lib/lib/capabilities/completion-loop/tool-execution.d.ts +60 -0
  61. package/dist-lib/lib/capabilities/completion-loop/tool-execution.js +270 -0
  62. package/dist-lib/lib/capabilities/completion-loop/turns.d.ts +23 -0
  63. package/dist-lib/lib/capabilities/completion-loop/turns.js +112 -0
  64. package/dist-lib/lib/capabilities/completion-runtime.d.ts +46 -0
  65. package/dist-lib/lib/capabilities/completion-runtime.js +74 -0
  66. package/dist-lib/lib/capabilities/completion.d.ts +18 -335
  67. package/dist-lib/lib/capabilities/completion.js +20 -1301
  68. package/dist-lib/lib/capabilities/download.d.ts +1 -1
  69. package/dist-lib/lib/capabilities/download.js +7 -2
  70. package/dist-lib/lib/capabilities/invoke.js +5 -3
  71. package/dist-lib/lib/capabilities/tool-plan.d.ts +35 -0
  72. package/dist-lib/lib/capabilities/tool-plan.js +66 -0
  73. package/dist-lib/lib/capabilities/tool-turns.d.ts +11 -2
  74. package/dist-lib/lib/capabilities/tool-turns.js +30 -2
  75. package/dist-lib/lib/capabilities/types.d.ts +287 -0
  76. package/dist-lib/lib/capabilities/types.js +1 -0
  77. package/dist-lib/lib/completion-lifecycle.d.ts +14 -0
  78. package/dist-lib/lib/completion-lifecycle.js +126 -0
  79. package/dist-lib/lib/connect.d.ts +10 -3
  80. package/dist-lib/lib/connect.js +329 -23
  81. package/dist-lib/lib/contract-types.d.ts +19 -2
  82. package/dist-lib/lib/harness-lifecycle.d.ts +66 -0
  83. package/dist-lib/lib/harness-lifecycle.js +129 -0
  84. package/dist-lib/lib/helpers/detach-json.d.ts +1 -0
  85. package/dist-lib/lib/helpers/detach-json.js +146 -0
  86. package/dist-lib/lib/helpers/markup-scanner.d.ts +26 -0
  87. package/dist-lib/lib/helpers/markup-scanner.js +156 -0
  88. package/dist-lib/lib/helpers/markup-stream-filter.js +138 -35
  89. package/dist-lib/lib/helpers/race-abort.d.ts +9 -4
  90. package/dist-lib/lib/helpers/race-abort.js +2 -7
  91. package/dist-lib/lib/helpers/recovery-required.d.ts +1 -0
  92. package/dist-lib/lib/helpers/recovery-required.js +1 -0
  93. package/dist-lib/lib/helpers/run-outcome.d.ts +10 -2
  94. package/dist-lib/lib/helpers/run-outcome.js +8 -5
  95. package/dist-lib/lib/helpers/tool-call-markup.d.ts +2 -0
  96. package/dist-lib/lib/helpers/tool-call-markup.js +197 -82
  97. package/dist-lib/lib/hooks/built-in/arg-repair.d.ts +1 -0
  98. package/dist-lib/lib/hooks/built-in/arg-repair.js +1 -0
  99. package/dist-lib/lib/hooks/built-in/egress.d.ts +1 -0
  100. package/dist-lib/lib/hooks/built-in/egress.js +2 -1
  101. package/dist-lib/lib/hooks/built-in/sandbox.d.ts +1 -0
  102. package/dist-lib/lib/hooks/built-in/sandbox.js +6 -1
  103. package/dist-lib/lib/hooks/built-in/skill-catalog.d.ts +1 -0
  104. package/dist-lib/lib/hooks/built-in/skill-catalog.js +2 -1
  105. package/dist-lib/lib/hooks/pipeline.d.ts +4 -1
  106. package/dist-lib/lib/hooks/pipeline.js +24 -2
  107. package/dist-lib/lib/hooks/run.d.ts +2 -1
  108. package/dist-lib/lib/hooks/run.js +19 -7
  109. package/dist-lib/lib/hooks/types.d.ts +17 -6
  110. package/dist-lib/lib/machine/completion/actions/approval.d.ts +3 -0
  111. package/dist-lib/lib/machine/completion/actions/approval.js +16 -0
  112. package/dist-lib/lib/machine/completion/actions/completion.d.ts +23 -0
  113. package/dist-lib/lib/machine/completion/actions/completion.js +142 -0
  114. package/dist-lib/lib/machine/completion/actions/skills.d.ts +9 -0
  115. package/dist-lib/lib/machine/completion/actions/skills.js +83 -0
  116. package/dist-lib/lib/machine/completion/actions/tools.d.ts +16 -0
  117. package/dist-lib/lib/machine/completion/actions/tools.js +173 -0
  118. package/dist-lib/lib/machine/completion/context.d.ts +9 -0
  119. package/dist-lib/lib/machine/completion/context.js +44 -0
  120. package/dist-lib/lib/machine/completion/contract.d.ts +6 -0
  121. package/dist-lib/lib/machine/completion/contract.js +1 -0
  122. package/dist-lib/lib/machine/completion/guards/retry.d.ts +9 -0
  123. package/dist-lib/lib/machine/completion/guards/retry.js +25 -0
  124. package/dist-lib/lib/machine/completion/implementations.d.ts +81 -0
  125. package/dist-lib/lib/machine/completion/implementations.js +152 -0
  126. package/dist-lib/lib/machine/completion/machine.d.ts +375 -0
  127. package/dist-lib/lib/machine/completion/machine.js +511 -0
  128. package/dist-lib/lib/machine/completion/ports.d.ts +65 -0
  129. package/dist-lib/lib/machine/completion/ports.js +224 -0
  130. package/dist-lib/lib/machine/completion/rules.d.ts +39 -0
  131. package/dist-lib/lib/machine/completion/rules.js +115 -0
  132. package/dist-lib/lib/machine/completion/types.d.ts +493 -0
  133. package/dist-lib/lib/machine/completion/types.js +1 -0
  134. package/dist-lib/lib/machine/engine.d.ts +93 -0
  135. package/dist-lib/lib/machine/engine.js +483 -0
  136. package/dist-lib/lib/machine/errors.d.ts +44 -0
  137. package/dist-lib/lib/machine/errors.js +373 -0
  138. package/dist-lib/lib/machine/guards.d.ts +2 -0
  139. package/dist-lib/lib/machine/guards.js +6 -0
  140. package/dist-lib/lib/machine/index.d.ts +20 -0
  141. package/dist-lib/lib/machine/index.js +11 -0
  142. package/dist-lib/lib/machine/replay-output.d.ts +24 -0
  143. package/dist-lib/lib/machine/replay-output.js +50 -0
  144. package/dist-lib/lib/machine/stable-stringify.d.ts +1 -0
  145. package/dist-lib/lib/machine/stable-stringify.js +11 -0
  146. package/dist-lib/lib/models/model-pool.d.ts +14 -6
  147. package/dist-lib/lib/models/model-pool.js +95 -30
  148. package/dist-lib/lib/run-log/history-codec.d.ts +23 -0
  149. package/dist-lib/lib/run-log/history-codec.js +973 -0
  150. package/dist-lib/lib/run-log/patch.d.ts +6 -0
  151. package/dist-lib/lib/run-log/patch.js +93 -0
  152. package/dist-lib/lib/run-log/persisted-actor.d.ts +18 -0
  153. package/dist-lib/lib/run-log/persisted-actor.js +218 -0
  154. package/dist-lib/lib/run-log/protocol-types.d.ts +153 -0
  155. package/dist-lib/lib/run-log/protocol-types.js +1 -0
  156. package/dist-lib/lib/run-log/protocol.d.ts +27 -0
  157. package/dist-lib/lib/run-log/protocol.js +1056 -0
  158. package/dist-lib/lib/run-log/replay.d.ts +25 -0
  159. package/dist-lib/lib/run-log/replay.js +129 -0
  160. package/dist-lib/lib/run-log/run-journal.d.ts +125 -0
  161. package/dist-lib/lib/run-log/run-journal.js +783 -0
  162. package/dist-lib/lib/run-log-inspect.d.ts +25 -0
  163. package/dist-lib/lib/run-log-inspect.js +627 -0
  164. package/dist-lib/lib/run-log-protocol.d.ts +2 -0
  165. package/dist-lib/lib/run-log-protocol.js +1 -0
  166. package/dist-lib/lib/run-log-redact.d.ts +27 -0
  167. package/dist-lib/lib/run-log-redact.js +1107 -0
  168. package/dist-lib/lib/serve.js +307 -11
  169. package/dist-lib/lib/spawn.js +3 -2
  170. package/dist-lib/lib/tool-call-display.d.ts +1 -21
  171. package/dist-lib/lib/tools/exec/adapters/python/wheels.js +2 -1
  172. package/dist-lib/lib/tools/exec.d.ts +2 -1
  173. package/dist-lib/lib/tools/exec.js +57 -2
  174. package/dist-lib/lib/tools/generate-image.js +1 -1
  175. package/dist-lib/lib/tools/generate-music.js +1 -1
  176. package/dist-lib/lib/tools/http-request.d.ts +2 -1
  177. package/dist-lib/lib/tools/http-request.js +2 -1
  178. package/dist-lib/lib/tools/mcp-call.js +8 -1
  179. package/dist-lib/lib/tools/operations/builders.d.ts +1 -0
  180. package/dist-lib/lib/tools/operations/builders.js +1 -0
  181. package/dist-lib/lib/tools/operations/compile.js +11 -1
  182. package/dist-lib/lib/tools/operations/manifest.js +1 -0
  183. package/dist-lib/lib/tools/outbound-guard.d.ts +8 -6
  184. package/dist-lib/lib/tools/outbound-guard.js +25 -8
  185. package/dist-lib/lib/tools/skills/catalog.d.ts +1 -0
  186. package/dist-lib/lib/tools/skills/catalog.js +35 -0
  187. package/dist-lib/lib/tools/skills/cli-schema-types.d.ts +1 -0
  188. package/dist-lib/lib/tools/skills/cli-schema.js +25 -0
  189. package/dist-lib/lib/wire.d.ts +5 -0
  190. package/dist-lib/lib/wire.js +128 -5
  191. package/dist-lib/schema/harness.js +20 -5
  192. package/dist-lib/spec/hrpc/messages.js +95 -15
  193. package/dist-lib/spec/hyperschema/index.js +95 -15
  194. package/dist-lib/spec/hyperschema/schema.json +69 -1
  195. package/dist-lib/spec/hyperschema/types.d.ts +10 -3
  196. package/package.json +20 -2
  197. package/patches/@qvac%2Finference@0.20.0.patch +67 -0
  198. package/patches/bare-stow@0.1.5.patch +68 -0
  199. package/patches/hypercore-blob-server@1.15.0.patch +15 -0
  200. package/patches/localwatch@1.4.3.patch +13 -0
  201. package/patches/pyodide@314.0.2.patch +144 -0
package/README.md CHANGED
@@ -308,6 +308,16 @@ bun run build:schema # regenerates spec/ (wire-compat rewind once v* tags exist
308
308
  bun run build:bundle # stows the sidecar entry into dist/, to prove it still stows
309
309
  ```
310
310
 
311
+ ## Engine layout
312
+
313
+ `lib/machine/` contains `completion/**`, `engine.ts`, `errors.ts`, `index.ts`, `replay-output.ts`,
314
+ `stable-stringify.ts`, and `guards.ts`. The run-log layer is in `lib/run-log/`:
315
+ `protocol.ts`, `protocol-types.ts`, `history-codec.ts`, `run-journal.ts`,
316
+ `persisted-actor.ts`, `replay.ts`, and `patch.ts`.
317
+
318
+ The completion graph has 24 states and 61 transitions. Reproduce these
319
+ counts with `examples/bench/graph-metrics.ts`.
320
+
311
321
  The schema baseline rewinds `spec/` against this repo's `v*` release tags and
312
322
  skips loudly while none exist. Dependency `overrides` pin the resolutions the
313
323
  engine is validated against — keep them aligned with direct specs, npm refuses
@@ -316,3 +326,217 @@ a mismatch on publish.
316
326
  ## License
317
327
 
318
328
  See [LICENSE](./LICENSE.md).
329
+
330
+ ## Resuming a completion
331
+
332
+ The harness emits resumable steps to a host-owned log. Direct `Harness.completion()` runs without
333
+ that host log work normally, but cannot recover after process death. For remote runs, pass a
334
+ `HostRunLog` to `connectHarness(...).completion()` and reuse the same `runId` when reconnecting.
335
+ The peer must advertise `hostRunLogVersion: 1`.
336
+
337
+ ```ts
338
+ const request = { runId: 'draft-42', model, messages, agent, runLog }
339
+ for await (const event of remote.completion(request)) {
340
+ // Save the run ID and render completion events.
341
+ }
342
+
343
+ // begin() returns the saved boot for this run. connectHarness resumes from it and sends the
344
+ // same request identity; supply current callbacks, tool configs, and filesystem grants again.
345
+ for await (const event of remote.completion({ runId: 'draft-42', resume: true, runLog, approve })) {
346
+ // Handle run-resumed, round-reset, content, tool activity, and paused.
347
+ }
348
+ ```
349
+
350
+ `HostRunLog.begin(runId)` acquires one exclusive generation and returns its compact boot, if the
351
+ run already exists, plus a 32-byte `digestKey`. Each `append(runId, generation, step)` must durably
352
+ commit the whole `EngineStep`—its `seq`, expected logical record length, and records—in one
353
+ transaction. The client waits for that commit before continuing. `release(runId, generation)` fences
354
+ late appends from the ended connection. A host may export its authenticated records for inspection;
355
+ the harness does not fetch storage or decrypt a host's log itself.
356
+
357
+ `hostAppendTimeoutMs` (default 5000) bounds that wait; it applies in full even to a resume issued
358
+ after the run's own deadline has passed. A spawned host raises it via the `runLog` start payload
359
+ when its appends (e.g. a large first append on resume) can take longer than the default.
360
+
361
+ Cancellation, iterator disposal, suspension, shutdown, and remote disconnect pause a run. A
362
+ `paused` event ends that stream without making the run terminal. Resume retains completed tool
363
+ results; time spent paused does not count against the run's deadline. A `round-reset` event identifies
364
+ the unfinished round. Ordinary resets carry no `replacedTypes`; only the terminal replay reset
365
+ (when resuming) lists the output kinds to replace. Both replace content and thinking. Text and
366
+ tool events carry run/round identities; tool events also carry their call identity. Completed runs
367
+ replay their final answer and metrics without loading a model.
368
+
369
+ An unfinished write produces `recovery-required` with an `operation` containing its ID, name,
370
+ resolved arguments, and effect. A write that the pause stopped before it started runs again. Reconcile the external result, then supply exactly that operation's
371
+ resolution on resume:
372
+
373
+ ```ts
374
+ remote.completion({
375
+ runId: 'draft-42',
376
+ resume: true,
377
+ model,
378
+ messages,
379
+ agent,
380
+ recovery: [{ operationId: operation.id, decision: 'result', result: confirmedResult }],
381
+ runLog,
382
+ approve
383
+ })
384
+
385
+ // If re-execution is appropriate, explicitly authorize it instead:
386
+ // recovery: [{ operationId: operation.id, decision: 'retry' }]
387
+ ```
388
+
389
+ A retry can repeat an external effect. Receipts prevent replay of completed operations; they do
390
+ not provide transactions or exactly-once effects in external systems. Missing runtime callbacks or
391
+ a model that cannot load leave the run paused with a recovery reason. For these runtime/read
392
+ reasons, restore the dependencies and resume without a `recovery` resolution. A tool that is gone
393
+ or whose schema changed since the pause ends the run with an error, because no resolution can
394
+ bring the old tool back. Stale resolutions and attempts to change the original conversation or
395
+ model are rejected.
396
+
397
+ A round can pause with one call's arguments redacted, whether or not a sibling call in the same
398
+ round already landed or was already dispatched. Resuming refuses with `PENDING_ARGS_REDACTED`
399
+ instead of replaying the redacted call. Its `recoveryOperationIds` lists every call the round still
400
+ needs settled before it can proceed: a dispatched write not yet resolved (redacted or not), or a
401
+ call whose real arguments exist only in the redacted snapshot, whether dispatched as a read or
402
+ never dispatched at all. A plain call that was never redacted and never dispatched is not listed;
403
+ it simply runs when the round resumes. Settle any listed operation with `decision: 'result'` and
404
+ the known outcome. `decision: 'skip'` is also available when the host knows a redacted operation
405
+ did not land, or for a read; it records a readable placeholder body,
406
+ `{ skipped: true, reason: 'not executed; arguments unavailable after resume' }`, never the literal
407
+ `undefined`. For a dispatched redacted write, `skip` asserts the call never landed; if the host
408
+ skips a write that did land, the model sees "not executed" and may issue it again. It remains
409
+ refused for a dispatched write whose arguments are available. The `retry` decision re-dispatches a
410
+ call still pending; it is refused once that call already has a settled result, for a call never
411
+ dispatched at all, and for a dispatched call whose arguments were redacted, which re-raises
412
+ `PENDING_ARGS_REDACTED` instead of retrying blind. Settling every listed operation finishes the
413
+ round; the landed write is never repeated, though a redacted read may run again. When no operation
414
+ can be reconciled this way, the error's `forkAt` gives the last log length that never held redacted
415
+ arguments as real ones, and `forkSafe` says whether forking there is actually safe: `false` means a
416
+ write whose intent starts at or after `forkAt` has an unknown outcome — its result already landed, or it was dispatched
417
+ and never resolved — so a fork could repeat it and the host should resolve `recoveryOperationIds`
418
+ instead. `forkRun(sourceCore, targetCore, { at: forkAt, runId })` copies the log up to that point
419
+ under a new `runId`, and the round reruns from there.
420
+
421
+ Tool definitions are checked again on resume, including their function source. A tool whose
422
+ behavior depends on values captured in a closure must supply and update `policyVersion` when
423
+ those values change; function source cannot reveal them. The `exec` and `http_request` factories
424
+ accept this value as an option; `exec` also fingerprints its static path and Python settings.
425
+
426
+ Tool configuration values are not stored in the log; a digest verifies that the caller supplies
427
+ the same configuration on resume. Credentials embedded in those values must currently remain
428
+ unchanged. Credentials managed through `setCredentials()` or runtime callbacks can be refreshed
429
+ without changing the request.
430
+
431
+ For host-owned logs, resume uses the boot returned by `begin()` and does not require the peer's
432
+ local `completionRecovery` capability. A transport `done` frame only means the stream ended; it
433
+ does not mean the run succeeded. Run-log and machine versions must match. XState restore accepts
434
+ older stable patches in the installed minor; future patches, other minors, and prereleases are
435
+ rejected. The first checkpoint and child actor snapshots are trusted inputs; diagnostic replay does
436
+ not verify actor effects or the initial context.
437
+
438
+ `connectHarness(transport, { runLogReleaseTimeoutMs })` bounds host lease cleanup to 5 seconds
439
+ by default. A rejected, throwing, or timed-out release is logged without replacing the completion
440
+ outcome. The host must keep generation fencing active until release actually settles; a cleanup
441
+ timeout does not authorize another writer.
442
+
443
+ Each run's metadata contains a JSON-only definition of the machine, including state hierarchy,
444
+ initial states, tags, invoked actors, and named transition guards/actions. Checkpoints and deltas
445
+ also carry the exact event, source/target state IDs, fired guard names, and action names observed in
446
+ XState's transition trace. The inspector displays these recorded traces and can replay state/context
447
+ at the same machine and XState versions; event-triggered guard/action traces are re-evaluated and
448
+ must match the recorded trace exactly, or replay reports a divergence. Eventless traces (no
449
+ triggering event, e.g. `always` transitions) are not replay-verified; the inspector marks each such
450
+ line `[eventless; not replay-verified]`. A version mismatch, legacy log, or prefix selection skips
451
+ replay and leaves the stored snapshot and trace authoritative. The checked-in [completion
452
+ graph](./docs/statecharts/completion-machine.mmd) is generated from the same machine definition and
453
+ CI rejects a stale artifact.
454
+
455
+ Export records from the host, then inspect a selected logical record or physical core length:
456
+
457
+ ```sh
458
+ bare examples/run-log/inspect.ts <records.json> --context <recordIndex>
459
+ bare examples/run-log/view.ts <records.json> report.html --limit <recordIndex>
460
+ ```
461
+
462
+ Logical record index, engine step sequence, and physical storage length are different positions;
463
+ host exports can include the mapping. The CLI reads exported records only. The host remains
464
+ responsible for remote lookup, authentication, decryption, and sharing access.
465
+
466
+ `@qvac/harness/run-log-inspect` is the library behind that CLI: `inspectRun(core, opts)` yields the
467
+ same lines the `inspect.ts` example prints, and `renderRunLogHtml(core, opts)` renders the HTML
468
+ report `view.ts` writes. A host imports either directly instead of shelling out to the examples,
469
+ passing a `MachineLog` adapter or an envelope whose `records()` method returns an async iterable
470
+ of `{ recordIndex, stepSeq, physicalLength, record }` entries. A raw records array or parsed JSON
471
+ envelope must first be adapted; the CLI's `recordsCore` helper shows this conversion. Options are
472
+ `recordLimit` (an exclusive logical index), `contextRecord`, `physicalLength`, and
473
+ `contextPhysicalLength`.
474
+
475
+ The completion machine is internal: its state names are part of the run-log format, not the API.
476
+ Hook consumers observe `runPause` and `runResume` for a run that can resume. A run with no run-log
477
+ store cannot resume, so a pause ends it and `runEnd` reports `aborted` (the caller aborted) or
478
+ `abandoned` (the consumer stopped iterating).
479
+
480
+ ## Tool-call markup grammar
481
+
482
+ A tool-call block opens with `<function`, up to 64 ASCII whitespace characters, `=`, up to 64
483
+ more ASCII whitespace characters, a name, up to 64 ASCII whitespace characters, and `>`. Tags are
484
+ case-insensitive, except in the legacy nested reading below. An executable name is 1–64
485
+ non-whitespace characters other than `<` or `>`; persistence also recognizes longer names so an
486
+ oversized call cannot bypass redaction. A parameter uses the same form with `parameter` in place
487
+ of `function`, and ends at the first case-insensitive
488
+ `</parameter>`. A function block ends at the first case-insensitive `</function>`. Anything after
489
+ that closer is prose and cannot extend the call, including more parameters or closers.
490
+
491
+ Text that does not form a complete opener and closed block is prose. In particular, empty names,
492
+ overlong executable names, bare parameter tags, and function-tag examples without a matching
493
+ closer are neither executed nor hidden from the displayed answer. Stray function closers, including
494
+ `</FUNCTION>`, also stay visible. A function opener inside a function block or a parameter opener
495
+ inside a parameter value makes that block ambiguous, and persistence redacts it conservatively.
496
+ A block with a nested function opener keeps the legacy recovery reading: the first lowercase
497
+ `<function=` with no whitespace before `=` names the call. The text between `=` and `>` is limited
498
+ to 64 characters before the name is trimmed. It runs with complete lowercase
499
+ parameters up to the first lowercase `</function>` within that block. That reading ignores other
500
+ function openers, so `<function=a><function=login>…` runs `a`,
501
+ `<FUNCTION=a><function=login>…` and `<function =a><function=login>…` run `login`, and
502
+ `<FUNCTION=a><FUNCTION=login>…` runs nothing. Likewise,
503
+ `<function=x><function= login ><parameter=path>p</parameter></function>` runs `x` with `path: "p"`
504
+ when `x` is a known tool. An unclosed parameter, such as `<parameter=key>S</function>`, contributes
505
+ no argument and does not itself make the function block ambiguous. Persistence still redacts its
506
+ value when the name is a credential field or a registered sensitive parameter, including in prose.
507
+
508
+ ## Run-log data and privacy
509
+
510
+ The harness redacts structured values before it sends a durable step to the host. String or object
511
+ values under exact credential field names are redacted. Numeric values under known credential
512
+ names are also redacted; generic `token` counts and `secret` flags are preserved. Registered tool
513
+ parameters marked `sensitive: true` are redacted regardless of name or value type. Known names include `db_password`, `GITHUB_TOKEN`,
514
+ `openai_api_key`, `client_password`, and `client_passphrase` (case, underscores, and hyphens
515
+ are normalized).
516
+ Name-based redaction is best effort: provider key names such as `ANTHROPIC_API_KEY`, `GH_TOKEN`,
517
+ and `PGPASSWORD` are not covered, and a bare `token` argument (e.g. a pagination cursor) is
518
+ redacted and lost on resume just like a real secret. The durable way to mark a value as a secret,
519
+ regardless of its name, is the schema's `sensitive: true` annotation.
520
+ Encoded assistant tool calls are redacted when history crosses persistence. Within one live process,
521
+ a model's next request still sees its own prior call's real arguments, because that history lives
522
+ only in the actor's memory. A **resumed** run has no such memory: its context is rebuilt from the
523
+ persisted (redacted) log, so the model's first request after resume sees `[redacted]` for any tool
524
+ call it made before the run was interrupted, not the original argument; that argument cannot be
525
+ recovered. The model sees that prior call as well-formed markup with the literal `"[redacted]"`
526
+ value, not an error; a model that re-issues such a call runs it with that literal placeholder.
527
+ A persisted `raw` or `rawCall` of 4,096 characters or more is rebuilt from its (redacted) arguments
528
+ even when nothing is sensitive, so it may differ byte-for-byte from the live call's `rawCall`.
529
+ Shorter unchanged text stays identical to the original.
530
+ Other arbitrary strings, tool results, and prose are preserved. Persisted machine
531
+ history can reference matching original request messages by index and materializes to the same
532
+ messages on resume and inspection — this applies to the caller-supplied conversation the run
533
+ started from, which is never redacted, not to arguments the model generated mid-run. Hosts must
534
+ treat free text and results as confidential and
535
+ protect their log with encryption and access control. The host's `digestKey` verifies request
536
+ identity and never enters a run record.
537
+
538
+ A pending tool call whose arguments were redacted cannot be executed on resume and is rejected.
539
+ A literal `[redacted]` supplied by the user is not enough to trigger that check. Tool configuration
540
+ values are not stored in the run log; a keyed digest verifies that the caller supplies the same
541
+ configuration on resume. Credentials managed through `setCredentials()` or runtime callbacks can
542
+ be refreshed without changing the request identity.
@@ -1,5 +1,6 @@
1
1
  import type { AbortSignal as BareAbortSignal } from 'bare-abort-controller';
2
2
  import ReadyResource from 'ready-resource';
3
+ import type { EngineBoot, EngineStep, RecoveryResolution } from './lib/machine/index.ts';
3
4
  import { type Agent, type ApproveFn, type AskUserFn, type AttachFn, type CompletionEvent, type FetchAttachmentFn, type Message, type ProgressFn, type RagSearchFn, type ResponseFormat, type ToolDefinition, type ToolExecuteContext } from './lib/capabilities/completion.ts';
4
5
  import { type DetectLanguageContext, type DetectedLanguage } from './lib/capabilities/detect-language.ts';
5
6
  import { type Capability } from './lib/capabilities/invoke.ts';
@@ -40,6 +41,7 @@ export * from './lib/models/model-catalog.ts';
40
41
  export * from './lib/models/model-list.ts';
41
42
  export type { ModelTaskType } from './lib/models/model-task.ts';
42
43
  export type { DeleteModelResult, DeleteModelSummary } from './lib/models/model-delete.ts';
44
+ export type { RecoveryResolution, RecoveryOperation } from './lib/machine/index.ts';
43
45
  export { harnessChild, spawnHarness, type HarnessChildOptions, type SpawnHarnessOptions } from './lib/spawn.ts';
44
46
  export { settingsDimension, settingsSteps } from './lib/tools/generate-image.ts';
45
47
  export * from './lib/tools/skills/catalog.ts';
@@ -52,6 +54,7 @@ export { readSetup as normalizeSkillSetup } from './lib/tools/skills/manifest-pa
52
54
  export type { CredentialRecord, CredentialStoreValue } from './lib/tools/skills/auth-resolver.ts';
53
55
  export { isAvailableState, SKILL_STATE, type SkillState } from './lib/tools/skills/skill-state.ts';
54
56
  export { connectHarness, HARNESS_DIED, RemoteHarness } from './lib/connect.ts';
57
+ export type { ConnectOptions, HostRunLog, HostRunLogLease } from './lib/connect.ts';
55
58
  export type { KvCacheDeletionResult } from './lib/capabilities/kv-cache.ts';
56
59
  export type { AgentKnobs, ApprovalCall, ChatMessage, CredentialEntry, DownloadContext, HarnessContract, HarnessDiagnostics, HarnessEvent, HarnessInfo, HarnessStatus, InfraModel, ModelState, SkillsOptions, SkillWriteResult } from './lib/contract-types.ts';
57
60
  import type { HarnessInfo } from './lib/contract-types.ts';
@@ -96,6 +99,9 @@ export interface HarnessOptions {
96
99
  completionModelCap?: number;
97
100
  }
98
101
  export interface CompletionContext {
102
+ runId?: string;
103
+ resume?: boolean;
104
+ recovery?: RecoveryResolution[];
99
105
  model: string;
100
106
  agent?: Agent;
101
107
  messages: Message[];
@@ -115,8 +121,16 @@ export interface CompletionContext {
115
121
  ragSearch?: RagSearchFn;
116
122
  progress?: ProgressFn;
117
123
  askUser?: AskUserFn;
124
+ boot?: EngineBoot;
125
+ onStep?: (step: EngineStep) => Promise<void>;
126
+ hostAppendTimeoutMs?: number;
127
+ requestDigestKey?: Uint8Array;
118
128
  signal?: BareAbortSignal;
119
129
  }
130
+ export type ResumeCompletionContext = Omit<Partial<CompletionContext>, 'runId' | 'resume'> & {
131
+ runId: string;
132
+ resume: true;
133
+ };
120
134
  export interface InvokeContext {
121
135
  capability: Capability;
122
136
  args: Record<string, unknown>;
@@ -145,6 +159,7 @@ export declare class Harness extends ReadyResource {
145
159
  private _models;
146
160
  private _embedModels;
147
161
  private readonly _retiredEmbedModels;
162
+ private readonly _faultedEmbedKeys;
148
163
  private _embedLane;
149
164
  private _embedFaults;
150
165
  private readonly _embedGpuLayers?;
@@ -153,7 +168,20 @@ export declare class Harness extends ReadyResource {
153
168
  private _ttsModels;
154
169
  private _transcriptionModels;
155
170
  private _voiceLane;
156
- private _state;
171
+ private readonly _lifecycle;
172
+ private readonly _detachedRuns;
173
+ private readonly _effectDrainWaiters;
174
+ private _activeWork;
175
+ private readonly _workDrainWaiters;
176
+ private _runtimeDraining;
177
+ private _runtimeUnloadFailed;
178
+ private _poolUnloadFailed;
179
+ private _poolCleanupFailed;
180
+ private readonly _failedModelUnloads;
181
+ private _sdkClosed;
182
+ private _runtimesReleased;
183
+ private _closeRetryNeeded;
184
+ private _closeRequested;
157
185
  private _skillsDir?;
158
186
  private readonly _modelsDir?;
159
187
  private _overlayWatcher;
@@ -178,6 +206,7 @@ export declare class Harness extends ReadyResource {
178
206
  private _musicSdk;
179
207
  private _translationSdk;
180
208
  private _musicRuntime;
209
+ private readonly _completionRuns;
181
210
  private _translationRuntime;
182
211
  private _capabilityTools;
183
212
  private _capability;
@@ -228,26 +257,46 @@ export declare class Harness extends ReadyResource {
228
257
  private _loadMusicSdk;
229
258
  private _deviceConfig;
230
259
  private _createPool;
260
+ private _loadModelLifecycleSdk;
231
261
  private _createEmbedPool;
232
262
  private _createTtsPool;
233
263
  private _createTranscriptionPool;
264
+ private _recordModelUnloadFailure;
234
265
  private _notifyModel;
235
266
  private _collectRetiredEmbedPool;
236
267
  private _releaseEmbedResource;
268
+ private _releaseEmbedResourceBeforeWork;
269
+ private _markEmbedFault;
237
270
  private _releaseEmbedResourceAfter;
238
271
  private _destroyEmbedPools;
239
272
  private _enqueueEmbedControl;
273
+ private _runLifecycle;
274
+ private _abortCompletions;
275
+ private _assertNotClosing;
276
+ private _enterWork;
277
+ private _waitForWorkDrain;
278
+ private _effectsSettled;
279
+ private _notifyEffectDrain;
280
+ private _waitForEffectDrain;
281
+ private _trackDetachedRun;
282
+ private _closeCompletions;
240
283
  getEmbeddingDevice(): EmbedDevice;
241
284
  setEmbeddingDevice(device: EmbedDevice): Promise<void>;
242
285
  private _setEmbeddingDevice;
243
286
  presuspend(): Promise<void>;
244
287
  suspend(): Promise<void>;
288
+ private _suspendRuntimes;
245
289
  private _sdkLifecycle;
246
290
  private _releaseRuntimes;
247
291
  private _unloadIdleModels;
248
292
  resume(): Promise<void>;
293
+ private _resumeRuntimes;
249
294
  wakeup(): Promise<void>;
295
+ ready(): Promise<void>;
250
296
  _close(): Promise<void>;
297
+ close(): Promise<void>;
298
+ private _closeWhileOpening;
299
+ private _finishClose;
251
300
  download(args: DownloadArgs): DownloadEventStream;
252
301
  embed(context: EmbedContext): Promise<number[][]>;
253
302
  private _embed;
@@ -278,10 +327,13 @@ export declare class Harness extends ReadyResource {
278
327
  private _yieldPins;
279
328
  private _residency;
280
329
  private _completionLoadOptions;
330
+ private _acquireModelLease;
281
331
  invoke(context: InvokeContext): AsyncGenerator<CompletionEvent>;
282
332
  private _invoke;
283
- completion(context: CompletionContext): AsyncGenerator<CompletionEvent>;
284
- private _completion;
333
+ completion(context: CompletionContext | ResumeCompletionContext): AsyncGenerator<CompletionEvent>;
334
+ private _runCompletion;
335
+ private _completionFailed;
336
+ private _completionRuntime;
285
337
  private _residentEnhancer;
286
338
  private _makeRoomForCompletion;
287
339
  private _evictWedgedModel;