@kodax-ai/kodax 0.7.67 → 0.7.69

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 (102) hide show
  1. package/CHANGELOG.md +209 -3
  2. package/README.md +61 -9
  3. package/README_CN.md +58 -9
  4. package/config-templates/config.example.jsonc +26 -0
  5. package/config-templates/integrations/a2a.example.jsonc +54 -0
  6. package/config-templates/integrations/extensions.example.jsonc +7 -0
  7. package/config-templates/integrations/mcp.example.jsonc +16 -0
  8. package/dist/chunks/agent-DQRXT6M7.js +2 -0
  9. package/dist/chunks/argument-completer-RSK6CCGP.js +2 -0
  10. package/dist/chunks/chunk-4WIODYOH.js +329 -0
  11. package/dist/chunks/chunk-AWMTNUDS.js +37 -0
  12. package/dist/chunks/chunk-F7C7J6IM.js +7 -0
  13. package/dist/chunks/chunk-I4TPQEJN.js +5 -0
  14. package/dist/chunks/chunk-M7TCFYTO.js +107 -0
  15. package/dist/chunks/chunk-NGHQIGVW.js +46 -0
  16. package/dist/chunks/chunk-NGJURAY5.js +306 -0
  17. package/dist/chunks/{chunk-KBBGDRER.js → chunk-PA76WUBL.js} +2 -2
  18. package/dist/chunks/chunk-PQ3XUSY2.js +357 -0
  19. package/dist/chunks/chunk-UJEMSPM5.js +78 -0
  20. package/dist/chunks/{chunk-DEICMTHO.js → chunk-URC6ZI6P.js} +1 -1
  21. package/dist/chunks/{chunk-EN3O2C43.js → chunk-VAZ25MDX.js} +122 -122
  22. package/dist/chunks/chunk-VYKFM3TB.js +751 -0
  23. package/dist/chunks/chunk-WW2O2DEP.js +2 -0
  24. package/dist/chunks/chunk-Y4WOTWUC.js +2 -0
  25. package/dist/chunks/chunk-Z4PPLJF2.js +60 -0
  26. package/dist/chunks/chunk-ZG4DMYBS.js +422 -0
  27. package/dist/chunks/compaction-config-ABLL6UH3.js +2 -0
  28. package/dist/chunks/{construction-bootstrap-GLEHQ2KB.js → construction-bootstrap-GH6RW6ST.js} +1 -1
  29. package/dist/chunks/dist-DRBKYVHF.js +2 -0
  30. package/dist/chunks/dist-RWL2RBO4.js +2 -0
  31. package/dist/chunks/host-7SAB4WEC.js +2 -0
  32. package/dist/chunks/run-manager-XMDKTONJ.js +2 -0
  33. package/dist/chunks/utils-QYE5Y73D.js +2 -0
  34. package/dist/index.d.ts +19 -19
  35. package/dist/index.js +6 -6
  36. package/dist/kodax_cli.js +1497 -1209
  37. package/dist/runtime-worker.js +1371 -1107
  38. package/dist/sdk-a2a.d.ts +397 -0
  39. package/dist/sdk-a2a.js +9 -0
  40. package/dist/sdk-agent.d.ts +172 -33
  41. package/dist/sdk-agent.js +1 -1
  42. package/dist/sdk-coding.d.ts +126 -51
  43. package/dist/sdk-coding.js +1 -1
  44. package/dist/sdk-experimental-memory.d.ts +618 -0
  45. package/dist/sdk-experimental-memory.js +2 -0
  46. package/dist/sdk-llm.d.ts +16 -9
  47. package/dist/sdk-llm.js +1 -1
  48. package/dist/sdk-mcp.d.ts +1 -1
  49. package/dist/sdk-mcp.js +1 -1
  50. package/dist/sdk-media.d.ts +1 -1
  51. package/dist/sdk-media.js +1 -1
  52. package/dist/sdk-repl.d.ts +35 -101
  53. package/dist/sdk-repl.js +2 -2
  54. package/dist/sdk-runtime.d.ts +744 -21
  55. package/dist/sdk-runtime.js +1 -1
  56. package/dist/sdk-session.d.ts +7 -7
  57. package/dist/sdk-session.js +1 -1
  58. package/dist/sdk-skills.js +1 -1
  59. package/dist/semantic-worker.js +100 -12
  60. package/dist/types-chunks/{base.d-DalIRhbb.d.ts → base.d-CYjtB68X.d.ts} +1 -1
  61. package/dist/types-chunks/{bash-prefix-extractor.d-D57Gt6n8.d.ts → bash-prefix-extractor.d-BkIA8Wto.d.ts} +89 -18
  62. package/dist/types-chunks/{capability-learning.d-BQB0M70w.d.ts → capability-learning.d-WtsyRv3O.d.ts} +1 -1
  63. package/dist/types-chunks/{capsule.d-BZmqu_HL.d.ts → capsule.d-xJvfh4YR.d.ts} +5 -5
  64. package/dist/types-chunks/{commands.d-Cg26ud-Y.d.ts → commands.d-DkPfcQWG.d.ts} +4 -4
  65. package/dist/types-chunks/{cost-tracker.d-CBLk1fiD.d.ts → cost-tracker.d-BeaenODW.d.ts} +4 -1
  66. package/dist/types-chunks/{guardrail.d-Ds7JHxR1.d.ts → guardrail.d-6ZDbNbHO.d.ts} +1 -1
  67. package/dist/types-chunks/{guardrail.d-vSaBe64I.d.ts → guardrail.d-R7AiGfrI.d.ts} +4 -4
  68. package/dist/types-chunks/integration-config.d-ojG4swOP.d.ts +95 -0
  69. package/dist/types-chunks/{manager.d-Zum9cGHU.d.ts → manager.d-CoEuPRAo.d.ts} +4 -1
  70. package/dist/types-chunks/{public-api.d-CPBMPDiU.d.ts → public-api.d-7G4--RZE.d.ts} +6 -7
  71. package/dist/types-chunks/{resolver.d-CGkfRULj.d.ts → resolver.d-CIVoGc97.d.ts} +2 -2
  72. package/dist/types-chunks/{run-manager.d-lnwnfyNC.d.ts → run-manager.d-BaXtkryp.d.ts} +1 -1
  73. package/dist/types-chunks/{sdk-session-ByoMLoUS.d.ts → sdk-session-BQccrODn.d.ts} +6 -4
  74. package/dist/types-chunks/{types.d-DG9B4SMm.d.ts → types.d-BtC4yLYO.d.ts} +170 -13
  75. package/dist/types-chunks/{types.d-DYv3a1ka.d.ts → types.d-C9YHEAmA.d.ts} +38 -7
  76. package/dist/types-chunks/{types.d-DU9lLkmE.d.ts → types.d-CJR7t6iW.d.ts} +2 -2
  77. package/dist/types-chunks/{types.d-Bp4Lm1jv.d.ts → types.d-TTvpAGWf.d.ts} +11 -1
  78. package/dist/types-chunks/{utils.d-DMI-9GTR.d.ts → utils.d-BbB5jzi1.d.ts} +15 -7
  79. package/docs/SDK_EMBEDDER_GUIDE.md +732 -14
  80. package/package.json +14 -2
  81. package/dist/chunks/agent-E4V5SLX6.js +0 -2
  82. package/dist/chunks/argument-completer-N6RT5KYT.js +0 -2
  83. package/dist/chunks/chunk-AKMXJSGM.js +0 -56
  84. package/dist/chunks/chunk-CW32NQFO.js +0 -2
  85. package/dist/chunks/chunk-D6QVXQNI.js +0 -1
  86. package/dist/chunks/chunk-NVDK375I.js +0 -729
  87. package/dist/chunks/chunk-O7N22GJ3.js +0 -31
  88. package/dist/chunks/chunk-PZTG2C33.js +0 -7
  89. package/dist/chunks/chunk-QI6SQ2UQ.js +0 -306
  90. package/dist/chunks/chunk-QJJKMNZF.js +0 -2
  91. package/dist/chunks/chunk-RS2GAABJ.js +0 -328
  92. package/dist/chunks/chunk-W2XMNYLK.js +0 -15
  93. package/dist/chunks/chunk-WCSSXBHR.js +0 -419
  94. package/dist/chunks/chunk-YH5BTUQO.js +0 -343
  95. package/dist/chunks/chunk-YRBNXFHY.js +0 -2
  96. package/dist/chunks/compaction-config-56NO42ZI.js +0 -2
  97. package/dist/chunks/dist-4PXB7U22.js +0 -2
  98. package/dist/chunks/dist-4UIRSSFS.js +0 -2
  99. package/dist/chunks/host-DMREFNSO.js +0 -2
  100. package/dist/chunks/paste-C33GZQV5.js +0 -2
  101. package/dist/chunks/run-manager-P5XMSGZ7.js +0 -2
  102. package/dist/chunks/utils-JENPFKAE.js +0 -2
