@kodax-ai/kodax 0.7.78 → 0.7.80

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 (126) hide show
  1. package/CHANGELOG.md +3556 -3197
  2. package/README.md +371 -277
  3. package/README_CN.md +295 -215
  4. package/config-templates/config.example.jsonc +24 -2
  5. package/config-templates/integrations/a2a.example.jsonc +23 -7
  6. package/dist/builtin/skill-creator/scripts/aggregate-benchmark.js +2 -1
  7. package/dist/builtin/skill-creator/scripts/analyze-benchmark.d.ts +1 -0
  8. package/dist/builtin/skill-creator/scripts/analyze-benchmark.js +4 -3
  9. package/dist/builtin/skill-creator/scripts/compare-runs.d.ts +1 -0
  10. package/dist/builtin/skill-creator/scripts/compare-runs.js +4 -3
  11. package/dist/builtin/skill-creator/scripts/generate-review.js +2 -1
  12. package/dist/builtin/skill-creator/scripts/grade-evals.d.ts +1 -0
  13. package/dist/builtin/skill-creator/scripts/grade-evals.js +4 -3
  14. package/dist/builtin/skill-creator/scripts/improve-description.js +2 -1
  15. package/dist/builtin/skill-creator/scripts/init-skill.d.ts +1 -0
  16. package/dist/builtin/skill-creator/scripts/init-skill.js +4 -3
  17. package/dist/builtin/skill-creator/scripts/install-skill.d.ts +1 -0
  18. package/dist/builtin/skill-creator/scripts/install-skill.js +4 -3
  19. package/dist/builtin/skill-creator/scripts/package-skill.d.ts +1 -0
  20. package/dist/builtin/skill-creator/scripts/package-skill.js +4 -3
  21. package/dist/builtin/skill-creator/scripts/quick-validate.d.ts +1 -0
  22. package/dist/builtin/skill-creator/scripts/quick-validate.js +4 -3
  23. package/dist/builtin/skill-creator/scripts/run-eval.d.ts +1 -0
  24. package/dist/builtin/skill-creator/scripts/run-eval.js +4 -3
  25. package/dist/builtin/skill-creator/scripts/run-loop.js +2 -1
  26. package/dist/builtin/skill-creator/scripts/run-trigger-eval.js +2 -1
  27. package/dist/builtin/skill-creator/scripts/utils.d.ts +3 -0
  28. package/dist/builtin/skill-creator/scripts/utils.js +5 -1
  29. package/dist/chunks/agent-5PHGNFW3.js +2 -0
  30. package/dist/chunks/argument-completer-FXRZFMTC.js +2 -0
  31. package/dist/chunks/chunk-6UENDY33.js +89 -0
  32. package/dist/chunks/{chunk-6FOJVETH.js → chunk-BWX4YZX3.js} +7 -7
  33. package/dist/chunks/chunk-CDCCTM5I.js +278 -0
  34. package/dist/chunks/chunk-CEB5FDND.js +810 -0
  35. package/dist/chunks/{chunk-2CFHBKRE.js → chunk-E7C4DRD2.js} +2 -2
  36. package/dist/chunks/chunk-EVXIJPZ6.js +218 -0
  37. package/dist/chunks/chunk-FQ7NVMSU.js +319 -0
  38. package/dist/chunks/chunk-GC4JBMBS.js +385 -0
  39. package/dist/chunks/{chunk-Z3KFRTSB.js → chunk-HTVLCHWH.js} +1 -1
  40. package/dist/chunks/chunk-IHPH3444.js +655 -0
  41. package/dist/chunks/{chunk-YAZQTC2L.js → chunk-KHEFUT6O.js} +1 -1
  42. package/dist/chunks/{chunk-7OEBJGHK.js → chunk-PMKAG4SZ.js} +1 -1
  43. package/dist/chunks/chunk-RPF5RGWZ.js +479 -0
  44. package/dist/chunks/{chunk-UID7BLAB.js → chunk-RXXKDR6Y.js} +1 -1
  45. package/dist/chunks/chunk-T5U4OP2N.js +292 -0
  46. package/dist/chunks/chunk-TRNAWSXP.js +123 -0
  47. package/dist/chunks/chunk-UZSHBQER.js +30 -0
  48. package/dist/chunks/{chunk-TQDBTTIM.js → chunk-VECRQTSD.js} +44 -44
  49. package/dist/chunks/compaction-config-6J6XCBJS.js +2 -0
  50. package/dist/chunks/{construction-bootstrap-VQLQGWPY.js → construction-bootstrap-KPCAE3VN.js} +1 -1
  51. package/dist/chunks/dist-RWEVZOFK.js +2 -0
  52. package/dist/chunks/{dist-BFT5YIGU.js → dist-WWL2DNIJ.js} +1 -1
  53. package/dist/chunks/host-OGS2R5J4.js +2 -0
  54. package/dist/chunks/run-manager-2YMM3BOT.js +2 -0
  55. package/dist/chunks/{utils-SID4HW2Q.js → utils-2YHRUZYC.js} +1 -1
  56. package/dist/index.d.ts +21 -20
  57. package/dist/index.js +4 -4
  58. package/dist/kodax_bootstrap.js +2 -1
  59. package/dist/kodax_cli.js +1850 -1476
  60. package/dist/kodax_resume.js +570 -34
  61. package/dist/provider-capabilities.json +85 -50
  62. package/dist/runtime-worker.js +1737 -1369
  63. package/dist/sandbox-workspace-session.js +285 -19
  64. package/dist/sdk-a2a.d.ts +19 -13
  65. package/dist/sdk-a2a.js +8 -8
  66. package/dist/sdk-agent.d.ts +80 -150
  67. package/dist/sdk-agent.js +1 -1
  68. package/dist/sdk-coding.d.ts +186 -66
  69. package/dist/sdk-coding.js +1 -1
  70. package/dist/sdk-experimental-memory.d.ts +3 -3
  71. package/dist/sdk-experimental-memory.js +1 -1
  72. package/dist/sdk-llm.d.ts +7 -7
  73. package/dist/sdk-llm.js +1 -1
  74. package/dist/sdk-mcp.d.ts +1 -1
  75. package/dist/sdk-mcp.js +1 -1
  76. package/dist/sdk-media.d.ts +1 -1
  77. package/dist/sdk-media.js +1 -1
  78. package/dist/sdk-repl.d.ts +31 -213
  79. package/dist/sdk-repl.js +2 -2
  80. package/dist/sdk-runtime.d.ts +230 -31
  81. package/dist/sdk-runtime.js +1 -1
  82. package/dist/sdk-sandbox.js +1 -1
  83. package/dist/sdk-session.d.ts +8 -8
  84. package/dist/sdk-session.js +1 -1
  85. package/dist/sdk-skills.js +1 -1
  86. package/dist/semantic-worker.js +283 -18
  87. package/dist/types-chunks/{base.d-4e74xDdy.d.ts → base.d-DxG0m09Z.d.ts} +1 -1
  88. package/dist/types-chunks/{bash-prefix-extractor.d-uAe2Oqda.d.ts → bash-prefix-extractor.d-DStqKIlu.d.ts} +31 -8
  89. package/dist/types-chunks/{capability-learning.d-CVsdHw4j.d.ts → capability-learning.d-iPSiAmc_.d.ts} +1 -1
  90. package/dist/types-chunks/{capsule.d-BlSv9l3V.d.ts → capsule.d-BuFJaYxA.d.ts} +14 -21
  91. package/dist/types-chunks/{types.d-BA-Jwpfs.d.ts → controller.d-C2aHqzHj.d.ts} +344 -11
  92. package/dist/types-chunks/{guardrail.d-CXDYRgZ3.d.ts → guardrail.d-DM-OK0m3.d.ts} +187 -116
  93. package/dist/types-chunks/{history-retrieval.d-DtCy7x64.d.ts → history-retrieval.d-DImTKgO6.d.ts} +16 -3
  94. package/dist/types-chunks/{oauth-login.d-Bgb4rdLN.d.ts → oauth-login.d-6i-vidxv.d.ts} +6 -2
  95. package/dist/types-chunks/{public-api.d-B3AohsxN.d.ts → public-api.d-CewTvUKV.d.ts} +335 -38
  96. package/dist/types-chunks/{repl.d-Ie_ZXb_U.d.ts → repl.d-DvXQtgr7.d.ts} +5 -5
  97. package/dist/types-chunks/{resolver.d-iAQ9ocLB.d.ts → resolver.d-C9IW4fmU.d.ts} +5 -2
  98. package/dist/types-chunks/{run-manager.d-D1twIhF9.d.ts → run-manager.d-C6ahvZqK.d.ts} +1 -1
  99. package/dist/types-chunks/{sdk-session-DB9KksIx.d.ts → sdk-session-C59RUrri.d.ts} +3 -3
  100. package/dist/types-chunks/shell-command-sets.d-Dp4UvPLL.d.ts +297 -0
  101. package/dist/types-chunks/{side-query.d-DTuLPcC5.d.ts → side-query.d-CP1LiRiH.d.ts} +8 -2
  102. package/dist/types-chunks/{types-D3g6XUQr.d.ts → types-Bf3Y3mW-.d.ts} +1 -1
  103. package/dist/types-chunks/{types.d-BH0ZkTGf.d.ts → types.d-BzpPDBPV.d.ts} +2 -2
  104. package/dist/types-chunks/{types.d-DIpZJKUl.d.ts → types.d-CgrXrFrq.d.ts} +16 -2
  105. package/dist/types-chunks/{types.d-DVDTIfB_.d.ts → types.d-yuJzE82x.d.ts} +4 -4
  106. package/dist/types-chunks/{utils.d-CVp6bFl9.d.ts → utils.d-BpACJmzt.d.ts} +24 -6
  107. package/docs/SDK_EMBEDDER_GUIDE.md +1148 -598
  108. package/package.json +6 -3
  109. package/dist/chunks/agent-ASP4MS3X.js +0 -2
  110. package/dist/chunks/argument-completer-JYQ7FX3W.js +0 -2
  111. package/dist/chunks/chunk-35PPHOQ2.js +0 -292
  112. package/dist/chunks/chunk-43QNNDHR.js +0 -29
  113. package/dist/chunks/chunk-5GO6FH7L.js +0 -458
  114. package/dist/chunks/chunk-5NDCSFOP.js +0 -78
  115. package/dist/chunks/chunk-6XM4B6K2.js +0 -48
  116. package/dist/chunks/chunk-6YZUE6NC.js +0 -240
  117. package/dist/chunks/chunk-KQLQYHWU.js +0 -386
  118. package/dist/chunks/chunk-NXO6GWSY.js +0 -46
  119. package/dist/chunks/chunk-T2XZTLYE.js +0 -316
  120. package/dist/chunks/chunk-VAT2QYXM.js +0 -765
  121. package/dist/chunks/chunk-YVRYHC4C.js +0 -655
  122. package/dist/chunks/compaction-config-BQSSIWK5.js +0 -2
  123. package/dist/chunks/dist-PCFE24YP.js +0 -2
  124. package/dist/chunks/host-UTFXCYYM.js +0 -2
  125. package/dist/chunks/run-manager-PJU3WIFJ.js +0 -2
  126. package/dist/types-chunks/guardrail.d-BRE_ErEj.d.ts +0 -156
