@jskit-ai/agent-docs 0.1.193 → 0.1.195

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 (40) hide show
  1. package/guide/agent/app-extras/assistant-conversation.md +2608 -72
  2. package/guide/agent/app-extras/assistant-voice.md +244 -0
  3. package/guide/agent/app-extras/assistant.md +189 -20
  4. package/guide/agent/app-extras/realtime.md +6 -0
  5. package/guide/agent/app-setup/database-layer.md +74 -5
  6. package/guide/agent/app-setup/existing-application-migration.md +34 -29
  7. package/guide/agent/app-setup/multi-homing.md +93 -116
  8. package/guide/agent/framework/crud-operations.md +16 -2
  9. package/guide/agent/index.md +1 -0
  10. package/package.json +1 -1
  11. package/patterns/feature-package/example/booking-engine/package.json +1 -1
  12. package/patterns/minimal-foundation/example/package.json +4 -4
  13. package/patterns/shell-foundation/example/package.json +5 -5
  14. package/patterns/shell-foundation/example/packages/main/package.json +1 -1
  15. package/reference/autogen/PATTERN_INDEX.md +35 -35
  16. package/reference/autogen/README.md +2 -0
  17. package/reference/autogen/packages/assistant-core.md +974 -9
  18. package/reference/autogen/packages/assistant-runtime.md +126 -28
  19. package/reference/autogen/packages/assistant-voice-sherpa.md +82 -0
  20. package/reference/autogen/packages/assistant-voice.md +227 -0
  21. package/reference/autogen/packages/kernel.md +10 -0
  22. package/reference/autogen/packages/realtime.md +5 -2
  23. package/skills/jskit/SKILL.md +1 -1
  24. package/skills/jskit/references/crud-operations.md +16 -2
  25. package/skills/jskit/references/existing-application-migration.md +34 -29
  26. package/skills/jskit/references/multi-homing.md +93 -116
  27. package/skills/jskit/references/pattern-index.md +35 -35
  28. package/skills/jskit/references/patterns/app/minimal-foundation/example/package.json +4 -4
  29. package/skills/jskit/references/patterns/app/shell-foundation/example/package.json +5 -5
  30. package/skills/jskit/references/patterns/app/shell-foundation/example/packages/main/package.json +1 -1
  31. package/skills/jskit/references/patterns/auth/supabase-auth/example/package.json +1 -1
  32. package/skills/jskit/references/patterns/connectors/calendar-cli/example/package.json +4 -4
  33. package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/package.json +2 -2
  34. package/skills/jskit/references/patterns/database/mysql-application/example/package.json +1 -1
  35. package/skills/jskit/references/patterns/database/postgres-application/example/package.json +1 -1
  36. package/skills/jskit/references/patterns/realtime/realtime-application/example/package.json +2 -2
  37. package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/package.json +1 -1
  38. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/package.json +2 -2
  39. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/package.json +3 -3
  40. package/skills/jskit/references/patterns/workspaces/workspace-server/PATTERN.md +23 -28
@@ -2,27 +2,487 @@
2
2
 
3
3
  # Embeddable assistant conversations
4
4
 