@@ -28,6 +28,9 @@ are NOT obvious from inspecting the type definitions alone:
28
28
  18. [External-agent executor plane](#18-external-agent-executor-plane-feature_258-v0767)
29
29
  19. [Session surface filtering and cursor pagination](#19-session-surface-filtering-and-cursor-pagination-feature_261-v0767)
30
30
  20. [Cost-disciplined workflow routing and telemetry](#20-cost-disciplined-workflow-routing-and-telemetry-feature_259-v0767)
31
+ 21. [Experimental governed memory — `/experimental-memory`](#21-experimental-governed-memory--experimental-memory-feature_260-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)
31
34
 
32
35
  §1–§3 (and the Phase-7/8 MCP-popout surface in §1) land in v0.7.42
33
36
  under FEATURE_186 (see [ADR-032](ADR.md#adr-032-sdk-embedder-surface-closure-feature_186-v0742)).
@@ -964,7 +967,7 @@ running your project's test suite against `main` — you can `npm link`
964
967
  the in-tree KodaX checkout instead of waiting for a published version.
965
968
 
966
969
  As of v0.7.43 the root `package.json` is in **already-published shape**:
967
- `"name": "@kodax-ai/kodax"` is baked in along with all 8 SDK subpath
970
+ `"name": "@kodax-ai/kodax"` is baked in along with all 10 SDK subpath
968
971
  exports. `npm link` "just works" — no need to run `scripts/release.mjs`
969
972
  first.
970
973
 
@@ -2000,9 +2003,14 @@ By default your `topics` **extend** the KodaX base manual. v0.7.58 adds
2000
2003
 
2001
2004
  `KODAX_UNDERLYING_CAPABILITY_TOPICS` (exported) is the recommended mechanism-topic
2002
2005
  subset a product built on KodaX should keep even in a full replace — so your users
2003
- still get correct answers about the underlying engine (providers / config /
2004
- permissions / tools / skills / extensions / mcp / repo-intelligence / sessions /
2005
- sdk / custom-providers) without the KodaX brand:
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.
2006
2014
 
2007
2015
  ```ts
2008
2016
  import {
@@ -2733,13 +2741,16 @@ fields requires a protocol version bump.
2733
2741
 
2734
2742
  ### Current verification status
2735
2743
 
2736
- The v0.7.67 release validation covers the runtime migration, the Worker
2744
+ The v0.7.69 release validation covers the runtime migration, the Worker
2737
2745
  isolation follow-ups delivered ahead of their original v0.7.71/v0.7.72
2738
- planning slots, and the v0.7.67 external-agent/session additions:
2739
-
2740
- - complete repository test suite on Node 20 and Node 22;
2741
- - package builds, Worker sidecar builds, and self-contained
2742
- `dist/sdk-runtime.d.ts` generation;
2746
+ planning slots, the v0.7.67 external-agent/session additions, the v0.7.68
2747
+ experimental Memory Agent surface, and the v0.7.69 A2A/integration/shared-
2748
+ daemon delivery:
2749
+
2750
+ - Node 20 and Node 22 post-review regression for process cleanup, lock
2751
+ ownership, frozen eval inputs, A2A transport, and the built-in manual;
2752
+ - root `tsc --noEmit`, package builds, bundle builds, and all 12 public DTS
2753
+ entries on Node 20 and Node 22;
2743
2754
  - runtime/daemon/SDK/ACP/REPL integration tests, including process-distinct SDK
2744
2755
  auto-start and multi-client sessions/permissions;
2745
2756
  - Worker Runtime identity, service parity, hard close, capability requirements,
@@ -2751,6 +2762,22 @@ planning slots, and the v0.7.67 external-agent/session additions:
2751
2762
  recovery, policy/credential/artifact gates, and disabled-plane failure;
2752
2763
  - exact session `surface` filtering plus opaque cursor continuation through the
2753
2764
  narrow `/session` API, embedded Runtime, and daemon transport;
2765
+ - scoped zero-wait memory recall, read-only deliberate query, trace-only
2766
+ receipts, bounded episode review, governed consult-before-write promotion,
2767
+ and the self-contained `/experimental-memory` bundle/DTS surface;
2768
+ - bidirectional A2A 1.0 discovery/call/serve, durable task replay, no-code
2769
+ configuration, local-Agent binding, exact Skill-script admission, SSRF/
2770
+ credential boundaries, and the self-contained `/a2a` bundle/DTS surface;
2771
+ - split MCP/A2A/Extension configuration, lossless migration, canonical-template
2772
+ drift checks, last-known-good hot reload, draining, and restart-required
2773
+ classification;
2774
+ - atomic shared-daemon observation/resync, durable operations and same-session
2775
+ ordering, settings/grant CAS, AskUser/permission transport, credential/Host
2776
+ Tool reverse bridges, owner fencing, restart outcomes, and process-distinct
2777
+ client/daemon smokes with credential-canary scans;
2778
+ - a fresh `0.7.69` tarball consumer importing all 11 public subpaths, creating a
2779
+ Worker-hosted session, running the packaged CLI, and checking packaged DTS and
2780
+ Worker sidecars; plus a Windows x64 binary/version/sidecar smoke;
2754
2781
  - external fresh npm consumer installation of the `0.7.67` tarball, proving
2755
2782
  Worker isolation and a distinct daemon PID through the published subpath;
2756
2783
  - Ubuntu Node 22 Unix-domain-socket daemon gate, including two clients sharing
@@ -2763,8 +2790,13 @@ The portable manual gates remain in
2763
2790
  two filenames retain their original planning slots while their content records
2764
2791
  the v0.7.66 delivery. v0.7.67 adds
2765
2792
  `FEATURE_258_v0.7.67_TEST_GUIDE.md`, `FEATURE_259_v0.7.67_TEST_GUIDE.md`, and
2766
- `FEATURE_261_v0.7.67_TEST_GUIDE.md`. The final GitHub Actions run is recorded in
2767
- the v0.7.67 release after the release commit is pushed.
2793
+ `FEATURE_261_v0.7.67_TEST_GUIDE.md`; v0.7.69 adds
2794
+ `FEATURE_267_v0.7.69_TEST_GUIDE.md`, `FEATURE_268_v0.7.69_TEST_GUIDE.md`, and
2795
+ `FEATURE_269_v0.7.69_TEST_GUIDE.md`. The earlier release-preparation Actions
2796
+ run is not reused after the severe-fix review. Fresh GitHub Actions run
2797
+ `29385073422` passes the Node 20/22 build, bundle, DTS, and full-test matrix plus
2798
+ the Node 22 Unix-domain-socket daemon gate. npm publication remains a
2799
+ maintainer-owned manual step.
2768
2800
 
2769
2801
  ---
2770
2802
 
@@ -3070,8 +3102,694 @@ The report is an audit/optimization artifact; correctness still comes from the
3070
3102
  workflow's structured findings, verification results, and quality gates.
3071
3103
 
3072
3104
  ---
3073
-
3074
- ## See also
3105
+
3106
+ ## 21. Experimental governed memory — `/experimental-memory` (FEATURE_260, v0.7.68)
3107
+
3108
+ KodaX has one durable memory plane: the F228 Memory Control Plane. FEATURE_260
3109
+ adds a thin, opt-in agent/session API over that plane; it does not add a second
3110
+ database, filesystem memory actions, a resident memory specialist, or online
3111
+ self-modification.
3112
+
3113
+ Top-level `runKodaX()` coding runs wire this lifecycle automatically. They build
3114
+ an exact-scoped memory pack at session start, keep passive recall off the
3115
+ blocking hot path, expose `memory_recall` only when the memory session starts,
3116
+ record bounded observations/outcomes, and close the session at the run boundary.
3117
+ Use the direct SDK below when a custom host needs to own those boundaries.
3118
+
3119
+ ### Minimal direct session
3120
+
3121
+ ```ts
3122
+ import { createMemoryControlPlane } from '@kodax-ai/kodax/agent';
3123
+ import { createMemoryAgent } from '@kodax-ai/kodax/experimental-memory';
3124
+
3125
+ const identity = {
3126
+ tenantId: 'tenant:acme',
3127
+ workspaceId: 'workspace:desktop',
3128
+ userId: 'user:42',
3129
+ agentId: 'agent:reviewer',
3130
+ projectId: 'project:kodax',
3131
+ sessionId: 'session:20260712',
3132
+ };
3133
+
3134
+ const controlPlane = createMemoryControlPlane({
3135
+ cwd: process.cwd(),
3136
+ identity,
3137
+ projectDocs: [],
3138
+ discoverSkills: false,
3139
+ });
3140
+ const memory = createMemoryAgent({ controlPlane });
3141
+ const session = await memory.startSession({
3142
+ identity,
3143
+ objective: 'Review the runtime shutdown change',
3144
+ });
3145
+
3146
+ const immediate = session.recall({
3147
+ decisionRevision: 'decision:1',
3148
+ objective: 'Review the runtime shutdown change',
3149
+ decisionContext: 'Choosing the first verification step',
3150
+ decisionIntent: 'runtime shutdown regression',
3151
+ throughSequence: 0,
3152
+ });
3153
+ const deliberate = await session.query({
3154
+ decisionRevision: 'decision:2',
3155
+ need: 'What uncommon daemon cleanup failure happened before?',
3156
+ throughSequence: 0,
3157
+ });
3158
+
3159
+ await session.complete({
3160
+ status: 'succeeded',
3161
+ summary: 'Shutdown state replacement verified',
3162
+ evidence: [],
3163
+ });
3164
+ await session.close();
3165
+ ```
3166
+
3167
+ `recall()` is synchronous: exact observations/hints can be returned immediately,
3168
+ while an optional semantic prefetch finishes for a later matching decision.
3169
+ `query()` is deliberate and read-only; one distinct query is admitted per
3170
+ decision epoch, and the result is bounded to at most three prompt-safe hints and
3171
+ 512 estimated tokens. `undefined` means there is no governed reminder to inject.
3172
+
3173
+ ### Evidence, tracing, and persistence boundaries
3174
+
3175
+ - Recalled content is low-authority evidence. Current repository/config/runtime
3176
+ facts must still come from current tools or host state.
3177
+ - `observe()` accepts monotonic, evidence-linked observations and rejects secret
3178
+ material. `rewind()` removes observations after a sequence boundary.
3179
+ - `complete()` can emit an Outcome Digest through `persistOutcomeDigest` and run
3180
+ bounded episode review through `reviewEpisode`; cancellation creates neither.
3181
+ - `onTrace` receives policy-versioned `MemoryDecisionReceipt` metadata that links
3182
+ candidates, selection, injection, and later outcome influence. Receipts are
3183
+ trace-only and contain no hidden reasoning.
3184
+ - Durable memory mutation remains owned by F228's
3185
+ proposal/preview/fingerprint/apply flow. `/memory` is the CLI governance
3186
+ surface; direct file/shell writes to managed memory roots are denied.
3187
+ - Identity/applicability matching is exact and fail-closed. Hosts should supply
3188
+ stable tenant/workspace/user/agent/project/session identifiers and must not put
3189
+ credentials into those identifiers.
3190
+
3191
+ The subpath is experimental and ESM-only. Treat its exported types as opt-in
3192
+ v0.7.x contracts; keep persistence and product policy behind your own adapter.
3193
+
3194
+ ---
3195
+
3196
+ ## 22. Bidirectional A2A 1.0 — `/a2a` (FEATURE_267, v0.7.69)
3197
+
3198
+ `@kodax-ai/kodax/a2a` is the protocol edge for the A2A 1.0 JSON-RPC/SSE
3199
+ profile. It composes the protocol-neutral F258 executor plane with the Runtime
3200
+ facade; `/agent` and `/coding` remain free of A2A wire types and dependencies.
3201
+
3202
+ ### Call an A2A Agent through F258
3203
+
3204
+ Discovery is an explicit host action. The URL must match `allowedOrigins`, and
3205
+ the default safe transport pins the validated DNS address for each connection
3206
+ and independently revalidates redirects, rejects public plain HTTP, bounds
3207
+ time/body/redirects, and strips authorization on a cross-origin redirect. A
3208
+ custom `fetch` option is a trusted transport override: the embedder then owns
3209
+ equivalent DNS-to-connection binding in that transport or proxy.
3210
+
3211
+ ```ts
3212
+ import {
3213
+ createA2AAgentExecutorFactory,
3214
+ discoverA2ARegistration,
3215
+ } from '@kodax-ai/kodax/a2a';
3216
+ import { createKodaXRuntime } from '@kodax-ai/kodax/runtime';
3217
+
3218
+ const client = {
3219
+ networkPolicy: {
3220
+ allowedOrigins: ['https://reviewer.example'],
3221
+ allowPrivateAddresses: false,
3222
+ requestTimeoutMs: 10_000,
3223
+ maxResponseBytes: 1_048_576,
3224
+ maxRedirects: 2,
3225
+ },
3226
+ pollIntervalMs: 500,
3227
+ } as const;
3228
+
3229
+ const discovered = await discoverA2ARegistration({
3230
+ agentId: 'external:a2a-reviewer',
3231
+ agentCardUrl: 'https://reviewer.example/.well-known/agent-card.json',
3232
+ credentialRef: 'a2a/reviewer',
3233
+ effects: { remote: 'read' },
3234
+ }, client);
3235
+
3236
+ const runtime = await createKodaXRuntime({
3237
+ mode: 'embedded',
3238
+ isolation: 'inline',
3239
+ externalAgents: {
3240
+ factories: [createA2AAgentExecutorFactory(client)],
3241
+ credentialBroker: {
3242
+ async withCredential(ref, use) {
3243
+ if (ref !== 'a2a/reviewer') throw new Error('Unknown credential reference.');
3244
+ return use(process.env.A2A_REVIEWER_TOKEN ?? '');
3245
+ },
3246
+ },
3247
+ policy: ({ registration }) => ({ allowed: registration.effects.remote === 'read' }),
3248
+ defaultContext: { actorId: 'a2a-host' },
3249
+ },
3250
+ });
3251
+
3252
+ await runtime.admin.agentRegistrations.upsert(discovered.registration);
3253
+ const started = await runtime.agentTasks.start({
3254
+ agentId: discovered.registration.agentId,
3255
+ objective: 'Review this change and return cited findings.',
3256
+ context: { actorId: 'a2a-host' },
3257
+ readOnly: true,
3258
+ expectedConfigurationRevision: discovered.registration.configurationRevision,
3259
+ });
3260
+ const terminal = await runtime.agentTasks.wait(started.taskId, 60_000);
3261
+ ```
3262
+
3263
+ The executor supports durable task start/get, input continuation, cancel,
3264
+ reconcile, SSE events, and polling fallback. An ambiguous start is not retried
3265
+ automatically. A `credentialRef` is resolved just in time by the F258 broker;
3266
+ the registration, task store, and diagnostics never contain the credential.
3267
+
3268
+ ### Built-in configured path (no host code)
3269
+
3270
+ The CLI product path stores one user document at
3271
+ `~/.kodax/integrations/a2a.json` and uses the same F258 plane:
3272
+
3273
+ ```bash
3274
+ kodax a2a add reviewer https://reviewer.example/.well-known/agent-card.json \
3275
+ --credential-env A2A_REVIEWER_TOKEN --effect read
3276
+ kodax a2a test reviewer
3277
+ kodax a2a call reviewer "Review this document"
3278
+ ```
3279
+
3280
+ Embedded CLI Runtimes and the user-owned daemon automatically reconcile these
3281
+ entries as `external:<name>`. Discovery/update failure retains that entry's
3282
+ last-known-good registration; another entry can still update. The environment
3283
+ broker resolves `credentialEnv` only at call time. Automatic Runtime
3284
+ registration accepts public HTTPS and exact loopback targets; explicit private
3285
+ network access remains an operator action on the direct CLI/SDK path.
3286
+
3287
+ Inbound publication is also no-code:
3288
+
3289
+ ```bash
3290
+ export KODAX_A2A_TOKEN='replace-with-a-long-random-token'
3291
+ kodax a2a expose # Runtime default Agent
3292
+ kodax a2a expose document-agent # ~/.kodax/agents/document-agent.md
3293
+ kodax a2a serve --port 8765
3294
+ ```
3295
+
3296
+ `expose` validates a named user Markdown Agent before writing its reference.
3297
+ `serve` loads configured MCP and Extensions before it resolves the execution
3298
+ binding or opens a socket. Native workspace read tools are admitted by
3299
+ workspace access; writes, narrow Extension Tools, MCP capabilities, subagents,
3300
+ and isolated Skill scripts require their corresponding exact `toolPolicy`
3301
+ authority. Internal Skills come from `~/.kodax/skills`, `~/.agents/skills`,
3302
+ plugins, and built-ins; public Agent Card skills are a separate explicit
3303
+ projection and never reveal the private Skill inventory.
3304
+
3305
+ The running server pins Agent, Skill, workspace, tool registration, process and
3306
+ store revisions. Card/auth/limits can hot reload; execution-authority changes
3307
+ require an explicit restart. Managed contexts live below
3308
+ `~/kodax_a2a_server_workspace/<profile>/contexts/`. Exact Skill scripts require
3309
+ `process: isolated`, an admitted `scripts/...` path, and a passing
3310
+ `kodax sandbox doctor`; KodaX never falls back to an unsandboxed shell.
3311
+
3312
+ ### Publish one KodaX Agent
3313
+
3314
+ Publication is host-owned and opt-in. The public card describes only the
3315
+ configured Agent, media types, and skills. Authentication runs before task
3316
+ lookup; authorization runs per operation; task visibility is principal-scoped.
3317
+
3318
+ ```ts
3319
+ import { createKodaXA2AServer } from '@kodax-ai/kodax/a2a';
3320
+ import { createKodaXRuntime } from '@kodax-ai/kodax/runtime';
3321
+
3322
+ const runtime = await createKodaXRuntime({ mode: 'embedded', isolation: 'inline' });
3323
+ const server = createKodaXA2AServer({
3324
+ runtime,
3325
+ dataDir: '/var/lib/kodax/a2a',
3326
+ agent: {
3327
+ name: 'KodaX Reviewer',
3328
+ description: 'Reviews bounded code changes.',
3329
+ version: '1.0.0',
3330
+ publicBaseUrl: 'https://kodax.example',
3331
+ skills: [{ id: 'review', name: 'Review', description: 'Review code.', tags: ['code'] }],
3332
+ inputModes: ['text/plain'],
3333
+ outputModes: ['text/plain'],
3334
+ },
3335
+ authentication: {
3336
+ securitySchemes: { bearer: { httpAuthSecurityScheme: { scheme: 'Bearer' } } },
3337
+ securityRequirements: [{ schemes: { bearer: { list: [] } } }],
3338
+ async authenticate(request) {
3339
+ return request.headers.get('authorization') === `Bearer ${process.env.KODAX_A2A_TOKEN}`
3340
+ ? { subject: 'trusted-orchestrator', scopes: ['a2a'] }
3341
+ : null;
3342
+ },
3343
+ },
3344
+ async authorize({ principal }) { return principal.scopes.includes('a2a'); },
3345
+ limits: {
3346
+ maxRequestBytes: 1_048_576,
3347
+ maxPartBytes: 524_288,
3348
+ maxConcurrentTasks: 8,
3349
+ maxTaskWaitMs: 30_000,
3350
+ maxActiveTasksPerPrincipal: 8,
3351
+ maxRetainedTasksPerPrincipal: 64,
3352
+ maxEventsPerTask: 1_000,
3353
+ maxEventBytesPerTask: 16_777_216,
3354
+ maxWorkspaceBytesPerContext: 1_073_741_824,
3355
+ },
3356
+ });
3357
+
3358
+ // Development only: the built-in listener refuses non-loopback hosts.
3359
+ const localBaseUrl = await server.listen({ hostname: '127.0.0.1', port: 0 });
3360
+ ```
3361
+
3362
+ Production hosts route `GET /.well-known/agent-card.json` and JSON-RPC `POST /`
3363
+ to `server.handle(request)` behind their own TLS terminator. `POST /a2a` remains
3364
+ an accepted compatibility alias. `listen()` waits for durable recovery before
3365
+ it resolves. A host that wires `handle()` directly may explicitly await
3366
+ `server.whenReady()` before it starts accepting traffic; `handle()` also waits
3367
+ for the same recovery promise. The durable edge store supports get/list,
3368
+ continuation, cancellation, ordered SSE subscription, and surviving Runtime-run
3369
+ reattachment after an edge restart. Push notifications, A2A 0.3, gRPC, and
3370
+ HTTP+JSON are not advertised; unsupported push methods return the standard
3371
+ `PushNotificationNotSupportedError`.
3372
+
3373
+ Non-streaming `SendMessage` waits at most `maxTaskWaitMs` (30 seconds by
3374
+ default). When that bound is reached the response contains the current working
3375
+ task; it does not cancel the Runtime run, and clients can continue with
3376
+ `GetTask` or `SubscribeToTask`.
3377
+
3378
+ Remote messages are ordinary user inputs. They cannot select provider, model,
3379
+ profile, tools, working directory, permission mode, or Runtime configuration.
3380
+ URL parts are rejected; inline raw/data parts are bounded and materialized under
3381
+ the server-owned data directory. Responses expose final approved output only,
3382
+ not system prompts, reasoning deltas, tool payloads, credentials, or local paths.
3383
+
3384
+ The normative baseline is A2A repository commit
3385
+ `2183794bfb9b67af4aee1be0a0ef726050642873`, protocol `1.0`, with
3386
+ `specification/a2a.proto` SHA-256
3387
+ `e195bf96ab630c69797851970203e1b2b6b19528f2e9803b7d904b91a5104016`.
3388
+
3389
+ ---
3390
+
3391
+ ## 23. Shared Coder daemon for Space and IDE hosts (FEATURE_269, v0.7.69)
3392
+
3393
+ FEATURE_269 makes one local daemon the source of truth for a Coder profile.
3394
+ CLI, Space, IDE, and SDK clients can observe and control the same sessions and
3395
+ runs. The transport remains local to the current OS user; it is not a remote
3396
+ collaboration protocol. Closing a client detaches that client and does not stop
3397
+ the daemon or another client's run.
3398
+
3399
+ Partner is deliberately outside this migration. Keep Partner on its existing
3400
+ inline callbacks and give it a distinct product data root and sessions root.
3401
+ Do not point a Partner inline Runtime at the Coder daemon profile or the Coder
3402
+ data root.
3403
+
3404
+ ### Connect and fail closed on required capabilities
3405
+
3406
+ Space should own the daemon SDK client in Electron Main. Persist a random,
3407
+ stable `instanceId` and a separate 32+ character `instanceSecret` per Space
3408
+ installation. Store the secret in the OS keychain; never accept either value
3409
+ from renderer or model output. `connectKodaXRuntime()` is attach-only unless `autoStart: true`.
3410
+ An explicit inline rollback policy blocks auto-start until the owner policy is
3411
+ explicitly changed back to daemon.
3412
+
3413
+ ```ts
3414
+ import { connectKodaXRuntime } from '@kodax-ai/kodax/runtime';
3415
+
3416
+ const runtime = await connectKodaXRuntime({
3417
+ profile: 'coder',
3418
+ autoStart: true,
3419
+ clientInfo: {
3420
+ name: 'kodax-space',
3421
+ version: '0.1.32',
3422
+ instanceId: spaceInstallationId,
3423
+ instanceSecret: await spaceKeychain.readRuntimeClientSecret(),
3424
+ },
3425
+ capabilities: {
3426
+ richEvents: true,
3427
+ permissionPrompts: true,
3428
+ operationDeduplication: true,
3429
+ },
3430
+ requirements: {
3431
+ operationDeduplication: 1,
3432
+ sessionObservation: 1,
3433
+ afterTurnInput: 1,
3434
+ askUserTransport: 1,
3435
+ permissionCas: 1,
3436
+ providerCredentialBroker: 1,
3437
+ runBoundHostTools: 1,
3438
+ coderOwnerFencing: 1,
3439
+ crashOutcomeModel: 1,
3440
+ coderFeatureMatrix: 1,
3441
+ sessionAdmission: 1,
3442
+ completeObservationSnapshot: 1,
3443
+ connectionLifecycle: 1,
3444
+ typedRuntimeEvents: 1,
3445
+ daemonSafeRunInput: 1,
3446
+ sharedSessionSettings: 1,
3447
+ durableRecoveryQueries: 1,
3448
+ },
3449
+ });
3450
+ ```
3451
+
3452
+ Requirements are server facts, not authorization requests. Check
3453
+ `runtime.grantedScopes` before enabling controls. Missing capabilities or
3454
+ scopes must disable the affected UI; Space must not silently start inline
3455
+ Coder. FEATURE_269 does not advertise `interruptInput`, so an interrupt-only
3456
+ product must require `{ interruptInput: 1 }` and fail connection. The supported
3457
+ fallback is `delivery: 'after_turn'` only when that is the user's intent.
3458
+
3459
+ The `coderFeatureMatrix` capability reports daemon availability for managed
3460
+ runs, transcript/session operations, Todo projection, managed tasks, Workflow,
3461
+ MCP, Reference External Agent, Memory, and Runtime artifacts. Reference
3462
+ External Agent is `false` when the daemon owner did not install its executor
3463
+ plane.
3464
+
3465
+ The packaged daemon authenticates one local OS-user/profile trust domain with
3466
+ a random token stored beside daemon state and a user-only local endpoint. It
3467
+ does not issue a different daemon token to each application in v0.7.69. The
3468
+ returned scope set is chosen by the host (the packaged host grants the public
3469
+ local-user set). `clientInfo.instanceId` is stable attribution for origin and
3470
+ operation deduplication. `instanceSecret` proves that a new authenticated
3471
+ connection is the same stable client when it resumes that client's credential
3472
+ or Host Tool leases; only its hash participates in daemon-owned bridge state.
3473
+ Keep all three values in Electron Main. Mutually distrusting processes running
3474
+ as the same OS account remain outside this release's threat model.
3475
+
3476
+ ### Join atomically and resync after disconnect
3477
+
3478
+ `sessions.observe()` installs the live subscription before taking the
3479
+ snapshot. Its snapshot contains one authoritative `runtimeId`, cursor,
3480
+ `transcriptRevision`, complete transcript, versioned settings, run/queue state,
3481
+ queued continuation IDs/order/origin/safe previews, pending permission and
3482
+ AskUser requests, and live assistant/thinking/tool/Todo/managed-task
3483
+ projection. Run requirements include the current credential/Host Tool
3484
+ availability. Listener events are strictly after the returned cursor.
3485
+
3486
+ ```ts
3487
+ let observedRuntimeId: string | undefined;
3488
+ let lastCursor = 0;
3489
+
3490
+ async function openCoderSession(sessionId: string) {
3491
+ const observation = await runtime.sessions.observe(sessionId, (event) => {
3492
+ if (event.seq <= lastCursor) return;
3493
+ applyRuntimeEvent(event);
3494
+ lastCursor = event.seq;
3495
+ });
3496
+
3497
+ const { snapshot } = observation;
3498
+ const runtimeChanged = observedRuntimeId !== undefined
3499
+ && observedRuntimeId !== snapshot.runtimeId;
3500
+ observedRuntimeId = snapshot.runtimeId;
3501
+ lastCursor = snapshot.cursor;
3502
+ replaceSessionProjection(snapshot, { runtimeChanged });
3503
+ return observation;
3504
+ }
3505
+ ```
3506
+
3507
+ Subscribe to `runtime.connection` to freeze mutation UI immediately rather
3508
+ than waiting for a status poll:
3509
+
3510
+ ```ts
3511
+ runtime.connection?.subscribe((state) => {
3512
+ setCoderConnectionState(state.state, state.reason);
3513
+ if (state.state === 'disconnected' && state.reconnectable) {
3514
+ scheduleReconnect();
3515
+ }
3516
+ });
3517
+ ```
3518
+
3519
+ The SDK reports the current `connectionId`, `runtimeEpoch`, optional
3520
+ `journalEpoch`, disconnect reason, and whether a new connection may be
3521
+ attempted. It does not transparently replay requests or subscriptions. Space
3522
+ creates a replacement Runtime client, checks its new epochs, resumes eligible
3523
+ leases, and observes the session again.
3524
+
3525
+ On transport failure, Runtime change, expired history, or `resync_required`,
3526
+ discard the local derived projection and call `sessions.observe()` again. Do
3527
+ not merge a new snapshot into the old projection. The handshake buffer is
3528
+ bounded; overflow fails explicitly instead of dropping events. A Runtime
3529
+ restart changes `runtimeId`, marks persisted non-terminal runs with a durable
3530
+ terminal fact, and closes old in-memory AskUser/permission requests through the
3531
+ reset boundary.
3532
+
3533
+ ### Durable mutations, stable ordering, and settings CAS
3534
+
3535
+ Every public control mutation uses a durable operation envelope. Credential
3536
+ supply and Host Tool completion are connection-local reverse-result frames and
3537
+ are deliberately excluded from the control journal so secrets/results are not
3538
+ persisted. The SDK creates an operation ID for ordinary one-shot calls. A
3539
+ product-level retry after a lost response must reuse its own stable operation
3540
+ ID; changing its method, payload, resource, or authenticated principal is
3541
+ rejected.
3542
+
3543
+ ```ts
3544
+ const session = await runtime.sessions.create({
3545
+ sessionId: stableSpaceSessionId,
3546
+ title: 'Shared session',
3547
+ surface: 'space-desktop',
3548
+ operation: { operationId: loadOrCreatePendingOperationId('space-session-draft-7') },
3549
+ });
3550
+
3551
+ const operationId = loadOrCreatePendingOperationId('space-run-draft-42');
3552
+ const handle = await runtime.runs.start({
3553
+ sessionId: session.id,
3554
+ input: { type: 'text', text: prompt },
3555
+ options: { provider: 'anthropic' },
3556
+ operation: { operationId },
3557
+ });
3558
+
3559
+ const current = await runtime.sessions.getSettingsVersioned(session.id);
3560
+ const updated = await runtime.sessions.updateSettingsVersioned(
3561
+ session.id,
3562
+ { model: 'claude-sonnet-4-5' },
3563
+ {
3564
+ operationId: loadOrCreatePendingOperationId('space-settings-draft-9'),
3565
+ expectedRevision: current.revision,
3566
+ },
3567
+ );
3568
+ ```
3569
+
3570
+ Create retries with the same explicit session and operation IDs cannot overwrite
3571
+ an existing session. Same-session starts and after-turn inputs receive a durable `sessionOrder`.
3572
+ Retries with the same operation ID return the canonical result and do not
3573
+ create another run. Settings use compare-and-swap; a stale revision returns a
3574
+ structured conflict and must be reloaded, never silently overwritten. The
3575
+ shared settings keys are `provider`, `model`, `effort`, `thinking`,
3576
+ `reasoningMode`, `permissionMode`, `executionCwd`, `agentMode`, and
3577
+ `autoModeEngine`.
3578
+
3579
+ ```ts
3580
+ const queued = await runtime.runs.submitInput({
3581
+ sessionId: session.id,
3582
+ afterRunId: handle.runId,
3583
+ delivery: 'after_turn',
3584
+ input: { type: 'text', text: 'Also update the tests.' },
3585
+ operation: { operationId: loadOrCreatePendingOperationId('space-input-17') },
3586
+ });
3587
+
3588
+ if (!queued.accepted) {
3589
+ // stale_run or unsupported_capability: show the factual result.
3590
+ }
3591
+ ```
3592
+
3593
+ Run status exposes acceptance/start/queue times, authenticated origin,
3594
+ `sessionOrder`, and a single terminal fact. Important terminal codes include
3595
+ `runtime_restarted`, `daemon_crashed`, `credential_unavailable`,
3596
+ `host_not_dispatched`, `host_outcome_unknown`, and
3597
+ `control_history_untrusted`. Respect `effectOutcome`; `unknown` must never be
3598
+ presented as success or automatically retried. After a lost response, query
3599
+ `runtime.operations.get({ operationId, journalEpoch })`; applied receipts
3600
+ include the canonical result. Permission grants remain daemon-owned and
3601
+ revisioned.
3602
+
3603
+ ### AskUser and permission from any client
3604
+
3605
+ AskUser is no longer an in-process callback for daemon Coder runs. Any client
3606
+ with the responder scope can list the pending request and answer or dismiss it.
3607
+ The request revision and run binding prevent a stale UI from answering a new
3608
+ request. Exactly one concurrent answer is accepted.
3609
+
3610
+ ```ts
3611
+ for (const request of await runtime.userInputs.listPending({ sessionId })) {
3612
+ const resolution = await runtime.userInputs.respond(request.id, answer, {
3613
+ expectedRevision: request.revision,
3614
+ runId: request.runId,
3615
+ });
3616
+ if (!resolution.accepted) refreshPendingInteractions();
3617
+ }
3618
+
3619
+ for (const request of await runtime.permissions.listPending({ sessionId })) {
3620
+ const accepted = await runtime.permissions.respond(
3621
+ request.id,
3622
+ { type: 'allow_once' },
3623
+ { runId: request.runId },
3624
+ );
3625
+ if (!accepted) refreshPendingInteractions();
3626
+ }
3627
+ ```
3628
+
3629
+ Persistent `allow_always` grants are owned by the daemon and require
3630
+ `permission:grant-admin`. Use `permissions.listGrants()` and
3631
+ `revokeGrant(grantId, expectedRevision)` for revision-safe administration.
3632
+ Clients must not keep separate persistent permission rule stores.
3633
+
3634
+ ### Broker a Space keychain credential
3635
+
3636
+ Credentials remain owned by Space's OS keychain. Register a broker in Electron
3637
+ Main, bind the returned lease only to runs that Space starts, and return the
3638
+ key only after checking the daemon-provided provider/session/run context.
3639
+
3640
+ ```ts
3641
+ const credentialLease = await runtime.credentials.register(
3642
+ { providers: ['anthropic'] },
3643
+ async ({ provider, sessionId: requestedSession, runId }) => {
3644
+ authorizeSpaceRunCredential({ provider, sessionId: requestedSession, runId });
3645
+ return spaceKeychain.readProviderCredential(provider);
3646
+ },
3647
+ );
3648
+
3649
+ const run = await runtime.runs.start({
3650
+ sessionId,
3651
+ input: { type: 'text', text: prompt },
3652
+ options: { provider: 'anthropic' },
3653
+ credential: { leaseId: credentialLease.id, provider: 'anthropic' },
3654
+ operation: { operationId: loadOrCreatePendingOperationId('space-run-88') },
3655
+ });
3656
+ ```
3657
+
3658
+ The secret crosses only the authenticated reverse frame and an in-memory
3659
+ run/provider scope. It is excluded from events, status, logs, diagnostics,
3660
+ operation records, and Runtime persistence. While such a scope is active,
3661
+ provider mismatch fails closed and never falls back to daemon environment.
3662
+ Without a stable `instanceSecret`, registration ends when the Space connection
3663
+ closes. With it, the daemon keeps the registration owned by that stable client;
3664
+ a replacement Space process reattaches the callback with
3665
+ `runtime.credentials.resume(leaseId, broker)`. An accepted run has already
3666
+ acquired its scoped credential, so it reports `requirements.credential.state`
3667
+ as `ready` and may continue after disconnect. If the broker cannot answer, the
3668
+ start is rejected instead of accepting an indefinitely waiting run. Expiry or
3669
+ Runtime restart is explicit (`expired` or terminal); no provider request is
3670
+ automatically replayed.
3671
+
3672
+ ### Bind Space-owned Host Tools to one run
3673
+
3674
+ Register only narrow product capabilities. Descriptors are data; handlers stay
3675
+ in Electron Main. A lease grants nothing until its ID is explicitly bound to a
3676
+ run.
3677
+
3678
+ ```ts
3679
+ const hostLease = await runtime.hostTools.register([{
3680
+ name: 'space_artifact_create',
3681
+ description: 'Create a Space-owned artifact for this run.',
3682
+ inputSchema: {
3683
+ type: 'object',
3684
+ properties: { title: { type: 'string' } },
3685
+ required: ['title'],
3686
+ additionalProperties: false,
3687
+ },
3688
+ sideEffect: 'non_idempotent',
3689
+ }], {
3690
+ async space_artifact_create(invocation) {
3691
+ authorizeBoundSpaceInvocation(invocation);
3692
+ const artifact = await createSpaceArtifact(invocation.input);
3693
+ return { content: `Created artifact ${artifact.displayId}` };
3694
+ },
3695
+ });
3696
+
3697
+ await runtime.runs.start({
3698
+ sessionId,
3699
+ input: { type: 'text', text: 'Create the report artifact.' },
3700
+ hostTools: { leaseId: hostLease.id },
3701
+ operation: { operationId: loadOrCreatePendingOperationId('space-artifact-run') },
3702
+ });
3703
+ ```
3704
+
3705
+ The daemon injects session/run/lease/invocation identity; renderer, model, and
3706
+ ordinary tool input cannot choose it. CLI runs never inherit a Space lease just
3707
+ because Space later observes their session. The client memoizes one handler
3708
+ promise per invocation ID, and the daemon never replays a dispatched Host Tool.
3709
+ After a stable-client reconnect, call
3710
+ `runtime.hostTools.resume(leaseId, handlers)`. Bound run status reports
3711
+ `ready`, `waiting_host`, `expired`, or `terminal`. Disconnect or timeout after dispatch produces `host_outcome_unknown`; Space
3712
+ must reconcile the product side effect itself before offering a new user
3713
+ action. `runtime.hostTools.getInvocation(invocationId)` returns the durable
3714
+ metadata state `prepared`, `dispatched`, `completed`, `unknown`, or
3715
+ `not_dispatched`; it never returns handler input, result, or credential data.
3716
+ The daemon writes the `dispatched` marker before attempting the reverse frame
3717
+ and never auto-replays an invocation.
3718
+
3719
+ ### Coder admission, typed events, and transport-safe inputs
3720
+
3721
+ The daemon enforces Coder session admission on the server for list/create/load,
3722
+ run, settings, delete, rewind, fork, compact, transcript, event, interaction,
3723
+ and diagnostic paths. Coder
3724
+ surfaces are `code`, `cli`, `repl`, `acp`, `a2a`, `sdk`, `ide`, and
3725
+ `space-desktop`. A session marked with
3726
+ Partner surface/profile metadata, or any unknown product surface, fails with
3727
+ typed `session_not_admitted` before mutation. Space must continue marking
3728
+ Partner sessions as `surface: 'partner'` and keep their inline storage root
3729
+ separate. Legacy sessions without a surface remain admitted for existing Coder
3730
+ compatibility, so absence of metadata is not a Partner namespace mechanism.
3731
+
3732
+ `RuntimeEventPayloadMap` and `RuntimeTypedEvent` provide the public
3733
+ discriminated contract for known events. Existing raw listeners remain
3734
+ compatible; consumers can use `parseRuntimeEvent(value)` before exhaustive
3735
+ handling. One unknown or malformed event is diagnosed and dropped without
3736
+ closing the observation stream.
3737
+
3738
+ Daemon clients use `RuntimeDaemonStartRunInput`. Function callbacks,
3739
+ `AbortSignal`, Extension Runtime objects, and guardrail instances are excluded
3740
+ from that type and rejected at runtime with `RuntimeTransportBoundaryError`
3741
+ and an exact value path if an untyped caller supplies them. Host-only values
3742
+ remain valid only for embedded Runtime calls.
3743
+
3744
+ ### Recovery queries and stop preflight
3745
+
3746
+ `runtime.status.preflight()` returns connected-client count, active and queued
3747
+ runs, pending AskUser/permission records, blockers, and `canStop`. Treat this as
3748
+ a required check before stop or inline rollback; the query does not stop the
3749
+ daemon by itself. `runtime.operations.get()` reconciles durable mutations,
3750
+ `hostTools.getInvocation()` reconciles Host Tool metadata, and
3751
+ `permissions.listGrants()` returns the daemon-owned persistent grant set.
3752
+
3753
+ Terminal notification read/unread state is intentionally client-owned in
3754
+ v0.7.69 (`durableRecoveryQueries.terminalAcknowledgement === false`): Space
3755
+ persists its own UI acknowledgement cursor against Runtime/run terminal facts.
3756
+ This avoids a false claim that one client's acknowledgement is global daemon
3757
+ truth.
3758
+
3759
+ ### Owner policy, rollback, and Electron boundary
3760
+
3761
+ Daemon and inline Coder use one profile fence. For an emergency rollback, stop
3762
+ the daemon, switch the policy with revision CAS, then acquire the inline owner:
3763
+
3764
+ ```ts
3765
+ import {
3766
+ acquireKodaXInlineOwner,
3767
+ setKodaXRuntimeOwnerMode,
3768
+ } from '@kodax-ai/kodax/runtime';
3769
+
3770
+ const policy = setKodaXRuntimeOwnerMode({
3771
+ homeDir: kodaxHome,
3772
+ profile: 'coder',
3773
+ mode: 'inline',
3774
+ expectedRevision: currentPolicyRevision,
3775
+ });
3776
+ const inlineOwner = acquireKodaXInlineOwner({ homeDir: kodaxHome, profile: 'coder' });
3777
+ ```
3778
+
3779
+ The inline policy is sticky: later CLI auto-start is rejected until an explicit
3780
+ CAS changes the policy back to `daemon`. Stale-owner handling validates the
3781
+ owned lock/state and never kills a process merely because a PID was reused.
3782
+
3783
+ Keep all trusted objects in Electron Main: daemon token/endpoint, stable client
3784
+ identity, operation IDs, owner policy, keychain broker, Host Tool handlers, and
3785
+ permission-grant administration. Renderer IPC should expose product-specific
3786
+ commands and sanitized projections only. Never pass daemon credentials,
3787
+ leases, operation epochs, or trusted session/run context to renderer or model
3788
+ tool arguments.
3789
+
3790
+ ---
3791
+
3792
+ ## See also
3075
3793
 
3076
3794
  - [README.md](../README.md) — end-user CLI quick start
3077
3795
  - [docs/ADR.md ADR-024](ADR.md#adr-024-npm-发布物正名-kodax-aikodax--sdk-subpath-exports-形式化-v0739) — SDK subpath architecture rationale