@kodax-ai/kodax 0.7.73 → 0.7.75

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 (77) hide show
  1. package/CHANGELOG.md +174 -0
  2. package/README.md +40 -6
  3. package/README_CN.md +34 -5
  4. package/config-templates/config.example.jsonc +7 -0
  5. package/dist/chunks/{agent-T6PGT6AU.js → agent-LVZJB2GQ.js} +1 -1
  6. package/dist/chunks/argument-completer-GHWEWPVQ.js +2 -0
  7. package/dist/chunks/chunk-4PVSDE5L.js +2 -0
  8. package/dist/chunks/chunk-5WSOB34U.js +329 -0
  9. package/dist/chunks/chunk-6YUDR7WN.js +5 -0
  10. package/dist/chunks/chunk-6ZMKX3XF.js +74 -0
  11. package/dist/chunks/chunk-737LI7OS.js +369 -0
  12. package/dist/chunks/{chunk-64M5HOCA.js → chunk-BN3W2DQY.js} +1 -1
  13. package/dist/chunks/chunk-DEHOFNQA.js +427 -0
  14. package/dist/chunks/{chunk-MBAPT2R5.js → chunk-G5VW55T4.js} +1 -1
  15. package/dist/chunks/{chunk-6RI2RBTF.js → chunk-N6M2LYEP.js} +1 -1
  16. package/dist/chunks/{chunk-GAFFYJU3.js → chunk-OGL227XV.js} +11 -11
  17. package/dist/chunks/{chunk-6JTWXGVU.js → chunk-OJQQRLZJ.js} +225 -225
  18. package/dist/chunks/{chunk-XXQLPTAN.js → chunk-Q65EAOX6.js} +7 -7
  19. package/dist/chunks/chunk-QRBWNYFV.js +321 -0
  20. package/dist/chunks/chunk-QSMXI4MH.js +770 -0
  21. package/dist/chunks/{chunk-MLEAQTCD.js → chunk-SB75RNNR.js} +11 -4
  22. package/dist/chunks/{chunk-M7FXNPWV.js → chunk-UJRRZINL.js} +6 -6
  23. package/dist/chunks/{compaction-config-IHJBP4GO.js → compaction-config-L2YZK7Q3.js} +1 -1
  24. package/dist/chunks/{construction-bootstrap-EGRUHKLP.js → construction-bootstrap-3TQWTHSQ.js} +1 -1
  25. package/dist/chunks/{dist-EVEJAII2.js → dist-D3EPVAOZ.js} +1 -1
  26. package/dist/chunks/dist-ZJFTZJ55.js +2 -0
  27. package/dist/chunks/host-KHDXYFVG.js +2 -0
  28. package/dist/chunks/{run-manager-AY7RQOWM.js → run-manager-CD24OO4V.js} +1 -1
  29. package/dist/chunks/{utils-2GAE4XIM.js → utils-HMVP6XPH.js} +1 -1
  30. package/dist/index.d.ts +12 -11
  31. package/dist/index.js +5 -5
  32. package/dist/kodax_cli.js +1300 -1258
  33. package/dist/runtime-worker.js +1209 -1167
  34. package/dist/sdk-a2a.d.ts +9 -8
  35. package/dist/sdk-a2a.js +1 -1
  36. package/dist/sdk-agent.d.ts +107 -59
  37. package/dist/sdk-agent.js +1 -1
  38. package/dist/sdk-coding.d.ts +32 -16
  39. package/dist/sdk-coding.js +1 -1
  40. package/dist/sdk-experimental-memory.js +1 -1
  41. package/dist/sdk-llm.js +1 -1
  42. package/dist/sdk-mcp.js +1 -1
  43. package/dist/sdk-media.js +1 -1
  44. package/dist/sdk-repl.d.ts +27 -20
  45. package/dist/sdk-repl.js +2 -2
  46. package/dist/sdk-runtime.d.ts +153 -15
  47. package/dist/sdk-runtime.js +1 -1
  48. package/dist/sdk-session.d.ts +4 -4
  49. package/dist/sdk-session.js +1 -1
  50. package/dist/sdk-skills.js +1 -1
  51. package/dist/semantic-worker.js +16 -14
  52. package/dist/types-chunks/{bash-prefix-extractor.d-bApf5nGw.d.ts → bash-prefix-extractor.d-r1beOESM.d.ts} +48 -12
  53. package/dist/types-chunks/{capsule.d-CGWltwno.d.ts → capsule.d-zeqV4IQX.d.ts} +10 -2
  54. package/dist/types-chunks/{commands.d-CEHGwRok.d.ts → commands.d-DUxnK2TU.d.ts} +10 -7
  55. package/dist/types-chunks/{guardrail.d-CC4vAQl7.d.ts → guardrail.d-CWYD1bdL.d.ts} +1 -1
  56. package/dist/types-chunks/{guardrail.d-CXIPlWP3.d.ts → guardrail.d-qjuKJZ31.d.ts} +72 -11
  57. package/dist/types-chunks/history-retrieval.d-BKTJIrVd.d.ts +57 -0
  58. package/dist/types-chunks/{public-api.d-FBmX0kxo.d.ts → public-api.d-CX4B11qY.d.ts} +17 -4
  59. package/dist/types-chunks/{run-manager.d-DUNnJcpE.d.ts → run-manager.d-B9fEIjZk.d.ts} +1 -1
  60. package/dist/types-chunks/{sdk-session-XqacVOs8.d.ts → sdk-session-B0fhAOPa.d.ts} +2 -2
  61. package/dist/types-chunks/{types.d-DKr6TCXL.d.ts → types.d-Bm_y6YuM.d.ts} +2 -2
  62. package/dist/types-chunks/{types.d-DgciGHoG.d.ts → types.d-DEctY20M.d.ts} +9 -2
  63. package/dist/types-chunks/{types.d-BnPhGmar.d.ts → types.d-sRLugmjy.d.ts} +61 -14
  64. package/dist/types-chunks/{utils.d-BPYX7-KN.d.ts → utils.d-D0wPxz8y.d.ts} +5 -5
  65. package/docs/SDK_EMBEDDER_GUIDE.md +1995 -1689
  66. package/package.json +8 -1
  67. package/dist/chunks/argument-completer-QGMWA2C3.js +0 -2
  68. package/dist/chunks/chunk-AGDDM5AW.js +0 -329
  69. package/dist/chunks/chunk-BUPJGG3C.js +0 -5
  70. package/dist/chunks/chunk-EAD4SF3Z.js +0 -413
  71. package/dist/chunks/chunk-HVCNAZ52.js +0 -365
  72. package/dist/chunks/chunk-OG6DRNKV.js +0 -755
  73. package/dist/chunks/chunk-PFH53O4M.js +0 -2
  74. package/dist/chunks/chunk-V4F6IYNK.js +0 -73
  75. package/dist/chunks/chunk-WQ6PWGK5.js +0 -320
  76. package/dist/chunks/dist-5N3I2RTW.js +0 -2
  77. package/dist/chunks/host-MECITNE3.js +0 -2