5
- `@jskit-ai/assistant-core` supplies a Vue/Vuetify conversation element, transcript
6
- policy, and native provider primitives. It has no dependency on an editor,
7
- project directory, application database, identity scheme, or avatar.
5
+ Start with the [standard Assistant integration](./assistant.md): one common
6
+ runtime, supplied server integration and `AssistantConversationClientElement`.
7
+ This reference describes that setup, its configuration and optional lower-level
8
+ contracts. Custom storage and native engines use the same supplied binding; they
9
+ do not require private conversation routes or a hand-written element adapter.
10
+
11
+ `@jskit-ai/assistant-core` owns the runtime, storage contract, transcript policy
12
+ and base `AssistantConversationElement`. `@jskit-ai/assistant-runtime` supplies
13
+ authorized HTTP/realtime integration and the component's client binding. Database
14
+ repositories and settings are optional. Neither ordinary file storage nor the
15
+ browser binding requires an editor, application database or particular avatar.
16
+ Choose one transcript owner for a conversation. The [ordinary API](./assistant.md#the-ordinary-api)
17
+ is the starting point; [advanced hosts](#advanced-host-facilities) and
18
+ [testing](#testing-entrypoints) are separate reference areas.
19
+
20
+ ### Supplied server integration
21
+
22
+ `@jskit-ai/assistant-runtime/server` exports `AssistantFeature`,
23
+ `createAssistantActions`, `registerAssistantRoutes` and
24
+ `registerConversationSubscriptions`. The feature accepts an application-supplied
25
+ `assistant.conversations` capability containing `createConversationRuntime()`
26
+ or an application facade with the same authorized `open({ id, context })` contract.
27
+ That runtime may use file storage or any adapter satisfying the storage contract.
28
+ Its authorized operations use the existing action catalogue and HTTP router.
29
+ Read, send, cancel and delivery inspection use the existing `createAssistantApi`
30
+ request client, including its surface header and workspace path. Inspection is
31
+ an authenticated POST operation because it may recover and save native receipts;
32
+ it never dispatches another model request. Browser inputs cannot set the actor,
33
+ native host or engine context.
34
+ `readConversation(id, { beforeTurnId, limit, signal })` optionally requests an
35
+ earlier or bounded page through the same authenticated GET. Its query contains
36
+ only the supplied `beforeTurnId` and `limit`; omitting both keeps the facade's
37
+ existing read default. The response retains current state alongside the page's
38
+ `conversationLog` and `pagination`. The existing transcript reader selects the
39
+ turns, caps positive limits at 100 and treats zero as unbounded. An application
40
+ facade forwards explicit page options to that reader; it must not read all
41
+ history and then slice the result.
42
+ Goal read/update use that same authorized boundary. Unsupported engines return
43
+ no current goal and reject goal commands; the runtime retains current segment,
44
+ goal identity and budget checks. Goal attachments are authorized identifiers,
45
+ never browser-supplied files or paths. A canonical client needs only `request`;
46
+ `requestStream` is required only when using the older `streamChat` method.
47
+ When the host supplies `runtime.realtime` and `runtime.events`, subscriptions
48
+ share that connection and detach when the feature shuts down. Detaching the
49
+ feature does not close the application-owned runtime or stop its active turns.
50
+
51
+ Applications can expose their existing authorized settings, model selection and
52
+ replacement operations through the same facade. Declare
53
+ `conversationConfigurationSchema`, `conversationSelectionSchema` or
54
+ `conversationReplacementSchema` on `assistant.conversations` using the existing
55
+ `createSchema` input format. The feature registers only declared operations under
56
+ the same surface path:
57
+
58
+ | Schema | HTTP operation | Request body | Facade method |
59
+ | --- | --- | --- | --- |
60
+ | `conversationConfigurationSchema` | `PATCH /conversations/:conversationId/configuration` | `{ configuration: value }` | `configure(value)` |
61
+ | `conversationSelectionSchema` | `POST /conversations/:conversationId/selection` | `{ selection: value }` | `select(value)` |
62
+ | `conversationReplacementSchema` | `POST /conversations/:conversationId/replacement` | `{ replacement: value }` | `replace(value)` |
63
+
64
+ Each schema describes the inner application choice. The facade rechecks current
65
+ access and resolves that choice through the application's existing policy before
66
+ using the common runtime. Schemas validate input; they do not grant access to
67
+ models or credentials. Keep engine, host configuration and continuity briefings
68
+ server-owned. A replacement choice should retain its original `operationId` and
69
+ observed `expectedSegmentId`: the runtime rejects a stale predecessor and can
70
+ identify retries of the same operation. The application still decides which
71
+ replacement choices are permitted.
72
+
73
+ Omitting a schema leaves its operation unregistered. Colleague, for example,
74
+ exposes its existing catalogue selection action and keeps its system prompt
75
+ server-owned. `wake()` remains server-side; this integration registers no browser
76
+ wake operation.
77
+
78
+ Use `createAssistantApi().configureConversation(id, value, { signal })`,
79
+ `selectConversation(id, value, { signal })` or
80
+ `replaceConversation(id, value, { signal })` from the existing picker or controls.
81
+ These preserve the shared surface header and encoded conversation identity.
82
+ When registering the lower-level routes/actions directly, pass the same schema
83
+ options to both `registerAssistantRoutes` and `createAssistantActions`.
84
+
85
+ Installed package discovery registers `AssistantFeature` automatically. A host
86
+ provider should supply `assistant.conversations`; it must not also register the
87
+ same conversation routes, actions or socket subscriptions. One feature owns those
88
+ registrations for the application's shared realtime connection.
89
+
90
+ For a plain Fastify application, `registerFastifyConversations` supplies the same
91
+ hosting assembly as configurable shorthand:
8
92
 
9
- Use this contract when the application already owns its conversation endpoints
10
- or needs custom storage and provider execution. Applications using the complete
11
- JSKIT assistant surface use [Assistant](./assistant.md), which
12
- owns its routes, database repositories, action-tool loop, and settings.
13
- Both integrations render the same `AssistantConversationElement`. Choose one transcript owner for a conversation.
93
+ ```js
94
+ import { registerFastifyConversations } from "@jskit-ai/assistant-runtime/server";
95
+
96
+ await registerFastifyConversations(app, {
97
+ runtime, // application-created common runtime and storage
98
+ config: appConfig, // surfaces and HTTP/realtime configuration
99
+ env: process.env,
100
+ async authenticate(request) { // trusted HTTP or reconstructed socket request
101
+ return authenticateSession(request); // application context, or null to deny
102
+ },
103
+ bootstrap: ({ context }) => ({ application: { subjectId: context.subjectId } })
104
+ });
105
+ app.addHook("onClose", () => runtime.close());
106
+ ```
107
+
108
+ An application with one surface can declare it once for both server and client:
109
+
110
+ ```js
111
+ import { defineAssistantSurface } from "@jskit-ai/assistant-runtime/shared";
112
+
113
+ const appConfig = {
114
+ ...defineAssistantSurface("home"),
115
+ // Other existing HTTP, realtime and application configuration.
116
+ };
117
+ ```
118
+
119
+ The required ID names that one surface; the defaults are root placement and
120
+ global assistant settings. Options such as `pagesRoot`, `requiresWorkspace`,
121
+ `accessPolicyId` and `enabled` retain their existing surface meanings. A workspace
122
+ surface defaults to workspace settings. The helper expands the existing
123
+ `surfaceDefinitions` and `assistantSurfaces` maps; it does not infer extra surfaces
124
+ or grant access. Use those maps directly when an application needs multiple
125
+ surfaces or a separate settings surface. Authentication and Origin/CSRF policy
126
+ still belong to the host.
127
+
128
+ `runtime`, `config` and `authenticate` are required. `env` defaults to
129
+ `process.env`; `bootstrap` is optional and adds application bootstrap data after
130
+ authentication. The helper does not create conversations, select a model, choose
131
+ storage or infer authorization from browser fields. Authentication may be async;
132
+ return a context object or throw an application error. A missing context is denied.
133
+ Apply the application's normal session, Origin/CSRF and socket-handshake rules
134
+ there. The returned trusted fields extend the action context; the runtime's own
135
+ `authorize` still controls each conversation operation and subscription.
136
+
137
+ The helper assembles `AssistantFeature`, the HTTP/action/bootstrap/event providers
138
+ and one realtime provider. It closes that hosting on Fastify `preClose`. The
139
+ supplied runtime remains application-owned: close it in `onClose`, as above.
140
+ Register once per Fastify app; all its chats share the supplied realtime service.
141
+ Transport paths/options remain in `config`, and the caller owns Fastify options
142
+ and plugins. Install `@jskit-ai/realtime` when using this helper. It does not add a
143
+ database dependency. An existing JSKIT host should use normal feature registration
144
+ on its existing services instead. Custom hosts can still use the lower-level
145
+ feature, route, action and subscription contracts described below.
146
+
147
+ Conversation IDs are route parameters. Fastify defaults to a 100-character
148
+ parameter limit; if the application's IDs exceed that, set a bounded
149
+ `routerOptions.maxParamLength` when creating Fastify, for example
150
+ `Fastify({ routerOptions: { maxParamLength: 1024 } })`. The helper accepts an
151
+ existing server and does not change its router configuration.
152
+
153
+ When an existing host uses a different request-access policy, it can supply the
154
+ optional server capability `assistant.conversation.access`:
155
+
156
+ ```js
157
+ {
158
+ router, // existing router.register interface with the host's request guard
159
+ wrapAction, // wraps an existing definition with the host's action-access owner
160
+ subscribeActionId, // fixed server-owned product action id
161
+ requestPolicy: "host"
162
+ }
163
+ ```
164
+
165
+ The feature passes only canonical conversation routes through that router and
166
+ only canonical conversation definitions through `wrapAction`. It sets the
167
+ subscribe definition's ID before wrapping it. The host must preserve the existing
168
+ handler, input validation and conversation authorization while applying its
169
+ request guard and trusted action context. Settings and legacy database routes
170
+ and definitions keep their existing authentication. An incomplete access facility
171
+ fails startup.
172
+
173
+ The default subscription policy is `authenticated`. An explicit `host` policy
174
+ requires a distinct fixed subscribe action and uses the shared connection's
175
+ `readRequest()`: this reconstructs the original handshake and revalidates any
176
+ configured authentication. Required hosted authentication still rejects missing,
177
+ revoked or changed identity. Local requests can lack an actor, so the host action
178
+ must apply its original local request and WebSocket-origin checks. Socket rooms,
179
+ action selection and request policy remain server-owned. Local events omit actor
180
+ metadata; hosted events retain the real actor and existing per-event access checks.
181
+
182
+ Database support is an optional peer dependency. Supplying a conversation runtime
183
+ does not import database repositories, create the legacy chat service or read a
184
+ host database, even if an unrelated database capability is present. An optional
185
+ `assistant.settings` service enables the existing settings routes. Without a
186
+ supplied conversation runtime, the existing database integration requires
187
+ `runtime.database`; its repositories and migrations remain available. The
188
+ database facade uses the same common runtime with its SQL conversation storage;
189
+ that optional storage does not change the server-integration contract.
190
+
191
+ ### Supplied client integration
192
+
193
+ Use `AssistantConversationClientElement` from `@jskit-ai/assistant-runtime/client`
194
+ for a conversation initialized by the application-owned common runtime:
195
+
196
+ ```vue
197
+ <AssistantConversationClientElement
198
+ ref="conversation"
199
+ :conversation-id="conversationId"
200
+ :attachments="attachments"
201
+ :suggestions="suggestions"
202
+ />
203
+ ```
204
+
205
+ Only `conversation-id` is required for the standard card. The optional feature
206
+ props above use the same binding. The element uses the current configured
207
+ surface/workspace HTTP path and the application's
208
+ existing authenticated realtime socket. It does not create another connection.
209
+ `surface-id`, `endpoint`, `hostSurfaceId` and `workspaceSlug` can supply an application's
210
+ existing placement mapping; the server still resolves and authorizes that scope.
211
+ It exposes `focus()` and `runtime`. Its slots receive their existing scoped
212
+ fields plus that same retained `runtime`; a `composer-tools` button can pass it
213
+ to the voice controller without constructing another binding.
214
+
215
+ Without application overrides, the binding uses the existing authenticated
216
+ placement identity, standard HTTP client and surface path. Applications with a
217
+ different bootstrap subject or request policy configure it once before mounting:
218
+
219
+ ```js
220
+ import { configureAssistantConversations } from "@jskit-ai/assistant-runtime/client";
221
+
222
+ configureAssistantConversations(app, {
223
+ actorKey: () => bootstrap.value.subjectId,
224
+ request: http.request, // the application's existing HTTP and Origin/CSRF policy
225
+ clearDraftOn: "accepted"
226
+ });
227
+ app.mount("#app");
228
+ ```
229
+
230
+ These defaults belong to that Vue app. `actorKey` accepts a value, ref or getter;
231
+ changing or clearing it retires the previous actor's retained browser state.
232
+ The browser subject key never grants server access. Server authentication,
233
+ conversation authorization and subscription guards remain unchanged. The voice
234
+ starter supplies its existing local request policy here, once for all five cards.
235
+
236
+ The optional per-card `actor-key` overrides the default. An omitted or undefined
237
+ value inherits it; an explicit empty value makes the reader inactive rather than
238
+ reusing the previous actor. `api` can supply a static client created with
239
+ `createAssistantApi`, either once in the app defaults or as a per-card override.
240
+ An omitted or null per-card `api` inherits the app's API or request function.
241
+ Otherwise the standard HTTP client is used. No option creates another realtime
242
+ connection. `clear-draft-on="accepted"` keeps typed input until acceptance;
243
+ `"dispatch"` remains the default when neither the app nor the card changes it.
244
+ Use consistent API and draft options for readers of the same retained target.
245
+
246
+ Custom presentation can call `useAssistantConversation()` with the same
247
+ identity/options and render `AssistantConversationElement` with its `adapter`.
248
+ The composable exposes the same `runtime` and `error` refs; it is unnecessary for
249
+ an application that only adds standard component slots.
250
+
251
+ The composable's `presentation` option accepts a value, ref or getter. Supply transcript
252
+ fields `assistantLabel`, `systemLabel`, `welcomeMessage`, `variant`, `visible`,
253
+ `userMessageFormat` and `progressPreviewLimit`, and composer fields `ariaLabel`,
254
+ `submitAriaLabel`, `submitLabel`, `placeholder` and `rows`. `layout: "compact"`
255
+ keeps the existing compact composer. These change presentation through the
256
+ supplied adapter; they do not replace its history, actions or delivery state.
257
+ An empty `welcomeMessage` hides the welcome text. `previewMessage` supplies the
258
+ single live user preview described below while the conversation is available.
259
+
260
+ Text views and `runtime.retain()` readers share draft, delivery and subscription
261
+ state for an exact actor, endpoint, surface, workspace and conversation. Release
262
+ a retained handle with `release()`. Final view/reader release detaches browser
263
+ observers; it does not stop server work. A lost receipt remains uncertain with
264
+ **Check delivery** until inspection or canonical history proves acceptance.
265
+ Reconnection reads state and does not resend the request.
266
+
267
+ Applications that create nonvisual tasks after setup can capture
268
+ `useAssistantConversationFactory(commonOptions)` once during setup, then call
269
+ the returned `acquire(taskOptions)` function as tasks are created or restored.
270
+ It returns the same `runtime`, `adapter` and `error` refs plus `release()`.
271
+ Each task uses the existing retained conversation registry; another reader of
272
+ the same identity shares its draft and delivery state. Release a task's handle
273
+ when the application removes that task. Ending the setup scope releases its
274
+ remaining handles, and the factory cannot acquire new handles afterward.
275
+ Hidden tasks can keep `active: true` while only their presentation is hidden.
276
+
277
+ When an application must save or create its logical conversation before Send,
278
+ use that exact application-selected ID and keep its reader inactive until
279
+ creation succeeds. The binding's `submitPrepared(payload, { messageId,
280
+ prepare, onAccepted })` uses the standard composer's draft and attachment cleanup
281
+ around the retained runtime's existing prepared submission. Supply ordinary
282
+ message/display attachments and declared `data`; an already authored canonical
283
+ `payload.request` is also accepted. `working` supplies the application's native
284
+ turn eligibility when its product state also includes non-native work such as
285
+ routing. The binding captures Send or Steer before preparation; retry keeps that
286
+ choice and the original message identity, text, settings and files.
287
+
288
+ `prepare(captured, { signal })` performs only the application's existing save,
289
+ creation and authorization prerequisites, then returns its canonical request or
290
+ `false` to cancel before dispatch. The optimistic entry exists before those awaits.
291
+ Presentation metadata may reflect a newer draft. `onSubmit` runs before preparation
292
+ for application pending-state updates; `isCurrent` preserves the application's
293
+ additional close/lifetime guard. `clearDraft: false` leaves a user's draft untouched
294
+ for an application-generated message. The optional attachment owner has the same
295
+ `clearAttachments({ accepted: true, attachmentIds })` contract as the standard
296
+ composer; acceptance removes only the submitted files, preserving later uploads.
297
+
298
+ Pass `retryMessageId` for an existing failed or uncertain message. An application
299
+ may supply its authoritative `retry` receipt projection when routing persists it
300
+ outside the current local delivery list. Failed delivery reuses the captured
301
+ payload; uncertain delivery only inspects the original receipt, never running
302
+ preparation or sending again. An accepted restored receipt applies idempotent
303
+ composer cleanup. Ordinary `runtime.submitPrepared` remains available to hosts
304
+ that already own their composer; it requires an authored `payload.request`.
305
+
306
+ Both paths use the existing API, abort and receipt owner. It rechecks the actor,
307
+ known access denial, disposal and abort after preparation. A preparation exception
308
+ returns a failed result with its original message/code; ambiguous failure after
309
+ dispatch returns `{ ok: false, status: "uncertain", error, code? }`. The server's
310
+ original admission policy still authorizes every request. Prepared submission
311
+ does not relax ordinary `send()` readiness or capability checks, create a
312
+ placeholder snapshot, or infer a conversation identity.
313
+
314
+ After creation, an active retained handle can call `runtime.cancel()` even while
315
+ its first read or Start response is pending. Inactive, unidentified, retired or
316
+ access-denied targets cannot dispatch cancellation. The standard element still
317
+ uses the loaded snapshot's Stop availability; product task controls can retain
318
+ their original earlier Stop boundary.
319
+
320
+ For a voice binding, `projectConversationVoiceState({ turns, status, interimReply })` from
321
+ `@jskit-ai/assistant-voice/client` projects those same retained turns into the
322
+ voice owner's `messages` and `streamingReply`. It keeps user message IDs and
323
+ stable assistant IDs across native partial/final representations. Commentary
324
+ and reasoning remain activity rather than spoken answers. A transient assistant
325
+ message explicitly marked `origin: "application"` is held until its saved
326
+ notification; it does not suppress that completed notification. The projector
327
+ stores no history and owns no subscription or playback queue. Application
328
+ progress acknowledgements remain an explicit presentation policy.
329
+
330
+ An application can supply a selected transient `interimReply` through its
331
+ authorized conversation read and events. The standard binding displays it only
332
+ on its exact `turnId`, without changing saved messages or acknowledging pending
333
+ delivery. Supply `null` when the actual answer replaces it or the operation
334
+ ends. The voice projection uses that same descriptor. A provider's optional
335
+ `outputId` identifies one output across its live and saved representations, so
336
+ a spoken acknowledgement and a later answer remain distinct. Older messages
337
+ without this field retain their existing identity; neither reads nor projections
338
+ backfill history. This field does not replace message IDs or admission receipts.
339
+
340
+ `useAssistantConversation({ clearDraftOn: "accepted", queueWhileSending: false })`
341
+ preserves an application's existing acceptance-based composer. The typed draft
342
+ stays visible until its exact delivery is accepted. A canonical receipt or
343
+ explicit delivery inspection can clear it before the HTTP response arrives;
344
+ a newer draft is kept. After a pre-admission failure, pressing Send again on the
345
+ same trimmed draft reuses that submission's message ID and captured data. A
346
+ different submission replaces that reference; it never selects an older matching
347
+ failure from history. Uncertain delivery still requires **Check delivery**.
348
+ Voice sends keep their own authored capture and do not consume the typed draft.
349
+
350
+ With `queueWhileSending: false`, the composer is disabled during admission and
351
+ overlapping typed or retained voice sends return `false` before calling the
352
+ supplied API. A runtime that supports sending while working still permits the
353
+ next request after admission settles; the host owns its meaning. Queuing is part
354
+ of the initial retained conversation composition, so use consistent options for
355
+ mirrored readers of the same identity. The default is `clearDraftOn: "dispatch"`
356
+ and the existing queuing behavior for a runtime that advertises steering.
357
+
358
+ Set `draftWhileLoading: true` when people should be able to type before the
359
+ application resolves its conversation ID or while the first read is unavailable.
360
+ This keeps one local input scoped to the real actor, endpoint, surface and
361
+ workspace. It creates no conversation, receipt or placeholder identity, and Send
362
+ still requires the existing ready, authorized conversation. An explicit submit
363
+ before readiness returns `false`; the application can show its existing loading
364
+ or offline feedback while the words remain in the composer.
365
+
366
+ Once the actual ID resolves, the input moves into that conversation's retained
367
+ draft. If it already has a different draft, the existing text-submission helper
368
+ appends the new text with a newline without sending; identical text stays once.
369
+ A known target keeps its own draft when loading or reconnecting, and changing
370
+ targets never copies the previous target's draft. Actor or scope changes clear
371
+ unresolved input synchronously; authorization denial still clears and disables
372
+ the resolved composer. This option does not share unresolved input between
373
+ separate views: shared retention begins with the actual conversation ID.
374
+ The default is `false`.
375
+
376
+ Custom presentation can supply `useAssistantConversation({ onEvent(event) {} })`
377
+ to receive notifications such as an application's `{ type: "application" }`
378
+ invalidation. The existing subscriber checks the event identity/order and applies
379
+ its stream snapshot, or schedules its canonical read, before invoking the hook.
380
+ Keep transcript rendering on `adapter`/`runtime`; the hook does not replace that
381
+ state handling. An application can use its own invalidation to coalesce an
382
+ authorized product-state refresh without fetching product state for every text
383
+ chunk. Only current, active readers receive callbacks. Releasing a view removes
384
+ its callback even when a mirrored view or `runtime.retain()` keeps the shared
385
+ subscription alive; retaining a runtime does not retain a released view's hook.
386
+ Hooks report their own errors. A throwing or rejected hook cannot interrupt
387
+ other readers or shared delivery, and no additional socket connection is created.
388
+
389
+ An application can supply a `data` object to the standard element, or a value,
390
+ ref or getter to `useAssistantConversation({ data })`. A new typed submission
391
+ captures it with the message in the existing delivery entry; a retry keeps the
392
+ original data even if the current selection has changed. A retained voice reader
393
+ can pass its own utterance capture with
394
+ `runtime.send({ message: text, data: capturedData }, { messageId })`; this leaves
395
+ the typed draft and its data untouched. The server must explicitly declare an
396
+ allowed `conversationDataSchema`. Data stays untrusted message input and never
397
+ supplies authenticated identity, authorization context or native configuration.
398
+ With `AssistantFeature`, expose that `json-rest-schema` schema as
399
+ `assistant.conversations.conversationDataSchema`; the feature forwards it to both
400
+ existing route and action validators. Explicit library composition passes the
401
+ same schema to `registerAssistantRoutes` and `createAssistantActions` through
402
+ their `conversationDataSchema` option. Without a declared schema, browser data is
403
+ not forwarded. The application still checks access to any resource identified by
404
+ those fields before using it.
405
+
406
+ `attachments` and `suggestions` accept their existing shared composable results
407
+ directly, or reactive presentation adapters. Upload and suggestion services remain
408
+ application-supplied, as described below. Set `questions` to `true` for the
409
+ standard retained answer form, or supply its small configuration described below.
410
+ Custom question and model presentation adapters remain available; model controls
411
+ do not expose a raw engine or credential-selection endpoint. Native goal controls use the runtime's
412
+ declared command and budget capabilities. Unsupported controls stay absent.
413
+
414
+ ### Existing bounded-task commands
415
+
416
+ An application whose assistant already returns one final result from an
417
+ authenticated command can use the same `useAssistantConversation` binding with
418
+ an explicit `boundedTask`. This mode retains its browser-local request history
419
+ and mounted lifetime. It does not acquire a canonical conversation, realtime
420
+ subscription, delivery ledger or server transcript.
421
+
422
+ ```js
423
+ const binding = useAssistantConversation({
424
+ boundedTask: {
425
+ command: existingCommand, // the actual useCommand owner: run and isRunning
426
+ endpoint: assistantEndpoint,
427
+ scope: () => selectedSessionId.value,
428
+ input: messages => ({ messages: messages.map(({ content, role, table }) => ({ content, role, table })) }),
429
+ result: response => ({ content: response.answer, role: "assistant", sql: response.sql }),
430
+ onResult(response, { message, scope }) {
431
+ // Optional application presentation, using the captured submitted record.
432
+ presentResult(response, { table: message.table, sessionId: scope });
433
+ }
434
+ },
435
+ active: helperAvailable,
436
+ data: () => ({ table: selectedTable.value }),
437
+ presentation: {
438
+ assistantLabel: "Copilot", variant: "task", visible: true,
439
+ placeholder: "Ask about this table…", submitOnEnter: false,
440
+ submitOnModifierEnter: true
441
+ }
442
+ });
443
+ ```
444
+
445
+ Pass `binding.adapter.value` to `AssistantConversationElement`, retaining product
446
+ slots and actions. The binding owns `runtime.value.draft`, ordered `messages`,
447
+ `submit()` and `clearHistory()`. History-only clearing preserves the draft; call
448
+ it at the application's existing hydration boundary. The command keeps its
449
+ normal authenticated HTTP, running state and error feedback. Required command
450
+ and input/result mappings are validated; endpoint and scope are explicit.
451
+
452
+ Submission captures `data` into the user record, appends it and clears the draft
453
+ before invoking the command once. It maps a truthy final response only while the
454
+ component remains mounted and the captured application scope is still current,
455
+ then calls optional `onResult`. Scope is a local presentation fence, not request
456
+ authority. Input mapping and the server's existing action schema determine which
457
+ fields are sent. Command errors propagate unchanged; a failed request leaves its
458
+ local authored row and does not create a receipt or automatic retry. Successful
459
+ submission retains the original undefined return value.
460
+
461
+ `active` gates new submissions. Changing availability or hiding the panel does
462
+ not cancel admitted work. Disposal discards late presentation through the same
463
+ owner; native cleanup remains with the existing server task. The bounded mode
464
+ defaults to modifier-Enter submission and ordinary Enter for a new line. Its
465
+ index-derived message keys are display identities, never proof of admission.
14
466
 
15
467
  ## Ownership
16
468
 
17
469
  | JSKIT owns | The application supplies |
18
470
  | --- | --- |
19
- | Bubbles, rich text, reasoning groups, long-message expansion, scroll following, composer, delivery controls, working status, suggestions, model and goal controls, file and question UI | Conversation selection, labels, loading/errors, current draft and action implementations |
471
+ | Bubbles, rich text, reasoning groups, long-message expansion, scroll following, composer, delivery controls, working status, suggestions, model and goal controls, file and question UI | Conversation selection, labels, optional presentation and authorized feature services |
472
+ | Supplied client binding: draft, loading/error state, delivery, actions, streaming and reconnect | Real conversation identity and endpoint, authenticated host context and application policy |
20
473
  | Turn grouping, message deduplication, final-answer replacement and history pagination | Storage adapter, authorized scope, transaction/locking implementation, retention, migrations and attachment bytes |
21
- | Codex JSON-RPC and notification classification, detached-turn completion/recovery, OpenCode HTTP/SSE | Provider process/connection, account credentials, execution environment, permissions, tools, model policy and reconnect ownership |
474
+ | Native process/connection coordination, shared-runtime reconnect/recovery, Codex JSON-RPC and notification classification, detached-turn completion and OpenCode HTTP/SSE | Authorized connection/account facilities, existing managed execution/environment, permissions, tools and model policy |
22
475
  | Display of supplied configuration and optional editing controls | Authoritative configuration, permitted changes and server validation |
23
476
 
477
+ `AssistantConversationClientElement` and `useAssistantConversation` construct the
478
+ generic adapter and retain its state for the conversation. Applications using
479
+ these supplied bindings do not implement unused optional adapter areas. A custom
480
+ low-level `AssistantConversationElement` adapter instead supplies its own draft,
481
+ loading/errors and action implementations through the contract below.
482
+
24
483
  No global store or router is installed by the element. Multiple assistants can
25
- coexist with separate adapters. The application mounts it in a pane with a
484
+ coexist using the supplied binding or custom adapters. The application mounts it
485
+ in a pane with a
26
486
  definite height and `min-height: 0`. The composer does not shrink under transcript
27
487
  pressure. Bubbles use the application's Vuetify theme colors.
28
488
 
@@ -76,6 +536,16 @@ available for an explicit retry. Clear the error when retrying succeeds.
76
536
  control the corresponding transcript presentation. `userMessageFormat` is
77
537
  `"formatted"` by default; `"plain"` preserves literal user-authored text.
78
538
 
539
+ For live speech that has not been admitted, supply
540
+ `adapter.conversation.previewMessage = { messageId, text }` (the original
541
+ voice capture's `id` is also accepted). The element displays one pending user
542
+ message after merging canonical and delivery turns. A matching canonical or
543
+ delivery message ID suppresses that preview, so an admitted utterance appears
544
+ once. The application owns updating or clearing its current capture. Preview
545
+ text never enters saved history, the retained runtime's turns or receipt
546
+ reconciliation; it cannot acknowledge a pending or uncertain send. Keep it out
547
+ of `adapter.conversation.turns`.
548
+
79
549
  ### Message delivery
80
550
 
81
551
  Use `createAssistantMessageDelivery` from `/client/conversation` (or the
@@ -115,7 +585,7 @@ or generated prompts; it still uses the same delivery controller.
115
585
 
116
586
  `send(payload, options?)` inserts the message synchronously, before invoking
117
587
  `deliver`. Payloads must be JSON-compatible and are copied at submission. They
118
- require `message`; optional `displayMessage` and
588
+ require nonempty `message` or `displayAttachments`; optional `displayMessage` and
119
589
  `displayAttachments` control the visible bubble. Additional fields travel to
120
590
  `deliver` unchanged. Include settings and attachment IDs in the payload so a
121
591
  retry retains the original request even if the composer settings later change. Each send creates a stable `messageId`, or
@@ -127,6 +597,17 @@ Success removes the pending treatment but retains the bubble until authoritative
127
597
  history contains its `user.messageId`, so it cannot flicker away before history
128
598
  arrives. Older histories without IDs can match by text and timestamp.
129
599
 
600
+ A host that saves an authored row before native admission must project
601
+ `user.receipt: false` on that row until it has exact admission evidence. This
602
+ transient read/patch qualifier keeps one visible bubble with its pending or
603
+ uncertain delivery state; it cannot settle a receipt race or discard a restored
604
+ request. Omission preserves the normal accepted authored-row contract. Keep the
605
+ qualifier out of persisted history, prompt history and message fingerprints.
606
+ An explicit successful delivery or exact receipt inspection can still accept
607
+ the request. A failed native read remains unknown; it must never trigger replay.
608
+ An error or unsuccessful response with `status: "uncertain"` retains the delivery
609
+ for inspection, and `send` continues to refuse that uncertain message ID.
610
+
130
611
  For native-agent steering, set `composer.queueWhileSending: true` to accept more
131
612
  text messages while delivery is outstanding. Custom composers call
132
613
  `send(payload, { queue: true })`. Each message appears immediately and keeps its
@@ -156,6 +637,21 @@ any persistence needed to retain unsent messages across a browser reload.
156
637
  Transport promises should settle on admission, not after the full AI answer;
157
638
  streaming and provider execution remain separately observable application work.
158
639
 
640
+ For canonical receipt observation, supply reactive `receiptTurns` and
641
+ `uncertainOnError: true`. Unknown transport failures then retain an uncertain
642
+ entry for inspection; they do not expose Retry, Edit or Cancel. Explicit
643
+ pre-admission rejection can still return `{ ok: false, error }`. Use `accept(id)`
644
+ only after the backend confirms admission. `restoreUncertain(request, savedTurns)`
645
+ restores the public pending request without sending it again.
646
+
647
+ The optional `send(..., { onAccepted })` callback acknowledges a captured UI
648
+ attachment queue. HTTP acceptance, matching canonical reconciliation and explicit
649
+ `accept(id)` notify it once. Retry retains the original callback and payload.
650
+ The callback stays private in memory; it is absent from messages, serialized
651
+ payloads and persisted receipts. An acknowledgement callback must report its own
652
+ UI errors without reversing accepted delivery. A full browser reload still needs
653
+ the application's existing saved-draft attachment state.
654
+
159
655
  ### Turns and messages
160
656
 
161
657
  ```js
@@ -299,6 +795,37 @@ requests. Disposal cancels requests; late results are ignored. `items`, `loading
299
795
  Selection edits the draft; submission remains explicit. The standalone example
300
796
  includes a separate suggestion integration and server prompt.
301
797
 
798
+ For the supplied client element, bind that same owner to the exposed canonical
799
+ target. `app.suggest` below is the application's authorized suggestion operation,
800
+ separate from ordinary conversation send:
801
+
802
+ ```js
803
+ const conversation = ref(null);
804
+ const target = computed(() => conversation.value?.runtime || null);
805
+ const suggestions = useAssistantSuggestions({
806
+ active: () => target.value?.available.value === true &&
807
+ target.value.snapshot.value?.status === "ready",
808
+ requestKey: () => target.value?.identity || null,
809
+ draft: () => target.value?.draft.value || "",
810
+ configuration: () => ({ integrationId: "suggestions" }),
811
+ generate(input) {
812
+ return app.suggest({ ...input, conversationId: target.value.identity.conversationId });
813
+ },
814
+ onSelect(prompt) {
815
+ if (!target.value?.available.value) return false;
816
+ target.value.draft.value = prompt;
817
+ return true;
818
+ }
819
+ });
820
+ ```
821
+
822
+ Include the host's suggestion policy and visible-reader state in `active`.
823
+ The complete identity in `requestKey` invalidates prior actors and targets;
824
+ changing a suggestion model in `configuration` affects only that generator.
825
+ The host operation derives its authenticated actor and checks the conversation
826
+ itself. When a view closes while voice retains its draft, disable that view's
827
+ suggestion owner; late results are discarded by the existing composable.
828
+
302
829
  ### Models and application configuration
303
830
 
304
831
  Use `adapter.models` for `AssistantModelControl`:
@@ -319,7 +846,18 @@ Use `adapter.models` for `AssistantModelControl`:
319
846
 
320
847
  Selections edit application draft configuration. `apply()` saves it and closes
321
848
  the chooser unless it resolves `false`. The app handles errors and sets `saving`
322
- and `changesDisabled`. More than six model choices get searchable selection.
849
+ and `changesDisabled`, and includes its admission rules in `canSave`. More than
850
+ six model choices get searchable selection.
851
+ The canonical element's `models` prop and `useAssistantConversation({ models })`
852
+ accept the same owner with plain fields, refs or computed fields. Its controls
853
+ are shown only while the canonical target is available. The application keeps
854
+ the actor-scoped catalogue, draft selection and authorized apply/reload callbacks;
855
+ the binding does not copy those settings or send native configuration.
856
+
857
+ An application with its own existing model dialog can put its launcher in
858
+ `composer-tools` and keep that dialog's selection and save callback. This preserves
859
+ the standard composer and delivery controls while retaining application-specific
860
+ catalogue and access policy.
323
861
  The standalone component additionally provides `before-choices`, `model-note`,
324
862
  `provider-controls`, and `footer` slots for account-specific controls. The
325
863
  aggregate's `configurationMode` applies to its separate custom configuration
@@ -351,9 +889,31 @@ accumulated **active running time**, and `sampledAt` is its sample time in epoch
351
889
  **milliseconds**. The UI adds time only while active. Omit elapsed time if the
352
890
  backend cannot supply it truthfully. The clock hides in a narrow conversation
353
891
  pane; the light stays visible and details retain the time. Reduced motion uses
354
- a steady red light. Pausing prevents future automatic turns and does not interrupt
355
- the current turn; Stop is separate. Omit `goal` or use `enabled: false` for agents
356
- without goals, including OpenCode.
892
+ a steady red light. Pause follows the command's declared behavior:
893
+ `pauseInterruptsTurn: true` means it stops the current turn and keeps the goal;
894
+ otherwise it prevents further automatic turns while Stop interrupts current work.
895
+ Likewise, `cancelInterruptsTurn` determines whether Cancel stops current work while
896
+ clearing the goal. The supplied client binding derives these fields and supported
897
+ callbacks from runtime capabilities; applications using it need no custom goal
898
+ adapter. For a low-level adapter, omit `goal` or use `enabled: false` when goals
899
+ are unsupported. The supplied binding hides unsupported controls automatically.
900
+
901
+ The supplied binding owns goal reads, command state and message receipts. It
902
+ refreshes on goal events, reconnect and window focus, with a 30-second passive
903
+ read deadline and no goal poll. Passive failures remain local to the control.
904
+ An explicit `goal: true` option also enables reads when an application's retained
905
+ goal may belong to a different target from its visible conversation. Such an
906
+ authorized facade can return `{ status, goal, target: { segmentId, capabilities },
907
+ routing? }` from its existing goal read. The target and declared goal capabilities
908
+ must come from that same native read; the client does not infer an engine or
909
+ reconstruct native expectations. Standalone goal reads still return a goal or
910
+ null. An object-valued `goal` presentation adapter remains supported.
911
+
912
+ Custom product presentation can use the retained runtime's `goalView`,
913
+ `goalState`, `goalLoadError`, `changeGoal` and `refreshGoal` instead of creating
914
+ another remote resource. New commands capture that view's target and goal ID.
915
+ A failed message command keeps its original UUID and request for explicit retry;
916
+ unknown delivery is inspected, never automatically resent.
357
917
 
358
918
  ### Files
359
919
 
@@ -386,6 +946,20 @@ the app's deletion endpoint must preserve files already accepted by a message,
386
946
  including when a browser loses the acknowledgement. Never infer acceptance from
387
947
  an HTTP request merely starting.
388
948
 
949
+ The supplied canonical client captures the attachment owner and submitted IDs.
950
+ It acknowledges only those IDs when HTTP, canonical history or explicit delivery
951
+ inspection confirms acceptance, preserving newer ready and uploading files.
952
+ Uncertain delivery retains its selected receipts until confirmation. Only IDs
953
+ are sent to the conversation API; attachment-only requests preserve empty
954
+ authored text. The runtime's attachment capability and unresolved upload state
955
+ gate submission. A disabled Send leaves ordinary textarea editing, including
956
+ newlines, available.
957
+
958
+ Upload scopes must track authenticated user, workspace and conversation changes.
959
+ Use the application's saved-draft retention when uploaded files must survive a
960
+ view closing or a browser reload. The binding's acknowledgement never calls
961
+ delete or abandon; those remain the upload owner's explicit policy.
962
+
389
963
  The shared `AssistantAttachmentQueue`, `AssistantMessageAttachments` and
390
964
  `AssistantAttachmentPreview` provide queue, sent-file list and preview UI.
391
965
  Set `adapter.attachments.open(receipt)` for queue preview. Sent-file rendering
@@ -398,14 +972,65 @@ picker, screenshot producer or project file browser remains an application tool.
398
972
 
399
973
  ### Questions
400
974
 
975
+ Set `questions` on `AssistantConversationClientElement`, or pass
976
+ `questions: true` to `useAssistantConversation`, to enable ordinary chat answer
977
+ fields. The retained conversation selects the latest authorized completed reply,
978
+ ignores an in-progress reply and stops at a subsequent user message. It owns the
979
+ answers, choice, dismissal and submitted-form state; no application selector or
980
+ reset watcher is needed.
981
+
982
+ ```vue
983
+ <AssistantConversationClientElement :conversation-id="conversationId" questions />
984
+ ```
985
+
986
+ The standard form requires all numbered answers, appends optional composer
987
+ context and sends one ordinary message through the same delivery entry. Choosing
988
+ a suggested-answer chip waits for Send and uses that choice's submitted value.
989
+ **Answer normally instead** keeps the draft. Submitted forms hide while delivery
990
+ is pending; retries keep the original formatted answer, and uncertain delivery
991
+ uses **Check delivery**.
992
+
993
+ Mirrored standard elements for the same target share the form. Answers and
994
+ dismissal survive a text panel closing while voice retains that conversation.
995
+ A different target or authenticated actor has separate state, even when the
996
+ question text is identical. Loss of authorized availability clears the form.
997
+
998
+ An optional `extraChoice` adds a product-specific fallback option to each select;
999
+ matching values are not duplicated. No extra option is added by default:
1000
+
1001
+ ```vue
1002
+ <AssistantConversationClientElement
1003
+ :conversation-id="conversationId"
1004
+ :questions="{ extraChoice: { label: 'I am not sure', value: 'I am not sure' } }"
1005
+ />
1006
+ ```
1007
+
1008
+ Use the same question configuration for mirrored views. The first active
1009
+ standard reader supplies the shared optional choice policy. Setting `questions`
1010
+ to `false` or omitting it hides that view's question controls.
1011
+
1012
+ For custom presentation, the same prop accepts an explicit question owner or the
1013
+ existing question adapter. `useAssistantQuestions` from
1014
+ `/client/conversation-questions` supplies the extracted answer state to a custom
1015
+ composer. Supply its authorized completed message and optional `extraChoice`;
1016
+ `latestAssistantMessageAwaitingUserReply` from `/shared/conversation` implements
1017
+ the standard message selection. A custom owner remains responsible for its
1018
+ message source and scope resets.
1019
+
401
1020
  `AssistantQuestionInputs` renders numbered question fields and suggested-answer
402
1021
  chips. Pass `questions`, optional `selectItems` keyed by question name, `choices`,
403
1022
  `v-model:answers`, and `v-model:choice`; handle `dismiss`. Questions contain
404
1023
  `{ name, number, label, choices }`; choices use `{ value, label, selectLabel }`.
405
1024
  The aggregate accepts the same fields under `adapter.questions`, with
406
1025
  `setAnswers`, `setChoice`, and `dismiss` callbacks. Existing question parsers and
407
- submission formatters are exported from `/shared/conversation`. The app decides
408
- which message is awaiting a reply and submits answers through its normal action.
1026
+ submission formatters are exported from `/shared/conversation`.
1027
+
1028
+ For a custom composer, `capture(draft)` returns `{ message, questionText }` without
1029
+ changing state. Keep the existing complete-answer and application admission
1030
+ checks, then call `markSubmitted(questionText)` at local submission and deliver
1031
+ the captured message through the normal action. `reset()` clears local question
1032
+ state. Native permission prompts and tool responses retain their separate
1033
+ authorized owners; these prose fields introduce no answer endpoint.
409
1034
 
410
1035
  ### Incremental answers
411
1036
 
@@ -427,9 +1052,33 @@ await transcript.writeConversationUserMessage(scope, { messageId: requestId, tex
427
1052
  const { conversationLog, pagination } = await transcript.readConversationLogPage(scope, { limit: 20 });
428
1053
  ```
429
1054
 
430
- The memory adapter is explicitly transient. Applications may supply SQL,
431
- filesystem, document-store, or other storage. JSKIT neither chooses a directory
432
- nor creates database tables. The storage object implements:
1055
+ The memory adapter is explicitly transient. For a single-process application,
1056
+ use the supplied file adapter with an application-owned private directory:
1057
+
1058
+ ```js
1059
+ import { createFileConversationStorage } from "@jskit-ai/assistant-core/server/conversation";
1060
+
1061
+ const storage = createFileConversationStorage({ directory: "/var/lib/my-app/conversations" });
1062
+ const transcript = createConversationTranscript({ storage });
1063
+ ```
1064
+
1065
+ Share one adapter instance across the application's conversations. It serializes
1066
+ writes per scope, flushes a replacement file before publishing it atomically,
1067
+ and retains messages across application restarts. Saved files use mode `0600`;
1068
+ a newly created directory uses `0700`. Existing directory permissions remain
1069
+ the operator's responsibility. Scopes are hashed into filenames and checked
1070
+ against each saved document. JSON-compatible message metadata and attachment
1071
+ descriptors are stored; attachment bytes remain application-owned.
1072
+
1073
+ Damaged JSON or an unsupported format causes an explicit error and is never
1074
+ silently replaced with an empty conversation. Back up this directory with the
1075
+ application's attachment data. This adapter is for one writer process: use an
1076
+ application database or another adapter with cross-process transactions when
1077
+ multiple servers write the same conversations.
1078
+
1079
+ Applications may supply SQL, filesystem, document-store, or other storage.
1080
+ JSKIT does not choose the directory or create database tables. The storage
1081
+ object implements:
433
1082
 
434
1083
  ```js
435
1084
  {
@@ -444,7 +1093,8 @@ client-supplied scope directly into storage. Apply the same authorization to
444
1093
  history, sends, stops, deletion and attachment reads. The memory reference
445
1094
  adapter requires a nonempty string; other adapters may use structured scopes.
446
1095
 
447
- Each callback receives these asynchronous operations:
1096
+ Each callback receives these asynchronous operations. The first six serve the
1097
+ transcript service. All nine are mandatory for `createConversationRuntime()`:
448
1098
 
449
1099
  | Transaction operation | Contract |
450
1100
  | --- | --- |
@@ -452,15 +1102,20 @@ Each callback receives these asynchronous operations:
452
1102
  | `readTurn(id)` | Detached turn snapshot in the client model above, or null if absent. |
453
1103
  | `nextTurnId()` | Allocate an ID after the current tail under the write lock. |
454
1104
  | `hasMessage(messageId)` | Check uniqueness across the whole scoped conversation. |
455
- | `appendMessage(turnId, message)` | Save `{ role, text, messageId, at }`; user messages also carry `attachments` and `turnMetadata`. |
1105
+ | `appendMessage(turnId, message)` | Save `{ role, text, messageId, at }` and any supplied `attachments`, `origin` and `turnMetadata`; user or application messages may start turns. |
456
1106
  | `replaceAssistant(turnId, message)` | Idempotently replace the final answer for that exact turn, preserving its existing timestamp/identity. |
1107
+ | `readMetadata()` | Detached conversation metadata object; return `{}` for an empty scope. |
1108
+ | `writeMetadata(value)` | Replace conversation metadata within this transaction, committing atomically with its turn/message changes. |
1109
+ | `updateTurnMetadata(turnId, patch)` | Shallow-merge a detached patch into an existing turn's metadata; preserve unrelated keys and reject absent turns. |
457
1110
 
458
1111
  `write` serializes mutations for the same scope, including duplicate checks and
459
1112
  ID allocation. A successful return means the write is durable for that adapter.
460
1113
  Database adapters should use a transaction; filesystem adapters must publish
461
1114
  the message only after its attachment references and metadata are recoverable.
462
1115
  Errors reject the operation and must remain visible to the caller. Do not
463
- acknowledge a failed save. Reads must not expose mutable backing objects.
1116
+ acknowledge a failed save. A failed runtime transaction must leave both transcript
1117
+ and metadata unchanged; serializing independently durable file writes is insufficient.
1118
+ Reads must not expose mutable backing objects.
464
1119
  Use storage-level locks or transactions when several processes share storage;
465
1120
  an in-process promise queue alone does not provide that guarantee.
466
1121
 
@@ -489,28 +1144,1463 @@ Run the reusable adapter checks against an isolated fixture:
489
1144
 
490
1145
  ```js
491
1146
  import { verifyConversationStorageContract } from "@jskit-ai/assistant-core/testing/conversation-storage";
492
- await verifyConversationStorageContract(storage);
1147
+ await verifyConversationStorageContract(storage, { runtime: true });
493
1148
  ```
494
1149
 
495
1150
  Those checks cover scoped isolation, concurrent duplicate writes, stable
496
1151
  ordering, pagination, final replacement, attachments and detached reads. An
497
- app must additionally test its authentication, crash/reopen behavior,
1152
+ adapter selected with `runtime: true` also checks all metadata operations, scope
1153
+ isolation, patch preservation and combined transcript/metadata rollback. Use a
1154
+ fresh isolated fixture for each invocation; omit the option only for transcript-only
1155
+ adapters. An app must additionally test its authentication, crash/reopen behavior,
498
1156
  multi-process writes, failed commits and attachment cleanup.
499
1157
 
500
1158
  ## Provider contract
501
1159
 
502
- API-model apps can use `createAiConnectionClient` with an authorized AI integration resolver (see [Assistant](./assistant.md)), or `createAiClient` for existing environment-based configurations, and the tool-catalog helpers
503
- from `@jskit-ai/assistant-core/server`, or the complete assistant runtime.
504
- Native-agent hosts can import:
1160
+ ### Application conversation operations
1161
+
1162
+ `createConversationRuntime()` currently supplies direct API inference and basic
1163
+ Claude, Codex and OpenCode conversations through the same application operations. The API path uses
1164
+ the existing AI connection resolver and an application-owned storage choice. It
1165
+ requires no native CLI or engine lifecycle callbacks:
1166
+
1167
+ ```js
1168
+ import { createAiConnectionResolver } from "@jskit-ai/connectors-catalog/server/ai";
1169
+ import { createConversationRuntime, createFileConversationStorage } from "@jskit-ai/assistant-core/server/conversation";
505
1170
 
506
- - `CodexAppServerJsonRpcClient` from `/server/codex-client`;
507
- - notification classifiers from `/server/codex-events`;
508
- - `createCodexAppServerDetachedTurnWatcher` from `/server/codex-turn`;
509
- - `createOpenCodeServerClient` from `/server/opencode-client`.
1171
+ const connections = createAiConnectionResolver({
1172
+ configuration: integrationConfiguration,
1173
+ authorize: authorizeModelConnection
1174
+ });
1175
+ const runtime = createConversationRuntime({
1176
+ engine: "api",
1177
+ connections,
1178
+ defaultIntegrationId: "assistant",
1179
+ storage: createFileConversationStorage({ directory: privateConversationDirectory }),
1180
+ authorize: ({ context, conversationId, operation }) => canAccessConversation(context, conversationId, operation)
1181
+ });
1182
+ const conversation = await runtime.open({
1183
+ id: applicationConversationId,
1184
+ context: authenticatedContext,
1185
+ configuration: { systemPrompt: "Answer concisely." }
1186
+ });
1187
+ const unsubscribe = await conversation.subscribe(event => publishToAuthorizedClient(event));
1188
+ const receipt = await conversation.send({ messageId: applicationMessageId, text: "Hello" });
1189
+ const snapshot = await conversation.read();
1190
+ await conversation.wait(); // optional: wait for current work, without polling
1191
+ await conversation.configure({ systemPrompt: "Use the updated instructions." });
1192
+ // await conversation.cancel(); // stop current model work
1193
+ unsubscribe();
1194
+ await runtime.close(); // application shutdown
1195
+ ```
1196
+
1197
+ Authorization belongs to the application and is checked on each operation and
1198
+ before subscription output. The existing connection resolver separately checks
1199
+ model-account access before every request. Secrets stay in its server-side result.
1200
+ `context` is never written into conversation storage.
1201
+
1202
+ `open({ id, context })` resumes saved configuration. Supplying different initial
1203
+ configuration to an existing conversation is rejected; change it explicitly with
1204
+ `configure()` while the conversation is idle. Closing the runtime or disposing a
1205
+ conversation stops its current inference and releases observers, retaining history.
1206
+
1207
+ `engine` and `host` on the runtime are defaults. `open({ id, context, engine, host,
1208
+ configuration })` may choose them per conversation. A saved conversation resumes
1209
+ its saved engine when `engine` is omitted, even when the runtime default differs.
1210
+ The host is a server-side facility, never browser input: it supplies the worktree,
1211
+ credential environment and execution owner for that conversation. Different
1212
+ conversations in one runtime can use different hosts. An open handle rejects a
1213
+ second, conflicting host; native bindings also validate their saved worktree and
1214
+ credential scope on reopen. Environment values are not persisted.
1215
+
1216
+ An existing application using the extracted Codex owner can supply
1217
+ `host.conversation({ id, context })` to bind its original transcript, native
1218
+ identity, delivery journal and lifecycle facilities. That bound path uses those
1219
+ existing records; it does not create a second runtime record or backfill history.
1220
+ This is an advanced host integration, not required for ordinary file/custom
1221
+ transaction storage.
1222
+
1223
+ Such a server integration may open a handle with `representation: "native"` when
1224
+ its existing command contract requires the original result. The option belongs
1225
+ to that handle and each invocation, not the cached conversation or saved state.
1226
+ Send/Cancel preserve the original result and its captured session when supplied;
1227
+ raw goal controls retain their exact native expectations. Canonical handles still
1228
+ share the same queue, native owner and receipts, and receive the ordinary canonical
1229
+ projection. HTTP actions do not accept this option, and canonical events never
1230
+ include the raw native result. A known native ID alone does not prove successful
1231
+ admission; the original bound sender must confirm delivery.
1232
+
1233
+ ### Bounded native structured output
1234
+
1235
+ Claude, Codex and OpenCode report `capabilities.structuredOutput: true`. A server
1236
+ may supply `configuration.outputSchema` when opening a conversation, or change it
1237
+ with `configure()` while idle. The schema resumes with the existing saved
1238
+ configuration. It is not a browser-authored message field or a permission grant.
1239
+
1240
+ ```js
1241
+ await conversation.configure({
1242
+ outputSchema: {
1243
+ type: "object",
1244
+ additionalProperties: false,
1245
+ properties: { answer: { type: "string", maxLength: 1_200 } },
1246
+ required: ["answer"]
1247
+ }
1248
+ });
1249
+ ```
1250
+
1251
+ The original bounded schema validator rejects unsupported or unbounded schemas
1252
+ before dispatch. Objects require every declared property and reject additional
1253
+ properties; strings need a finite `maxLength` or string enum, and arrays need a
1254
+ finite `maxItems`. Boolean and null values are also supported. Below the root,
1255
+ `anyOf` may contain up to 64 closed object alternatives. The largest alternative
1256
+ counts toward the response bound; their sizes are not added together. Alternatives
1257
+ count toward the existing nesting limit. The other limits remain 64 KiB of schema,
1258
+ eight nesting levels, and 64 properties or enum values.
1259
+ The worst-case JSON response, including escaped string characters, must fit the
1260
+ driver's `limits.maxOutputCharacters` (64,000 by default).
1261
+
1262
+ Claude uses its native schema flag and restarts the owned process when the schema
1263
+ changes, retaining the conversation. Codex uses its native turn schema setting.
1264
+ OpenCode uses the existing schema instruction and removes a single JSON fence
1265
+ from the completed answer; raw output remains subject to the output bound, and
1266
+ the schema instruction counts toward `limits.maxInputCharacters`. Applications
1267
+ still validate the returned result before applying product behavior. Setting
1268
+ `outputSchema: undefined` restores ordinary output. The API driver reports this
1269
+ capability as false and rejects the option before resolving an account or sending.
1270
+
1271
+ ### Engine selection and native conversation replacement
1272
+
1273
+ `select()` changes the engine or model while retaining native histories. Supply
1274
+ the current segment and a stable operation ID, just as for an ordinary accepted
1275
+ application operation:
1276
+
1277
+ ```js
1278
+ await conversation.select({
1279
+ operationId: selectionOperationId,
1280
+ expectedSegmentId: (await conversation.read()).segmentId,
1281
+ engine: "codex",
1282
+ configuration: { systemPrompt: instructions, model: "gpt-6.1-sol" }
1283
+ });
1284
+ ```
1285
+
1286
+ Selecting a native engine used earlier restores its latest retained binding.
1287
+ Changing a model on the current engine retains that native conversation. Before
1288
+ the next request, JSKIT supplies missed messages, corrections and removed message
1289
+ identities as quoted history. Unchanged messages are not injected again. All
1290
+ corrections are included, even outside the recent-history window; if they exceed
1291
+ `maxContinuityCharacters`, delivery fails with an instruction to renew the context
1292
+ with a briefing. Nothing is silently dropped. The application remains responsible
1293
+ for authorizing edits to its transcript.
1294
+
1295
+ Selection requires idle work and confirmed process cleanup. An uncertain receipt
1296
+ may remain with another selected engine, but the same message ID cannot be sent
1297
+ there again. Return to its original engine and use `inspectDelivery()` to resolve
1298
+ it. Selection never resends the pending request. The API driver has no native
1299
+ thread to restore and starts a new context with the recorded continuity when
1300
+ selected from a different engine.
1301
+
1302
+ One application conversation can retain its transcript across several native
1303
+ conversations. To deliberately start a successor, read its current `segmentId`
1304
+ and supply a stable operation ID:
1305
+
1306
+ ```js
1307
+ const before = await conversation.read();
1308
+ await conversation.replace({
1309
+ operationId: applicationOperationId,
1310
+ expectedSegmentId: before.segmentId,
1311
+ reason: "engine-change", // or model-change, renewal
1312
+ engine: "codex",
1313
+ configuration: { systemPrompt: "Continue the agreed work.", model: "gpt-6.1-sol" },
1314
+ briefing: savedWorkSummary
1315
+ });
1316
+ await conversation.send({ messageId: nextMessageId, text: "Continue." });
1317
+ ```
1318
+
1319
+ Replacement performs no inference. It saves the operation, confirms predecessor
1320
+ cleanup, retains the predecessor's native binding and installs the successor.
1321
+ The first native submission carries the saved briefing and bounded recent written
1322
+ history; later native turns do not repeat that history. The API driver supplies
1323
+ the same continuity with its subsequent request history. Visible user messages
1324
+ remain exactly the submitted text. Carry-over includes up to 30 recent messages,
1325
+ with each text bounded to 2,000 characters and truncation/omission marked. The
1326
+ combined budget is `limits.maxContinuityCharacters` (128,000 by default); an
1327
+ oversized supplied briefing fails before changing the binding.
1328
+
1329
+ The operation requires idle work and resolved delivery. A failed cleanup or commit
1330
+ leaves the saved replacement pending, blocking new sends and configuration changes.
1331
+ Retry exactly the same operation ID and arguments after fixing the problem,
1332
+ including after application restart. A completed retry returns the prior receipt.
1333
+ No previous request is replayed. `configure()` still supports compatible settings
1334
+ changes on the current native history; it does not silently replace that history.
1335
+
1336
+ Conversation Undo/rewind is not exposed. Completed historical branch markers
1337
+ remain readable, and their admission receipts remain reserved. An unfinished
1338
+ historical Undo blocks opening or replacing the conversation and fails offline
1339
+ preflight without changing its saved state. Complete that operation with the
1340
+ previous release before upgrading; the runtime does not guess a new boundary.
1341
+ Application checkpoints and file restoration remain separate application actions.
1342
+
1343
+ The unreleased common runtime now validates its versioned metadata. Earlier local
1344
+ development records with an older runtime version fail with an explicit export/restart
1345
+ message; normal startup does not rewrite them. Consumer migration of published
1346
+ application records requires that application's explicit upgrade process.
1347
+
1348
+ ### Admission and recovery
1349
+
1350
+ `send()` returns after admission; generation continues independently of its HTTP
1351
+ request. The application supplies a stable ID and reuses it for uncertain retries.
1352
+ Repeated IDs return their saved receipt; reusing an ID with different content is
1353
+ rejected. An accepted receipt confirms application admission, not model completion.
1354
+ `read()` returns `conversationLog`, `streaming`, `status`, `phase`, `error`, `configuration`
1355
+ and `capabilities`. Events include `accepted`, `message` snapshots and `settled`,
1356
+ plus common `phase` updates and `error` when an owned process cannot be cleaned up.
1357
+ A `transcript` event carries a saved `upsert-turn` patch from the existing transcript
1358
+ owner. The supplied binding merges it directly and preserves patches received
1359
+ while a history read is in flight. A later independent canonical read remains
1360
+ authoritative; applications do not need their own patch queue or parser.
1361
+ Every driver uses the same phases: `preparing` before dispatch, `working` after
1362
+ admission, `compacting` during observed context compaction, and `retrying` during
1363
+ a native retry. An empty string clears the phase after work settles, including
1364
+ failure or cancellation. Not every engine reports every intermediate phase.
1365
+ Native busy/idle flags and status sentences are not application phase values;
1366
+ the runtime's `status` and `settled` event determine completion.
1367
+
1368
+ Treat message IDs as opaque. Some engines retain an ID from streaming through
1369
+ storage; Claude's temporary stream blocks and saved native snapshots have
1370
+ different IDs. `message-complete` retires the temporary block. The runtime's
1371
+ `streaming` state contains live output, while `conversationLog` contains saved
1372
+ messages. Do not save temporary blocks or assume that every answer in a turn
1373
+ has the same ID. Native snapshot identities match recovered history.
1374
+
1375
+ All engines accept the same configuration fields: required `systemPrompt`, plus
1376
+ optional `integrationId`, `model` and `effort`. API and OpenCode require an
1377
+ authorized integration; Claude and Codex can instead use their native login.
1378
+ With an integration, an explicit `model` must match its exact resolved model.
1379
+ Omit `effort` for the provider default. API passes standard effort levels
1380
+ (`none`, `minimal`, `low`, `medium`, `high`, `xhigh`) through its installed SDK;
1381
+ Claude validates its CLI levels (`low`, `medium`, `high`, `xhigh`, `max`), and
1382
+ OpenCode validates the chosen effort against that exact native model's variants.
1383
+ Codex validates its native levels and the selected external model's allowed
1384
+ efforts. Unsupported choices fail instead of being silently replaced.
1385
+
1386
+ Pass those common fields directly to the runtime. It normalizes new configuration
1387
+ inputs for the conversation's actual engine. Set an application-selected default
1388
+ connection once when constructing the runtime:
1389
+
1390
+ ```js
1391
+ const runtime = createConversationRuntime({
1392
+ engine, connections, storage, authorize,
1393
+ defaultIntegrationId: "assistant"
1394
+ });
1395
+ await runtime.open({
1396
+ id: conversationId, context, engine,
1397
+ configuration: {
1398
+ systemPrompt: instructions,
1399
+ integrationId: selectedIntegrationId,
1400
+ model: selectedModel,
1401
+ effort: selectedEffort
1402
+ }
1403
+ });
1404
+ ```
1405
+
1406
+ The default ID is an explicit application choice. It applies only when API or
1407
+ OpenCode requires a connection and no ID was supplied. Claude and Codex keep
1408
+ native login when the ID is absent; an explicit ID is honored by every engine.
1409
+ New inputs to `open`, `configure`, selection and replacement use the same
1410
+ normalization; existing saved configuration is not rewritten on resume.
1411
+ `normalizeConversationConfiguration` remains available from the same
1412
+ `/server/conversation` entry for applications that need to normalize an object
1413
+ before passing it on, but ordinary callers do not need a separate helper call.
1414
+ Normalization omits undefined optional fields so the configuration survives a JSON
1415
+ round trip, but preserves exact values and invalid input for existing validation.
1416
+ It does not resolve credentials, select a substitute model or change engine
1417
+ capabilities. An empty integration catalogue can support an API read-only starter;
1418
+ missing connections or credentials still fail when Send resolves them. Changing
1419
+ settings on an existing conversation uses `configure()` or authorized selection,
1420
+ not a rewritten configuration at startup. No saved data is migrated by normalization.
1421
+
1422
+ `wake({ messageId, text })` starts an application-initiated turn, such as an update
1423
+ from a watched session. It uses the same admission, cancellation and duplicate
1424
+ receipt rules but authorizes `operation: "wake"` separately before dispatch.
1425
+ The turn records `origin: "application"` and a `system` transcript message, not a
1426
+ user message. Native/API input identifies the observation as data within existing
1427
+ authority; it is never promoted to model system instructions. The application
1428
+ still owns its watch/assignment policy and action permissions. A public chat
1429
+ endpoint must not expose wake authority merely because its user can send text.
1430
+
1431
+ Both `send()` and `wake()` accept optional `data`, a JSON object containing
1432
+ application observations or the request's captured UI focus. JSKIT saves it with
1433
+ the accepted message and includes it as application data in model input, without
1434
+ changing the stable system prompt or visible user text. Text and serialized data
1435
+ share the configured input-size limit. A retry must retain the same data as well
1436
+ as the same message ID and text. Supply trusted authorization separately through
1437
+ the conversation context; this data never grants permissions.
1438
+
1439
+ Saved turns distinguish complete, failed, cancelled and interrupted work. Error
1440
+ messages are not assistant replies. After restart, accepted work is marked
1441
+ interrupted and is never replayed. API reservations interrupted before admission
1442
+ were never dispatched; only another explicit send can start them. Storage failure
1443
+ retains unsaved output in the current handle and prevents new submissions through
1444
+ that handle. Repair storage and call `retrySave()` to save the retained result;
1445
+ this never reruns inference. Disposal and shutdown also retry that pending save
1446
+ and report failure without discarding it. Do not blindly resend the message.
1447
+
1448
+ Native acknowledgement can be lost after the engine received a message.
1449
+ `inspectDelivery({ messageId })` checks its native history and returns an accepted
1450
+ receipt only when that message is found. It also recovers output for an already
1451
+ accepted message interrupted by restart. Output remains interrupted unless the
1452
+ adapter can prove native completion; Codex checks the native turn's saved status.
1453
+ Completed application turns, cancellation/failure status and saved application-tool
1454
+ receipts are retained. Repeated inspection does not duplicate messages or execute
1455
+ tools. An application tool without a verified result keeps the turn interrupted,
1456
+ even if native history reports completion; inspect that operation's target before
1457
+ requesting another execution. API connections have no native history to inspect and keep their saved
1458
+ receipt without claiming recovery.
1459
+
1460
+ Recovery commits output and receipts together. If storage fails, restore access
1461
+ and retry the inspection; native work is not repeated. An unknown result leaves
1462
+ the submission unresolved. A message in another segment must be inspected after
1463
+ selecting that segment's engine; recovery never revives a rewound turn. Neither
1464
+ inspection nor reopening sends the message again. If native process cleanup fails,
1465
+ the handle reports unavailable and rejects new work; `cancel()` retries that exact
1466
+ owned cleanup rather than starting another process.
1467
+ An optional `limits.timeoutMs` deadline aborts work and waits for owned cleanup;
1468
+ a failed cleanup is still reported as unavailable rather than as a successful stop.
1469
+
1470
+ The supplied memory and file adapters also provide `readMetadata()`,
1471
+ `writeMetadata(value)` and `updateTurnMetadata(turnId, patch)` inside the existing
1472
+ storage transactions. Runtime state occupies `metadata.runtime` at conversation
1473
+ and turn level. Custom adapters must commit that state with accepted messages in
1474
+ the same transaction. The file adapter remains one-writer storage.
1475
+
1476
+ `createConversationStorage({ readRecord, writeRecord, deleteRecord })` is exported
1477
+ for custom single-writer record stores. Each record contains `{ turns: Map,
1478
+ metadata: object }`; a missing record uses an empty map and metadata object. Its
1479
+ copy-on-write transaction methods match the supplied memory/file adapters. It
1480
+ calls `writeRecord(id, draft, transaction)` to commit a successful transaction.
1481
+ Record formats that store the canonical transcript can use `transaction.readTurn()`
1482
+ while serializing, without copying the transcript's ordering and role logic.
1483
+ Publish the new in-memory record only after its durable write succeeds. The adapter
1484
+ serializes writes through one instance, not across processes. A database-backed
1485
+ application with concurrent writers must implement `read`/`write` using its own
1486
+ database transaction or hold that transaction across record loading and commit.
1487
+
1488
+ The current API driver supports application action tools and authorized attachments.
1489
+ It reports steering and native goals as unsupported. Limits can bound input, output and history characters, output
1490
+ tokens and request duration. Oversized history fails explicitly; it is not silently
1491
+ truncated or presented as native compaction. These capabilities will be extended
1492
+ as their shared integrations are migrated.
1493
+
1494
+ API replies use the shared streaming completion parser. Internal reasoning and
1495
+ provider tool markup stay out of visible replies, including tags split across
1496
+ chunks. Structured and text-encoded application calls use the same authorized
1497
+ execution and saved receipts. A completed response is required before executing
1498
+ a call; interrupted streams do not imply permission to execute or retry it.
1499
+ Output limits count raw provider text, including hidden blocks. Code indentation
1500
+ and blank lines remain intact in ordinary API answers.
1501
+
1502
+ The API driver uses the existing assistant tool-loop policy: up to sixteen tool
1503
+ rounds, then three recovery passes with tools disabled. Progress-only narration
1504
+ is retained as commentary rather than accepted as the final answer. An available
1505
+ clock action marked `current-time` runs before inference for time questions only
1506
+ when it requires no parameters, after connection authorization and turn admission.
1507
+ Uncertain application effects stop the turn; recovery never silently retries them.
1508
+ The legacy database service delegates to the same loop through its transactional
1509
+ adapter over the existing conversation, message and request-claim rows.
1510
+
1511
+ ### Authorized attachments
1512
+
1513
+ Upload storage, file access and retention belong to the application. Pass an
1514
+ `attachments` resolver to `createConversationRuntime()` and send opaque IDs:
1515
+
1516
+ ```js
1517
+ const runtime = createConversationRuntime({
1518
+ storage, authorize, connections,
1519
+ attachments: {
1520
+ async resolve({ attachmentIds, context, conversationId, signal }) {
1521
+ // Check access to each ID for this actor and conversation before reading.
1522
+ const files = await uploads.readAuthorized({ attachmentIds, context, conversationId, signal });
1523
+ return {
1524
+ attachments: files.map(file => ({
1525
+ attachmentId: file.id, fileName: file.name, size: file.bytes.length
1526
+ })),
1527
+ content: files.map(file => ({
1528
+ type: "image", image: file.bytes, mediaType: file.mediaType
1529
+ }))
1530
+ };
1531
+ }
1532
+ }
1533
+ });
1534
+ await conversation.send({ messageId, text: "Describe this image", attachmentIds: [uploadId] });
1535
+ ```
1536
+
1537
+ The resolver returns receipts in the requested order and AI SDK content parts.
1538
+ All four engines accept extracted `{ type: "text", text }` and
1539
+ `{ type: "image", image: Uint8Array, mediaType }` parts. Supported image media
1540
+ types are PNG, JPEG, WebP and GIF; the selected model must also support image
1541
+ input. The API driver additionally accepts `{ type: "file", data: Uint8Array,
1542
+ mediaType, filename? }` when supported by its provider. Native drivers reject
1543
+ other binary files explicitly; extract their text or supported page images in
1544
+ the application's resolver. JSKIT does not fetch attachment URLs or open
1545
+ caller-supplied paths.
1546
+
1547
+ The native Codex owner maps already authorized local image descriptors to native
1548
+ input internally. The host retains upload authorization, leases and storage;
1549
+ applications do not assemble a separate native image-input mapper. This does not
1550
+ change the common resolver's byte-content contract.
1551
+
1552
+ A send accepts at most ten distinct IDs and may contain an empty `text` when it
1553
+ has attachments. The resolver must return one receipt per ID with `attachmentId`,
1554
+ `fileName` and nonnegative integer `size`; an optional `reference` labels it in
1555
+ the transcript. Extra receipt fields are discarded. Transcript and delivery
1556
+ receipts never store file bytes. Native engines may retain their own input in
1557
+ native history. Retain authorized uploads according to the application's policy.
1558
+
1559
+ `limits.maxAttachmentBytes` defaults to 8 MiB for all file content used in a
1560
+ dispatch, including history. API history re-resolves saved files before each
1561
+ request. Conversation replacement re-resolves attachments included in its
1562
+ retained briefing; returning to native history supplies files in changed
1563
+ messages. Unavailable files or denied access fail before dispatch, rather than
1564
+ silently dropping content. Identical accepted retries return their saved receipt
1565
+ without rereading or resending files. Attachment capability is enabled only when
1566
+ the application supplies a resolver.
1567
+
1568
+ ### Application tools
1569
+
1570
+ Pass the application's existing JSKIT action catalogue as `actions` to
1571
+ `createConversationRuntime()`. The common runtime uses `createServiceToolCatalog`
1572
+ for authorized discovery, input/output contracts and execution through the
1573
+ `automation` channel. It does not create another action registry or bypass action
1574
+ permissions. The conversation's `authorize` callback is checked again with
1575
+ `operation: "tool"` before executing a requested action. The action receives the
1576
+ turn's abort `signal` in its trusted execution context.
1577
+
1578
+ Applications with narrower rules for particular turns may supply
1579
+ `toolPolicy({ actionId, kind, context })`, returning a boolean. For example, an
1580
+ application notification can allow only `kind === "query"`. This policy filters
1581
+ discovery and is checked again at execution, including calls using a previously
1582
+ loaded contract. It supplements action permissions; it cannot grant an action
1583
+ that the actor otherwise cannot use. The lower-level `createServiceToolCatalog`
1584
+ accepts the same callback as `isActionAvailable`.
1585
+
1586
+ The API adapter sends these tool schemas using the selected provider's supported
1587
+ protocol and continues after receiving each result. It resolves the same
1588
+ authorized connection again before every inference. No reply-or-tool JSON
1589
+ envelope is required in application instructions. Codex carries calls through its
1590
+ native dynamic-tool protocol. Its search, contract and execute entry points stay
1591
+ fixed for the native thread while action availability and permissions are checked
1592
+ through the current catalogue. Claude uses the same fixed discovery tools through
1593
+ its SDK MCP control connection. JSKIT matches each request to its admitted native
1594
+ tool identity and arguments before shared execution. OpenCode's native plugin
1595
+ uses a private authenticated loopback connection owned by its adapter. Each call
1596
+ must match a native tool part in the currently admitted turn before shared
1597
+ execution. Neither path requires a separately configured tool service.
1598
+
1599
+ Calls and results are committed in the accepted turn's
1600
+ `metadata.applicationTools`. A reservation is saved before execution, and a result
1601
+ is saved before inference continues. Repeated call IDs within that admitted turn
1602
+ return their recorded result; conflicting arguments are rejected. A later user
1603
+ or application turn owns separate call identities, even if the model reuses an
1604
+ ID. An interrupted reservation without a
1605
+ result or a server failure after execution stops the turn with an unknown-outcome
1606
+ error instead of executing again. Applications
1607
+ still own action-level idempotency and inspection of uncertain external effects.
1608
+ The runtime cannot infer whether two different call IDs mean the same mutation.
1609
+
1610
+ Events with `type: "tool"` carry the call's `id`, `name`, `arguments`, `status` and,
1611
+ after completion, `result`. The saved result distinguishes action success from
1612
+ failure. If a result cannot be saved, `retrySave()` persists the retained result
1613
+ without rerunning the action. Cancellation waits for an already invoked action to
1614
+ settle; actions must honor their signal where cancellation is supported.
1615
+
1616
+ `limits.maxToolCalls` defaults to 32 per turn, including repeat requests.
1617
+ `maxToolArgumentBytes` and `maxToolResultBytes` use the service catalogue's existing
1618
+ defaults (32 KiB and 48 KiB). These bounds also apply during streaming and before
1619
+ call reservation; discovery switches to bounded catalogue entry points using the
1620
+ existing `maxDirectTools` and `discoveryPageSize` settings.
1621
+
1622
+ ### Steering an active native turn
1623
+
1624
+ Codex, Claude and OpenCode report `capabilities.steering: true`. Send a distinct
1625
+ message ID with `steer: true` while the conversation is working:
1626
+
1627
+ ```js
1628
+ await conversation.send({
1629
+ messageId: newMessageId,
1630
+ text: "Keep the existing layout and change only the colours.",
1631
+ steer: true
1632
+ });
1633
+ ```
510
1634
 
511
- These are real execution primitives, also consumed by applications with their
512
- own process and permission owners. They do not spawn an agent, select a user
513
- account, grant filesystem access or install tools.
1635
+ The runtime checks authorization with `operation: "steer"`, resolves any supplied
1636
+ `attachmentIds`, and records a separate application turn only after native
1637
+ admission. Earlier displayed output retains its identity. Application actions
1638
+ already in flight keep their original actor and saved result. A repeated accepted
1639
+ message returns its receipt without resending.
1640
+
1641
+ Codex uses its active-turn steering operation. Claude interrupts the current
1642
+ generation, waits for the native interruption barrier, and sends the instruction
1643
+ through the same process. OpenCode admits the instruction in the same session
1644
+ and follows the new input through completion. No adapter recreates the native
1645
+ conversation to imitate steering.
1646
+
1647
+ Steering an idle conversation fails; submit a normal message instead. A rejected
1648
+ instruction does not become an accepted history entry. A lost acknowledgement
1649
+ remains uncertain until `inspectDelivery()` finds native proof. `cancel()` stops
1650
+ the whole active run, including steering and pending native work. A configured
1651
+ request deadline applies to the whole run; steering does not extend it. API
1652
+ connections report steering as unsupported.
1653
+
1654
+ ### Native goals
1655
+
1656
+ Codex and Claude expose `capabilities.goals`; only Codex exposes
1657
+ `capabilities.goalBudgets`. API and OpenCode conversations report goals as
1658
+ unsupported. These operations belong
1659
+ to the server runtime; an application calls them through its authorized transport.
1660
+
1661
+ ```js
1662
+ const state = await conversation.read();
1663
+ await conversation.updateGoal({
1664
+ action: "set",
1665
+ messageId: goalCommandId, // Required when goalCommands.set.delivery is "message".
1666
+ expectedSegmentId: state.segmentId,
1667
+ objective: "Implement and verify the requested change.",
1668
+ tokenBudget: 40_000 // Optional; only when goalBudgets is supported.
1669
+ });
1670
+
1671
+ const goal = await conversation.readGoal();
1672
+ await conversation.updateGoal({
1673
+ action: "pause",
1674
+ expectedSegmentId: (await conversation.read()).segmentId,
1675
+ expectedGoalId: goal.id
1676
+ });
1677
+ ```
1678
+
1679
+ Commands whose `goalCommands[action].delivery` is `"message"` require a stable
1680
+ `messageId`. Resume, pause and cancel require the current `expectedGoalId` and
1681
+ `expectedSegmentId`, preventing controls from acting on another native
1682
+ conversation or goal.
1683
+ Commands are authorized with `operation: "goal"`; inspection uses `readGoal`.
1684
+
1685
+ A host-bound native conversation can report `segmentId: null` before its first
1686
+ thread exists. Pass that explicit value from the latest read; do not invent an
1687
+ ID or omit the expected identity. The original native owner allows only the
1688
+ initial Set to prepare a thread from that state. Later controls use the actual
1689
+ thread and current goal identity. A stopped or replaced thread does not become
1690
+ an unstarted conversation; its existing recovery and replacement checks still
1691
+ apply. The supplied client binding preserves the value automatically.
1692
+
1693
+ Claude uses its original native `/goal` commands through ordinary message
1694
+ admission. Set and resume require idle work; an unfinished goal must be cleared
1695
+ before another is set. Pause stops the current work while retaining the goal.
1696
+ Cancel proves the current work has stopped before sending `/goal clear`, so it
1697
+ also requires a stable `messageId`. Failed cleanup blocks clear and leaves cleanup
1698
+ retryable through `cancel()`. These commands accept neither attachments nor a token
1699
+ budget. After replacement or engine change, an ordinary Send must deliver the
1700
+ pending continuity briefing before a goal can start.
1701
+
1702
+ Codex uses native goal controls and may attach a goal to ordinary active work.
1703
+ Starting another goal after completion requires the completed goal's ID. These
1704
+ controls accept neither attachments nor authored message receipts. They return
1705
+ the native goal state; clearing returns `null`. Codex pause/clear prevents future
1706
+ automatic continuations but does not interrupt an already running turn;
1707
+ `cancel()` stops that work.
1708
+
1709
+ Claude Set, Resume and Clear return the same admission receipt as `send()`.
1710
+ Reusing an accepted command's `messageId` returns its receipt without executing
1711
+ it again. A lost acknowledgement stays uncertain until `inspectDelivery()` finds
1712
+ native proof. Codex controls use the existing native goal identity checks;
1713
+ refresh `readGoal()` after a failed control before choosing another action.
1714
+ Older Codex goal-message records remain inspection-only and require offline
1715
+ inspection; native goal state cannot prove an authored message receipt.
1716
+
1717
+ Codex schedules its own continuations. The native turn owner preserves its
1718
+ transcript grouping and saved output. When an admitted request continues, JSKIT
1719
+ checks access again and retains that request's actor, tool budget and effect
1720
+ receipts. A goal control alone grants no application-tool authority. A terminal
1721
+ goal notification does not settle the run until its final native turn and
1722
+ persistence callbacks have finished. The configured request deadline covers the
1723
+ admitted request, including its continuations. `read()` includes the last observed
1724
+ `goal`; `readGoal()` queries native state without pausing or resuming it, including
1725
+ work started by another native client. Reopening does not resume unobserved goal
1726
+ work automatically.
1727
+
1728
+ After restart, call `inspectDelivery({ messageId })` for an actual authored Send
1729
+ or steering message. Codex checks that exact native receipt and recovers output
1730
+ through the original native turn owner when the saved thread and run establish
1731
+ ownership. Saved cancellation, failure and application-tool receipts remain
1732
+ authoritative. An older binding without the original run record, or a run owned
1733
+ by a different request, reports a `recoveryLimitation` and preserves stored
1734
+ history. Inspection never sends the message again. Use `readGoal()` to inspect
1735
+ native goal controls; they have no authored message receipt to reconstruct.
1736
+
1737
+ ### Native coding tools
1738
+
1739
+ The native drivers disable built-in tools by default. An application that intends
1740
+ to run coding agents can explicitly grant them in its server-side host:
1741
+
1742
+ ```js
1743
+ host: { workdir: applicationWorkdir, nativeTools: true }
1744
+ ```
1745
+
1746
+ `conversation.capabilities.nativeTools` reports this grant. Codex can execute
1747
+ commands and edit files; Claude enables its built-in command, file and notebook
1748
+ tools; OpenCode enables its command, file and task-list tools. The application
1749
+ still controls the execution account, working directory and environment. This
1750
+ grant uses that account's filesystem permissions and adds no OS sandbox. A
1751
+ multi-user host must supply its managed execution facility and appropriate
1752
+ isolation. User messages and model configuration cannot enable these tools.
1753
+
1754
+ Ambient hooks, MCP integrations and skills remain disabled. Native subagents and
1755
+ interactive permission questions are not enabled by this grant. Application
1756
+ `actions` continue through the authorized, durable tool executor above. The API
1757
+ driver does not provide native coding tools.
1758
+
1759
+ Hosts that require a session command wrapper can also supply its absolute
1760
+ executable path:
1761
+
1762
+ ```js
1763
+ host: {
1764
+ workdir: applicationWorkdir,
1765
+ nativeTools: true,
1766
+ commandWrapper: sessionCommandExecutable
1767
+ }
1768
+ ```
1769
+
1770
+ The executable receives the original shell command as one argument and runs under
1771
+ the configured host identity and environment. It owns the application's command
1772
+ policy and execution. JSKIT owns the Codex hook, Claude SDK callback and OpenCode
1773
+ tool interception that deliver the command. It quotes both the executable and
1774
+ command without evaluating the command first. A missing or invalid wrapper is
1775
+ an error; there is no unwrapped fallback.
1776
+
1777
+ Codex installs and trusts only this exact hook in its thread configuration; it
1778
+ does not write the shared CLI account configuration. Claude retains safe mode
1779
+ while registering the explicit SDK callback. Other ambient hooks stay disabled.
1780
+ The wrapper applies to shell commands, not native file-editing tools; the managed
1781
+ execution identity remains the filesystem boundary for those tools.
1782
+
1783
+ ### Supplied Claude integration
1784
+
1785
+ Install Claude Code and log in with `claude auth login` under the application's
1786
+ server identity. The ordinary single-user host uses that account and owned POSIX
1787
+ process groups. It requires no Vibe64, Genesis or application-specific startup
1788
+ callbacks:
1789
+
1790
+ ```js
1791
+ const runtime = createConversationRuntime({
1792
+ engine: "claude",
1793
+ host: { workdir: applicationWorkdir },
1794
+ storage: createFileConversationStorage({ directory: privateConversationDirectory }),
1795
+ authorize: ({ context, conversationId, operation }) => canAccessConversation(context, conversationId, operation)
1796
+ });
1797
+ const conversation = await runtime.open({
1798
+ id: applicationConversationId,
1799
+ context: authenticatedContext,
1800
+ configuration: { systemPrompt: "Answer concisely.", model: "haiku", effort: "low" }
1801
+ });
1802
+ await conversation.send({ messageId: applicationMessageId, text: "Hello" });
1803
+ await conversation.wait();
1804
+ await runtime.close();
1805
+ ```
1806
+
1807
+ The native identity, working directory and credential-home binding remain in
1808
+ server-side runtime metadata. Prompt changes replace the process while preserving
1809
+ native history; compatible model/effort changes use native controls. Account changes
1810
+ cannot silently continue the previous account's conversation. Application access
1811
+ is checked again after native preparation and before dispatch. Built-in tools are
1812
+ disabled unless the server grants `host.nativeTools`. Optional application `actions` use the shared
1813
+ executor and authorized attachments use the common resolver above. Steering uses
1814
+ the shared operation described above. Goals use the extracted native commands
1815
+ through the same admission and delivery path; see Native goals above.
1816
+ The application-tool transport was verified with Claude Code 2.1.287.
1817
+
1818
+ Native login checks use the extracted production status reader. Concurrent reads
1819
+ share a query; public account details may be reused for 30 seconds while native
1820
+ credential-file metadata is unchanged. File changes invalidate the result,
1821
+ failed reads are retryable, and signed-out JSON is distinct from a failed query.
1822
+ On macOS, only concurrent reads are shared because native credentials may live
1823
+ in the keychain. JSKIT does not parse credential contents for this cache.
1824
+ The extracted native account guard prevents a different signed-in Claude account
1825
+ from resuming owned history. Authorized external-provider connections can rotate
1826
+ their API keys while retaining the native conversation; application authorization
1827
+ still decides which connection the caller may use.
1828
+ Compatible connection/key changes use the production flag-then-model control
1829
+ sequence on the existing process. Selected connection credentials and model
1830
+ overrides are installed after initialization, rather than in process arguments
1831
+ or its startup environment. Returning to the native connection clears those
1832
+ overrides and restores any explicit standalone host routing. Changed system
1833
+ instructions still require a confirmed stop and resume of the same native history.
1834
+
1835
+ An authorized API connection can use the same `connections` resolver and
1836
+ `{ systemPrompt, integrationId, effort }` configuration as Codex. JSKIT configures
1837
+ the selected provider's endpoint, exact model, background models and compaction
1838
+ window. The native `[1m]` context flag is added where required; it does not rename
1839
+ the model. No native subscription login is required for this connection. Use
1840
+ `select()` with a complete configuration to change provider or return to the
1841
+ native account while retaining the Claude conversation. An explicit `model` must
1842
+ match the connection. The connection resolver authorizes the current key before
1843
+ dispatch; rotating an authorized external-provider key preserves the conversation.
1844
+
1845
+ Advanced hosts may supply `host.execution` with `start`/`stop`, `host.env`,
1846
+ `host.commands.claude` and `host.limits`. The common execution request carries the
1847
+ executable, arguments, directory, environment, stream requirement and resource
1848
+ limits. The supplied local facility rejects unenforceable resource limits. It
1849
+ does not recover ownership of an unknown process after a hard application crash;
1850
+ that requires operator cleanup or a host with durable execution ownership. Clean
1851
+ runtime shutdown drains its owned processes and preserves native history for resume.
1852
+
1853
+ Advanced execution hosts can use `createConversationProcessIdentity()` from
1854
+ `/server/conversation` for the extracted Linux process-group ownership checks.
1855
+ Supply the environment variable names carrying the runtime token and command
1856
+ hash. `capture()` records the leader's start time; `inspect()` checks the leader
1857
+ and marked descendant groups; `stop()` rechecks ownership before signalling and
1858
+ escalation. Ambiguous or unreadable ownership does not authorize signalling.
1859
+ The caller must validate its persisted metadata contract before requesting these
1860
+ operations and must retain managed-execution authority where one exists. This
1861
+ facility leaves native server sharing and conversation lifetime to their owners.
1862
+
1863
+ ### Supplied Codex integration
1864
+
1865
+ Use the same operations, storage and authorization with `engine: "codex"` after
1866
+ installing Codex and running `codex login` as the server identity. Configuration
1867
+ takes `systemPrompt` and optional `model` and `effort`; omit the latter two to use
1868
+ the CLI defaults. `host.commands.codex` can select the executable. The host must
1869
+ support POSIX process ownership and private Unix sockets. Codex must support
1870
+ paginated thread history; the shared-driver check used Codex 0.160.0.
1871
+
1872
+ The common driver uses the extracted `createCodexAppServerRuntime()` and
1873
+ `CodexAppServerAgentProvider`. Conversations own separate native threads and
1874
+ observer connections on a shared server per account/runtime scope. Stopping or
1875
+ closing one conversation leaves other consumers' work and shared server intact. The
1876
+ former process-per-conversation launcher has been removed.
1877
+
1878
+ The existing owner preserves per-directory startup serialization, waiter
1879
+ revalidation, compatibility and liveness checks, verified retirement, helper
1880
+ preparation and startup/publication cleanup. Execution and credential facilities
1881
+ supply host policy; native threads remain independent.
1882
+ The extracted `runCodexAppServerProcess()` leader already owns startup catalogue
1883
+ preparation and the combined Codex/history-adapter lifetime. Its caller supplies
1884
+ the command, runtime directory, runtime token and offered model definitions;
1885
+ the shared runtime coordinates reuse and recovery around this leader.
1886
+
1887
+ The existing native connection/thread owner has also moved into
1888
+ `CodexAppServerAgentProvider` under `/server/codex-provider`. It consumes that shared
1889
+ runtime with execution and credential-state facilities. Its native behavior includes
1890
+ helper token activation/refresh, bounded inventories, control recovery, goals,
1891
+ hook trust, exact command interruption and history export. The existing
1892
+ native consumer now supplies only its host policy and application callbacks. The common
1893
+ driver uses this backend. Ordinary applications use the common conversation API
1894
+ with its supplied execution and credential defaults.
1895
+
1896
+ Changed instructions use the existing thread-control restoration and verification
1897
+ before sending new work; they do not replace the shared process. Ordinary turns
1898
+ preserve the instruction binding. Native compaction remains the engine's job.
1899
+ JSKIT owns the local history adapter used for model-provider changes. It keeps
1900
+ native history intact and translates only outgoing requests. For qualified
1901
+ DeepSeek and GLM models, an OpenAI encrypted compaction is supplemented with the
1902
+ exact readable records from its saved native boundary. Recovery is bounded and
1903
+ fails explicitly for unsupported or ambiguous history, including native Undo or
1904
+ fork records. It never silently truncates, retries inference or rewrites a rollout.
1905
+
1906
+ For a supported external provider, supply the existing `connections` resolver and
1907
+ select `{ systemPrompt, integrationId, effort }`. For example, an integration
1908
+ configured as `deepseek/deepseek-flash` uses that exact model and its private
1909
+ credential. The driver supplies the native catalogue, Responses configuration and
1910
+ history route. No CLI OpenAI login is needed for that external connection. Use
1911
+ `select()` with a complete configuration when changing connection/provider; it
1912
+ retains the Codex thread. `configure()` patches the current configuration. An
1913
+ explicit `model` must agree with the authorized integration. Changing a provider's
1914
+ account requires restoring that account or opening a new conversation.
1915
+
1916
+ The supplied host uses a stable private account/runtime directory, independent
1917
+ of the conversation's workdir. `host.runtimeDirectory` can select an absolute
1918
+ application-owned base directory. Runtime metadata, locks, the history adapter
1919
+ and the Unix socket belong to this shared scope. Closing one conversation retains
1920
+ the service while another conversation uses it. Closing the last consumer stops
1921
+ the service and preserves native history. Runtime retirement requires the existing
1922
+ execution or persisted native process-identity proof.
1923
+
1924
+ The same owner coordinates provider acquisition, account invalidation and lost
1925
+ observation across consumers. Recovery first attempts to stop the affected native
1926
+ thread. If that cannot be confirmed, it records every affected consumer's admission
1927
+ barrier before stopping the shared service. A failed storage write does not leave
1928
+ native work running; failed stop or final persistence remains retryable. Recovery
1929
+ uses the recorded native owner after reopening and never replays the request.
1930
+ Applications using the common runtime do not supply Codex recovery callbacks.
1931
+
1932
+ A custom Codex execution facility supplies `run()` for capture/detached requests
1933
+ and `stop()` with confirmed scope cleanup. The supplied local facility supports
1934
+ both. An existing managed native integration may supply `host.codex({ providerId })`
1935
+ with its prepared runtime, parameters, credential-state and execution facilities,
1936
+ configuration and native options. This is a host-policy seam for existing managed
1937
+ integrations; it supplies no alternative native process or protocol algorithm.
1938
+ The public editor uses it to retain its account/login owner, resource profiles,
1939
+ runtime packs and managed stop authority while its Main conversation uses the
1940
+ same common runtime and original native owner.
1941
+
1942
+ Codex's native `turn/start` response is the admission receipt. A lost response
1943
+ keeps the request uncertain; `inspectDelivery({ messageId })` searches the latest
1944
+ 20 native turns for its exact client message ID. If it cannot find proof, delivery
1945
+ remains uncertain and the runtime does not resend. The basic driver disables
1946
+ native tools unless the server grants `host.nativeTools`; hooks and ambient MCP integrations stay disabled through the shared policy
1947
+ used by isolated assistants. Application tools use the shared executor described
1948
+ above, as do authorized attachments, steering and the native goal operations
1949
+ described above.
1950
+
1951
+ ### Supplied OpenCode integration
1952
+
1953
+ Install OpenCode 1.18.31 and use `engine: "opencode"` with the same `connections`
1954
+ resolver and `{ systemPrompt, integrationId }` configuration as the API driver.
1955
+ The selected integration supplies the authorized provider, model, context/output
1956
+ limits and API key. The native configuration includes that exact model definition,
1957
+ so OpenCode's own catalogue need not contain the same entry;
1958
+ there is no second account configuration. `host.commands.opencode` can select
1959
+ the installed executable. The supplied host requires POSIX process ownership.
1960
+
1961
+ The host starts a shared authenticated loopback server on first use. Conversations
1962
+ on the same account and runtime directory acquire that owner and retain separate
1963
+ native session IDs. Its private
1964
+ credential home contains only the selected account; ordinary project plugins,
1965
+ configuration and external skills are disabled. Native history is kept under
1966
+ `host.stateDirectory`, defaulting to `.assistant/native/opencode` beneath the
1967
+ working directory, in the shared account directory's `opencode.db`. Keep this directory alongside application conversation storage
1968
+ across restarts. The private process directory is removed only after confirmed
1969
+ cleanup. The local host has the hard-crash ownership limitation described above.
1970
+
1971
+ Instructions are installed through the native system transformation hook before
1972
+ each inference, including after automatic compaction. Updating instructions keeps
1973
+ the same process and native conversation without appending a user message. Each
1974
+ turn selects its configured native model and tool permissions independently of
1975
+ other conversations. A selected model must be available in that server's native
1976
+ catalogue; unavailable definitions fail before dispatch.
1977
+ A different account requires a new conversation or restoration of the original
1978
+ connection. Built-in tools require the server's `host.nativeTools` grant; optional
1979
+ application `actions` use the shared executor. Attachments and steering use the
1980
+ shared operations above. Native goals are not supported by this driver.
1981
+
1982
+ With the registered execution guard, tool-free agents retain native tool definitions
1983
+ behind approval so included providers can accept the request. This does not grant
1984
+ file or command access: the plugin rejects every ungranted native call before
1985
+ execution, including child sessions, and fails closed if its binding is missing
1986
+ or unreadable. Without that registry, native tools remain denied. Application
1987
+ tools still require the current authorized catalog and shared executor.
1988
+
1989
+ The native prompt response acknowledges admission. Lost acknowledgement remains
1990
+ uncertain; inspection searches the latest 100 native messages for the exact
1991
+ submitted ID and never resends. Event observation is established before dispatch;
1992
+ losing it uses the original native interrupt/idle check for that exact session.
1993
+ If session stopping cannot be confirmed, the shared owner stops that exact server
1994
+ and fails its affected peers. Closing an ordinary conversation releases its own
1995
+ binding; the final user releases the server. A saved process reference alone does
1996
+ not authorize an unopened conversation to stop the shared service.
1997
+ The shared native turn observer follows automatic compaction continuations and
1998
+ waits for a stable completed answer and native idle state, including after tool
1999
+ rounds. If a successful response contains only reasoning, it requests the missing
2000
+ final answer once within the same admitted application turn. A second empty
2001
+ response fails explicitly; the original request is not submitted again.
2002
+ Native event filtering excludes errors from older turns or another conversation.
2003
+
2004
+ Managed applications that already own an OpenCode service can supply
2005
+ `host.opencode({ connection, context })`. It returns the existing shared `runtime`,
2006
+ absolute `runtimeDirectory`, `registryPath` and `databasePath`, the authorized
2007
+ `selected` connection identity, and lazy `prepareServer()` application preparation.
2008
+ The common driver passes that preparation to the same runtime's
2009
+ `startPreparedProcess`; native acquisition and startup stay inside the shared
2010
+ owner. The host retains execution policy, credential resolution and durable
2011
+ restart/stop proof. Ordinary apps use the supplied facility without writing one.
2012
+ Existing development bindings from the earlier private `history.db` implementation
2013
+ are rejected before inference; changing the stored path does not migrate native
2014
+ history. Their offline conversion remains unfinished.
2015
+ Managed consumers retain their application-specific error presentation.
2016
+
2017
+ ## Advanced host facilities
2018
+
2019
+ Ordinary applications use the [five entrypoints](./assistant.md#the-ordinary-api)
2020
+ and the supplied runtime's engine defaults. The following modules support hosts
2021
+ that already own one of the facilities in this table. They share the runtime's
2022
+ native implementations; they are not separate conversation integration recipes.
2023
+ Keep authorization, credentials, storage and verified cleanup with their named
2024
+ owners. A custom host does not create another sender, monitor or receipt journal.
2025
+
2026
+ | Core subpath | Existing host responsibility |
2027
+ | --- | --- |
2028
+ | `/server/codex-configuration` | Account configuration, offered-model policy and authorized Helper isolation. |
2029
+ | `/server/codex-process` | Managed execution, credential-home/runtime scope and native process-leader launch. |
2030
+ | `/server/codex-provider` | Compose the original provider owner with host account, execution and history-preservation facilities. |
2031
+ | `/server/codex-turn` | Compose the original run owner with the application's admitted transcript, checkpoint and publication effects. |
2032
+ | `/server/codex-events` | Project authorized native event identities and history facts into product status. |
2033
+ | `/server/claude-process` | Managed process launch and authorized account/model/usage inspection. |
2034
+ | `/server/claude-turn` | Compose the original retained conversation owner with application admission, persistence and result effects. |
2035
+ | `/server/claude-history` | Inventory and preserve authorized native history for explicit retirement. |
2036
+ | `/server/opencode-process` | Managed shared-service execution, account setup and native instruction/environment plugin configuration. |
2037
+ | `/server/opencode-turn` | Bounded renewal and authorized prompt/result projection for an existing native host. |
2038
+ | `/server/opencode-client` | Preserve and retire authorized native history through the existing shared client. |
2039
+ | `/shared/native-providers` | Native provider/model facts; the application still chooses its offerings and permissions. |
2040
+
2041
+ The advanced storage, transcript, hook and process-identity helpers already
2042
+ exported by `/server/conversation` support an application's existing storage or
2043
+ execution boundary. They are not required beside `createConversationRuntime`
2044
+ and a supplied storage adapter. The sections below explain their contracts.
2045
+ No additional host aggregator or engine-specific entrypoint is needed for normal
2046
+ API, Codex, Claude or OpenCode conversations.
2047
+
2048
+ ### Testing entrypoints
2049
+
2050
+ These entries require disposable fixtures and explicit cleanup. They never grant
2051
+ account access and are not an application startup path.
2052
+
2053
+ | Core subpath | Fixture purpose |
2054
+ | --- | --- |
2055
+ | `/testing/conversation-storage` | Exercise a custom adapter's transcript, scope, atomicity and rollback contract. |
2056
+ | `/testing/native-codex` | Drive the original native protocol/history/catalogue probes against an isolated executable or endpoint. |
2057
+ | `/testing/native-opencode` | Drive the original loopback client against an isolated host fixture. |
2058
+ | `/server/native-history` | Retain the shared retirement-contract entry used by existing archive/deletion fixtures. Engine integrations use their engine-specific retirement operations. |
2059
+
2060
+ ### Existing scoped native hosts
2061
+
2062
+ An application with an existing admitted scoped-operation owner can use the same
2063
+ created runtime's `runScopedTurn({ operations, scope, input, context,
2064
+ executionProfile })`. Supply that owner, whose existing methods are
2065
+ `createEphemeralConversation`, `startEphemeralConversationTurn`,
2066
+ `waitForEphemeralConversationTurn` and `stopEphemeralConversation`; each receives
2067
+ `(scope, input, context)`. The operations retain their current account, profile
2068
+ and access checks. Their native execution uses the same runtime instance. This
2069
+ is a server-only host facility with no HTTP or socket command.
2070
+
2071
+ When an existing host must create a native identity before opening a retained
2072
+ handle, its admitted server operation can call
2073
+ `runtime.createNativeConversation({ id, context, input })`. Here `id` is the
2074
+ existing parent session or Helper scope, not a placeholder native identity. The
2075
+ runtime checks access with operation `create`, then calls only its configured
2076
+ `host.conversation({ id, context, input, operation: "create" })`. This host branch
2077
+ must return its prepared `{ sessionId: id, engine, native, input, context }` using
2078
+ the same native owner and authorized application configuration. It must not build
2079
+ a Main binding or open another conversation. The existing driver performs Create;
2080
+ the original native owner allocates and records the real identity. The application
2081
+ still owns profile validation, parent receipts and failed-creation cleanup.
2082
+ This advanced host operation has no HTTP, socket or client counterpart and accepts
2083
+ no caller-supplied engine, native descriptor or host override. Ordinary standalone
2084
+ applications keep using `open()` and their existing storage contract.
2085
+
2086
+ The same advanced host can prepare native readiness before retaining a handle
2087
+ with `runtime.ensureNativeConversation({ id, context })`. Authorization uses
2088
+ operation `ensure`; only the configured host supplies the prepared
2089
+ `{ sessionId: id, engine, native, context }`. The existing driver and native owner
2090
+ perform readiness. This operation creates no placeholder identity or additional
2091
+ retained runtime handle. Closure during host preparation prevents acquisition.
2092
+
2093
+ `runtime.inspectNativeTemporaryActivity({ id, context })` authorizes
2094
+ `inspectTemporaryActivity` and reads the selected owner's existing temporary-work
2095
+ inventory. Its configured host supplies `{ sessionId: id, engine, native, context }`.
2096
+ It opens no Main conversation and remains available during cleanup after runtime
2097
+ closure. Native restoration and activity exclusions are those of the same owner;
2098
+ there is no additional cache. This advanced server operation has no browser route
2099
+ or caller-supplied owner/engine override.
2100
+
2101
+ An existing application's renewal transaction can invoke
2102
+ `runtime.generateNativeRenewalHandover({ id, context, input })` and
2103
+ `runtime.seedNativeRenewalHandover({ id, context, input })`. The runtime authorizes
2104
+ `generateRenewalHandover` or `seedRenewalHandover` and obtains
2105
+ `{ sessionId: id, engine, native, context, input }` only from its configured host.
2106
+ The driver uses the same native owner and application configuration/storage
2107
+ effects. Neither operation opens a retained handle or replaces the application's
2108
+ hidden-successor transaction. These advanced server operations have no browser
2109
+ transport and accept no caller-selected engine, native owner or host. Applications
2110
+ retain approved handover, private access, leases and ACK/commit policy.
2111
+
2112
+ An application's already-authorized renewal cleanup can release its predecessor
2113
+ or successor evidence with
2114
+ `runtime.releaseNativeRenewalPredecessorProcessExitProof({ id, context, input })`
2115
+ and `runtime.releaseNativeRenewalSuccessorProcessExitProof({ id, context, input })`.
2116
+ These trusted server operations obtain matching `{ sessionId: id, engine, native,
2117
+ context, input }` synchronously from the configured host. They open no conversation,
2118
+ admit no inference and remain retryable after runtime closure. They have no browser
2119
+ endpoint and accept no caller-selected owner or engine. The host must preserve
2120
+ its existing renewal authorization before native cleanup.
2121
+
2122
+ The same native owners preserve their original contracts: Codex validates
2123
+ application renewal authority before its lifecycle queue, then reads saved runtime
2124
+ facts inside that queue and releases persisted proof only after shared-peer checks;
2125
+ Claude closes through its existing entry/terminal cleanup; OpenCode predecessor
2126
+ cleanup awaits close before consuming proof, while successor cleanup only consumes
2127
+ proof. OpenCode retains its original distinction between a resolved failed close
2128
+ (which still consumes proof) and rejected close (which leaves proof retryable).
2129
+ Applications retain result projection and successful binding retirement.
2130
+
2131
+ `runtime.disposeNative({ namespace, sessionId, context, options })` also supports
2132
+ cleanup when no ready retained handle exists. It authorizes operation `dispose`
2133
+ and requests `{ id: sessionId, context, input: options, operation: "dispose" }`
2134
+ from its configured host. The response must carry the matching `sessionId` and
2135
+ `namespace`, plus `engine`, `native`, `context` and `options`. The same driver
2136
+ performs native cleanup through the existing owner. A failed cleanup remains
2137
+ retryable, including after runtime closure; no Main record is opened. Ready
2138
+ retained handles keep their existing queue and retire only after native and
2139
+ awaited application completion succeed. An unready local handle retains its
2140
+ existing early-retirement fence before falling back to the configured owner.
2141
+ These server-only operations accept no caller-selected owner or engine and add
2142
+ no browser endpoints. Ordinary applications still use their conversation handle.
2143
+
2144
+ Historical native records can predate an application's retained scope identity.
2145
+ An advanced host can clean up those exact records through
2146
+ `runtime.interruptNativeDetachedConversation({ id, context, input })` or
2147
+ `runtime.deleteNativeDetachedConversation({ id, context, input })`. Authorization
2148
+ uses `interruptDetachedConversation` or `deleteDetachedConversation`; the configured
2149
+ host returns `{ sessionId: id, engine, native, context, input }`. The same drivers
2150
+ invoke the original native control owners without constructing a scope, opening
2151
+ Main or acquiring a retained queue. Native identity, profile and shutdown checks
2152
+ remain with those owners. Cleanup still checks caller authority after runtime
2153
+ closure; the runtime does not add a new closed-state veto to an existing retry.
2154
+
2155
+ Saved-history maintenance uses
2156
+ `runtime.listNativeConversationStorage({ id, context, binding })` and
2157
+ `runtime.retireNativeConversationHistory({ id, context, binding })`. These are
2158
+ trusted server storage operations: the host must already authorize the inventory
2159
+ or archive transaction, preserve its idle/writer exclusion rules and provide the
2160
+ retirement preservation effect. The configured host receives operations
2161
+ `listNativeConversationStorage` or `retireConversationHistory` and returns
2162
+ `{ sessionId: id, engine, native, context, binding }`. `engine` must match the
2163
+ same saved `binding.engineId`; current conversation selection is not consulted.
2164
+ The existing native owners enumerate, export and delete history. These calls add
2165
+ no inference authorization or runtime-closure rule to the original storage policy.
2166
+ All four operations are server-only, accept no caller-supplied native owner or
2167
+ host, and have no HTTP, socket or client route. Ordinary applications continue
2168
+ using their conversation handles.
2169
+
2170
+ `runtime.closeNativeProject({ context, input })` is trusted server lifecycle
2171
+ cleanup, like `runtime.close()`. The application must already authorize the
2172
+ project operation; it does not use conversation authorization or expose a browser
2173
+ operation. The configured host receives `{ context, input, operation: "closeProject" }`
2174
+ and synchronously returns `{ engine, native, context, input }` with its existing
2175
+ native owner and application preparation. Codex, Claude and OpenCode support this
2176
+ advanced operation. It opens no conversation or retained handle and remains
2177
+ retryable after runtime closure, including when credentials have been revoked.
2178
+
2179
+ `runtime.invalidateNativeRuntimes({ context, input })` uses the same trusted
2180
+ server boundary for account changes and shutdown. Its configured host receives
2181
+ operation `"invalidateRuntimes"` and supplies the existing native owner
2182
+ synchronously. The runtime returns the native operation directly: shutdown
2183
+ fences and synchronous errors are not deferred behind another await. The host
2184
+ retains account policy and result presentation; the native owner retains its
2185
+ original cleanup sequence and retryable failures. No conversation is opened,
2186
+ and failed cleanup can be retried after runtime closure. This is not a browser
2187
+ operation or a way to submit a caller-selected native owner.
2188
+
2189
+ `runtime.reconcileNativeSessions({ context, sessions, options })` restores an
2190
+ application's saved session collection through those same configured native
2191
+ owners. The host synchronously supplies `{ engine, native, context, sessions,
2192
+ options }` for operation `"reconcileSessions"`. The application retains its
2193
+ session inventory, authorization and storage codecs. Codex retains its original
2194
+ concurrent recovery and prune coordination; OpenCode retains per-session failure
2195
+ results and continues; Claude retains sequential, fail-fast restoration.
2196
+
2197
+ `runtime.unsubscribeNativeSessions({ context, sessions })` supplies startup
2198
+ subscription reset for Codex through operation `"unsubscribeSessions"` and a
2199
+ synchronous `{ engine, native, context, sessions }` descriptor. It does not select
2200
+ or stop every engine. These trusted server lifecycle calls open no conversation,
2201
+ create no admission receipt and replay no submission. They return the original
2202
+ owner operation directly, preserving its error and retry behavior. They are not
2203
+ browser operations; ordinary applications continue using conversation handles.
2204
+
2205
+ The original coordinator creates a native conversation only when `input` has no
2206
+ `conversationId` or `threadId`. It awaits `context.onEvent` for the native thread
2207
+ before Start and for the native turn before waiting. Keep durable task ownership
2208
+ in those awaited host writes, rather than a presentation subscription. Abort
2209
+ stops the exact native turn and waits for the existing Stop proof; a Stop failure
2210
+ retains precedence over the earlier wait or abort error. Parent-write failures
2211
+ propagate to the application's existing cleanup owner.
2212
+
2213
+ Repeated calls retain the supplied conversation ID and return the current native
2214
+ `threadId` and `turnId`. The coordinator creates no transcript, placeholder ID or
2215
+ receipt registry, and does not delete the conversation between responses. The
2216
+ optional `executionProfile` argument is an already-validated audit snapshot copied
2217
+ only into the final result; it grants no authority and does not alter early
2218
+ failure envelopes. The application retains native deletion, durable cleanup
2219
+ receipts and retry policy. Ordinary standalone conversations continue using
2220
+ `open`, `send`, `wait` and `cancel`.
2221
+
2222
+ An existing bounded structured-response workflow can compose those same native
2223
+ turns with `runBoundedAssistantToolLoop` from `@jskit-ai/assistant-core/server`.
2224
+ Supply the application instructions as `prompt`, a final-only `outputSchema`,
2225
+ the existing `toolCatalog` and trusted `toolContext`, `signal`, and the response
2226
+ policy. The shared owner derives a closed final-or-tool schema from the catalogue,
2227
+ parses the reply, executes one selected action through `createConversationTools`,
2228
+ and constructs the next untrusted-result prompt. The application supplies no
2229
+ reply parser, action selector or next-prompt callback.
2230
+
2231
+ `policy.maximumResponses` counts completed responses. The existing
2232
+ `complete(prompt, { timeoutMs, outputSchema })` facility runs one admitted native
2233
+ turn and returns its `{ ok, text }` result; the loop returns the parsed final value.
2234
+ `policy.timeoutMs` applies to each response, not the intervening application
2235
+ operation. An operation requested by the last allowed response still completes
2236
+ before `limitError` is thrown. There is no extra inference or recovery pass.
2237
+ `invalidResponseError` and `failureError` may supply application error defaults;
2238
+ provider failure messages/codes and thrown host errors retain their existing path.
2239
+
2240
+ Catalogue descriptors are resolved before each response to construct its bounded
2241
+ schema, including a final response; no action executes for a final result. The
2242
+ shared owner creates call IDs and permits one application operation per response.
2243
+ This structured transport does not register native tools or grant new native
2244
+ permissions. Keep application access checks, schema validation and result bounds
2245
+ in the action definitions, and retain the existing scoped turn's native isolation.
2246
+
2247
+ This bounded path explicitly uses transient call ownership: it serializes the
2248
+ operation and retains its result in memory for that response, without claiming
2249
+ durable replay or creating an authored turn. A host exception is rethrown as the
2250
+ same exception after safe result classification, ending the loop before another
2251
+ native response; it is never put in a tool result or context. Applications retain
2252
+ their existing parent cleanup receipt and restart policy. Canonical conversation
2253
+ tool execution still requires its authorization, durable reservation/result
2254
+ writer and event facilities.
2255
+ Use this bounded operation for a task with an existing completed-response budget,
2256
+ transient results and immediate failure policy. Ordinary conversations use the
2257
+ runtime's durable tool calls. Disabling built-in native tools alone is not a reason
2258
+ to select the bounded protocol: both paths can execute supplied application actions
2259
+ through the same catalogue and execution owner.
2260
+
2261
+ ### Native instruction lifecycle
2262
+
2263
+ The created conversation runtime owns instruction installation and refresh.
2264
+ Ordinary applications supply their current system prompt and authorized host
2265
+ facilities through that runtime; they do not construct separate instruction
2266
+ adapters. Native history remains intact, and instructions do not become authored
2267
+ user messages. None of the native owners imports an application or a
2268
+ project-guidance compiler.
2269
+
2270
+ | Native owner | Host supplies | Owner retains |
2271
+ | --- | --- | --- |
2272
+ | Claude turn | Current prompt/configuration and the existing process facility | Serialized preparation, compatible native settings updates, replacement against the same history and installation invalidation |
2273
+ | Codex provider | Current instructions and its existing execution/environment/history facilities | Developer instruction installation, per-thread control serialization and verified recovery |
2274
+ | OpenCode plugin | Registered conversation bindings and optional host prompt composition | Native/host transformer selection, instruction contributions and compaction/deletion invalidation |
2275
+
2276
+ Claude's retained conversation owner owns instruction preparation and the
2277
+ ordered turn receiver. Managed hosts use `createClaudeConversationOwner` from
2278
+ `/server/claude-turn`; ordinary applications use the common runtime. Neither
2279
+ assembles a second turn receiver or supplies a parallel stop implementation.
2280
+ Its activity query restores saved entries through the existing storage/acquisition
2281
+ facilities before inspecting its own native state. Its history-idle query reads
2282
+ that same state; the host separately checks durable process receipts and actual
2283
+ terminal activity. No second activity cache or restoration loop is required.
2284
+
2285
+ The owner serializes preparation with native controls and preserves the original
2286
+ control-interrupt followed by mandatory process drain. A failed drain stays an
2287
+ error. The host retains execution policy, durable cleanup and publication at
2288
+ their existing boundaries.
2289
+
2290
+ Preparation uses the current system prompt, context identity, instruction mode,
2291
+ settings and model. Complete dedicated contexts replace native instructions;
2292
+ append mode retains native coding defaults. Compatible live updates install
2293
+ native flag settings before selecting a model. Failed controls retire the
2294
+ process; active incompatible updates are rejected without invalidating ordinary
2295
+ steering. Lost instruction installations are invalidated before new work.
2296
+
2297
+ The same owner prepares instruction flags for an explicitly opened native
2298
+ terminal. The flags disable Claude's native system-prompt snapshot so resumed
2299
+ history receives the current prompt before compaction. A host launches the
2300
+ prepared terminal arguments unchanged and proves cleanup before replacement.
2301
+
2302
+ Codex's existing provider distinguishes a socket reconnect, native process loss
2303
+ and a different native conversation. It serializes controls per thread,
2304
+ pauses/resumes an active goal only when still authorized, verifies the owned
2305
+ shell environment after reload and never retries the caller's operation.
2306
+ Changed instructions or an unknown installation update native configuration and
2307
+ add one explicit developer-context revision before new work. Acknowledged ordinary
2308
+ turns and socket reconnects do not repeat it. Compaction rebuilds context with the
2309
+ current configuration; restricted consumers retain their own execution environment.
2310
+ The same provider owner selects and replaces cached providers, constructs native
2311
+ connections, acquires the shared runtime and retries an unconfirmed observation
2312
+ stop before reconnecting. It retains the original command-control subscription and
2313
+ native project-hook trust refresh before resume. A host supplies authorized account,
2314
+ context, settings and managed execution preparation at their existing read points.
2315
+ The existing run owner coordinates native readiness and Send preparation as well.
2316
+ Managed readiness acquires its provider before entering the supplied startup gate;
2317
+ Send acquisition stays inside that gate. Hosts supply authorized configuration,
2318
+ identity storage and application health effects. Native preparation, observation,
2319
+ managed registration and abandoned-start recovery retain their original order.
2320
+ The same owner selects the first available managed provider matching the existing
2321
+ session, workdir and thread. It reads its existing shared provider maps; a host
2322
+ does not scan or duplicate that native inventory.
2323
+ Ordinary Send uses the same preparation with its tool settings and durable binding.
2324
+ The same native provider owner coordinates retained-thread release, drains its
2325
+ existing notification queue and selects cached, shared or persisted-runtime
2326
+ cleanup before classifying exit proof. The host supplies current identity values
2327
+ and configured managed execution; application admission, renewal policy and
2328
+ terminal/attachment cleanup remain outside that native release sequence.
2329
+ The same provider owner releases preserved process-exit proof only after its
2330
+ original shared-peer and persisted-runtime checks. It reuses the existing
2331
+ disk-locked stop and post-stop existence evidence; authorized lifecycle policy,
2332
+ metadata-to-host preparation and application result/error projection stay with
2333
+ the host.
2334
+ For supplied provider sessions, output preparation returns identity and lazy runtime
2335
+ options; the run owner performs acquisition through its existing provider selector.
2336
+ The provider owner also performs managed-thread registration, recovery scheduling
2337
+ and the before-resume observation guard using that same run owner. The host supplies
2338
+ the authorized current run record and its original execution context.
2339
+ For an advanced interactive terminal, the same run owner exposes full thread
2340
+ readiness and visible-terminal runtime attachment as separate native operations.
2341
+ The host keeps application admission and authorized PTY preparation between them:
2342
+ readiness precedes source/account checks, and runtime attachment follows the
2343
+ startup gate's current session/environment read. Both operations use the existing
2344
+ shared provider inventory; they do not create a second terminal-native owner.
2345
+ Interactive-terminal attachment grants also use this owner
2346
+ (`allowTerminalAttachments`): it selects the existing retained native session
2347
+ and issues the original grant before the host writes to the PTY. The host keeps
2348
+ attachment admission, Git policy and the PTY namespace.
2349
+ The same owner also retains native-target terminal attachment bookkeeping. It
2350
+ reuses the host's existing terminal snapshot before attaching, records the
2351
+ returned terminal ID on that target, and clears matching IDs after the host's
2352
+ existing PTY close resolves. The host supplies only its original read, close
2353
+ and namespace functions; a thrown close keeps the native attachment retryable.
2354
+ Project cleanup restores the same Helper records, closes the scoped native
2355
+ conversations and selects their providers through these existing owners.
2356
+ Conversation preparation also applies Helper restoration or ordinary-thread
2357
+ ledger exclusion before admission. Native activity and plan-usage queries read
2358
+ the same owned maps; hosts retain authorization and result presentation.
2359
+
2360
+ `createOpenCodeConversationPlugin` from `/server/opencode-process` owns the native
2361
+ system hook and event invalidation. An existing host that composes its own
2362
+ instructions may supply `resolveHostInstructions(id)`, returning
2363
+ `{ conversation, instructions }`. `conversation` is the selected registry row's
2364
+ native conversation binding; `instructions` is the original host descriptor
2365
+ `{ identity, read, placement }` or `null` for an unowned prompt. Prompt composition
2366
+ and its errors remain host-owned.
2367
+
2368
+ The plugin resolves the host selection first, then lets the selected native or
2369
+ host instruction owner resolve its current input again. Its native registry path
2370
+ remains the path captured at plugin creation; the host resolver retains its own
2371
+ lookup policy. The two existing caches stay separate, and events invalidate host
2372
+ then native state. Do not add application event/system-hook fanout around this
2373
+ plugin.
2374
+
2375
+ Omit a host descriptor's `identity` to read current text before every inference.
2376
+ Supply one only when it covers every source of prompt content; unchanged identities
2377
+ share a pending or completed read. Changed identity, compaction or deletion forces
2378
+ a fresh read. Failed reads remain retryable, and an invalidated pending read cannot
2379
+ install stale text. `placement: "append"` preserves native defaults;
2380
+ `"replace"` supplies the complete system context. A conversation ID or project
2381
+ path alone is not a content revision.
2382
+
2383
+ `createConversationHookBridge({ resolveConversation, readInstructions })` connects
2384
+ an existing native hook to the same ownership boundary. An absent binding returns
2385
+ `{ kind: "unclaimed" }`; `{ delivery: "native" }` returns
2386
+ `{ kind: "delivery", text: "" }`, yielding to the engine's native system field.
2387
+ `{ delivery: "hook" }` reads the complete current instructions on every invocation
2388
+ and returns `{ kind: "delivery", text }`. The host must propagate lookup and
2389
+ instruction failures. `readInstructions` is required only for hook delivery.
2390
+ Independent native IDs sharing a directory do not share
2391
+ ownership. Keep bindings through browser closure and resolve native ancestry
2392
+ through a trusted engine API before inheriting a parent's binding. The bridge
2393
+ owns no durable registry and never imports a project compiler.
2394
+
2395
+ `createConversationSystemPrompt()` remains the low-level installation primitive
2396
+ for adapters and custom transports. `ensure({ systemPrompt, contextIdentity,
2397
+ install, retained })` acknowledges only successful installation, serializes updates
2398
+ and rejects invalidation during installation. `isCurrent()` checks the acknowledged
2399
+ binding; it is not a provider health check. `retained: false` supports protocols
2400
+ requiring a system field on every request without adding history entries.
2401
+
2402
+ Instruction updates grant no permissions. Keep discovery bounded, authorize every
2403
+ tool execution against current application state, and preserve ordinary message
2404
+ text. These adapters do not own credentials, application tools, durable storage,
2405
+ process admission or UI. Native integration changes belong here; applications
2406
+ continue to own their content and resource facilities. Model caching, token
2407
+ accounting and the engine's own compaction remain native behavior.
2408
+
2409
+ API-model apps can use `createAiConnectionClient` with an authorized AI integration resolver (see [Assistant](./assistant.md)), or `createAiClient` for existing environment-based configurations, and the tool-catalog helpers
2410
+ from `@jskit-ai/assistant-core/server`, or the complete assistant runtime.
2411
+ Ordinary native conversations use `createConversationRuntime` and the supplied
2412
+ server/client integration described above. The native facilities below support
2413
+ hosts that already own managed execution, credential homes, authorized native
2414
+ history or archive preservation. Each host supplies those existing facilities
2415
+ while JSKIT retains the native protocol and lifecycle implementation. The common
2416
+ runtime supplies its own defaults, so ordinary applications need no engine-specific
2417
+ assembly. Native server entry points expose only the operations required by
2418
+ these hosts; implementation helpers remain private. Isolated host tests obtain
2419
+ the original Codex client, history-adapter, event and catalogue probes from
2420
+ `/testing/native-codex`, or the original OpenCode client from
2421
+ `/testing/native-opencode`. Tests own their executable or endpoint,
2422
+ authorization and cleanup; these entries do not supply another conversation
2423
+ runtime.
2424
+
2425
+ Claude's JSON control client is internal to the shared native implementation.
2426
+ Use the common runtime for conversations and the documented process host facility
2427
+ when supplying execution. Native admission, ordered events, bounded frames and
2428
+ control deadlines remain owned by JSKIT; applications do not assemble that
2429
+ protocol themselves.
2430
+ The ordinary Claude driver uses the same retained-entry owner for process launch,
2431
+ Send, steering, completion and verified Stop. Its binding storage and application
2432
+ tools retain their admission and cleanup order. Confirmed disposal retires only
2433
+ that entry; unconfirmed cleanup remains available for retry before reopening.
2434
+ The retained owner also handles process readiness, scoped Start, saved-execution
2435
+ recovery and verified native-history deletion. A managed host retains account
2436
+ and profile authorization and removes its saved receipt at the original point
2437
+ after native deletion succeeds.
2438
+ The owner classifies native goal and usage updates after raw observer forwarding,
2439
+ prepares native history/resume and terminal arguments, and stops selected entries
2440
+ from its existing live inventory. A managed host supplies authorized terminal
2441
+ configuration and performs its own PTY launch; account, storage and publication
2442
+ effects retain their original awaited positions.
2443
+ Managed Main and scoped conversations acquire this same owner through the common
2444
+ Claude driver; they do not supply native Send/read/wait/Stop operation dictionaries.
2445
+ The existing authorized acquisition and broad application cleanup facilities
2446
+ remain explicit. One shared command lifetime follows the actual admitted
2447
+ completion, preserving duplicate delivery, steering, cleanup retry and tool drains.
2448
+
2449
+ `/server/claude-turn` supplies the retained owner used by the common Claude
2450
+ driver and managed native bindings. Its internal turn receiver owns native
2451
+ acknowledgement, stream blocks, nested-event exclusion, background-task
2452
+ completion and the interruption barrier for steering. Normalized owner events
2453
+ let the host persist admissions and publish application state; receiver
2454
+ completion alone does not prove process exit. `claudeNativeMessageId()`
2455
+ retains the same stable native message IDs used by the Vibe64 adapter.
2456
+ The turn owner preserves the production receiver's block lifecycle: deltas are
2457
+ temporary, block-stop removes them, and completed snapshots use their native
2458
+ UUIDs. It does not group distinct history snapshots into a synthetic reply.
2459
+ The retained owner's `createAccountQueries({ accountIdentity, createProcess })`
2460
+ composes the existing native model/usage query owner with its own process inventory
2461
+ and closing state. A managed host supplies its authorized identity reader and
2462
+ account-process factory. The original query caches, pending-query sharing and
2463
+ retryable cleanup remain unchanged; account queries share already-owned processes
2464
+ and the same conversation shutdown boundary. Matching owned processes
2465
+ are reused; temporary query processes must prove cleanup. Failed stops remain
2466
+ owned and are retried before another native query. `invalidate()` drains pending
2467
+ queries, clears both caches and retries retained cleanup while new work is
2468
+ blocked. For managed bindings, the existing retained conversation owner also
2469
+ owns the original project and account cleanup sequence: it groups retained
2470
+ contexts, drains queries before full account cleanup and retains the synchronous
2471
+ closing fence. The host supplies current storage facts, authorized context,
2472
+ terminal cleanup and product error conversion. These trusted server lifecycle
2473
+ operations use the configured common runtime and are not browser actions.
2474
+ The account owner's usage normalization retains only native windows whose values and reset times
2475
+ are valid; it does not invent refreshed allowance. Product model offerings and
2476
+ account visibility remain application configuration. The same subpath supplies
2477
+ `claudeCatalogueModels(initialization)` for the original native model and effort
2478
+ projection, retaining native IDs, display-name fallbacks and effort labels.
2479
+ It performs no query and does not change the account owner's cached result;
2480
+ hosts still choose their offerings, defaults and visibility.
2481
+
2482
+ The configured Claude owner also acquires retained entries itself. Application
2483
+ preparation supplies authorized context, original open options and fixed product
2484
+ postconditions; native open and saved-entry restoration stay in the same owner.
2485
+ Account binding, verified Stop and identity persistence run between the original
2486
+ application configuration guards and settings preparation. The owner applies its
2487
+ native closing checks after the application's replacement and PTY guards; an
2488
+ optional application session-closing fact remains lazy. Passive retained-turn
2489
+ projection reads the same entry and saved receipt without opening a native session.
2490
+ Credential selection, storage and actual terminal access remain host facilities.
2491
+ The same owner reads its closing state and retained turn snapshot; application
2492
+ preparation supplies the current authorized session-closing fact, saved receipt
2493
+ and PTY state at the original read points. Configured history retirement applies
2494
+ its native busy check before the existing history-preservation operation, while
2495
+ the application retains saved execution and terminal writer facts.
2496
+
2497
+ The retained owner handles the extracted native goal commands and status
2498
+ interpretation using its history, activity, interrupt and ordinary Send operations. Pause interrupts the
2499
+ work and retains the goal; Cancel interrupts and sends `/goal clear`. Revision
2500
+ checks use the native conversation, creation time and objective. This native
2501
+ facility is shared by managed native bindings and the common runtime. The runtime
2502
+ supplies its existing serialized stop and Send operations, so clearing a goal
2503
+ cannot bypass admission, authorization, delivery inspection or cleanup proof.
2504
+
2505
+ `/server/claude-history` supplies `readClaudeHistory`; native identity validation
2506
+ and message-block mapping stay inside the owner. Pass an authorized
2507
+ `configRoot`, `workdir` and native `conversationId` to the reader; these
2508
+ operations do not authorize an account or conversation.
2509
+ Reads preserve the selected rewind branch and tolerate only an unfinished final
2510
+ line in a growing transcript. `listClaudeConversationStorage` enumerates the
2511
+ specified binding's native conversations. Export and deletion policy remain with
2512
+ the host application. After confirming its temporary conversation has stopped,
2513
+ the retained owner removes that conversation's active native transcript.
2514
+ The host removes its ownership receipt only after the awaited operation succeeds.
2515
+ The owner's internal deletion retains the original missing-file behavior and
2516
+ propagates lookup and removal failures; native path lookup stays private. It does
2517
+ not perform archival preservation or remove other native history files.
2518
+ `retireClaudeConversationHistory({ configRoot, binding, beforeDelete, requireIdle,
2519
+ signal })` provides the extracted native inventory, branch export and safe removal.
2520
+ The host supplies authorization, preservation and writer exclusion; `requireIdle`
2521
+ must reject any active writer. It works with archived source directories and
2522
+ leaves shared settings and unrelated native conversations intact.
2523
+
2524
+ Hosts with an existing execution gateway or credential-home policy use
2525
+ `/server/claude-process` for
2526
+ `createClaudeCodeProcess`, `claudeCodeArguments`, `claudeFlagSettings` and
2527
+ `claudeModelConfiguration`. `claudeFlagSettings({ effort, providerEnv, commandHook })`
2528
+ assembles the extracted native settings. A managed host supplies an authorized
2529
+ `commandHook: { command, timeout }`; JSKIT formats the native command-tool hook.
2530
+ Advanced hosts can still supply explicit `hooks` when no `commandHook` is selected.
2531
+ Install these settings before selecting the model through the existing
2532
+ native turn owner's instruction lifecycle.
2533
+ `verifyClaudeProviderKey(provider, apiKey, fetchImpl)` on the same subpath
2534
+ performs the original single Messages compatibility request for an authorized
2535
+ external provider's `claudeBaseUrl` and first model ID. It uses the supplied
2536
+ fetch operation and preserves the 30-second deadline, redirect rejection,
2537
+ 64-token output bound, failure-body cancellation and exact Messages validation.
2538
+ It returns no upstream body or credential and does not save keys. The host keeps
2539
+ selected/optional engine-check order, account authorization and key replacement
2540
+ policy. Both compatibility probes retain their sanitized connection errors;
2541
+ neither operation is a conversation or an account store.
2542
+ `readClaudeCodeAuthStatus` uses a supplied capture runner for native account
2543
+ inspection. Hosts select the executable and credential home and retain their
2544
+ execution policy; the reader returns only public account fields. The ordinary
2545
+ runtime supplies this integration automatically.
2546
+ The retained owner stops its native process or recovers its durable execution
2547
+ identity through the supplied host. Its internal stop operation preserves the
2548
+ original stop-proof contract and throws `claude_stop_unconfirmed` when cleanup
2549
+ cannot be confirmed. Process and execution identity remain owned until cleanup
2550
+ succeeds; application persistence and run-state publication happen afterwards.
2551
+ `bindClaudeConversationAccount` applies the same native-account guard to an
2552
+ existing binding, with the owner's stop and persistence operations. Existing
2553
+ managed integrations retain their binding storage and credential resolution.
2554
+ The configuration helper accepts `{ providerId, model }` and the authorized
2555
+ connection's `{ apiKey, baseUrl }`, returning native model flags and environment.
2556
+ Applications choose permitted connections; JSKIT owns the provider rules.
2557
+ The retained turn owner's existing `prepareTerminal` result also carries the
2558
+ native updater-disable setting in its `env`, after the authorized terminal
2559
+ environment. Hosts keep managed PTY launch, credential policy and their earlier
2560
+ environment layers; they do not reconstruct that native setting themselves.
2561
+ Its same conversation owner performs `stopForTerminal`: native active-turn and
2562
+ closing checks, the existing authorized account binding, then verified Stop.
2563
+ The host supplies its application closing fact and retains subsequent Git,
2564
+ environment and managed PTY preparation.
2565
+ Startup uses an execution
2566
+ facility with `start({ command, args, cwd, env, stream })` and `stop(id)`;
2567
+ `start` returns `{ id, stdin, stdout }` for duplex protocols, and a successful
2568
+ stop must return `{ scopeEmpty: true }`. Engine flags, environment normalization
2569
+ and the JSON initialization handshake stay inside JSKIT.
2570
+
2571
+ Without a replacement execution facility, Claude startup uses the supplied local
2572
+ POSIX process-group implementation. This is a trusted single-user host, not a
2573
+ sandbox or resource limiter. It does not grant a permission bypass by default.
2574
+ The native executable and its account login must already exist. Call `stop()`
2575
+ and await its proof on shutdown; closing the protocol stream alone does not
2576
+ prove that native work stopped. Failed cleanup remains an error or an unconfirmed
2577
+ proof, and the owning application must retain its stop handle. Managed applications
2578
+ such as Vibe64 replace execution to enforce identity, resources and scope cleanup.
2579
+
2580
+ For native history retirement, `nativeConversationStoragePolicy(engineId)` from
2581
+ `/server/conversation` describes the engine's writer requirements. Codex's native
2582
+ deletion transaction owns writer locking. Claude and OpenCode require host writer
2583
+ exclusion; their policy supplies `matchesProcess({ executable, commandLine })`,
2584
+ where `commandLine` contains NUL-separated arguments. OpenCode permits a managed
2585
+ server exception only after the host independently verifies that server's identity.
2586
+ The host still authorizes retirement, inventories processes, verifies ownership
2587
+ and holds its exclusion lock until the operation finishes. Matching a command
2588
+ name alone never establishes a managed identity.
2589
+ `retireNativeConversation` from `/server/native-history` owns the shared retirement
2590
+ sequence used by all three native integrations. Its existing `beforeDelete`
2591
+ contract requires `{ preserved: true, exclusive: true }` after exporting every
2592
+ inspected conversation. It rechecks exact export revisions and inventory before
2593
+ removal, then verifies absence. A failed proof, changed history or unconfirmed
2594
+ deletion remains an error; application retention and archive publication stay
2595
+ outside this operation.
2596
+
2597
+ `retireCodexConversationHistory(provider, binding, { toolHomeSource, beforeDelete,
2598
+ signal, errorPrefix })` from `/server/codex-provider` composes that sequence for
2599
+ an already authorized Codex provider. It checks the complete idle family, exact
2600
+ saved directory, paginated history and safe native rollout files before exporting
2601
+ and deleting. The host supplies its authorized native home and preservation
2602
+ callback; it still owns provider acquisition, archive publication and retirement
2603
+ policy.
514
2604
 
515
2605
  ### Live native-provider text
516
2606
 
@@ -520,14 +2610,16 @@ call `update(scope, { turnId, messageId, role, delta })` for incremental text or
520
2610
  `update(scope, { turnId, messageId, text })` for a provider snapshot. Deltas retain
521
2611
  all whitespace; snapshots replace text. A changed update returns
522
2612
  `{ revision, messages }`; an unchanged snapshot or a completed item returns `null`.
523
- Messages carry `status: "inProgress"`. Supply the same message ID to live output
524
- and its eventual durable writer.
2613
+ Messages carry `status: "inProgress"`. Preserve the provider's message IDs. If
2614
+ the provider proves that different live and saved IDs represent one output,
2615
+ carry that same `outputId` in both representations to retain stable output
2616
+ identity. It does not replace either message ID or an admission receipt.
525
2617
 
526
- The Codex classifier exposes `assistant_started` (including assistant/commentary
527
- role) and `assistant_delta` (including raw `delta`). Start the stream item before
528
- its deltas to retain its role. `openCodeAssistantMessageText(message)` from
529
- `/server/opencode-client` reads text from the client's normalized message rows,
530
- preserving whitespace at the partial-text boundary.
2618
+ The Codex run owner interprets `assistant_started` (including assistant/commentary
2619
+ role) and `assistant_delta` (including raw `delta`), starting each stream item before
2620
+ its deltas to retain its role. OpenCode's shared runtime reads text from its
2621
+ normalized native message rows, preserving whitespace at the partial-text
2622
+ boundary; applications do not parse those rows for live output.
531
2623
 
532
2624
  Send snapshots over the application's existing transport and include `read(scope)`
533
2625
  in its authorized history read for reconnects. On the client,
@@ -536,16 +2628,18 @@ overlays live messages without mutating saved turns. Saved message IDs win over
536
2628
  matching live messages. Consumers such as final-answer speech and answer actions
537
2629
  must wait until the message no longer has `status: "inProgress"`.
538
2630
 
539
- After successfully saving an item, call `complete(scope, messageId)` and publish
540
- the returned snapshot with the saved turn. Completed items reject late chunks
541
- until the next native turn. Call `clear(scope)` when work stops or the scope is
2631
+ After successfully saving an item, call `complete(scope, messageId)` with the
2632
+ live item's message ID and publish the returned snapshot with the saved turn.
2633
+ Completed items reject late chunks until the next native turn. Call `clear(scope)`
2634
+ when work stops or the scope is
542
2635
  retired. Applications must reject stale provider events before updating a stream;
543
2636
  the buffer does not authorize events or determine which native turn is current.
544
2637
  The buffer survives browser reconnects while its server process remains alive.
545
2638
  Provider history and application persistence remain responsible for process-restart
546
2639
  recovery; partial text is never persisted as a completed answer by this API.
547
2640
 
548
- The Codex client takes `{ endpoint, maxMessageBytes, requestTimeoutMs,
2641
+ For isolated host tests, the original Codex client in `/testing/native-codex`
2642
+ takes `{ endpoint, maxMessageBytes, requestTimeoutMs,
549
2643
  WebSocketImpl }`. It connects to a WebSocket or `unix://` endpoint, then
550
2644
  `initialize({ clientInfo, capabilities })` performs the native handshake.
551
2645
  `request(method, params, { signal })`, `subscribe(callback)`,
@@ -555,19 +2649,215 @@ Set transport limits appropriate to the host; the default payload limit is
555
2649
  unbounded. A request abort retires the local request; interrupt a running native
556
2650
  turn with `turn/interrupt` when cancellation must stop provider work.
557
2651
 
558
- The detached watcher takes `(provider, threadId, { includeThreadHistory,
559
- onEvent, timeoutMs })`. `provider.subscribe` delivers native notifications;
560
- `provider.readThread` supplies authoritative history when enabled. Call `wait()`
561
- **before** starting the turn, then `setTurnId()` when startup acknowledges it.
562
- This preserves completion/failure notifications that arrive before the start
563
- response. `completeNow`, `failNow`, and `failAfterDetailGrace` handle authoritative
564
- startup statuses. `onEvent` is a synchronous, nonthrowing observer; enqueue
565
- asynchronous persistence in the application. The completion result contains
566
- `{ status, text, threadId, turnId, usage }`. A zero timeout requires provider
567
- `isAvailable()` and `currentConnectionGeneration()` for connection-loss checks.
568
- Always await or handle the wait promise and retire it on startup failure.
569
-
570
- The OpenCode client accepts a loopback HTTP `baseUrl`, `directory`, credentials,
2652
+ Managed Codex hosts use `createCodexAppServerRunOwner` from
2653
+ `/server/codex-turn`. It constructs the native notification queue from the
2654
+ supplied request-context and logging facilities and its own shutdown fence.
2655
+ The same original owner coordinates receipts, observation, goals, steering, interruption,
2656
+ scoped turns and private renewal execution against the supplied application
2657
+ store and publication policy. Detached waiting and run-transition helpers stay
2658
+ inside that owner; hosts do not compose another observer or delivery journal.
2659
+
2660
+ This advanced constructor adapts an existing managed host's retained run store
2661
+ and application effects. Ordinary applications use `createConversationRuntime`
2662
+ with their chosen conversation storage; they do not configure the following
2663
+ run-owner facilities themselves.
2664
+
2665
+ | Existing run-owner input | Host supplies | Shared owner retains |
2666
+ | --- | --- | --- |
2667
+ | `createRuntime`, `createStore`, `namespace`, `normalizeRunState`, `deliveryStateMetadataKey` | Existing store access, identity and persisted-schema adapters; bounded reads stay separate from full runtime hydration | Native run transitions, receipt ordering and recovery |
2668
+ | `providerSessions` | The shared provider `owner`, authorized `context`, `keyFields` and `connectionPolicy` | Managed registrations, provider acquisition, reuse, native identity checks and shared recovery; application closure/current-record fences surround the original native connection checks |
2669
+ | `conversationPreparation.context` and `.scope` | Authorized session or scope, workdir, account/profile policy and settings | Native acquisition and isolation verification |
2670
+ | `conversationPreparation.execution` and `.control` | Validated input/output limits, application admission, settings and result projection | Turn dispatch, stable-account checks, current-turn guards, stop and completion state |
2671
+ | `conversationPreparation.response`, `.expired`, `.failure` | Existing product result/error contracts | Native outcome and cleanup evidence |
2672
+ | `messageMetadata`, `checkpoint`, `publish`, notices | Authenticated authorship, application metadata, checkpoints and publication | When each original effect runs relative to native delivery and completion |
2673
+ | `serverClosingError` | Existing application error message, code and retryable value | The managed lifecycle shutdown assertion and its invocation points; error construction occurs only after the native closing fence |
2674
+ | `captureContext`, `runInContext`, `debugLog`, `debugError` | Request-context isolation and application logging | Notification queue, shutdown fence and recovery coordination |
2675
+
2676
+ Managed observation supplies the provider key and application error presentation;
2677
+ the run owner chooses retirement through its existing provider lifecycle. A
2678
+ presenter can read the current managed record without allocating another map.
2679
+ The ordinary driver's existing native close path remains internal to JSKIT.
2680
+
2681
+ `conversationPreparation.admissionError` reports application admission policy.
2682
+ Supply the already-configured shared isolation owner once as
2683
+ `conversationPreparation.isolation`; the run owner uses that same instance for
2684
+ compatibility checks and scoped create/resume/turn execution. The execution
2685
+ projection does not return another isolation reference. These preparation
2686
+ functions supply policy and application effects, not alternate native algorithms. The optional `helperThreads` ledger is described below and
2687
+ is not required for ordinary conversations or stable-account checks.
2688
+
2689
+ For a bound Codex conversation, the application's existing `prepareInput`
2690
+ projects its final authorized actor into `actorContext`. The driver passes that
2691
+ same value to receipt attribution and turn preparation; it does not interpret
2692
+ application-specific user fields. Derive this value from the authenticated
2693
+ application context, not a browser-supplied identity.
2694
+
2695
+ Its `checkManagedConnection` and `restoreLoadedThread` operations also own
2696
+ native status/control checks, loaded-thread pagination, observation before resume
2697
+ and exact run reconciliation. The existing host supplies current-session
2698
+ admission and closure checks, lazy resume settings, managed registration and
2699
+ ready-state persistence at their original sequence points. The same run owner
2700
+ selects the active provider for Stop and the retained failed-observation owner.
2701
+ Observation recovery acquires its shared provider directly without taking an
2702
+ active-turn shortcut. Its original reconciliation map and prune waiter retain
2703
+ the full loaded-thread attempt and readiness fallback, including application
2704
+ health/error handling and result projection. The host wraps that same native
2705
+ readiness operation with authorization and persistence; it does not dispatch a
2706
+ separate native fallback. The same run owner's private lifecycle facet owns
2707
+ the original wellbeing timers, closing fence, generation, session-closure and
2708
+ reconciliation-task collections. Shutdown retains its cached two-sweep operation:
2709
+ start invalidation before draining existing work, preserve original error
2710
+ priority, then complete the second sweep. The host supplies project inventory,
2711
+ authorized preparation and result policy. Its private reconciliation facet owns
2712
+ the original batch restoration, concurrent Helper inventory, stale-generation
2713
+ pruning and startup unsubscribe/retirement. It uses that same task/generation
2714
+ state. Session and receipt reads, source-write leases and result presentation
2715
+ remain supplied by the host; no second native recovery loop is created.
2716
+ The run owner's `outputProvider` keeps active-provider reuse ahead of lazy host
2717
+ configuration. Its `runtimeForVisibleTerminal` retains the existing authorized
2718
+ provider choice, persisted project hook trust and tracked runtime acquisition;
2719
+ the host still prepares and launches its terminal. These paths intentionally
2720
+ retain their distinct configuration timing.
2721
+ The existing goal owner also selects first-goal readiness before native acquisition;
2722
+ later goal controls retain their authorized pinned command context. The host
2723
+ supplies readiness health/storage policy and the stored-goal/result projection.
2724
+ The provider-selection facet restores Helper ownership before model discovery or
2725
+ account description, retaining supplied-provider reuse, lifecycle gating and the
2726
+ original per-connection catalog cache. Authorized scope/account preparation,
2727
+ deadlines and result policy remain with the host.
2728
+ Legacy detached host operations use that same owner's `acquireDetachedThread`,
2729
+ `detachedThreadId` and `dispatchDetachedTurn`. The ID projection accepts the
2730
+ original native response shapes and requested-thread fallback after either
2731
+ ordinary acquisition or retained Helper preparation. Applications retain
2732
+ account/profile policy, bounds and result policy around those operations. The run owner's optional
2733
+ `helperThreads` facility owns durable native-thread identity, mutation/start and
2734
+ cleanup coordination, provider retirement, queued restart restoration and
2735
+ isolated Helper inventory. Restoration revalidates the same ledger revision and
2736
+ native identity before attachment; failed retirement retains retryable records.
2737
+ A managed host supplies its
2738
+ codec-bound ledger owner and retains its authorized storage root and selection
2739
+ policy; it does not implement another ledger or retirement algorithm.
2740
+ The ledger owner supplies `matchesExecutionProfile(recorded, expected)` using its
2741
+ canonical profile snapshot. A host can retain its existing shorthand-profile
2742
+ policy through that named method. Native control checks ownership,
2743
+ lifecycle, provider, workdir and turn identity before invoking that policy.
2744
+ The host retains admission/frozen-state projection. Native conversation commands
2745
+ call the same Helper owner directly for thread lookup and retirement, without a
2746
+ host inspection callback. A returned retirement promise retains the original
2747
+ admission-release order; native dispatch remains awaited.
2748
+ It also rejects ordinary access to a retained Helper thread and aggregates
2749
+ restoration failures without changing their retryability. The host decides when
2750
+ successful restoration is required and supplies its authorized storage root.
2751
+ Stable-account capture and completion validation also belong to the shared run
2752
+ owner, independently of the optional retained Helper ledger. They preserve the
2753
+ account check after native resume, and the exact current-turn guard before
2754
+ completion changes state. Hosts supply profile/output policy and synchronous
2755
+ application result projection; they do not mutate native completion state.
2756
+
2757
+ Internally the retained run owner composes its original journal, message and
2758
+ conversation commands, delivery, output, reasoning, notification observation,
2759
+ readiness, result settlement, recovery and control operations. Each responsibility
2760
+ retains its original collections; dependent operations receive those same owner
2761
+ instances. The shared prompt-delivery marker remains in the coordinator because
2762
+ both recovery and message admission use it. No second observer, provider,
2763
+ transcript or dispatch implementation is introduced. Existing host imports remain
2764
+ unchanged. Progress length, finalization grace, snapshot-recovery bounds and
2765
+ native outcome names remain private native defaults, rather than host tuning
2766
+ options.
2767
+ `codexContextUsageFromNotification` from the existing `/server/codex-events`
2768
+ host facility interprets native token-usage updates and validates their counts
2769
+ and context window. Applications retain current-conversation checks, storage
2770
+ and renewal presentation around the returned usage facts.
2771
+
2772
+ `createCodexHelperThreadLedgerOwner({ executionProfile, snapshotExecutionProfile,
2773
+ errorPrefix })` from `/server/codex-turn` binds the host's strict persisted-profile
2774
+ codec. The optional snapshot codec defaults to that codec; use the existing
2775
+ normalizer when accepted inputs need normalization before strict persistence.
2776
+ The returned `createCodexHelperThreadLedger({ projectRuntimeRoot })` retains the
2777
+ original versioned record shape, immutable native identity, revision comparisons,
2778
+ atomic writes, file locks and retryable cleanup. Moving this owner does not
2779
+ migrate or reinterpret existing records. It uses the shared kernel's
2780
+ `tryAcquireExclusiveFileLock` from `/server/support`; `errorCode` retains a host's
2781
+ existing failure contract where required. These are advanced host facilities;
2782
+ ordinary persistent conversations use the supplied runtime and storage.
2783
+ For that advanced integration, `/server/codex-turn` also exposes
2784
+ `sendPreparedCodexAppServerHelperTurn(provider, prepared, helperIsolation)`.
2785
+ The host first validates its policy and supplies `prepared` with `threadId`,
2786
+ `input`, `executionProfile` and `turnSettings(cwd)`. The shared operation obtains
2787
+ the existing isolation facility's execution context, sends the native turn and
2788
+ returns the frozen `{ executionProfile, input, turn }` result. Scoped runtime
2789
+ work uses the same native statement. Ordinary applications use the conversation
2790
+ runtime and do not need this host facility.
2791
+ The same run owner recovers an abandoned starting claim before native turn
2792
+ selection: it retains the original 15-second grace, requires both native IDs to
2793
+ be absent and checks that no prompt delivery is active. Managed hosts may supply
2794
+ `admissionTaskFinished(session, run)` for their existing task-completion policy;
2795
+ they do not supply the claim classifier or its failed-run persistence sequence.
2796
+ `/server/codex-events` retains only the event identities, thread-history
2797
+ projections and context-refresh facts used by existing host policy. The native
2798
+ renewal owner resumes the exact predecessor or creates/adopts the successor,
2799
+ then inspects its snapshot for the original operation, missing turns, unreadable
2800
+ predecessor history or unrelated successor history. The host supplies lazy
2801
+ settings and authorized identity/receipt storage, retaining renewal approval,
2802
+ metadata names, user-facing errors and retry policy. Hidden successor preparation
2803
+ does not open or publish an ordinary Main conversation. The snapshot classifier
2804
+ is now internal to that native owner.
2805
+
2806
+ `/server/codex-provider` supplies the original provider, shared acquisition and
2807
+ thread-selection operations. Its existing provider owner also selects cached or
2808
+ uncached verified shutdown. `stopProvider` uses the supplied host `stopRuntime`
2809
+ facility when no retained provider exists; `stopThreadBeforeRelease` preserves
2810
+ the original shared-peer check before allowing process shutdown as the fallback
2811
+ for an unconfirmed thread stop. The host retains its application cleanup and
2812
+ must still obtain the existing exit proof. `/server/codex-process` supplies the shared runtime
2813
+ and process-leader operations for an existing execution host.
2814
+ `/server/codex-configuration` supplies model/account configuration and the
2815
+ verified Helper isolation facility. Its `codexCatalogRows` reads the original
2816
+ array or native `data` response without copying its rows; an optional lazy error
2817
+ factory preserves a managed host's existing failure type. `codexCatalogModels`
2818
+ and `codexCatalogReasoningEfforts` retain native IDs, display-name fallbacks and
2819
+ reasoning-option order. Hosts choose offerings, defaults, connection visibility
2820
+ and product labels; they may use the original row fields `model`, `hidden`,
2821
+ `isDefault` and `defaultReasoningEffort` for that policy.
2822
+ `codexProviderFileConfiguration(provider, key, codexHome)` formats the original
2823
+ private `config.toml` text from the host's authorized provider record and key.
2824
+ The record supplies `id`, `label`, `baseUrl`, `webSearch` and the selected first
2825
+ model's `id` and `defaultThinking`; the native catalogue path remains
2826
+ `<codexHome>/models.json`. The formatter does not read or write credentials.
2827
+ `verifyCodexProviderKey(provider, apiKey, fetchImpl = fetch)` performs the original
2828
+ single Responses compatibility request using that first model. It retains the
2829
+ 30-second deadline, redirect rejection, 64-token output bound, failure-body
2830
+ cancellation and exact successful-response validation. It returns no provider
2831
+ response or credential. Hosts authorize the provider record and key before
2832
+ verification and preserve their existing connection until verification and
2833
+ required runtime invalidation succeed; they own private file writes and account
2834
+ publication. The formatter's returned text contains the key and must stay in
2835
+ private server storage.
2836
+ `codexConfiguredModelCatalog` formats a host-selected model and effort without
2837
+ querying a provider. The host decides whether to include that configured row. Its ordinary thread/turn formatters and
2838
+ `codexInteractiveArguments` serialize native request and CLI shapes from the
2839
+ host's authorized settings; they do not choose sandbox, approval or managed
2840
+ execution policy. A host that already provides the execution sandbox can select
2841
+ `externalSandbox: true` in `codexAppServerTurnSettings`; the codec supplies the
2842
+ native external-sandbox/network fields. The default grants no such policy.
2843
+ The isolation owner's `threadStartSettings(settings, threadSource)` only adds
2844
+ native start metadata, so retained conversations can reuse it with their own
2845
+ source label without applying Helper policy.
2846
+ `normalizeCodexThreadId` retains the native UUID grammar used
2847
+ for interactive resume and saved native identities. A host that has selected a
2848
+ restricted, read-only scope can apply `codexAppServerReadOnlyThreadSettings`
2849
+ to its prepared thread settings: it overlays the original empty native facility
2850
+ lists and read-only sandbox. The host retains selection and preparation of the
2851
+ tool-free configuration; this formatter does not replace isolation verification.
2852
+ Ordinary conversations use the common runtime; Helper isolation is not a
2853
+ substitute policy for a retained conversation.
2854
+ The same isolation owner formats authorized Helper start, resume and turn
2855
+ settings after profile validation. It preserves the verified configuration object
2856
+ and the distinct start/resume field sets; hosts retain their permission and
2857
+ output-bound policy.
2858
+
2859
+ For isolated host tests, `createOpenCodeServerClient` from
2860
+ `/testing/native-opencode` accepts a loopback HTTP `baseUrl`, `directory`, credentials,
571
2861
  and optional `fetchImpl`. It exposes native sessions, prompt, interruption,
572
2862
  messages, status, events, model/agent catalogues and account operations. Response
573
2863
  and event reads are bounded. `allowAttachmentDirectories` defaults to false;
@@ -575,11 +2865,253 @@ enabling it grants the native conversation access to parent directories of
575
2865
  supplied attachments. Only the host can make that permission decision after
576
2866
  resolving and authorizing each file descriptor.
577
2867
 
2868
+ Its `readConversationStorage`, `readConversationStoragePage`,
2869
+ `listConversationChildren` and `listConversationsForDirectory` operations read
2870
+ native persisted records without initializing a live project directory. Inventories
2871
+ reject incomplete results; page reads preserve the native continuation cursor and
2872
+ support cancellation. `allowConversationAttachments` updates only that native
2873
+ conversation's retained file permissions. These operations do not authorize the
2874
+ caller or implement retention policy.
2875
+
2876
+ `/server/opencode-client` supplies
2877
+ `retireOpenCodeConversationHistory(storageClient, controlClient, binding,
2878
+ { beforeDelete, signal })`. A managed host supplies its authorized shared storage
2879
+ client, control client scoped to the exact saved directory, saved binding and
2880
+ durable preservation callback. The operation owns native family inspection,
2881
+ bounded export and deletion through the existing preserve/recheck/remove/verify
2882
+ sequence. Its private inspection rejects busy or foreign-directory children;
2883
+ export retains native records and text projections, checks message and cursor
2884
+ identity, and bounds pages, duration and output. Incomplete exports never
2885
+ authorize deletion. Process acquisition, access policy and archive persistence
2886
+ remain with the host.
2887
+
2888
+ `/server/opencode-turn` also owns `runOpenCodeConversationTurn` for an approved,
2889
+ bounded native request. It first checks the exact input's history, dispatches
2890
+ only when that result is absent, and rechecks admission if waiting fails. Hosts
2891
+ supply the authorized native input/model/agent, deadline and optional diagnostic
2892
+ or error-construction hooks. It does not choose renewal policy or create another
2893
+ conversation store.
2894
+
2895
+ An existing renewal host may pass a fourth argument containing
2896
+ `{ expectedThreadId, forbiddenThreadId, requireFreshHistory, completeResult, close }`.
2897
+ The owner checks native identity and optional bounded freshness before reading a
2898
+ lazily supplied `input.prompt`, then keeps its separate receipt-history read.
2899
+ For seeding, `completeResult(result)` awaits the host's acknowledgement validation
2900
+ and durable metadata; only success invokes its original `close()` facility.
2901
+ Native failure or failed result projection does not add automatic cleanup.
2902
+ The result includes the original freshness fact and exit proof. Prompt grammar,
2903
+ authorization and private successor storage stay with the host. Ordinary chats
2904
+ use the common runtime's existing operations.
2905
+
2906
+ `/server/opencode-turn` exposes `openCodeRowsForInput` for the existing host's
2907
+ presentation of authorized native history. Its `openCodeMessageError(message)`
2908
+ extracts the original native error text; a host can apply its application-specific
2909
+ diagnostics afterward without decoding another native error envelope.
2910
+ Managed turn control and completion stay in `createOpenCodeSharedRuntime`;
2911
+ the host supplies its existing `readError(message)` diagnostic projection to that
2912
+ owner. Native history interpretation and observation loss remain internal.
2913
+ The same owner answers temporary activity from its original conversation map,
2914
+ excluding cosmetic reasoning-summary work. The host validates its session ID and
2915
+ keeps application preparation gates; it does not scan native entries itself.
2916
+ The shared runtime's internal observer retains production readiness, failure
2917
+ propagation and awaited closure; waiting for readiness preserves native event
2918
+ order. Its internal admission inspection uses the bounded native user-message lookup: a missing or unreadable receipt is
2919
+ unknown, never permission to resend. The shared runtime owns admitted-turn
2920
+ steering and the original one-attempt missing-answer recovery. Its existing
2921
+ monitor projection preserves completed reasoning before recovery advances the
2922
+ native input boundary. Applications retain their authorization and durable
2923
+ delivery receipts. The shared runtime's internal dispatch preserves event readiness before
2924
+ the application's admission hook, then records the attempted native send and
2925
+ submits the original resumed prompt. Delivery defaults to queue; a host steering
2926
+ through its existing connected observer supplies `input.delivery: "steer"` and
2927
+ omits the new-observer wait. The owner reads `options.signal` after readiness, so
2928
+ a retained host can resolve its current turn's cancellation signal at that same
2929
+ boundary. The host keeps its durable acceptance policy: an authored row and its
2930
+ publication may precede advancing the observed input ID. Main, managed temporary
2931
+ and standalone consumers use this same dispatch operation.
2932
+ For a retained target that can refresh during readiness or admission, the
2933
+ existing options object can supply `client` and `conversationId` getters. The
2934
+ owner reads them, then the prompt envelope, after the admission hook completes.
2935
+ The shared runtime retains native completion, interruption and verified failure
2936
+ cleanup. Its projection and after-close notifications preserve application output
2937
+ validation and checkpoint ordering without moving product policy into the native
2938
+ layer. Unconfirmed cleanup retains the active native record for retry. The common
2939
+ driver uses that same sender, native admission gate, monitor and Stop owner. Its
2940
+ connection, local execution, tool, attachment and structured-output facilities
2941
+ adapt to that owner. Tool effects await the exact authored delivery receipt;
2942
+ rejected steering preserves the previous active request. `/server/opencode-process` exports
2943
+ `createOpenCodeServerProcess`
2944
+ for advanced managed integration. The common driver's internal configuration
2945
+ uses that same server startup, authentication, version, observation and cleanup
2946
+ implementation. `openCodeProcessEnvironment(baseEnv, { cacheRoot, dbPath,
2947
+ inlineConfig, outputTokenMax, password })` assembles native flags, cache,
2948
+ loopback proxy bypass and supplied inline configuration. A managed host supplies
2949
+ its isolated environment, credential roots, approved plugin configuration and
2950
+ application command hooks around those native values. That boundary preserves
2951
+ the host's existing permission and process policies while using the same native
2952
+ startup, cleanup and acquisition owner.
2953
+ The same owner selects retained scoped targets, projects persistent native
2954
+ status and admission, reconciles restored turns, and reads final native output
2955
+ before application validation. A retained host binding supplies its existing
2956
+ owner with authorized configuration and application effects; the common driver
2957
+ invokes that owner directly for Main and scoped conversations. It does not route
2958
+ native operations back through an application controller. Process acquisition
2959
+ and native event observation stay with the shared owner, using the original
2960
+ managed-execution, storage and publication facilities. Managed hosts retain their
2961
+ existing storage transactions, saved-run fences, authorized profile limits and
2962
+ publication. This internal binding adaptation is not a second application
2963
+ integration API; ordinary applications use the common conversation runtime.
2964
+ The lower-level operation takes an owned `execution` facility, private environment,
2965
+ absolute `workdir` and `privateRoot`, and authorized `{ providerId, apiKey }`
2966
+ connections. It removes the private root only after confirmed host cleanup.
2967
+ Readiness keeps the production 30-second deadline, bounded health probes, strict
2968
+ `healthy: true` response and exact trimmed version check. Startup timeout errors
2969
+ retain the last health failure as their cause. Authentication follows readiness
2970
+ once per provider; it uses the caller's cancellation signal, not the readiness
2971
+ deadline. A successful stop retains its complete proof for repeated calls; an
2972
+ unconfirmed or rejected stop remains retryable. The execution facility receives
2973
+ the native stop reason and three-second termination grace period.
2974
+ The application still owns its database path and retention. Startup errors retain
2975
+ the execution ID and cleanup proof; optional host `readLogs()` supplies bounded
2976
+ diagnostics. If cleanup is unconfirmed, the error also retains `retryCleanup`,
2977
+ the same native stop operation. The shared owner holds that operation until exit
2978
+ is verified and blocks replacement startup while cleanup remains unconfirmed.
2979
+ Its `cleanupPending` flag lets a managed host retry cleanup during project close
2980
+ or shutdown even when acquisition never returned a server. An unconfirmed stop
2981
+ leaves state intact for host recovery. Ordinary
2982
+ applications use `createConversationRuntime()` rather than assembling these
2983
+ native setup options.
2984
+
2985
+ For an advanced host's connection-setup flow, the same subpath provides
2986
+ `readOpenCodeCatalog({ privateRoot, workdir, createServerProcess })`. It writes
2987
+ OpenCode's non-secret public catalogue identity, calls the supplied server factory
2988
+ with `{ dbPath, privateRoot, providerConnections: [], workdir }`, reads providers
2989
+ and agents, and awaits confirmed Stop before returning. A cleanup failure takes
2990
+ precedence over a captured read error. The host retains managed execution,
2991
+ catalogue caching and model eligibility. `readOpenCodeZenModelIds({ fetchImpl,
2992
+ timeoutMs })` reads the bounded credential-free public model list without
2993
+ borrowing an account key.
2994
+
2995
+ `verifyOpenCodeApiKey({ apiKey, agentId, modelId, modelProviderId, privateRoot,
2996
+ workdir, prepareCommand })` owns the original isolated auth-file write, one pure
2997
+ native request, result classification and credential cleanup. Its synchronous
2998
+ `prepareCommand({ providerId, modelId, privateRoot, workdir, outputTokenMax })`
2999
+ returns the host's existing command operation. The owner calls that operation
3000
+ once with `{ args, maxBuffer, mode, timeout }`; the host supplies approved
3001
+ execution identity, credential-home policy and environment. Preparation runs
3002
+ after the auth write, outside the command-failure sanitization boundary and
3003
+ inside the credential-cleanup `finally`. This preserves setup errors separately
3004
+ from sanitized invocation failures. The request keeps its 16-token output limit,
3005
+ 256-KiB capture bound and 30-second deadline. The host supplies an isolated
3006
+ tool-free agent configuration; `agentId` defaults to the existing common
3007
+ `jskit-assistant` agent name. Hosts may translate the shared `assistant_opencode_*`
3008
+ error categories to their existing public errors without copying native logic.
3009
+ These operations support host account setup; ordinary conversation consumers
3010
+ continue to use the common runtime.
3011
+
3012
+ The same subpath exposes the extracted `createOpenCodeSharedRuntime` for hosts
3013
+ whose managed service is shared by several conversation consumers. It owns server
3014
+ reuse, pending acquisition, verified final release and exact-server recovery.
3015
+ Its retained Main turn and monitor maps also back the original
3016
+ `interruptTurn(key, target, { threadId, writeRun })` operation. The host selects
3017
+ the authorized target, supplies a saved identity only when no target exists, and
3018
+ binds its existing run writer. Ordinary Stop retains the five-second native
3019
+ acknowledgement, retryable failure flags, admission release and current-monitor
3020
+ wait. It does not add an idle query. The existing observation-loss branch keeps
3021
+ its stronger native-stop or exact-process-exit proof. `turnSnapshot` supplies the
3022
+ same result fields to host readers.
3023
+ The bound OpenCode driver uses the same owner's `readSessionState` for canonical
3024
+ and native reads. Application context, saved identity and terminal-state facilities
3025
+ retain their original read order. Passive reads do not prepare or start a native
3026
+ server; ordinary consumers use the common conversation read API.
3027
+ The same native host subpath supplies `openCodeModel(selection, executionProfile)`
3028
+ for the existing selected-model shape. It retains the original model fallback,
3029
+ profile effort override and native `{ id, providerID, variant? }` formatting.
3030
+ Hosts authorize the account and validate their execution profile before calling;
3031
+ the formatter does not select a workload or grant tools. Ordinary applications
3032
+ continue to pass common configuration to `createConversationRuntime`.
3033
+ Before native identity selection or publication, `ensureOpenCodeSession` and
3034
+ the shared scoped create/select operations await the actual directory's startup.
3035
+ The scoped client's `prepareDirectory` uses the authenticated, read-only native
3036
+ `GET /path` operation with the normal 30-second initialization bound. Global
3037
+ health and v2 session reads alone do not initialize the directory used by native
3038
+ prompt and Stop. Preparation performs no inference, preserves the saved native
3039
+ identity on failure, and is retried through the existing acquisition operation.
3040
+ There is no permanent readiness cache or change to Stop's five-second deadline.
3041
+ `inspectSessionReadiness(target)` retains the original health/session read and
3042
+ captured server facts. The host revalidates its session, selection and closure
3043
+ before accepting that result. Inside its existing write admission,
3044
+ `recoverSessionReadiness(target, recovery, prepare)` stops only the captured
3045
+ unhealthy process still owned by this runtime, clears a missing native session
3046
+ only on the same target, and invokes the host's existing preparation. A lazy
3047
+ `recovery.key` preserves a key read after the awaited stop. The separate
3048
+ `recoverObservation(target, threadId, commit)` confirms that exact thread is idle
3049
+ before the host's existing saved-run comparison and commit. Its synchronous
3050
+ `needsObservationRecovery(key, run)` predicate uses the same owned monitor map;
3051
+ an existing host checks it before write admission and again after refreshing its
3052
+ saved run inside that admission. The host's full current-run comparison remains
3053
+ inside its storage transaction. Recovery does not interrupt or start native work.
3054
+ Healthy inspection does not require write admission.
3055
+ The same owner's `beginMonitor(key, target, turn, options)` retains the original
3056
+ Main admission, native completion, compaction phase and failure-cleanup sequence.
3057
+ It uses the host's already-prepared run record and existing observation adapter.
3058
+ The three application effects are `writeRun`, `projectMessages` and
3059
+ `completeResult`: run persistence, transcript projection and final notices.
3060
+ The owner's history projection supplies ordered reasoning facts (`messageId`,
3061
+ `partId`, unmodified `text`, `createdAt` and `complete`) to its reasoning presenter,
3062
+ with separate message-level `complete` and `flush` options. Native part selection
3063
+ and end-marker decoding stay inside JSKIT. The host keeps saved-message identity,
3064
+ headline policy, first-write timestamp formatting and publication.
3065
+ Final-response inputs are read after admission and the Active write. Observer
3066
+ closure precedes notices and the final write. The existing `onRetired` resource
3067
+ continuation starts only after identity-safe monitor removal; its original
3068
+ fire-and-forget cleanup does not become an awaited native shutdown step.
3069
+ The same temporary records also back `createConversation`, `selectConversation`,
3070
+ `runConversationTurn`, `readConversation`, `readPersistentConversation` and
3071
+ `waitForConversationTurn`. These methods retain native creation and model/agent
3072
+ selection, active-turn steering, admission, completion and history-read ordering.
3073
+ Scoped completion also uses the owner's existing native row and structured-result
3074
+ codecs. Applications receive normalized messages for publication and retain their
3075
+ raw-output bounds and result metadata; native structured unwrapping follows that
3076
+ bound at its original late read point.
3077
+ The host supplies authorized targets, profile policy and native message-ID mapping;
3078
+ its existing observation presenter, registry write, bounded result projection,
3079
+ publication and checkpoint effects run at their original positions. Selection
3080
+ awaits registry publication after recording the native identity. Turn admission
3081
+ attaches completion notices before returning; bounded Helper waits retain the
3082
+ saved completion even when a caller supplies another timeout. Ordinary consumers
3083
+ continue to use `createConversationRuntime()`.
3084
+ `releaseTarget` drains the exact active native threads and retained completions
3085
+ before the existing shared release. The host keeps prior PTY/resource cleanup
3086
+ and its registry-removal notification. The same temporary records back native
3087
+ `stopConversation` and `deleteConversation`: persistent Stop retains its separate
3088
+ idle confirmation, and deletion precedes observer retirement and awaited
3089
+ registry removal. Hosts retain access checks, Helper scope release and durable
3090
+ parent cleanup receipts; those are not replaced by process disposal.
3091
+ For an existing scoped binding, explicit deletion calls
3092
+ `preparation.existing(input, context, { operation: "delete" })`. The host resolves
3093
+ its scope/persistence policy from that current context. Read, wait and Stop use
3094
+ the same preparation without the deletion descriptor; closing the handle still
3095
+ uses its supplied close operation. The driver does not interpret application
3096
+ scope fields to choose deletion policy.
3097
+ A host supplies authorized startup and its application removal notification.
3098
+ `openCodeServerForDirectory` keeps each consumer's native client scoped to its
3099
+ working directory. `openCodeEnvironmentForDirectory` and
3100
+ `openCodeEnvironmentForSession` use supplied private bindings, choosing the most
3101
+ specific directory or following at most 32 native parents. Hosts retain the
3102
+ binding values and application authorization. `writeBindings(path, scope, rows)`
3103
+ publishes each consumer's contribution through one private atomic registry;
3104
+ updating or removing one scope retains the others. `readOpenCodeEnvironments`
3105
+ reads that registry for native plugin lookup. The common driver uses this same
3106
+ acquisition layer. Its plugin resolves the current instructions, environment,
3107
+ command wrapper and application-tool bridge by the registered native session.
3108
+ Changing one conversation's grant does not change another's permissions.
3109
+
578
3110
  ## Companions and templates
579
3111
 
580
3112
  A companion can receive an app-selected layer containing conversation state and
581
3113
  `submitText`, rather than searching the DOM. `createAssistantTextSubmission`
582
- from `/client/conversation-submit` builds that action from
3114
+ from `/client/conversation` builds that action from
583
3115
  `{ getState, setDraft, submit, afterDraftChange }`. State is
584
3116
  `{ id, active, draft, canSend, turnActive }`. It preserves existing drafts,
585
3117
  supports `{ sendImmediately: false }`, waits briefly for send readiness, and
@@ -587,23 +3119,27 @@ checks draft/ownership again before using the canonical submit action.
587
3119
  Pass an `AbortSignal` and abort it when selection, visibility or ownership
588
3120
  changes, including switching away and back to the same retained conversation.
589
3121
 
590
- The published package contains `examples/conversation`, a standalone Vue app
591
- with a Node backend and no editor dependency. Copy it as an application template
592
- or use the component directly. It runs without credentials using a labelled demo
3122
+ The `assistant-core/examples/conversation` directory demonstrates lower-level
3123
+ UI, delivery and HTTP primitives. For a new conversation application, start with
3124
+ `assistant-voice/examples/conversation`: speech is optional, and its text cards
3125
+ and headless task use the supplied runtime, transport and browser binding.
3126
+ The lower-level example has a Node backend and no editor dependency. It runs without credentials using a labelled demo
593
3127
  provider; an explicit integration configuration enables real AI inference.
594
3128
  Its backend validates configuration independently of the UI and accepts a
595
3129
  replacement storage module. See its README for commands and scope limits.
596
3130
 
597
3131
  ## Observation loss and composer responsiveness
598
3132
 
599
- `CodexAppServerJsonRpcClient({ endpoint, onDisconnect(error) })` reports an
600
- unexpected close, socket error, or unreadable message once for the current
601
- connection. An explicit `close()` does not call the callback. Obsolete socket
602
- events cannot settle current requests or reach subscribers. The application
603
- owns handling callback failures, verified cancellation, reconnection and any
604
- policy requiring an explicit Resume or Send. A transport disconnect is not
605
- proof that native execution stopped. OpenCode's SSE iterator ending is likewise
606
- not proof that the native session is idle; its consumer owns that decision.
3133
+ For isolated host tests, `CodexAppServerJsonRpcClient({ endpoint,
3134
+ onDisconnect(error) })` from `/testing/native-codex` reports an unexpected close,
3135
+ socket error, or unreadable message once for the current connection. An explicit
3136
+ `close()` does not call the callback. Obsolete socket events cannot settle current
3137
+ requests or reach subscribers. A test harness using this low-level transport owns
3138
+ callback failures, verified cancellation and reconnection. Ordinary applications
3139
+ use `createConversationRuntime()` for that native coordination and retain their
3140
+ admission and presentation policy. A transport disconnect is not proof that native
3141
+ execution stopped. OpenCode's SSE iterator ending likewise does not prove that
3142
+ the native session is idle; the shared native owner establishes that separately.
607
3143
 
608
3144
  Update `draft` synchronously in `setDraft`. Keep the composer mounted during
609
3145
  external state changes, and change `canSend`, `canStop`, `stopPending` and labels