@@ -28,17 +28,17 @@ are NOT obvious from inspecting the type definitions alone:
28
28
  18. [External-agent executor plane](#18-external-agent-executor-plane-feature_258-v0767)
29
29
  19. [Session surface filtering and cursor pagination](#19-session-surface-filtering-and-cursor-pagination-feature_261-v0767)
30
30
  20. [Cost-disciplined workflow routing and telemetry](#20-cost-disciplined-workflow-routing-and-telemetry-feature_259-v0767)
31
- 21. [Experimental governed memory — `/experimental-memory`](#21-experimental-governed-memory--experimental-memory-feature_260--feature_275-v0768v0777)
31
+ 21. [Experimental governed memory — `/experimental-memory`](#21-experimental-governed-memory--experimental-memory-feature_260--feature_275-v0768v0777)
32
32
  22. [Bidirectional A2A 1.0 — `/a2a`](#22-bidirectional-a2a-10--a2a-feature_267-v0769)
33
33
  23. [Shared Coder daemon for Space and IDE hosts](#23-shared-coder-daemon-for-space-and-ide-hosts-feature_269-v0769)
34
34
  24. [Runtime-owned Auto Mode and plan-approval bridges](#24-runtime-owned-auto-mode-and-plan-approval-bridges-v0772v0773)
35
35
  25. [Always-on context compaction and bounded transcript recovery](#25-always-on-context-compaction-and-bounded-transcript-recovery-v0774)
36
- 26. [Agent mailbox control versus SDK event telemetry](#26-agent-mailbox-control-versus-sdk-event-telemetry-v0774)
37
- 27. [Windows GUI background subprocess visibility](#27-windows-gui-background-subprocess-visibility-v0775)
38
- 28. [Host-configurable Shell Execution Contract](#28-host-configurable-shell-execution-contract-v0777)
39
- 29. [Evidence-gated background Skill learning](#29-evidence-gated-background-skill-learning-feature_263-v0778)
40
- 30. [Standalone sandbox SDK](#30-standalone-sandbox-sdk-v0778)
41
- - [Learned Skill promotion reference](#learned-skill-promotion-reference-v0778)
36
+ 26. [Agent mailbox control versus SDK event telemetry](#26-agent-mailbox-control-versus-sdk-event-telemetry-v0774)
37
+ 27. [Windows GUI background subprocess visibility](#27-windows-gui-background-subprocess-visibility-v0775)
38
+ 28. [Host-configurable Shell Execution Contract](#28-host-configurable-shell-execution-contract-v0777)
39
+ 29. [Evidence-gated background Skill learning](#29-evidence-gated-background-skill-learning-feature_263-v0778)
40
+ 30. [Standalone sandbox SDK](#30-standalone-sandbox-sdk-v0778)
41
+ - [Learned Skill promotion reference](#learned-skill-promotion-reference-v0778)
42
42
 
43
43
  §1–§3 (and the Phase-7/8 MCP-popout surface in §1) land in v0.7.42
44
44
  under FEATURE_186 (see [ADR-032](ADR.md#adr-032-sdk-embedder-surface-closure-feature_186-v0742)).
@@ -678,7 +678,10 @@ missing-storage warning by itself.
678
678
 
679
679
  ```ts
680
680
  import { runKodaX } from '@kodax-ai/kodax/coding';
681
- import { createSessionManager } from '@kodax-ai/kodax/session';
681
+ import {
682
+ createSessionManager,
683
+ exportSessionBundle,
684
+ } from '@kodax-ai/kodax/session';
682
685
 
683
686
  // One manager per host process; reuse across runs so the
684
687
  // per-session write queue + append-watermark caches stay coherent.
@@ -687,6 +690,8 @@ const {
687
690
  listSessions,
688
691
  loadSession,
689
692
  loadFullTranscript,
693
+ readFullTranscript,
694
+ readSessionCapture,
690
695
  appendClientNotice,
691
696
  compactSession,
692
697
  } = createSessionManager();
@@ -720,6 +725,17 @@ const nextPage = recent.at(-1)?.cursor
720
725
  : [];
721
726
  const replay = await loadSession('s_my_chat');
722
727
  const scrollback = await loadFullTranscript('s_my_chat');
728
+ const historyAbort = new AbortController();
729
+ const strictScrollback = await readFullTranscript('s_my_chat', {
730
+ timeoutMs: 15_000,
731
+ signal: historyAbort.signal,
732
+ });
733
+ const capture = await readSessionCapture('s_my_chat', {
734
+ timeoutMs: 15_000,
735
+ });
736
+ const bundle = await exportSessionBundle('s_my_chat', {
737
+ timeoutMs: 15_000,
738
+ });
723
739
  const compacted = await compactSession('s_my_chat', { dryRun: true });
724
740
 
725
741
  await appendClientNotice('s_my_chat', {
@@ -728,6 +744,14 @@ await appendClientNotice('s_my_chat', {
728
744
  });
729
745
  ```
730
746
 
747
+ `readSessionCapture()` returns the active `data` and full `transcript` from one
748
+ immutable storage boundary. `readFullTranscript()` and
749
+ `readSessionCapture()` are strict read-only APIs: timeout/cancellation,
750
+ corruption, version drift, or resync fail explicitly and never trigger legacy
751
+ Session migration or recovery. `exportSessionBundle()` preserves the exact
752
+ main/sidecar bytes with hashes and compatibility diagnostics; use it for a
753
+ support or recovery bundle, not as proof that a Session can resume.
754
+
731
755
  ### Auto-resume selection in v0.7.74
732
756
 
733
757
  With `session.autoResume: true` (or `resume: true`) and no explicit ID, KodaX
@@ -751,6 +775,9 @@ interface SessionManager {
751
775
  listSessions(...): Promise<SessionSummary[]>;
752
776
  loadSession(id): Promise<...>;
753
777
  loadFullTranscript(id): Promise<...>;
778
+ readFullTranscript(id, options?): Promise<...>;
779
+ readConversationHistory(id, options?): Promise<...>;
780
+ readSessionCapture(id, options?): Promise<SessionReadCapture | null>;
754
781
  appendClientNotice(id, opts): Promise<SessionTranscriptEntry | null>;
755
782
  compactSession(id, opts?): Promise<CompactSessionResult>;
756
783
  forkSession(id, opts?): Promise<...>;
@@ -776,13 +803,22 @@ Session persistence exposes three related but different layers:
776
803
  | Need | Use | Meaning |
777
804
  |---|---|---|
778
805
  | Continue a model turn | `loadSession(id)` | Active branch only. This is the context KodaX would resume from. |
779
- | Render a host sidebar / scrollback | `loadFullTranscript(id)` | Append-order transcript entries, including entries no longer on the active branch when available. |
806
+ | Render the ordinary conversation | `readConversationHistory(id)` | SDK-resolved conversation order with proven compaction copies folded and ambiguity reported. |
807
+ | Render audit / raw scrollback | `readFullTranscript(id)` or `loadFullTranscript(id)` | Append-order physical entries, including archived islands and non-active branches. |
780
808
  | Reuse TUI display projection | `SessionData.uiHistory` | Optional bounded replay cache. Interactive REPL sessions may write it; headless SDK sessions may not. |
781
809
 
782
- For product UI, prefer `loadFullTranscript(id)` for conversation history and
783
- treat `loadSession(id)` as the model-context API. Do not assume `uiHistory`
784
- exists. It is intentionally a small, lossy replay cache; canonical facts remain
785
- in `messages` / `lineage`.
810
+ For product UI, use `readConversationHistory(id)` for the ordinary chat and
811
+ keep `readFullTranscript(id)` available for an audit/details view.
812
+ `loadSession(id)` remains the model-context API. Do not assume `uiHistory`
813
+ exists; it is intentionally a small, lossy replay cache.
814
+
815
+ `loadSession(id)` and Runtime `sessions.load(id)` are pure snapshot reads. They
816
+ do not emit `session.loaded`; a host that explicitly loads a snapshot should
817
+ update its own selected/loaded UI state from the resolved promise. The
818
+ compatibility `session.loaded` event (and the CLI bridge's `onSessionStart`)
819
+ remains bound to the Provider Run execution boundary, where a Session actually
820
+ becomes active. Do not use that Run-lifecycle event as a generic data-load
821
+ notification.
786
822
 
787
823
  `loadFullTranscript(id).transcriptEntries` is the structured host-facing
788
824
  scrollback. Each entry has stable ownership and ordering fields:
@@ -806,14 +842,145 @@ interface SessionTranscriptEntry {
806
842
 
807
843
  `entryId` is the physical lineage node id. `logicalId` is stable across
808
844
  forked/cloned copies of the same transcript item, and `sourceEntryId` is present
809
- on cloned entries to point back to the root physical source entry. Hosts that
810
- want to fold cloned history should group by `logicalId`, not by
811
- `message.role`, content, timestamp, or `[compacted]` placeholders.
845
+ on cloned entries to point back to the root physical source entry. These fields
846
+ support audit inspection, but a host must not infer the complete ordinary-chat
847
+ fold merely by grouping them: legacy omissions and conflicting metadata require
848
+ lineage validation. Use `readConversationHistory()` instead of implementing
849
+ host-side folding, and never guess from `message.role`, content, timestamp,
850
+ `turnId`, or `[compacted]` placeholders.
812
851
  Legacy entries without persisted provenance use `logicalId === entryId` and omit
813
852
  `sourceEntryId`; treat that as "unknown/not cloned", not as content-based proof
814
853
  that no older clone exists.
815
854
  `loadFullTranscript()` still returns raw append-order scrollback; it does not
816
- hide compaction notices or silently merge branches.
855
+ hide archived islands or non-active branches, and it does not silently merge
856
+ branches. The host owns branch visibility, folding, and main-chat presentation.
857
+
858
+ `readConversationHistory()` is the supported folding boundary. Every returned
859
+ entry has a physical `boundaryId` and `auditEntryIds` naming all copies that the
860
+ SDK could prove represent the same interaction. Modern Sessions use
861
+ `logicalId` / `sourceEntryId`; legacy copies are folded only when a persisted
862
+ compaction boundary and one unique lineage suffix prove the relationship.
863
+ An inactive compaction epoch or non-leaf ancestor is crossed only when a
864
+ contiguous retained prefix has exact provenance and its complete parent path
865
+ predates the compaction in append order. Content-only legacy matching never
866
+ authorizes that fallback.
867
+ KodaX never globally sorts or deduplicates by content, timestamp, or `turnId`.
868
+
869
+ The result status is part of the contract:
870
+
871
+ - `resolved`: every fold and branch predecessor used by the projection was
872
+ proven. A persisted Session with no conversation records is also resolved
873
+ with empty `entries` and `issues`, including the normal interval after a Run
874
+ is accepted but before its body enters canonical history.
875
+ - `partial`: persisted lineage or transcript identity needed for a complete
876
+ projection of one or more existing conversation records is unavailable.
877
+ Available physical records are retained; Actor/Run state by itself does not
878
+ make an otherwise empty persisted conversation partial.
879
+ - `ambiguous`: multiple legacy interpretations remain. All candidates are
880
+ retained and `issues[]` explains why; the host must not present the result as
881
+ confidently deduplicated.
882
+
883
+ Issues are bounded diagnostic summaries: `occurrenceCount` is the number of
884
+ diagnostics represented, `entryCount` is the pre-bounding evidence-reference
885
+ count, and `entryIds` is a bounded exact sample. Conversation entries themselves
886
+ are never removed to make diagnostic metadata fit a transport page.
887
+
888
+ Runtime exposes the identical projection in embedded and daemon modes through
889
+ `sessions.conversation()`, `sessions.conversationPage()`, and
890
+ `sessions.conversationEntryChunk()`. Modern Session writes prepare a bounded
891
+ on-disk page index, so the first finite page reads only its metadata, fixed-size
892
+ index records, and requested inline bodies instead of materializing all history.
893
+ A source-boundary-fenced copy of the Session's minimal admission identity lets
894
+ the shared daemon authorize that bounded read before checking cursors, capacity,
895
+ or entry metadata; it does not perform a full Session `peek()` on a cache hit.
896
+ A cache-less older Session is upgraded by one canonical fallback read. Direct
897
+ and paged reads share the same `revision`, `sourceRevision`, status, issues, and
898
+ logical entry order. Page and chunk cursors are revision-fenced: if the Session
899
+ changes, the Runtime returns `resync_required` and the host must request a fresh
900
+ first page. Derived manifest, descriptor, and chunk reads have fixed allocation
901
+ ceilings; a corrupt or concurrently replaced generation is rejected rather
902
+ than trusted. Pages are fetched newest-tail-first while each page is internally in
903
+ forward order, so prepend each fetched page to reconstruct `conversation().entries`;
904
+ do not append pages in fetch order.
905
+ Request `requirements: { conversationHistory: 1 }` when connecting to a daemon
906
+ that must support this contract.
907
+
908
+ For write-side hosts that already own a newly produced append tail,
909
+ `await FileSessionStorage.prepareSessionAppend(id)` returns an authenticated
910
+ one-shot boundary or `null` when no canonical cache witness is available.
911
+ `appendPreparedSessionTail(id, delta)` appends only the supplied new linear
912
+ lineage, artifact, and extension records. The delta deliberately has no
913
+ historical-array field: its read and compute cost is bounded by the new tail,
914
+ and a stale boundary, prefix/sidecar change, duplicate identity, non-linear
915
+ lineage tail, or concurrent durable write fails with
916
+ `SessionReadError.code === 'data_changed'`. Reload, obtain a fresh boundary,
917
+ and rebuild the tail before retrying.
918
+
919
+ A non-null fulfilled append result is the reusable successor boundary. A
920
+ fulfilled `null` means the tail did commit exactly once, but the successor
921
+ could not be witnessed; reload before another append and do not retry that
922
+ tail. Use `appendSessionDelta(id, data)` for a full mutable snapshot, a `null`
923
+ prepared boundary, or any historical rewrite; that API performs the exact
924
+ canonical merge and persists index, nested-object, and post-helper mutations.
925
+ Data returned by `storage.load()` remains an ordinary mutable,
926
+ `structuredClone()`-compatible `KodaXSessionData` object. The derived page
927
+ cache is only a recoverable acceleration structure: its fixed-size identity
928
+ filter is accepted for appends only when its exact bundle revision and hash
929
+ match canonical metadata in the Session main file.
930
+
931
+ ```ts
932
+ const baseline = await storage.prepareSessionAppend(sessionId);
933
+ if (baseline === null) {
934
+ await storage.appendSessionDelta(sessionId, completeMutableSnapshot);
935
+ } else {
936
+ const successor = await storage.appendPreparedSessionTail(sessionId, {
937
+ baseline,
938
+ title,
939
+ activeEntryId: newEntry.id,
940
+ lineageEntries: [newEntry],
941
+ });
942
+ if (successor === null) {
943
+ await storage.load(sessionId); // committed; resync before the next tail
944
+ }
945
+ }
946
+ ```
947
+
948
+ Use a returned entry as a revision-fenced fork or rewind boundary instead of
949
+ guessing from content or a historical `turnId`:
950
+
951
+ ```ts
952
+ const history = await runtime.sessions.conversation(sessionId);
953
+ const item = history?.entries.find((entry) => entry.boundaryId !== undefined);
954
+ if (history && item?.boundaryId) {
955
+ await runtime.sessions.fork({
956
+ sessionId,
957
+ historyBoundary: {
958
+ entryId: item.boundaryId,
959
+ sourceRevision: history.sourceRevision,
960
+ },
961
+ });
962
+ }
963
+ ```
964
+
965
+ If the Session changes before the mutation, the Runtime returns
966
+ `resync_required`; an unknown boundary returns `null` and never falls back to a
967
+ different point.
968
+
969
+ The standalone session API accepts the same boundary (named `boundaryId` on
970
+ that surface) and fails closed with `null`:
971
+
972
+ ```ts
973
+ const history = await manager.readConversationHistory(sessionId);
974
+ const item = history?.entries.at(-1);
975
+ if (history && item?.boundaryId) {
976
+ await manager.forkSession(sessionId, {
977
+ historyBoundary: {
978
+ boundaryId: item.boundaryId,
979
+ sourceRevision: history.sourceRevision,
980
+ },
981
+ });
982
+ }
983
+ ```
817
984
 
818
985
  Since v0.7.63, rewind audit markers are represented as
819
986
  `type: 'rewind_marker'`. They are useful for host scrollback and audit UI, but
@@ -1328,7 +1495,8 @@ interface KodaXModelCapabilities {
1328
1495
  supportedEfforts?: Array<{ value: string; isDefault?: boolean; isUserVisible?: boolean }>;
1329
1496
  };
1330
1497
  contextWindow?: number; // input tokens (provider default + per-model override cascade)
1331
- maxOutputTokens?: number; // per-turn max_tokens KodaX requests — see note below
1498
+ maxOutputTokens?: number; // per-turn output limit KodaX requests — see note below
1499
+ maxOutputTokensField?: 'max_tokens' | 'max_completion_tokens';
1332
1500
  thinkingBudgetCap?: number; // tokens (native-budget providers only)
1333
1501
  isDefault: boolean; // true for the provider's default model
1334
1502
  }
@@ -1435,9 +1603,10 @@ for (const providerName of Object.keys(KODAX_PROVIDER_SNAPSHOTS)) {
1435
1603
 
1436
1604
  ### A note on `maxOutputTokens`
1437
1605
 
1438
- `KodaXModelCapabilities.maxOutputTokens` is the **per-turn `max_tokens`
1439
- KodaX requests**, NOT the upstream "theoretical maximum". The two
1440
- diverge because:
1606
+ `KodaXModelCapabilities.maxOutputTokens` is the **per-turn output-token limit
1607
+ KodaX requests**, NOT the upstream "theoretical maximum". For OpenAI-compatible
1608
+ routes, `maxOutputTokensField` reports whether that limit is serialized as
1609
+ `max_tokens` or `max_completion_tokens`. The two size concepts diverge because:
1441
1610
 
1442
1611
  - **What upstream advertises is often unreliable.** A 2026-05 probe
1443
1612
  against `zhipu-coding` / `kimi-code` / `minimax-coding` / `ark-coding`
@@ -1951,10 +2120,19 @@ registerCustomProviders([{
1951
2120
  baseUrl: 'https://api.example.com/v1',
1952
2121
  apiKeyEnv: 'MY_GATEWAY_KEY',
1953
2122
  model: 'gpt-4-mini',
2123
+ maxOutputTokensField: 'max_completion_tokens',
1954
2124
  verifyStrategy: 'minimal-message', // optional override
1955
2125
  }]);
1956
2126
  ```
1957
2127
 
2128
+ For an OpenAI-compatible endpoint that follows DeepSeek Chat Completions, set
2129
+ `maxOutputTokensField: 'max_tokens'`. The default is
2130
+ `max_completion_tokens`; an object entry in `models[]` may override the
2131
+ provider-level value for a mixed gateway. DeepSeek V4 custom configurations
2132
+ should likewise use the model-specific `deepseek-v4-flash-openai` or
2133
+ `deepseek-v4-pro-openai` reasoning preset. Both built-in DeepSeek V4 routes are
2134
+ text-only.
2135
+
1958
2136
  The validator rejects illegal combinations:
1959
2137
  - `protocol: 'openai'` + `verifyStrategy: 'count-tokens'` → throws (OpenAI protocol has no count_tokens endpoint).
1960
2138
 
@@ -2385,6 +2563,7 @@ The important creation options are:
2385
2563
  | `isolation` | `'inline'` | Embedded-only. `'worker'` creates a private Runtime Worker; daemon rejects any explicit isolation because it is already process-isolated. |
2386
2564
  | `worker.resourceLimits` | unset | Optional V8 heap/stack limits; requires `isolation: 'worker'`. |
2387
2565
  | `worker.shutdownTimeoutMs` | `2000` | Grace before the parent terminates the Runtime Worker. |
2566
+ | `worker.configuredA2A` | `false` | Explicitly lets the Worker owner load and reconcile `<homeDir>/.kodax/integrations/a2a.json`; installs the full list/describe/spawn/task plane inside the Worker. |
2388
2567
  | `requirements.hardDispose` | `false` | Rejects inline and daemon forms; prevents an accidental weaker ownership form. |
2389
2568
  | `homeDir` | unset | When omitted, use the exact resolved `KODAX_HOME`. When set, this is the base directory that owns `.kodax`, with the same meaning as CLI `daemon --home`; daemon state/config live under `<homeDir>/.kodax`. |
2390
2569
  | `profile` | `'default'` | Daemon uniqueness and runtime configuration namespace. |
@@ -2472,6 +2651,15 @@ SDK auto-start allows `daemonStartupTimeoutMs` (default 60 seconds) and
2472
2651
  concurrent test/desktop startup without weakening PID, endpoint, token, or
2473
2652
  runtime-identity validation.
2474
2653
 
2654
+ On Windows, current child cleanup verifies observed process identities and
2655
+ reports indeterminate outcomes instead of falling back to bare-PID success.
2656
+ That is not Job Object-grade containment: if an intermediate process exits
2657
+ between snapshots, an already-running descendant can become unobservable.
2658
+ Until Issue 256 is resolved (scheduled for v0.7.84) with spawn-time Job Object
2659
+ assignment and a host-issued Worker owner lease, embedders must not treat
2660
+ Runtime/Worker close or executor cleanup as proof that every descendant has
2661
+ terminated.
2662
+
2475
2663
  `homeDir` and `KODAX_HOME` deliberately name different levels. Runtime SDK and
2476
2664
  CLI daemon `--home` accept the **base directory that contains `.kodax`**;
2477
2665
  lower-level `KODAX_HOME` points at the **data directory itself** and need not be
@@ -2493,7 +2681,9 @@ const runtime = await createKodaXRuntime({
2493
2681
  worker: {
2494
2682
  resourceLimits: { maxOldGenerationSizeMb: 1024 },
2495
2683
  shutdownTimeoutMs: 2000,
2684
+ configuredA2A: true,
2496
2685
  },
2686
+ requirements: { externalAgents: true },
2497
2687
  });
2498
2688
 
2499
2689
  try {
@@ -2515,6 +2705,12 @@ Worker `resourceLimits` bound parts of the V8 heap only. They do not cover every
2515
2705
  kind of native/external memory and do not make Node code safe to treat as
2516
2706
  untrusted. Worker isolation is a fault boundary, not a security sandbox.
2517
2707
 
2708
+ Configured A2A is loaded by the Worker owner, not serialized from a parent
2709
+ factory closure. `worker.configuredA2A` is therefore an explicit opt-in. When
2710
+ enabled, the Worker reconciles the same user document as the CLI/daemon path,
2711
+ advertises `externalAgents`, and backs `listDispatchable`, `describe`,
2712
+ `preflight`, and external Actor dispatch with one real executor plane.
2713
+
2518
2714
  Callers that cannot accept a silent fallback can require the capability:
2519
2715
 
2520
2716
  ```ts
@@ -2543,6 +2739,33 @@ The same method name deliberately has deployment-specific ownership effects:
2543
2739
  automatically retry or replay an in-flight run after a Worker/daemon owner dies,
2544
2740
  because provider and tool side effects may already have happened.
2545
2741
 
2742
+ `runtime.shutdown` and `stopForInline()` responses mean the fenced stop was
2743
+ accepted; host-close logging and owner-lock release are also intermediate
2744
+ progress boundaries. A successful `kodax daemon stop` / `restart` additionally
2745
+ waits for the original daemon PID to disappear and verifies a shutdown-success
2746
+ outcome bound to that exact Runtime ID and PID. Before that process exits, the
2747
+ serve host completes bounded A2A/extension, LSP, managed-child-tree, and tracing
2748
+ cleanup; an unverified current-owner child tree, cleanup timeout, failed outcome,
2749
+ or missing outcome makes CLI stop fail instead of reporting a false success.
2750
+ The stop client also owns an outer watchdog that starts when the request is
2751
+ accepted. On Windows it captures the daemon's creation identity before
2752
+ requesting shutdown and can therefore reclaim that exact process tree if
2753
+ Runtime close never settles or synchronous code blocks the daemon event loop.
2754
+ Node 20 exposes only cached-PID signaling on POSIX, so KodaX fails closed there
2755
+ instead of risking a reused PID/PGID; the result is `cleanup_unverified` and the
2756
+ process is left to an external lifecycle manager until Issue 269 supplies a
2757
+ retained native handle/supervisor. A forced exit without a matching success
2758
+ outcome is never success. After the original PID exits, the client re-reads the profile; JSON
2759
+ results use `replacementRunning: true` and return the current state/health when
2760
+ a replacement daemon has already acquired the owner lock. Such a result also
2761
+ uses `stopped: false` and `reason: 'replacement_running'`, so existing callers
2762
+ that gate cleanup on `stopped` remain safe. Callers must not clean or rebuild a
2763
+ profile while that flag is present or health is non-missing.
2764
+ The administrative connection also verifies that `initialize.identity` still
2765
+ matches the Runtime ID/profile observed before connecting; if an old owner exits
2766
+ and a replacement binds the same endpoint first, the stale stop command fails
2767
+ without sending `daemon.stop` to the replacement.
2768
+
2546
2769
  ### Daemon ownership and state
2547
2770
 
2548
2771
  Daemon ownership is scoped by `homeDir + profile`.
@@ -2591,7 +2814,9 @@ Operational guidance:
2591
2814
  configurations;
2592
2815
  - test harnesses that auto-start a process daemon must send authenticated
2593
2816
  `runtime.shutdown` (or run `kodax daemon stop --home <dir> --profile <name>`)
2594
- before deleting their temporary home; `runtime.close()` only detaches;
2817
+ before deleting their temporary home; use the CLI command when the caller
2818
+ needs a completed process-exit boundary, because `runtime.close()` only
2819
+ detaches and the low-level shutdown response is acceptance, not completion;
2595
2820
  - KodaX's own Vitest harness also supplies an internal worker-PID marker so a
2596
2821
  forcibly terminated worker cannot strand its test daemon; this is a test-only
2597
2822
  fallback, not a public SDK option or a production idle-shutdown policy;
@@ -2779,11 +3004,11 @@ await runtime.mcp.upsertServer('filesystem', {
2779
3004
  });
2780
3005
  await runtime.mcp.reloadServers();
2781
3006
 
2782
- const commands = await runtime.catalog.commands(process.cwd());
2783
- const skills = await runtime.catalog.skills({ projectRoot: process.cwd() });
2784
- ```
2785
-
2786
- This is the intended path for KodaX Space, IDE adapters, and settings UIs:
3007
+ const commands = await runtime.catalog.commands(process.cwd());
3008
+ const skills = await runtime.catalog.skills({ projectRoot: process.cwd() });
3009
+ ```
3010
+
3011
+ This is the intended path for KodaX Space, IDE adapters, and settings UIs:
2787
3012
  session defaults go through `sessions.updateSettings()`, one-turn overrides go
2788
3013
  through `runs.start({ options })`, and daemon/profile config goes through
2789
3014
  `config`, `catalog`, and `mcp`.
@@ -2803,19 +3028,19 @@ engine:
2803
3028
  Hosts that set `capabilities.contextDiagnostics: true` can read:
2804
3029
 
2805
3030
  ```ts
2806
- const budget = await runtime.diagnostics.latestContextBudget({ sessionId });
2807
- const exposure = await runtime.diagnostics.latestToolExposure({ sessionId });
2808
- const cache = await runtime.diagnostics.latestProviderCacheDiagnostic({ sessionId });
2809
- ```
2810
-
2811
- Pass `{ sessionId, contextKind: 'child', agentId }` to query one logical child
2812
- even though its physical transcript uses an isolated Session. Diagnostic
2813
- payloads carry `contextId` and, for children, `parentContextId`; reconnecting
2814
- hosts can use the same latest APIs instead of fabricating identity fields.
2815
- These diagnostics are designed for status panels and debugging. Budget
2816
- snapshots contain counts, and cache diagnostics contain hashes plus
2817
- Provider-reported usage; they do not contain raw prompt or sensitive tool
2818
- input/output.
3031
+ const budget = await runtime.diagnostics.latestContextBudget({ sessionId });
3032
+ const exposure = await runtime.diagnostics.latestToolExposure({ sessionId });
3033
+ const cache = await runtime.diagnostics.latestProviderCacheDiagnostic({ sessionId });
3034
+ ```
3035
+
3036
+ Pass `{ sessionId, contextKind: 'child', agentId }` to query one logical child
3037
+ even though its physical transcript uses an isolated Session. Diagnostic
3038
+ payloads carry `contextId` and, for children, `parentContextId`; reconnecting
3039
+ hosts can use the same latest APIs instead of fabricating identity fields.
3040
+ These diagnostics are designed for status panels and debugging. Budget
3041
+ snapshots contain counts, and cache diagnostics contain hashes plus
3042
+ Provider-reported usage; they do not contain raw prompt or sensitive tool
3043
+ input/output.
2819
3044
 
2820
3045
  ### Protocol schema and versioning
2821
3046
 
@@ -2834,7 +3059,11 @@ import {
2834
3059
  The schema is additive within this patch line. Removing or changing required
2835
3060
  fields requires a protocol version bump.
2836
3061
 
2837
- ### Current verification status
3062
+ ### v0.7.69 Runtime verification record
3063
+
3064
+ This subsection is the historical verification record for the original shared
3065
+ Runtime delivery. Current 0.7.80 release gates and evidence live in
3066
+ [`docs/release.md`](release.md#v0780-release-preparation).
2838
3067
 
2839
3068
  The v0.7.69 release validation covers the runtime migration, the Worker
2840
3069
  isolation follow-ups delivered ahead of their original v0.7.71/v0.7.72
@@ -2912,7 +3141,7 @@ daemon DTO boundary. Install factories where the Runtime owner executes:
2912
3141
  | Private in-process owner | `createKodaXRuntime({ mode: 'embedded', isolation: 'inline', externalAgents })` |
2913
3142
  | New locally hosted daemon owner | `createKodaXRuntime({ mode: 'daemon', profile: '<unique>', externalAgents })` |
2914
3143
  | Existing daemon | Configure its owner, then attach with `connectKodaXRuntime({ requirements: { externalAgents: true } })`; a client cannot inject factories. |
2915
- | Runtime Worker | The Worker owner must install factories itself; passing `externalAgents` from the parent is rejected. |
3144
+ | Runtime Worker | Use `worker: { configuredA2A: true }` for the built-in configured A2A plane. Custom factories must still be installed by a custom Worker owner; passing function-valued `externalAgents` from the parent is rejected. |
2916
3145
 
2917
3146
  When `mode: 'daemon'` and `externalAgents` are supplied, the caller must win a
2918
3147
  new in-process daemon lease. KodaX rejects an already-running profile instead of
@@ -3230,7 +3459,7 @@ workflow's structured findings, verification results, and quality gates.
3230
3459
 
3231
3460
  ---
3232
3461
 
3233
- ## 21. Experimental governed memory — `/experimental-memory` (FEATURE_260 + FEATURE_275, v0.7.68–v0.7.77)
3462
+ ## 21. Experimental governed memory — `/experimental-memory` (FEATURE_260 + FEATURE_275, v0.7.68–v0.7.77)
3234
3463
 
3235
3464
  KodaX has one durable memory plane: the F228 Memory Control Plane. FEATURE_260
3236
3465
  adds a thin, opt-in agent/session API over that plane; it does not add a second
@@ -3291,53 +3520,53 @@ await session.complete({
3291
3520
  await session.close();
3292
3521
  ```
3293
3522
 
3294
- `recall()` is synchronous and exact. `query()` is deliberate and read-only; one
3295
- distinct query is admitted per decision epoch, and the result is bounded to at
3296
- most three prompt-safe hints and 512 estimated tokens. `undefined` means there
3297
- is no governed reminder to inject.
3298
-
3299
- ### Sparse foreground intervention (FEATURE_275, v0.7.77)
3300
-
3301
- `MemorySession.intervene()` replaces the old timing-ineffective semantic
3302
- prefetch. It is awaited only after `tool_failure`, `verification_failure`, or a
3303
- durably committed `context_compacted` event, then supplies at most three
3304
- prompt-safe, low-authority evidence items to the next Action-LLM request. The
3305
- candidate set is rebuilt from current objective/open todos, recent governed
3306
- observations, and a fresh F228 pack. Exact selection is deterministic; stale,
3307
- unknown, malformed, timed-out, cancelled, or failed semantic results are
3308
- discarded without blocking the coding run.
3309
-
3310
- Top-level coding runs wire the deterministic path automatically and make zero
3311
- selector calls by default. An inline host that deliberately wants semantic
3312
- selection can provide the coding-owned forced-tool runner:
3313
-
3314
- ```ts
3315
- import {
3316
- createCodingMemoryInterventionRunner,
3317
- runKodaX,
3318
- } from '@kodax-ai/kodax/coding';
3319
- import { resolveProvider } from '@kodax-ai/kodax/llm';
3320
-
3321
- const memoryRecallRunner = createCodingMemoryInterventionRunner({
3322
- provider: resolveProvider('zhipu-coding'),
3323
- model: 'glm-5.2',
3324
- });
3325
-
3326
- await runKodaX(
3327
- {
3328
- provider: 'zhipu-coding',
3329
- model: 'glm-5.2',
3330
- memoryRecallRunner,
3331
- },
3332
- 'Finish the repository migration and verify it.',
3333
- );
3334
- ```
3335
-
3336
- `memoryRecallRunner` is a process-local function binding. Worker and daemon DTO
3337
- options reject it instead of silently dropping it; configure the binding
3338
- inside the Runtime owner or keep this run inline. The selector can return only
3339
- exact IDs from the closed offered set and is capped at three calls per memory
3340
- Session.
3523
+ `recall()` is synchronous and exact. `query()` is deliberate and read-only; one
3524
+ distinct query is admitted per decision epoch, and the result is bounded to at
3525
+ most three prompt-safe hints and 512 estimated tokens. `undefined` means there
3526
+ is no governed reminder to inject.
3527
+
3528
+ ### Sparse foreground intervention (FEATURE_275, v0.7.77)
3529
+
3530
+ `MemorySession.intervene()` replaces the old timing-ineffective semantic
3531
+ prefetch. It is awaited only after `tool_failure`, `verification_failure`, or a
3532
+ durably committed `context_compacted` event, then supplies at most three
3533
+ prompt-safe, low-authority evidence items to the next Action-LLM request. The
3534
+ candidate set is rebuilt from current objective/open todos, recent governed
3535
+ observations, and a fresh F228 pack. Exact selection is deterministic; stale,
3536
+ unknown, malformed, timed-out, cancelled, or failed semantic results are
3537
+ discarded without blocking the coding run.
3538
+
3539
+ Top-level coding runs wire the deterministic path automatically and make zero
3540
+ selector calls by default. An inline host that deliberately wants semantic
3541
+ selection can provide the coding-owned forced-tool runner:
3542
+
3543
+ ```ts
3544
+ import {
3545
+ createCodingMemoryInterventionRunner,
3546
+ runKodaX,
3547
+ } from '@kodax-ai/kodax/coding';
3548
+ import { resolveProvider } from '@kodax-ai/kodax/llm';
3549
+
3550
+ const memoryRecallRunner = createCodingMemoryInterventionRunner({
3551
+ provider: resolveProvider('zhipu-coding'),
3552
+ model: 'glm-5.2',
3553
+ });
3554
+
3555
+ await runKodaX(
3556
+ {
3557
+ provider: 'zhipu-coding',
3558
+ model: 'glm-5.2',
3559
+ memoryRecallRunner,
3560
+ },
3561
+ 'Finish the repository migration and verify it.',
3562
+ );
3563
+ ```
3564
+
3565
+ `memoryRecallRunner` is a process-local function binding. Worker and daemon DTO
3566
+ options reject it instead of silently dropping it; configure the binding
3567
+ inside the Runtime owner or keep this run inline. The selector can return only
3568
+ exact IDs from the closed offered set and is capped at three calls per memory
3569
+ Session.
3341
3570
 
3342
3571
  ### Evidence, tracing, and persistence boundaries
3343
3572
 
@@ -3347,11 +3576,11 @@ Session.
3347
3576
  material. `rewind()` removes observations after a sequence boundary.
3348
3577
  - `complete()` can emit an Outcome Digest through `persistOutcomeDigest` and run
3349
3578
  bounded episode review through `reviewEpisode`; cancellation creates neither.
3350
- - `onTrace` receives policy-versioned `MemoryDecisionReceipt` metadata that links
3351
- offered `candidateIds`, validated `selectedCandidateIds`, exposed
3352
- `injectedEvidenceRefs`, triggers, and later outcome influence. Receipts are
3353
- trace-only and contain no hidden reasoning; exposure is not proof of
3354
- causality.
3579
+ - `onTrace` receives policy-versioned `MemoryDecisionReceipt` metadata that links
3580
+ offered `candidateIds`, validated `selectedCandidateIds`, exposed
3581
+ `injectedEvidenceRefs`, triggers, and later outcome influence. Receipts are
3582
+ trace-only and contain no hidden reasoning; exposure is not proof of
3583
+ causality.
3355
3584
  - Durable memory mutation remains owned by F228's
3356
3585
  proposal/preview/fingerprint/apply flow. `/memory` is the CLI governance
3357
3586
  surface; direct file/shell writes to managed memory roots are denied.
@@ -3502,6 +3731,23 @@ kodax a2a test reviewer
3502
3731
  kodax a2a call reviewer "Review this document"
3503
3732
  ```
3504
3733
 
3734
+ Private-address access and plaintext HTTP beyond exact loopback are independent
3735
+ persisted permissions. Both default to false; private HTTP requires both:
3736
+
3737
+ ```bash
3738
+ kodax a2a add intranet http://10.20.30.40/.well-known/agent-card.json \
3739
+ --allow-private --allow-insecure-http --effect read
3740
+ ```
3741
+
3742
+ Private HTTPS requires only `--allow-private`. Public HTTP requires only
3743
+ `--allow-insecure-http`, but TLS remains the recommended deployment. The flags
3744
+ are persisted under the Agent's `network` block and are honored by Card
3745
+ discovery, interface execution, Runtime registration, and task execution.
3746
+ Private-address permission also applies to HTTPS OAuth endpoints, but OAuth
3747
+ token endpoints deliberately retain the stricter HTTPS-or-exact-loopback
3748
+ protocol rule; Agent-transport plaintext authorization does not weaken OAuth.
3749
+ `a2a test` and `a2a call` also accept the flags as one-shot overrides.
3750
+
3505
3751
  The no-code OAuth path stores only the environment-variable name for the client
3506
3752
  secret. It can be staged disabled and hot-activated later:
3507
3753
 
@@ -3524,8 +3770,9 @@ Embedded CLI Runtimes and the user-owned daemon automatically reconcile these
3524
3770
  entries as `external:<name>`. Discovery/update failure retains that entry's
3525
3771
  last-known-good registration; another entry can still update. The environment
3526
3772
  broker resolves `credentialEnv` only at call time. Automatic Runtime
3527
- registration accepts public HTTPS and exact loopback targets; explicit private
3528
- network access remains an operator action on the direct CLI/SDK path.
3773
+ registration accepts public HTTPS and exact loopback targets; private-address
3774
+ and non-loopback plaintext access require their independent persisted operator
3775
+ permissions.
3529
3776
 
3530
3777
  `enabled` is desired state in `a2a.json`, not a fabricated cross-process live
3531
3778
  flag. `a2a list` reports configured entries and that desired state. The owning
@@ -3699,16 +3946,16 @@ const server = createKodaXA2AServer({
3699
3946
  },
3700
3947
  });
3701
3948
 
3702
- // Development only: the built-in listener refuses non-loopback hosts.
3703
- const localBaseUrl = await server.listen({ hostname: '127.0.0.1', port: 0 });
3704
- ```
3705
-
3706
- The listener also refuses an explicit port blocked by WHATWG Fetch clients.
3707
- With `port: 0`, it retries ephemeral allocation rather than returning a URL
3708
- that Fetch would reject before connecting.
3709
-
3710
- Production hosts route `GET /.well-known/agent-card.json` and canonical
3711
- JSON-RPC `POST /a2a` to `server.handle(request)` behind their own TLS
3949
+ // Development only: the built-in listener refuses non-loopback hosts.
3950
+ const localBaseUrl = await server.listen({ hostname: '127.0.0.1', port: 0 });
3951
+ ```
3952
+
3953
+ The listener also refuses an explicit port blocked by WHATWG Fetch clients.
3954
+ With `port: 0`, it retries ephemeral allocation rather than returning a URL
3955
+ that Fetch would reject before connecting.
3956
+
3957
+ Production hosts route `GET /.well-known/agent-card.json` and canonical
3958
+ JSON-RPC `POST /a2a` to `server.handle(request)` behind their own TLS
3712
3959
  terminator. `POST /` remains an accepted compatibility alias. `listen()` waits for durable recovery before
3713
3960
  it resolves. A host that wires `handle()` directly may explicitly await
3714
3961
  `server.whenReady()` before it starts accepting traffic; `handle()` also waits
@@ -3792,14 +4039,14 @@ SDK does not relaunch the GUI or silently fall back to an inline Runtime.
3792
4039
  ```ts
3793
4040
  import { connectKodaXRuntime } from '@kodax-ai/kodax/runtime';
3794
4041
 
3795
- const runtime = await connectKodaXRuntime({
3796
- profile: 'coder',
3797
- autoStart: true,
3798
- // Opt in only when this product remains the visible owner of the daemon.
3799
- // If the product crashes, the daemon stops after its final client is gone
3800
- // and governed work becomes idle.
3801
- daemonOrphanExitMs: 30_000,
3802
- homeDir: coderRuntimeBaseDir, // owns <coderRuntimeBaseDir>/.kodax
4042
+ const runtime = await connectKodaXRuntime({
4043
+ profile: 'coder',
4044
+ autoStart: true,
4045
+ // Opt in only when this product remains the visible owner of the daemon.
4046
+ // If the product crashes, the daemon stops after its final client is gone
4047
+ // and governed work becomes idle.
4048
+ daemonOrphanExitMs: 30_000,
4049
+ homeDir: coderRuntimeBaseDir, // owns <coderRuntimeBaseDir>/.kodax
3803
4050
  clientInfo: {
3804
4051
  name: 'kodax-space',
3805
4052
  version: '0.1.32',
@@ -3831,10 +4078,11 @@ const runtime = await connectKodaXRuntime({
3831
4078
  sharedSessionSettings: 1,
3832
4079
  durableRecoveryQueries: 1,
3833
4080
  daemonManagement: 1,
3834
- runtimeAutoModeGuardrail: 4,
3835
- },
3836
- });
3837
- ```
4081
+ runtimeEventCoalescing: 1,
4082
+ runtimeAutoModeGuardrail: 4,
4083
+ },
4084
+ });
4085
+ ```
3838
4086
 
3839
4087
  Requirements are server facts, not authorization requests. Check
3840
4088
  `runtime.grantedScopes` before enabling controls. Missing capabilities or
@@ -3844,35 +4092,54 @@ Coder. Products that depend on same-Run delivery should require
3844
4092
  (for example, SA execution) still return `unsupported_capability`; do not
3845
4093
  silently substitute `delivery:'after_turn'` unless that is the user's intent.
3846
4094
 
3847
- The SDK requires `runtimeAutoModeGuardrail:4` automatically for ordinary
3848
- `autoStart: true`. Supplying `daemonOrphanExitMs` additionally requires the
3849
- dedicated `daemonOrphanExit:1` capability and passes the option only when
3850
- spawning a new daemon. It does not silently reinterpret an already-running
3851
- persistent daemon: the SDK uses the normal fenced capability-upgrade path and
3852
- replaces it only when preflight proves that doing so is safe. After the daemon
3853
- has observed a logical client, final-client detach arms the requested grace
3854
- period; a new client cancels it, and active/queued runs, Workflow, Agent turns,
3855
- pending permission/user input, or other governed work defer exit until
3856
- preflight is idle. Omit the option for CLI-style daemons that are intentionally
3857
- persistent.
3858
-
3859
- An embedder can inspect `KODAX_RUNTIME_SDK_CAPABILITIES.daemonOrphanExit`
3860
- before calling an auto-start API. This prevents an older SDK from spawning a
3861
- persistent daemon and only then discovering that it cannot honor the requested
3862
- lifecycle policy. The connected daemon capability remains the authoritative
3863
- check that the current host actually enabled the policy.
3864
-
3865
- When a healthy profile daemon is too old, the SDK first requires
3866
- `daemonManagement:1`, takes a revision/owner-policy fenced preflight, and
3867
- replaces it only when no active or queued run, Workflow, Agent turn, pending
3868
- permission/user input, or other logical client exists. A busy or still-older
3869
- daemon is never stopped: the connection rejects with
3870
- `RuntimeDaemonCapabilityUpgradeError`, whose `recoverable` and
3871
- `restartRequired` fields are `true` and whose optional `preflight` explains the
3872
- blockers. Attach-only connections never mutate daemon ownership and must request
3873
- the exact capabilities they depend on. Capability requirements are minimum
3874
- versions: v4 satisfies v1-v3, v3 satisfies v1/v2, v2 satisfies v1, and an older
3875
- daemon never satisfies a newer requirement.
4095
+ The SDK requires `runtimeAutoModeGuardrail:4` automatically for ordinary
4096
+ `autoStart: true`. Supplying `daemonOrphanExitMs` additionally requires the
4097
+ dedicated `daemonOrphanExit:1` capability and passes the option only when
4098
+ spawning a new daemon. It does not silently reinterpret an already-running
4099
+ persistent daemon: the SDK uses the normal fenced capability-upgrade path and
4100
+ replaces it only when preflight proves that doing so is safe. After the daemon
4101
+ has observed a logical client, final-client detach arms the requested grace
4102
+ period; a new client cancels it, and active/queued runs, Workflow, Agent turns,
4103
+ pending permission/user input, or other governed work defer exit until
4104
+ preflight is idle. Omit the option for CLI-style daemons that are intentionally
4105
+ persistent.
4106
+
4107
+ An embedder can inspect `KODAX_RUNTIME_SDK_CAPABILITIES.daemonOrphanExit`
4108
+ before calling an auto-start API. This prevents an older SDK from spawning a
4109
+ persistent daemon and only then discovering that it cannot honor the requested
4110
+ lifecycle policy. The connected daemon capability remains the authoritative
4111
+ check that the current host actually enabled the policy.
4112
+
4113
+ `KODAX_RUNTIME_SDK_CAPABILITIES.runtimeEventCoalescing` performs the same
4114
+ local-SDK preflight for bounded source-level text/reasoning coalescing. Requiring
4115
+ `runtimeEventCoalescing:1` lets auto-start replace only an idle legacy daemon;
4116
+ a busy or otherwise unsafe owner produces the normal capability-upgrade error.
4117
+ The capability changes event allocation/persistence pressure, not reconstructed
4118
+ stream content: flush boundaries remain explicit and an accumulated merge
4119
+ never exceeds 8 KiB.
4120
+
4121
+ Observation boundaries such as `events.subscribe()`, `events.replay()`, and
4122
+ Session status projection flush pending events before answering, so they can
4123
+ surface a durable-persistence failure instead of returning a stale waterline.
4124
+ A determinate append failure retains one bounded batch for a later explicit
4125
+ retry. If both append and rollback fail, commit state is unknowable: the error
4126
+ is intentionally sticky and the Runtime must be closed/recreated rather than
4127
+ guessing or replaying the batch. Latest-only progress coalescing preserves its
4128
+ required first sample plus the most recent sample; discarded intermediate
4129
+ snapshots are not lifecycle events, and surviving samples retain their latest
4130
+ emission order.
4131
+
4132
+ When a healthy profile daemon is too old, the SDK first requires
4133
+ `daemonManagement:1`, takes a revision/owner-policy fenced preflight, and
4134
+ replaces it only when no active or queued run, Workflow, Agent turn, pending
4135
+ permission/user input, or other logical client exists. A busy or still-older
4136
+ daemon is never stopped: the connection rejects with
4137
+ `RuntimeDaemonCapabilityUpgradeError`, whose `recoverable` and
4138
+ `restartRequired` fields are `true` and whose optional `preflight` explains the
4139
+ blockers. Attach-only connections never mutate daemon ownership and must request
4140
+ the exact capabilities they depend on. Capability requirements are minimum
4141
+ versions: v4 satisfies v1-v3, v3 satisfies v1/v2, v2 satisfies v1, and an older
4142
+ daemon never satisfies a newer requirement.
3876
4143
 
3877
4144
  The `coderFeatureMatrix` capability reports daemon availability for managed
3878
4145
  runs, transcript/session operations, Todo projection, managed tasks, Workflow,
@@ -3891,11 +4158,116 @@ or Host Tool leases; only its hash participates in daemon-owned bridge state.
3891
4158
  Keep all three values in Electron Main. Mutually distrusting processes running
3892
4159
  as the same OS account remain outside this release's threat model.
3893
4160
 
4161
+ ### Query authoritative Session and Run lifecycle
4162
+
4163
+ Use `runtime.runs.get(runId)` for the exact Run and
4164
+ `runtime.sessions.status(sessionId)` for the one current Session projection.
4165
+ Do not infer completion from assistant text:
4166
+
4167
+ ```ts
4168
+ const run = await runtime.runs.get(runId);
4169
+ const sessionStatus = await runtime.sessions.status(sessionId);
4170
+
4171
+ switch (run.phase) {
4172
+ case 'running':
4173
+ case 'waiting_agent':
4174
+ case 'recovering':
4175
+ case 'waiting_permission':
4176
+ case 'waiting_user_input':
4177
+ renderActive(run.phase);
4178
+ break;
4179
+ case 'unknown':
4180
+ renderUnconfirmedExecution(run.error);
4181
+ break;
4182
+ case 'completed':
4183
+ case 'cancelled':
4184
+ case 'interrupted':
4185
+ case 'failed':
4186
+ renderTerminal(run.terminal);
4187
+ break;
4188
+ }
4189
+ ```
4190
+
4191
+ `phase` answers whether the Run is queued, active, terminal, or unconfirmed.
4192
+ Use `stage` for the finer executor location: `executing`,
4193
+ `waiting_agent`, `recovering`, managed phases such as `worker` or `verifying`,
4194
+ and `finalizing`. In particular, managed status `completed` first appears as
4195
+ `phase:'running', stage:'finalizing'`; it is not terminal until the outer
4196
+ executor settles. `stageChangedAt` timestamps that transition, and
4197
+ `activeSubtaskCount` is present only when the managed executor reported an
4198
+ authoritative count. If Runtime shutdown begins after an executor terminal
4199
+ callback was latched, that terminal fact is persisted synchronously before the
4200
+ owner-liveness endpoint is released. Result-handle resolution and queue drain
4201
+ still wait for the executor result or the deferred lost-result fallback.
4202
+ Destructive Session mutations remain fenced across the same settlement window.
4203
+
4204
+ Run ownership is private persistence metadata, not a public ordering field. A
4205
+ second Runtime may observe a live owner's status but cannot abort or mutate
4206
+ that Run. A definitely dead owner is recovered to a durable `interrupted`
4207
+ result. If liveness cannot be proven either way, the observer returns
4208
+ `phase: 'unknown'`; it does not report a successful stop and does not overwrite
4209
+ the original non-terminal record. The same rule applies to ownerless legacy
4210
+ non-terminal records. A durable terminal event is reconciled before this
4211
+ fallback; non-terminal evidence such as an input-delivery event may refine the
4212
+ read projection but does not prove that execution stopped. Persisted terminal
4213
+ states are monotonic.
4214
+
4215
+ `runtime.status.preflight()` treats `queued`, all active/waiting/recovery
4216
+ phases, and `unknown` as stop blockers. A host must never infer completion from
4217
+ reply text or convert `unknown` into success.
4218
+
4219
+ `runtime.runs.abort(runId)` requests Stop; it does not manufacture an
4220
+ acknowledgement. Queued work can become `cancelled` immediately. For an active
4221
+ executor, inspect `run.stop`: while termination is unconfirmed, the Run reports
4222
+ `phase/stage:'unknown'` and `stop.state/outcome:'unknown'`. A later executor
4223
+ result confirms the actual outcome, including `completed` when the executor
4224
+ ignored Stop and completed normally. Terminal callback order is latched before
4225
+ deferred result settlement: Stop cannot rewrite an earlier completion as
4226
+ interrupted, and late finalizer/recovery progress cannot revive an unconfirmed
4227
+ stopped Run. If the Runtime owner then dies, recovery resolves the pending Stop
4228
+ as `confirmed/interrupted`.
4229
+
4230
+ For support bundles, use the pure read-only compositor:
4231
+
4232
+ ```ts
4233
+ import { captureRuntimeSessionDiagnostics } from '@kodax-ai/kodax/runtime';
4234
+
4235
+ const diagnostic = await captureRuntimeSessionDiagnostics(runtime, {
4236
+ sessionId,
4237
+ runId,
4238
+ timeoutMs: 10_000,
4239
+ signal: abortController.signal,
4240
+ });
4241
+ ```
4242
+
4243
+ The schema-versioned result includes SDK/Runtime/daemon versions, Runtime and
4244
+ Session identity, observation cursor/transcript revision, Run/Turn identity,
4245
+ phase/stage and stage time, terminal fact/time, Run-owned active child count,
4246
+ Stop and chat-interrupt records, and structured errors. If the selected Run
4247
+ did not persist an authoritative child count, the result returns
4248
+ `activeSubtaskCount:null` and `activeSubtaskCountSource:'unknown'`; it never
4249
+ attributes a later Session-wide child sample to that Run. If a historical
4250
+ Session has no Run control record, it returns `controlRecord:'unknown'` with
4251
+ `run_control_unknown`; it never infers completion from transcript text.
4252
+ `owner_liveness_unconfirmed`, `owner_recovery_required`,
4253
+ `stop_outcome_unconfirmed`, `run_failed`, `run_status_unknown`, and
4254
+ `terminal_time_unknown` remain distinct. These are independent facts rather
4255
+ than an enum: for example, an unavailable owner and an unconfirmed Stop appear
4256
+ together in `errors`. `sdkVersion` is the calling SDK package version;
4257
+ `runtimeVersion` and `daemonVersion` identify the connected daemon in daemon
4258
+ mode, so a support bundle preserves version skew. One timeout/cancellation
4259
+ budget covers the transcript, settings, pending interactions, and owner
4260
+ liveness inspection. The helper uses the dedicated
4261
+ `sessions.diagnostics()` read boundary: it never runs status preflight,
4262
+ resumes, repairs, migrates, takes ownership, or evicts a retained transcript
4263
+ page boundary. Embedded and daemon facades use the same schema-validated
4264
+ `session.diagnostics` contract.
4265
+
3894
4266
  ### Join atomically and resync after disconnect
3895
4267
 
3896
4268
  `sessions.observe()` installs the live subscription before taking the
3897
4269
  snapshot. Its snapshot contains one authoritative `runtimeId`, cursor,
3898
- `transcriptRevision`, complete transcript, versioned settings, run/queue state,
4270
+ `transcriptRevision`, bounded transcript slice, versioned settings, run/queue state,
3899
4271
  queued continuation IDs/order/origin/safe previews, pending permission and
3900
4272
  AskUser requests, and live assistant/thinking/tool/Todo/managed-task
3901
4273
  projection. Run requirements include the current credential/Host Tool
@@ -3918,6 +4290,10 @@ async function openCoderSession(sessionId: string) {
3918
4290
  observedRuntimeId = snapshot.runtimeId;
3919
4291
  lastCursor = snapshot.cursor;
3920
4292
  replaceSessionProjection(snapshot, { runtimeChanged });
4293
+ void observation.invalidated.then((reason) => {
4294
+ discardSessionProjection(reason);
4295
+ scheduleFreshObservation(sessionId);
4296
+ });
3921
4297
  return observation;
3922
4298
  }
3923
4299
  ```
@@ -3944,9 +4320,14 @@ On transport failure, Runtime change, expired history, or `resync_required`,
3944
4320
  discard the local derived projection and call `sessions.observe()` again. Do
3945
4321
  not merge a new snapshot into the old projection. The handshake buffer is
3946
4322
  bounded; overflow fails explicitly instead of dropping events. A Runtime
3947
- restart changes `runtimeId`, marks persisted non-terminal runs with a durable
3948
- terminal fact, and closes old in-memory AskUser/permission requests through the
3949
- reset boundary.
4323
+ restart changes `runtimeId`. The observation's `invalidated` promise reports
4324
+ `event_overflow`, `event_order`, `runtime_changed`, or
4325
+ `transport_disconnected`; after it resolves, no state derived from that
4326
+ observation remains authoritative. Restart recovery interrupts only a Run
4327
+ whose owner is definitely gone; uncertain external execution is `unknown`.
4328
+ Timeout or cancellation removes the daemon request immediately. If a
4329
+ third-party transport ignores cancellation and later returns an observation,
4330
+ the client compensates by unsubscribing that late observation.
3950
4331
 
3951
4332
  ### Durable mutations, stable ordering, and settings CAS
3952
4333
 
@@ -4022,17 +4403,17 @@ complete ordered batch. Exact operation retries return the same `inputId`.
4022
4403
  The accepted result's `runId` is the existing owning Run (equal to
4023
4404
  `afterRunId`), not a newly created continuation.
4024
4405
 
4025
- Interrupt admission closes when the Runner publishes its final completion or
4026
- terminal error signal, or when the Run's supplied `abortSignal` aborts, even if
4027
- the outer Run is still settling. Non-terminal observer diagnostics do not close
4028
- the window. Same-Run lifecycle continuations also have a fixed internal
4029
- allowance beyond the configured iteration ceiling; the Runner closes admission
4030
- before its final absolute generation so repeated submissions cannot keep one
4031
- Run alive indefinitely. The allowance never expands an admitted manifest's
4032
- `maxIterations` governance cap. A submission after closure returns
4033
- `accepted:false` with `reason:'interrupt_window_closed'` and is not queued. Keep
4034
- the original input available for retry after the Run ends; do not silently
4035
- change its delivery to `after_turn`. As a final race/recovery
4406
+ Interrupt admission closes when the Runner publishes its final completion or
4407
+ terminal error signal, or when the Run's supplied `abortSignal` aborts, even if
4408
+ the outer Run is still settling. Non-terminal observer diagnostics do not close
4409
+ the window. Same-Run lifecycle continuations also have a fixed internal
4410
+ allowance beyond the configured iteration ceiling; the Runner closes admission
4411
+ before its final absolute generation so repeated submissions cannot keep one
4412
+ Run alive indefinitely. The allowance never expands an admitted manifest's
4413
+ `maxIterations` governance cap. A submission after closure returns
4414
+ `accepted:false` with `reason:'interrupt_window_closed'` and is not queued. Keep
4415
+ the original input available for retry after the Run ends; do not silently
4416
+ change its delivery to `after_turn`. As a final race/recovery
4036
4417
  guard, inspect terminal Run status: any `interruptInputs` entry whose state is
4037
4418
  `terminal` was not delivered. Reconcile it by `inputId` and present a visible
4038
4419
  non-delivery outcome rather than leaving a pending queue indicator.
@@ -4059,6 +4440,16 @@ query `runtime.operations.get({ operationId, journalEpoch })`; applied receipts
4059
4440
  include the canonical result. Permission grants remain daemon-owned and
4060
4441
  revisioned.
4061
4442
 
4443
+ Runtime startup restores all indexed active Runs and at most 200 recent
4444
+ terminal Runs from a bounded durable status index. `runs.get(runId)` remains an
4445
+ exact persisted lookup even when an older terminal Run is outside that recent
4446
+ window. The first start after upgrading an unindexed home may perform one full
4447
+ compatibility scan. A start after a crash, or a concurrent Runtime starting
4448
+ while a live writer has intentionally left the index dirty, may likewise do
4449
+ one authoritative reconciliation. Normal writers publish canonical
4450
+ `status.json` first and keep the derived index recovery-fenced across those
4451
+ crashes and concurrent Runtime instances.
4452
+
4062
4453
  ### AskUser and permission from any client
4063
4454
 
4064
4455
  AskUser is no longer an in-process callback for daemon Coder runs. Any client
@@ -4102,11 +4493,11 @@ Write/Edit content may differ). Generic extension calls can receive only an
4102
4493
  exact in-memory Session grant. Raw command/argv data is not stored in the
4103
4494
  matcher; grants and audit contain only its fingerprint plus a bounded,
4104
4495
  secret-redacted operator label. Clients must not keep separate persistent
4105
- permission rule stores. Runtime capability `runtimeAutoModeGuardrail` v4
4106
- advertises this opaque concrete-grant contract plus the intent-aligned
4107
- retry/Accept-edits behavior that never changes the engine to rules. Embedded,
4108
- Worker, and daemon hosts all report `fallbackPersistsEngine:false`; restart or
4109
- upgrade an older daemon instead of falling back to a client-side alias.
4496
+ permission rule stores. Runtime capability `runtimeAutoModeGuardrail` v4
4497
+ advertises this opaque concrete-grant contract plus the intent-aligned
4498
+ retry/Accept-edits behavior that never changes the engine to rules. Embedded,
4499
+ Worker, and daemon hosts all report `fallbackPersistsEngine:false`; restart or
4500
+ upgrade an older daemon instead of falling back to a client-side alias.
4110
4501
 
4111
4502
  ### Broker a Space keychain credential
4112
4503
 
@@ -4330,7 +4721,7 @@ await runtime.sessions.updateSettings(session.id, {
4330
4721
  permissionMode: 'auto',
4331
4722
  autoModeEngine: 'llm',
4332
4723
  autoModeClassifierModel: 'zhipu:glm-5.2', // optional; otherwise follow the run model
4333
- autoModeTimeoutMs: 20_000, // positive safe integer, optional
4724
+ autoModeTimeoutMs: 30_000, // positive safe integer, optional
4334
4725
  autoModeSpeculativeWindowMs: 0, // non-negative safe integer, optional
4335
4726
  executionCwd: projectDirectory,
4336
4727
  });
@@ -4385,9 +4776,9 @@ engine/denial/breaker state per Session. It reuses bounded context-specific
4385
4776
  guardrails across turns while provider/model, repository boundary, execution
4386
4777
  directory, classifier model, and timeout remain the same. Updating one of
4387
4778
  those inputs selects a new context guardrail by design without copying stale
4388
- state from a queued turn. Active runs, queued runs, explicit settings updates,
4389
- and explicit engine changes merge through the same Session mutation queue.
4390
- Classifier infrastructure failures do not mutate the engine to rules.
4779
+ state from a queued turn. Active runs, queued runs, explicit settings updates,
4780
+ and explicit engine changes merge through the same Session mutation queue.
4781
+ Classifier infrastructure failures do not mutate the engine to rules.
4391
4782
 
4392
4783
  ### What an embedder should expect
4393
4784
 
@@ -4401,35 +4792,129 @@ Exactly modeled ordinary reads and workspace/system-temp mutations are admitted
4401
4792
  before classifier latency, independent of sandbox readiness. Other calls are
4402
4793
  reviewed against the latest genuine user request, bounded user-only intent
4403
4794
  evidence, and exact operation facts. Consequently, an LLM/rules `allow` does
4404
- not create a pending permission request just because a host installed a static
4405
- approval hook. Classifier concerns, critical deterministic matches, and rules
4406
- concerns use the existing shared
4407
- `runtime.permissions` flow, so another authorized client may render and answer
4795
+ not create a pending permission request just because a host installed a static
4796
+ approval hook. Classifier concerns, critical deterministic matches, and rules
4797
+ concerns use the existing shared
4798
+ `runtime.permissions` flow, so another authorized client may render and answer
4408
4799
  it. Hosts should subscribe to permission events to display such a request, but
4409
4800
  must not treat a missing request as an error for a safe tool call.
4410
4801
 
4411
- The classifier deadline remains 20 seconds by default and includes connection
4412
- setup, provider Retry-After/backoff, inference, and stream completion. KodaX
4413
- retries one timeout/provider/response-contract failure once; a second failure
4414
- uses the Accept-edits safety boundary and never switches to Auto[rules].
4415
- KodaX does not solve timeouts by extending that deadline indefinitely. Before the
4416
- provider call it removes assistant prose/thinking and image paths, limits each
4417
- tool result to 2 KiB and the serialized permission-relevant transcript to
4418
- 8 KiB, then enforces 16 KiB action and 32 KiB total-prompt ceilings plus a
4419
- 256-token output cap. An oversized action or prompt escalates without a
4420
- provider call; it is never truncated into an automatic allow. These limits are
4421
- owned by `classify()` itself, so custom callers cannot accidentally bypass the
4422
- session-history boundary.
4802
+ Direct guardrail embedders should supply `projectRoot` or `executionCwd` for
4803
+ path-bearing calls. If both are absent, KodaX keeps those targets unresolved
4804
+ instead of treating the host process cwd as the user's workspace.
4805
+
4806
+ On PowerShell, deterministic read admission includes independently validated
4807
+ sequential/pipeline stages such as `where.exe`, ordinary `rg` inspection,
4808
+ non-sensitive `$env:NAME` reads, and constrained `Where-Object` /
4809
+ `Select-Object` expressions. This is a structural allowlist, not a blanket
4810
+ "review task" exemption. The executable token must be an admitted bare command
4811
+ name; path-qualified executables, arbitrary `& script.cmd`, effectful `find` /
4812
+ `awk` / `sed` forms, script blocks, sensitive credential environment names,
4813
+ external ripgrep preprocessors, and file redirection continue through the LLM
4814
+ classifier. They reach user approval only when the classifier identifies a
4815
+ concrete hazard (or when its bounded retry and fallback policy requires it).
4816
+ Authenticated child constraints are checked before deterministic admission:
4817
+ for example, `Do not execute shell commands` keeps even a read-only shell call
4818
+ under review, while `Do not modify files` does not make an ordinary read ask.
4819
+
4820
+ The classifier deadline is 45 seconds for the first default attempt and 90
4821
+ seconds for its one retry. A configured `timeoutMs` overrides both attempts
4822
+ with the same explicit deadline. Each deadline includes connection setup,
4823
+ provider Retry-After/backoff, inference, and stream completion. A second
4824
+ timeout/provider/response-contract failure uses the Accept-edits safety
4825
+ boundary and never switches to Auto[rules]. Before the
4826
+ provider call it removes assistant prose/thinking and image paths, limits each
4827
+ tool result to 2 KiB and the serialized permission-relevant transcript to
4828
+ 8 KiB, then enforces 16 KiB action and 32 KiB total-prompt ceilings plus a
4829
+ 256-token first-attempt output cap. If that answer is truncated before a
4830
+ decision, the retry uses 1024 tokens instead of repeating the same impossible
4831
+ budget. An oversized action or prompt escalates without a provider call; it is
4832
+ never truncated into an automatic allow. These limits are owned by
4833
+ `classify()` itself, so custom callers cannot accidentally bypass the
4834
+ session-history boundary.
4835
+
4836
+ Deterministic read admission keeps ordinary project directories, structured
4837
+ `grep`/`glob`, ripgrep/GNU-grep filters, and other bounded search selectors on
4838
+ the read fast path. A wildcard remains unresolved only when it can reach a
4839
+ protected credential area or its path boundary cannot be established.
4840
+ Explicit PowerShell `-LiteralPath` remains literal. Indirect file lists,
4841
+ dynamic Git pathspecs, and expanded `git grep --no-index`/`--untracked` scopes
4842
+ remain LLM-reviewed when their targets cannot be bounded safely. Git
4843
+ line-log `-L` paths, patch-enabling short-option clusters, and merge-diff modes
4844
+ are resolved before deciding whether output is scoped. Exact-file reads and
4845
+ metadata-only output such as `git show --stat` remain deterministic. Exclusion
4846
+ selectors do not become input targets. On Windows, protected-name checks also
4847
+ cover trailing-dot/space aliases, alternate data streams, and canonicalized
4848
+ existing 8.3 names. Git-config
4849
+ regexp/URL reads account for accepted option abbreviations and effective
4850
+ boolean-option ordering; only complete anchored selectors over known
4851
+ non-secret metadata can take the deterministic fast path. Likewise, incomplete
4852
+ current-user content may still admit a proven read, but a complete current
4853
+ request that explicitly prohibits reading or shell execution routes the
4854
+ matching read-only operation through the classifier. Ordinary review and
4855
+ read-only requests remain deterministic. `currentUserContentTruncated:true` prevents a
4856
+ write, delete, move, copy, or unmodeled execution from relying on the compacted
4857
+ fragment alone. On classifier failure, Accept-edits fallback is available only
4858
+ when the full structured review would pass the same deterministic predicate;
4859
+ protected/unresolved targets and partial/risky reviews remain approval-bound.
4423
4860
 
4424
4861
  `ClassifyDecision.diagnostics` and the lower-level
4425
4862
  `SideQueryResult.diagnostics` expose provider, model, effective timeout,
4426
- elapsed time, retry count/wait, and a coarse terminal phase without including
4427
- the prompt, action, messages, or response text. `pre_output` means no non-empty
4428
- text delta was observed; `streaming` means output began before termination.
4429
- `firstOutputMs` and `streamMs` are present only when the provider adapter emits
4430
- a text delta. The current provider API cannot honestly separate DNS/connect,
4431
- TLS, provider queueing, and inference, so embedders must not infer those stages
4432
- from `pre_output`.
4863
+ elapsed time, retry count/wait, provider stop reason, response byte/text-block
4864
+ counts, and a coarse terminal phase without including the prompt, action,
4865
+ messages, or response text. `pre_output` means no non-empty text delta was
4866
+ observed; `streaming` means output began before termination. `firstOutputMs`
4867
+ and `streamMs` are present only when the provider adapter emits a text delta.
4868
+ Each classifier attempt may additionally expose `observedProtocol`
4869
+ (`structured_v2`, `legacy_v1`, or `unknown`) and a bounded
4870
+ `parseFailureCode` when no valid unambiguous decision exists. When exactly one
4871
+ valid `decision=allow|ask` (or legacy `block=no|yes`) is present, that decision
4872
+ is authoritative. Missing, malformed, or contradictory `hazard` / `reason`
4873
+ fields and surrounding-format defects are reported as bounded
4874
+ `outputWarnings`; they do not change the decision, consume the retry, increment
4875
+ the circuit breaker, or create an approval request after `allow`. Runtime
4876
+ permission requests copy these fields into `autoModeDiagnostics`, allowing a
4877
+ host to distinguish an LLM confirmation decision (`source:
4878
+ classifier_confirm`) from provider/decision-contract failure (`source:
4879
+ classifier_failure`) without retaining model output. Missing or invalid
4880
+ decision values and ambiguous duplicate/mixed decisions remain contract
4881
+ failures and receive the same single bounded retry. The current
4882
+ provider API cannot honestly separate DNS/connect, TLS, provider queueing, and
4883
+ inference, so embedders must not infer those stages from `pre_output`.
4884
+ Because raw classifier text is deliberately not retained, these diagnostics
4885
+ cannot retroactively prove the shape of an older response. A future
4886
+ `observedProtocol:legacy_v1` value is evidence for that attempt; compatibility
4887
+ with `legacy_v1` alone is not proof that a prior provider returned it.
4888
+ `outputWarnings` explain the accepted attempt; they are not a second verdict.
4889
+ Under the LLM engine, a broken/non-string tool projection and an unavailable or
4890
+ faulty direct-read analyzer are recoverable metadata faults: KodaX records a
4891
+ warning, builds a bounded credential-redacted fallback projection, and still
4892
+ asks the classifier. They do not directly create a user approval request.
4893
+ Extension/provider exception bodies are omitted entirely from Auto-mode logs
4894
+ and approval reasons; only the stable failure stage and exception category are
4895
+ retained. All Auto-mode host warning messages are additionally
4896
+ credential-redacted, normalized to one line, and capped at 768 characters.
4897
+ Logging is best-effort observation: an embedder logger that throws cannot alter
4898
+ the permission decision or interrupt the documented fallback.
4899
+ For source compatibility, the public `ClassifierDecision` allow branch keeps
4900
+ `hazard?: 'none'`. If a model returns `decision=allow` with another hazard, the
4901
+ allow remains authoritative, `decision_hazard_conflict` appears in
4902
+ `outputWarnings`, and the contradictory hazard value is not placed in that
4903
+ legacy field.
4904
+ Auto[LLM] is allow-by-default automatic review. An operational classifier is
4905
+ instructed to return `ask` only for one of two evidence-based classes: a
4906
+ concrete read from a known key/token/credential store or a mutation to KodaX
4907
+ authorization controls; or direct system destruction/resource exhaustion that
4908
+ can destabilize the OS or unrelated software. Ordinary project mutations, Git
4909
+ stash and other Git writes, and normal global dependency install/uninstall/
4910
+ reinstall do not require per-command root authorization. Syntax complexity,
4911
+ incomplete analysis, general uncertainty, and command category are not ask
4912
+ reasons. Static signals, including historical catastrophic-pattern matches,
4913
+ are classifier facts rather than a second Auto[LLM] verdict; explicit
4914
+ Auto[Rules] retains its legacy deterministic gate. A host must honor a valid
4915
+ classifier `allow` without manufacturing another approval. Infrastructure or
4916
+ decision-contract failure still uses the bounded retry and documented
4917
+ Accept-edits fallback; only exhaustion beyond that fallback reaches approval.
4433
4918
 
4434
4919
  The permission event's `inputPreview` is a display-safe diagnostic projection:
4435
4920
  it is bounded, credential-redacted, valid JSON, and includes the effective
@@ -4439,21 +4924,21 @@ session repository safety boundary, whereas relative operands resolve from the
4439
4924
  validated `executionCwd`. In particular, quoted Python/JavaScript/regexp source
4440
4925
  inside a shell command is not a path operand.
4441
4926
 
4442
- Every Runtime permission request has a deadline. If the host does not answer,
4443
- the current operation is not executed and the guardrail returns a stable
4444
- `approval_timeout` result telling the main model to try a safer, narrower, or
4445
- reversible approach, or stop and wait for explicit user approval. A timeout is
4446
- not serialized as an ordinary user rejection.
4447
-
4448
- The user-level `.kodax` directory is a credential/configuration boundary, not
4449
- an ordinary project path. Direct shell mutations, output redirects, and
4450
- recognized nested-shell payloads whose target is provably beneath that
4451
- directory are identified before LLM classification and routed to permission
4452
- approval rather than permanently policy-blocked. The check is segment-safe and
4453
- Windows case-insensitive. KodaX deliberately does not scan arbitrary
4454
- quoted language source for path-looking substrings: doing so would turn Python,
4455
- JavaScript, YAML, and regular expressions into false Tier-0 matches. Trusted
4456
- configuration changes should use the KodaX config CLI or SDK configuration API.
4927
+ Every Runtime permission request has a deadline. If the host does not answer,
4928
+ the current operation is not executed and the guardrail returns a stable
4929
+ `approval_timeout` result telling the main model to try a safer, narrower, or
4930
+ reversible approach, or stop and wait for explicit user approval. A timeout is
4931
+ not serialized as an ordinary user rejection.
4932
+
4933
+ The user-level `.kodax` directory is a credential/configuration boundary, not
4934
+ an ordinary project path. Direct shell mutations, output redirects, and
4935
+ recognized nested-shell payloads whose target is provably beneath that
4936
+ directory are identified before LLM classification and supplied as precise
4937
+ facts rather than permanently policy-blocked. The check is segment-safe and
4938
+ Windows case-insensitive. KodaX deliberately does not scan arbitrary
4939
+ quoted language source for path-looking substrings: doing so would turn Python,
4940
+ JavaScript, YAML, and regular expressions into false Tier-0 matches. Trusted
4941
+ configuration changes should use the KodaX config CLI or SDK configuration API.
4457
4942
 
4458
4943
  ### 0.7.x source compatibility
4459
4944
 
@@ -4613,8 +5098,12 @@ while (page) {
4613
5098
 
4614
5099
  `transcriptEntryChunk()` returns lossless `base64-json` chunks. Concatenate the
4615
5100
  decoded bytes and parse JSON only after `hasMore` becomes false. Page and entry
4616
- cursors are opaque and revision-bound; a changed transcript produces an
4617
- explicit resync error, so restart from a fresh observation. The shared daemon's
5101
+ cursors are opaque and revision-bound. Runtime retains a bounded immutable
5102
+ snapshot for an in-progress cursor, so new appends do not create duplicates,
5103
+ gaps, or an endless moving boundary. Once that snapshot is no longer retained,
5104
+ the next read returns `resync_required`; restart from a fresh observation.
5105
+ All transcript/history methods accept `{ timeoutMs, signal }` as their final
5106
+ read options in embedded and daemon mode. The shared daemon's
4618
5107
  legacy `session.transcript` method rejects payloads above 512 KiB and names the
4619
5108
  page/chunk methods rather than attempting a frame near the 8 MiB ceiling.
4620
5109
 
@@ -4737,394 +5226,455 @@ boundary, but they do not replace product-level validation in the packaged host.
4737
5226
  KodaX Space should install the exact v0.7.75 tarball and complete
4738
5227
  [`ISSUE_205_v0.7.75_REGRESSION_GUIDE.md`](test-guides/ISSUE_205_v0.7.75_REGRESSION_GUIDE.md)
4739
5228
  on Windows 10 and Windows 11 as a non-blocking product validation follow-up.
4740
- This follow-up does not gate SDK packaging, tagging, or publication.
4741
-
4742
- ---
4743
-
4744
- ## 28. Host-configurable Shell Execution Contract (v0.7.77)
4745
-
4746
- The shared daemon is long-lived, so its startup `process.env.PATH` is not a
4747
- reliable description of every project's toolchain. A host can opt a Session
4748
- into a serializable shell contract:
4749
-
4750
- ```ts
4751
- await runtime.sessions.updateSettings(session.id, {
4752
- executionCwd: projectDirectory,
4753
- shellExecution: {
4754
- version: 1,
4755
- shell: {
4756
- kind: 'pwsh', // pwsh | powershell | cmd | bash | zsh
4757
- profile: 'default',
4758
- },
4759
- environment: {
4760
- inherit: 'filtered',
4761
- // Trusted host code, not model input. Use it for a directory-aware
4762
- // activation command when the shell profile does not switch on cwd.
4763
- setup: 'fnm use --silent-if-unchanged',
4764
- windowsPath: 'registry',
4765
- },
4766
- cache: {
4767
- ttlMs: 30_000,
4768
- refreshToken: 'toolchain-revision-1',
4769
- },
4770
- probeTimeoutMs: 10_000,
4771
- },
4772
- });
4773
- ```
4774
-
4775
- An individual Run can supply `options.context.shellExecution`; a concrete Run
4776
- contract overrides the Session setting. An omitted value or explicit
4777
- `undefined` does not erase the Session setting. Use a `null` Session patch to
4778
- remove it.
4779
-
4780
- The contract supports an optional absolute `shell.executable` (for example Git
4781
- Bash) and bounded fixed `shell.args`. Command/file/persistence/profile/server
4782
- and working-directory control flags are rejected from those fixed arguments.
4783
- `environment.set` is for non-secret host variables. `denyPatterns` can remove
4784
- additional names but cannot weaken the built-in Provider credential deny set.
4785
- `inherit: "none"` retains only the OS variables required to start the selected
4786
- shell. On Windows, `windowsPath: "registry"` re-reads current Machine/User
4787
- environment values instead of reusing the daemon's startup PATH.
4788
-
4789
- Resolution is two-stage and uses the effective cwd:
4790
-
4791
- 1. sanitize the bootstrap environment, including credentials for built-in,
4792
- custom, active, inactive, and stacked runtime Providers;
4793
- 2. start the selected shell, load the requested profile/setup, capture a
4794
- random-framed environment, validate and sanitize it again;
4795
- 3. execute the actual command through that same explicit interpreter.
4796
-
4797
- The cache is in-memory and isolated by normalized contract, canonical cwd,
4798
- Session scratch identity, credential deny names, and refresh generation. TTL
4799
- is bounded to ten minutes; zero disables caching. Daemon restart clears it.
4800
- `clearShellExecutionEnvironmentCache()` is available for an in-process owner
4801
- that needs immediate global invalidation.
4802
-
4803
- Native children, nested Actor turns, Workflow child paths, and deterministic
4804
- build/test/lint evaluators inherit the effective contract. Runtime exact-command
4805
- permission grants bind the interpreter family and contract fingerprint, so a
4806
- grant created under cmd cannot silently authorize the same command after a
4807
- switch to PowerShell or Bash.
4808
-
4809
- Configured-shell failures are visible and fail closed: KodaX does not reinterpret
4810
- the command through another shell. When `shellExecution` is absent, KodaX keeps
4811
- the pre-v0.7.77 platform-shell behavior for compatibility. See
4812
- [`ISSUE_214_v0.7.77_REGRESSION_GUIDE.md`](test-guides/ISSUE_214_v0.7.77_REGRESSION_GUIDE.md)
4813
- for cross-project, cache, cancellation, credential, and Windows argv checks.
4814
-
4815
- ---
4816
-
4817
- ## 29. Evidence-gated background Skill learning (FEATURE_263, v0.7.78)
4818
-
4819
- F263 completes the existing Learning Center rather than introducing a second
4820
- queue or client-owned Skill store. Episode review runs after durable foreground
4821
- completion and stays off the active Run's latency path. A correction, failure,
4822
- or verifier result is Memory evidence first; it does not itself authorize a
4823
- Skill mutation.
4824
-
4825
- A low-risk declarative Skill can enter automatic project-scoped testing only
4826
- after an explicit preserve-as-Skill request with verified terminal evidence,
4827
- or repeated independent root episodes plus independent verified artifact
4828
- evidence. The owner writes an immutable revision and its canonical capability
4829
- record before discovery can expose it. Formal/builtin/plugin/human Skills keep
4830
- precedence and cannot be shadowed. Protected/formal changes, user-global
4831
- promotion, and Extension authoring remain explicit user actions.
4832
-
4833
- Hosts that require this behavior should negotiate both Runtime capabilities:
4834
-
4835
- ```ts
4836
- const runtime = await createKodaXRuntime({
4837
- mode: 'daemon',
4838
- requirements: {
4839
- learningCenter: 1,
4840
- skillLearningLoop: 1,
4841
- },
4842
- clientInfo: {
4843
- name: 'my-host',
4844
- instanceId: stableClientId,
4845
- instanceSecret: keychainSecret,
4846
- },
4847
- });
4848
-
4849
- const snapshot = await runtime.learning.getSnapshot();
4850
- const page = await runtime.learning.list({ limit: 50 });
4851
-
4852
- for await (const event of runtime.learning.subscribe({
4853
- afterRevision: snapshot.revision,
4854
- })) {
4855
- renderLearningEvent(event);
4856
- }
4857
- ```
4858
-
4859
- `runtime.learning` is the authoritative host surface:
4860
-
4861
- | Need | API |
4862
- |---|---|
4863
- | Render inventory or one exact record | `list()` / `get()` |
4864
- | Render client-specific badges | `getSnapshot()` |
4865
- | Replay or follow durable lifecycle events | `events()` / `subscribe()` |
4866
- | Clear or defer only this client's notice | `acknowledge()` / `snooze()` |
4867
- | Explicitly control a learned revision | `reject()` / `disable()` / `rollback()` / `review()` / `trust()` |
4868
- | Promote to the user scope | `promote(nameOrSlugOrId, 'user')` |
4869
-
4870
- Do not scan learned files and infer activation from their presence. Discovery
4871
- requires the canonical record, matching project identity, lifecycle,
4872
- fingerprint, regular-file checks, formal-name policy, and exact revision.
4873
- Testing admission permits one concurrent root binding and at most three
4874
- exact-revision invocations. Promotion requires independently verified success;
4875
- failed or inconclusive canaries return to Ready/attention. A Run retains the
4876
- revision it captured at admission, so rollback or replacement affects future
4877
- bindings without mutating an in-flight prompt.
4878
-
4879
- Learning Center notification state is client-specific, but capability
4880
- lifecycle and project canary state are owner-global. Renderer code should
4881
- receive sanitized records/events through host IPC; it should not receive
4882
- daemon credentials or mutate files directly. Inline, Worker, and daemon
4883
- facades expose the same learning methods. A host missing `skillLearningLoop:1`
4884
- may still support the older Ready/manual Learning Center surface, but must not
4885
- claim the complete F263 project-canary contract.
4886
-
4887
- ---
4888
-
4889
- ## 30. Standalone sandbox SDK (v0.7.78)
4890
-
4891
- ASRT containment is a public SDK capability, not an Auto[LLM]-only
4892
- implementation detail. Import the dedicated subpath when a host needs to
4893
- sandbox its own commands or scripts:
4894
-
4895
- ```ts
4896
- import {
4897
- activateKodaXSandbox,
4898
- doctorKodaXSandbox,
4899
- getKodaXSandboxCapability,
4900
- getKodaXSandboxSetupGuidance,
4901
- runKodaXSandboxed,
4902
- } from '@kodax-ai/kodax/sandbox';
4903
-
4904
- const capability = getKodaXSandboxCapability();
4905
- const doctor = await doctorKodaXSandbox({ refresh: true });
4906
-
4907
- if (!doctor.ready) {
4908
- showSandboxInfo(getKodaXSandboxSetupGuidance(doctor));
4909
- // Call only from an explicit setup/onboarding action. On Windows this may
4910
- // display UAC; ordinary SDK calls never invoke it automatically.
4911
- const activation = await activateKodaXSandbox();
4912
- if (activation.status !== 'ready') {
4913
- showSandboxInfo(activation.guidance);
4914
- }
4915
- }
4916
- ```
4917
-
4918
- Platform behavior:
4919
-
4920
- - Windows uses the pinned ASRT restricted-user/WFP setup. The parent terminal
4921
- does not need to be elevated; the one-time installer requests UAC itself.
4922
- - macOS uses Seatbelt through `sandbox-exec` and requires ripgrep. Guide users
4923
- to `brew install ripgrep` when doctor reports it missing.
4924
- - Linux uses bubblewrap and requires `bubblewrap`, `socat`, and `ripgrep`.
4925
- Present the emitted `apt`/`dnf`/`pacman` guidance; do not run `sudo` or a
4926
- package manager silently.
4927
-
4928
- Do not call `activateKodaXSandbox()` during ordinary Runtime startup, tool
4929
- execution, or a background permission check. KodaX's own first-run/setup UI
4930
- checks once; a declined UAC prompt or missing dependency is reported there and
4931
- is not repeatedly surfaced until the user runs setup again.
4932
-
4933
- ### Run a host-owned command with an explicit policy
4934
-
4935
- ```ts
4936
- import os from 'node:os';
4937
- import path from 'node:path';
4938
- import { runKodaXSandboxed } from '@kodax-ai/kodax/sandbox';
4939
-
4940
- const result = await runKodaXSandboxed({
4941
- command: process.execPath,
4942
- args: ['scripts/generate-report.mjs'],
4943
- cwd: projectDirectory,
4944
- filesystem: {
4945
- allowRead: [projectDirectory, process.execPath],
4946
- allowWrite: [projectDirectory, os.tmpdir()],
4947
- denyRead: [path.join(os.homedir(), '.ssh')],
4948
- denyWrite: [path.join(projectDirectory, '.git', 'config')],
4949
- },
4950
- network: {
4951
- mode: 'allowlist',
4952
- origins: ['https://api.example.com'],
4953
- },
4954
- // false by default: start with KodaX's minimal execution environment.
4955
- inheritEnvironment: false,
4956
- env: { REPORT_FORMAT: 'pdf' },
4957
- timeoutMs: 120_000,
4958
- maxOutputBytes: 2 * 1024 * 1024,
4959
- });
4960
-
4961
- if (result.status === 'unavailable') {
4962
- // The command was NOT run. Decide explicitly whether your product should
4963
- // wait for setup, reject the operation, or use its own non-sandbox path.
4964
- showSandboxInfo(getKodaXSandboxSetupGuidance(result.doctor));
4965
- } else if (result.exitCode !== 0) {
4966
- throw new Error(result.stderr || `sandboxed command exited ${result.exitCode}`);
4967
- }
4968
- ```
4969
-
4970
- The generic executor supports network `allow`, `deny`, and exact HTTP(S)
4971
- origin `allowlist` modes plus filesystem policy roots, environment inheritance,
4972
- timeout, cancellation, and bounded output. ASRT permits ordinary reads by
4973
- default: `denyRead` removes access and a more specific `allowRead` carves access
4974
- back. `allowWrite` defines the writable roots, while `denyWrite` removes
4975
- subtrees and always takes precedence over `allowWrite`. The HTTP(S) `origins`
4976
- are normalized to the hostname/port pair enforced by ASRT's network proxy. It
4977
- never silently runs without containment: sandbox unavailability is the typed
4978
- `{ status: 'unavailable', sandboxed: false, doctor }` result.
4979
-
4980
- KodaX's own local workspace-shell policy supplies a stricter `denyRead` set
4981
- than the generic SDK default: common home credential locations, sensitive
4982
- private-key/environment filenames, and the complete resolved agent home are
4983
- denied. Home-local executable search paths nested below those roots are not
4984
- re-granted. This policy belongs to KodaX's command adapter; a standalone SDK
4985
- host must declare the sensitive paths required by its own threat boundary.
4986
-
4987
- `command` and `args` remain separate process arguments. On Windows KodaX uses
4988
- an encoded bootstrap followed by `shell: false`, so `%VAR%`, `&`, embedded
4989
- quotes, and spaces are not expanded or re-parsed by the host shell. The
4990
- explicit environment policy is overlaid into ASRT's fresh restricted-user
4991
- environment; ASRT-owned proxy, CA, and Git safety variables retain precedence.
4992
- `timeoutMs` covers the complete broker lifecycle, including ASRT
4993
- initialization, the command, and cleanup. Windows ACL initialization on a cold
4994
- path can take tens of seconds, so do not reuse a classifier-scale 20–30 second
4995
- deadline unless that early cancellation is intentional.
4996
-
4997
- This is intentionally different from KodaX's local permission fallback. When
4998
- ASRT is unavailable, Auto[LLM] still makes the same deterministic/LLM/user
4999
- permission decision and an admitted local shell may use the ordinary execution
5000
- path; only OS containment is absent. The same local fallback applies when
5001
- ASRT preparation or backend initialization fails before the target process
5002
- starts. KodaX never retries after the target has started, so a sandbox fault
5003
- cannot duplicate command side effects. Remote A2A admitted Skill scripts
5004
- retain their stronger isolation contract and do not fall back to an
5005
- unsandboxed script. Embedded and daemon Runtime capability metadata expose
5006
- `sandboxRuntime` with the platform backend, ASRT version, supported control
5007
- dimensions, elevation behavior, and fallback semantics.
5008
-
5009
- Local workspace commands reuse one long-lived ASRT session per canonical
5010
- workspace. Session-level ACL/WFP initialization is warmed once; the owner
5011
- coordinates short wrap/cleanup RPCs without locking across the target process
5012
- lifetime, so concurrent and background targets do not block later preparation.
5013
- Per-command target attestation and fallback remain independent. Session reset
5014
- happens only after an idle drain or host-process shutdown, never on the
5015
- interactive command critical path. A cold first command may still wait for
5016
- platform initialization, but later commands must not repeatedly pay that
5017
- setup/reset cost. Abort signals and the command deadline cover that prepare
5018
- wait; a cancelled/timed-out prepare never starts the target or changes to the
5019
- ordinary fallback path. Explicit
5020
- Windows system-temp operations that ASRT cannot safely ACL-manage are not
5021
- selected for containment and keep the already-approved normal execution path.
5022
-
5023
- An unhealthy session fails the current prepare immediately so the host can use
5024
- its normal permission fallback. Cleanup continues out of band. On Windows the
5025
- owner first closes its command input and allows up to 130 seconds for ASRT
5026
- 0.0.65's two serial ACL cleanup helpers to finish; only then may process-tree
5027
- termination be forced. The failed workspace session is not replaced until
5028
- that bounded cleanup settles, preventing a replacement from racing stale ACL
5029
- recovery. macOS and Linux use the same EOF-first sequence with a shorter
5030
- bounded termination grace.
5031
-
5032
- Runtime event streams may also contain one terminal `tool.sandbox` observation
5033
- associated with the tool ID. `applied` is emitted only after the in-sandbox
5034
- bootstrap confirms that the real target process reached Node's `spawn` event;
5035
- wrapper startup alone is not sufficient. Before that handshake, a local
5036
- backend failure may fall back to normal execution. After it, KodaX never
5037
- restarts the target:
5038
-
5039
- ```ts
5040
- runtime.events.subscribe({ sessionId: session.id, type: 'tool.sandbox' }, (event) => {
5041
- if (event.type !== 'tool.sandbox') return;
5042
- const { observation } = event.payload.update;
5043
- // observation.state: 'applied' | 'fallback' | 'not_selected'
5044
- diagnostics.recordSandboxRoute(observation);
5045
- });
5046
- ```
5047
-
5048
- This event is optional diagnostics, not conversation content. It is never added
5049
- to model-visible messages. Default consumer UX should not render it in startup
5050
- output, command cards, notifications, or conversation history; expose it only
5051
- in an explicit advanced diagnostics view. KodaX's own Ink REPL does not
5052
- subscribe to the event and refreshes human-readable status only when the user
5053
- runs `/sandbox`. Explicit JSON output and SDK subscriptions retain the
5054
- structured event for professional diagnostics. `/sandbox` is read-only and
5055
- never activates the backend or requests elevation.
5056
-
5057
- ---
5058
-
5059
- ## Learned Skill promotion reference (v0.7.78)
5060
-
5061
- Promotion is the explicit transfer of one immutable, reviewed `ready` or
5062
- `active_learned` Skill revision into the formal user catalog. It is not the
5063
- evidence-driven `testing -> active_learned` canary transition. The public named
5064
- service type is exported from the Runtime SDK:
5065
-
5066
- ```ts
5067
- import {
5068
- createKodaXRuntime,
5069
- type RuntimeLearningService,
5070
- } from '@kodax-ai/kodax/runtime';
5071
-
5072
- const runtime = await createKodaXRuntime({
5073
- requirements: {
5074
- learningCenter: 1,
5075
- skillLearningLoop: 1,
5076
- },
5077
- });
5078
-
5079
- const learning: RuntimeLearningService = runtime.learning;
5080
- const record = await learning.get('normalize-release-notes');
5081
-
5082
- try {
5083
- await learning.promote(record.capabilityId, 'user');
5084
- } finally {
5085
- await runtime.close();
5086
- }
5087
- ```
5088
-
5089
- `name`, `slug`, and exact `capabilityId` are accepted. Exact IDs are preferred
5090
- when multiple projects expose the same display name or slug. `'user'` is the
5091
- only supported scope. Daemon clients need the server-issued
5092
- `learning:control` scope; advertising a client capability does not grant it.
5093
- Inline, Worker, and daemon facades carry the same v2 learned-record shape and
5094
- promotion method.
5095
-
5096
- The Runtime verifies the source is a regular non-symlink file inside the exact
5097
- project Learned Area and that its content still matches the recorded
5098
- fingerprint. It then creates the configured user Skill destination—normally
5099
- `~/.kodax/skills/<slug>/SKILL.md`—with atomic exclusive-publish semantics.
5100
- Existing identical content is idempotent; the final path appears only after the
5101
- complete temporary file is synced. Different formal content returns an
5102
- `action_failed` error and is never overwritten. On success the canonical
5103
- project record changes to `promoted_user`.
5104
-
5105
- Terminal users can inspect the same contract with:
5106
-
5107
- ```text
5108
- /learn promote --help
5109
- /learn help promote
5110
- /help learn promote
5111
- ```
5112
-
5113
- The canonical command is
5114
- `/learn promote <name|slug|capability-id> --scope user`; omitting the scope is a
5115
- backward-compatible shorthand for the same user scope. Unknown, duplicate, or
5116
- unsupported options fail before the Runtime mutation.
5117
-
5118
- ---
5119
-
5120
- ## See also
5121
-
5122
- - [README.md](../README.md) end-user CLI quick start
5229
+ This follow-up does not gate SDK packaging, tagging, or publication.
5230
+
5231
+ ---
5232
+
5233
+ ## 28. Host-configurable Shell Execution Contract (v0.7.77)
5234
+
5235
+ The shared daemon is long-lived, so its startup `process.env.PATH` is not a
5236
+ reliable description of every project's toolchain. A host can opt a Session
5237
+ into a serializable shell contract:
5238
+
5239
+ ```ts
5240
+ await runtime.sessions.updateSettings(session.id, {
5241
+ executionCwd: projectDirectory,
5242
+ shellExecution: {
5243
+ version: 1,
5244
+ shell: {
5245
+ kind: 'pwsh', // pwsh | powershell | cmd | bash | zsh
5246
+ profile: 'default',
5247
+ },
5248
+ environment: {
5249
+ inherit: 'filtered',
5250
+ // Trusted host code, not model input. Use it for a directory-aware
5251
+ // activation command when the shell profile does not switch on cwd.
5252
+ setup: 'fnm use --silent-if-unchanged',
5253
+ windowsPath: 'registry',
5254
+ },
5255
+ cache: {
5256
+ ttlMs: 30_000,
5257
+ refreshToken: 'toolchain-revision-1',
5258
+ },
5259
+ probeTimeoutMs: 10_000,
5260
+ },
5261
+ });
5262
+ ```
5263
+
5264
+ An individual Run can supply `options.context.shellExecution`; a concrete Run
5265
+ contract overrides the Session setting. An omitted value or explicit
5266
+ `undefined` does not erase the Session setting. Use a `null` Session patch to
5267
+ remove it.
5268
+
5269
+ The contract supports an optional absolute `shell.executable` (for example Git
5270
+ Bash) and bounded fixed `shell.args`. Command/file/persistence/profile/server
5271
+ and working-directory control flags are rejected from those fixed arguments.
5272
+ `environment.set` is for non-secret host variables. `denyPatterns` can remove
5273
+ additional names but cannot weaken the built-in Provider credential deny set.
5274
+ `inherit: "none"` retains only the OS variables required to start the selected
5275
+ shell. On Windows, `windowsPath: "registry"` re-reads current Machine/User
5276
+ environment values instead of reusing the daemon's startup PATH.
5277
+
5278
+ The CLI config and SDK expose the same small command-target setting for commands
5279
+ that intentionally need host credentials:
5280
+
5281
+ ```json
5282
+ {
5283
+ "sandbox": {
5284
+ "envPass": ["GH_TOKEN", "GITHUB_TOKEN"]
5285
+ }
5286
+ }
5287
+ ```
5288
+
5289
+ SDK callers pass the same shape per Run, without mutating `process.env` config:
5290
+
5291
+ ```ts
5292
+ const handle = await runtime.runs.start({
5293
+ sessionId: session.id,
5294
+ prompt: 'Inspect the authenticated GitHub repository.',
5295
+ options: {
5296
+ sandbox: { envPass: ['GH_TOKEN', 'GITHUB_TOKEN'] },
5297
+ },
5298
+ });
5299
+ ```
5300
+
5301
+ Direct `runKodaX()` / `startKodaX()` callers use the same
5302
+ `KodaXOptions.sandbox` field. The option is Run-scoped, overrides the process-
5303
+ level fallback even when `envPass` is empty, and is inherited by native child
5304
+ Agents, Workflow children, and deterministic evaluators. Concurrent SDK Runs
5305
+ can therefore use different lists without changing global configuration.
5306
+
5307
+ `sandbox.envPass` defaults to empty and stores exact names, never values. It
5308
+ does not expose credentials to shell profile/setup resolution; the current
5309
+ host values are restored only into the final command environment, which then
5310
+ flows to ASRT or the ordinary fallback path. Windows matching is
5311
+ case-insensitive; POSIX matching is case-sensitive. `NODE_OPTIONS`, `BASH_ENV`,
5312
+ `RIPGREP_CONFIG_PATH`, and imported Bash functions remain blocked even if
5313
+ named. The CLI projects its user config to the Run option through
5314
+ `KODAX_SANDBOX_ENV_PASS`; that environment variable remains a CLI/backward-
5315
+ compatibility fallback rather than the SDK API. Worker and daemon transports
5316
+ carry names only. Values are read from the command-execution host environment;
5317
+ an auto-started daemon inherits that environment, while an attached persistent
5318
+ daemon must already have the variables and must restart after they change.
5319
+
5320
+ Resolution is two-stage and uses the effective cwd:
5321
+
5322
+ 1. sanitize the bootstrap environment, including credentials for built-in,
5323
+ custom, active, inactive, and stacked runtime Providers;
5324
+ 2. start the selected shell, load the requested profile/setup, capture a
5325
+ random-framed environment, validate and sanitize it again;
5326
+ 3. execute the actual command through that same explicit interpreter.
5327
+
5328
+ The cache is in-memory and isolated by normalized contract, canonical cwd,
5329
+ Session scratch identity, credential deny names, and refresh generation. TTL
5330
+ is bounded to ten minutes; zero disables caching. Daemon restart clears it.
5331
+ `clearShellExecutionEnvironmentCache()` is available for an in-process owner
5332
+ that needs immediate global invalidation.
5333
+
5334
+ Native children, nested Actor turns, Workflow child paths, and deterministic
5335
+ build/test/lint evaluators inherit the effective contract. Runtime exact-command
5336
+ permission grants bind the interpreter family and contract fingerprint, so a
5337
+ grant created under cmd cannot silently authorize the same command after a
5338
+ switch to PowerShell or Bash.
5339
+
5340
+ Configured-shell failures are visible and fail closed: KodaX does not reinterpret
5341
+ the command through another shell. When `shellExecution` is absent, KodaX keeps
5342
+ the pre-v0.7.77 platform-shell interpreter behavior for compatibility; the
5343
+ credential filter and explicit `sandbox.envPass` final-target restoration still
5344
+ apply. See
5345
+ [`ISSUE_214_v0.7.77_REGRESSION_GUIDE.md`](test-guides/ISSUE_214_v0.7.77_REGRESSION_GUIDE.md)
5346
+ for cross-project, cache, cancellation, credential, and Windows argv checks.
5347
+
5348
+ ---
5349
+
5350
+ ## 29. Evidence-gated background Skill learning (FEATURE_263, v0.7.78)
5351
+
5352
+ F263 completes the existing Learning Center rather than introducing a second
5353
+ queue or client-owned Skill store. Episode review runs after durable foreground
5354
+ completion and stays off the active Run's latency path. A correction, failure,
5355
+ or verifier result is Memory evidence first; it does not itself authorize a
5356
+ Skill mutation.
5357
+
5358
+ A low-risk declarative Skill can enter automatic project-scoped testing only
5359
+ after an explicit preserve-as-Skill request with verified terminal evidence,
5360
+ or repeated independent root episodes plus independent verified artifact
5361
+ evidence. The owner writes an immutable revision and its canonical capability
5362
+ record before discovery can expose it. Formal/builtin/plugin/human Skills keep
5363
+ precedence and cannot be shadowed. Protected/formal changes, user-global
5364
+ promotion, and Extension authoring remain explicit user actions.
5365
+
5366
+ Hosts that require this behavior should negotiate both Runtime capabilities:
5367
+
5368
+ ```ts
5369
+ const runtime = await createKodaXRuntime({
5370
+ mode: 'daemon',
5371
+ requirements: {
5372
+ learningCenter: 1,
5373
+ skillLearningLoop: 1,
5374
+ },
5375
+ clientInfo: {
5376
+ name: 'my-host',
5377
+ instanceId: stableClientId,
5378
+ instanceSecret: keychainSecret,
5379
+ },
5380
+ });
5381
+
5382
+ const snapshot = await runtime.learning.getSnapshot();
5383
+ const page = await runtime.learning.list({ limit: 50 });
5384
+
5385
+ for await (const event of runtime.learning.subscribe({
5386
+ afterRevision: snapshot.revision,
5387
+ })) {
5388
+ renderLearningEvent(event);
5389
+ }
5390
+ ```
5391
+
5392
+ `loadFullTranscript()` remains the nullable compatibility API.
5393
+ `readFullTranscript()` is the strict host/audit API: it reads main and sidecars
5394
+ under one Session boundary without migration, execution recovery, takeover, or
5395
+ repair. It reports `data_corrupt`, `version_incompatible`, `read_timeout`, and
5396
+ `read_cancelled` instead of turning those cases into an empty/null history.
5397
+ `readConversationHistory()` uses that same read-only boundary, then derives the
5398
+ ordinary conversation projection without changing Session or Run state.
5399
+
5400
+ For an old or incompatible Session that cannot safely resume, use the
5401
+ top-level `exportSessionBundle(id, options?)`. It returns the exact main,
5402
+ `.islands.jsonl`, and legacy `.archive.jsonl` bytes plus hashes and
5403
+ compatibility diagnostics. Export is read-only and fails closed on ambiguous
5404
+ duplicate main files; it does not choose a Session, migrate it, or claim that
5405
+ the task is resumable. Decode each file's `contentBase64` for the canonical
5406
+ lossless bytes; `content` is only a UTF-8 compatibility preview. `byteLength`
5407
+ and `sha256` are computed from the original bytes.
5408
+
5409
+ `runtime.learning` is the authoritative host surface:
5410
+
5411
+ | Need | API |
5412
+ |---|---|
5413
+ | Render inventory or one exact record | `list()` / `get()` |
5414
+ | Render client-specific badges | `getSnapshot()` |
5415
+ | Replay or follow durable lifecycle events | `events()` / `subscribe()` |
5416
+ | Clear or defer only this client's notice | `acknowledge()` / `snooze()` |
5417
+ | Explicitly control a learned revision | `reject()` / `disable()` / `rollback()` / `review()` / `trust()` |
5418
+ | Promote to the user scope | `promote(nameOrSlugOrId, 'user')` |
5419
+
5420
+ Do not scan learned files and infer activation from their presence. Discovery
5421
+ requires the canonical record, matching project identity, lifecycle,
5422
+ fingerprint, regular-file checks, formal-name policy, and exact revision.
5423
+ Testing admission permits one concurrent root binding and at most three
5424
+ exact-revision invocations. Promotion requires independently verified success;
5425
+ failed or inconclusive canaries return to Ready/attention. A Run retains the
5426
+ revision it captured at admission, so rollback or replacement affects future
5427
+ bindings without mutating an in-flight prompt.
5428
+
5429
+ Learning Center notification state is client-specific, but capability
5430
+ lifecycle and project canary state are owner-global. Renderer code should
5431
+ receive sanitized records/events through host IPC; it should not receive
5432
+ daemon credentials or mutate files directly. Inline, Worker, and daemon
5433
+ facades expose the same learning methods. A host missing `skillLearningLoop:1`
5434
+ may still support the older Ready/manual Learning Center surface, but must not
5435
+ claim the complete F263 project-canary contract.
5436
+
5437
+ ---
5438
+
5439
+ ## 30. Standalone sandbox SDK (v0.7.78)
5440
+
5441
+ ASRT containment is a public SDK capability, not an Auto[LLM]-only
5442
+ implementation detail. Import the dedicated subpath when a host needs to
5443
+ sandbox its own commands or scripts:
5444
+
5445
+ ```ts
5446
+ import {
5447
+ activateKodaXSandbox,
5448
+ doctorKodaXSandbox,
5449
+ getKodaXSandboxCapability,
5450
+ getKodaXSandboxSetupGuidance,
5451
+ runKodaXSandboxed,
5452
+ } from '@kodax-ai/kodax/sandbox';
5453
+
5454
+ const capability = getKodaXSandboxCapability();
5455
+ const doctor = await doctorKodaXSandbox({ refresh: true });
5456
+
5457
+ if (!doctor.ready) {
5458
+ showSandboxInfo(getKodaXSandboxSetupGuidance(doctor));
5459
+ // Call only from an explicit setup/onboarding action. On Windows this may
5460
+ // display UAC; ordinary SDK calls never invoke it automatically.
5461
+ const activation = await activateKodaXSandbox();
5462
+ if (activation.status !== 'ready') {
5463
+ showSandboxInfo(activation.guidance);
5464
+ }
5465
+ }
5466
+ ```
5467
+
5468
+ Platform behavior:
5469
+
5470
+ - Windows uses the pinned ASRT restricted-user/WFP setup. The parent terminal
5471
+ does not need to be elevated; the one-time installer requests UAC itself.
5472
+ - macOS uses Seatbelt through `sandbox-exec` and requires ripgrep. Guide users
5473
+ to `brew install ripgrep` when doctor reports it missing.
5474
+ - Linux uses bubblewrap and requires `bubblewrap`, `socat`, and `ripgrep`.
5475
+ Present the emitted `apt`/`dnf`/`pacman` guidance; do not run `sudo` or a
5476
+ package manager silently.
5477
+
5478
+ Do not call `activateKodaXSandbox()` during ordinary Runtime startup, tool
5479
+ execution, or a background permission check. KodaX's own first-run/setup UI
5480
+ checks once; a declined UAC prompt or missing dependency is reported there and
5481
+ is not repeatedly surfaced until the user runs setup again.
5482
+
5483
+ ### Run a host-owned command with an explicit policy
5484
+
5485
+ ```ts
5486
+ import os from 'node:os';
5487
+ import path from 'node:path';
5488
+ import { runKodaXSandboxed } from '@kodax-ai/kodax/sandbox';
5489
+
5490
+ const result = await runKodaXSandboxed({
5491
+ command: process.execPath,
5492
+ args: ['scripts/generate-report.mjs'],
5493
+ cwd: projectDirectory,
5494
+ filesystem: {
5495
+ allowRead: [projectDirectory, process.execPath],
5496
+ allowWrite: [projectDirectory, os.tmpdir()],
5497
+ denyRead: [path.join(os.homedir(), '.ssh')],
5498
+ denyWrite: [path.join(projectDirectory, '.git', 'config')],
5499
+ },
5500
+ network: {
5501
+ mode: 'allowlist',
5502
+ origins: ['https://api.example.com'],
5503
+ },
5504
+ // false by default: start with KodaX's minimal execution environment.
5505
+ inheritEnvironment: false,
5506
+ env: { REPORT_FORMAT: 'pdf' },
5507
+ timeoutMs: 120_000,
5508
+ maxOutputBytes: 2 * 1024 * 1024,
5509
+ });
5510
+
5511
+ if (result.status === 'unavailable') {
5512
+ // The command was NOT run. Decide explicitly whether your product should
5513
+ // wait for setup, reject the operation, or use its own non-sandbox path.
5514
+ showSandboxInfo(getKodaXSandboxSetupGuidance(result.doctor));
5515
+ } else if (result.exitCode !== 0) {
5516
+ throw new Error(result.stderr || `sandboxed command exited ${result.exitCode}`);
5517
+ }
5518
+ ```
5519
+
5520
+ The generic executor supports network `allow`, `deny`, and exact HTTP(S)
5521
+ origin `allowlist` modes plus filesystem policy roots, environment inheritance,
5522
+ timeout, cancellation, and bounded output. ASRT permits ordinary reads by
5523
+ default: `denyRead` removes access and a more specific `allowRead` carves access
5524
+ back. `allowWrite` defines the writable roots, while `denyWrite` removes
5525
+ subtrees and always takes precedence over `allowWrite`. The HTTP(S) `origins`
5526
+ are normalized to the hostname/port pair enforced by ASRT's network proxy. It
5527
+ never silently runs without containment: sandbox unavailability is the typed
5528
+ `{ status: 'unavailable', sandboxed: false, doctor }` result.
5529
+
5530
+ KodaX's own local workspace-shell policy supplies a stricter `denyRead` set
5531
+ than the generic SDK default: common home credential locations, sensitive
5532
+ private-key/environment filenames, and the complete resolved agent home are
5533
+ denied. Home-local executable search paths nested below those roots are not
5534
+ re-granted. This policy belongs to KodaX's command adapter; a standalone SDK
5535
+ host must declare the sensitive paths required by its own threat boundary.
5536
+
5537
+ `command` and `args` remain separate process arguments. On Windows KodaX uses
5538
+ an encoded bootstrap followed by `shell: false`, so `%VAR%`, `&`, embedded
5539
+ quotes, and spaces are not expanded or re-parsed by the host shell. The
5540
+ explicit environment policy is overlaid into ASRT's fresh restricted-user
5541
+ environment; ASRT-owned proxy, CA, and Git safety variables retain precedence.
5542
+ `timeoutMs` covers the complete broker lifecycle, including ASRT
5543
+ initialization, the command, and cleanup. Windows ACL initialization on a cold
5544
+ path can take tens of seconds, so do not reuse a classifier-scale 20–30 second
5545
+ deadline unless that early cancellation is intentional.
5546
+
5547
+ This is intentionally different from KodaX's local permission fallback. When
5548
+ ASRT is unavailable, Auto[LLM] still makes the same deterministic/LLM/user
5549
+ permission decision and an admitted local shell may use the ordinary execution
5550
+ path; only OS containment is absent. The same local fallback applies when
5551
+ ASRT preparation or backend initialization fails before the target process
5552
+ starts. KodaX never retries after the target has started, so a sandbox fault
5553
+ cannot duplicate command side effects. Remote A2A admitted Skill scripts
5554
+ retain their stronger isolation contract and do not fall back to an
5555
+ unsandboxed script. Embedded and daemon Runtime capability metadata expose
5556
+ `sandboxRuntime` with the platform backend, ASRT version, supported control
5557
+ dimensions, elevation behavior, and fallback semantics.
5558
+
5559
+ Local workspace commands reuse one long-lived ASRT session per canonical
5560
+ workspace. Session-level ACL/WFP initialization is warmed once; the owner
5561
+ coordinates short wrap/cleanup RPCs without locking across the target process
5562
+ lifetime, so concurrent and background targets do not block later preparation.
5563
+ Per-command target attestation and fallback remain independent. Session reset
5564
+ happens only after an idle drain or host-process shutdown, never on the
5565
+ interactive command critical path. A cold first command may still wait for
5566
+ platform initialization, but later commands must not repeatedly pay that
5567
+ setup/reset cost. Abort signals and the command deadline cover that prepare
5568
+ wait; a cancelled/timed-out prepare never starts the target or changes to the
5569
+ ordinary fallback path. Explicit
5570
+ Windows system-temp operations that ASRT cannot safely ACL-manage are not
5571
+ selected for containment and keep the already-approved normal execution path.
5572
+
5573
+ An unhealthy session fails the current prepare immediately so the host can use
5574
+ its normal permission fallback. Cleanup continues out of band. On Windows the
5575
+ owner first closes its command input and allows up to 130 seconds for ASRT
5576
+ 0.0.65's two serial ACL cleanup helpers to finish; only then may process-tree
5577
+ termination be forced. The failed workspace session is not replaced until
5578
+ that bounded cleanup settles, preventing a replacement from racing stale ACL
5579
+ recovery. macOS and Linux use the same EOF-first sequence with a shorter
5580
+ bounded termination grace.
5581
+
5582
+ Runtime event streams may also contain one terminal `tool.sandbox` observation
5583
+ associated with the tool ID. `applied` is emitted only after the in-sandbox
5584
+ bootstrap confirms that the real target process reached Node's `spawn` event;
5585
+ wrapper startup alone is not sufficient. Before that handshake, a local
5586
+ backend failure may fall back to normal execution. After it, KodaX never
5587
+ restarts the target:
5588
+
5589
+ ```ts
5590
+ runtime.events.subscribe({ sessionId: session.id, type: 'tool.sandbox' }, (event) => {
5591
+ if (event.type !== 'tool.sandbox') return;
5592
+ const { observation } = event.payload.update;
5593
+ // observation.state: 'applied' | 'fallback' | 'not_selected'
5594
+ diagnostics.recordSandboxRoute(observation);
5595
+ });
5596
+ ```
5597
+
5598
+ This event is optional diagnostics, not conversation content. It is never added
5599
+ to model-visible messages. Default consumer UX should not render it in startup
5600
+ output, command cards, notifications, or conversation history; expose it only
5601
+ in an explicit advanced diagnostics view. KodaX's own Ink REPL does not
5602
+ subscribe to the event and refreshes human-readable status only when the user
5603
+ runs `/sandbox`. Explicit JSON output and SDK subscriptions retain the
5604
+ structured event for professional diagnostics. `/sandbox` is read-only and
5605
+ never activates the backend or requests elevation.
5606
+
5607
+ ---
5608
+
5609
+ ## Learned Skill promotion reference (v0.7.78)
5610
+
5611
+ Promotion is the explicit transfer of one immutable, reviewed `ready` or
5612
+ `active_learned` Skill revision into the formal user catalog. It is not the
5613
+ evidence-driven `testing -> active_learned` canary transition. The public named
5614
+ service type is exported from the Runtime SDK:
5615
+
5616
+ ```ts
5617
+ import {
5618
+ createKodaXRuntime,
5619
+ type RuntimeLearningService,
5620
+ } from '@kodax-ai/kodax/runtime';
5621
+
5622
+ const runtime = await createKodaXRuntime({
5623
+ requirements: {
5624
+ learningCenter: 1,
5625
+ skillLearningLoop: 1,
5626
+ },
5627
+ });
5628
+
5629
+ const learning: RuntimeLearningService = runtime.learning;
5630
+ const record = await learning.get('normalize-release-notes');
5631
+
5632
+ try {
5633
+ await learning.promote(record.capabilityId, 'user');
5634
+ } finally {
5635
+ await runtime.close();
5636
+ }
5637
+ ```
5638
+
5639
+ `name`, `slug`, and exact `capabilityId` are accepted. Exact IDs are preferred
5640
+ when multiple projects expose the same display name or slug. `'user'` is the
5641
+ only supported scope. Daemon clients need the server-issued
5642
+ `learning:control` scope; advertising a client capability does not grant it.
5643
+ Inline, Worker, and daemon facades carry the same v2 learned-record shape and
5644
+ promotion method.
5645
+
5646
+ The Runtime verifies the source is a regular non-symlink file inside the exact
5647
+ project Learned Area and that its content still matches the recorded
5648
+ fingerprint. It then creates the configured user Skill destination—normally
5649
+ `~/.kodax/skills/<slug>/SKILL.md`—with atomic exclusive-publish semantics.
5650
+ Existing identical content is idempotent; the final path appears only after the
5651
+ complete temporary file is synced. Different formal content returns an
5652
+ `action_failed` error and is never overwritten. On success the canonical
5653
+ project record changes to `promoted_user`.
5654
+
5655
+ Terminal users can inspect the same contract with:
5656
+
5657
+ ```text
5658
+ /learn promote --help
5659
+ /learn help promote
5660
+ /help learn promote
5661
+ ```
5662
+
5663
+ The canonical command is
5664
+ `/learn promote <name|slug|capability-id> --scope user`; omitting the scope is a
5665
+ backward-compatible shorthand for the same user scope. Unknown, duplicate, or
5666
+ unsupported options fail before the Runtime mutation.
5667
+
5668
+ ---
5669
+
5670
+ ## See also
5671
+
5672
+ - [README.md](../README.md) — end-user CLI quick start
5123
5673
  - [docs/ADR.md ADR-024](ADR.md#adr-024-npm-发布物正名-kodax-aikodax--sdk-subpath-exports-形式化-v0739) — SDK subpath architecture rationale
5124
5674
  - [docs/ADR.md ADR-032](ADR.md#adr-032-sdk-embedder-surface-closure-feature_186-v0742) — FEATURE_186 design record (all 8 phases)
5125
5675
  - [docs/ADR.md ADR-057](ADR.md#adr-057-large-compaction-is-an-always-on-context-scoped-full-coverage-transaction) — v0.7.74 compaction and exact-history ownership
5126
5676
  - [docs/ADR.md ADR-058](ADR.md#adr-058-model-agent-wait-is-mailbox-control-not-event-telemetry) — mailbox control versus Actor telemetry
5127
5677
  - [docs/features/v0.7.42.md FEATURE_186](features/v0.7.42.md#feature_186-sdk-embedder-surface-closure--kodax-space-gap-list--mcp-popout) — gap-by-gap landing matrix
5128
- - [docs/features/v0.7.74.md](features/v0.7.74.md) — v0.7.74 release-candidate design and verification record
5129
- - [docs/features/v0.7.75.md](features/v0.7.75.md) — v0.7.75 Windows GUI and Sidecar/Runtime stabilization candidate
5130
- - [docs/features/v0.7.77.md](features/v0.7.77.md) — v0.7.77 adaptive-quality, governed-memory, and release-hardening record
5678
+ - [docs/features/v0.7.74.md](features/v0.7.74.md) — v0.7.74 release-candidate design and verification record
5679
+ - [docs/features/v0.7.75.md](features/v0.7.75.md) — v0.7.75 Windows GUI and Sidecar/Runtime stabilization candidate
5680
+ - [docs/features/v0.7.77.md](features/v0.7.77.md) — v0.7.77 adaptive-quality, governed-memory, and release-hardening record