@@ -22,15 +22,19 @@ are NOT obvious from inspecting the type definitions alone:
22
22
  12. [Provider credential verification — `verifyProviderCredential`](#12-provider-credential-verification--verifyprovidercredential-feature_216-v0745)
23
23
  13. [Inject your product's manual — `selfManual`](#13-inject-your-products-manual--selfmanual-feature_221-v0747)
24
24
  14. [Media input artifacts — `@kodax-ai/kodax/media`](#14-media-input-artifacts--kodax-aikodaxmedia-feature_239-v0756)
25
- 15. [Space v0.7.57 follow-up ledger](#15-space-v0757-follow-up-ledger)
26
- 16. [SDK agent-profile surface — `KodaXAgentProfile`](#16-sdk-agent-profile-surface--kodaxagentprofile-feature_247-v0758)
27
- 17. [Runtime SDK, Worker isolation, and local daemon](#17-runtime-sdk-worker-isolation-and-local-daemon-feature_253-feature_257)
28
- 18. [External-agent executor plane](#18-external-agent-executor-plane-feature_258-v0767)
29
- 19. [Session surface filtering and cursor pagination](#19-session-surface-filtering-and-cursor-pagination-feature_261-v0767)
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-v0768)
32
- 22. [Bidirectional A2A 1.0 — `/a2a`](#22-bidirectional-a2a-10--a2a-feature_267-v0769)
33
- 23. [Shared Coder daemon for Space and IDE hosts](#23-shared-coder-daemon-for-space-and-ide-hosts-feature_269-v0769)
25
+ 15. [Space v0.7.57 follow-up ledger](#15-space-v0757-follow-up-ledger)
26
+ 16. [SDK agent-profile surface — `KodaXAgentProfile`](#16-sdk-agent-profile-surface--kodaxagentprofile-feature_247-v0758)
27
+ 17. [Runtime SDK, Worker isolation, and local daemon](#17-runtime-sdk-worker-isolation-and-local-daemon-feature_253-feature_257)
28
+ 18. [External-agent executor plane](#18-external-agent-executor-plane-feature_258-v0767)
29
+ 19. [Session surface filtering and cursor pagination](#19-session-surface-filtering-and-cursor-pagination-feature_261-v0767)
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-v0768)
32
+ 22. [Bidirectional A2A 1.0 — `/a2a`](#22-bidirectional-a2a-10--a2a-feature_267-v0769)
33
+ 23. [Shared Coder daemon for Space and IDE hosts](#23-shared-coder-daemon-for-space-and-ide-hosts-feature_269-v0769)
34
+ 24. [Runtime-owned Auto Mode and plan-approval bridges](#24-runtime-owned-auto-mode-and-plan-approval-bridges-v0772v0773)
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)
34
38
 
35
39
  §1–§3 (and the Phase-7/8 MCP-popout surface in §1) land in v0.7.42
36
40
  under FEATURE_186 (see [ADR-032](ADR.md#adr-032-sdk-embedder-surface-closure-feature_186-v0742)).
@@ -697,19 +701,19 @@ await runKodaX(
697
701
  // ✓ ~/.kodax/sessions/s_my_chat.jsonl now exists after the run.
698
702
 
699
703
  // Same `storage` instance reads back through SessionManager:
700
- const recent = await listSessions({
701
- scope: 'user',
702
- surface: 'acp',
703
- limit: 50,
704
- });
705
- const nextPage = recent.at(-1)?.cursor
706
- ? await listSessions({
707
- scope: 'user',
708
- surface: 'acp',
709
- limit: 50,
710
- cursor: recent.at(-1)?.cursor,
711
- })
712
- : [];
704
+ const recent = await listSessions({
705
+ scope: 'user',
706
+ surface: 'acp',
707
+ limit: 50,
708
+ });
709
+ const nextPage = recent.at(-1)?.cursor
710
+ ? await listSessions({
711
+ scope: 'user',
712
+ surface: 'acp',
713
+ limit: 50,
714
+ cursor: recent.at(-1)?.cursor,
715
+ })
716
+ : [];
713
717
  const replay = await loadSession('s_my_chat');
714
718
  const scrollback = await loadFullTranscript('s_my_chat');
715
719
  const compacted = await compactSession('s_my_chat', { dryRun: true });
@@ -720,10 +724,25 @@ await appendClientNotice('s_my_chat', {
720
724
  });
721
725
  ```
722
726
 
727
+ ### Auto-resume selection in v0.7.74
728
+
729
+ With `session.autoResume: true` (or `resume: true`) and no explicit ID, KodaX
730
+ calls `storage.list(context.gitRoot, { limit: 1000 })` and chooses the first
731
+ newest-first summary whose `msgCount > 0`. This prevents newer zero-message
732
+ ACP/bootstrap placeholders from shadowing a real conversation. An explicit
733
+ `session.id` always wins. Custom `KodaXSessionStorage` implementations should
734
+ therefore honor the optional `limit` argument and return `msgCount` accurately;
735
+ they may return fewer than 1000 records.
736
+
737
+ The standalone interactive CLI additionally restores the persisted
738
+ workspace/runtime identity before the next turn. SDK embedders still own
739
+ `context.gitRoot`, `context.executionCwd`, and storage construction; do not rely
740
+ on process cwd as a substitute for host-owned runtime context.
741
+
723
742
  ### What `createSessionManager()` returns (v0.7.43+)
724
743
 
725
744
  ```ts
726
- interface SessionManager {
745
+ interface SessionManager {
727
746
  // Read side (FEATURE_173 v0.7.42)
728
747
  listSessions(...): Promise<SessionSummary[]>;
729
748
  loadSession(id): Promise<...>;
@@ -738,13 +757,13 @@ interface SessionManager {
738
757
  watchSessions(cb): () => void;
739
758
  // Write side (v0.7.43 follow-up)
740
759
  storage: FileSessionStorage; // ← NEW — pass into runKodaX
741
- }
742
- ```
743
-
744
- `surface` is an exact filter applied before `limit`. Each returned summary may
745
- include an opaque `cursor`; pass the last summary's cursor back unchanged to
746
- continue the stable newest-first listing. Callers must not parse or construct
747
- cursors themselves.
760
+ }
761
+ ```
762
+
763
+ `surface` is an exact filter applied before `limit`. Each returned summary may
764
+ include an opaque `cursor`; pass the last summary's cursor back unchanged to
765
+ continue the stable newest-first listing. Callers must not parse or construct
766
+ cursors themselves.
748
767
 
749
768
  ### Active context vs full transcript vs UI replay
750
769
 
@@ -967,7 +986,7 @@ running your project's test suite against `main` — you can `npm link`
967
986
  the in-tree KodaX checkout instead of waiting for a published version.
968
987
 
969
988
  As of v0.7.43 the root `package.json` is in **already-published shape**:
970
- `"name": "@kodax-ai/kodax"` is baked in along with all 10 SDK subpath
989
+ `"name": "@kodax-ai/kodax"` is baked in along with all 10 SDK subpath
971
990
  exports. `npm link` "just works" — no need to run `scripts/release.mjs`
972
991
  first.
973
992
 
@@ -1296,7 +1315,7 @@ import {
1296
1315
  ```ts
1297
1316
  interface KodaXModelCapabilities {
1298
1317
  provider: string; // 'anthropic' | 'kimi' | 'ark-coding' | <custom-name>
1299
- model: string; // model id (e.g. 'claude-sonnet-4-6', 'kimi-k2.7-code')
1318
+ model: string; // model id (e.g. 'claude-sonnet-4-6', 'kimi-k2.7-code')
1300
1319
  displayName: string; // human label — falls back to model id
1301
1320
  supportsThinking: boolean; // native reasoning is available?
1302
1321
  reasoningCapability: 'native-budget' | 'native-effort' | 'native-toggle' | 'prompt-only' | 'none' | 'unknown'; // legacy mechanism label
@@ -1328,8 +1347,8 @@ for (const caps of listAllModelCapabilities()) {
1328
1347
  ```ts
1329
1348
  import { resolveModelCapabilities } from '@kodax-ai/kodax/llm';
1330
1349
 
1331
- const caps = resolveModelCapabilities('kimi', 'kimi-k2.7-code');
1332
- // => { contextWindow: 262_144, supportsThinking: true, reasoningProfile: { defaultEffort: 'high', ... }, ... }
1350
+ const caps = resolveModelCapabilities('kimi', 'kimi-k2.7-code');
1351
+ // => { contextWindow: 262_144, supportsThinking: true, reasoningProfile: { defaultEffort: 'high', ... }, ... }
1333
1352
  ```
1334
1353
 
1335
1354
  For picker/status UIs, use `reasoningProfile.supportedEfforts` and
@@ -1466,11 +1485,11 @@ and loaded into the in-memory `KODAX_PROVIDER_SNAPSHOTS` export. When upstream
1466
1485
  providers publish a new model or change a context-window cap, the JSON file is
1467
1486
  the patch site — the new value flows to runtime (via `buildProviderConfig`) AND
1468
1487
  to SDK consumers (via the getters) in a single edit. The current snapshot is
1469
- dated 2026-07-16 and includes the GPT-5.4, Kimi K2.7 Code / HighSpeed, GLM-5.2, MiniMax
1488
+ dated 2026-07-16 and includes the GPT-5.4, Kimi K2.7 Code / HighSpeed, GLM-5.2, MiniMax
1470
1489
  M3/M2.7, DeepSeek V4, and Doubao Seed 2.0 route refreshes where supported. The
1471
1490
  test suite at
1472
1491
  [`packages/llm/src/providers/model-capabilities.test.ts`](../packages/llm/src/providers/model-capabilities.test.ts)
1473
- locks in specific values (e.g. the public Kimi lineup at 262,144 tokens, deepseek-v4-pro at 1M)
1492
+ locks in specific values (e.g. the public Kimi lineup at 262,144 tokens, deepseek-v4-pro at 1M)
1474
1493
  so accidental drift is caught at PR time.
1475
1494
 
1476
1495
  The probe scripts that surveyed upstream APIs live at
@@ -1905,12 +1924,12 @@ Each provider has one `verifyStrategy` value baked into `provider-capabilities.j
1905
1924
 
1906
1925
  | Strategy | What runs | Cost | Used by built-ins |
1907
1926
  |---|---|---|---|
1908
- | `count-tokens` | `client.messages.countTokens({ messages: [{role:'user',content:'hi'}] })` | 0 token | `anthropic`, `kimi-code`, `qwen-token-plan`, `zhipu-coding`, `zai-coding`, `minimax-coding`, `ark-coding` |
1927
+ | `count-tokens` | `client.messages.countTokens({ messages: [{role:'user',content:'hi'}] })` | 0 token | `anthropic`, `kimi-code`, `qwen-token-plan`, `zhipu-coding`, `zai-coding`, `minimax-coding`, `ark-coding` |
1909
1928
  | `models-list` | `client.models.list()` | 0 token | `openai`, `deepseek`, `kimi`, `qwen` |
1910
1929
  | `minimal-message` | `chat.completions.create({max_tokens:1, content:'hi'})` (or Anthropic equivalent) | ~6–7 token | `zhipu`, `mimo`, `mimo-coding` |
1911
1930
  | `unsupported` | nothing — short-circuits | — | `gemini-cli`, `codex-cli` (cli-bridge: credentials live in CLI binary) |
1912
1931
 
1913
- `models-list` is NOT used as a universal default because (a) some providers' `/v1/models` is publicly accessible (so a bad key returns 200 — false positive), and (b) some compat layers don't implement it (404) or 401 even for valid keys (false negative). The 2026-05-28 provider probe matrix captured the original per-provider evidence (12 providers at the time; 15 built-in aliases as of 2026-06-28). The current capability catalog has 16 aliases, including `qwen-token-plan` with `count-tokens`; opencode's `setup-recording-env.ts` makes the same per-provider decision across its 20+ providers.
1932
+ `models-list` is NOT used as a universal default because (a) some providers' `/v1/models` is publicly accessible (so a bad key returns 200 — false positive), and (b) some compat layers don't implement it (404) or 401 even for valid keys (false negative). The 2026-05-28 provider probe matrix captured the original per-provider evidence (12 providers at the time; 15 built-in aliases as of 2026-06-28). The current capability catalog has 16 aliases, including `qwen-token-plan` with `count-tokens`; opencode's `setup-recording-env.ts` makes the same per-provider decision across its 20+ providers.
1914
1933
 
1915
1934
  ### Custom providers
1916
1935
 
@@ -1954,7 +1973,7 @@ Cli-bridge providers (`gemini-cli`, `codex-cli`) return their CLI binary's known
1954
1973
 
1955
1974
  ### Reference
1956
1975
 
1957
- - Source: `packages/llm/src/providers/verify-credential.ts` (orchestrator + classifier) + `verify-credential.test.ts` (27 unit tests) + `verify-credential-integration.test.ts` (12 gated real-key/fake-key tests, enabled by `KODAX_INTEGRATION_TEST=1`).
1976
+ - Source: `packages/llm/src/providers/verify-credential.ts` (orchestrator + classifier) + `verify-credential.test.ts` (27 unit tests) + `verify-credential-integration.test.ts` (12 gated real-key/fake-key tests, enabled by `KODAX_INTEGRATION_TEST=1`).
1958
1977
  - Data: `packages/llm/src/providers/provider-capabilities.json` `verifyStrategy` field per provider.
1959
1978
  - Design notes + probe matrix: [docs/features/v0.7.45.md FEATURE_216](features/v0.7.45.md#feature_216-provider-credential-verification-api).
1960
1979
 
@@ -2003,14 +2022,14 @@ By default your `topics` **extend** the KodaX base manual. v0.7.58 adds
2003
2022
 
2004
2023
  `KODAX_UNDERLYING_CAPABILITY_TOPICS` (exported) is the recommended mechanism-topic
2005
2024
  subset a product built on KodaX should keep even in a full replace — so your users
2006
- still get correct answers about the underlying engine (providers / config /
2007
- permissions / tools / skills / extensions / mcp / repo-intelligence / sessions /
2008
- sdk / custom-providers) without the KodaX brand:
2009
-
2010
- The default full manual also includes the `memory` topic. It is intentionally
2011
- not part of `KODAX_UNDERLYING_CAPABILITY_TOPICS` because `/experimental-memory`
2012
- is opt-in; hosts that expose it should add `memory` explicitly or provide a
2013
- product-specific override.
2025
+ still get correct answers about the underlying engine (providers / config /
2026
+ permissions / tools / skills / extensions / mcp / repo-intelligence / sessions /
2027
+ sdk / custom-providers) without the KodaX brand:
2028
+
2029
+ The default full manual also includes the `memory` topic. It is intentionally
2030
+ not part of `KODAX_UNDERLYING_CAPABILITY_TOPICS` because `/experimental-memory`
2031
+ is opt-in; hosts that expose it should add `memory` explicitly or provide a
2032
+ product-specific override.
2014
2033
 
2015
2034
  ```ts
2016
2035
  import {
@@ -2169,30 +2188,32 @@ For streaming follow-ups, use `enqueueWithArtifacts()` instead of the raw
2169
2188
  message queue:
2170
2189
 
2171
2190
  ```ts
2172
- enqueueWithArtifacts({
2173
- provider: selectedProvider,
2174
- model: selectedModel,
2175
- sessionId: activeSessionId,
2176
- content: followupText,
2177
- inputArtifacts: [artifact],
2178
- });
2179
- ```
2180
-
2181
- The helper validates first and then stores `inputArtifacts` on the queued prompt.
2182
- Queued image follow-ups are rebuilt as multimodal content blocks on the next
2183
- runner turn. Unsupported file/video attachments are rejected before enqueueing.
2184
- Pass `sessionId` whenever the host can run more than one session concurrently;
2185
- it targets that Actor session's root queue without exposing Actor paths. For
2186
- backward compatibility, omitting both `sessionId` and `agentId` still binds to
2187
- the sole active Actor root, or uses the legacy unscoped SA queue when no Actor
2188
- run is active. If multiple Actor roots are active, the helper rejects the
2189
- ambiguous call instead of risking cross-session delivery. Low-level child-Actor
2190
- producers may continue to pass an explicit `agentId`.
2191
-
2192
- `enqueueWithArtifacts()` is an in-process queue helper for direct/inline runs.
2193
- Runtime Worker and daemon clients must use `runtime.runs.submitInput(...)` (with
2194
- the same `sessionId`, `afterRunId`, and `delivery:'after_turn'`) because a
2195
- process-local MessageQueue cannot cross those transport boundaries.
2191
+ enqueueWithArtifacts({
2192
+ provider: selectedProvider,
2193
+ model: selectedModel,
2194
+ sessionId: activeSessionId,
2195
+ content: followupText,
2196
+ inputArtifacts: [artifact],
2197
+ });
2198
+ ```
2199
+
2200
+ The helper validates first and then stores `inputArtifacts` on the queued prompt.
2201
+ Queued image follow-ups are rebuilt as multimodal content blocks on the next
2202
+ runner turn. Unsupported file/video attachments are rejected before enqueueing.
2203
+ Pass `sessionId` whenever the host can run more than one session concurrently;
2204
+ it targets that Actor session's root queue without exposing Actor paths. For
2205
+ backward compatibility, omitting both `sessionId` and `agentId` still binds to
2206
+ the sole active Actor root, or uses the legacy unscoped SA queue when no Actor
2207
+ run is active. If multiple Actor roots are active, the helper rejects the
2208
+ ambiguous call instead of risking cross-session delivery. Low-level child-Actor
2209
+ producers may continue to pass an explicit `agentId`.
2210
+
2211
+ `enqueueWithArtifacts()` is an in-process queue helper for direct/inline runs.
2212
+ Runtime Worker and daemon clients must use `runtime.runs.submitInput(...)` (with
2213
+ the same `sessionId` and `afterRunId`) because a process-local MessageQueue
2214
+ cannot cross those transport boundaries. Use `delivery:'after_turn'` to create
2215
+ a continuation Run after the current Run ends, or `delivery:'interrupt'` to
2216
+ inject into the current active Actor Run at its next safe Runner boundary.
2196
2217
 
2197
2218
  ### Boundaries
2198
2219
 
@@ -2361,14 +2382,14 @@ The important creation options are:
2361
2382
  | `worker.resourceLimits` | unset | Optional V8 heap/stack limits; requires `isolation: 'worker'`. |
2362
2383
  | `worker.shutdownTimeoutMs` | `2000` | Grace before the parent terminates the Runtime Worker. |
2363
2384
  | `requirements.hardDispose` | `false` | Rejects inline and daemon forms; prevents an accidental weaker ownership form. |
2364
- | `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`. |
2385
+ | `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`. |
2365
2386
  | `profile` | `'default'` | Daemon uniqueness and runtime configuration namespace. |
2366
2387
  | `sessionsDir` | `<homeDir>/.kodax/sessions` | Explicit session storage override. |
2367
2388
  | `daemonStartupTimeoutMs` | `60000` | Total cold-start/concurrent-owner wait budget. |
2368
- | `daemonConnectTimeoutMs` | `2000` | Per-socket connection timeout. |
2369
- | `autoStartDaemon` | conditional | For `createKodaXRuntime({mode:'daemon'})`, true only when no explicit endpoint/transport is supplied. |
2370
- | `externalAgents` | unset | Host-installed executor factories, dispatch policy, optional credential/artifact policies, and default dispatch context. Inline owner only; see §18. |
2371
- | `requirements.externalAgents` | `false` | Reject a Runtime/daemon connection that does not advertise an installed external-agent plane. |
2389
+ | `daemonConnectTimeoutMs` | `2000` | Per-socket connection timeout. |
2390
+ | `autoStartDaemon` | conditional | For `createKodaXRuntime({mode:'daemon'})`, true only when no explicit endpoint/transport is supplied. |
2391
+ | `externalAgents` | unset | Host-installed executor factories, dispatch policy, optional credential/artifact policies, and default dispatch context. Inline owner only; see §18. |
2392
+ | `requirements.externalAgents` | `false` | Reject a Runtime/daemon connection that does not advertise an installed external-agent plane. |
2372
2393
 
2373
2394
  KodaX rejects contradictory options. Worker settings without Worker isolation,
2374
2395
  `requirements.hardDispose` on inline/daemon forms, and any explicit isolation
@@ -2442,20 +2463,20 @@ no explicit `daemonEndpoint` or `daemonTransport` is supplied it starts or reuse
2442
2463
  the local profile daemon. `connectKodaXRuntime()` is attach-only unless
2443
2464
  `autoStart: true` is passed.
2444
2465
 
2445
- SDK auto-start allows `daemonStartupTimeoutMs` (default 60 seconds) and
2446
- `daemonConnectTimeoutMs`. The longer startup budget covers cold machines and
2447
- concurrent test/desktop startup without weakening PID, endpoint, token, or
2448
- runtime-identity validation.
2449
-
2450
- `homeDir` and `KODAX_HOME` deliberately name different levels. Runtime SDK and
2451
- CLI daemon `--home` accept the **base directory that contains `.kodax`**;
2452
- lower-level `KODAX_HOME` points at the **data directory itself** and need not be
2453
- named `.kodax`. To share the default CLI daemon, omit `homeDir`; this honors the
2454
- exact resolved `KODAX_HOME`. Passing `os.homedir()` explicitly instead selects
2455
- `<os.homedir()>/.kodax`, regardless of an ambient custom `KODAX_HOME`. For an
2456
- isolated embedder namespace, pass a private base directory and expect data at
2457
- `<homeDir>/.kodax`. Passing `~/.kodax` as `homeDir` would instead select
2458
- `~/.kodax/.kodax` and a different daemon namespace.
2466
+ SDK auto-start allows `daemonStartupTimeoutMs` (default 60 seconds) and
2467
+ `daemonConnectTimeoutMs`. The longer startup budget covers cold machines and
2468
+ concurrent test/desktop startup without weakening PID, endpoint, token, or
2469
+ runtime-identity validation.
2470
+
2471
+ `homeDir` and `KODAX_HOME` deliberately name different levels. Runtime SDK and
2472
+ CLI daemon `--home` accept the **base directory that contains `.kodax`**;
2473
+ lower-level `KODAX_HOME` points at the **data directory itself** and need not be
2474
+ named `.kodax`. To share the default CLI daemon, omit `homeDir`; this honors the
2475
+ exact resolved `KODAX_HOME`. Passing `os.homedir()` explicitly instead selects
2476
+ `<os.homedir()>/.kodax`, regardless of an ambient custom `KODAX_HOME`. For an
2477
+ isolated embedder namespace, pass a private base directory and expect data at
2478
+ `<homeDir>/.kodax`. Passing `~/.kodax` as `homeDir` would instead select
2479
+ `~/.kodax/.kodax` and a different daemon namespace.
2459
2480
 
2460
2481
  ### Worker-hosted embedded usage
2461
2482
 
@@ -2534,16 +2555,16 @@ For a given profile, one owner wins an atomic lock. Concurrent starters wait for
2534
2555
  the winner and connect once it is ready. Stale state is cleaned only after pid,
2535
2556
  endpoint, token, and runtime identity checks. If ownership cannot be verified,
2536
2557
  KodaX reports the daemon as unhealthy instead of killing an arbitrary process.
2537
- SDK auto-start launches a detached `kodax daemon serve` process; it never treats
2538
- an in-process socket listener as daemon mode. Closing the SDK client detaches
2539
- without stopping that shared process. Use `kodax daemon stop` or the explicit
2540
- runtime shutdown protocol to stop the owner.
2541
-
2542
- CLI and SDK startup retain the exact spawned candidate until health confirms
2543
- that candidate PID. Early exit, timeout, identity mismatch, cancellation, or a
2544
- different owner winning the race reclaims only the unsuccessful candidate
2545
- process tree. Once healthy, the owner detaches normally and is not tied to the
2546
- creating client process.
2558
+ SDK auto-start launches a detached `kodax daemon serve` process; it never treats
2559
+ an in-process socket listener as daemon mode. Closing the SDK client detaches
2560
+ without stopping that shared process. Use `kodax daemon stop` or the explicit
2561
+ runtime shutdown protocol to stop the owner.
2562
+
2563
+ CLI and SDK startup retain the exact spawned candidate until health confirms
2564
+ that candidate PID. Early exit, timeout, identity mismatch, cancellation, or a
2565
+ different owner winning the race reclaims only the unsuccessful candidate
2566
+ process tree. Once healthy, the owner detaches normally and is not tied to the
2567
+ creating client process.
2547
2568
 
2548
2569
  ### Daemon startup, conflict, and recovery behavior
2549
2570
 
@@ -2559,18 +2580,18 @@ recovered as `interrupted` with a runtime event; they are not resumed
2559
2580
  automatically. Session and bounded event records remain available for explicit
2560
2581
  reconnect/retry decisions.
2561
2582
 
2562
- Operational guidance:
2563
-
2564
- - use one stable profile for cooperating desktop clients;
2565
- - use a separate `homeDir` or profile for tests, previews, and incompatible
2566
- configurations;
2567
- - test harnesses that auto-start a process daemon must send authenticated
2568
- `runtime.shutdown` (or run `kodax daemon stop --home <dir> --profile <name>`)
2569
- before deleting their temporary home; `runtime.close()` only detaches;
2570
- - KodaX's own Vitest harness also supplies an internal worker-PID marker so a
2571
- forcibly terminated worker cannot strand its test daemon; this is a test-only
2572
- fallback, not a public SDK option or a production idle-shutdown policy;
2573
- - query `kodax daemon status --json` before deciding to restart;
2583
+ Operational guidance:
2584
+
2585
+ - use one stable profile for cooperating desktop clients;
2586
+ - use a separate `homeDir` or profile for tests, previews, and incompatible
2587
+ configurations;
2588
+ - test harnesses that auto-start a process daemon must send authenticated
2589
+ `runtime.shutdown` (or run `kodax daemon stop --home <dir> --profile <name>`)
2590
+ before deleting their temporary home; `runtime.close()` only detaches;
2591
+ - KodaX's own Vitest harness also supplies an internal worker-PID marker so a
2592
+ forcibly terminated worker cannot strand its test daemon; this is a test-only
2593
+ fallback, not a public SDK option or a production idle-shutdown policy;
2594
+ - query `kodax daemon status --json` before deciding to restart;
2574
2595
  - inspect `kodax daemon logs --lines 100` when startup times out;
2575
2596
  - custom endpoints and injected transports are attach-only unless the caller
2576
2597
  implements their owner lifecycle explicitly.
@@ -2606,10 +2627,10 @@ Every `KodaXRuntime` exposes the same service set in inline, Worker, and daemon
2606
2627
  | `mcp` | MCP server CRUD, validation, reload, and tool catalog listing. |
2607
2628
  | `artifacts` | Create/get/delete runtime artifact references for file/image/video inputs. |
2608
2629
  | `status` | Runtime snapshot with sessions, runs, permissions, workflows, and daemon counters. |
2609
- | `diagnostics` | Latest context-budget and tool-exposure decisions for GUI/debug surfaces. |
2610
- | `admin.agentRegistrations` | List/upsert, atomically set `enabled` while preserving the full registration, or remove redacted external-agent registrations. Owner/revision-conditional mutation prevents a stale manager from changing a same-ID replacement. With no plane, list is empty and mutations fail clearly. |
2611
- | `agents` | Check `enabled`, list/describe policy-filtered dispatchable agents, and preflight a selected route. |
2612
- | `agentTasks` | Start/list/get/wait/continue/cancel/reconcile durable external-agent tasks and read their ordered event stream. |
2630
+ | `diagnostics` | Latest context-budget and tool-exposure decisions for GUI/debug surfaces. |
2631
+ | `admin.agentRegistrations` | List/upsert, atomically set `enabled` while preserving the full registration, or remove redacted external-agent registrations. Owner/revision-conditional mutation prevents a stale manager from changing a same-ID replacement. With no plane, list is empty and mutations fail clearly. |
2632
+ | `agents` | Check `enabled`, list/describe policy-filtered dispatchable agents, and preflight a selected route. |
2633
+ | `agentTasks` | Start/list/get/wait/continue/cancel/reconcile durable external-agent tasks and read their ordered event stream. |
2613
2634
 
2614
2635
  ### Sessions, runs, and queueing
2615
2636
 
@@ -2621,14 +2642,14 @@ runtime close.
2621
2642
  ```ts
2622
2643
  const session = await runtime.sessions.create({ title: 'Shared session' });
2623
2644
 
2624
- const sub = runtime.events.subscribe({ sessionId: session.id }, (event) => {
2625
- if (event.type === 'assistant.delta') {
2626
- // Render streaming text in the host UI.
2627
- }
2628
- });
2629
- // Daemon subscriptions establish a remote handshake. Await this before
2630
- // starting work whose first event must not be missed; local subscriptions omit it.
2631
- await sub.ready;
2645
+ const sub = runtime.events.subscribe({ sessionId: session.id }, (event) => {
2646
+ if (event.type === 'assistant.delta') {
2647
+ // Render streaming text in the host UI.
2648
+ }
2649
+ });
2650
+ // Daemon subscriptions establish a remote handshake. Await this before
2651
+ // starting work whose first event must not be missed; local subscriptions omit it.
2652
+ await sub.ready;
2632
2653
 
2633
2654
  const artifact = await runtime.artifacts.create({
2634
2655
  kind: 'image',
@@ -2656,14 +2677,14 @@ sub.close();
2656
2677
 
2657
2678
  `runs.start({ options })` is a DTO boundary in Worker and daemon forms. Do not
2658
2679
  pass process-local objects such as `extensionRuntime`, callbacks, `AbortSignal`,
2659
- LSP services, class instances, or cyclic structures. KodaX rejects them before
2660
- transport instead of silently dropping fields.
2661
-
2662
- `events.beforeToolExecute` is an executable policy hook, not an observation
2663
- callback. It is preserved in embedded mode and rejected in daemon mode; install
2664
- the equivalent policy in the daemon owner instead. The KodaX REPL explicitly
2665
- marks and removes only its own legacy approval hook because Runtime-owned
2666
- permission brokering replaces that hook.
2680
+ LSP services, class instances, or cyclic structures. KodaX rejects them before
2681
+ transport instead of silently dropping fields.
2682
+
2683
+ `events.beforeToolExecute` is an executable policy hook, not an observation
2684
+ callback. It is preserved in embedded mode and rejected in daemon mode; install
2685
+ the equivalent policy in the daemon owner instead. The KodaX REPL explicitly
2686
+ marks and removes only its own legacy approval hook because Runtime-owned
2687
+ permission brokering replaces that hook.
2667
2688
 
2668
2689
  Use the runtime APIs for cross-boundary behavior:
2669
2690
 
@@ -2692,7 +2713,7 @@ Space-style client can subscribe to permission events and answer a request
2692
2713
  created by a REPL-style client.
2693
2714
 
2694
2715
  ```ts
2695
- const sub = runtime.events.subscribe({ type: 'permission.requested' }, (event) => {
2716
+ const sub = runtime.events.subscribe({ type: 'permission.requested' }, (event) => {
2696
2717
  const payload = event.payload;
2697
2718
  if (!payload || typeof payload !== 'object' || typeof payload.id !== 'string') {
2698
2719
  return;
@@ -2701,35 +2722,35 @@ const sub = runtime.events.subscribe({ type: 'permission.requested' }, (event) =
2701
2722
  payload.id,
2702
2723
  { type: 'allow_once' },
2703
2724
  { runId: payload.runId },
2704
- );
2705
- });
2706
- await sub.ready;
2707
- ```
2708
-
2709
- Await `ready` before another client starts work that may request permission;
2710
- this creates an explicit cross-connection ordering boundary. Only the first
2711
- valid response wins. Wrong-run or stale responses are rejected.
2712
- Abort, runtime close, daemon stop, and timeout reject unresolved permission
2713
- requests so tool approval promises do not hang forever.
2714
-
2715
- SDK clients that create a concrete request may pass `toolInput` and
2716
- `executionCwd` to `runtime.permissions.request(...)` in both embedded and
2717
- daemon mode. The Runtime removes raw `toolInput` before publishing the pending
2718
- request, derives the bounded/redacted preview from that concrete input,
2719
- canonicalizes it into a matcher, and returns only opaque `grantSuggestions`.
2720
- A caller-supplied `inputPreview` cannot override this trusted summary.
2721
- `projectRoot`, classifier signals, and other owner-only safety context are
2722
- deliberately not client request fields.
2723
-
2724
- Grant administration is typed through `RuntimePermissionScope` and the
2725
- exported `RuntimePermissionMatcher` union (`exact-command`, `exact-path`, and
2726
- `exact-call`). These types are available from both the package root and
2727
- `@kodax-ai/kodax/runtime`; matcher construction remains Runtime-owned.
2728
- Legacy `allow_always.scope` responses remain accepted for 0.7.x clients, but
2729
- the Runtime narrows them to its concrete candidate and never persists the
2730
- client-provided coarse scope. Legacy persisted grants that lack a Runtime
2731
- matcher remain inspectable and revocable, but never authorize a concrete tool
2732
- call; the user must approve a fresh Runtime-issued exact suggestion.
2725
+ );
2726
+ });
2727
+ await sub.ready;
2728
+ ```
2729
+
2730
+ Await `ready` before another client starts work that may request permission;
2731
+ this creates an explicit cross-connection ordering boundary. Only the first
2732
+ valid response wins. Wrong-run or stale responses are rejected.
2733
+ Abort, runtime close, daemon stop, and timeout reject unresolved permission
2734
+ requests so tool approval promises do not hang forever.
2735
+
2736
+ SDK clients that create a concrete request may pass `toolInput` and
2737
+ `executionCwd` to `runtime.permissions.request(...)` in both embedded and
2738
+ daemon mode. The Runtime removes raw `toolInput` before publishing the pending
2739
+ request, derives the bounded/redacted preview from that concrete input,
2740
+ canonicalizes it into a matcher, and returns only opaque `grantSuggestions`.
2741
+ A caller-supplied `inputPreview` cannot override this trusted summary.
2742
+ `projectRoot`, classifier signals, and other owner-only safety context are
2743
+ deliberately not client request fields.
2744
+
2745
+ Grant administration is typed through `RuntimePermissionScope` and the
2746
+ exported `RuntimePermissionMatcher` union (`exact-command`, `exact-path`, and
2747
+ `exact-call`). These types are available from both the package root and
2748
+ `@kodax-ai/kodax/runtime`; matcher construction remains Runtime-owned.
2749
+ Legacy `allow_always.scope` responses remain accepted for 0.7.x clients, but
2750
+ the Runtime narrows them to its concrete candidate and never persists the
2751
+ client-provided coarse scope. Legacy persisted grants that lack a Runtime
2752
+ matcher remain inspectable and revocable, but never authorize a concrete tool
2753
+ call; the user must approve a fresh Runtime-issued exact suggestion.
2733
2754
 
2734
2755
  ### Config, catalogs, MCP, and Space-style admin APIs
2735
2756
 
@@ -2802,18 +2823,18 @@ import {
2802
2823
  The schema is additive within this patch line. Removing or changing required
2803
2824
  fields requires a protocol version bump.
2804
2825
 
2805
- ### Current verification status
2806
-
2807
- The v0.7.69 release validation covers the runtime migration, the Worker
2808
- isolation follow-ups delivered ahead of their original v0.7.71/v0.7.72
2809
- planning slots, the v0.7.67 external-agent/session additions, the v0.7.68
2810
- experimental Memory Agent surface, and the v0.7.69 A2A/integration/shared-
2811
- daemon delivery:
2812
-
2813
- - Node 20 and Node 22 post-review regression for process cleanup, lock
2814
- ownership, frozen eval inputs, A2A transport, and the built-in manual;
2815
- - root `tsc --noEmit`, package builds, bundle builds, and all 12 public DTS
2816
- entries on Node 20 and Node 22;
2826
+ ### Current verification status
2827
+
2828
+ The v0.7.69 release validation covers the runtime migration, the Worker
2829
+ isolation follow-ups delivered ahead of their original v0.7.71/v0.7.72
2830
+ planning slots, the v0.7.67 external-agent/session additions, the v0.7.68
2831
+ experimental Memory Agent surface, and the v0.7.69 A2A/integration/shared-
2832
+ daemon delivery:
2833
+
2834
+ - Node 20 and Node 22 post-review regression for process cleanup, lock
2835
+ ownership, frozen eval inputs, A2A transport, and the built-in manual;
2836
+ - root `tsc --noEmit`, package builds, bundle builds, and all 12 public DTS
2837
+ entries on Node 20 and Node 22;
2817
2838
  - runtime/daemon/SDK/ACP/REPL integration tests, including process-distinct SDK
2818
2839
  auto-start and multi-client sessions/permissions;
2819
2840
  - Worker Runtime identity, service parity, hard close, capability requirements,
@@ -2821,1525 +2842,1806 @@ daemon delivery:
2821
2842
  - constructed-handler reverse tool RPC, abort bridging, CPU-loop termination,
2822
2843
  respawn, and revoke/dispose queue drainage;
2823
2844
  - context/tool-exposure eval gate;
2824
- - embedded and hosted-daemon external-agent catalog/task parity, durable task
2825
- recovery, policy/credential/artifact gates, and disabled-plane failure;
2826
- - exact session `surface` filtering plus opaque cursor continuation through the
2827
- narrow `/session` API, embedded Runtime, and daemon transport;
2828
- - scoped zero-wait memory recall, read-only deliberate query, trace-only
2829
- receipts, bounded episode review, governed consult-before-write promotion,
2830
- and the self-contained `/experimental-memory` bundle/DTS surface;
2831
- - bidirectional A2A 1.0 discovery/call/serve, durable task replay, no-code
2832
- configuration, local-Agent binding, exact Skill-script admission, SSRF/
2833
- credential boundaries, and the self-contained `/a2a` bundle/DTS surface;
2834
- - split MCP/A2A/Extension configuration, lossless migration, canonical-template
2835
- drift checks, last-known-good hot reload, draining, and restart-required
2836
- classification;
2837
- - atomic shared-daemon observation/resync, durable operations and same-session
2838
- ordering, settings/grant CAS, AskUser/permission transport, credential/Host
2839
- Tool reverse bridges, owner fencing, restart outcomes, and process-distinct
2840
- client/daemon smokes with credential-canary scans;
2841
- - a fresh `0.7.69` tarball consumer importing all 11 public subpaths, creating a
2842
- Worker-hosted session, running the packaged CLI, and checking packaged DTS and
2843
- Worker sidecars; plus a Windows x64 binary/version/sidecar smoke;
2844
- - external fresh npm consumer installation of the `0.7.67` tarball, proving
2845
- Worker isolation and a distinct daemon PID through the published subpath;
2845
+ - embedded and hosted-daemon external-agent catalog/task parity, durable task
2846
+ recovery, policy/credential/artifact gates, and disabled-plane failure;
2847
+ - exact session `surface` filtering plus opaque cursor continuation through the
2848
+ narrow `/session` API, embedded Runtime, and daemon transport;
2849
+ - scoped zero-wait memory recall, read-only deliberate query, trace-only
2850
+ receipts, bounded episode review, governed consult-before-write promotion,
2851
+ and the self-contained `/experimental-memory` bundle/DTS surface;
2852
+ - bidirectional A2A 1.0 discovery/call/serve, durable task replay, no-code
2853
+ configuration, local-Agent binding, exact Skill-script admission, SSRF/
2854
+ credential boundaries, and the self-contained `/a2a` bundle/DTS surface;
2855
+ - split MCP/A2A/Extension configuration, lossless migration, canonical-template
2856
+ drift checks, last-known-good hot reload, draining, and restart-required
2857
+ classification;
2858
+ - atomic shared-daemon observation/resync, durable operations and same-session
2859
+ ordering, settings/grant CAS, AskUser/permission transport, credential/Host
2860
+ Tool reverse bridges, owner fencing, restart outcomes, and process-distinct
2861
+ client/daemon smokes with credential-canary scans;
2862
+ - a fresh `0.7.69` tarball consumer importing all 11 public subpaths, creating a
2863
+ Worker-hosted session, running the packaged CLI, and checking packaged DTS and
2864
+ Worker sidecars; plus a Windows x64 binary/version/sidecar smoke;
2865
+ - external fresh npm consumer installation of the `0.7.67` tarball, proving
2866
+ Worker isolation and a distinct daemon PID through the published subpath;
2846
2867
  - Ubuntu Node 22 Unix-domain-socket daemon gate, including two clients sharing
2847
2868
  one runtime and cross-client permission resolution.
2848
2869
 
2849
- The portable manual gates remain in
2850
- `docs/test-guides/FEATURE_255_v0.7.66_TEST_GUIDE.md`,
2851
- `FEATURE_256_v0.7.71_TEST_GUIDE.md`, and
2852
- `FEATURE_257_v0.7.72_TEST_GUIDE.md` for release-machine verification; the latter
2853
- two filenames retain their original planning slots while their content records
2854
- the v0.7.66 delivery. v0.7.67 adds
2855
- `FEATURE_258_v0.7.67_TEST_GUIDE.md`, `FEATURE_259_v0.7.67_TEST_GUIDE.md`, and
2856
- `FEATURE_261_v0.7.67_TEST_GUIDE.md`; v0.7.69 adds
2857
- `FEATURE_267_v0.7.69_TEST_GUIDE.md`, `FEATURE_268_v0.7.69_TEST_GUIDE.md`, and
2858
- `FEATURE_269_v0.7.69_TEST_GUIDE.md`. The earlier release-preparation Actions
2859
- run is not reused after the severe-fix review. Fresh GitHub Actions run
2860
- `29385073422` passes the Node 20/22 build, bundle, DTS, and full-test matrix plus
2861
- the Node 22 Unix-domain-socket daemon gate. npm publication remains a
2862
- maintainer-owned manual step.
2863
-
2864
- ---
2865
-
2866
- ## 18. External-agent executor plane (FEATURE_258, v0.7.67)
2867
-
2868
- FEATURE_258 lets an SDK host register remote agents without teaching the coding
2869
- runtime a specific A2A, MCP, or HTTP client. The public contracts live in
2870
- `@kodax-ai/kodax/agent`; the host-facing catalog and task services live on
2871
- `@kodax-ai/kodax/runtime`.
2872
-
2873
- ### Ownership rule
2874
-
2875
- An `AgentExecutorFactory` contains functions, so it cannot cross a Worker or
2876
- daemon DTO boundary. Install factories where the Runtime owner executes:
2877
-
2878
- | Desired owner | Supported construction |
2879
- |---|---|
2880
- | Private in-process owner | `createKodaXRuntime({ mode: 'embedded', isolation: 'inline', externalAgents })` |
2881
- | New locally hosted daemon owner | `createKodaXRuntime({ mode: 'daemon', profile: '<unique>', externalAgents })` |
2882
- | Existing daemon | Configure its owner, then attach with `connectKodaXRuntime({ requirements: { externalAgents: true } })`; a client cannot inject factories. |
2883
- | Runtime Worker | The Worker owner must install factories itself; passing `externalAgents` from the parent is rejected. |
2884
-
2885
- When `mode: 'daemon'` and `externalAgents` are supplied, the caller must win a
2886
- new in-process daemon lease. KodaX rejects an already-running profile instead of
2887
- silently replacing its executor configuration. Closing that owner facade shuts
2888
- down the host it created; closing an ordinary attached client only detaches.
2889
-
2890
- ### Minimal owner and task flow
2891
-
2892
- The reference executor below is a contract/conformance adapter. Replace it with
2893
- your own `AgentExecutorFactory` for a real remote protocol.
2894
-
2895
- ```ts
2896
- import {
2897
- createReferenceAgentExecutorFactory,
2898
- type ExternalAgentRegistration,
2899
- } from '@kodax-ai/kodax/agent';
2900
- import { createKodaXRuntime } from '@kodax-ai/kodax/runtime';
2901
-
2902
- const runtime = await createKodaXRuntime({
2903
- mode: 'embedded',
2904
- isolation: 'inline',
2905
- externalAgents: {
2906
- factories: [createReferenceAgentExecutorFactory({
2907
- executorId: 'example-http',
2908
- protocol: 'http',
2909
- })],
2910
- policy: ({ registration, query }) => ({
2911
- allowed: registration.enabled && query.readOnly === true,
2912
- reasons: query.readOnly === true ? [] : ['This host allows read-only dispatch only.'],
2913
- }),
2914
- defaultContext: { actorId: 'desktop-host' },
2915
- },
2916
- });
2917
-
2918
- const registration: ExternalAgentRegistration = {
2919
- agentId: 'external:reviewer',
2920
- displayName: 'Remote Reviewer',
2921
- enabled: true,
2922
- executorId: 'example-http',
2923
- protocol: 'http',
2924
- configurationRevision: 'reviewer-config-v1',
2925
- endpointIdentityHash: 'sha256:replace-with-stable-endpoint-identity',
2926
- skills: ['code-review'],
2927
- inputModalities: ['text'],
2928
- outputModalities: ['text'],
2929
- capabilities: {
2930
- streaming: 'supported',
2931
- durableTasks: 'supported',
2932
- inputRequired: 'conditional',
2933
- cancellation: 'supported',
2934
- artifacts: 'unsupported',
2935
- },
2936
- effects: { remote: 'read', workspace: 'proposal' },
2937
- maxConcurrency: 1,
2938
- };
2939
-
2940
- try {
2941
- await runtime.admin.agentRegistrations.upsert(registration);
2942
-
2943
- const query = {
2944
- actorId: 'desktop-host',
2945
- requiredSkills: ['code-review'],
2946
- readOnly: true,
2947
- } as const;
2948
- const available = await runtime.agents.listDispatchable(query);
2949
- const preflight = await runtime.agents.preflight({
2950
- agentId: 'external:reviewer',
2951
- query,
2952
- expectedConfigurationRevision: registration.configurationRevision,
2953
- });
2954
- if (!preflight.ok) throw new Error(preflight.reasons.join('; '));
2955
-
2956
- const started = await runtime.agentTasks.start({
2957
- agentId: 'external:reviewer',
2958
- objective: 'Review the supplied immutable patch and return cited findings.',
2959
- context: { actorId: 'desktop-host', runId: 'host-run-42' },
2960
- readOnly: true,
2961
- requiredSkills: ['code-review'],
2962
- expectedConfigurationRevision: registration.configurationRevision,
2963
- });
2964
- const terminal = await runtime.agentTasks.wait(started.taskId, 60_000);
2965
- console.log(available, terminal.state, terminal.output, terminal.usage);
2966
- } finally {
2967
- await runtime.close();
2968
- }
2969
- ```
2970
-
2971
- `runtime.agents.enabled` is the cheap external-plane feature check. If it is
2972
- false, catalog queries can still return built-in local agents but no external
2973
- agents; registration and task lists are empty, while point reads and mutations
2974
- fail clearly. Set
2975
- `requirements.externalAgents: true` when absence must abort connection.
2976
-
2977
- ### Service reference
2978
-
2979
- | Surface | Methods | Contract |
2980
- |---|---|---|
2981
- | `runtime.admin.agentRegistrations` | `list`, `upsert`, `setEnabled`, `remove` | Durable owner configuration. `setEnabled` preserves the complete captured executor registration while changing admission. Mutations accept both `expectedConfigurationRevision` and `expectedManagementOwner`; `setEnabled` can also atomically `claimOwner` on an unowned registration and rejects another owner. List results expose `managementOwner` and `credentialConfigured`, never a credential value. The same contract is carried across the daemon transport. With no plane, `list()` is empty and mutations fail clearly. |
2982
- | `runtime.agents` | `enabled`, `listDispatchable`, `describe`, `preflight` | Applies health, capability, effect, concurrency, credential-presence, configuration-revision, and host-policy checks before dispatch. |
2983
- | `runtime.agentTasks` | `start`, `list`, `get`, `events`, `wait`, `sendInput`, `cancel`, `reconcile` | Durable snapshots and append-only events for external tasks. The task keeps the immutable registration/executor binding captured at start. |
2984
-
2985
- `agentTasks.events(taskId, cursor)` uses the last seen numeric event `seq` as its
2986
- cursor and returns events with a greater sequence. `wait()` resolves only at a
2987
- terminal task state and rejects on a positive `timeoutMs` expiry. `sendInput()`
2988
- is valid only while the task reports `input-required` or `auth-required`.
2989
- `reconcile()` asks the bound executor for authoritative remote state after an
2990
- owner restart or uncertain failure.
2991
-
2992
- For external tasks, the built-in stores persist an internal full registration
2993
- snapshot before the public task ledger. It is keyed by Agent ID and revision,
2994
- is never returned by task or daemon APIs, and lets an admitted task keep using
2995
- its original executor route after registration update/removal and Runtime
2996
- restart. The internal form fixes `enabled: true` and omits management ownership
2997
- and health diagnostics. The task's public route summary is validated against that internal
2998
- snapshot before recovery. Terminal task state is durable before the last
2999
- unreferenced snapshot is removed; startup cleans crash-window orphans.
3000
-
3001
- Custom `AgentExecutorPlaneStore` implementations should implement
3002
- `loadTaskRegistrationSnapshots()` and `saveTaskRegistrationSnapshots()` as a
3003
- pair and give one Runtime exclusive write ownership of that store. Omitting
3004
- both remains compatible, but restart recovery then succeeds only while the
3005
- exact current registration still exists. Store only non-secret executor config
3006
- or secret references in `executorConfig`/`credentialRef`; the broker resolves
3007
- the current referenced credential just in time, so removing a registration is
3008
- not equivalent to revoking that credential at its issuer.
3009
-
3010
- The owner plane has a terminal close contract. Closing it rejects every pending
3011
- `wait()` (including a wait without `timeoutMs`), disposes its executor instances,
3012
- and makes subsequent registration, catalog, preflight, and task calls reject
3013
- with `Agent executor plane is closed.` One overall deadline covers admitted
3014
- work plus executor disposal: the default is 30 seconds, and direct
3015
- `createAgentExecutorPlane()` hosts may supply a positive finite
3016
- `closeTimeoutMs`. A timeout rejects visibly even though already-admitted cleanup
3017
- may finish in the background. Repeated `close()` calls are safe. SDK hosts
3018
- should stop accepting work before closing the owner and must not retain a plane
3019
- service as a reusable handle after Runtime shutdown.
3020
-
3021
- Restricted Workflow scripts use the same route as direct SDK calls. Both
3022
- `wf.spawnAgent()` and `wf.runAgent()` validate and forward
3023
- `target: { agentId, expectedConfigurationRevision? }`; `phase` is forwarded as
3024
- well. A blank ID or revision is rejected at the script boundary instead of
3025
- silently falling back to the native child backend.
3026
-
3027
- ### Credential, artifact, and failure boundaries
3028
-
3029
- - Put only a `credentialRef` in a registration. Resolve secret material through
3030
- `AgentCredentialBroker.withCredential()`; do not place tokens in
3031
- `executorConfig`, events, diagnostics, or task output.
3032
- - Remote artifacts are denied by default. Supply `artifactPolicy` to authorize
3033
- each artifact before it materializes in the host boundary.
3034
- - External agents may declare workspace effect `none` or `proposal`; direct
3035
- workspace mutation is intentionally not a valid external registration.
3036
- - Use `expectedConfigurationRevision` for dispatch. For registration mutations,
3037
- compare both it and `expectedManagementOwner` so a same-revision ownership
3038
- change cannot be overwritten from an earlier catalog read. Config managers
3039
- should set a stable `managementOwner`; they may atomically claim an unowned
3040
- legacy registration while disabling it, but must not mutate a registration
3041
- owned by another manager.
3042
- - Treat `configurationRevision` as the stable identity of immutable execution
3043
- content, not as a small counter. The same content may deterministically reuse
3044
- the same revision across remove/re-add or Runtime restart, but different
3045
- endpoint, protocol, executor/auth config, capabilities, effects, Skills,
3046
- modalities, or resource limits must never reuse it. Built-in A2A
3047
- configuration derives it from content.
3048
- - A remote start followed by uncertain local persistence is recorded as
3049
- `unknown` with its executor reference preserved; reconcile it rather than
3050
- blindly starting a duplicate. Stable idempotency keys protect retries.
3051
- - The durable plane records provider-reported usage when available. It never
3052
- invents missing token or cost fields.
3053
-
3054
- For a production adapter, implement `preflight?`, `start`, `events`, `get`,
3055
- `sendInput`, `cancel`, `reconcile`, and `dispose` on `AgentExecutor`. The factory
3056
- receives `withCredential()` and `authorizeArtifact()` callbacks so protocol code
3057
- cannot bypass the host's secret/artifact policies.
3058
-
3059
- ---
3060
-
3061
- ## 19. Session surface filtering and cursor pagination (FEATURE_261, v0.7.67)
3062
-
3063
- The narrow session SDK and Runtime facade now share the same listing semantics:
3064
- `surface` is an exact filter applied before `limit`, and `cursor` is an opaque
3065
- continuation token carried by each returned summary.
3066
-
3067
- ### Narrow `/session` API
3068
-
3069
- ```ts
3070
- import { listSessions, type SessionSummary } from '@kodax-ai/kodax/session';
3071
-
3072
- const all: SessionSummary[] = [];
3073
- let cursor: string | undefined;
3074
- do {
3075
- const page = await listSessions({
3076
- scope: 'user',
3077
- surface: 'partner',
3078
- limit: 50,
3079
- ...(cursor ? { cursor } : {}),
3080
- });
3081
- all.push(...page);
3082
- cursor = page.length === 50 ? page.at(-1)?.cursor : undefined;
3083
- } while (cursor);
3084
- ```
3085
-
3086
- ### Embedded/daemon Runtime API
3087
-
3088
- ```ts
3089
- const first = await runtime.sessions.list({ surface: 'acp', limit: 25 });
3090
- const nextCursor = first.at(-1)?.cursor;
3091
- const second = nextCursor
3092
- ? await runtime.sessions.list({ surface: 'acp', limit: 25, cursor: nextCursor })
3093
- : [];
3094
- ```
3095
-
3096
- The filter also composes with `projectRoot`, `scope`, `includeArchived`, `before`,
3097
- and `tag`. Treat cursors as opaque: do not parse them, compare them to session
3098
- IDs, or manufacture them. An invalid cursor produces an empty page on the narrow
3099
- session API. A page shorter than the requested limit is terminal; a full page
3100
- may continue with its last item's cursor.
3101
-
3102
- The interactive `kodax -r` picker is a consumer of these session semantics, not
3103
- a separate SDK API. Headless hosts should build their own UI on `listSessions()`
3104
- or `runtime.sessions.list()` and resume by the selected full session ID.
3105
-
3106
- ---
3107
-
3108
- ## 20. Cost-disciplined workflow routing and telemetry (FEATURE_259, v0.7.67)
3109
-
3110
- FEATURE_259 adds a public cost/quality contract without making the authoring LLM
3111
- choose provider-specific model names. The SDK host maps semantic tiers to routes;
3112
- workflow/child briefs request intent.
3113
-
3114
- ### Configure tiers and bounded concurrency
3115
-
3116
- ```ts
3117
- import { join } from 'node:path';
3118
- import { runKodaX } from '@kodax-ai/kodax/coding';
3119
-
3120
- const workflowRunsBaseDir = join(process.cwd(), '.kodax-host', 'workflow-runs');
3121
- const result = await runKodaX({
3122
- provider: 'zai-coding',
3123
- model: 'glm-5.2',
3124
- agentMode: 'amaw',
3125
- workflowRunsBaseDir,
3126
- modelTiers: {
3127
- fast: { provider: 'deepseek', model: 'deepseek-v4-flash' },
3128
- deep: { provider: 'zai-coding', model: 'glm-5.2' },
3129
- },
3130
- workflow: { maxConcurrency: 3 },
3131
- events: {
3132
- onWorkflowAgentDigest: ({ runId, event }) => {
3133
- if (event.type === 'agent_completed' || event.type === 'agent_unverified'
3134
- || event.type === 'agent_failed') {
3135
- console.log(runId, event.data);
3136
- }
3137
- },
3138
- },
3139
- }, 'Review this change using scoped evidence packets.');
3140
-
3141
- console.log(result.lastText);
3142
- ```
3143
-
3144
- Tier rules are deliberately small:
3145
-
3146
- - `fast`: mechanical read-only lookup. A write child is ineligible and safely
3147
- inherits the parent route.
3148
- - `balanced`: ordinary implementation/investigation/review; uses the parent
3149
- route, so there is no separate `balanced` mapping.
3150
- - `deep`: architecture, adversarial verification, severity calibration, and
3151
- final synthesis.
3152
- - An unconfigured or selector-shadowed tier inherits the appropriate explicit,
3153
- specialist, or parent route and records why; it does not silently claim the
3154
- requested route was applied.
3155
-
3156
- The workflow authoring contract also supports `scopeSummary`, `constraints`,
3157
- `evidenceRefs`, `verification`, `readOnly`, `outputSchema`, and `terseResult`.
3158
- Use those fields to transfer a compact immutable packet instead of asking every
3159
- child to rediscover the same repository context.
3160
-
3161
- ### Live route facts
3162
-
3163
- Terminal `WorkflowEvent.data` may include `requestedTier`, `tierOutcome`,
3164
- `providerSource`, `modelSource`, initial/final provider and model,
3165
- `fallbackReason`, `iterations`, `durationMs`, `usage`, and `digestUsage`.
3166
- Treat absent fields as unknown. In particular, KodaX does not fabricate usage
3167
- for external executors that did not report it.
3168
-
3169
- Direct child-dispatch consumers receive the typed `KodaXChildAgentResult.routeFacts`
3170
- surface, including resolved effort and input/cache-read/output/digest token
3171
- breakdown when known. Inline workflow consumers can subscribe to the raw
3172
- `onWorkflowAgentDigest` event as above; GUI progress remains available through
3173
- `onWorkflowProcessEvent` / `runtime.workflows`.
3174
-
3175
- ### Durable efficiency report
3176
-
3177
- When `workflowRunsBaseDir` is supplied, every terminal workflow writes:
3178
-
3179
- ```text
3180
- <workflowRunsBaseDir>/<runId>/run.json
3181
- <workflowRunsBaseDir>/<runId>/events.jsonl
3182
- <workflowRunsBaseDir>/<runId>/artifacts/*.json
3183
- ```
3184
-
3185
- `run.json.efficiencyReport` includes:
3186
-
3187
- - total/input/cache-read/output/digest tokens and wall-clock duration;
3188
- - total starts, child turns, starts by `role/tier`, and primary-review starts;
3189
- - duplicate primary packet reads plus verification/synthesis packet reads;
3190
- - review/fix/re-review waves and structured review quality-gate outcomes;
3191
- - `tokenCoverage.ok` plus missing local task IDs;
3192
- - `excludedExternalTaskIds` for external tasks whose executor reported no usage.
3193
-
3194
- Do not interpret `totalModelTokens: 0` as free execution unless
3195
- `tokenCoverage.ok` is true and the relevant external task IDs are not excluded.
3196
- The report is an audit/optimization artifact; correctness still comes from the
3197
- workflow's structured findings, verification results, and quality gates.
3198
-
3199
- ---
3200
-
3201
- ## 21. Experimental governed memory — `/experimental-memory` (FEATURE_260, v0.7.68)
3202
-
3203
- KodaX has one durable memory plane: the F228 Memory Control Plane. FEATURE_260
3204
- adds a thin, opt-in agent/session API over that plane; it does not add a second
3205
- database, filesystem memory actions, a resident memory specialist, or online
3206
- self-modification.
3207
-
3208
- Top-level `runKodaX()` coding runs wire this lifecycle automatically. They build
3209
- an exact-scoped memory pack at session start, keep passive recall off the
3210
- blocking hot path, expose `memory_recall` only when the memory session starts,
3211
- record bounded observations/outcomes, and close the session at the run boundary.
3212
- Use the direct SDK below when a custom host needs to own those boundaries.
3213
-
3214
- ### Minimal direct session
3215
-
3216
- ```ts
3217
- import { createMemoryControlPlane } from '@kodax-ai/kodax/agent';
3218
- import { createMemoryAgent } from '@kodax-ai/kodax/experimental-memory';
3219
-
3220
- const identity = {
3221
- tenantId: 'tenant:acme',
3222
- workspaceId: 'workspace:desktop',
3223
- userId: 'user:42',
3224
- agentId: 'agent:reviewer',
3225
- projectId: 'project:kodax',
3226
- sessionId: 'session:20260712',
3227
- };
3228
-
3229
- const controlPlane = createMemoryControlPlane({
3230
- cwd: process.cwd(),
3231
- identity,
3232
- projectDocs: [],
3233
- discoverSkills: false,
3234
- });
3235
- const memory = createMemoryAgent({ controlPlane });
3236
- const session = await memory.startSession({
3237
- identity,
3238
- objective: 'Review the runtime shutdown change',
3239
- });
3240
-
3241
- const immediate = session.recall({
3242
- decisionRevision: 'decision:1',
3243
- objective: 'Review the runtime shutdown change',
3244
- decisionContext: 'Choosing the first verification step',
3245
- decisionIntent: 'runtime shutdown regression',
3246
- throughSequence: 0,
3247
- });
3248
- const deliberate = await session.query({
3249
- decisionRevision: 'decision:2',
3250
- need: 'What uncommon daemon cleanup failure happened before?',
3251
- throughSequence: 0,
3252
- });
3253
-
3254
- await session.complete({
3255
- status: 'succeeded',
3256
- summary: 'Shutdown state replacement verified',
3257
- evidence: [],
3258
- });
3259
- await session.close();
3260
- ```
3261
-
3262
- `recall()` is synchronous: exact observations/hints can be returned immediately,
3263
- while an optional semantic prefetch finishes for a later matching decision.
3264
- `query()` is deliberate and read-only; one distinct query is admitted per
3265
- decision epoch, and the result is bounded to at most three prompt-safe hints and
3266
- 512 estimated tokens. `undefined` means there is no governed reminder to inject.
3267
-
3268
- ### Evidence, tracing, and persistence boundaries
3269
-
3270
- - Recalled content is low-authority evidence. Current repository/config/runtime
3271
- facts must still come from current tools or host state.
3272
- - `observe()` accepts monotonic, evidence-linked observations and rejects secret
3273
- material. `rewind()` removes observations after a sequence boundary.
3274
- - `complete()` can emit an Outcome Digest through `persistOutcomeDigest` and run
3275
- bounded episode review through `reviewEpisode`; cancellation creates neither.
3276
- - `onTrace` receives policy-versioned `MemoryDecisionReceipt` metadata that links
3277
- candidates, selection, injection, and later outcome influence. Receipts are
3278
- trace-only and contain no hidden reasoning.
3279
- - Durable memory mutation remains owned by F228's
3280
- proposal/preview/fingerprint/apply flow. `/memory` is the CLI governance
3281
- surface; direct file/shell writes to managed memory roots are denied.
3282
- - Identity/applicability matching is exact and fail-closed. Hosts should supply
3283
- stable tenant/workspace/user/agent/project/session identifiers and must not put
3284
- credentials into those identifiers.
3285
-
3286
- The subpath is experimental and ESM-only. Treat its exported types as opt-in
3287
- v0.7.x contracts; keep persistence and product policy behind your own adapter.
3288
-
3289
- ---
3290
-
3291
- ## 22. Bidirectional A2A 1.0 — `/a2a` (FEATURE_267, v0.7.69)
3292
-
3293
- `@kodax-ai/kodax/a2a` is the protocol edge for the A2A 1.0 JSON-RPC/SSE
3294
- profile. It composes the protocol-neutral F258 executor plane with the Runtime
3295
- facade; `/agent` and `/coding` remain free of A2A wire types and dependencies.
3296
-
3297
- ### Call an A2A Agent through F258
3298
-
3299
- Discovery is an explicit host action. The URL must match `allowedOrigins`, and
3300
- the default safe transport pins the validated DNS address for each connection
3301
- and independently revalidates redirects, rejects public plain HTTP, bounds
3302
- time/body/redirects, and strips authorization on a cross-origin redirect. A
3303
- custom `fetch` option is a trusted transport override: the embedder then owns
3304
- equivalent DNS-to-connection binding in that transport or proxy.
3305
-
3306
- The selected interface must remain on the Card's trusted origin. KodaX parses
3307
- typed Card-level and Skill-level security declarations: requirement objects are
3308
- alternatives (OR), every scheme inside one object is conjunctive (AND), and an
3309
- empty requirement is anonymous. A configured credential is used only when one
3310
- complete requirement is satisfiable; protected Skills that the configured
3311
- profile cannot satisfy are not advertised to the Runtime catalog.
3312
-
3313
- The built-in profiles are HTTP Bearer and OAuth 2.0 Client Credentials. The
3314
- OAuth profile pins the Card scheme, issuer, exact token endpoint, client ID,
3315
- secret reference, scopes, optional RFC 8707 resource, and client authentication
3316
- method. The external Authorization Server—not the Agent and not KodaX—issues
3317
- the access token. KodaX resolves the client secret only for refresh, keeps an
3318
- expiring token in process memory, coalesces refreshes, and retries one RPC once
3319
- with a fresh token after `401`. Card, Agent RPC, and token endpoints remain
3320
- separate safe-fetch trust boundaries, so a remote Agent cannot redirect a task
3321
- payload to the token origin. API key, Basic, interactive OAuth, OIDC, mTLS, and
3322
- multi-scheme AND requirements fail explicitly in the built-in client.
3323
-
3324
- ```ts
3325
- import {
3326
- createA2AAgentExecutorFactory,
3327
- discoverA2ARegistration,
3328
- } from '@kodax-ai/kodax/a2a';
3329
- import { createKodaXRuntime } from '@kodax-ai/kodax/runtime';
3330
-
3331
- const client = {
3332
- networkPolicy: {
3333
- // Card/RPC and OAuth token endpoints are separate trust boundaries.
3334
- allowedOrigins: ['https://reviewer.example', 'https://identity.example'],
3335
- allowPrivateAddresses: false,
3336
- requestTimeoutMs: 10_000,
3337
- maxResponseBytes: 1_048_576,
3338
- maxRedirects: 2,
3339
- },
3340
- pollIntervalMs: 500,
3341
- } as const;
3342
-
3343
- const discovered = await discoverA2ARegistration({
3344
- agentId: 'external:a2a-reviewer',
3345
- agentCardUrl: 'https://reviewer.example/.well-known/agent-card.json',
3346
- credentialRef: 'a2a/reviewer',
3347
- effects: { remote: 'read' },
3348
- }, client);
3349
-
3350
- const runtime = await createKodaXRuntime({
3351
- mode: 'embedded',
3352
- isolation: 'inline',
3353
- externalAgents: {
3354
- factories: [createA2AAgentExecutorFactory(client)],
3355
- credentialBroker: {
3356
- async withCredential(ref, use) {
3357
- const value = ref === 'a2a/reviewer'
3358
- ? process.env.A2A_REVIEWER_TOKEN
3359
- : ref === 'a2a/reviewer-client-secret'
3360
- ? process.env.A2A_REVIEWER_CLIENT_SECRET
3361
- : undefined;
3362
- if (!value) throw new Error(`Missing credential for reference: ${ref}.`);
3363
- return use(value);
3364
- },
3365
- },
3366
- policy: ({ registration }) => ({ allowed: registration.effects.remote === 'read' }),
3367
- defaultContext: { actorId: 'a2a-host' },
3368
- },
3369
- });
3370
-
3371
- await runtime.admin.agentRegistrations.upsert(discovered.registration);
3372
- const started = await runtime.agentTasks.start({
3373
- agentId: discovered.registration.agentId,
3374
- objective: 'Review this change and return cited findings.',
3375
- context: { actorId: 'a2a-host' },
3376
- readOnly: true,
3377
- expectedConfigurationRevision: discovered.registration.configurationRevision,
3378
- });
3379
- const terminal = await runtime.agentTasks.wait(started.taskId, 60_000);
3380
- ```
3381
-
3382
- For OAuth, replace the legacy `credentialRef` input with the structured form;
3383
- the same F258 `credentialBroker` must resolve `clientSecretRef`. The shared
3384
- network policy must admit both origins, while each Card, RPC, and token request
3385
- is still narrowed to its own exact origin:
3386
-
3387
- ```ts
3388
- const discovered = await discoverA2ARegistration({
3389
- agentId: 'external:a2a-reviewer',
3390
- agentCardUrl: 'https://reviewer.example/.well-known/agent-card.json',
3391
- authentication: {
3392
- type: 'oauth2-client-credentials',
3393
- scheme: 'enterprise-oauth',
3394
- issuer: 'https://identity.example/',
3395
- tokenUrl: 'https://identity.example/oauth/token',
3396
- clientId: 'kodax-reviewer',
3397
- clientSecretRef: 'a2a/reviewer-client-secret',
3398
- scopes: ['a2a.invoke'],
3399
- resource: 'https://reviewer.example/',
3400
- clientAuthentication: 'client-secret-basic',
3401
- },
3402
- effects: { remote: 'read' },
3403
- }, client);
3404
- ```
3405
-
3406
- The executor supports durable task start/get, input continuation, cancel,
3407
- reconcile, SSE events, and polling fallback. An ambiguous start is not retried
3408
- automatically. A `credentialRef` is resolved just in time by the F258 broker;
3409
- the registration, task store, and diagnostics never contain the credential.
3410
- Authenticated SSE uses that same broker. JSON-RPC ID/version and task/context
3411
- correlation are validated before an event is accepted; if a stream ends
3412
- normally before a terminal snapshot, the executor resumes bounded polling.
3413
- Streamed `artifactUpdate` chunks are accumulated by artifact ID according to
3414
- `append`, and direct Message file Parts are preserved as authorized artifact
3415
- references.
3416
-
3417
- ### Built-in configured path (no host code)
3418
-
3419
- The CLI product path stores one user document at
3420
- `~/.kodax/integrations/a2a.json` and uses the same F258 plane:
3421
-
3422
- ```bash
3423
- kodax a2a add reviewer https://reviewer.example/.well-known/agent-card.json \
3424
- --credential-env A2A_REVIEWER_TOKEN --effect read
3425
- kodax a2a test reviewer
3426
- kodax a2a call reviewer "Review this document"
3427
- ```
3428
-
3429
- The no-code OAuth path stores only the environment-variable name for the client
3430
- secret. It can be staged disabled and hot-activated later:
3431
-
3432
- ```bash
3433
- export A2A_REVIEWER_CLIENT_SECRET='provisioned-out-of-band'
3434
- # PowerShell: $env:A2A_REVIEWER_CLIENT_SECRET='provisioned-out-of-band'
3435
- # PowerShell: use one line or replace each trailing \ with a backtick.
3436
- kodax a2a add reviewer https://reviewer.example/.well-known/agent-card.json \
3437
- --disabled --effect read --oauth-scheme enterprise-oauth \
3438
- --oauth-issuer https://identity.example/ \
3439
- --oauth-token-url https://identity.example/oauth/token \
3440
- --oauth-client-id kodax-reviewer \
3441
- --oauth-client-secret-env A2A_REVIEWER_CLIENT_SECRET \
3442
- --oauth-scope a2a.invoke --oauth-resource https://reviewer.example/
3443
- kodax a2a enable reviewer
3444
- kodax a2a disable reviewer
3445
- ```
3446
-
3447
- Embedded CLI Runtimes and the user-owned daemon automatically reconcile these
3448
- entries as `external:<name>`. Discovery/update failure retains that entry's
3449
- last-known-good registration; another entry can still update. The environment
3450
- broker resolves `credentialEnv` only at call time. Automatic Runtime
3451
- registration accepts public HTTPS and exact loopback targets; explicit private
3452
- network access remains an operator action on the direct CLI/SDK path.
3453
-
3454
- `enabled` is desired state in `a2a.json`, not a fabricated cross-process live
3455
- flag. `a2a list` reports configured entries and that desired state. The owning
3456
- Runtime's `admin.agentRegistrations.list()` is authoritative for applied
3457
- registrations. Automatic reconciliation handles disables/removals first,
3458
- skips unchanged peers, performs no Card or token request for disabled entries,
3459
- and rediscovers before re-enable. Once the owning Runtime observes and applies
3460
- the revision, disable blocks all new starts, including an explicit
3461
- `external:<name>` target, but does not cancel or break an already admitted task.
3462
- The CLI mutation returning is not cross-process acknowledgement. A failed
3463
- activation remains retryable through the owning
3464
- `ConfiguredA2ARuntimeHandle.reload()` even when the disk revision is unchanged;
3465
- the passive `kodax integrations reload` command validates only its own process.
3466
-
3467
- `kodax a2a test` performs Card discovery and security planning only. It never
3468
- requests an OAuth access token; token acquisition starts at `a2a call` or the
3469
- first Runtime dispatch.
3470
-
3471
- Inbound publication is also no-code:
3472
-
3473
- ```bash
3474
- export KODAX_A2A_TOKEN='replace-with-a-long-random-token'
3475
- # PowerShell: $env:KODAX_A2A_TOKEN='replace-with-a-long-random-token'
3476
- kodax a2a expose # Runtime default Agent
3477
- kodax a2a expose document-agent # ~/.kodax/agents/document-agent.md
3478
- kodax a2a serve --port 8765
3479
- ```
3480
-
3481
- The fixed token above is the compatibility profile. For dynamic production
3482
- tokens, configure KodaX as an OAuth Resource Server and point it at an external
3483
- issuer:
3484
-
3485
- ```bash
3486
- kodax a2a expose document-agent --auth oauth2-jwt \
3487
- --oauth-scheme enterprise-oauth \
3488
- --oauth-issuer https://identity.example/ \
3489
- --oauth-audience https://kodax.example/a2a \
3490
- --oauth-jwks-url https://identity.example/.well-known/jwks.json \
3491
- --oauth-token-url https://identity.example/oauth/token \
3492
- --oauth-metadata-url https://identity.example/.well-known/oauth-authorization-server \
3493
- --required-scope a2a.invoke
3494
- kodax a2a serve --port 8765
3495
- ```
3496
-
3497
- The Authorization Server authenticates clients, provisions client IDs/secrets,
3498
- issues/rotates/revokes tokens and, for JWT access tokens, signs them and
3499
- publishes metadata/JWKS. The calling A2A
3500
- client obtains a token out of band or with Client Credentials and sends it in
3501
- the Bearer header. KodaX validates JWT type, asymmetric signature, issuer,
3502
- audience, lifetime, subject, and required scopes before task lookup, then maps
3503
- `sub` to the A2A principal. Missing/invalid credentials return `401`; a valid
3504
- token without the required scope returns `403 insufficient_scope`. KodaX does
3505
- not hold the issuer signing key or expose token, refresh, client-registration,
3506
- login, or consent endpoints. Opaque-token introspection and mTLS deployments
3507
- must use a host authentication adapter or reverse proxy. Offline JWT/JWKS
3508
- validation also cannot observe immediate per-token revocation: use short access
3509
- token lifetimes, signing-key rotation, or an introspecting proxy/adapter when
3510
- that property is required.
3511
-
3512
- #### Upgrade retained pre-realm tasks
3513
-
3514
- Realm-aware task ownership intentionally has no normal-request legacy fallback:
3515
- an authority switch must never adopt tasks merely because it reuses a subject.
3516
- If a v0.7.70 task store must remain addressable after upgrading, stop the A2A
3517
- server and first inspect an exact-owner migration plan:
3518
-
3519
- ```bash
3520
- kodax a2a migrate-tasks
3521
- kodax a2a migrate-tasks --apply --confirm-server-stopped
3522
-
3523
- # OAuth identity is token-specific, so provide the known historical subject.
3524
- kodax a2a migrate-tasks --subject trusted-orchestrator
3525
- ```
3526
-
3527
- The configured Bearer profile supplies its fixed `principalId`; OAuth requires
3528
- `--subject`. Dry-run does not rewrite `tasks.json`. Apply rekeys only exact
3529
- matches, preserves unmatched records, and refuses a live task-store owner.
3530
- Custom SDK hosts can plan multiple known owners without exposing raw tokens:
3531
-
3532
- ```ts
3533
- import { migrateA2ALegacyTaskOwners } from '@kodax-ai/kodax/a2a';
3534
-
3535
- const mappings = [{
3536
- securityRealm: 'oauth2-jwt:https://identity.example/',
3537
- subject: 'trusted-orchestrator',
3538
- }] as const;
3539
- const plan = migrateA2ALegacyTaskOwners({
3540
- dataDir: '/var/lib/kodax/a2a', mappings, apply: false,
3541
- });
3542
-
3543
- // After the host/operator verifies the plan:
3544
- if (plan.matchedLegacyTaskCount > 0) {
3545
- migrateA2ALegacyTaskOwners({
3546
- dataDir: '/var/lib/kodax/a2a', mappings, apply: true,
3547
- });
3548
- }
3549
- ```
3550
-
3551
- The SDK also accepts `tenant` when a custom authentication adapter historically
3552
- returned one. Two mappings that claim the same legacy owner for different
3553
- realms are ambiguous and rejected; split or guessed ownership is never applied.
3554
-
3555
- `a2a serve` resolves its Runtime provider in this order: explicit CLI option,
3556
- environment, core configuration, then the built-in default. Provider-compatible
3557
- model selection follows the normal hosted Runtime rule. A selected Markdown
3558
- Agent may declare its own validated `provider`; remote A2A input cannot choose
3559
- or override provider, model, reasoning, profile, workspace, or tools.
3560
-
3561
- `expose` validates a named user Markdown Agent before writing its reference.
3562
- `serve` loads configured MCP and Extensions before it resolves the execution
3563
- binding or opens a socket. Native workspace read tools are admitted by
3564
- workspace access; writes, narrow Extension Tools, MCP capabilities, subagents,
3565
- and isolated Skill scripts require their corresponding exact `toolPolicy`
3566
- authority. Internal Skills come from `~/.kodax/skills`, `~/.agents/skills`,
3567
- plugins, and built-ins; public Agent Card skills are a separate explicit
3568
- projection and never reveal the private Skill inventory.
3569
-
3570
- The running server pins Agent, Skill, workspace, tool registration, process and
3571
- store revisions. Card/auth/limits can hot reload; execution-authority changes
3572
- require an explicit restart. Managed contexts live below
3573
- `~/kodax_a2a_server_workspace/<runtime-profile>/contexts/<context-key>/`. Exact Skill scripts require
3574
- `process: isolated`, an admitted `scripts/...` path, and a passing
3575
- `kodax sandbox doctor`; KodaX never falls back to an unsandboxed shell.
3576
-
3577
- Every concrete file reached by `read`, `grep`, or `glob` is checked against the
3578
- bound workspace. Child runs inherit ceilings for native reads, tools, Skills,
3579
- and Skill scripts; they cannot expand the parent's admitted authority.
3580
-
3581
- ### Publish one KodaX Agent
3582
-
3583
- Publication is host-owned and opt-in. The public card describes only the
3584
- configured Agent, media types, and skills. Authentication runs before task
3585
- lookup; authorization runs per operation; task visibility is principal-scoped.
3586
-
3587
- ```ts
3588
- import {
3589
- createBearerEnvA2AAuthentication,
3590
- createKodaXA2AServer,
3591
- } from '@kodax-ai/kodax/a2a';
3592
- import { createKodaXRuntime } from '@kodax-ai/kodax/runtime';
3593
-
3594
- const runtime = await createKodaXRuntime({ mode: 'embedded', isolation: 'inline' });
3595
- const server = createKodaXA2AServer({
3596
- runtime,
3597
- dataDir: '/var/lib/kodax/a2a',
3598
- agent: {
3599
- name: 'KodaX Reviewer',
3600
- description: 'Reviews bounded code changes.',
3601
- version: '1.0.0',
3602
- publicBaseUrl: 'https://kodax.example',
3603
- skills: [{ id: 'review', name: 'Review', description: 'Review code.', tags: ['code'] }],
3604
- inputModes: ['text/plain'],
3605
- outputModes: ['text/plain'],
3606
- },
3607
- authentication: createBearerEnvA2AAuthentication({
3608
- type: 'bearer-env',
3609
- tokenEnv: 'KODAX_A2A_TOKEN',
3610
- principalId: 'trusted-orchestrator',
3611
- }),
3612
- async authorize({ principal }) { return principal.scopes.includes('a2a:invoke'); },
3613
- limits: {
3614
- maxRequestBytes: 1_048_576,
3615
- maxPartBytes: 524_288,
3616
- maxConcurrentTasks: 8,
3617
- maxTaskWaitMs: 30_000,
3618
- maxActiveTasksPerPrincipal: 8,
3619
- maxRetainedTasksPerPrincipal: 64,
3620
- maxEventsPerTask: 1_000,
3621
- maxEventBytesPerTask: 16_777_216,
3622
- maxWorkspaceBytesPerContext: 1_073_741_824,
3623
- },
3624
- });
3625
-
3626
- // Development only: the built-in listener refuses non-loopback hosts.
3627
- const localBaseUrl = await server.listen({ hostname: '127.0.0.1', port: 0 });
3628
- ```
3629
-
3630
- Production hosts route `GET /.well-known/agent-card.json` and canonical
3631
- JSON-RPC `POST /a2a` to `server.handle(request)` behind their own TLS
3632
- terminator. `POST /` remains an accepted compatibility alias. `listen()` waits for durable recovery before
3633
- it resolves. A host that wires `handle()` directly may explicitly await
3634
- `server.whenReady()` before it starts accepting traffic; `handle()` also waits
3635
- for the same recovery promise. The durable edge store supports get/list,
3636
- continuation, cancellation, ordered SSE subscription, and surviving Runtime-run
3637
- reattachment after an edge restart. Push notifications, A2A 0.3, gRPC, and
3638
- HTTP+JSON are not advertised; unsupported push methods return the standard
3639
- `PushNotificationNotSupportedError`.
3640
-
3641
- Non-streaming `SendMessage` waits at most `maxTaskWaitMs` (30 seconds by
3642
- default). When that bound is reached the response contains the current working
3643
- task; it does not cancel the Runtime run, and clients can continue with
3644
- `GetTask` or `SubscribeToTask`.
3645
-
3646
- When a task enters `INPUT_REQUIRED`, the next accepted input answers the pending
3647
- interaction on the original Runtime run; it does not start a replacement run.
3648
- History length and list filters are validated and bounded. Task listing uses a
3649
- stable opaque cursor, while per-principal retention prunes only the oldest
3650
- terminal records. Terminal subscriptions and failed-start resources are closed
3651
- by their owning lifecycle.
3652
-
3653
- Remote messages are ordinary user inputs. They cannot select provider, model,
3654
- profile, tools, working directory, permission mode, or Runtime configuration.
3655
- URL parts are rejected; inline raw/data parts are bounded and materialized under
3656
- the server-owned data directory. Responses expose final approved output only,
3657
- not system prompts, reasoning deltas, tool payloads, credentials, or local paths.
3658
-
3659
- Generated files are published only through the trusted output broker: a normal
3660
- tool or Extension stages a file in the context's `.kodax-a2a-staging` area, or
3661
- a successfully admitted `run_skill_script` promotes one of its declared
3662
- outputs. The server rechecks that the result is a regular non-symlink file in
3663
- the real bound workspace and applies part-size/output-mode limits before
3664
- inlining it. A declaration from a failed Skill run, an ordinary `write`/`edit`
3665
- elsewhere in the workspace, and a local path in model text never become A2A
3666
- artifacts implicitly.
3667
-
3668
- The normative baseline is A2A repository commit
3669
- `2183794bfb9b67af4aee1be0a0ef726050642873`, protocol `1.0`, with
3670
- `specification/a2a.proto` SHA-256
3671
- `e195bf96ab630c69797851970203e1b2b6b19528f2e9803b7d904b91a5104016`.
3672
-
3673
- ---
3674
-
3675
- ## 23. Shared Coder daemon for Space and IDE hosts (FEATURE_269, v0.7.69)
3676
-
3677
- FEATURE_269 makes one local daemon the source of truth for a Coder profile.
3678
- CLI, Space, IDE, and SDK clients can observe and control the same sessions and
3679
- runs. The transport remains local to the current OS user; it is not a remote
3680
- collaboration protocol. Closing a client detaches that client and does not stop
3681
- the daemon or another client's run.
3682
-
3683
- Partner is deliberately outside this migration. Keep Partner on its existing
3684
- inline callbacks and give it a distinct product data root and sessions root.
3685
- Do not point a Partner inline Runtime at the Coder daemon profile or the Coder
3686
- data root.
3687
-
3688
- ### Connect and fail closed on required capabilities
3689
-
3690
- Space should own the daemon SDK client in Electron Main. Persist a random,
3691
- stable `instanceId` and a separate 32+ character `instanceSecret` per Space
3692
- installation. Store the secret in the OS keychain; never accept either value
3693
- from renderer or model output. `connectKodaXRuntime()` is attach-only unless `autoStart: true`.
3694
- An explicit inline rollback policy blocks auto-start until the owner policy is
3695
- explicitly changed back to daemon.
3696
-
3697
- For Electron, `homeDir` is still the CLI-style base directory, not
3698
- `process.env.KODAX_HOME`. Packaged/asar applications may use `autoStart: true`
3699
- directly; the SDK launches only the daemon child in Electron's Node execution
3700
- mode and does not mutate the application's environment or start a second GUI
3701
- instance. `ELECTRON_RUN_AS_NODE` exists only at the child exec boundary and is
3702
- removed before daemon application code loads, so Bash, MCP, LSP, sandboxed
3703
- commands, and ordinary external processes do not inherit Electron Node mode.
3704
-
3705
- Packaged auto-start requires Electron's `RunAsNode` fuse, which Electron enables
3706
- by default. If an embedder deliberately disables that fuse, the packaged
3707
- executable cannot serve as a detached Node host: start the daemon with an
3708
- ordinary Node/CLI process and use attach-only mode instead. A packaged
3709
- `autoStart: true` timeout includes this fuse requirement in its diagnostic; the
3710
- SDK does not relaunch the GUI or silently fall back to an inline Runtime.
3711
-
3712
- ```ts
3713
- import { connectKodaXRuntime } from '@kodax-ai/kodax/runtime';
3714
-
3715
- const runtime = await connectKodaXRuntime({
3716
- profile: 'coder',
3717
- autoStart: true,
3718
- homeDir: coderRuntimeBaseDir, // owns <coderRuntimeBaseDir>/.kodax
3719
- clientInfo: {
3720
- name: 'kodax-space',
3721
- version: '0.1.32',
3722
- instanceId: spaceInstallationId,
3723
- instanceSecret: await spaceKeychain.readRuntimeClientSecret(),
3724
- },
3725
- capabilities: {
3726
- richEvents: true,
3727
- permissionPrompts: true,
3728
- operationDeduplication: true,
3729
- },
3730
- requirements: {
3731
- operationDeduplication: 1,
3732
- sessionObservation: 1,
3733
- afterTurnInput: 1,
3734
- askUserTransport: 1,
3735
- permissionCas: 1,
3736
- providerCredentialBroker: 1,
3737
- runBoundHostTools: 1,
3738
- coderOwnerFencing: 1,
3739
- crashOutcomeModel: 1,
3740
- coderFeatureMatrix: 1,
3741
- sessionAdmission: 1,
3742
- completeObservationSnapshot: 1,
3743
- connectionLifecycle: 1,
3744
- typedRuntimeEvents: 1,
3745
- daemonSafeRunInput: 1,
3746
- sharedSessionSettings: 1,
3747
- durableRecoveryQueries: 1,
3748
- daemonManagement: 1,
3749
- runtimeAutoModeGuardrail: 3,
3750
- },
3751
- });
3752
- ```
3753
-
3754
- Requirements are server facts, not authorization requests. Check
3755
- `runtime.grantedScopes` before enabling controls. Missing capabilities or
3756
- scopes must disable the affected UI; Space must not silently start inline
3757
- Coder. FEATURE_269 does not advertise `interruptInput`, so an interrupt-only
3758
- product must require `{ interruptInput: 1 }` and fail connection. The supported
3759
- fallback is `delivery: 'after_turn'` only when that is the user's intent.
3760
-
3761
- The v0.7.73 SDK requires `runtimeAutoModeGuardrail:3` automatically for
3762
- `autoStart: true`, even when the caller omits it from `requirements`. If the healthy
3763
- profile daemon advertises v1 or v2, the SDK first requires `daemonManagement:1`,
3764
- takes a revision/owner-policy fenced preflight, and replaces it only when no
3765
- active or queued run, Workflow, Agent turn, pending permission/user input, or
3766
- other logical client exists. A busy or still-older daemon is never stopped: the
3767
- connection rejects with `RuntimeDaemonCapabilityUpgradeError`, whose
3768
- `recoverable` and `restartRequired` fields are `true` and whose optional
3769
- `preflight` explains the blockers. Attach-only connections never mutate daemon
3770
- ownership and must request `runtimeAutoModeGuardrail:1` explicitly when they
3771
- depend only on the v1 owner contract, v2 for bounded input, effective-default
3772
- metadata, structured diagnostics, and speculative-window parity, or v3 for
3773
- opaque exact grant suggestions and concrete permission matchers. Capability
3774
- requirements are minimum versions: v3 satisfies v1/v2, v2 satisfies v1, and an
3775
- older daemon never satisfies a newer requirement.
3776
-
3777
- The `coderFeatureMatrix` capability reports daemon availability for managed
3778
- runs, transcript/session operations, Todo projection, managed tasks, Workflow,
3779
- MCP, Reference External Agent, Memory, and Runtime artifacts. Reference
3780
- External Agent is `false` when the daemon owner did not install its executor
3781
- plane.
3782
-
3783
- The packaged daemon authenticates one local OS-user/profile trust domain with
3784
- a random token stored beside daemon state and a user-only local endpoint. It
3785
- does not issue a different daemon token to each application in v0.7.69. The
3786
- returned scope set is chosen by the host (the packaged host grants the public
3787
- local-user set). `clientInfo.instanceId` is stable attribution for origin and
3788
- operation deduplication. `instanceSecret` proves that a new authenticated
3789
- connection is the same stable client when it resumes that client's credential
3790
- or Host Tool leases; only its hash participates in daemon-owned bridge state.
3791
- Keep all three values in Electron Main. Mutually distrusting processes running
3792
- as the same OS account remain outside this release's threat model.
3793
-
3794
- ### Join atomically and resync after disconnect
3795
-
3796
- `sessions.observe()` installs the live subscription before taking the
3797
- snapshot. Its snapshot contains one authoritative `runtimeId`, cursor,
3798
- `transcriptRevision`, complete transcript, versioned settings, run/queue state,
3799
- queued continuation IDs/order/origin/safe previews, pending permission and
3800
- AskUser requests, and live assistant/thinking/tool/Todo/managed-task
3801
- projection. Run requirements include the current credential/Host Tool
3802
- availability. Listener events are strictly after the returned cursor.
3803
-
3804
- ```ts
3805
- let observedRuntimeId: string | undefined;
3806
- let lastCursor = 0;
3807
-
3808
- async function openCoderSession(sessionId: string) {
3809
- const observation = await runtime.sessions.observe(sessionId, (event) => {
3810
- if (event.seq <= lastCursor) return;
3811
- applyRuntimeEvent(event);
3812
- lastCursor = event.seq;
3813
- });
3814
-
3815
- const { snapshot } = observation;
3816
- const runtimeChanged = observedRuntimeId !== undefined
3817
- && observedRuntimeId !== snapshot.runtimeId;
3818
- observedRuntimeId = snapshot.runtimeId;
3819
- lastCursor = snapshot.cursor;
3820
- replaceSessionProjection(snapshot, { runtimeChanged });
3821
- return observation;
3822
- }
3823
- ```
3824
-
3825
- Subscribe to `runtime.connection` to freeze mutation UI immediately rather
3826
- than waiting for a status poll:
3827
-
3828
- ```ts
3829
- runtime.connection?.subscribe((state) => {
3830
- setCoderConnectionState(state.state, state.reason);
3831
- if (state.state === 'disconnected' && state.reconnectable) {
3832
- scheduleReconnect();
3833
- }
3834
- });
3835
- ```
3836
-
3837
- The SDK reports the current `connectionId`, `runtimeEpoch`, optional
3838
- `journalEpoch`, disconnect reason, and whether a new connection may be
3839
- attempted. It does not transparently replay requests or subscriptions. Space
3840
- creates a replacement Runtime client, checks its new epochs, resumes eligible
3841
- leases, and observes the session again.
3842
-
3843
- On transport failure, Runtime change, expired history, or `resync_required`,
3844
- discard the local derived projection and call `sessions.observe()` again. Do
3845
- not merge a new snapshot into the old projection. The handshake buffer is
3846
- bounded; overflow fails explicitly instead of dropping events. A Runtime
3847
- restart changes `runtimeId`, marks persisted non-terminal runs with a durable
3848
- terminal fact, and closes old in-memory AskUser/permission requests through the
3849
- reset boundary.
3850
-
3851
- ### Durable mutations, stable ordering, and settings CAS
3852
-
3853
- Every durable public control mutation uses an operation envelope. Credential
3854
- and Host Tool register/revoke/supply/complete requests are reverse-bridge
3855
- control frames and are deliberately excluded from the control journal so
3856
- secrets/results are not persisted. They still enter the daemon management
3857
- draining fence: once an atomic stop begins, they fail with typed `conflict` and
3858
- cannot change reverse-bridge state. The SDK creates an operation ID for
3859
- ordinary one-shot calls. A
3860
- product-level retry after a lost response must reuse its own stable operation
3861
- ID; changing its method, payload, resource, or authenticated principal is
3862
- rejected.
3863
-
3864
- ```ts
3865
- const session = await runtime.sessions.create({
3866
- sessionId: stableSpaceSessionId,
3867
- title: 'Shared session',
3868
- surface: 'space-desktop',
3869
- operation: { operationId: loadOrCreatePendingOperationId('space-session-draft-7') },
3870
- });
3871
-
3872
- const operationId = loadOrCreatePendingOperationId('space-run-draft-42');
3873
- const handle = await runtime.runs.start({
3874
- sessionId: session.id,
3875
- input: { type: 'text', text: prompt },
3876
- options: { provider: 'anthropic' },
3877
- operation: { operationId },
3878
- });
3879
-
3880
- const current = await runtime.sessions.getSettingsVersioned(session.id);
3881
- const updated = await runtime.sessions.updateSettingsVersioned(
3882
- session.id,
3883
- { model: 'claude-sonnet-4-5' },
3884
- {
3885
- operationId: loadOrCreatePendingOperationId('space-settings-draft-9'),
3886
- expectedRevision: current.revision,
3887
- },
3888
- );
3889
- ```
3890
-
3891
- Create retries with the same explicit session and operation IDs cannot overwrite
3892
- an existing session. Same-session starts and after-turn inputs receive a durable `sessionOrder`.
3893
- Retries with the same operation ID return the canonical result and do not
3894
- create another run. Settings use compare-and-swap; a stale revision returns a
3895
- structured conflict and must be reloaded, never silently overwritten. The
3896
- shared settings keys are `provider`, `model`, `effort`, `thinking`,
3897
- `reasoningMode`, `permissionMode`, `executionCwd`, `agentMode`, and
3898
- `autoModeEngine`, `autoModeClassifierModel`, `autoModeTimeoutMs`, and
3899
- `autoModeSpeculativeWindowMs`.
3900
-
3901
- ```ts
3902
- const queued = await runtime.runs.submitInput({
3903
- sessionId: session.id,
3904
- afterRunId: handle.runId,
3905
- delivery: 'after_turn',
3906
- input: { type: 'text', text: 'Also update the tests.' },
3907
- operation: { operationId: loadOrCreatePendingOperationId('space-input-17') },
3908
- });
3909
-
3910
- if (!queued.accepted) {
3911
- // stale_run or unsupported_capability: show the factual result.
3912
- }
3913
- ```
3914
-
3915
- Run status exposes acceptance/start/queue times, authenticated origin,
3916
- `sessionOrder`, and a single terminal fact. Important terminal codes include
3917
- `runtime_restarted`, `daemon_crashed`, `credential_unavailable`,
3918
- `host_not_dispatched`, `host_outcome_unknown`, and
3919
- `control_history_untrusted`. Respect `effectOutcome`; `unknown` must never be
3920
- presented as success or automatically retried. After a lost response, query
3921
- `runtime.operations.get({ operationId, journalEpoch })`; applied receipts
3922
- include the canonical result. Permission grants remain daemon-owned and
3923
- revisioned.
3924
-
3925
- ### AskUser and permission from any client
3926
-
3927
- AskUser is no longer an in-process callback for daemon Coder runs. Any client
3928
- with the responder scope can list the pending request and answer or dismiss it.
3929
- The request revision and run binding prevent a stale UI from answering a new
3930
- request. Exactly one concurrent answer is accepted.
3931
-
3932
- ```ts
3933
- for (const request of await runtime.userInputs.listPending({ sessionId })) {
3934
- const resolution = await runtime.userInputs.respond(request.id, answer, {
3935
- expectedRevision: request.revision,
3936
- runId: request.runId,
3937
- });
3938
- if (!resolution.accepted) refreshPendingInteractions();
3939
- }
3940
-
3941
- for (const request of await runtime.permissions.listPending({ sessionId })) {
3942
- const sessionScope = request.grantSuggestions
3943
- ?.find((candidate) => candidate.kind === 'session');
3944
- const accepted = await runtime.permissions.respond(
3945
- request.id,
3946
- sessionScope
3947
- ? { type: 'allow_session', suggestionId: sessionScope.id }
3948
- : { type: 'allow_once' },
3949
- { runId: request.runId },
3950
- );
3951
- if (!accepted) refreshPendingInteractions();
3952
- }
3953
- ```
3954
-
3955
- Persistent `allow_always` grants are owned by the daemon and require
3956
- `permission:grant-admin`. Use `permissions.listGrants()` and
3957
- `revokeGrant(grantId, expectedRevision)` for revision-safe administration.
3958
- Clients must return one opaque `grantSuggestions[].id` from the pending request;
3959
- they must not infer, construct, or widen a scope from the display label or input
3960
- preview. A safe request can offer `allow_session` and `allow_always`; a risky or
3961
- dynamic shell request deliberately omits the persistent candidate. Command
3962
- grants match one exact normalized command/cwd/shell/background combination,
3963
- while path grants match one tool and normalized absolute path (the future
3964
- Write/Edit content may differ). Generic extension calls can receive only an
3965
- exact in-memory Session grant. Raw command/argv data is not stored in the
3966
- matcher; grants and audit contain only its fingerprint plus a bounded,
3967
- secret-redacted operator label. Clients must not keep separate persistent
3968
- permission rule stores. Runtime capability `runtimeAutoModeGuardrail` v3 advertises this
3969
- opaque concrete-grant contract; restart or upgrade an older daemon instead of
3970
- falling back to a client-side alias.
3971
-
3972
- ### Broker a Space keychain credential
3973
-
3974
- Credentials remain owned by Space's OS keychain. Register a broker in Electron
3975
- Main, bind the returned lease only to runs that Space starts, and return the
3976
- key only after checking the daemon-provided provider/session/run context.
3977
-
3978
- ```ts
3979
- const credentialLease = await runtime.credentials.register(
3980
- { providers: ['anthropic'] },
3981
- async ({ provider, sessionId: requestedSession, runId }) => {
3982
- authorizeSpaceRunCredential({ provider, sessionId: requestedSession, runId });
3983
- return spaceKeychain.readProviderCredential(provider);
3984
- },
3985
- );
3986
-
3987
- const run = await runtime.runs.start({
3988
- sessionId,
3989
- input: { type: 'text', text: prompt },
3990
- options: { provider: 'anthropic' },
3991
- credential: { leaseId: credentialLease.id, provider: 'anthropic' },
3992
- operation: { operationId: loadOrCreatePendingOperationId('space-run-88') },
3993
- });
3994
- ```
3995
-
3996
- The secret crosses only the authenticated reverse frame and an in-memory
3997
- run/provider scope. It is excluded from events, status, logs, diagnostics,
3998
- operation records, and Runtime persistence. While such a scope is active,
3999
- provider mismatch fails closed and never falls back to daemon environment.
4000
- Without a stable `instanceSecret`, registration ends when the Space connection
4001
- closes. With it, the daemon keeps the registration owned by that stable client;
4002
- a replacement Space process reattaches the callback with
4003
- `runtime.credentials.resume(leaseId, broker)`. An accepted run has already
4004
- acquired its scoped credential, so it reports `requirements.credential.state`
4005
- as `ready` and may continue after disconnect. If the broker cannot answer, the
4006
- start is rejected instead of accepting an indefinitely waiting run. Expiry or
4007
- Runtime restart is explicit (`expired` or terminal); no provider request is
4008
- automatically replayed.
4009
-
4010
- ### Bind Space-owned Host Tools to one run
4011
-
4012
- Register only narrow product capabilities. Descriptors are data; handlers stay
4013
- in Electron Main. A lease grants nothing until its ID is explicitly bound to a
4014
- run.
4015
-
4016
- ```ts
4017
- const hostLease = await runtime.hostTools.register([{
4018
- name: 'space_artifact_create',
4019
- description: 'Create a Space-owned artifact for this run.',
4020
- inputSchema: {
4021
- type: 'object',
4022
- properties: { title: { type: 'string' } },
4023
- required: ['title'],
4024
- additionalProperties: false,
4025
- },
4026
- sideEffect: 'non_idempotent',
4027
- }], {
4028
- async space_artifact_create(invocation) {
4029
- authorizeBoundSpaceInvocation(invocation);
4030
- const artifact = await createSpaceArtifact(invocation.input);
4031
- return { content: `Created artifact ${artifact.displayId}` };
4032
- },
4033
- });
4034
-
4035
- await runtime.runs.start({
4036
- sessionId,
4037
- input: { type: 'text', text: 'Create the report artifact.' },
4038
- hostTools: { leaseId: hostLease.id },
4039
- operation: { operationId: loadOrCreatePendingOperationId('space-artifact-run') },
4040
- });
4041
- ```
4042
-
4043
- The daemon injects session/run/lease/invocation identity; renderer, model, and
4044
- ordinary tool input cannot choose it. CLI runs never inherit a Space lease just
4045
- because Space later observes their session. The client memoizes one handler
4046
- promise per invocation ID, and the daemon never replays a dispatched Host Tool.
4047
- After a stable-client reconnect, call
4048
- `runtime.hostTools.resume(leaseId, handlers)`. Bound run status reports
4049
- `ready`, `waiting_host`, `expired`, or `terminal`. Disconnect or timeout after dispatch produces `host_outcome_unknown`; Space
4050
- must reconcile the product side effect itself before offering a new user
4051
- action. `runtime.hostTools.getInvocation(invocationId)` returns the durable
4052
- metadata state `prepared`, `dispatched`, `completed`, `unknown`, or
4053
- `not_dispatched`; it never returns handler input, result, or credential data.
4054
- The daemon writes the `dispatched` marker before attempting the reverse frame
4055
- and never auto-replays an invocation.
4056
-
4057
- ### Coder admission, typed events, and transport-safe inputs
4058
-
4059
- The daemon enforces Coder session admission on the server for list/create/load,
4060
- run, settings, delete, rewind, fork, compact, transcript, event, interaction,
4061
- and diagnostic paths. Coder
4062
- surfaces are `code`, `cli`, `repl`, `acp`, `a2a`, `sdk`, `ide`, and
4063
- `space-desktop`. A session marked with
4064
- Partner surface/profile metadata, or any unknown product surface, fails with
4065
- typed `session_not_admitted` before mutation. Space must continue marking
4066
- Partner sessions as `surface: 'partner'` and keep their inline storage root
4067
- separate. Legacy sessions without a surface remain admitted for existing Coder
4068
- compatibility, so absence of metadata is not a Partner namespace mechanism.
4069
-
4070
- `RuntimeEventPayloadMap` and `RuntimeTypedEvent` provide the public
4071
- discriminated contract for known events. Existing raw listeners remain
4072
- compatible; consumers can use `parseRuntimeEvent(value)` before exhaustive
4073
- handling. One unknown or malformed event is diagnosed and dropped without
4074
- closing the observation stream.
4075
-
4076
- Daemon clients use `RuntimeDaemonStartRunInput`. Function callbacks,
4077
- `AbortSignal`, Extension Runtime objects, and guardrail instances are excluded
4078
- from that type and rejected at runtime with `RuntimeTransportBoundaryError`
4079
- and an exact value path if an untyped caller supplies them. Host-only values
4080
- remain valid only for embedded Runtime calls.
4081
-
4082
- ### Recovery queries and stop preflight
4083
-
4084
- `runtime.status.preflight()` returns the initialized logical-client count,
4085
- active and queued runs, running/paused Workflows, every non-terminal External
4086
- Agent task (including `unknown`), pending AskUser/permission records, blockers,
4087
- and `canStop`. The background-work blockers are `active_workflows` and
4088
- `active_agent_tasks`. The current facade counts as one; daemon self-connections
4089
- and bounded health probes do not count. A second process changes the count to
4090
- two, and its awaited `close()` makes the count converge back to one.
4091
-
4092
- Preflight is useful for UI, but it is not a stop authorization token. Use
4093
- `runtime.daemon.inspect()` to obtain one consistent management revision,
4094
- verified owner fence, owner-policy revision, and preflight projection. Only
4095
- `runtime.daemon.stopForInline()` atomically rechecks and commits a rollback.
4096
- The management revision also advances when the preflight projection changes,
4097
- so a Workflow or AgentTask lifecycle transition between inspect and commit
4098
- invalidates the stale stop. Capability details
4099
- `daemonManagement.backgroundWorkPreflight` and
4100
- `daemonManagement.reverseBridgeDrainingFence` identify this complete contract.
4101
- `runtime.operations.get()` reconciles durable mutations,
4102
- `hostTools.getInvocation()` reconciles Host Tool metadata, and
4103
- `permissions.listGrants()` returns the daemon-owned persistent grant set.
4104
-
4105
- Terminal notification read/unread state is intentionally client-owned in
4106
- v0.7.69 (`durableRecoveryQueries.terminalAcknowledgement === false`): Space
4107
- persists its own UI acknowledgement cursor against Runtime/run terminal facts.
4108
- This avoids a false claim that one client's acknowledgement is global daemon
4109
- truth.
4110
-
4111
- ### Owner policy, rollback, and Electron boundary
4112
-
4113
- Daemon and inline Coder use one profile fence. Do not compose
4114
- `status.preflight()` with a low-level unconditional stop: another client or run
4115
- can appear between those calls. The public rollback transaction gates new
4116
- clients and mutations, rechecks the same Runtime and management/policy
4117
- revisions, verifies there is no other client or active/queued/pending work,
4118
- commits sticky inline policy while that daemon still owns the fence, and then
4119
- requests shutdown.
4120
-
4121
- ```ts
4122
- import {
4123
- acquireKodaXInlineOwner,
4124
- enableKodaXDaemonOwner,
4125
- getKodaXRuntimeOwnerState,
4126
- } from '@kodax-ai/kodax/runtime';
4127
-
4128
- const management = await runtime.daemon.inspect();
4129
- if (!management.preflight.canStop) {
4130
- showRollbackBlockers(management.preflight.blockers);
4131
- return;
4132
- }
4133
-
4134
- const rollback = await runtime.daemon.stopForInline({
4135
- expectedRuntimeId: management.runtimeId,
4136
- expectedRevision: management.revision,
4137
- expectedOwnerPolicyRevision: management.ownerPolicy.revision,
4138
- operation: { operationId: loadOrCreatePendingOperationId('coder-inline-rollback') },
4139
- });
4140
-
4141
- // `accepted` means inline policy is committed and shutdown is in progress.
4142
- // Wait through the public owner-state query; never infer release from a PID.
4143
- const shutdownDeadline = Date.now() + 30_000;
4144
- while (getKodaXRuntimeOwnerState({ homeDir: kodaxHome, profile: 'coder' }).owner?.runtimeId
4145
- === rollback.runtimeId) {
4146
- if (Date.now() >= shutdownDeadline) throw new Error('Timed out waiting for daemon owner release.');
4147
- await delay(25);
4148
- }
4149
- const releasedOwner = getKodaXRuntimeOwnerState({ homeDir: kodaxHome, profile: 'coder' });
4150
- if (releasedOwner.ownerStatus !== 'unowned') {
4151
- throw new Error('Coder profile acquired a different owner during rollback.');
4152
- }
4153
- await runtime.close(); // Detach only; it does not perform a second stop.
4154
- const inlineOwner = acquireKodaXInlineOwner({ homeDir: kodaxHome, profile: 'coder' });
4155
-
4156
- // Later, after the inline owner has released its fence:
4157
- inlineOwner.close();
4158
- const daemonPolicy = enableKodaXDaemonOwner({ homeDir: kodaxHome, profile: 'coder' });
4159
- // daemonPolicy.revision is authoritative; no expectedRevision guess is needed.
4160
- ```
4161
-
4162
- Any management revision change, another logical client, active or queued run,
4163
- running/paused Workflow, non-terminal/unknown AgentTask, pending
4164
- AskUser/permission, or in-flight mutation returns structured `conflict`; the
4165
- daemon remains running and policy remains unchanged. Draining also rejects
4166
- credential and Host Tool state changes without journaling their secrets or
4167
- results. The inline policy is sticky: later CLI auto-start is rejected until
4168
- `enableKodaXDaemonOwner()` changes it back to `daemon`. `runtime.close()` still
4169
- only detaches. Stale-owner handling validates the owned lock/state and never
4170
- kills a process merely because a PID was reused.
4171
-
4172
- Keep all trusted objects in Electron Main: daemon token/endpoint, stable client
4173
- identity, operation IDs, owner policy, keychain broker, Host Tool handlers, and
4174
- permission-grant administration. Renderer IPC should expose product-specific
4175
- commands and sanitized projections only. Never pass daemon credentials,
4176
- leases, operation epochs, or trusted session/run context to renderer or model
4177
- tool arguments.
4178
-
4179
- ---
4180
-
4181
- ## 24. Runtime-owned Auto Mode and plan-approval bridges (v0.7.72–v0.7.73)
4182
-
4183
- Auto Mode is a Runtime session contract, including in shared-daemon mode. Do
4184
- not implement a second classifier or decide permissions from a client-side
4185
- `beforeToolExecute` hook before the Runtime has classified the call.
4186
-
4187
- ### Configure the session, not an individual UI callback
4188
-
4189
- ```ts
4190
- await runtime.sessions.updateSettings(session.id, {
4191
- permissionMode: 'auto',
4192
- autoModeEngine: 'llm',
4193
- autoModeClassifierModel: 'zhipu:glm-5.2', // optional; otherwise follow the run model
4194
- autoModeTimeoutMs: 20_000, // positive safe integer, optional
4195
- autoModeSpeculativeWindowMs: 0, // non-negative safe integer, optional
4196
- executionCwd: projectDirectory,
4197
- });
4198
- ```
4199
-
4200
- All three Auto fields are durable session settings in inline, Worker, and
4201
- daemon forms. A `null` patch removes an optional override. Timeout must be a
4202
- positive safe integer; speculative window must be non-negative, so `0` is a
4203
- valid request to wait for the actual verdict. Daemon capability discovery
4204
- advertises all fields in `sharedSessionSettings.keys`.
4205
-
4206
- SDK hosts that need config precedence without creating a REPL can reuse the
4207
- same typed resolver as KodaX:
4208
-
4209
- ```ts
4210
- import {
4211
- loadAutoModeSettings,
4212
- resolveAutoModeSettings,
4213
- type ResolveAutoModeSettingsInput,
4214
- } from '@kodax-ai/kodax/repl';
4215
-
4216
- const persisted = loadAutoModeSettings(process.env);
4217
- const preview = resolveAutoModeSettings({
4218
- settings: { engine: 'llm', speculativeWindowMs: 0 },
4219
- env: process.env,
4220
- });
4221
- ```
4222
-
4223
- `resolveAutoModeSettings()` is pure. `loadAutoModeSettings()` reads the KodaX
4224
- config once and delegates to that resolver; `loadConfig()` declares and
4225
- returns the same optional `autoMode` object.
4226
-
4227
- Starting with the v0.7.73 patch, `permissionMode: 'auto'` with an omitted
4228
- `autoModeEngine` still means the documented `llm` default and is still owned by
4229
- Runtime. If neither `autoModeClassifierModel` nor the effective run/session/
4230
- Runtime model exists, `runs.start()` rejects with
4231
- `RuntimeAutoModeConfigurationError` (`code:
4232
- 'auto_mode_classifier_model_required'`, `recoverable: true`) before provider
4233
- construction, a classifier call, or a pending permission. Blank and malformed
4234
- classifier model specs are rejected by the same typed configuration boundary;
4235
- a live rules-to-LLM switch is blocked rather than converted into approval work.
4236
-
4237
- Direct consumers of `createAutoModeToolGuardrail()` receive the same terminal
4238
- model boundary. After resolving CLI/env/session/settings/live-default
4239
- precedence, an empty effective model returns a local configuration `block`
4240
- before provider lookup. It does not call `askUser`, mutate the denial or
4241
- circuit-breaker trackers, or change the engine to rules. An explicit non-empty
4242
- classifier override remains valid even when the main-session model is empty.
4243
-
4244
- The Runtime owns one serialized permission-settings stream and one shared
4245
- engine/denial/breaker state per Session. It reuses bounded context-specific
4246
- guardrails across turns while provider/model, repository boundary, execution
4247
- directory, classifier model, and timeout remain the same. Updating one of
4248
- those inputs selects a new context guardrail by design without copying stale
4249
- state from a queued turn. Active runs, queued runs, explicit settings updates,
4250
- and automatic LLM-to-rules fallback merge through the same Session mutation
4251
- queue; fallback is persisted before a later classification reads the engine.
4252
-
4253
- ### What an embedder should expect
4254
-
4255
- The Runtime's execution order is fixed:
4256
-
4257
- ```text
4258
- Runtime Auto Mode guardrail -> host permission bridge only for escalate -> tool execution
4259
- ```
4260
-
4261
- Consequently, an LLM/rules `allow` does not create a pending permission request
4262
- just because a host installed a static approval hook. `block` does not become a
4263
- spurious approval prompt. A real `escalate` uses the existing shared
4264
- `runtime.permissions` flow, so another authorized client may render and answer
4265
- it. Hosts should subscribe to permission events to display such a request, but
4266
- must not treat a missing request as an error for a safe tool call.
4267
-
4268
- The classifier deadline remains 20 seconds by default and includes connection
4269
- setup, provider Retry-After/backoff, inference, and stream completion. KodaX
4270
- does not solve timeouts by extending that deadline indefinitely. Before the
4271
- provider call it removes assistant prose/thinking and image paths, limits each
4272
- tool result to 2 KiB and the serialized permission-relevant transcript to
4273
- 8 KiB, then enforces 16 KiB action and 32 KiB total-prompt ceilings plus a
4274
- 256-token output cap. An oversized action or prompt escalates without a
4275
- provider call; it is never truncated into an automatic allow. These limits are
4276
- owned by `classify()` itself, so custom callers cannot accidentally bypass the
4277
- session-history boundary.
4278
-
4279
- `ClassifyDecision.diagnostics` and the lower-level
4280
- `SideQueryResult.diagnostics` expose provider, model, effective timeout,
4281
- elapsed time, retry count/wait, and a coarse terminal phase without including
4282
- the prompt, action, messages, or response text. `pre_output` means no non-empty
4283
- text delta was observed; `streaming` means output began before termination.
4284
- `firstOutputMs` and `streamMs` are present only when the provider adapter emits
4285
- a text delta. The current provider API cannot honestly separate DNS/connect,
4286
- TLS, provider queueing, and inference, so embedders must not infer those stages
4287
- from `pre_output`.
4288
-
4289
- The permission event's `inputPreview` is a display-safe diagnostic projection:
4290
- it is bounded, credential-redacted, valid JSON, and includes the effective
4291
- execution directory. Use the Runtime owner’s typed tool input for execution;
4292
- do not reconstruct or authorize a tool from the preview. `gitRoot` remains the
4293
- session repository safety boundary, whereas relative operands resolve from the
4294
- validated `executionCwd`. In particular, quoted Python/JavaScript/regexp source
4295
- inside a shell command is not a path operand.
4296
-
4297
- The user-level `.kodax` directory is a credential/configuration boundary, not
4298
- an ordinary project path. Direct shell mutations, output redirects, and
4299
- recognized nested-shell payloads whose target is provably beneath that
4300
- directory are rejected before LLM classification. The check is segment-safe
4301
- and Windows case-insensitive. KodaX deliberately does not scan arbitrary
4302
- quoted language source for path-looking substrings: doing so would turn Python,
4303
- JavaScript, YAML, and regular expressions into false Tier-0 matches. Trusted
4304
- configuration changes should use the KodaX config CLI or SDK configuration API.
4305
-
4306
- ### 0.7.x source compatibility
4307
-
4308
- The v0.7.72 public declarations retain the following migration aliases:
4309
-
4310
- | Legacy source | Current source | Contract |
4311
- |---|---|---|
4312
- | `agentMode: 'amaw'` | `'ama'` | accepted as deprecated input and normalized to AMA; no separate AMAW runtime is restored |
4313
- | `SkillSource` | `ResolvedSkillSource` | formal source union remains `project \| user \| plugin \| builtin`; only resolved discovery output adds `learned` |
4314
- | `RuntimeDaemonPreflight.activeAgentTasks` | `activeAgentTurns` | both required fields are returned and reference the same array throughout the 0.7.x line |
4315
-
4316
- ### Plan capability is opt-in
4317
-
4318
- `exit_plan_mode` is exposed to a Runtime run only when that run supplies an
4319
- approval bridge. A daemon or headless host that cannot approve a plan should
4320
- leave the bridge absent; KodaX removes the tool from that run's scope.
4321
-
4322
- ```ts
4323
- const planned = await runtime.runs.start({
4324
- sessionId: session.id,
4325
- prompt: 'Draft a migration plan, then ask for approval.',
4326
- options: {
4327
- events: {
4328
- exitPlanMode: async () => showPlanAndAskUser(),
4329
- },
4330
- },
4331
- });
4332
- await planned.result;
4333
- ```
2870
+ The portable manual gates remain in
2871
+ `docs/test-guides/FEATURE_255_v0.7.66_TEST_GUIDE.md`,
2872
+ `FEATURE_256_v0.7.71_TEST_GUIDE.md`, and
2873
+ `FEATURE_257_v0.7.72_TEST_GUIDE.md` for release-machine verification; the latter
2874
+ two filenames retain their original planning slots while their content records
2875
+ the v0.7.66 delivery. v0.7.67 adds
2876
+ `FEATURE_258_v0.7.67_TEST_GUIDE.md`, `FEATURE_259_v0.7.67_TEST_GUIDE.md`, and
2877
+ `FEATURE_261_v0.7.67_TEST_GUIDE.md`; v0.7.69 adds
2878
+ `FEATURE_267_v0.7.69_TEST_GUIDE.md`, `FEATURE_268_v0.7.69_TEST_GUIDE.md`, and
2879
+ `FEATURE_269_v0.7.69_TEST_GUIDE.md`. The earlier release-preparation Actions
2880
+ run is not reused after the severe-fix review. Fresh GitHub Actions run
2881
+ `29385073422` passes the Node 20/22 build, bundle, DTS, and full-test matrix plus
2882
+ the Node 22 Unix-domain-socket daemon gate. npm publication remains a
2883
+ maintainer-owned manual step.
2884
+
2885
+ ---
2886
+
2887
+ ## 18. External-agent executor plane (FEATURE_258, v0.7.67)
2888
+
2889
+ FEATURE_258 lets an SDK host register remote agents without teaching the coding
2890
+ runtime a specific A2A, MCP, or HTTP client. The public contracts live in
2891
+ `@kodax-ai/kodax/agent`; the host-facing catalog and task services live on
2892
+ `@kodax-ai/kodax/runtime`.
2893
+
2894
+ ### Ownership rule
2895
+
2896
+ An `AgentExecutorFactory` contains functions, so it cannot cross a Worker or
2897
+ daemon DTO boundary. Install factories where the Runtime owner executes:
2898
+
2899
+ | Desired owner | Supported construction |
2900
+ |---|---|
2901
+ | Private in-process owner | `createKodaXRuntime({ mode: 'embedded', isolation: 'inline', externalAgents })` |
2902
+ | New locally hosted daemon owner | `createKodaXRuntime({ mode: 'daemon', profile: '<unique>', externalAgents })` |
2903
+ | Existing daemon | Configure its owner, then attach with `connectKodaXRuntime({ requirements: { externalAgents: true } })`; a client cannot inject factories. |
2904
+ | Runtime Worker | The Worker owner must install factories itself; passing `externalAgents` from the parent is rejected. |
2905
+
2906
+ When `mode: 'daemon'` and `externalAgents` are supplied, the caller must win a
2907
+ new in-process daemon lease. KodaX rejects an already-running profile instead of
2908
+ silently replacing its executor configuration. Closing that owner facade shuts
2909
+ down the host it created; closing an ordinary attached client only detaches.
2910
+
2911
+ ### Minimal owner and task flow
2912
+
2913
+ The reference executor below is a contract/conformance adapter. Replace it with
2914
+ your own `AgentExecutorFactory` for a real remote protocol.
2915
+
2916
+ ```ts
2917
+ import {
2918
+ createReferenceAgentExecutorFactory,
2919
+ type ExternalAgentRegistration,
2920
+ } from '@kodax-ai/kodax/agent';
2921
+ import { createKodaXRuntime } from '@kodax-ai/kodax/runtime';
2922
+
2923
+ const runtime = await createKodaXRuntime({
2924
+ mode: 'embedded',
2925
+ isolation: 'inline',
2926
+ externalAgents: {
2927
+ factories: [createReferenceAgentExecutorFactory({
2928
+ executorId: 'example-http',
2929
+ protocol: 'http',
2930
+ })],
2931
+ policy: ({ registration, query }) => ({
2932
+ allowed: registration.enabled && query.readOnly === true,
2933
+ reasons: query.readOnly === true ? [] : ['This host allows read-only dispatch only.'],
2934
+ }),
2935
+ defaultContext: { actorId: 'desktop-host' },
2936
+ },
2937
+ });
2938
+
2939
+ const registration: ExternalAgentRegistration = {
2940
+ agentId: 'external:reviewer',
2941
+ displayName: 'Remote Reviewer',
2942
+ enabled: true,
2943
+ executorId: 'example-http',
2944
+ protocol: 'http',
2945
+ configurationRevision: 'reviewer-config-v1',
2946
+ endpointIdentityHash: 'sha256:replace-with-stable-endpoint-identity',
2947
+ skills: ['code-review'],
2948
+ inputModalities: ['text'],
2949
+ outputModalities: ['text'],
2950
+ capabilities: {
2951
+ streaming: 'supported',
2952
+ durableTasks: 'supported',
2953
+ inputRequired: 'conditional',
2954
+ cancellation: 'supported',
2955
+ artifacts: 'unsupported',
2956
+ },
2957
+ effects: { remote: 'read', workspace: 'proposal' },
2958
+ maxConcurrency: 1,
2959
+ };
2960
+
2961
+ try {
2962
+ await runtime.admin.agentRegistrations.upsert(registration);
2963
+
2964
+ const query = {
2965
+ actorId: 'desktop-host',
2966
+ requiredSkills: ['code-review'],
2967
+ readOnly: true,
2968
+ } as const;
2969
+ const available = await runtime.agents.listDispatchable(query);
2970
+ const preflight = await runtime.agents.preflight({
2971
+ agentId: 'external:reviewer',
2972
+ query,
2973
+ expectedConfigurationRevision: registration.configurationRevision,
2974
+ });
2975
+ if (!preflight.ok) throw new Error(preflight.reasons.join('; '));
2976
+
2977
+ const started = await runtime.agentTasks.start({
2978
+ agentId: 'external:reviewer',
2979
+ objective: 'Review the supplied immutable patch and return cited findings.',
2980
+ context: { actorId: 'desktop-host', runId: 'host-run-42' },
2981
+ readOnly: true,
2982
+ requiredSkills: ['code-review'],
2983
+ expectedConfigurationRevision: registration.configurationRevision,
2984
+ });
2985
+ const terminal = await runtime.agentTasks.wait(started.taskId, 60_000);
2986
+ console.log(available, terminal.state, terminal.output, terminal.usage);
2987
+ } finally {
2988
+ await runtime.close();
2989
+ }
2990
+ ```
2991
+
2992
+ `runtime.agents.enabled` is the cheap external-plane feature check. If it is
2993
+ false, catalog queries can still return built-in local agents but no external
2994
+ agents; registration and task lists are empty, while point reads and mutations
2995
+ fail clearly. Set
2996
+ `requirements.externalAgents: true` when absence must abort connection.
2997
+
2998
+ ### Service reference
2999
+
3000
+ | Surface | Methods | Contract |
3001
+ |---|---|---|
3002
+ | `runtime.admin.agentRegistrations` | `list`, `upsert`, `setEnabled`, `remove` | Durable owner configuration. `setEnabled` preserves the complete captured executor registration while changing admission. Mutations accept both `expectedConfigurationRevision` and `expectedManagementOwner`; `setEnabled` can also atomically `claimOwner` on an unowned registration and rejects another owner. List results expose `managementOwner` and `credentialConfigured`, never a credential value. The same contract is carried across the daemon transport. With no plane, `list()` is empty and mutations fail clearly. |
3003
+ | `runtime.agents` | `enabled`, `listDispatchable`, `describe`, `preflight` | Applies health, capability, effect, concurrency, credential-presence, configuration-revision, and host-policy checks before dispatch. |
3004
+ | `runtime.agentTasks` | `start`, `list`, `get`, `events`, `wait`, `sendInput`, `cancel`, `reconcile` | Durable snapshots and append-only events for external tasks. The task keeps the immutable registration/executor binding captured at start. |
3005
+
3006
+ `agentTasks.events(taskId, cursor)` uses the last seen numeric event `seq` as its
3007
+ cursor and returns events with a greater sequence. `wait()` resolves only at a
3008
+ terminal task state and rejects on a positive `timeoutMs` expiry. `sendInput()`
3009
+ is valid only while the task reports `input-required` or `auth-required`.
3010
+ `reconcile()` asks the bound executor for authoritative remote state after an
3011
+ owner restart or uncertain failure.
3012
+
3013
+ For external tasks, the built-in stores persist an internal full registration
3014
+ snapshot before the public task ledger. It is keyed by Agent ID and revision,
3015
+ is never returned by task or daemon APIs, and lets an admitted task keep using
3016
+ its original executor route after registration update/removal and Runtime
3017
+ restart. The internal form fixes `enabled: true` and omits management ownership
3018
+ and health diagnostics. The task's public route summary is validated against that internal
3019
+ snapshot before recovery. Terminal task state is durable before the last
3020
+ unreferenced snapshot is removed; startup cleans crash-window orphans.
3021
+
3022
+ Custom `AgentExecutorPlaneStore` implementations should implement
3023
+ `loadTaskRegistrationSnapshots()` and `saveTaskRegistrationSnapshots()` as a
3024
+ pair and give one Runtime exclusive write ownership of that store. Omitting
3025
+ both remains compatible, but restart recovery then succeeds only while the
3026
+ exact current registration still exists. Store only non-secret executor config
3027
+ or secret references in `executorConfig`/`credentialRef`; the broker resolves
3028
+ the current referenced credential just in time, so removing a registration is
3029
+ not equivalent to revoking that credential at its issuer.
3030
+
3031
+ The owner plane has a terminal close contract. Closing it rejects every pending
3032
+ `wait()` (including a wait without `timeoutMs`), disposes its executor instances,
3033
+ and makes subsequent registration, catalog, preflight, and task calls reject
3034
+ with `Agent executor plane is closed.` One overall deadline covers admitted
3035
+ work plus executor disposal: the default is 30 seconds, and direct
3036
+ `createAgentExecutorPlane()` hosts may supply a positive finite
3037
+ `closeTimeoutMs`. A timeout rejects visibly even though already-admitted cleanup
3038
+ may finish in the background. Repeated `close()` calls are safe. SDK hosts
3039
+ should stop accepting work before closing the owner and must not retain a plane
3040
+ service as a reusable handle after Runtime shutdown.
3041
+
3042
+ Restricted Workflow scripts use the same route as direct SDK calls. Both
3043
+ `wf.spawnAgent()` and `wf.runAgent()` validate and forward
3044
+ `target: { agentId, expectedConfigurationRevision? }`; `phase` is forwarded as
3045
+ well. A blank ID or revision is rejected at the script boundary instead of
3046
+ silently falling back to the native child backend.
3047
+
3048
+ ### Credential, artifact, and failure boundaries
3049
+
3050
+ - Put only a `credentialRef` in a registration. Resolve secret material through
3051
+ `AgentCredentialBroker.withCredential()`; do not place tokens in
3052
+ `executorConfig`, events, diagnostics, or task output.
3053
+ - Remote artifacts are denied by default. Supply `artifactPolicy` to authorize
3054
+ each artifact before it materializes in the host boundary.
3055
+ - External agents may declare workspace effect `none` or `proposal`; direct
3056
+ workspace mutation is intentionally not a valid external registration.
3057
+ - Use `expectedConfigurationRevision` for dispatch. For registration mutations,
3058
+ compare both it and `expectedManagementOwner` so a same-revision ownership
3059
+ change cannot be overwritten from an earlier catalog read. Config managers
3060
+ should set a stable `managementOwner`; they may atomically claim an unowned
3061
+ legacy registration while disabling it, but must not mutate a registration
3062
+ owned by another manager.
3063
+ - Treat `configurationRevision` as the stable identity of immutable execution
3064
+ content, not as a small counter. The same content may deterministically reuse
3065
+ the same revision across remove/re-add or Runtime restart, but different
3066
+ endpoint, protocol, executor/auth config, capabilities, effects, Skills,
3067
+ modalities, or resource limits must never reuse it. Built-in A2A
3068
+ configuration derives it from content.
3069
+ - A remote start followed by uncertain local persistence is recorded as
3070
+ `unknown` with its executor reference preserved; reconcile it rather than
3071
+ blindly starting a duplicate. Stable idempotency keys protect retries.
3072
+ - The durable plane records provider-reported usage when available. It never
3073
+ invents missing token or cost fields.
3074
+
3075
+ For a production adapter, implement `preflight?`, `start`, `events`, `get`,
3076
+ `sendInput`, `cancel`, `reconcile`, and `dispose` on `AgentExecutor`. The factory
3077
+ receives `withCredential()` and `authorizeArtifact()` callbacks so protocol code
3078
+ cannot bypass the host's secret/artifact policies.
3079
+
3080
+ ---
3081
+
3082
+ ## 19. Session surface filtering and cursor pagination (FEATURE_261, v0.7.67)
3083
+
3084
+ The narrow session SDK and Runtime facade now share the same listing semantics:
3085
+ `surface` is an exact filter applied before `limit`, and `cursor` is an opaque
3086
+ continuation token carried by each returned summary.
3087
+
3088
+ ### Narrow `/session` API
3089
+
3090
+ ```ts
3091
+ import { listSessions, type SessionSummary } from '@kodax-ai/kodax/session';
3092
+
3093
+ const all: SessionSummary[] = [];
3094
+ let cursor: string | undefined;
3095
+ do {
3096
+ const page = await listSessions({
3097
+ scope: 'user',
3098
+ surface: 'partner',
3099
+ limit: 50,
3100
+ ...(cursor ? { cursor } : {}),
3101
+ });
3102
+ all.push(...page);
3103
+ cursor = page.length === 50 ? page.at(-1)?.cursor : undefined;
3104
+ } while (cursor);
3105
+ ```
3106
+
3107
+ ### Embedded/daemon Runtime API
3108
+
3109
+ ```ts
3110
+ const first = await runtime.sessions.list({ surface: 'acp', limit: 25 });
3111
+ const nextCursor = first.at(-1)?.cursor;
3112
+ const second = nextCursor
3113
+ ? await runtime.sessions.list({ surface: 'acp', limit: 25, cursor: nextCursor })
3114
+ : [];
3115
+ ```
3116
+
3117
+ The filter also composes with `projectRoot`, `scope`, `includeArchived`, `before`,
3118
+ and `tag`. Treat cursors as opaque: do not parse them, compare them to session
3119
+ IDs, or manufacture them. An invalid cursor produces an empty page on the narrow
3120
+ session API. A page shorter than the requested limit is terminal; a full page
3121
+ may continue with its last item's cursor.
3122
+
3123
+ The interactive `kodax -r` picker is a consumer of these session semantics, not
3124
+ a separate SDK API. Headless hosts should build their own UI on `listSessions()`
3125
+ or `runtime.sessions.list()` and resume by the selected full session ID.
3126
+
3127
+ ---
3128
+
3129
+ ## 20. Cost-disciplined workflow routing and telemetry (FEATURE_259, v0.7.67)
3130
+
3131
+ FEATURE_259 adds a public cost/quality contract without making the authoring LLM
3132
+ choose provider-specific model names. The SDK host maps semantic tiers to routes;
3133
+ workflow/child briefs request intent.
3134
+
3135
+ ### Configure tiers and bounded concurrency
3136
+
3137
+ ```ts
3138
+ import { join } from 'node:path';
3139
+ import { runKodaX } from '@kodax-ai/kodax/coding';
3140
+
3141
+ const workflowRunsBaseDir = join(process.cwd(), '.kodax-host', 'workflow-runs');
3142
+ const result = await runKodaX({
3143
+ provider: 'zai-coding',
3144
+ model: 'glm-5.2',
3145
+ agentMode: 'amaw',
3146
+ workflowRunsBaseDir,
3147
+ modelTiers: {
3148
+ fast: { provider: 'deepseek', model: 'deepseek-v4-flash' },
3149
+ deep: { provider: 'zai-coding', model: 'glm-5.2' },
3150
+ },
3151
+ workflow: { maxConcurrency: 3 },
3152
+ events: {
3153
+ onWorkflowAgentDigest: ({ runId, event }) => {
3154
+ if (event.type === 'agent_completed' || event.type === 'agent_unverified'
3155
+ || event.type === 'agent_failed') {
3156
+ console.log(runId, event.data);
3157
+ }
3158
+ },
3159
+ },
3160
+ }, 'Review this change using scoped evidence packets.');
3161
+
3162
+ console.log(result.lastText);
3163
+ ```
3164
+
3165
+ Tier rules are deliberately small:
3166
+
3167
+ - `fast`: mechanical read-only lookup. A write child is ineligible and safely
3168
+ inherits the parent route.
3169
+ - `balanced`: ordinary implementation/investigation/review; uses the parent
3170
+ route, so there is no separate `balanced` mapping.
3171
+ - `deep`: architecture, adversarial verification, severity calibration, and
3172
+ final synthesis.
3173
+ - An unconfigured or selector-shadowed tier inherits the appropriate explicit,
3174
+ specialist, or parent route and records why; it does not silently claim the
3175
+ requested route was applied.
3176
+
3177
+ The workflow authoring contract also supports `scopeSummary`, `constraints`,
3178
+ `evidenceRefs`, `verification`, `readOnly`, `outputSchema`, and `terseResult`.
3179
+ Use those fields to transfer a compact immutable packet instead of asking every
3180
+ child to rediscover the same repository context.
3181
+
3182
+ ### Live route facts
3183
+
3184
+ Terminal `WorkflowEvent.data` may include `requestedTier`, `tierOutcome`,
3185
+ `providerSource`, `modelSource`, initial/final provider and model,
3186
+ `fallbackReason`, `iterations`, `durationMs`, `usage`, and `digestUsage`.
3187
+ Treat absent fields as unknown. In particular, KodaX does not fabricate usage
3188
+ for external executors that did not report it.
3189
+
3190
+ Direct child-dispatch consumers receive the typed `KodaXChildAgentResult.routeFacts`
3191
+ surface, including resolved effort and input/cache-read/output/digest token
3192
+ breakdown when known. Inline workflow consumers can subscribe to the raw
3193
+ `onWorkflowAgentDigest` event as above; GUI progress remains available through
3194
+ `onWorkflowProcessEvent` / `runtime.workflows`.
3195
+
3196
+ ### Durable efficiency report
3197
+
3198
+ When `workflowRunsBaseDir` is supplied, every terminal workflow writes:
3199
+
3200
+ ```text
3201
+ <workflowRunsBaseDir>/<runId>/run.json
3202
+ <workflowRunsBaseDir>/<runId>/events.jsonl
3203
+ <workflowRunsBaseDir>/<runId>/artifacts/*.json
3204
+ ```
3205
+
3206
+ `run.json.efficiencyReport` includes:
3207
+
3208
+ - total/input/cache-read/output/digest tokens and wall-clock duration;
3209
+ - total starts, child turns, starts by `role/tier`, and primary-review starts;
3210
+ - duplicate primary packet reads plus verification/synthesis packet reads;
3211
+ - review/fix/re-review waves and structured review quality-gate outcomes;
3212
+ - `tokenCoverage.ok` plus missing local task IDs;
3213
+ - `excludedExternalTaskIds` for external tasks whose executor reported no usage.
3214
+
3215
+ Do not interpret `totalModelTokens: 0` as free execution unless
3216
+ `tokenCoverage.ok` is true and the relevant external task IDs are not excluded.
3217
+ The report is an audit/optimization artifact; correctness still comes from the
3218
+ workflow's structured findings, verification results, and quality gates.
3219
+
3220
+ ---
3221
+
3222
+ ## 21. Experimental governed memory — `/experimental-memory` (FEATURE_260, v0.7.68)
3223
+
3224
+ KodaX has one durable memory plane: the F228 Memory Control Plane. FEATURE_260
3225
+ adds a thin, opt-in agent/session API over that plane; it does not add a second
3226
+ database, filesystem memory actions, a resident memory specialist, or online
3227
+ self-modification.
3228
+
3229
+ Top-level `runKodaX()` coding runs wire this lifecycle automatically. They build
3230
+ an exact-scoped memory pack at session start, keep passive recall off the
3231
+ blocking hot path, expose `memory_recall` only when the memory session starts,
3232
+ record bounded observations/outcomes, and close the session at the run boundary.
3233
+ Use the direct SDK below when a custom host needs to own those boundaries.
3234
+
3235
+ ### Minimal direct session
3236
+
3237
+ ```ts
3238
+ import { createMemoryControlPlane } from '@kodax-ai/kodax/agent';
3239
+ import { createMemoryAgent } from '@kodax-ai/kodax/experimental-memory';
3240
+
3241
+ const identity = {
3242
+ tenantId: 'tenant:acme',
3243
+ workspaceId: 'workspace:desktop',
3244
+ userId: 'user:42',
3245
+ agentId: 'agent:reviewer',
3246
+ projectId: 'project:kodax',
3247
+ sessionId: 'session:20260712',
3248
+ };
3249
+
3250
+ const controlPlane = createMemoryControlPlane({
3251
+ cwd: process.cwd(),
3252
+ identity,
3253
+ projectDocs: [],
3254
+ discoverSkills: false,
3255
+ });
3256
+ const memory = createMemoryAgent({ controlPlane });
3257
+ const session = await memory.startSession({
3258
+ identity,
3259
+ objective: 'Review the runtime shutdown change',
3260
+ });
3261
+
3262
+ const immediate = session.recall({
3263
+ decisionRevision: 'decision:1',
3264
+ objective: 'Review the runtime shutdown change',
3265
+ decisionContext: 'Choosing the first verification step',
3266
+ decisionIntent: 'runtime shutdown regression',
3267
+ throughSequence: 0,
3268
+ });
3269
+ const deliberate = await session.query({
3270
+ decisionRevision: 'decision:2',
3271
+ need: 'What uncommon daemon cleanup failure happened before?',
3272
+ throughSequence: 0,
3273
+ });
3274
+
3275
+ await session.complete({
3276
+ status: 'succeeded',
3277
+ summary: 'Shutdown state replacement verified',
3278
+ evidence: [],
3279
+ });
3280
+ await session.close();
3281
+ ```
3282
+
3283
+ `recall()` is synchronous: exact observations/hints can be returned immediately,
3284
+ while an optional semantic prefetch finishes for a later matching decision.
3285
+ `query()` is deliberate and read-only; one distinct query is admitted per
3286
+ decision epoch, and the result is bounded to at most three prompt-safe hints and
3287
+ 512 estimated tokens. `undefined` means there is no governed reminder to inject.
3288
+
3289
+ ### Evidence, tracing, and persistence boundaries
3290
+
3291
+ - Recalled content is low-authority evidence. Current repository/config/runtime
3292
+ facts must still come from current tools or host state.
3293
+ - `observe()` accepts monotonic, evidence-linked observations and rejects secret
3294
+ material. `rewind()` removes observations after a sequence boundary.
3295
+ - `complete()` can emit an Outcome Digest through `persistOutcomeDigest` and run
3296
+ bounded episode review through `reviewEpisode`; cancellation creates neither.
3297
+ - `onTrace` receives policy-versioned `MemoryDecisionReceipt` metadata that links
3298
+ candidates, selection, injection, and later outcome influence. Receipts are
3299
+ trace-only and contain no hidden reasoning.
3300
+ - Durable memory mutation remains owned by F228's
3301
+ proposal/preview/fingerprint/apply flow. `/memory` is the CLI governance
3302
+ surface; direct file/shell writes to managed memory roots are denied.
3303
+ - Identity/applicability matching is exact and fail-closed. Hosts should supply
3304
+ stable tenant/workspace/user/agent/project/session identifiers and must not put
3305
+ credentials into those identifiers.
3306
+
3307
+ The subpath is experimental and ESM-only. Treat its exported types as opt-in
3308
+ v0.7.x contracts; keep persistence and product policy behind your own adapter.
3309
+
3310
+ ---
3311
+
3312
+ ## 22. Bidirectional A2A 1.0 — `/a2a` (FEATURE_267, v0.7.69)
3313
+
3314
+ `@kodax-ai/kodax/a2a` is the protocol edge for the A2A 1.0 JSON-RPC/SSE
3315
+ profile. It composes the protocol-neutral F258 executor plane with the Runtime
3316
+ facade; `/agent` and `/coding` remain free of A2A wire types and dependencies.
3317
+
3318
+ ### Call an A2A Agent through F258
3319
+
3320
+ Discovery is an explicit host action. The URL must match `allowedOrigins`, and
3321
+ the default safe transport pins the validated DNS address for each connection
3322
+ and independently revalidates redirects, rejects public plain HTTP, bounds
3323
+ time/body/redirects, and strips authorization on a cross-origin redirect. A
3324
+ custom `fetch` option is a trusted transport override: the embedder then owns
3325
+ equivalent DNS-to-connection binding in that transport or proxy.
3326
+
3327
+ The selected interface must remain on the Card's trusted origin. KodaX parses
3328
+ typed Card-level and Skill-level security declarations: requirement objects are
3329
+ alternatives (OR), every scheme inside one object is conjunctive (AND), and an
3330
+ empty requirement is anonymous. A configured credential is used only when one
3331
+ complete requirement is satisfiable; protected Skills that the configured
3332
+ profile cannot satisfy are not advertised to the Runtime catalog.
3333
+
3334
+ The built-in profiles are HTTP Bearer and OAuth 2.0 Client Credentials. The
3335
+ OAuth profile pins the Card scheme, issuer, exact token endpoint, client ID,
3336
+ secret reference, scopes, optional RFC 8707 resource, and client authentication
3337
+ method. The external Authorization Server—not the Agent and not KodaX—issues
3338
+ the access token. KodaX resolves the client secret only for refresh, keeps an
3339
+ expiring token in process memory, coalesces refreshes, and retries one RPC once
3340
+ with a fresh token after `401`. Card, Agent RPC, and token endpoints remain
3341
+ separate safe-fetch trust boundaries, so a remote Agent cannot redirect a task
3342
+ payload to the token origin. API key, Basic, interactive OAuth, OIDC, mTLS, and
3343
+ multi-scheme AND requirements fail explicitly in the built-in client.
3344
+
3345
+ ```ts
3346
+ import {
3347
+ createA2AAgentExecutorFactory,
3348
+ discoverA2ARegistration,
3349
+ } from '@kodax-ai/kodax/a2a';
3350
+ import { createKodaXRuntime } from '@kodax-ai/kodax/runtime';
3351
+
3352
+ const client = {
3353
+ networkPolicy: {
3354
+ // Card/RPC and OAuth token endpoints are separate trust boundaries.
3355
+ allowedOrigins: ['https://reviewer.example', 'https://identity.example'],
3356
+ allowPrivateAddresses: false,
3357
+ requestTimeoutMs: 10_000,
3358
+ maxResponseBytes: 1_048_576,
3359
+ maxRedirects: 2,
3360
+ },
3361
+ pollIntervalMs: 500,
3362
+ } as const;
3363
+
3364
+ const discovered = await discoverA2ARegistration({
3365
+ agentId: 'external:a2a-reviewer',
3366
+ agentCardUrl: 'https://reviewer.example/.well-known/agent-card.json',
3367
+ credentialRef: 'a2a/reviewer',
3368
+ effects: { remote: 'read' },
3369
+ }, client);
3370
+
3371
+ const runtime = await createKodaXRuntime({
3372
+ mode: 'embedded',
3373
+ isolation: 'inline',
3374
+ externalAgents: {
3375
+ factories: [createA2AAgentExecutorFactory(client)],
3376
+ credentialBroker: {
3377
+ async withCredential(ref, use) {
3378
+ const value = ref === 'a2a/reviewer'
3379
+ ? process.env.A2A_REVIEWER_TOKEN
3380
+ : ref === 'a2a/reviewer-client-secret'
3381
+ ? process.env.A2A_REVIEWER_CLIENT_SECRET
3382
+ : undefined;
3383
+ if (!value) throw new Error(`Missing credential for reference: ${ref}.`);
3384
+ return use(value);
3385
+ },
3386
+ },
3387
+ policy: ({ registration }) => ({ allowed: registration.effects.remote === 'read' }),
3388
+ defaultContext: { actorId: 'a2a-host' },
3389
+ },
3390
+ });
3391
+
3392
+ await runtime.admin.agentRegistrations.upsert(discovered.registration);
3393
+ const started = await runtime.agentTasks.start({
3394
+ agentId: discovered.registration.agentId,
3395
+ objective: 'Review this change and return cited findings.',
3396
+ context: { actorId: 'a2a-host' },
3397
+ readOnly: true,
3398
+ expectedConfigurationRevision: discovered.registration.configurationRevision,
3399
+ });
3400
+ const terminal = await runtime.agentTasks.wait(started.taskId, 60_000);
3401
+ ```
3402
+
3403
+ For OAuth, replace the legacy `credentialRef` input with the structured form;
3404
+ the same F258 `credentialBroker` must resolve `clientSecretRef`. The shared
3405
+ network policy must admit both origins, while each Card, RPC, and token request
3406
+ is still narrowed to its own exact origin:
3407
+
3408
+ ```ts
3409
+ const discovered = await discoverA2ARegistration({
3410
+ agentId: 'external:a2a-reviewer',
3411
+ agentCardUrl: 'https://reviewer.example/.well-known/agent-card.json',
3412
+ authentication: {
3413
+ type: 'oauth2-client-credentials',
3414
+ scheme: 'enterprise-oauth',
3415
+ issuer: 'https://identity.example/',
3416
+ tokenUrl: 'https://identity.example/oauth/token',
3417
+ clientId: 'kodax-reviewer',
3418
+ clientSecretRef: 'a2a/reviewer-client-secret',
3419
+ scopes: ['a2a.invoke'],
3420
+ resource: 'https://reviewer.example/',
3421
+ clientAuthentication: 'client-secret-basic',
3422
+ },
3423
+ effects: { remote: 'read' },
3424
+ }, client);
3425
+ ```
3426
+
3427
+ The executor supports durable task start/get, input continuation, cancel,
3428
+ reconcile, SSE events, and polling fallback. An ambiguous start is not retried
3429
+ automatically. A `credentialRef` is resolved just in time by the F258 broker;
3430
+ the registration, task store, and diagnostics never contain the credential.
3431
+ Authenticated SSE uses that same broker. JSON-RPC ID/version and task/context
3432
+ correlation are validated before an event is accepted; if a stream ends
3433
+ normally before a terminal snapshot, the executor resumes bounded polling.
3434
+ Streamed `artifactUpdate` chunks are accumulated by artifact ID according to
3435
+ `append`, and direct Message file Parts are preserved as authorized artifact
3436
+ references.
3437
+
3438
+ ### Built-in configured path (no host code)
3439
+
3440
+ The CLI product path stores one user document at
3441
+ `~/.kodax/integrations/a2a.json` and uses the same F258 plane:
3442
+
3443
+ ```bash
3444
+ kodax a2a add reviewer https://reviewer.example/.well-known/agent-card.json \
3445
+ --credential-env A2A_REVIEWER_TOKEN --effect read
3446
+ kodax a2a test reviewer
3447
+ kodax a2a call reviewer "Review this document"
3448
+ ```
3449
+
3450
+ The no-code OAuth path stores only the environment-variable name for the client
3451
+ secret. It can be staged disabled and hot-activated later:
3452
+
3453
+ ```bash
3454
+ export A2A_REVIEWER_CLIENT_SECRET='provisioned-out-of-band'
3455
+ # PowerShell: $env:A2A_REVIEWER_CLIENT_SECRET='provisioned-out-of-band'
3456
+ # PowerShell: use one line or replace each trailing \ with a backtick.
3457
+ kodax a2a add reviewer https://reviewer.example/.well-known/agent-card.json \
3458
+ --disabled --effect read --oauth-scheme enterprise-oauth \
3459
+ --oauth-issuer https://identity.example/ \
3460
+ --oauth-token-url https://identity.example/oauth/token \
3461
+ --oauth-client-id kodax-reviewer \
3462
+ --oauth-client-secret-env A2A_REVIEWER_CLIENT_SECRET \
3463
+ --oauth-scope a2a.invoke --oauth-resource https://reviewer.example/
3464
+ kodax a2a enable reviewer
3465
+ kodax a2a disable reviewer
3466
+ ```
3467
+
3468
+ Embedded CLI Runtimes and the user-owned daemon automatically reconcile these
3469
+ entries as `external:<name>`. Discovery/update failure retains that entry's
3470
+ last-known-good registration; another entry can still update. The environment
3471
+ broker resolves `credentialEnv` only at call time. Automatic Runtime
3472
+ registration accepts public HTTPS and exact loopback targets; explicit private
3473
+ network access remains an operator action on the direct CLI/SDK path.
3474
+
3475
+ `enabled` is desired state in `a2a.json`, not a fabricated cross-process live
3476
+ flag. `a2a list` reports configured entries and that desired state. The owning
3477
+ Runtime's `admin.agentRegistrations.list()` is authoritative for applied
3478
+ registrations. Automatic reconciliation handles disables/removals first,
3479
+ skips unchanged peers, performs no Card or token request for disabled entries,
3480
+ and rediscovers before re-enable. Once the owning Runtime observes and applies
3481
+ the revision, disable blocks all new starts, including an explicit
3482
+ `external:<name>` target, but does not cancel or break an already admitted task.
3483
+ The CLI mutation returning is not cross-process acknowledgement. A failed
3484
+ activation remains retryable through the owning
3485
+ `ConfiguredA2ARuntimeHandle.reload()` even when the disk revision is unchanged;
3486
+ the passive `kodax integrations reload` command validates only its own process.
3487
+
3488
+ `kodax a2a test` performs Card discovery and security planning only. It never
3489
+ requests an OAuth access token; token acquisition starts at `a2a call` or the
3490
+ first Runtime dispatch.
3491
+
3492
+ Inbound publication is also no-code:
3493
+
3494
+ ```bash
3495
+ export KODAX_A2A_TOKEN='replace-with-a-long-random-token'
3496
+ # PowerShell: $env:KODAX_A2A_TOKEN='replace-with-a-long-random-token'
3497
+ kodax a2a expose # Runtime default Agent
3498
+ kodax a2a expose document-agent # ~/.kodax/agents/document-agent.md
3499
+ kodax a2a serve --port 8765
3500
+ ```
3501
+
3502
+ The fixed token above is the compatibility profile. For dynamic production
3503
+ tokens, configure KodaX as an OAuth Resource Server and point it at an external
3504
+ issuer:
3505
+
3506
+ ```bash
3507
+ kodax a2a expose document-agent --auth oauth2-jwt \
3508
+ --oauth-scheme enterprise-oauth \
3509
+ --oauth-issuer https://identity.example/ \
3510
+ --oauth-audience https://kodax.example/a2a \
3511
+ --oauth-jwks-url https://identity.example/.well-known/jwks.json \
3512
+ --oauth-token-url https://identity.example/oauth/token \
3513
+ --oauth-metadata-url https://identity.example/.well-known/oauth-authorization-server \
3514
+ --required-scope a2a.invoke
3515
+ kodax a2a serve --port 8765
3516
+ ```
3517
+
3518
+ The Authorization Server authenticates clients, provisions client IDs/secrets,
3519
+ issues/rotates/revokes tokens and, for JWT access tokens, signs them and
3520
+ publishes metadata/JWKS. The calling A2A
3521
+ client obtains a token out of band or with Client Credentials and sends it in
3522
+ the Bearer header. KodaX validates JWT type, asymmetric signature, issuer,
3523
+ audience, lifetime, subject, and required scopes before task lookup, then maps
3524
+ `sub` to the A2A principal. Missing/invalid credentials return `401`; a valid
3525
+ token without the required scope returns `403 insufficient_scope`. KodaX does
3526
+ not hold the issuer signing key or expose token, refresh, client-registration,
3527
+ login, or consent endpoints. Opaque-token introspection and mTLS deployments
3528
+ must use a host authentication adapter or reverse proxy. Offline JWT/JWKS
3529
+ validation also cannot observe immediate per-token revocation: use short access
3530
+ token lifetimes, signing-key rotation, or an introspecting proxy/adapter when
3531
+ that property is required.
3532
+
3533
+ #### Upgrade retained pre-realm tasks
3534
+
3535
+ Realm-aware task ownership intentionally has no normal-request legacy fallback:
3536
+ an authority switch must never adopt tasks merely because it reuses a subject.
3537
+ If a v0.7.70 task store must remain addressable after upgrading, stop the A2A
3538
+ server and first inspect an exact-owner migration plan:
3539
+
3540
+ ```bash
3541
+ kodax a2a migrate-tasks
3542
+ kodax a2a migrate-tasks --apply --confirm-server-stopped
3543
+
3544
+ # OAuth identity is token-specific, so provide the known historical subject.
3545
+ kodax a2a migrate-tasks --subject trusted-orchestrator
3546
+ ```
3547
+
3548
+ The configured Bearer profile supplies its fixed `principalId`; OAuth requires
3549
+ `--subject`. Dry-run does not rewrite `tasks.json`. Apply rekeys only exact
3550
+ matches, preserves unmatched records, and refuses a live task-store owner.
3551
+ Custom SDK hosts can plan multiple known owners without exposing raw tokens:
3552
+
3553
+ ```ts
3554
+ import { migrateA2ALegacyTaskOwners } from '@kodax-ai/kodax/a2a';
3555
+
3556
+ const mappings = [{
3557
+ securityRealm: 'oauth2-jwt:https://identity.example/',
3558
+ subject: 'trusted-orchestrator',
3559
+ }] as const;
3560
+ const plan = migrateA2ALegacyTaskOwners({
3561
+ dataDir: '/var/lib/kodax/a2a', mappings, apply: false,
3562
+ });
3563
+
3564
+ // After the host/operator verifies the plan:
3565
+ if (plan.matchedLegacyTaskCount > 0) {
3566
+ migrateA2ALegacyTaskOwners({
3567
+ dataDir: '/var/lib/kodax/a2a', mappings, apply: true,
3568
+ });
3569
+ }
3570
+ ```
3571
+
3572
+ The SDK also accepts `tenant` when a custom authentication adapter historically
3573
+ returned one. Two mappings that claim the same legacy owner for different
3574
+ realms are ambiguous and rejected; split or guessed ownership is never applied.
3575
+
3576
+ `a2a serve` resolves its Runtime provider in this order: explicit CLI option,
3577
+ environment, core configuration, then the built-in default. Provider-compatible
3578
+ model selection follows the normal hosted Runtime rule. A selected Markdown
3579
+ Agent may declare its own validated `provider`; remote A2A input cannot choose
3580
+ or override provider, model, reasoning, profile, workspace, or tools.
3581
+
3582
+ `expose` validates a named user Markdown Agent before writing its reference.
3583
+ `serve` loads configured MCP and Extensions before it resolves the execution
3584
+ binding or opens a socket. Native workspace read tools are admitted by
3585
+ workspace access; writes, narrow Extension Tools, MCP capabilities, subagents,
3586
+ and isolated Skill scripts require their corresponding exact `toolPolicy`
3587
+ authority. Internal Skills come from `~/.kodax/skills`, `~/.agents/skills`,
3588
+ plugins, and built-ins; public Agent Card skills are a separate explicit
3589
+ projection and never reveal the private Skill inventory.
3590
+
3591
+ The running server pins Agent, Skill, workspace, tool registration, process and
3592
+ store revisions. Card/auth/limits can hot reload; execution-authority changes
3593
+ require an explicit restart. Managed contexts live below
3594
+ `~/kodax_a2a_server_workspace/<runtime-profile>/contexts/<context-key>/`. Exact Skill scripts require
3595
+ `process: isolated`, an admitted `scripts/...` path, and a passing
3596
+ `kodax sandbox doctor`; KodaX never falls back to an unsandboxed shell.
3597
+
3598
+ Every concrete file reached by `read`, `grep`, or `glob` is checked against the
3599
+ bound workspace. Child runs inherit ceilings for native reads, tools, Skills,
3600
+ and Skill scripts; they cannot expand the parent's admitted authority.
3601
+
3602
+ ### Publish one KodaX Agent
3603
+
3604
+ Publication is host-owned and opt-in. The public card describes only the
3605
+ configured Agent, media types, and skills. Authentication runs before task
3606
+ lookup; authorization runs per operation; task visibility is principal-scoped.
3607
+
3608
+ ```ts
3609
+ import {
3610
+ createBearerEnvA2AAuthentication,
3611
+ createKodaXA2AServer,
3612
+ } from '@kodax-ai/kodax/a2a';
3613
+ import { createKodaXRuntime } from '@kodax-ai/kodax/runtime';
3614
+
3615
+ const runtime = await createKodaXRuntime({ mode: 'embedded', isolation: 'inline' });
3616
+ const server = createKodaXA2AServer({
3617
+ runtime,
3618
+ dataDir: '/var/lib/kodax/a2a',
3619
+ agent: {
3620
+ name: 'KodaX Reviewer',
3621
+ description: 'Reviews bounded code changes.',
3622
+ version: '1.0.0',
3623
+ publicBaseUrl: 'https://kodax.example',
3624
+ skills: [{ id: 'review', name: 'Review', description: 'Review code.', tags: ['code'] }],
3625
+ inputModes: ['text/plain'],
3626
+ outputModes: ['text/plain'],
3627
+ },
3628
+ authentication: createBearerEnvA2AAuthentication({
3629
+ type: 'bearer-env',
3630
+ tokenEnv: 'KODAX_A2A_TOKEN',
3631
+ principalId: 'trusted-orchestrator',
3632
+ }),
3633
+ async authorize({ principal }) { return principal.scopes.includes('a2a:invoke'); },
3634
+ limits: {
3635
+ maxRequestBytes: 1_048_576,
3636
+ maxPartBytes: 524_288,
3637
+ maxConcurrentTasks: 8,
3638
+ maxTaskWaitMs: 30_000,
3639
+ maxActiveTasksPerPrincipal: 8,
3640
+ maxRetainedTasksPerPrincipal: 64,
3641
+ maxEventsPerTask: 1_000,
3642
+ maxEventBytesPerTask: 16_777_216,
3643
+ maxWorkspaceBytesPerContext: 1_073_741_824,
3644
+ },
3645
+ });
3646
+
3647
+ // Development only: the built-in listener refuses non-loopback hosts.
3648
+ const localBaseUrl = await server.listen({ hostname: '127.0.0.1', port: 0 });
3649
+ ```
3650
+
3651
+ Production hosts route `GET /.well-known/agent-card.json` and canonical
3652
+ JSON-RPC `POST /a2a` to `server.handle(request)` behind their own TLS
3653
+ terminator. `POST /` remains an accepted compatibility alias. `listen()` waits for durable recovery before
3654
+ it resolves. A host that wires `handle()` directly may explicitly await
3655
+ `server.whenReady()` before it starts accepting traffic; `handle()` also waits
3656
+ for the same recovery promise. The durable edge store supports get/list,
3657
+ continuation, cancellation, ordered SSE subscription, and surviving Runtime-run
3658
+ reattachment after an edge restart. Push notifications, A2A 0.3, gRPC, and
3659
+ HTTP+JSON are not advertised; unsupported push methods return the standard
3660
+ `PushNotificationNotSupportedError`.
3661
+
3662
+ Non-streaming `SendMessage` waits at most `maxTaskWaitMs` (30 seconds by
3663
+ default). When that bound is reached the response contains the current working
3664
+ task; it does not cancel the Runtime run, and clients can continue with
3665
+ `GetTask` or `SubscribeToTask`.
3666
+
3667
+ When a task enters `INPUT_REQUIRED`, the next accepted input answers the pending
3668
+ interaction on the original Runtime run; it does not start a replacement run.
3669
+ History length and list filters are validated and bounded. Task listing uses a
3670
+ stable opaque cursor, while per-principal retention prunes only the oldest
3671
+ terminal records. Terminal subscriptions and failed-start resources are closed
3672
+ by their owning lifecycle.
3673
+
3674
+ Remote messages are ordinary user inputs. They cannot select provider, model,
3675
+ profile, tools, working directory, permission mode, or Runtime configuration.
3676
+ URL parts are rejected; inline raw/data parts are bounded and materialized under
3677
+ the server-owned data directory. Responses expose final approved output only,
3678
+ not system prompts, reasoning deltas, tool payloads, credentials, or local paths.
3679
+
3680
+ Generated files are published only through the trusted output broker: a normal
3681
+ tool or Extension stages a file in the context's `.kodax-a2a-staging` area, or
3682
+ a successfully admitted `run_skill_script` promotes one of its declared
3683
+ outputs. The server rechecks that the result is a regular non-symlink file in
3684
+ the real bound workspace and applies part-size/output-mode limits before
3685
+ inlining it. A declaration from a failed Skill run, an ordinary `write`/`edit`
3686
+ elsewhere in the workspace, and a local path in model text never become A2A
3687
+ artifacts implicitly.
3688
+
3689
+ The normative baseline is A2A repository commit
3690
+ `2183794bfb9b67af4aee1be0a0ef726050642873`, protocol `1.0`, with
3691
+ `specification/a2a.proto` SHA-256
3692
+ `e195bf96ab630c69797851970203e1b2b6b19528f2e9803b7d904b91a5104016`.
3693
+
3694
+ ---
3695
+
3696
+ ## 23. Shared Coder daemon for Space and IDE hosts (FEATURE_269, v0.7.69)
3697
+
3698
+ FEATURE_269 makes one local daemon the source of truth for a Coder profile.
3699
+ CLI, Space, IDE, and SDK clients can observe and control the same sessions and
3700
+ runs. The transport remains local to the current OS user; it is not a remote
3701
+ collaboration protocol. Closing a client detaches that client and does not stop
3702
+ the daemon or another client's run.
3703
+
3704
+ Partner is deliberately outside this migration. Keep Partner on its existing
3705
+ inline callbacks and give it a distinct product data root and sessions root.
3706
+ Do not point a Partner inline Runtime at the Coder daemon profile or the Coder
3707
+ data root.
3708
+
3709
+ ### Connect and fail closed on required capabilities
3710
+
3711
+ Space should own the daemon SDK client in Electron Main. Persist a random,
3712
+ stable `instanceId` and a separate 32+ character `instanceSecret` per Space
3713
+ installation. Store the secret in the OS keychain; never accept either value
3714
+ from renderer or model output. `connectKodaXRuntime()` is attach-only unless `autoStart: true`.
3715
+ An explicit inline rollback policy blocks auto-start until the owner policy is
3716
+ explicitly changed back to daemon.
3717
+
3718
+ For Electron, `homeDir` is still the CLI-style base directory, not
3719
+ `process.env.KODAX_HOME`. Packaged/asar applications may use `autoStart: true`
3720
+ directly; the SDK launches only the daemon child in Electron's Node execution
3721
+ mode and does not mutate the application's environment or start a second GUI
3722
+ instance. `ELECTRON_RUN_AS_NODE` exists only at the child exec boundary and is
3723
+ removed before daemon application code loads, so Bash, MCP, LSP, sandboxed
3724
+ commands, and ordinary external processes do not inherit Electron Node mode.
3725
+
3726
+ Packaged auto-start requires Electron's `RunAsNode` fuse, which Electron enables
3727
+ by default. If an embedder deliberately disables that fuse, the packaged
3728
+ executable cannot serve as a detached Node host: start the daemon with an
3729
+ ordinary Node/CLI process and use attach-only mode instead. A packaged
3730
+ `autoStart: true` timeout includes this fuse requirement in its diagnostic; the
3731
+ SDK does not relaunch the GUI or silently fall back to an inline Runtime.
3732
+
3733
+ ```ts
3734
+ import { connectKodaXRuntime } from '@kodax-ai/kodax/runtime';
3735
+
3736
+ const runtime = await connectKodaXRuntime({
3737
+ profile: 'coder',
3738
+ autoStart: true,
3739
+ homeDir: coderRuntimeBaseDir, // owns <coderRuntimeBaseDir>/.kodax
3740
+ clientInfo: {
3741
+ name: 'kodax-space',
3742
+ version: '0.1.32',
3743
+ instanceId: spaceInstallationId,
3744
+ instanceSecret: await spaceKeychain.readRuntimeClientSecret(),
3745
+ },
3746
+ capabilities: {
3747
+ richEvents: true,
3748
+ permissionPrompts: true,
3749
+ operationDeduplication: true,
3750
+ },
3751
+ requirements: {
3752
+ operationDeduplication: 1,
3753
+ sessionObservation: 1,
3754
+ afterTurnInput: 1,
3755
+ interruptInput: 1,
3756
+ askUserTransport: 1,
3757
+ permissionCas: 1,
3758
+ providerCredentialBroker: 1,
3759
+ runBoundHostTools: 1,
3760
+ coderOwnerFencing: 1,
3761
+ crashOutcomeModel: 1,
3762
+ coderFeatureMatrix: 1,
3763
+ sessionAdmission: 1,
3764
+ completeObservationSnapshot: 1,
3765
+ connectionLifecycle: 1,
3766
+ typedRuntimeEvents: 1,
3767
+ daemonSafeRunInput: 1,
3768
+ sharedSessionSettings: 1,
3769
+ durableRecoveryQueries: 1,
3770
+ daemonManagement: 1,
3771
+ runtimeAutoModeGuardrail: 3,
3772
+ },
3773
+ });
3774
+ ```
3775
+
3776
+ Requirements are server facts, not authorization requests. Check
3777
+ `runtime.grantedScopes` before enabling controls. Missing capabilities or
3778
+ scopes must disable the affected UI; Space must not silently start inline
3779
+ Coder. Products that depend on same-Run delivery should require
3780
+ `{ interruptInput: 1 }`. Individual active Runs without a safe Actor boundary
3781
+ (for example, SA execution) still return `unsupported_capability`; do not
3782
+ silently substitute `delivery:'after_turn'` unless that is the user's intent.
3783
+
3784
+ The v0.7.73 SDK requires `runtimeAutoModeGuardrail:3` automatically for
3785
+ `autoStart: true`, even when the caller omits it from `requirements`. If the healthy
3786
+ profile daemon advertises v1 or v2, the SDK first requires `daemonManagement:1`,
3787
+ takes a revision/owner-policy fenced preflight, and replaces it only when no
3788
+ active or queued run, Workflow, Agent turn, pending permission/user input, or
3789
+ other logical client exists. A busy or still-older daemon is never stopped: the
3790
+ connection rejects with `RuntimeDaemonCapabilityUpgradeError`, whose
3791
+ `recoverable` and `restartRequired` fields are `true` and whose optional
3792
+ `preflight` explains the blockers. Attach-only connections never mutate daemon
3793
+ ownership and must request `runtimeAutoModeGuardrail:1` explicitly when they
3794
+ depend only on the v1 owner contract, v2 for bounded input, effective-default
3795
+ metadata, structured diagnostics, and speculative-window parity, or v3 for
3796
+ opaque exact grant suggestions and concrete permission matchers. Capability
3797
+ requirements are minimum versions: v3 satisfies v1/v2, v2 satisfies v1, and an
3798
+ older daemon never satisfies a newer requirement.
3799
+
3800
+ The `coderFeatureMatrix` capability reports daemon availability for managed
3801
+ runs, transcript/session operations, Todo projection, managed tasks, Workflow,
3802
+ MCP, Reference External Agent, Memory, and Runtime artifacts. Reference
3803
+ External Agent is `false` when the daemon owner did not install its executor
3804
+ plane.
3805
+
3806
+ The packaged daemon authenticates one local OS-user/profile trust domain with
3807
+ a random token stored beside daemon state and a user-only local endpoint. It
3808
+ does not issue a different daemon token to each application in v0.7.69. The
3809
+ returned scope set is chosen by the host (the packaged host grants the public
3810
+ local-user set). `clientInfo.instanceId` is stable attribution for origin and
3811
+ operation deduplication. `instanceSecret` proves that a new authenticated
3812
+ connection is the same stable client when it resumes that client's credential
3813
+ or Host Tool leases; only its hash participates in daemon-owned bridge state.
3814
+ Keep all three values in Electron Main. Mutually distrusting processes running
3815
+ as the same OS account remain outside this release's threat model.
3816
+
3817
+ ### Join atomically and resync after disconnect
3818
+
3819
+ `sessions.observe()` installs the live subscription before taking the
3820
+ snapshot. Its snapshot contains one authoritative `runtimeId`, cursor,
3821
+ `transcriptRevision`, complete transcript, versioned settings, run/queue state,
3822
+ queued continuation IDs/order/origin/safe previews, pending permission and
3823
+ AskUser requests, and live assistant/thinking/tool/Todo/managed-task
3824
+ projection. Run requirements include the current credential/Host Tool
3825
+ availability. Listener events are strictly after the returned cursor.
3826
+
3827
+ ```ts
3828
+ let observedRuntimeId: string | undefined;
3829
+ let lastCursor = 0;
3830
+
3831
+ async function openCoderSession(sessionId: string) {
3832
+ const observation = await runtime.sessions.observe(sessionId, (event) => {
3833
+ if (event.seq <= lastCursor) return;
3834
+ applyRuntimeEvent(event);
3835
+ lastCursor = event.seq;
3836
+ });
3837
+
3838
+ const { snapshot } = observation;
3839
+ const runtimeChanged = observedRuntimeId !== undefined
3840
+ && observedRuntimeId !== snapshot.runtimeId;
3841
+ observedRuntimeId = snapshot.runtimeId;
3842
+ lastCursor = snapshot.cursor;
3843
+ replaceSessionProjection(snapshot, { runtimeChanged });
3844
+ return observation;
3845
+ }
3846
+ ```
3847
+
3848
+ Subscribe to `runtime.connection` to freeze mutation UI immediately rather
3849
+ than waiting for a status poll:
3850
+
3851
+ ```ts
3852
+ runtime.connection?.subscribe((state) => {
3853
+ setCoderConnectionState(state.state, state.reason);
3854
+ if (state.state === 'disconnected' && state.reconnectable) {
3855
+ scheduleReconnect();
3856
+ }
3857
+ });
3858
+ ```
3859
+
3860
+ The SDK reports the current `connectionId`, `runtimeEpoch`, optional
3861
+ `journalEpoch`, disconnect reason, and whether a new connection may be
3862
+ attempted. It does not transparently replay requests or subscriptions. Space
3863
+ creates a replacement Runtime client, checks its new epochs, resumes eligible
3864
+ leases, and observes the session again.
3865
+
3866
+ On transport failure, Runtime change, expired history, or `resync_required`,
3867
+ discard the local derived projection and call `sessions.observe()` again. Do
3868
+ not merge a new snapshot into the old projection. The handshake buffer is
3869
+ bounded; overflow fails explicitly instead of dropping events. A Runtime
3870
+ restart changes `runtimeId`, marks persisted non-terminal runs with a durable
3871
+ terminal fact, and closes old in-memory AskUser/permission requests through the
3872
+ reset boundary.
3873
+
3874
+ ### Durable mutations, stable ordering, and settings CAS
3875
+
3876
+ Every durable public control mutation uses an operation envelope. Credential
3877
+ and Host Tool register/revoke/supply/complete requests are reverse-bridge
3878
+ control frames and are deliberately excluded from the control journal so
3879
+ secrets/results are not persisted. They still enter the daemon management
3880
+ draining fence: once an atomic stop begins, they fail with typed `conflict` and
3881
+ cannot change reverse-bridge state. The SDK creates an operation ID for
3882
+ ordinary one-shot calls. A
3883
+ product-level retry after a lost response must reuse its own stable operation
3884
+ ID; changing its method, payload, resource, or authenticated principal is
3885
+ rejected.
3886
+
3887
+ ```ts
3888
+ const session = await runtime.sessions.create({
3889
+ sessionId: stableSpaceSessionId,
3890
+ title: 'Shared session',
3891
+ surface: 'space-desktop',
3892
+ operation: { operationId: loadOrCreatePendingOperationId('space-session-draft-7') },
3893
+ });
3894
+
3895
+ const operationId = loadOrCreatePendingOperationId('space-run-draft-42');
3896
+ const handle = await runtime.runs.start({
3897
+ sessionId: session.id,
3898
+ input: { type: 'text', text: prompt },
3899
+ options: { provider: 'anthropic' },
3900
+ operation: { operationId },
3901
+ });
3902
+
3903
+ const current = await runtime.sessions.getSettingsVersioned(session.id);
3904
+ const updated = await runtime.sessions.updateSettingsVersioned(
3905
+ session.id,
3906
+ { model: 'claude-sonnet-4-5' },
3907
+ {
3908
+ operationId: loadOrCreatePendingOperationId('space-settings-draft-9'),
3909
+ expectedRevision: current.revision,
3910
+ },
3911
+ );
3912
+ ```
3913
+
3914
+ Create retries with the same explicit session and operation IDs cannot overwrite
3915
+ an existing session. Same-session starts and after-turn inputs receive a durable `sessionOrder`.
3916
+ Retries with the same operation ID return the canonical result and do not
3917
+ create another run. Settings use compare-and-swap; a stale revision returns a
3918
+ structured conflict and must be reloaded, never silently overwritten. The
3919
+ shared settings keys are `provider`, `model`, `effort`, `thinking`,
3920
+ `reasoningMode`, `permissionMode`, `executionCwd`, `agentMode`, and
3921
+ `autoModeEngine`, `autoModeClassifierModel`, `autoModeTimeoutMs`, and
3922
+ `autoModeSpeculativeWindowMs`.
3923
+
3924
+ ```ts
3925
+ const queued = await runtime.runs.submitInput({
3926
+ sessionId: session.id,
3927
+ afterRunId: handle.runId,
3928
+ delivery: 'after_turn',
3929
+ input: { type: 'text', text: 'Also update the tests.' },
3930
+ operation: { operationId: loadOrCreatePendingOperationId('space-input-17') },
3931
+ });
3932
+
3933
+ if (!queued.accepted) {
3934
+ // stale_run, unsupported_capability, or interrupt_window_closed:
3935
+ // show the factual result and preserve the user's unsent input.
3936
+ }
3937
+ ```
3938
+
3939
+ For input that must join the current active Run, submit `delivery:'interrupt'`.
3940
+ This does not create a Run. Each accepted input appears as `queued` in the
3941
+ owning Run's `interruptInputs`. At the next safe boundary, all accumulated
3942
+ interrupts are drained FIFO, remain separate user messages in one next LLM
3943
+ request, and produce one `run.input.delivered` event whose `inputs` array is the
3944
+ complete ordered batch. Exact operation retries return the same `inputId`.
3945
+ The accepted result's `runId` is the existing owning Run (equal to
3946
+ `afterRunId`), not a newly created continuation.
3947
+
3948
+ Interrupt admission closes when the Runner publishes its final completion or
3949
+ terminal error signal, or when the Run's supplied `abortSignal` aborts, even if
3950
+ the outer Run is still settling. Non-terminal observer diagnostics do not close
3951
+ the window. A submission after closure returns `accepted:false` with
3952
+ `reason:'interrupt_window_closed'` and is not queued. Keep the original input
3953
+ available for retry after the Run ends; do not silently change its delivery to
3954
+ `after_turn`. As a final race/recovery
3955
+ guard, inspect terminal Run status: any `interruptInputs` entry whose state is
3956
+ `terminal` was not delivered. Reconcile it by `inputId` and present a visible
3957
+ non-delivery outcome rather than leaving a pending queue indicator.
3958
+
3959
+ ```ts
3960
+ const interrupted = await runtime.runs.submitInput({
3961
+ sessionId: session.id,
3962
+ afterRunId: handle.runId,
3963
+ delivery: 'interrupt',
3964
+ input: { type: 'text', text: 'Also preserve the public API.' },
3965
+ operation: { operationId: loadOrCreatePendingOperationId('space-input-18') },
3966
+ });
3967
+ ```
3968
+
3969
+ Run status exposes acceptance/start/queue times, authenticated origin,
3970
+ `sessionOrder`, and a single terminal fact. Important terminal codes include
3971
+ `runtime_restarted`, `daemon_crashed`, `credential_unavailable`,
3972
+ `host_not_dispatched`, `host_outcome_unknown`, and
3973
+ `control_history_untrusted`. Managed tasks that require user input use
3974
+ `terminal.code = 'blocked'`; surface `terminal.message` when present instead of
3975
+ replacing it with a generic run failure. Respect `effectOutcome`; `unknown` must
3976
+ never be presented as success or automatically retried. After a lost response,
3977
+ query `runtime.operations.get({ operationId, journalEpoch })`; applied receipts
3978
+ include the canonical result. Permission grants remain daemon-owned and
3979
+ revisioned.
3980
+
3981
+ ### AskUser and permission from any client
3982
+
3983
+ AskUser is no longer an in-process callback for daemon Coder runs. Any client
3984
+ with the responder scope can list the pending request and answer or dismiss it.
3985
+ The request revision and run binding prevent a stale UI from answering a new
3986
+ request. Exactly one concurrent answer is accepted.
3987
+
3988
+ ```ts
3989
+ for (const request of await runtime.userInputs.listPending({ sessionId })) {
3990
+ const resolution = await runtime.userInputs.respond(request.id, answer, {
3991
+ expectedRevision: request.revision,
3992
+ runId: request.runId,
3993
+ });
3994
+ if (!resolution.accepted) refreshPendingInteractions();
3995
+ }
3996
+
3997
+ for (const request of await runtime.permissions.listPending({ sessionId })) {
3998
+ const sessionScope = request.grantSuggestions
3999
+ ?.find((candidate) => candidate.kind === 'session');
4000
+ const accepted = await runtime.permissions.respond(
4001
+ request.id,
4002
+ sessionScope
4003
+ ? { type: 'allow_session', suggestionId: sessionScope.id }
4004
+ : { type: 'allow_once' },
4005
+ { runId: request.runId },
4006
+ );
4007
+ if (!accepted) refreshPendingInteractions();
4008
+ }
4009
+ ```
4010
+
4011
+ Persistent `allow_always` grants are owned by the daemon and require
4012
+ `permission:grant-admin`. Use `permissions.listGrants()` and
4013
+ `revokeGrant(grantId, expectedRevision)` for revision-safe administration.
4014
+ Clients must return one opaque `grantSuggestions[].id` from the pending request;
4015
+ they must not infer, construct, or widen a scope from the display label or input
4016
+ preview. A safe request can offer `allow_session` and `allow_always`; a risky or
4017
+ dynamic shell request deliberately omits the persistent candidate. Command
4018
+ grants match one exact normalized command/cwd/shell/background combination,
4019
+ while path grants match one tool and normalized absolute path (the future
4020
+ Write/Edit content may differ). Generic extension calls can receive only an
4021
+ exact in-memory Session grant. Raw command/argv data is not stored in the
4022
+ matcher; grants and audit contain only its fingerprint plus a bounded,
4023
+ secret-redacted operator label. Clients must not keep separate persistent
4024
+ permission rule stores. Runtime capability `runtimeAutoModeGuardrail` v3 advertises this
4025
+ opaque concrete-grant contract; restart or upgrade an older daemon instead of
4026
+ falling back to a client-side alias.
4027
+
4028
+ ### Broker a Space keychain credential
4029
+
4030
+ Credentials remain owned by Space's OS keychain. Register a broker in Electron
4031
+ Main, bind the returned lease only to runs that Space starts, and return the
4032
+ key only after checking the daemon-provided provider/session/run context.
4033
+
4034
+ ```ts
4035
+ const credentialLease = await runtime.credentials.register(
4036
+ { providers: ['anthropic'] },
4037
+ async ({ provider, sessionId: requestedSession, runId }) => {
4038
+ authorizeSpaceRunCredential({ provider, sessionId: requestedSession, runId });
4039
+ return spaceKeychain.readProviderCredential(provider);
4040
+ },
4041
+ );
4042
+
4043
+ const run = await runtime.runs.start({
4044
+ sessionId,
4045
+ input: { type: 'text', text: prompt },
4046
+ options: { provider: 'anthropic' },
4047
+ credential: { leaseId: credentialLease.id, provider: 'anthropic' },
4048
+ operation: { operationId: loadOrCreatePendingOperationId('space-run-88') },
4049
+ });
4050
+ ```
4051
+
4052
+ The secret crosses only the authenticated reverse frame and an in-memory
4053
+ run/provider scope. It is excluded from events, status, logs, diagnostics,
4054
+ operation records, and Runtime persistence. While such a scope is active,
4055
+ provider mismatch fails closed and never falls back to daemon environment.
4056
+ Without a stable `instanceSecret`, registration ends when the Space connection
4057
+ closes. With it, the daemon keeps the registration owned by that stable client;
4058
+ a replacement Space process reattaches the callback with
4059
+ `runtime.credentials.resume(leaseId, broker)`. An accepted run has already
4060
+ acquired its scoped credential, so it reports `requirements.credential.state`
4061
+ as `ready` and may continue after disconnect. If the broker cannot answer, the
4062
+ start is rejected instead of accepting an indefinitely waiting run. Expiry or
4063
+ Runtime restart is explicit (`expired` or terminal); no provider request is
4064
+ automatically replayed.
4065
+
4066
+ ### Bind Space-owned Host Tools to one run
4067
+
4068
+ Register only narrow product capabilities. Descriptors are data; handlers stay
4069
+ in Electron Main. A lease grants nothing until its ID is explicitly bound to a
4070
+ run.
4071
+
4072
+ ```ts
4073
+ const hostLease = await runtime.hostTools.register([{
4074
+ name: 'space_artifact_create',
4075
+ description: 'Create a Space-owned artifact for this run.',
4076
+ inputSchema: {
4077
+ type: 'object',
4078
+ properties: { title: { type: 'string' } },
4079
+ required: ['title'],
4080
+ additionalProperties: false,
4081
+ },
4082
+ sideEffect: 'non_idempotent',
4083
+ }], {
4084
+ async space_artifact_create(invocation) {
4085
+ authorizeBoundSpaceInvocation(invocation);
4086
+ const artifact = await createSpaceArtifact(invocation.input);
4087
+ return { content: `Created artifact ${artifact.displayId}` };
4088
+ },
4089
+ });
4090
+
4091
+ await runtime.runs.start({
4092
+ sessionId,
4093
+ input: { type: 'text', text: 'Create the report artifact.' },
4094
+ hostTools: { leaseId: hostLease.id },
4095
+ operation: { operationId: loadOrCreatePendingOperationId('space-artifact-run') },
4096
+ });
4097
+ ```
4098
+
4099
+ The daemon injects session/run/lease/invocation identity; renderer, model, and
4100
+ ordinary tool input cannot choose it. CLI runs never inherit a Space lease just
4101
+ because Space later observes their session. The client memoizes one handler
4102
+ promise per invocation ID, and the daemon never replays a dispatched Host Tool.
4103
+ After a stable-client reconnect, call
4104
+ `runtime.hostTools.resume(leaseId, handlers)`. Bound run status reports
4105
+ `ready`, `waiting_host`, `expired`, or `terminal`. Disconnect or timeout after dispatch produces `host_outcome_unknown`; Space
4106
+ must reconcile the product side effect itself before offering a new user
4107
+ action. `runtime.hostTools.getInvocation(invocationId)` returns the durable
4108
+ metadata state `prepared`, `dispatched`, `completed`, `unknown`, or
4109
+ `not_dispatched`; it never returns handler input, result, or credential data.
4110
+ The daemon writes the `dispatched` marker before attempting the reverse frame
4111
+ and never auto-replays an invocation.
4112
+
4113
+ ### Coder admission, typed events, and transport-safe inputs
4114
+
4115
+ The daemon enforces Coder session admission on the server for list/create/load,
4116
+ run, settings, delete, rewind, fork, compact, transcript, event, interaction,
4117
+ and diagnostic paths. Coder
4118
+ surfaces are `code`, `cli`, `repl`, `acp`, `a2a`, `sdk`, `ide`, and
4119
+ `space-desktop`. A session marked with
4120
+ Partner surface/profile metadata, or any unknown product surface, fails with
4121
+ typed `session_not_admitted` before mutation. Space must continue marking
4122
+ Partner sessions as `surface: 'partner'` and keep their inline storage root
4123
+ separate. Legacy sessions without a surface remain admitted for existing Coder
4124
+ compatibility, so absence of metadata is not a Partner namespace mechanism.
4125
+
4126
+ `RuntimeEventPayloadMap` and `RuntimeTypedEvent` provide the public
4127
+ discriminated contract for known events. Existing raw listeners remain
4128
+ compatible; consumers can use `parseRuntimeEvent(value)` before exhaustive
4129
+ handling. One unknown or malformed event is diagnosed and dropped without
4130
+ closing the observation stream.
4131
+
4132
+ Daemon clients use `RuntimeDaemonStartRunInput`. Function callbacks,
4133
+ `AbortSignal`, Extension Runtime objects, and guardrail instances are excluded
4134
+ from that type and rejected at runtime with `RuntimeTransportBoundaryError`
4135
+ and an exact value path if an untyped caller supplies them. Host-only values
4136
+ remain valid only for embedded Runtime calls.
4137
+
4138
+ ### Recovery queries and stop preflight
4139
+
4140
+ `runtime.status.preflight()` returns the initialized logical-client count,
4141
+ active and queued runs, running/paused Workflows, every non-terminal External
4142
+ Agent task (including `unknown`), pending AskUser/permission records, blockers,
4143
+ and `canStop`. The background-work blockers are `active_workflows` and
4144
+ `active_agent_tasks`. The current facade counts as one; daemon self-connections
4145
+ and bounded health probes do not count. A second process changes the count to
4146
+ two, and its awaited `close()` makes the count converge back to one.
4147
+
4148
+ Preflight is useful for UI, but it is not a stop authorization token. Use
4149
+ `runtime.daemon.inspect()` to obtain one consistent management revision,
4150
+ verified owner fence, owner-policy revision, and preflight projection. Only
4151
+ `runtime.daemon.stopForInline()` atomically rechecks and commits a rollback.
4152
+ The management revision also advances when the preflight projection changes,
4153
+ so a Workflow or AgentTask lifecycle transition between inspect and commit
4154
+ invalidates the stale stop. Capability details
4155
+ `daemonManagement.backgroundWorkPreflight` and
4156
+ `daemonManagement.reverseBridgeDrainingFence` identify this complete contract.
4157
+ `runtime.operations.get()` reconciles durable mutations,
4158
+ `hostTools.getInvocation()` reconciles Host Tool metadata, and
4159
+ `permissions.listGrants()` returns the daemon-owned persistent grant set.
4160
+
4161
+ Terminal notification read/unread state is intentionally client-owned in
4162
+ v0.7.69 (`durableRecoveryQueries.terminalAcknowledgement === false`): Space
4163
+ persists its own UI acknowledgement cursor against Runtime/run terminal facts.
4164
+ This avoids a false claim that one client's acknowledgement is global daemon
4165
+ truth.
4166
+
4167
+ ### Owner policy, rollback, and Electron boundary
4168
+
4169
+ Daemon and inline Coder use one profile fence. Do not compose
4170
+ `status.preflight()` with a low-level unconditional stop: another client or run
4171
+ can appear between those calls. The public rollback transaction gates new
4172
+ clients and mutations, rechecks the same Runtime and management/policy
4173
+ revisions, verifies there is no other client or active/queued/pending work,
4174
+ commits sticky inline policy while that daemon still owns the fence, and then
4175
+ requests shutdown.
4176
+
4177
+ ```ts
4178
+ import {
4179
+ acquireKodaXInlineOwner,
4180
+ enableKodaXDaemonOwner,
4181
+ getKodaXRuntimeOwnerState,
4182
+ } from '@kodax-ai/kodax/runtime';
4183
+
4184
+ const management = await runtime.daemon.inspect();
4185
+ if (!management.preflight.canStop) {
4186
+ showRollbackBlockers(management.preflight.blockers);
4187
+ return;
4188
+ }
4189
+
4190
+ const rollback = await runtime.daemon.stopForInline({
4191
+ expectedRuntimeId: management.runtimeId,
4192
+ expectedRevision: management.revision,
4193
+ expectedOwnerPolicyRevision: management.ownerPolicy.revision,
4194
+ operation: { operationId: loadOrCreatePendingOperationId('coder-inline-rollback') },
4195
+ });
4196
+
4197
+ // `accepted` means inline policy is committed and shutdown is in progress.
4198
+ // Wait through the public owner-state query; never infer release from a PID.
4199
+ const shutdownDeadline = Date.now() + 30_000;
4200
+ while (getKodaXRuntimeOwnerState({ homeDir: kodaxHome, profile: 'coder' }).owner?.runtimeId
4201
+ === rollback.runtimeId) {
4202
+ if (Date.now() >= shutdownDeadline) throw new Error('Timed out waiting for daemon owner release.');
4203
+ await delay(25);
4204
+ }
4205
+ const releasedOwner = getKodaXRuntimeOwnerState({ homeDir: kodaxHome, profile: 'coder' });
4206
+ if (releasedOwner.ownerStatus !== 'unowned') {
4207
+ throw new Error('Coder profile acquired a different owner during rollback.');
4208
+ }
4209
+ await runtime.close(); // Detach only; it does not perform a second stop.
4210
+ const inlineOwner = acquireKodaXInlineOwner({ homeDir: kodaxHome, profile: 'coder' });
4211
+
4212
+ // Later, after the inline owner has released its fence:
4213
+ inlineOwner.close();
4214
+ const daemonPolicy = enableKodaXDaemonOwner({ homeDir: kodaxHome, profile: 'coder' });
4215
+ // daemonPolicy.revision is authoritative; no expectedRevision guess is needed.
4216
+ ```
4217
+
4218
+ Any management revision change, another logical client, active or queued run,
4219
+ running/paused Workflow, non-terminal/unknown AgentTask, pending
4220
+ AskUser/permission, or in-flight mutation returns structured `conflict`; the
4221
+ daemon remains running and policy remains unchanged. Draining also rejects
4222
+ credential and Host Tool state changes without journaling their secrets or
4223
+ results. The inline policy is sticky: later CLI auto-start is rejected until
4224
+ `enableKodaXDaemonOwner()` changes it back to `daemon`. `runtime.close()` still
4225
+ only detaches. Stale-owner handling validates the owned lock/state and never
4226
+ kills a process merely because a PID was reused.
4227
+
4228
+ Keep all trusted objects in Electron Main: daemon token/endpoint, stable client
4229
+ identity, operation IDs, owner policy, keychain broker, Host Tool handlers, and
4230
+ permission-grant administration. Renderer IPC should expose product-specific
4231
+ commands and sanitized projections only. Never pass daemon credentials,
4232
+ leases, operation epochs, or trusted session/run context to renderer or model
4233
+ tool arguments.
4234
+
4235
+ ---
4236
+
4237
+ ## 24. Runtime-owned Auto Mode and plan-approval bridges (v0.7.72–v0.7.73)
4238
+
4239
+ Auto Mode is a Runtime session contract, including in shared-daemon mode. Do
4240
+ not implement a second classifier or decide permissions from a client-side
4241
+ `beforeToolExecute` hook before the Runtime has classified the call.
4242
+
4243
+ ### Configure the session, not an individual UI callback
4244
+
4245
+ ```ts
4246
+ await runtime.sessions.updateSettings(session.id, {
4247
+ permissionMode: 'auto',
4248
+ autoModeEngine: 'llm',
4249
+ autoModeClassifierModel: 'zhipu:glm-5.2', // optional; otherwise follow the run model
4250
+ autoModeTimeoutMs: 20_000, // positive safe integer, optional
4251
+ autoModeSpeculativeWindowMs: 0, // non-negative safe integer, optional
4252
+ executionCwd: projectDirectory,
4253
+ });
4254
+ ```
4255
+
4256
+ All three Auto fields are durable session settings in inline, Worker, and
4257
+ daemon forms. A `null` patch removes an optional override. Timeout must be a
4258
+ positive safe integer; speculative window must be non-negative, so `0` is a
4259
+ valid request to wait for the actual verdict. Daemon capability discovery
4260
+ advertises all fields in `sharedSessionSettings.keys`.
4261
+
4262
+ SDK hosts that need config precedence without creating a REPL can reuse the
4263
+ same typed resolver as KodaX:
4264
+
4265
+ ```ts
4266
+ import {
4267
+ loadAutoModeSettings,
4268
+ resolveAutoModeSettings,
4269
+ type ResolveAutoModeSettingsInput,
4270
+ } from '@kodax-ai/kodax/repl';
4271
+
4272
+ const persisted = loadAutoModeSettings(process.env);
4273
+ const preview = resolveAutoModeSettings({
4274
+ settings: { engine: 'llm', speculativeWindowMs: 0 },
4275
+ env: process.env,
4276
+ });
4277
+ ```
4278
+
4279
+ `resolveAutoModeSettings()` is pure. `loadAutoModeSettings()` reads the KodaX
4280
+ config once and delegates to that resolver; `loadConfig()` declares and
4281
+ returns the same optional `autoMode` object.
4282
+
4283
+ Starting with the v0.7.73 patch, `permissionMode: 'auto'` with an omitted
4284
+ `autoModeEngine` still means the documented `llm` default and is still owned by
4285
+ Runtime. If neither `autoModeClassifierModel` nor the effective run/session/
4286
+ Runtime model exists, `runs.start()` rejects with
4287
+ `RuntimeAutoModeConfigurationError` (`code:
4288
+ 'auto_mode_classifier_model_required'`, `recoverable: true`) before provider
4289
+ construction, a classifier call, or a pending permission. Blank and malformed
4290
+ classifier model specs are rejected by the same typed configuration boundary;
4291
+ a live rules-to-LLM switch is blocked rather than converted into approval work.
4292
+
4293
+ Direct consumers of `createAutoModeToolGuardrail()` receive the same terminal
4294
+ model boundary. After resolving CLI/env/session/settings/live-default
4295
+ precedence, an empty effective model returns a local configuration `block`
4296
+ before provider lookup. It does not call `askUser`, mutate the denial or
4297
+ circuit-breaker trackers, or change the engine to rules. An explicit non-empty
4298
+ classifier override remains valid even when the main-session model is empty.
4299
+
4300
+ The Runtime owns one serialized permission-settings stream and one shared
4301
+ engine/denial/breaker state per Session. It reuses bounded context-specific
4302
+ guardrails across turns while provider/model, repository boundary, execution
4303
+ directory, classifier model, and timeout remain the same. Updating one of
4304
+ those inputs selects a new context guardrail by design without copying stale
4305
+ state from a queued turn. Active runs, queued runs, explicit settings updates,
4306
+ and automatic LLM-to-rules fallback merge through the same Session mutation
4307
+ queue; fallback is persisted before a later classification reads the engine.
4308
+
4309
+ ### What an embedder should expect
4310
+
4311
+ The Runtime's execution order is fixed:
4312
+
4313
+ ```text
4314
+ Runtime Auto Mode guardrail -> host permission bridge only for escalate -> tool execution
4315
+ ```
4316
+
4317
+ Consequently, an LLM/rules `allow` does not create a pending permission request
4318
+ just because a host installed a static approval hook. `block` does not become a
4319
+ spurious approval prompt. A real `escalate` uses the existing shared
4320
+ `runtime.permissions` flow, so another authorized client may render and answer
4321
+ it. Hosts should subscribe to permission events to display such a request, but
4322
+ must not treat a missing request as an error for a safe tool call.
4323
+
4324
+ The classifier deadline remains 20 seconds by default and includes connection
4325
+ setup, provider Retry-After/backoff, inference, and stream completion. KodaX
4326
+ does not solve timeouts by extending that deadline indefinitely. Before the
4327
+ provider call it removes assistant prose/thinking and image paths, limits each
4328
+ tool result to 2 KiB and the serialized permission-relevant transcript to
4329
+ 8 KiB, then enforces 16 KiB action and 32 KiB total-prompt ceilings plus a
4330
+ 256-token output cap. An oversized action or prompt escalates without a
4331
+ provider call; it is never truncated into an automatic allow. These limits are
4332
+ owned by `classify()` itself, so custom callers cannot accidentally bypass the
4333
+ session-history boundary.
4334
+
4335
+ `ClassifyDecision.diagnostics` and the lower-level
4336
+ `SideQueryResult.diagnostics` expose provider, model, effective timeout,
4337
+ elapsed time, retry count/wait, and a coarse terminal phase without including
4338
+ the prompt, action, messages, or response text. `pre_output` means no non-empty
4339
+ text delta was observed; `streaming` means output began before termination.
4340
+ `firstOutputMs` and `streamMs` are present only when the provider adapter emits
4341
+ a text delta. The current provider API cannot honestly separate DNS/connect,
4342
+ TLS, provider queueing, and inference, so embedders must not infer those stages
4343
+ from `pre_output`.
4344
+
4345
+ The permission event's `inputPreview` is a display-safe diagnostic projection:
4346
+ it is bounded, credential-redacted, valid JSON, and includes the effective
4347
+ execution directory. Use the Runtime owner’s typed tool input for execution;
4348
+ do not reconstruct or authorize a tool from the preview. `gitRoot` remains the
4349
+ session repository safety boundary, whereas relative operands resolve from the
4350
+ validated `executionCwd`. In particular, quoted Python/JavaScript/regexp source
4351
+ inside a shell command is not a path operand.
4352
+
4353
+ The user-level `.kodax` directory is a credential/configuration boundary, not
4354
+ an ordinary project path. Direct shell mutations, output redirects, and
4355
+ recognized nested-shell payloads whose target is provably beneath that
4356
+ directory are rejected before LLM classification. The check is segment-safe
4357
+ and Windows case-insensitive. KodaX deliberately does not scan arbitrary
4358
+ quoted language source for path-looking substrings: doing so would turn Python,
4359
+ JavaScript, YAML, and regular expressions into false Tier-0 matches. Trusted
4360
+ configuration changes should use the KodaX config CLI or SDK configuration API.
4361
+
4362
+ ### 0.7.x source compatibility
4363
+
4364
+ The v0.7.72 public declarations retain the following migration aliases:
4365
+
4366
+ | Legacy source | Current source | Contract |
4367
+ |---|---|---|
4368
+ | `agentMode: 'amaw'` | `'ama'` | accepted as deprecated input and normalized to AMA; no separate AMAW runtime is restored |
4369
+ | `SkillSource` | `ResolvedSkillSource` | formal source union remains `project \| user \| plugin \| builtin`; only resolved discovery output adds `learned` |
4370
+ | `RuntimeDaemonPreflight.activeAgentTasks` | `activeAgentTurns` | both required fields are returned and reference the same array throughout the 0.7.x line |
4371
+
4372
+ ### Plan capability is opt-in
4373
+
4374
+ `exit_plan_mode` is exposed to a Runtime run only when that run supplies an
4375
+ approval bridge. A daemon or headless host that cannot approve a plan should
4376
+ leave the bridge absent; KodaX removes the tool from that run's scope.
4377
+
4378
+ ```ts
4379
+ const planned = await runtime.runs.start({
4380
+ sessionId: session.id,
4381
+ prompt: 'Draft a migration plan, then ask for approval.',
4382
+ options: {
4383
+ events: {
4384
+ exitPlanMode: async () => showPlanAndAskUser(),
4385
+ },
4386
+ },
4387
+ });
4388
+ await planned.result;
4389
+ ```
4390
+
4391
+ The callback belongs in a trusted host process (for Electron, Main rather than
4392
+ renderer). It is intentionally not inferred from the presence of a permission
4393
+ UI: tool permission and plan approval are different user decisions.
4394
+
4395
+ See [ADR-056](ADR.md#adr-056-runtime-owns-auto-mode-permission-decisions-and-host-capability-exposure)
4396
+ for the ownership decision, [the v0.7.72 design](features/v0.7.72.md#2026-07-18-runtime-permission-queue-and-resume-closure)
4397
+ for the release boundary, and [Known Issue 187](KNOWN_ISSUES.md#187-shared-daemon-auto-permission-ownership-upgrade-fencing-preview-bounds-and-sdk-compatibility-were-incomplete)
4398
+ for the final capability-upgrade and compatibility closure.
4399
+
4400
+ ---
4401
+
4402
+ ## 25. Always-on context compaction and bounded transcript recovery (v0.7.74)
4403
+
4404
+ Automatic large compaction is always enabled. `enabled` remains accepted for
4405
+ v0.7.x source compatibility, but `false` is normalized to `true`.
4406
+ `triggerPercent` defaults to `75` and is clamped to `15..90`. The optional
4407
+ absolute threshold is inactive when omitted or zero; otherwise the smaller
4408
+ percentage, absolute, and physical-capacity threshold wins.
4409
+
4410
+ ```ts
4411
+ const run = await startKodaX({
4412
+ provider: 'zhipu-coding',
4413
+ model: 'glm-5.2',
4414
+ compaction: {
4415
+ triggerPercent: 60,
4416
+ triggerTokens: 300_000,
4417
+ },
4418
+ });
4419
+ ```
4420
+
4421
+ The recent raw tail is 20% of that effective trigger, not 20% of the model's
4422
+ maximum context window. A manual Runtime compact bypasses only the trigger
4423
+ comparison and uses the Session's same effective policy:
4424
+
4425
+ ```ts
4426
+ await runtime.sessions.updateSettings(session.id, {
4427
+ compactionTriggerPercent: 60,
4428
+ compactionTriggerTokens: 300_000,
4429
+ });
4430
+
4431
+ await runtime.sessions.compact({ sessionId: session.id });
4432
+ ```
4433
+
4434
+ Setting `compactionTriggerTokens: 0` removes the absolute Session override.
4435
+ Percentage updates are normalized to `15..90`; negative/fractional absolute
4436
+ values are rejected. Explicit per-run `options.compaction` values override
4437
+ Session settings.
4438
+
4439
+ Large compaction covers the complete eligible prefix once, preserves an atomic
4440
+ recent tail, and installs a synthetic user checkpoint. Every genuine user query
4441
+ is rendered mechanically in its checkpoint ledger; tool-result wire messages
4442
+ and synthetic prompts are excluded. The normal summary request preserves the
4443
+ main request's system, message, tool, model, and reasoning prefix and appends a
4444
+ text-only ephemeral instruction so providers can reuse prompt/KV cache.
4445
+
4446
+ The exact pre-compaction transcript has a separate durability guarantee. The
4447
+ root host persists all pre-compaction messages (including messages created in
4448
+ the active Run) before old payload is evicted from memory. Island records are
4449
+ flushed before a slim main JSONL is published. If the main replacement fails,
4450
+ main and sidecar may temporarily overlap, but stable entry IDs project the
4451
+ logical entry once. A persistence failure keeps the exact live copy; child
4452
+ compaction never writes root Session lineage.
4453
+
4454
+ The in-process `onCompactedMessages` callback may return a `Promise`. KodaX
4455
+ awaits it before the next provider request and before
4456
+ `context.compaction.finished`. Embedded and daemon Runtime execution always
4457
+ uses Runtime-owned Session storage, regardless of a client-side
4458
+ `persistedByHost` value; a daemon client cannot be the durability owner because
4459
+ its callback is not present in the daemon process. Runtime-backed Ink and
4460
+ classic REPL hosts therefore update only their live projection after the
4461
+ Runtime acknowledgement; they do not perform a second Session write. If a
4462
+ headless Runtime Run compacts before its first routine snapshot, Runtime seeds
4463
+ the new Session from explicit Run metadata before applying the exact compact
4464
+ transaction. A rejected durability callback also rolls back the tentative
4465
+ `contextRevision`, so a later successful compact does not expose a phantom gap.
4466
+
4467
+ ### Context-owned events
4468
+
4469
+ `context.compaction.finished` is the canonical post-commit Runtime fact. It
4470
+ includes stable root/child identity, revision, before/after tokens, strategy,
4471
+ effective trigger, protected budget, and component accounting. Consumers must
4472
+ not use `scope: 'worker'` as a parent/child identity substitute.
4473
+
4474
+ ```ts
4475
+ const subscription = runtime.events.subscribe(
4476
+ { sessionId: session.id, types: ['context.compaction.finished'] },
4477
+ (event) => {
4478
+ if (event.type !== 'context.compaction.finished') return;
4479
+ const fact = event.payload;
4480
+ if (fact.contextKind === 'root' && fact.committed) {
4481
+ renderRootContext(fact.tokensAfter, fact.tokensBefore);
4482
+ }
4483
+ },
4484
+ );
4485
+ ```
4486
+
4487
+ Legacy `onCompactStats`/`onCompact` callbacks remain compatibility projections.
4488
+ The old `onCompact` callback now receives the post-compact count; it no longer
4489
+ echoes the pre-compact `currentTokens` value. Hosts that need ownership or
4490
+ component metrics should use `onContextCompactionFinished` or the Runtime
4491
+ event. Compatibility success callbacks fire only after a strict token
4492
+ reduction has restored physical request validity and committed. An unchanged,
4493
+ failed, stale, or still-oversized candidate is not a successful compaction.
4494
+
4495
+ ### Transcript observation below the daemon frame limit
4496
+
4497
+ `sessions.observe()` no longer embeds `FullTranscriptSessionData`. Its snapshot
4498
+ contains a bounded `RuntimeTranscriptSlice`. Inline entries carry the complete
4499
+ transcript entry; an oversized entry carries an explicit descriptor.
4500
+
4501
+ ```ts
4502
+ const observation = await runtime.sessions.observe(session.id, onLiveEvent);
4503
+ let page = observation.snapshot.transcript;
4504
+
4505
+ while (page) {
4506
+ for (const descriptor of page.entries) {
4507
+ if (descriptor.entry) consume(descriptor.entry);
4508
+ else await consumeEntryChunks(runtime, session.id, page.revision, descriptor.index);
4509
+ }
4510
+ if (!page.hasMore) break;
4511
+ page = await runtime.sessions.transcriptPage({
4512
+ sessionId: session.id,
4513
+ cursor: page.nextCursor,
4514
+ });
4515
+ }
4516
+ ```
4517
+
4518
+ `transcriptEntryChunk()` returns lossless `base64-json` chunks. Concatenate the
4519
+ decoded bytes and parse JSON only after `hasMore` becomes false. Page and entry
4520
+ cursors are opaque and revision-bound; a changed transcript produces an
4521
+ explicit resync error, so restart from a fresh observation. The shared daemon's
4522
+ legacy `session.transcript` method rejects payloads above 512 KiB and names the
4523
+ page/chunk methods rather than attempting a frame near the 8 MiB ceiling.
4524
+
4525
+ ### Search compacted history before fetching exact content
4526
+
4527
+ Use `transcriptSearch()` when the host or user knows a historical detail but
4528
+ not its page/index. It searches the authoritative main-plus-sidecar lineage and
4529
+ returns bounded revision-bound hits with stable `entryId`/`logicalId`, entry
4530
+ index, role/source, timestamp, active/compacted status, and a citation:
4531
+
4532
+ ```ts
4533
+ const found = await runtime.sessions.transcriptSearch({
4534
+ sessionId: session.id,
4535
+ query: 'permission test output capture',
4536
+ role: 'assistant',
4537
+ limit: 5,
4538
+ });
4539
+
4540
+ for (const hit of found.hits) {
4541
+ renderSearchHit(hit.citation, hit.snippet);
4542
+ // For an oversized exact entry, pass found.revision + hit.entryIndex to
4543
+ // transcriptEntryChunk(); ordinary entries can be obtained from the page.
4544
+ }
4545
+ ```
4546
+
4547
+ Search is deterministic Unicode lexical/metadata ranking, not an embedding or
4548
+ background-model index. The Action LLM gets the corresponding
4549
+ `session_history_search` and `session_history_read` pair only when its current
4550
+ Run owns full-lineage-capable Session storage. A root Run reads its root
4551
+ lineage. A persistent child Run gets a separately minted hidden
4552
+ `managed-task-worker` Session and can recover only that child's compacted
4553
+ history; it is never given root-history access. Storage-less Runs and a tool
4554
+ visibility policy that hides either member expose neither member. Results are
4555
+ low-authority historical evidence; current instructions and freshly verified
4556
+ workspace state take precedence. System/control entries, hidden-only content,
4557
+ synthetic current or legacy `[对话历史摘要]` checkpoints, and `[compacted]`
4558
+ placeholders are neither searchable nor directly readable. Short ordinary
4559
+ terms do not gain a metadata match merely because they occur inside a random
4560
+ entry ID; direct identifier lookup is reserved for a sufficiently specific ID
4561
+ query. Sessions compacted by older builds without an exact main/sidecar copy
4562
+ cannot reconstruct bytes that were already discarded.
4563
+
4564
+ Clients that depend on these guarantees should require
4565
+ `contextCompaction: 3`, `transcriptPaging: 1`, and `transcriptSearch: 1` during
4566
+ connection.
4567
+
4568
+ ---
4569
+
4570
+ ## 26. Agent mailbox control versus SDK event telemetry (v0.7.74)
4571
+
4572
+ The model-visible `wait_agent` tool and the public Runtime Actor event APIs have
4573
+ different jobs. Do not expose `runtime.agents.wait()` to the model as though it
4574
+ were the same operation.
4575
+
4576
+ | Need | API | Wake/data contract |
4577
+ |---|---|---|
4578
+ | Let the action model yield until useful coordination evidence exists | `wait_agent({ timeout_ms })` | Caller-scoped Agent mailbox, root user input, interruption, or timeout; returns only a small acknowledgement. |
4579
+ | Render or diagnose Actor activity | `runtime.agents.events(sessionId, afterSequence?)` | Bounded event snapshot/replay, including progress and terminal events. |
4580
+ | Long-poll Actor telemetry from a host | `runtime.agents.wait(sessionId, afterSequence?, timeoutMs?)` | Returns the next sequenced Actor event, including progress. |
4581
+ | Read one known result | `runtime.agents.output(sessionId, actorPath, turnId?)` | Bounded current/terminal output and structured artifact metadata. |
4582
+ | Deliver a real user follow-up to the active root Run | `runtime.runs.submitInput(...)` | Ordered active-run input delivered at the next safe Runner boundary; requires `interruptInput:1`. |
4583
+
4584
+ For example, a host activity view can replay the current tail and then wait
4585
+ from its last sequence without causing another action-model request:
4586
+
4587
+ ```ts
4588
+ const snapshot = await runtime.agents.events(session.id);
4589
+ for (const event of snapshot) renderActorEvent(event);
4590
+
4591
+ const afterSequence = snapshot.at(-1)?.sequence;
4592
+ const next = await runtime.agents.wait(session.id, afterSequence, 30_000);
4593
+ if (next) renderActorEvent(next);
4594
+ ```
4595
+
4596
+ Model `wait_agent` has only `timeout_ms` in its schema (10,000 to 3,600,000 ms,
4597
+ default 120,000). Actor progress and Runtime `system-reminder` messages do not
4598
+ end that wait. A scoped Agent message/completion produces `mailbox`; queued root
4599
+ input produces `user_input_pending`; cancellation and expiry produce
4600
+ `interrupted` and `wait_expired`. Authenticated Agent evidence is drained at the
4601
+ next safe Runner boundary as synthetic context, while root input remains a real
4602
+ user turn. The acknowledgement itself never carries raw event batches.
4603
+
4604
+ Completion delivery is post-transcript and crash-recoverable. The Actor snapshot
4605
+ persists the explicit root completion turn IDs that still await transcript
4606
+ acknowledgement. A hard restart republishes only those IDs; a same-process
4607
+ Runtime rebuild deduplicates the projected queue by child turn ID. Once the
4608
+ parent transcript commits and acknowledges the completion, later restores do
4609
+ not replay it. Legacy snapshots without the explicit pending set do not infer
4610
+ replay work from historical mailbox content.
4611
+
4612
+ This separation changes no Actor event capability or daemon version: existing
4613
+ SDK snapshot, replay, and long-poll clients keep their telemetry surface. It
4614
+ only prevents high-frequency progress from becoming a model control signal.
4334
4615
 
4335
- The callback belongs in a trusted host process (for Electron, Main rather than
4336
- renderer). It is intentionally not inferred from the presence of a permission
4337
- UI: tool permission and plan approval are different user decisions.
4616
+ ---
4338
4617
 
4339
- See [ADR-056](ADR.md#adr-056-runtime-owns-auto-mode-permission-decisions-and-host-capability-exposure)
4340
- for the ownership decision, [the v0.7.72 design](features/v0.7.72.md#2026-07-18-runtime-permission-queue-and-resume-closure)
4341
- for the release boundary, and [Known Issue 187](KNOWN_ISSUES.md#187-shared-daemon-auto-permission-ownership-upgrade-fencing-preview-bounds-and-sdk-compatibility-were-incomplete)
4342
- for the final capability-upgrade and compatibility closure.
4618
+ ## 27. Windows GUI background subprocess visibility (v0.7.75)
4619
+
4620
+ KodaX SDK hosts do not need to add process-wide console suppression around the
4621
+ Runtime. In the v0.7.75 release candidate, Runtime Worker-reachable
4622
+ non-interactive/background child processes request `windowsHide: true` at their
4623
+ own spawn boundary. The covered paths include:
4624
+
4625
+ - memory and Git metadata probes;
4626
+ - provider CLI execution and ACP servers;
4627
+ - LSP acquisition and language servers;
4628
+ - clipboard helpers, worktrees, review commands, and extension commands;
4629
+ - managed-task checkpoints and sandbox helpers.
4630
+
4631
+ The contract is intentionally narrow. Explicit external editors, terminal
4632
+ commands, and PTY sessions remain interactive. POSIX-only `ps`, `tmux`, and
4633
+ sandbox branches are reviewed bundle-audit exceptions rather than Windows
4634
+ visibility paths.
4635
+
4636
+ `npm run build:bundle` audits every statically identifiable child-process call
4637
+ reachable from `dist/runtime-worker.js`. The packaged Electron daemon smoke then
4638
+ runs 20 ordinary queries with a Win32 probe and checks that the expected Git
4639
+ children never own a visible console window. These checks validate the SDK
4640
+ boundary, but they do not replace product-level validation in the packaged host.
4641
+ KodaX Space should install the exact v0.7.75 tarball and complete
4642
+ [`ISSUE_205_v0.7.75_REGRESSION_GUIDE.md`](test-guides/ISSUE_205_v0.7.75_REGRESSION_GUIDE.md)
4643
+ on Windows 10 and Windows 11 as a non-blocking product validation follow-up.
4644
+ This follow-up does not gate SDK packaging, tagging, or publication.
4343
4645
 
4344
4646
  ---
4345
4647
 
@@ -4348,4 +4650,8 @@ for the final capability-upgrade and compatibility closure.
4348
4650
  - [README.md](../README.md) — end-user CLI quick start
4349
4651
  - [docs/ADR.md ADR-024](ADR.md#adr-024-npm-发布物正名-kodax-aikodax--sdk-subpath-exports-形式化-v0739) — SDK subpath architecture rationale
4350
4652
  - [docs/ADR.md ADR-032](ADR.md#adr-032-sdk-embedder-surface-closure-feature_186-v0742) — FEATURE_186 design record (all 8 phases)
4351
- - [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
4653
+ - [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
4654
+ - [docs/ADR.md ADR-058](ADR.md#adr-058-model-agent-wait-is-mailbox-control-not-event-telemetry) — mailbox control versus Actor telemetry
4655
+ - [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
4656
+ - [docs/features/v0.7.74.md](features/v0.7.74.md) — v0.7.74 release-candidate design and verification record
4657
+ - [docs/features/v0.7.75.md](features/v0.7.75.md) — v0.7.75 Windows GUI and Sidecar/Runtime stabilization candidate