From 6dee1e31816eb8e9597b224d377cd6b8fc664223 Mon Sep 17 00:00:00 2001 From: Fabio Pliger Date: Mon, 5 Oct 2026 21:44:17 -0500 Subject: [PATCH 1/2] docs(kilo): map cloud consumer contracts (#14426) --- .../baseline/cloud-agent-contracts.md | 260 ++++++++++++++++++ 1 file changed, 260 insertions(+) create mode 100644 migration-tracking/technical-notes/baseline/cloud-agent-contracts.md diff --git a/migration-tracking/technical-notes/baseline/cloud-agent-contracts.md b/migration-tracking/technical-notes/baseline/cloud-agent-contracts.md new file mode 100644 index 000000000000..90361590d696 --- /dev/null +++ b/migration-tracking/technical-notes/baseline/cloud-agent-contracts.md @@ -0,0 +1,260 @@ +# Cloud agent consumer contracts — mapping and dispositions + +Discovery for Kilo-Org/kilocode#14426 (parent epic #14023), building on #14425. +Discovery only: no source changes in any repository. + +## Header + +| Item | Value | +|---|---| +| Date | 2026-10-05 | +| Cloud repo | `Kilo-Org/cloud` at `3de933dd85070e9d15c41d8ff3af6c91ca856fd4` (read-only clone `/Users/fpliger/dev/kiloverse/cloud`) | +| This repo, `origin/main` (v1) | `62f674d4a91969ac077e4a7b6ceeb8a99de80e47` | +| This repo, `kilo-v2` (v2) | `782263326df5be50081517ddedd937f1080a89cf` | +| #14425 inventory | `migration-tracking/technical-notes/baseline/cloud-agent-consumers.md` | + +#14425 assessed the cloud repo at the same SHA; its this-repo SHAs were older +(`origin/main` `622ed1f5`, `kilo-v2` `a2e6c1f6`, both ancestors of the SHAs +above). Owners, pinning and recommended gates below are reused from #14425 and +not re-derived. + +## Citation conventions + +- `cloud@3de933dd :` — the cloud repo at the SHA above. +- `origin/main::` — v1 runtime surface in this repo at the SHA above. +- `kilo-v2::` — v2 runtime surface in this repo at the SHA above. +- A bare `#14425` reference means the inventory document, not a GitHub issue. + +## Scope and boundaries + +In scope from #14425: **cloud-agent-next**, **auto-routing-benchmark**, **MCP +catalog generator (CI)**, **session-ingest** (contract-coupled). + +Out of scope and not re-assessed: Gastown/KiloClaw (EOL by owner disposition), +code-review/auto-fix/auto-triage/security orchestration callers (no runtime), +app-builder, cloud-agent-sdk/web/mobile and the `/remote` relay (#14019), VS +Code Agent Manager (#14016), the `kilo cloud` CLI client. None of the four +in-scope consumers depend on the `/remote` relay; session-ingest is a distinct +HTTP/WS contract. + +The cloud repo pins the v1 CLI/SDK at **7.8.1**. v2 publishes the CLI package as +`@kilocode/cli` too (`kilo-v2:packages/kilo-cli/package.json:2`), but it is +`private` with **no `bin`** and version `0.0.0-internal`, so "v2 reaches a +consumer" is a package/channel and packaging question, not a name question. + +## 1. cloud-agent-next + +Primary hosted runtime; wrapper spawns `kilo serve`, imports `@kilocode/sdk` and +`@kilocode/sdk/v2`, and pins `KILOCODE_CLI_VERSION=7.8.1` +(`cloud@3de933dd services/cloud-agent-next/Dockerfile:6`; +`src/shared/kilo-cli-version.ts:1`; +`wrapper/package.json:11`). + +| Area | Current behavior + evidence | v2 behavior + evidence | Breaks? | Change needed | Lands in | +|---|---|---|---|---|---| +| Startup | Two paths. Legacy wrapper calls `createKilo()`; the SDK spawns `kilo serve --hostname --port` and waits for stdout `kilo server listening` (`origin/main:packages/sdk/js/src/server.ts:62-90`; `origin/main:packages/opencode/src/cli/cmd/serve.ts:24`). Control-plane wrapper spawns `kilo serve --hostname=127.0.0.1 --port=0` and matches `/^kilo server listening on (http:\/\/127\.0\.0\.1:\d+)/` (`cloud@3de933dd wrapper/src/control-plane/kilo-runtime.ts:21,184`; `wrapper/src/main.ts:5-9,54,349-353`). `createKilo` rebinds the client (`wrapper/src/kilo-runtime-lifecycle.ts:118-133`). | No `kilo` binary: built artifacts are `kilo2` (`kilo-v2:packages/kilo-cli/script/build.ts:35`; `script/build-tui.ts:29-41`). Real serve host prints `URL:` / `Password file:`, not a listening banner (`kilo-v2:packages/kilo-cli/src/tui-preview.ts:179-180`), and `serve` accepts no `--hostname`/`--port` (`kilo-v2:packages/kilo-cli/src/commands.ts:262-282`). The headless `kilo2 serve` entry is admission-only and refuses execution (`kilo-v2:packages/kilo-cli/src/index.ts:11-19,52-57`; `src/server.ts:53-57,75,96-100`). | yes | Give v2 a spawnable headless serve with a stable readiness signal and auth, or adapt the wrapper to the v2 CLI/host. | both (v2 contract first, then cloud) | +| Session admission | Wrapper calls `session.create`, `session.get`, `kilocode.sessionImport.session`, then `session.promptAsync` (async) with `parts`, `model.providerID=kilo`, `tools`, `agent` (`cloud@3de933dd wrapper/src/kilo-api.ts:380-384,389-395,407-449,456-459,343-368`). Session IDs are `ses_` (`src/utils/kilo-session-id.ts:26-27`). Wrapper HTTP admission routes `/job/prompt`,`/job/command`,`/job/answer-*` (`wrapper/src/server.ts:1235-1251`); control-plane frames `session.prepare/prompt/abort/answer` (`src/shared/control-plane-protocol.ts:611-669`). | v2 admits a prompt durably: `POST /api/session`, `POST /api/session/:sessionID/prompt` returning a `SessionInbox.User` enqueue, with execution scheduled by `SessionExecution.wake` unless `resume:false` (`kilo-v2:packages/protocol/src/groups/session.ts:170,338`; `packages/core/src/session/session.ts:154-187`; `packages/core/src/session/inbox.ts:169-202,277-336`). There is no `prompt_async`; `session.prompt` is the async admission. v2 has `POST /api/session/import` for v2 transcripts, not the v1 `kilocode.sessionImport.session` shape (`packages/protocol/src/groups/session.ts:189`). | yes | Adapt the wrapper to v2 create/prompt/import routes and payloads; implement/expose the capability handshake so the wrapper can select v1 vs v2 (N9). | both | +| Streaming | Subscribes via `v2Client.global.event` → `GET /global/event` (`cloud@3de933dd wrapper/src/kilo-api.ts:373-377`; `origin/main:packages/sdk/js/src/v2/gen/sdk.gen.ts:1586`), including a raw SSE topology (`wrapper/src/global-feed.ts:236-243`). Depends on events `message.updated`, `message.part.updated`, `session.idle`, `session.status`, `session.error`, `permission.asked`, `question.asked`, `session.turn.open/close`, `session.network.asked` (`wrapper/src/connection.ts:219-221,105-106,1173,1193,1232,1269`; `origin/main:packages/opencode/src/kilocode/session/event.ts`). Worker ingests by entity key `message.updated`→`message/{id}`, `message.part.updated`→`part/{msg}/{id}` (`src/session/ingest-handlers/entity-id.ts:12-27`). | v2 stream is `GET /api/event` SSE, volatile by contract (overflow fails the stream), 15 s heartbeats (`kilo-v2:packages/protocol/src/groups/event.ts:38-55`; `packages/server/src/handlers/event.ts:12-33`). Current events are `session.text.delta`, `session.tool.*`, `session.step.*`, `session.inbox.*`, `session.execution.*`, `session.status`; `session.idle` is deprecated; `message.updated`/`message.part.*` are V1-only (`packages/schema/src/session-event.ts:189,196,234,383,455-509`; `packages/schema/src/session-status-event.ts:35-51`; `packages/schema/src/v1/session.ts:597,613`). | yes | Map every consumed event to the v2 vocabulary (or add a compatibility projection); adapt the worker ingest entity keys to v2 events/messages. | both | +| Tools | Passes a permission map and MCP servers through `KILO_CONFIG_CONTENT` (`cloud@3de933dd src/session-service.ts:1334-1357,1404-1424,1467-1470`); MCP types `{type:local|remote}` (`src/mcp-config.ts:7-21`). Reads tool parts (`part.type==='tool'`) and replies via `permission.reply/list`, `question.reply/reject/list` (`wrapper/src/kilo-api.ts:546-591`); auto-approves or rejects permissions (`wrapper/src/connection.ts:1171-1196`). | v2 registers tools through `Tool.Service` (no authorization in the registry) and enforces in `Permission.Service` with a persisted `permission` SQLite table (`kilo-v2:packages/core/src/tool.ts:157-183,220-271`; `packages/core/src/permission.ts:214-310`; `packages/core/src/permission/sql.ts:7`). MCP is config-driven (`packages/core/src/config/plugin/mcp.ts:39-56`). v2 reads `OPENCODE_CONFIG_CONTENT` (`packages/core/src/config.ts:206-210`) but **not** `KILO_CONFIG_CONTENT` as an input (it is only an internal constant, `packages/kilo-cli/src/skill-policy.ts:12`). | yes | Confirm v2 reads the permission/provider/MCP payload the wrapper actually injects (or adapt the injected config key); adapt permission/question reply calls. | both | +| Credentials | Injects `KILOCODE_TOKEN` (opaque capability redeemed at the outbound proxy), `KILO_AUTH_CONTENT` and writes `.local/share/kilo/auth.json` `{kilo:{type:'api',key}}`; sets `provider.kilo.options.apiKey/kilocodeToken/baseURL` (`cloud@3de933dd src/session-service.ts:1277-1311,814-828,1404-1410`; `wrapper/src/session-bootstrap.ts:286-287,680-688`). v1 reads these (`origin/main:packages/opencode/src/auth/index.ts:61-63`; provider `packages/core/src/plugin/provider/kilo.ts:18-19`). Runtime credential proxy on the worker (`src/server.ts:273,341-344,448`). | v2 has no `KILO_AUTH_CONTENT`, `KILOCODE_TOKEN` or `KILO_API_KEY`/`KILO_ORG_ID` env inputs; credentials are rows in the isolated `credential` table, resolved from the gateway account (`kilo-v2:packages/core/src/credential.ts:99-134`; `packages/core/src/credential/sql.ts:5-14`). `KILO_API_URL` selects the gateway only (`packages/kilo-cli/src/tui-preview.ts:141`). `kilo2 import-v1 --auth --apply [--gateway-server]` can seed isolated credentials from an auth file (`packages/kilo-cli/src/commands.ts:216-238`; `packages/kilo-cli/src/import-v1-config.ts`). | yes | Provide a v2 credential-injection path for hosted/floating consumers (env or import-v1 bootstrap), or adapt the cloud to write v2 credentials. | both | +| Persistence | v1 identity is `kilo`: data `~/.local/share/kilo`, config `~/.config/kilo` (`origin/main:packages/core/src/global.ts:10-40`). Wrapper writes `$XDG_DATA_HOME/kilo/storage/session_share/.json` `{id,ingestPath}` and runs `kilo import ` after downloading `GET /api/session/:id/export` (`cloud@3de933dd wrapper/src/restore-session.ts:169-195,1069,1196-1207`). | v2 identity is `kilo2`: `~/.local/share/kilo2/...`, DB `kilo2.db` (`kilo-v2:packages/kilo-cli/src/paths.ts:6,26`), and it refuses the v1 store (`packages/kilo-cli/src/storage.ts:19-22`). v2 never reads `session_share/.json`; v2 `import` decodes `SessionTransfer.Data {info,messages}` from a v2 JSON transcript and rejects URLs (`packages/kilo-cli/src/commands.ts:353-370`; `packages/schema/src/session-transfer.ts:5-9`). | yes | Migrate restore/import to the v2 transcript shape and v2 storage location, or add a compatibility importer for the v1/session-ingest export. | both | +| Cancellation | `v2Client.session.abort` → `POST /session/:id/abort` (`cloud@3de933dd wrapper/src/kilo-api.ts:468-472`; `origin/main:packages/sdk/js/src/v2/gen/sdk.gen.ts:4872`); wrapper `/job/abort` sends an `interrupted` ingest event (`wrapper/src/server.ts:1247,831-845`); control-plane `session.abort` (`wrapper/src/control-plane/turn.ts:859-866`); worker `markAsInterrupted`/`interruptExecution` (`src/router/handlers/session-management.ts:267-272`). | v2 route is `POST /api/session/:sessionID/interrupt`, returning `{interrupted:boolean}`; interruption targets the process-local ownership chain, idle is a no-op, unknown sessions are rejected (`kilo-v2:packages/protocol/src/groups/session.ts:665-685`; `packages/core/src/session/execution.ts:27-33,148-165`; `packages/core/src/session/run-coordinator.ts:142-162`). | yes | Adapt to the v2 interrupt route/semantics; keep the `interrupted` ingest signal. | both | + +**Disposition: v2 client adaptation + runtime packaging change (both).** +cloud-agent-next is pinned at 7.8.1, so it keeps working until its own pin bump; +its cutover is independent with its own go/no-go (#14425). Nothing here is +"no change": binary, serve contract, routes, events, credentials, persistence and +cancellation all differ. + +## 2. auto-routing-benchmark + +Container installs `@kilocode/cli@latest` unpinned (`cloud@3de933dd +services/auto-routing-benchmark/container/Dockerfile:9-16`) and spawns `kilo run +--format json --auto -m kilo/ [--variant ] ` (`container/server.mjs:55-79`). + +| Area | Current behavior + evidence | v2 behavior + evidence | Breaks? | Change needed | Lands in | +|---|---|---|---|---|---| +| Startup | Image build runs `npm install -g @kilocode/cli@latest`; worker spawns `kilo` in a temp cwd (`cloud@3de933dd services/auto-routing-benchmark/container/Dockerfile:12`; `container/server.mjs:46,55-79`). | v2 `@kilocode/cli` is private with no `bin` and builds `kilo2` (`kilo-v2:packages/kilo-cli/package.json:2-4`; `script/build.ts:35`). No global `kilo` binary is produced. | yes | Publish v2 CLI with an installable `bin` (or a documented replacement binary); adapt spawn. | both | +| Session admission | One fresh `kilo run` per case; no session ids/continuation; runs serialized because the CLI store is not concurrency-safe (`container/server.mjs:37-41`; `src/run.ts:1258`). | v2 `run` accepts positional prompt, `-s/--session`, `-m/--model`, `--agent`, `--auto`, `--file`, `--directory`, `--format text|json` (`kilo-v2:packages/kilo-cli/src/commands.ts:283-352`). No `--variant` option. | yes | Drop/replace `--variant`, or add it to v2 with a defined meaning. | both | +| Streaming | Parses one JSON event per stdout line: completed `type:text` parts and `step_finish` cost (`cloud@3de933dd src/kilo-events.ts:52-85`; emitted by `origin/main:packages/opencode/src/cli/cmd/run.ts:876-891`). | v2 `run --format json` prints one completed `{sessionID,text}` object, explicitly "not a streaming event log" (`kilo-v2:packages/kilo-cli/src/tui-preview.ts:231`; help `packages/kilo-cli/src/commands.ts:110`). The event parser gets no `type` field and returns empty text. | yes | Either restore a streaming JSON mode in v2, or adapt the parser to the single-object result. | both | +| Tools | None; `--auto` only. | `--auto` exists; no tool/permission flags passed. No break. | no | none | — | +| Credentials | Injects `KILO_AUTH_CONTENT {kilo:{type:api,key,organizationId}}`, `KILO_API_URL`, `KILO_ORG_ID`, `KILO_DISABLE_SESSION_INGEST=1` (`cloud@3de933dd container/server.mjs:66-79`). v1 reads them (`origin/main:packages/core/src/plugin/provider/kilo.ts:18-19`; `packages/opencode/src/auth/index.ts:61`). | v2 reads none of `KILO_AUTH_CONTENT`/`KILO_ORG_ID`/`KILO_DISABLE_SESSION_INGEST`; only `KILO_API_URL` as the gateway (`kilo-v2:packages/kilo-cli/src/tui-preview.ts:141`). Credentials must be seeded (e.g. `import-v1 --auth`). | yes | Provide a v2 credential env/bootstrap for headless containers, or seed credentials before `run`. | both | +| Persistence | Only a temp cwd, removed after each run (`cloud@3de933dd container/server.mjs:46,119`); no CLI home/config read. | v2 uses isolated `kilo2` paths (`kilo-v2:packages/kilo-cli/src/paths.ts:6,26`); a fresh temp cwd still works. | no | none (v2 store is created in-container). | — | +| Cancellation | 180 s timeout, kills the whole process group (`cloud@3de933dd container/server.mjs:59-63,84-95`; `src/cli-runner.ts:15`). | v2 `run` installs abort handling and interrupts its session (`kilo-v2:packages/kilo-cli/src/run.ts:78,269-270`); SIGKILL of the group still applies. | no | none | — | + +**Disposition: runtime packaging change + v2 client adaptation (both).** It +floats `@kilocode/cli@latest`, so it is exposed the instant v2 is published to +that channel. v2 must be ready (binary, argv, JSON, credentials) **before** v2 +takes `latest`, and the cloud must adapt in the same window. + +## 3. MCP catalog generator (CI) + +Workflow installs the CLI unpinned (`npm install -g @kilocode/cli`) and shells +out to `kilo run` (`cloud@3de933dd .github/workflows/kilo-mcp-catalog.yml:112-115,368-371`; +`apps/web/src/scripts/mcp-catalog/catalog.ts:721-732`). + +| Area | Current behavior + evidence | v2 behavior + evidence | Breaks? | Change needed | Lands in | +|---|---|---|---|---|---| +| Startup | `npm install -g @kilocode/cli` in both jobs (`cloud@3de933dd .github/workflows/kilo-mcp-catalog.yml:115,371`). | v2 package is private/no-`bin`, artifact `kilo2` (`kilo-v2:packages/kilo-cli/package.json:2-4`; `script/build.ts:35`). | yes | Same as benchmark: publish installable v2 CLI. | both | +| Session admission | One independent `spawnSync('kilo',['run','--model',M,'--variant',V,'--format','json'])`, prompt on stdin, cwd `tmpdir()` (`cloud@3de933dd apps/web/src/scripts/mcp-catalog/catalog.ts:721-732`). | v2 `run` has `-m/--model` and `--format` but **no `--variant`** (`kilo-v2:packages/kilo-cli/src/commands.ts:283-352`). | yes | Drop/replace `--variant`, or add it to v2. | both | +| Streaming | Parses completed `type:text` parts from stdout lines (`cloud@3de933dd catalog.ts:684-707,758`). | v2 emits a single `{sessionID,text}` object for `--format json` (`kilo-v2:packages/kilo-cli/src/tui-preview.ts:231`). | yes | Restore streaming JSON in v2, or adapt the parser. | both | +| Tools | None. | None passed; no break. | no | none | — | +| Credentials | Mints a service token, exports `KILO_API_KEY`/`KILO_ORG_ID`; otherwise ambient `kilo auth login` (`cloud@3de933dd .github/workflows/kilo-mcp-catalog.yml:155-173,397-415`). v1 reads `KILO_API_KEY`/`KILO_ORG_ID` (`origin/main:packages/core/src/plugin/provider/kilo.ts:18-19`). | v2 reads neither env (`kilo-v2`: no `KILO_API_KEY`/`KILO_ORG_ID` in `packages/kilo-cli/src`, `packages/kilo-gateway/src`). | yes | v2 credential env/bootstrap, or seed credentials via `import-v1`. | both | +| Persistence | Runs in `tmpdir()` so project config is not loaded (`cloud@3de933dd catalog.ts:713-715,728`); writes only `catalog.json`. | Isolated `kilo2` store in the runner home; temp cwd still avoids project config. | no | none | — | +| Cancellation | 10 min `spawnSync` timeout; 20 min job timeout (`cloud@3de933dd catalog.ts:84,729`; `kilo-mcp-catalog.yml:66,324`). | v2 `run` handles abort/interrupt; process kill still applies. | no | none | — | + +**Disposition: runtime packaging change + v2 client adaptation (both), G1.** +Same floating exposure as the benchmark; a required `catalog (PR)` check will +fail on a v2 `latest` until both land. + +## 4. session-ingest + +No runtime executor; contract-coupled to the CLI's session shipping and to +`kilo import` (#14425, owner-confirmed). The wire contract is +`@kilocode/session-ingest-contracts` and the server's `SessionItemSchema` union +(`cloud@3de933dd services/session-ingest/src/types/session-sync.ts:12-99`; +`packages/session-ingest-contracts/src/rpc-contract.ts:4`). + +| Area | Current behavior + evidence | v2 behavior + evidence | Breaks? | Change needed | Lands in | +|---|---|---|---|---|---| +| Startup | CLI-side producer is `KiloSessions`; server base is `KILO_SESSION_INGEST_URL` default `https://ingest.kilosessions.ai` (`origin/main:packages/opencode/src/kilo-sessions/kilo-sessions.ts:253,987`). Registration `POST /api/session` returns `{id,ingestPath}` (`cloud@3de933dd services/session-ingest/src/routes/api.ts:215,279-285`). | v2 producer is `@kilocode/gateway` `registerSessions` (`kilo-v2:packages/kilo-gateway/src/session.ts:104-115`), default base `KILO_SESSION_INGEST_URL ?? https://ingest.kilosessions.ai` (`:402`), registration `POST /api/session` (`:267-276`). | partial | Keep the same base/registration; map the reduced v2 producer. | this repo | +| Session admission | Continuous ingest: register once, then stream `message`/`part`/`session_diff`/`model`/`session_open`/`session_close`/`session_status`/`agent_notification`/`session_pr_link` items (`cloud@3de933dd session-sync.ts:12-99`; emitted by v1 `kilo-sessions.ts:718,812,929,1745`). | v2 only uploads on an explicit `share` RPC, as a reduced batch `kilo_meta` + `session` + self-contained `message` items (`kilo-v2:packages/kilo-gateway/src/session.ts:64-97,279-300`). No `part`/`session_diff`/`session_open`/`session_close`/`session_status`/`agent_notification`/`session_pr_link`; no continuous stream. | yes | Decide whether session-ingest must consume v2 self-contained messages, and add a continuous producer (or an explicit-sync equivalent) for hosted sessions. | both (v2 producer first; cloud validates) | +| Streaming | WS frame `{type:'ingest',sessionId,data:[…]}` and HTTP `POST /api/session/:id/ingest?v=N` with root `{data:[…]}` (`cloud@3de933dd services/session-ingest/src/types/ws-protocol.ts:5-9`; `src/routes/api.ts:431`; limits `util/ingest-limits.ts:11-12`). | v2 posts `{data: batch}` to `?v=2` over HTTP only (`kilo-v2:packages/kilo-gateway/src/session.ts:279-300,409-411`); no WS producer. | partial | Confirm reduced HTTP batch is accepted; WS continuity not provided. | this repo/cloud | +| Tools | n/a (no tools in the payload). | n/a. | no | none | — | +| Credentials | Bearer Kilo JWT via `KILOCODE_TOKEN`/auth.json (`cloud@3de933dd services/session-ingest/src/middleware/kilo-jwt-auth.ts:52-101`; wrapper writes `.local/share/kilo/auth.json`). | v2 resolves the active Kilo credential from the isolated store (`kilo-v2:packages/kilo-gateway/src/session.ts:386-397`); bearer still used on the wire. | partial | Confirm v2 credential resolution yields the same JWT audience; seed credentials for headless. | this repo/cloud | +| Persistence | Wrapper bridges by writing `$XDG_DATA_HOME/kilo/storage/session_share/.json` `{id,ingestPath}`; v1 CLI reads it (`cloud@3de933dd wrapper/src/restore-session.ts:169-195`; `origin/main:.../kilo-sessions.ts:1562,1603`). Restore downloads `GET /api/session/:id/export` then `kilo import ` (`wrapper/src/restore-session.ts:1069,1196-1207`). | v2 stores bootstrap under host storage key `session:`, not the `session_share` file (`kilo-v2:packages/kilo-gateway/src/session.ts:210-225,466-468`); v2 `import` accepts only a local v2 `SessionTransfer.Data` transcript and rejects URLs (`packages/kilo-cli/src/commands.ts:353-370`; `packages/schema/src/session-transfer.ts:5-9`). | yes | Provide a v1/session-ingest-export importer (or a v2 export/import bridge) so restore keeps working. | both | +| Cancellation | n/a; `session_close` reason `completed|error|interrupted` is data, not a cancel call (`cloud@3de933dd session-sync.ts:53-55`). | n/a; v2 has no `session_close` item. | partial | Carry terminal outcome another way if consumers rely on `session_close`. | this repo | + +**Disposition: v2 contract change (producer) with cloud coordination.** The v2 +producer exists but is reduced and explicit-share-only; continuous session +shipping and the `session_share`/import bridge are absent. session-ingest's +server code likely needs no schema change (the union is permissive), but its +message projection and the `session_close`/diff/part semantics must be validated +with v2 self-contained messages. + +## Disposition summary + +| Consumer | Disposition | Lands in | Pinning (#14425) | Recommended gate (#14425) | Evidence | +|---|---|---|---|---|---| +| cloud-agent-next | v2 client adaptation + runtime packaging change | both (v2 contract/packaging first) | pinned 7.8.1; independent cutover | G1 for adaptation; independent go/no-go | §1; `wrapper/src/control-plane/kilo-runtime.ts:21,184`; `packages/protocol/src/groups/session.ts:170,338,666` | +| auto-routing-benchmark | runtime packaging change + v2 client adaptation | both (v2 packaging before v2 `latest`) | floats `@kilocode/cli@latest` | G1 | §2; `container/server.mjs:55-79`; `src/kilo-events.ts:52-85`; `packages/kilo-cli/package.json:2-4` | +| MCP catalog generator (CI) | runtime packaging change + v2 client adaptation | both | unpinned `npm install -g @kilocode/cli` | G1 | §3; `catalog.ts:721-732`; `kilo-mcp-catalog.yml:115,371` | +| session-ingest | v2 contract change (producer) + cloud coordination | both (v2 producer first) | contract-coupled, no runtime | G1 (producer-side) | §4; `packages/kilo-gateway/src/session.ts:64-97,279-300`; `session-sync.ts:12-99` | + +No in-scope consumer is "no change". Every disposition includes a v2 +contract/packaging change in this repo; cloud-agent-next additionally owns a +client adaptation, and the two floating consumers (benchmark, MCP catalog) must +be adapted or protected before v2 is published to the channels they track. + +## Gaps for #14427 + +Ordering rule: v2 contract/packaging items land in this repo first; cloud client +adaptations follow, except the floating consumers, whose cloud adaptations must +land before v2 replaces `@kilocode/cli@latest`. + +### G1 — Spawnable v2 headless serve contract +- Repo: this repo. +- Touches: `packages/kilo-cli/src/index.ts`, `packages/kilo-cli/src/commands.ts`, `packages/kilo-cli/src/tui-preview.ts`, `packages/kilo-cli/src/interactive-server.ts`, `packages/server/src/{process.ts,routes.ts,fetch.ts,handlers/health.ts}`, `packages/protocol/src/groups/{health.ts,server.ts}`. +- Why: cloud-agent-next and the v1 SDK both need a documented argv, readiness signal and auth for a headless server; v2 prints `URL:`/`Password file:` and defaults to Basic auth username `opencode` (`kilo-v2:packages/kilo-cli/src/tui-preview.ts:179-180`; `interactive-server.ts:250-253`). +- Dependencies: G2 (binary), G3 (handshake). +- Cloud coordination: yes — cloud-agent-next wrapper serves as the reference consumer. + +### G2 — Publishable v2 CLI binary and package metadata +- Repo: this repo. +- Touches: `packages/kilo-cli/package.json` (add `bin`, drop `private` for the preview/customer channel), `packages/kilo-cli/script/{build.ts,build-tui.ts}`. +- Why: v2 builds `kilo2` and declares no `bin`; `npm install -g @kilocode/cli` yields no `kilo` command (`kilo-v2:packages/kilo-cli/package.json:2-4`; `script/build.ts:35`). +- Dependencies: none; blocks every consumer. +- Cloud coordination: yes — release-channel decision (v2 under the same `latest` tag or a new channel, per #14425 unknowns). + +### G3 — Version/capability handshake (N9) +- Repo: this repo. +- Touches: `packages/protocol/src/groups/health.ts`, `packages/server/src/handlers/health.ts`, `packages/client/src/service-version.ts`, `packages/kilo-client/src`. +- Why: v2 health returns only `{healthy,version,pid}`; no capability/handshake surface exists, so a client cannot safely select v1 vs v2 routes in one session (`kilo-v2:packages/protocol/src/groups/health.ts:5-10`; `packages/client/src/service-version.ts:3-8`). Record what each consumer needs: cloud-agent-next needs backend generation + feature flags; benchmark/MCP catalog need an argv/output contract marker. +- Dependencies: G1. +- Cloud coordination: yes. + +### G4 — Consumer-facing v2 SDK surface +- Repo: this repo. +- Touches: `packages/sdk` (currently `@opencode-ai/sdk`), `packages/kilo-client` (`@kilocode/client`), `packages/client`. +- Why: the cloud imports `@kilocode/sdk` and `@kilocode/sdk/v2` (`cloud@3de933dd wrapper/src/main.ts:18`; `kilo-api.ts:9-16`); v2 publishes no `@kilocode/sdk` (only `@opencode-ai/sdk` at `kilo-v2:packages/sdk/package.json:3`). Decide whether to publish a v2 `@kilocode/sdk` shim or migrate the cloud to `@kilocode/client`. +- Dependencies: G3 for handshake; G5 consumes it. +- Cloud coordination: yes. + +### G5 — cloud-agent-next client adaptation to v2 routes/events +- Repo: cloud. +- Touches: `services/cloud-agent-next/wrapper/src/{main.ts,kilo-api.ts,connection.ts,global-feed.ts}`, `wrapper/src/control-plane/{kilo-runtime.ts,turn.ts}`, `src/session/ingest-handlers/entity-id.ts`, `src/router/handlers/session-management.ts`. +- Why: routes (`/api/session`, `/api/session/:id/prompt`, `/api/event`, `/api/session/:id/interrupt`), event vocabulary (`session.text.delta`, `session.tool.*`, `session.inbox.*`, `session.execution.*`) and payloads all differ (§1). +- Dependencies: G1, G3, G4. +- Cloud coordination: yes — cloud team owns the change. + +### G6 — v2 credential injection for hosted and floating consumers +- Repo: this repo (runtime), cloud (usage). +- Touches: `packages/kilo-cli/src/{credential-import.ts,import-v1-config.ts,commands.ts,tui-preview.ts}`, `packages/core/src/credential.ts`, `packages/kilo-gateway/src/account.ts`. +- Why: cloud-agent-next injects `KILOCODE_TOKEN`/`KILO_AUTH_CONTENT`/auth.json and the benchmark/MCP CI inject `KILO_AUTH_CONTENT` or `KILO_API_KEY`/`KILO_ORG_ID`; v2 reads none of these (`cloud@3de933dd src/session-service.ts:1277-1289`; `container/server.mjs:66-79`; `kilo-mcp-catalog.yml:155-173`). Options: an explicit v2 env, or seed isolated credentials via `import-v1 --auth` (`kilo-v2:packages/kilo-cli/src/commands.ts:216-238`). +- Dependencies: G2. +- Cloud coordination: yes. + +### G7 — v2 `run` argv/output compatibility for headless consumers +- Repo: this repo, cloud. +- Touches: `packages/kilo-cli/src/{commands.ts,run.ts,tui-preview.ts}`, cloud `services/auto-routing-benchmark/{container/server.mjs,src/kilo-events.ts}`, `apps/web/src/scripts/mcp-catalog/catalog.ts`. +- Why: consumers pass `--variant` and parse a streaming JSON event log; v2 has no `--variant` and prints one `{sessionID,text}` object (`kilo-v2:packages/kilo-cli/src/commands.ts:283-352`; `tui-preview.ts:231`). Decide whether v2 preserves `--variant` and a streaming mode or the cloud adapts. +- Dependencies: G2; must precede v2 reaching `latest`. +- Cloud coordination: yes. + +### G8 — v2 restore/import compatibility +- Repo: this repo, cloud. +- Touches: `packages/kilo-cli/src/{commands.ts,import-v1-session.ts}`, `packages/schema/src/session-transfer.ts`, `packages/kilo-gateway/src/session.ts`, cloud `services/cloud-agent-next/wrapper/src/restore-session.ts`. +- Why: cloud-agent-next writes `session_share/.json` and runs `kilo import ` from the session-ingest export; v2 ignores that file and `import` accepts only a local v2 transcript (`cloud@3de933dd wrapper/src/restore-session.ts:169-195,1069,1196-1207`; `kilo-v2:packages/kilo-cli/src/commands.ts:353-370`). +- Dependencies: G9; cross-repo order: v2 importer first, cloud restore second. +- Cloud coordination: yes. + +### G9 — session-ingest producer continuity +- Repo: this repo (producer), cloud (validation). +- Touches: `packages/kilo-gateway/src/session.ts`, `packages/schema/src/session-event.ts`, `packages/core/src/session/*`. +- Why: v2 emits only `kilo_meta`/`session`/`message` on explicit share, with no continuous stream, no `part`/`session_diff`/`session_open`/`session_close`/`session_status`/`agent_notification`/`session_pr_link`, and stores bootstrap under `session:` (`kilo-v2:packages/kilo-gateway/src/session.ts:64-97,279-300`; session-ingest union `cloud@3de933dd services/session-ingest/src/types/session-sync.ts:12-99`). +- Dependencies: G3; cloud validates its message projection against v2 self-contained messages. +- Cloud coordination: yes. + +### G10 — Event/entity projection mapping for cloud ingest +- Repo: this repo, cloud. +- Touches: cloud `services/cloud-agent-next/src/session/ingest-handlers/*`, `src/websocket/ingest.ts`; v2 `packages/schema/src/session-event.ts`. +- Why: the worker keys entities on `message.updated`/`message.part.updated` (`cloud@3de933dd src/session/ingest-handlers/entity-id.ts:12-27`); v2 uses `session.message.content.updated`/`session.step.*`/`session.tool.*` and self-contained messages. +- Dependencies: G5. +- Cloud coordination: yes. + +## Proposed progress-plan row changes (for N7; do not apply here) + +File: `migration-tracking/plans/kilo-opencode-v2-plan-progress.md`, section +"Remote, sharing and distribution". + +1. Add a row: + `| Cloud agent consumer contracts | Kilo CLI / Gateway | 4, 6 | Partial | Contract mapping and dispositions complete; implementation gaps open in #14427 | Four in-scope consumers mapped across startup, admission, streaming, tools, credentials, persistence, cancellation. cloud-agent-next needs a v2 client adaptation plus packaging; auto-routing-benchmark and MCP catalog CI float and break on v2 latest; session-ingest has a reduced explicit-share-only v2 producer. See technical-notes/baseline/cloud-agent-contracts.md. |` +2. Amend the #14425-added "Cloud agent runtime consumers (hosted)" row's + remaining scope to say contract mapping is complete and point at + `technical-notes/baseline/cloud-agent-contracts.md`. +3. Amend the `Cloud CLI client` row to note that local `kilo cloud` fixtures and + the `/remote` relay do not establish cloud-hosted consumer compatibility + (already asserted by #14425; keep the two baselines linked). + +Evidence links for the epic: #14425 (inventory), #14426 (contracts, this file), +#14427 (adaptation), #14428 (validation). + +## Open questions for the cloud owners + +1. Release channel: does v2 replace `@kilocode/cli@latest` (breaking the two + floating consumers concurrently) or ship under a new tag/name? This decides + whether the cloud adaptations in G6/G7 must land before v2 is published. +2. Binary contract: will the v2 CLI keep a `kilo` command and a + `kilo server listening` style readiness line, or is adoption of the `kilo2` + binary and `URL:`/`Password file:` output acceptable (G1/G2)? +3. Credentials: is `kilo2 import-v1 --auth --apply` an acceptable + migration for hosted/floating consumers, or does v2 need a first-class env + injection path for `KILOCODE_TOKEN`/`KILO_API_KEY` (G6)? +4. Session-ingest semantics: can session-ingest consume v2 self-contained + `message` items without `part` rows, and how should `session_close`/diff/ + status/notifications be carried (G9)? +5. Restore: is there a session-ingest export format both v1 and v2 can import, + or does the restore path need a v1→v2 transcript converter (G8)? +6. Deployed versions: #14425 recorded that exact dev/prod versions are + unverified; confirm the currently deployed `KILOCODE_CLI_VERSION` and whether + any floating image already resolves a v2 `latest`. From ceb1794d23518766447d00619a2251a68a8f2803 Mon Sep 17 00:00:00 2001 From: Fabio Pliger Date: Wed, 7 Oct 2026 15:19:21 -0500 Subject: [PATCH 2/2] docs(kilo): correct cloud consumer contracts against upstream v2 (#14426) --- .../baseline/cloud-agent-contracts.md | 276 +++++++++++++----- 1 file changed, 204 insertions(+), 72 deletions(-) diff --git a/migration-tracking/technical-notes/baseline/cloud-agent-contracts.md b/migration-tracking/technical-notes/baseline/cloud-agent-contracts.md index 90361590d696..71318b3d2218 100644 --- a/migration-tracking/technical-notes/baseline/cloud-agent-contracts.md +++ b/migration-tracking/technical-notes/baseline/cloud-agent-contracts.md @@ -7,10 +7,11 @@ Discovery only: no source changes in any repository. | Item | Value | |---|---| -| Date | 2026-10-05 | +| Date | 2026-10-07 (amended; first pass 2026-10-05) | | Cloud repo | `Kilo-Org/cloud` at `3de933dd85070e9d15c41d8ff3af6c91ca856fd4` (read-only clone `/Users/fpliger/dev/kiloverse/cloud`) | | This repo, `origin/main` (v1) | `62f674d4a91969ac077e4a7b6ceeb8a99de80e47` | | This repo, `kilo-v2` (v2) | `782263326df5be50081517ddedd937f1080a89cf` | +| Upstream opencode, `origin/v2` | `f20f5b68ee318721c453fb19d20e46d2b9225bc0` (clone `/Users/fpliger/dev/opencode`) | | #14425 inventory | `migration-tracking/technical-notes/baseline/cloud-agent-consumers.md` | #14425 assessed the cloud repo at the same SHA; its this-repo SHAs were older @@ -18,11 +19,51 @@ Discovery only: no source changes in any repository. above). Owners, pinning and recommended gates below are reused from #14425 and not re-derived. +## How to read this document + +This is the second pass. The first pass (2026-10-05) mapped the cloud consumers +against **kilo2's current surface** and framed most problems as "v2 must build +this." That framing was incomplete: `packages/kilo-cli` on `kilo-v2` is a +**thin, diverged fork** of upstream opencode's v2 `packages/cli`, and upstream +`origin/v2` already contains much of what the consumers need (a headless +`serve` with host/port, a streaming `run --format json`, `session import` from +a file or URL, and a v1→v2 session-history migration). Where kilo2 differs from +upstream, the divergence is called out explicitly and the fix is usually "sync +kilo-v2 forward," not "design something new." + +A second correction: the terms are overloaded. The **v1 SDK's `v2` subpath** is +the second-generation SDK API that talks to the **v1** runtime over v1 routes; +it is unrelated to the **v2 runtime** on `kilo-v2`. Citations say +`@kilocode/sdk/v2` when they mean the former and `kilo-v2:` when they mean +the latter. + +### Owner decisions recorded (2026-10-07) + +1. **Release channel:** v2 ships on a new pre-release channel called **`next`**; + `@kilocode/cli@latest` stays on the v1 line for the migration. (No `next` + channel exists yet; see G2.) +2. **Binary contract:** keep the **`kilo2`** binary for the duration of the + migration, then switch later if warranted. Cloud consumers adapt to `kilo2`. +3. **Credentials:** if v1 and v2 do not share auth, an import tool for users + (which includes auth) is acceptable. They do not share auth automatically, + but both upstream and Kilo already ship import paths (§1, §5, G6). +4. **Session migration goal:** migrate existing v1 users' sessions to v2; use + what upstream opencode has. Confirmed: upstream `origin/v2` has a v1→v2 + session-history migration; kilo-v2 already vendors an older copy (§5, G11). +5. **Restore:** resolve from both kilo-v2 and upstream v2 (§4, G8). +6. **Deployed versions:** no v2 consumers are deployed and versions are pinned, + so this is not an active concern; the `next` channel removes the residual + risk from the two floating consumers. + +These decisions supersede the corresponding "open questions" from the first +pass; the remaining open questions are at the end. + ## Citation conventions - `cloud@3de933dd :` — the cloud repo at the SHA above. - `origin/main::` — v1 runtime surface in this repo at the SHA above. -- `kilo-v2::` — v2 runtime surface in this repo at the SHA above. +- `kilo-v2::` — v2 runtime surface (kilo2 fork) at the SHA above. +- `origin/v2::` — upstream opencode v2 surface at the SHA above. - A bare `#14425` reference means the inventory document, not a GitHub issue. ## Scope and boundaries @@ -40,7 +81,9 @@ HTTP/WS contract. The cloud repo pins the v1 CLI/SDK at **7.8.1**. v2 publishes the CLI package as `@kilocode/cli` too (`kilo-v2:packages/kilo-cli/package.json:2`), but it is `private` with **no `bin`** and version `0.0.0-internal`, so "v2 reaches a -consumer" is a package/channel and packaging question, not a name question. +consumer" is a packaging + channel question, not a name question. Because v2 +goes to `next` (not `latest`), the two floating consumers keep resolving the v1 +CLI until they opt in. ## 1. cloud-agent-next @@ -52,12 +95,12 @@ Primary hosted runtime; wrapper spawns `kilo serve`, imports `@kilocode/sdk` and | Area | Current behavior + evidence | v2 behavior + evidence | Breaks? | Change needed | Lands in | |---|---|---|---|---|---| -| Startup | Two paths. Legacy wrapper calls `createKilo()`; the SDK spawns `kilo serve --hostname --port` and waits for stdout `kilo server listening` (`origin/main:packages/sdk/js/src/server.ts:62-90`; `origin/main:packages/opencode/src/cli/cmd/serve.ts:24`). Control-plane wrapper spawns `kilo serve --hostname=127.0.0.1 --port=0` and matches `/^kilo server listening on (http:\/\/127\.0\.0\.1:\d+)/` (`cloud@3de933dd wrapper/src/control-plane/kilo-runtime.ts:21,184`; `wrapper/src/main.ts:5-9,54,349-353`). `createKilo` rebinds the client (`wrapper/src/kilo-runtime-lifecycle.ts:118-133`). | No `kilo` binary: built artifacts are `kilo2` (`kilo-v2:packages/kilo-cli/script/build.ts:35`; `script/build-tui.ts:29-41`). Real serve host prints `URL:` / `Password file:`, not a listening banner (`kilo-v2:packages/kilo-cli/src/tui-preview.ts:179-180`), and `serve` accepts no `--hostname`/`--port` (`kilo-v2:packages/kilo-cli/src/commands.ts:262-282`). The headless `kilo2 serve` entry is admission-only and refuses execution (`kilo-v2:packages/kilo-cli/src/index.ts:11-19,52-57`; `src/server.ts:53-57,75,96-100`). | yes | Give v2 a spawnable headless serve with a stable readiness signal and auth, or adapt the wrapper to the v2 CLI/host. | both (v2 contract first, then cloud) | -| Session admission | Wrapper calls `session.create`, `session.get`, `kilocode.sessionImport.session`, then `session.promptAsync` (async) with `parts`, `model.providerID=kilo`, `tools`, `agent` (`cloud@3de933dd wrapper/src/kilo-api.ts:380-384,389-395,407-449,456-459,343-368`). Session IDs are `ses_` (`src/utils/kilo-session-id.ts:26-27`). Wrapper HTTP admission routes `/job/prompt`,`/job/command`,`/job/answer-*` (`wrapper/src/server.ts:1235-1251`); control-plane frames `session.prepare/prompt/abort/answer` (`src/shared/control-plane-protocol.ts:611-669`). | v2 admits a prompt durably: `POST /api/session`, `POST /api/session/:sessionID/prompt` returning a `SessionInbox.User` enqueue, with execution scheduled by `SessionExecution.wake` unless `resume:false` (`kilo-v2:packages/protocol/src/groups/session.ts:170,338`; `packages/core/src/session/session.ts:154-187`; `packages/core/src/session/inbox.ts:169-202,277-336`). There is no `prompt_async`; `session.prompt` is the async admission. v2 has `POST /api/session/import` for v2 transcripts, not the v1 `kilocode.sessionImport.session` shape (`packages/protocol/src/groups/session.ts:189`). | yes | Adapt the wrapper to v2 create/prompt/import routes and payloads; implement/expose the capability handshake so the wrapper can select v1 vs v2 (N9). | both | +| Startup | Two paths. Legacy wrapper calls `createKilo()`; the SDK spawns `kilo serve --hostname --port` and waits for stdout `kilo server listening` (`origin/main:packages/sdk/js/src/server.ts:62-90`; `origin/main:packages/opencode/src/cli/cmd/serve.ts:24`). Control-plane wrapper spawns `kilo serve --hostname=127.0.0.1 --port=0` and matches `/^kilo server listening on (http:\/\/127\.0\.0\.1:\d+)/` (`cloud@3de933dd wrapper/src/control-plane/kilo-runtime.ts:21,184`; `wrapper/src/main.ts:5-9,54,349-353`). | Built artifact is `kilo2` (Kilo decision; `kilo-v2:packages/kilo-cli/script/build.ts:35`; `script/build-tui.ts:29-41`), so the wrapper spawns `kilo2` and readiness must change: the interactive host prints `URL:` / `Password file:`, not a listening banner (`kilo-v2:packages/kilo-cli/src/tui-preview.ts:179-180`). kilo2's `serve` currently takes no `--hostname`/`--port` (`packages/kilo-cli/src/commands.ts:262-282`) and the headless `src/index.ts serve` is admission-only (`packages/kilo-cli/src/index.ts:11-19,52-57`). **Upstream already has the missing shape:** `serve --hostname --port --cors --service --stdio` (`origin/v2:packages/cli/src/commands/commands.ts`), so the fix is to align kilo2's serve with upstream, not invent one. | yes | Align kilo2's serve with upstream v2 (host/port, startup banner/readiness, auth) or define an explicit headless-spawn contract; cloud switches spawn to `kilo2` and the new banner/auth. | both (v2 contract first, then cloud) | +| Session admission | Wrapper calls `session.create`, `session.get`, `kilocode.sessionImport.session`, then `session.promptAsync` (async) with `parts`, `model.providerID=kilo`, `tools`, `agent` (`cloud@3de933dd wrapper/src/kilo-api.ts:380-384,389-395,407-449,456-459,343-368`). Session IDs are `ses_` (`src/utils/kilo-session-id.ts:26-27`). Wrapper HTTP admission routes `/job/prompt`,`/job/command`,`/job/answer-*` (`wrapper/src/server.ts:1235-1251`); control-plane frames `session.prepare/prompt/abort/answer` (`src/shared/control-plane-protocol.ts:611-669`). | v2 admits a prompt durably: `POST /api/session`, `POST /api/session/:sessionID/prompt` returning a `SessionInbox.User` enqueue, with execution scheduled by `SessionExecution.wake` unless `resume:false` (`kilo-v2:packages/protocol/src/groups/session.ts:170,338`; `packages/core/src/session/session.ts:154-187`; `packages/core/src/session/inbox.ts:169-202,277-336`). There is no `prompt_async`; `session.prompt` is the async admission. v2 has `POST /api/session/import` for v2 transcripts, not the v1 `kilocode.sessionImport.session` shape (`packages/protocol/src/groups/session.ts:189`). | yes | Adapt the wrapper to v2 create/prompt/import routes and payloads; expose the capability handshake so the wrapper can select v1 vs v2 (N9). | both | | Streaming | Subscribes via `v2Client.global.event` → `GET /global/event` (`cloud@3de933dd wrapper/src/kilo-api.ts:373-377`; `origin/main:packages/sdk/js/src/v2/gen/sdk.gen.ts:1586`), including a raw SSE topology (`wrapper/src/global-feed.ts:236-243`). Depends on events `message.updated`, `message.part.updated`, `session.idle`, `session.status`, `session.error`, `permission.asked`, `question.asked`, `session.turn.open/close`, `session.network.asked` (`wrapper/src/connection.ts:219-221,105-106,1173,1193,1232,1269`; `origin/main:packages/opencode/src/kilocode/session/event.ts`). Worker ingests by entity key `message.updated`→`message/{id}`, `message.part.updated`→`part/{msg}/{id}` (`src/session/ingest-handlers/entity-id.ts:12-27`). | v2 stream is `GET /api/event` SSE, volatile by contract (overflow fails the stream), 15 s heartbeats (`kilo-v2:packages/protocol/src/groups/event.ts:38-55`; `packages/server/src/handlers/event.ts:12-33`). Current events are `session.text.delta`, `session.tool.*`, `session.step.*`, `session.inbox.*`, `session.execution.*`, `session.status`; `session.idle` is deprecated; `message.updated`/`message.part.*` are V1-only (`packages/schema/src/session-event.ts:189,196,234,383,455-509`; `packages/schema/src/session-status-event.ts:35-51`; `packages/schema/src/v1/session.ts:597,613`). | yes | Map every consumed event to the v2 vocabulary (or add a compatibility projection); adapt the worker ingest entity keys to v2 events/messages. | both | | Tools | Passes a permission map and MCP servers through `KILO_CONFIG_CONTENT` (`cloud@3de933dd src/session-service.ts:1334-1357,1404-1424,1467-1470`); MCP types `{type:local|remote}` (`src/mcp-config.ts:7-21`). Reads tool parts (`part.type==='tool'`) and replies via `permission.reply/list`, `question.reply/reject/list` (`wrapper/src/kilo-api.ts:546-591`); auto-approves or rejects permissions (`wrapper/src/connection.ts:1171-1196`). | v2 registers tools through `Tool.Service` (no authorization in the registry) and enforces in `Permission.Service` with a persisted `permission` SQLite table (`kilo-v2:packages/core/src/tool.ts:157-183,220-271`; `packages/core/src/permission.ts:214-310`; `packages/core/src/permission/sql.ts:7`). MCP is config-driven (`packages/core/src/config/plugin/mcp.ts:39-56`). v2 reads `OPENCODE_CONFIG_CONTENT` (`packages/core/src/config.ts:206-210`) but **not** `KILO_CONFIG_CONTENT` as an input (it is only an internal constant, `packages/kilo-cli/src/skill-policy.ts:12`). | yes | Confirm v2 reads the permission/provider/MCP payload the wrapper actually injects (or adapt the injected config key); adapt permission/question reply calls. | both | -| Credentials | Injects `KILOCODE_TOKEN` (opaque capability redeemed at the outbound proxy), `KILO_AUTH_CONTENT` and writes `.local/share/kilo/auth.json` `{kilo:{type:'api',key}}`; sets `provider.kilo.options.apiKey/kilocodeToken/baseURL` (`cloud@3de933dd src/session-service.ts:1277-1311,814-828,1404-1410`; `wrapper/src/session-bootstrap.ts:286-287,680-688`). v1 reads these (`origin/main:packages/opencode/src/auth/index.ts:61-63`; provider `packages/core/src/plugin/provider/kilo.ts:18-19`). Runtime credential proxy on the worker (`src/server.ts:273,341-344,448`). | v2 has no `KILO_AUTH_CONTENT`, `KILOCODE_TOKEN` or `KILO_API_KEY`/`KILO_ORG_ID` env inputs; credentials are rows in the isolated `credential` table, resolved from the gateway account (`kilo-v2:packages/core/src/credential.ts:99-134`; `packages/core/src/credential/sql.ts:5-14`). `KILO_API_URL` selects the gateway only (`packages/kilo-cli/src/tui-preview.ts:141`). `kilo2 import-v1 --auth --apply [--gateway-server]` can seed isolated credentials from an auth file (`packages/kilo-cli/src/commands.ts:216-238`; `packages/kilo-cli/src/import-v1-config.ts`). | yes | Provide a v2 credential-injection path for hosted/floating consumers (env or import-v1 bootstrap), or adapt the cloud to write v2 credentials. | both | -| Persistence | v1 identity is `kilo`: data `~/.local/share/kilo`, config `~/.config/kilo` (`origin/main:packages/core/src/global.ts:10-40`). Wrapper writes `$XDG_DATA_HOME/kilo/storage/session_share/.json` `{id,ingestPath}` and runs `kilo import ` after downloading `GET /api/session/:id/export` (`cloud@3de933dd wrapper/src/restore-session.ts:169-195,1069,1196-1207`). | v2 identity is `kilo2`: `~/.local/share/kilo2/...`, DB `kilo2.db` (`kilo-v2:packages/kilo-cli/src/paths.ts:6,26`), and it refuses the v1 store (`packages/kilo-cli/src/storage.ts:19-22`). v2 never reads `session_share/.json`; v2 `import` decodes `SessionTransfer.Data {info,messages}` from a v2 JSON transcript and rejects URLs (`packages/kilo-cli/src/commands.ts:353-370`; `packages/schema/src/session-transfer.ts:5-9`). | yes | Migrate restore/import to the v2 transcript shape and v2 storage location, or add a compatibility importer for the v1/session-ingest export. | both | +| Credentials | Injects `KILOCODE_TOKEN` (opaque capability redeemed at the outbound proxy), `KILO_AUTH_CONTENT` and writes `.local/share/kilo/auth.json` `{kilo:{type:'api',key}}`; sets `provider.kilo.options.apiKey/kilocodeToken/baseURL` (`cloud@3de933dd src/session-service.ts:1277-1311,814-828,1404-1410`; `wrapper/src/session-bootstrap.ts:286-287,680-688`). v1 reads these (`origin/main:packages/opencode/src/auth/index.ts:61-63`; provider `packages/core/src/plugin/provider/kilo.ts:18-19`). Runtime credential proxy on the worker (`src/server.ts:273,341-344,448`). | v2 does **not** read `KILO_AUTH_CONTENT`, `KILOCODE_TOKEN`, or `KILO_API_KEY`/`KILO_ORG_ID`; credentials are rows in the isolated `credential` table, resolved from the gateway account (`kilo-v2:packages/core/src/credential.ts:99-134`; `packages/core/src/credential/sql.ts:5-14`). `KILO_API_URL` selects the gateway only (`packages/kilo-cli/src/tui-preview.ts:141`). Two import paths exist: upstream's DB migration imports `/auth.json` into `credential` (`origin/v2:packages/core/src/database/migration/20260805200742_import_legacy_credentials.ts`), and Kilo's `kilo2 import-v1 --auth --apply [--gateway-server]` seeds the isolated profile (`packages/kilo-cli/src/commands.ts:216-238`; `packages/kilo-cli/src/import-v1-config.ts`). Note the public HTTP API intentionally has no credential-import RPC (`packaging-next-slice.md:168-169`), so a hosted consumer must seed via file/CLI or a new RPC. | yes (env path only) | For hosted consumers that inject env, add a v2 credential-injection path (a new env/RPC), or have the cloud write an `auth.json` into the v2 data dir / run `import-v1 --auth` before `run`. | both | +| Persistence | v1 identity is `kilo`: data `~/.local/share/kilo`, config `~/.config/kilo` (`origin/main:packages/core/src/global.ts:10-40`). Wrapper writes `$XDG_DATA_HOME/kilo/storage/session_share/.json` `{id,ingestPath}` and runs `kilo import ` after downloading `GET /api/session/:id/export` (`cloud@3de933dd wrapper/src/restore-session.ts:169-195,1069,1196-1207`). | v2 identity is `kilo2`: `~/.local/share/kilo2/...`, DB `kilo2.db` (`kilo-v2:packages/kilo-cli/src/paths.ts:6,26`), and it refuses the v1 store (`packages/kilo-cli/src/storage.ts:19-22`). v2 never reads `session_share/.json`. v2 `import` decodes `SessionTransfer.Data {info,messages}` and rejects URLs (`packages/kilo-cli/src/commands.ts:353-370`; `packages/schema/src/session-transfer.ts:5-9`), but upstream v2 `session import` accepts a file **or URL** (`origin/v2:packages/cli/src/commands/handlers/session/import.ts`), and a v1→v2 history migration exists (§5). | yes | Move restore to the v2 transcript/import path (upstream `session import` accepts URLs); add a v1-export→`SessionTransfer.Data` converter reusing the migration's transform (§5, G8). | both | | Cancellation | `v2Client.session.abort` → `POST /session/:id/abort` (`cloud@3de933dd wrapper/src/kilo-api.ts:468-472`; `origin/main:packages/sdk/js/src/v2/gen/sdk.gen.ts:4872`); wrapper `/job/abort` sends an `interrupted` ingest event (`wrapper/src/server.ts:1247,831-845`); control-plane `session.abort` (`wrapper/src/control-plane/turn.ts:859-866`); worker `markAsInterrupted`/`interruptExecution` (`src/router/handlers/session-management.ts:267-272`). | v2 route is `POST /api/session/:sessionID/interrupt`, returning `{interrupted:boolean}`; interruption targets the process-local ownership chain, idle is a no-op, unknown sessions are rejected (`kilo-v2:packages/protocol/src/groups/session.ts:665-685`; `packages/core/src/session/execution.ts:27-33,148-165`; `packages/core/src/session/run-coordinator.ts:142-162`). | yes | Adapt to the v2 interrupt route/semantics; keep the `interrupted` ingest signal. | both | **Disposition: v2 client adaptation + runtime packaging change (both).** @@ -74,18 +117,19 @@ services/auto-routing-benchmark/container/Dockerfile:9-16`) and spawns `kilo run | Area | Current behavior + evidence | v2 behavior + evidence | Breaks? | Change needed | Lands in | |---|---|---|---|---|---| -| Startup | Image build runs `npm install -g @kilocode/cli@latest`; worker spawns `kilo` in a temp cwd (`cloud@3de933dd services/auto-routing-benchmark/container/Dockerfile:12`; `container/server.mjs:46,55-79`). | v2 `@kilocode/cli` is private with no `bin` and builds `kilo2` (`kilo-v2:packages/kilo-cli/package.json:2-4`; `script/build.ts:35`). No global `kilo` binary is produced. | yes | Publish v2 CLI with an installable `bin` (or a documented replacement binary); adapt spawn. | both | -| Session admission | One fresh `kilo run` per case; no session ids/continuation; runs serialized because the CLI store is not concurrency-safe (`container/server.mjs:37-41`; `src/run.ts:1258`). | v2 `run` accepts positional prompt, `-s/--session`, `-m/--model`, `--agent`, `--auto`, `--file`, `--directory`, `--format text|json` (`kilo-v2:packages/kilo-cli/src/commands.ts:283-352`). No `--variant` option. | yes | Drop/replace `--variant`, or add it to v2 with a defined meaning. | both | -| Streaming | Parses one JSON event per stdout line: completed `type:text` parts and `step_finish` cost (`cloud@3de933dd src/kilo-events.ts:52-85`; emitted by `origin/main:packages/opencode/src/cli/cmd/run.ts:876-891`). | v2 `run --format json` prints one completed `{sessionID,text}` object, explicitly "not a streaming event log" (`kilo-v2:packages/kilo-cli/src/tui-preview.ts:231`; help `packages/kilo-cli/src/commands.ts:110`). The event parser gets no `type` field and returns empty text. | yes | Either restore a streaming JSON mode in v2, or adapt the parser to the single-object result. | both | +| Startup | Image build runs `npm install -g @kilocode/cli@latest`; worker spawns `kilo` in a temp cwd (`cloud@3de933dd services/auto-routing-benchmark/container/Dockerfile:12`; `container/server.mjs:46,55-79`). | v2 `@kilocode/cli` is private with no `bin` and builds `kilo2` (`kilo-v2:packages/kilo-cli/package.json:2-4`; `script/build.ts:35`). Because v2 ships to `next`, `@latest` keeps resolving v1 until this consumer opts in. | yes (on opt-in) | Publish v2 CLI with an installable `bin`; adapt spawn to `kilo2`; pin to `next` explicitly when adopting. | both | +| Session admission | One fresh `kilo run` per case; no session ids/continuation; runs serialized because the CLI store is not concurrency-safe (`container/server.mjs:37-41`; `src/run.ts:1258`). | v2 `run` accepts positional prompt, `-s/--session`, `-m/--model`, `--agent`, `--auto`, `--file`, `--directory`, `--format text|json` (`kilo-v2:packages/kilo-cli/src/commands.ts:283-352`). There is no standalone `--variant` flag, but upstream v2 encodes the variant in the model ref as `provider/model#variant` (`origin/v2:packages/cli/src/commands/commands.ts`, `run` model help). | yes | Pass the variant as `-m kilo/#`, or add a `--variant` compatibility flag. | both | +| Streaming | Parses one JSON event per stdout line: completed `type:text` parts and `step_finish` cost (`cloud@3de933dd src/kilo-events.ts:52-85`; emitted by `origin/main:packages/opencode/src/cli/cmd/run.ts:876-891`). | kilo2's `run --format json` prints one completed `{sessionID,text}` object (`kilo-v2:packages/kilo-cli/src/tui-preview.ts:231`), but **upstream v2 preserves the streaming event log**: `noninteractive.ts` emits `{type,timestamp,sessionID,part}` for `text`/`step_start`/`step_finish`/`tool_use`/`reasoning` and `{type,error}` for errors (`origin/v2:packages/cli/src/run/noninteractive.ts`). The parser works against upstream v2; it fails only against kilo2 today. | yes (kilo2 only) | Align kilo2's `run` with upstream v2's non-interactive path (preferred), or adapt the parser to the single-object result. | this repo | | Tools | None; `--auto` only. | `--auto` exists; no tool/permission flags passed. No break. | no | none | — | -| Credentials | Injects `KILO_AUTH_CONTENT {kilo:{type:api,key,organizationId}}`, `KILO_API_URL`, `KILO_ORG_ID`, `KILO_DISABLE_SESSION_INGEST=1` (`cloud@3de933dd container/server.mjs:66-79`). v1 reads them (`origin/main:packages/core/src/plugin/provider/kilo.ts:18-19`; `packages/opencode/src/auth/index.ts:61`). | v2 reads none of `KILO_AUTH_CONTENT`/`KILO_ORG_ID`/`KILO_DISABLE_SESSION_INGEST`; only `KILO_API_URL` as the gateway (`kilo-v2:packages/kilo-cli/src/tui-preview.ts:141`). Credentials must be seeded (e.g. `import-v1 --auth`). | yes | Provide a v2 credential env/bootstrap for headless containers, or seed credentials before `run`. | both | +| Credentials | Injects `KILO_AUTH_CONTENT {kilo:{type:api,key,organizationId}}`, `KILO_API_URL`, `KILO_ORG_ID`, `KILO_DISABLE_SESSION_INGEST=1` (`cloud@3de933dd container/server.mjs:66-79`). v1 reads them (`origin/main:packages/core/src/plugin/provider/kilo.ts:18-19`; `packages/opencode/src/auth/index.ts:61`). | v2 reads none of `KILO_AUTH_CONTENT`/`KILO_ORG_ID`/`KILO_DISABLE_SESSION_INGEST`; only `KILO_API_URL` as the gateway (`kilo-v2:packages/kilo-cli/src/tui-preview.ts:141`). Credentials can be seeded with `import-v1 --auth` or by writing an `auth.json` the DB migration imports (§5, G6). | yes | Seed v2 credentials via `import-v1 --auth`/`auth.json` before `run`, or add a v2 env path. | both | | Persistence | Only a temp cwd, removed after each run (`cloud@3de933dd container/server.mjs:46,119`); no CLI home/config read. | v2 uses isolated `kilo2` paths (`kilo-v2:packages/kilo-cli/src/paths.ts:6,26`); a fresh temp cwd still works. | no | none (v2 store is created in-container). | — | | Cancellation | 180 s timeout, kills the whole process group (`cloud@3de933dd container/server.mjs:59-63,84-95`; `src/cli-runner.ts:15`). | v2 `run` installs abort handling and interrupts its session (`kilo-v2:packages/kilo-cli/src/run.ts:78,269-270`); SIGKILL of the group still applies. | no | none | — | **Disposition: runtime packaging change + v2 client adaptation (both).** It -floats `@kilocode/cli@latest`, so it is exposed the instant v2 is published to -that channel. v2 must be ready (binary, argv, JSON, credentials) **before** v2 -takes `latest`, and the cloud must adapt in the same window. +floats `@kilocode/cli@latest`, but the `next` channel keeps `latest` on v1, so +there is no forced break: this consumer adapts only when it opts into `next` +(or when v2 is eventually promoted to `latest`). Adaptation (binary, variant +syntax, streaming output, credentials) must land before that opt-in. ## 3. MCP catalog generator (CI) @@ -95,17 +139,17 @@ out to `kilo run` (`cloud@3de933dd .github/workflows/kilo-mcp-catalog.yml:112-11 | Area | Current behavior + evidence | v2 behavior + evidence | Breaks? | Change needed | Lands in | |---|---|---|---|---|---| -| Startup | `npm install -g @kilocode/cli` in both jobs (`cloud@3de933dd .github/workflows/kilo-mcp-catalog.yml:115,371`). | v2 package is private/no-`bin`, artifact `kilo2` (`kilo-v2:packages/kilo-cli/package.json:2-4`; `script/build.ts:35`). | yes | Same as benchmark: publish installable v2 CLI. | both | -| Session admission | One independent `spawnSync('kilo',['run','--model',M,'--variant',V,'--format','json'])`, prompt on stdin, cwd `tmpdir()` (`cloud@3de933dd apps/web/src/scripts/mcp-catalog/catalog.ts:721-732`). | v2 `run` has `-m/--model` and `--format` but **no `--variant`** (`kilo-v2:packages/kilo-cli/src/commands.ts:283-352`). | yes | Drop/replace `--variant`, or add it to v2. | both | -| Streaming | Parses completed `type:text` parts from stdout lines (`cloud@3de933dd catalog.ts:684-707,758`). | v2 emits a single `{sessionID,text}` object for `--format json` (`kilo-v2:packages/kilo-cli/src/tui-preview.ts:231`). | yes | Restore streaming JSON in v2, or adapt the parser. | both | +| Startup | `npm install -g @kilocode/cli` in both jobs (`cloud@3de933dd .github/workflows/kilo-mcp-catalog.yml:115,371`). | v2 package is private/no-`bin`, artifact `kilo2` (`kilo-v2:packages/kilo-cli/package.json:2-4`; `script/build.ts:35`); `next` channel keeps the unpinned install on v1 until the workflow pins `next`. | yes (on opt-in) | Publish installable v2 CLI; adapt install/spawn; pin `next` when adopting. | both | +| Session admission | One independent `spawnSync('kilo',['run','--model',M,'--variant',V,'--format','json'])`, prompt on stdin, cwd `tmpdir()` (`cloud@3de933dd apps/web/src/scripts/mcp-catalog/catalog.ts:721-732`). | v2 `run` has `-m/--model` and `--format` but no `--variant`; upstream v2 encodes variant as `provider/model#variant` (`kilo-v2:packages/kilo-cli/src/commands.ts:283-352`; `origin/v2:packages/cli/src/commands/commands.ts`). | yes | Move the variant into the model ref, or add a `--variant` flag. | both | +| Streaming | Parses completed `type:text` parts from stdout lines (`cloud@3de933dd catalog.ts:684-707,758`). | Upstream v2 keeps the streaming `type:text`/`step_finish` log (`origin/v2:packages/cli/src/run/noninteractive.ts`); kilo2 currently emits a single `{sessionID,text}` object (`kilo-v2:packages/kilo-cli/src/tui-preview.ts:231`). | yes (kilo2 only) | Align kilo2's `run` with upstream, or adapt the parser. | this repo | | Tools | None. | None passed; no break. | no | none | — | -| Credentials | Mints a service token, exports `KILO_API_KEY`/`KILO_ORG_ID`; otherwise ambient `kilo auth login` (`cloud@3de933dd .github/workflows/kilo-mcp-catalog.yml:155-173,397-415`). v1 reads `KILO_API_KEY`/`KILO_ORG_ID` (`origin/main:packages/core/src/plugin/provider/kilo.ts:18-19`). | v2 reads neither env (`kilo-v2`: no `KILO_API_KEY`/`KILO_ORG_ID` in `packages/kilo-cli/src`, `packages/kilo-gateway/src`). | yes | v2 credential env/bootstrap, or seed credentials via `import-v1`. | both | +| Credentials | Mints a service token, exports `KILO_API_KEY`/`KILO_ORG_ID`; otherwise ambient `kilo auth login` (`cloud@3de933dd .github/workflows/kilo-mcp-catalog.yml:155-173,397-415`). v1 reads `KILO_API_KEY`/`KILO_ORG_ID` (`origin/main:packages/core/src/plugin/provider/kilo.ts:18-19`). | v2 reads neither env. Seed via `import-v1 --auth`, or write an `auth.json` for the upstream DB migration (`origin/v2:.../20260805200742_import_legacy_credentials.ts`). | yes | Seed v2 credentials, or add a v2 env path. | both | | Persistence | Runs in `tmpdir()` so project config is not loaded (`cloud@3de933dd catalog.ts:713-715,728`); writes only `catalog.json`. | Isolated `kilo2` store in the runner home; temp cwd still avoids project config. | no | none | — | | Cancellation | 10 min `spawnSync` timeout; 20 min job timeout (`cloud@3de933dd catalog.ts:84,729`; `kilo-mcp-catalog.yml:66,324`). | v2 `run` handles abort/interrupt; process kill still applies. | no | none | — | **Disposition: runtime packaging change + v2 client adaptation (both), G1.** -Same floating exposure as the benchmark; a required `catalog (PR)` check will -fail on a v2 `latest` until both land. +Same floating exposure as the benchmark, neutralized by the `next` channel; the +required `catalog (PR)` check only breaks if/when the workflow opts into v2. ## 4. session-ingest @@ -122,7 +166,7 @@ No runtime executor; contract-coupled to the CLI's session shipping and to | Streaming | WS frame `{type:'ingest',sessionId,data:[…]}` and HTTP `POST /api/session/:id/ingest?v=N` with root `{data:[…]}` (`cloud@3de933dd services/session-ingest/src/types/ws-protocol.ts:5-9`; `src/routes/api.ts:431`; limits `util/ingest-limits.ts:11-12`). | v2 posts `{data: batch}` to `?v=2` over HTTP only (`kilo-v2:packages/kilo-gateway/src/session.ts:279-300,409-411`); no WS producer. | partial | Confirm reduced HTTP batch is accepted; WS continuity not provided. | this repo/cloud | | Tools | n/a (no tools in the payload). | n/a. | no | none | — | | Credentials | Bearer Kilo JWT via `KILOCODE_TOKEN`/auth.json (`cloud@3de933dd services/session-ingest/src/middleware/kilo-jwt-auth.ts:52-101`; wrapper writes `.local/share/kilo/auth.json`). | v2 resolves the active Kilo credential from the isolated store (`kilo-v2:packages/kilo-gateway/src/session.ts:386-397`); bearer still used on the wire. | partial | Confirm v2 credential resolution yields the same JWT audience; seed credentials for headless. | this repo/cloud | -| Persistence | Wrapper bridges by writing `$XDG_DATA_HOME/kilo/storage/session_share/.json` `{id,ingestPath}`; v1 CLI reads it (`cloud@3de933dd wrapper/src/restore-session.ts:169-195`; `origin/main:.../kilo-sessions.ts:1562,1603`). Restore downloads `GET /api/session/:id/export` then `kilo import ` (`wrapper/src/restore-session.ts:1069,1196-1207`). | v2 stores bootstrap under host storage key `session:`, not the `session_share` file (`kilo-v2:packages/kilo-gateway/src/session.ts:210-225,466-468`); v2 `import` accepts only a local v2 `SessionTransfer.Data` transcript and rejects URLs (`packages/kilo-cli/src/commands.ts:353-370`; `packages/schema/src/session-transfer.ts:5-9`). | yes | Provide a v1/session-ingest-export importer (or a v2 export/import bridge) so restore keeps working. | both | +| Persistence | Wrapper bridges by writing `$XDG_DATA_HOME/kilo/storage/session_share/.json` `{id,ingestPath}`; v1 CLI reads it (`cloud@3de933dd wrapper/src/restore-session.ts:169-195`; `origin/main:.../kilo-sessions.ts:1562,1603`). Restore downloads `GET /api/session/:id/export` then `kilo import ` (`wrapper/src/restore-session.ts:1069,1196-1207`). | v2 stores bootstrap under host storage key `session:`, not the `session_share` file (`kilo-v2:packages/kilo-gateway/src/session.ts:210-225,466-468`); v2 `import` accepts only a local v2 `SessionTransfer.Data` transcript and rejects URLs (`packages/kilo-cli/src/commands.ts:353-370`). Upstream v2 `session import` accepts a URL and the export shape is convertible via the migration's transform (§5). | yes | Add a v1-export→`SessionTransfer.Data` converter (reuse §5), or have session-ingest export v2 transfer data. | both | | Cancellation | n/a; `session_close` reason `completed|error|interrupted` is data, not a cancel call (`cloud@3de933dd session-sync.ts:53-55`). | n/a; v2 has no `session_close` item. | partial | Carry terminal outcome another way if consumers rely on `session_close`. | this repo | **Disposition: v2 contract change (producer) with cloud coordination.** The v2 @@ -132,129 +176,217 @@ server code likely needs no schema change (the union is permissive), but its message projection and the `session_close`/diff/part semantics must be validated with v2 self-contained messages. +## 5. v1 → v2 session history migration + +This is the load-bearing piece for the "migrate existing v1 users' sessions" +goal. The owner asked whether upstream already solves it — it does, and kilo-v2 +already vendors an older copy of the same file. + +| Item | Evidence | +|---|---| +| Upstream migration engine | `origin/v2:packages/core/src/database/v1-migration.bun.ts` — migrates the legacy local SQLite store in place: `session`→`session_v2`, and `message`+`part`→`session_message` via `transformSession`, plus importing a previous-channel `opencode-next.db`. | +| Upstream history | Introduced by `0fbe489e35 feat(core): migrate v1 data to v2 (#40723)`; refined by `3facbe1dd3` (older previous-channel DBs, #43142), `facd7ff452` (simplify effects, #45685), `2c369a21c9` (renamed legacy tools notice, #50188). | +| Platform variants | `origin/v2:packages/core/src/database/v1-migration.ts` selects a platform build; `v1-migration.noop.ts` is a no-op (`status: "completed"`) for hosts without Bun SQLite. Cloud-agent-next runs the Bun binary, so the Bun path applies. | +| Wiring | Auto-runs in the server graph via `V1Migration.layer` (`origin/v2:packages/server/src/routes.ts`; kilo-v2 `packages/server/src/routes.ts:185`); exposed as `GET /api/experimental/migration/v1` (`kilo-v2:packages/protocol/src/groups/migration.ts:18`). | +| Kilo's copy | `kilo-v2:packages/core/src/database/v1-migration.bun.ts` is an older revision (missing the renamed-tool notice; its project insert lacks upstream's `time_active`) and still uses the pre-rename scope `@opencode-ai/*`, while upstream v2 uses `@opencode/*`. | +| Local-store copy | `kilo-v2:packages/kilo-cli/src/import-v1-session.ts` copies the v1 store into the isolated `kilo2` layout (read-only source, WAL-consistent, no-clobber), but explicitly leaves running `V1Migration` against the copy to "the CLI command slice," which is not wired — kilo2 has no `migrate`/import-v1-sessions command (`packages/kilo-cli/src/commands.ts`). | +| Config/credential migration | `origin/v2:packages/cli/src/config/migrate.ts` (`ConfigMigrateV1`); `kilo-v2:packages/kilo-cli/src/import-v1-config.ts`; upstream credential import `origin/v2:.../migration/20260805200742_import_legacy_credentials.ts`. | + +**Consequence:** the migration algorithm exists and is proven (upstream, with +tests and follow-up fixes); there is no need to design one. The work is (a) +sync kilo-v2's copy up to upstream `origin/v2`, (b) account for the npm-scope +rename when syncing, and (c) wire the user-facing flow (discover v1 store → +copy into `kilo2` via `import-v1-session` → run `V1Migration`). None of this +covers the cloud session-ingest wire, which is a separate contract (§4). + ## Disposition summary | Consumer | Disposition | Lands in | Pinning (#14425) | Recommended gate (#14425) | Evidence | |---|---|---|---|---|---| | cloud-agent-next | v2 client adaptation + runtime packaging change | both (v2 contract/packaging first) | pinned 7.8.1; independent cutover | G1 for adaptation; independent go/no-go | §1; `wrapper/src/control-plane/kilo-runtime.ts:21,184`; `packages/protocol/src/groups/session.ts:170,338,666` | -| auto-routing-benchmark | runtime packaging change + v2 client adaptation | both (v2 packaging before v2 `latest`) | floats `@kilocode/cli@latest` | G1 | §2; `container/server.mjs:55-79`; `src/kilo-events.ts:52-85`; `packages/kilo-cli/package.json:2-4` | -| MCP catalog generator (CI) | runtime packaging change + v2 client adaptation | both | unpinned `npm install -g @kilocode/cli` | G1 | §3; `catalog.ts:721-732`; `kilo-mcp-catalog.yml:115,371` | +| auto-routing-benchmark | runtime packaging change + v2 client adaptation | both; adapt on `next` opt-in | floats `@kilocode/cli@latest` (safe while v2 is on `next`) | G1 | §2; `container/server.mjs:55-79`; `src/kilo-events.ts:52-85`; `origin/v2:packages/cli/src/run/noninteractive.ts` | +| MCP catalog generator (CI) | runtime packaging change + v2 client adaptation | both; adapt on `next` opt-in | unpinned `npm install -g @kilocode/cli` (safe while v2 is on `next`) | G1 | §3; `catalog.ts:721-732`; `kilo-mcp-catalog.yml:115,371` | | session-ingest | v2 contract change (producer) + cloud coordination | both (v2 producer first) | contract-coupled, no runtime | G1 (producer-side) | §4; `packages/kilo-gateway/src/session.ts:64-97,279-300`; `session-sync.ts:12-99` | No in-scope consumer is "no change". Every disposition includes a v2 contract/packaging change in this repo; cloud-agent-next additionally owns a -client adaptation, and the two floating consumers (benchmark, MCP catalog) must -be adapted or protected before v2 is published to the channels they track. +client adaptation. Because v2 ships on `next`, the two floating consumers are +not forced to adapt until they opt in. ## Gaps for #14427 Ordering rule: v2 contract/packaging items land in this repo first; cloud client -adaptations follow, except the floating consumers, whose cloud adaptations must -land before v2 replaces `@kilocode/cli@latest`. +adaptations follow. The floating consumers adapt only when they move to `next`. -### G1 — Spawnable v2 headless serve contract +### G1 — Spawnable kilo2 serve contract - Repo: this repo. -- Touches: `packages/kilo-cli/src/index.ts`, `packages/kilo-cli/src/commands.ts`, `packages/kilo-cli/src/tui-preview.ts`, `packages/kilo-cli/src/interactive-server.ts`, `packages/server/src/{process.ts,routes.ts,fetch.ts,handlers/health.ts}`, `packages/protocol/src/groups/{health.ts,server.ts}`. -- Why: cloud-agent-next and the v1 SDK both need a documented argv, readiness signal and auth for a headless server; v2 prints `URL:`/`Password file:` and defaults to Basic auth username `opencode` (`kilo-v2:packages/kilo-cli/src/tui-preview.ts:179-180`; `interactive-server.ts:250-253`). +- Problem: the cloud wrapper spawns `kilo serve --hostname/--port` and waits for + `kilo server listening`; kilo2 is named `kilo2`, prints `URL:`/`Password file:`, + and its `serve` lacks `--hostname`/`--port`. +- Solution: align kilo2's `serve` with upstream v2's (`--hostname`, `--port`, + `--cors`, `--service`, `--stdio`) and publish a documented readiness/auth + contract, or define an explicit headless-spawn contract. +- Touches: `packages/kilo-cli/src/{index.ts,commands.ts,tui-preview.ts,interactive-server.ts}`, `packages/server/src/{routes.ts,fetch.ts,handlers/health.ts}`, `packages/protocol/src/groups/{health.ts,server.ts}`. - Dependencies: G2 (binary), G3 (handshake). -- Cloud coordination: yes — cloud-agent-next wrapper serves as the reference consumer. +- Cloud coordination: yes — cloud-agent-next wrapper is the reference consumer. -### G2 — Publishable v2 CLI binary and package metadata +### G2 — Packaging + release channel - Repo: this repo. -- Touches: `packages/kilo-cli/package.json` (add `bin`, drop `private` for the preview/customer channel), `packages/kilo-cli/script/{build.ts,build-tui.ts}`. -- Why: v2 builds `kilo2` and declares no `bin`; `npm install -g @kilocode/cli` yields no `kilo` command (`kilo-v2:packages/kilo-cli/package.json:2-4`; `script/build.ts:35`). +- Problem: `@kilocode/cli` on v2 is private, has no `bin`, and there is no + pre-release channel, so no consumer can install v2 and the floating + consumers would break if v2 took `latest`. +- Solution: add `bin` (keep the `kilo2` name per owner decision), drop `private` + for the pre-release channel, publish to a new **`next`** dist-tag, and keep + `latest` on v1 for the migration. Add a release guard/test that asserts + `latest` still resolves the v1 CLI. +- Touches: `packages/kilo-cli/package.json`, `packages/kilo-cli/script/{build.ts,build-tui.ts}`, release tooling (`.github/workflows/publish.yml`, `packages/script/src/index.ts`). - Dependencies: none; blocks every consumer. -- Cloud coordination: yes — release-channel decision (v2 under the same `latest` tag or a new channel, per #14425 unknowns). +- Cloud coordination: yes — release-channel naming/ownership. ### G3 — Version/capability handshake (N9) - Repo: this repo. +- Problem: v2 health returns only `{healthy,version,pid}`, so a client cannot + safely select v1 vs v2 routes in one session. +- Solution: add a capability/version handshake. Record what each consumer needs: + cloud-agent-next needs backend generation + feature flags; benchmark/MCP + catalog need an argv/output contract marker. - Touches: `packages/protocol/src/groups/health.ts`, `packages/server/src/handlers/health.ts`, `packages/client/src/service-version.ts`, `packages/kilo-client/src`. -- Why: v2 health returns only `{healthy,version,pid}`; no capability/handshake surface exists, so a client cannot safely select v1 vs v2 routes in one session (`kilo-v2:packages/protocol/src/groups/health.ts:5-10`; `packages/client/src/service-version.ts:3-8`). Record what each consumer needs: cloud-agent-next needs backend generation + feature flags; benchmark/MCP catalog need an argv/output contract marker. - Dependencies: G1. - Cloud coordination: yes. ### G4 — Consumer-facing v2 SDK surface - Repo: this repo. -- Touches: `packages/sdk` (currently `@opencode-ai/sdk`), `packages/kilo-client` (`@kilocode/client`), `packages/client`. -- Why: the cloud imports `@kilocode/sdk` and `@kilocode/sdk/v2` (`cloud@3de933dd wrapper/src/main.ts:18`; `kilo-api.ts:9-16`); v2 publishes no `@kilocode/sdk` (only `@opencode-ai/sdk` at `kilo-v2:packages/sdk/package.json:3`). Decide whether to publish a v2 `@kilocode/sdk` shim or migrate the cloud to `@kilocode/client`. -- Dependencies: G3 for handshake; G5 consumes it. +- Problem: the cloud imports `@kilocode/sdk` and `@kilocode/sdk/v2`; v2 publishes + no `@kilocode/sdk` (only `@opencode-ai/sdk`). +- Solution: publish a v2 `@kilocode/sdk` (or document `@kilocode/client` as the + replacement) and provide a migration path for the two SDK import sites. +- Touches: `packages/sdk`, `packages/kilo-client` (`@kilocode/client`), `packages/client`. +- Dependencies: G3. - Cloud coordination: yes. -### G5 — cloud-agent-next client adaptation to v2 routes/events +### G5 — cloud-agent-next client adaptation - Repo: cloud. +- Problem: routes, event vocabulary and payloads all differ (§1). +- Solution: adapt wrapper to `/api/session`, `/api/session/:id/prompt`, + `/api/event`, `/api/session/:id/interrupt`; map events + (`session.text.delta`, `session.tool.*`, `session.inbox.*`, + `session.execution.*`); seed credentials. - Touches: `services/cloud-agent-next/wrapper/src/{main.ts,kilo-api.ts,connection.ts,global-feed.ts}`, `wrapper/src/control-plane/{kilo-runtime.ts,turn.ts}`, `src/session/ingest-handlers/entity-id.ts`, `src/router/handlers/session-management.ts`. -- Why: routes (`/api/session`, `/api/session/:id/prompt`, `/api/event`, `/api/session/:id/interrupt`), event vocabulary (`session.text.delta`, `session.tool.*`, `session.inbox.*`, `session.execution.*`) and payloads all differ (§1). - Dependencies: G1, G3, G4. - Cloud coordination: yes — cloud team owns the change. -### G6 — v2 credential injection for hosted and floating consumers +### G6 — v2 credential seeding for hosted/floating consumers - Repo: this repo (runtime), cloud (usage). -- Touches: `packages/kilo-cli/src/{credential-import.ts,import-v1-config.ts,commands.ts,tui-preview.ts}`, `packages/core/src/credential.ts`, `packages/kilo-gateway/src/account.ts`. -- Why: cloud-agent-next injects `KILOCODE_TOKEN`/`KILO_AUTH_CONTENT`/auth.json and the benchmark/MCP CI inject `KILO_AUTH_CONTENT` or `KILO_API_KEY`/`KILO_ORG_ID`; v2 reads none of these (`cloud@3de933dd src/session-service.ts:1277-1289`; `container/server.mjs:66-79`; `kilo-mcp-catalog.yml:155-173`). Options: an explicit v2 env, or seed isolated credentials via `import-v1 --auth` (`kilo-v2:packages/kilo-cli/src/commands.ts:216-238`). +- Problem: cloud-agent-next injects `KILOCODE_TOKEN`/`KILO_AUTH_CONTENT`/auth.json + and the benchmark/MCP CI inject `KILO_AUTH_CONTENT` or + `KILO_API_KEY`/`KILO_ORG_ID`; v2 reads none of these. v2 credentials are rows + in the isolated store, and the HTTP API intentionally has no credential-import + RPC. +- Solution: an explicit v2 seeding path. Short term, seed via `kilo2 import-v1 + --auth --apply` or by writing an `auth.json` the upstream DB migration imports; + long term, a small env/RPC for hosted consumers. Owner confirmed an import + tool (including auth) is acceptable. +- Touches: `packages/kilo-cli/src/{credential-import.ts,import-v1-config.ts,commands.ts,tui-preview.ts}`, `packages/core/src/credential.ts`, `packages/kilo-gateway/src/account.ts`, `packages/core/src/database/migration/20260805200742_import_legacy_credentials.ts`. - Dependencies: G2. - Cloud coordination: yes. -### G7 — v2 `run` argv/output compatibility for headless consumers -- Repo: this repo, cloud. -- Touches: `packages/kilo-cli/src/{commands.ts,run.ts,tui-preview.ts}`, cloud `services/auto-routing-benchmark/{container/server.mjs,src/kilo-events.ts}`, `apps/web/src/scripts/mcp-catalog/catalog.ts`. -- Why: consumers pass `--variant` and parse a streaming JSON event log; v2 has no `--variant` and prints one `{sessionID,text}` object (`kilo-v2:packages/kilo-cli/src/commands.ts:283-352`; `tui-preview.ts:231`). Decide whether v2 preserves `--variant` and a streaming mode or the cloud adapts. -- Dependencies: G2; must precede v2 reaching `latest`. +### G7 — `run` argv/output compatibility for headless consumers +- Repo: this repo (align with upstream), cloud (`#variant`). +- Problem: consumers pass `--variant` and parse a streaming JSON event log. + kilo2 has no `--variant` and prints one `{sessionID,text}` object, **but + upstream v2 already preserves the streaming log** and encodes the variant as + `provider/model#variant`. +- Solution: port upstream's `run/noninteractive.ts` path into kilo2 (preferred); + change consumers to `-m provider/model#variant` or keep a `--variant` shim. +- Touches: `packages/kilo-cli/src/{commands.ts,run.ts,tui-preview.ts}` plus the upstream `run/` port; cloud `services/auto-routing-benchmark/{container/server.mjs,src/kilo-events.ts}`, `apps/web/src/scripts/mcp-catalog/catalog.ts`. +- Dependencies: G2; needed before the consumers move to `next`. - Cloud coordination: yes. -### G8 — v2 restore/import compatibility +### G8 — Restore/import compatibility - Repo: this repo, cloud. -- Touches: `packages/kilo-cli/src/{commands.ts,import-v1-session.ts}`, `packages/schema/src/session-transfer.ts`, `packages/kilo-gateway/src/session.ts`, cloud `services/cloud-agent-next/wrapper/src/restore-session.ts`. -- Why: cloud-agent-next writes `session_share/.json` and runs `kilo import ` from the session-ingest export; v2 ignores that file and `import` accepts only a local v2 transcript (`cloud@3de933dd wrapper/src/restore-session.ts:169-195,1069,1196-1207`; `kilo-v2:packages/kilo-cli/src/commands.ts:353-370`). -- Dependencies: G9; cross-repo order: v2 importer first, cloud restore second. +- Problem: cloud-agent-next runs `kilo import ` on the + session-ingest **v1** export (`{info, messages:[{info, parts}], sessionDiff}`), + while v2 `import` expects `SessionTransfer.Data` (self-contained v2 messages). +- Solution: add a v1-export → `SessionTransfer.Data` converter that reuses + `V1Migration.transformSession`'s part→content mapping (preferred), or have + session-ingest export v2 transfer data. Upstream `session import` already + accepts a file or URL to target. +- Touches: `packages/kilo-cli/src/{commands.ts,import-v1-session.ts}`, `packages/schema/src/session-transfer.ts`, `packages/core/src/database/v1-migration.bun.ts`, `packages/kilo-gateway/src/session.ts`, cloud `services/cloud-agent-next/wrapper/src/restore-session.ts`. +- Dependencies: G11 (reuse its transform); cross-repo order: v2 converter first, cloud restore second. - Cloud coordination: yes. ### G9 — session-ingest producer continuity - Repo: this repo (producer), cloud (validation). +- Problem: v2 emits only `kilo_meta`/`session`/`message` on explicit share, with + no continuous stream and no `part`/`session_diff`/`session_open`/`session_close`/ + `session_status`/`agent_notification`/`session_pr_link`; bootstrap is stored + under `session:`, not the `session_share` bridge file. +- Solution: decide whether session-ingest consumes v2 self-contained messages + and add a continuous (or explicit-sync) producer; map terminal outcome in + place of `session_close`. - Touches: `packages/kilo-gateway/src/session.ts`, `packages/schema/src/session-event.ts`, `packages/core/src/session/*`. -- Why: v2 emits only `kilo_meta`/`session`/`message` on explicit share, with no continuous stream, no `part`/`session_diff`/`session_open`/`session_close`/`session_status`/`agent_notification`/`session_pr_link`, and stores bootstrap under `session:` (`kilo-v2:packages/kilo-gateway/src/session.ts:64-97,279-300`; session-ingest union `cloud@3de933dd services/session-ingest/src/types/session-sync.ts:12-99`). -- Dependencies: G3; cloud validates its message projection against v2 self-contained messages. +- Dependencies: G3; cloud validates its projection against v2 self-contained messages. - Cloud coordination: yes. ### G10 — Event/entity projection mapping for cloud ingest - Repo: this repo, cloud. +- Problem: the worker keys entities on `message.updated`/`message.part.updated`; + v2 uses `session.message.content.updated`/`session.step.*`/`session.tool.*` and + self-contained messages. +- Solution: map v2 events/messages to the worker's entity model, or add a + projection. - Touches: cloud `services/cloud-agent-next/src/session/ingest-handlers/*`, `src/websocket/ingest.ts`; v2 `packages/schema/src/session-event.ts`. -- Why: the worker keys entities on `message.updated`/`message.part.updated` (`cloud@3de933dd src/session/ingest-handlers/entity-id.ts:12-27`); v2 uses `session.message.content.updated`/`session.step.*`/`session.tool.*` and self-contained messages. - Dependencies: G5. - Cloud coordination: yes. +### G11 — Sync + wire the v1→v2 session migration +- Repo: this repo. +- Problem: upstream `origin/v2` has a proven migration + (`v1-migration.bun.ts`, #40723 + follow-ups); kilo-v2 vendors an older copy and + never wires the user-facing flow; `import-v1-session.ts` exists but has no + command. +- Solution: sync kilo-v2's copy up to upstream `origin/v2` (including the + renamed-tool notice and `time_active`), reconcile the `@opencode-ai`→`@opencode` + scope rename, and wire the CLI flow (discover v1 store → `import-v1-session` + copy → run `V1Migration`), surfacing progress via + `/api/experimental/migration/v1`. +- Touches: `packages/core/src/database/{v1-migration.bun.ts,v1-migration.noop.ts,v1-migration.ts}`, `packages/kilo-cli/src/{import-v1-session.ts,import-v1-config.ts,commands.ts}`, `packages/server/src/routes.ts`. +- Dependencies: none for the engine; G2 for a user-installable binary. +- Cloud coordination: limited — this is local-user migration; confirm no cloud path assumes the v1 store. + ## Proposed progress-plan row changes (for N7; do not apply here) File: `migration-tracking/plans/kilo-opencode-v2-plan-progress.md`, section "Remote, sharing and distribution". 1. Add a row: - `| Cloud agent consumer contracts | Kilo CLI / Gateway | 4, 6 | Partial | Contract mapping and dispositions complete; implementation gaps open in #14427 | Four in-scope consumers mapped across startup, admission, streaming, tools, credentials, persistence, cancellation. cloud-agent-next needs a v2 client adaptation plus packaging; auto-routing-benchmark and MCP catalog CI float and break on v2 latest; session-ingest has a reduced explicit-share-only v2 producer. See technical-notes/baseline/cloud-agent-contracts.md. |` + `| Cloud agent consumer contracts | Kilo CLI / Gateway | 4, 6 | Partial | Contract mapping and dispositions complete; implementation gaps open in #14427 | Four in-scope consumers mapped across startup, admission, streaming, tools, credentials, persistence, cancellation. v2 ships on a new next channel so floating consumers are not forced to break. Upstream v2 already provides serve, streaming run, session import and the v1→v2 migration; kilo-v2 needs to converge. See technical-notes/baseline/cloud-agent-contracts.md. |` 2. Amend the #14425-added "Cloud agent runtime consumers (hosted)" row's remaining scope to say contract mapping is complete and point at `technical-notes/baseline/cloud-agent-contracts.md`. 3. Amend the `Cloud CLI client` row to note that local `kilo cloud` fixtures and the `/remote` relay do not establish cloud-hosted consumer compatibility (already asserted by #14425; keep the two baselines linked). +4. Add a row for the v1→v2 session history migration (upstream-sourced, sync + + CLI wiring) so it is tracked independently of the cloud consumers. Evidence links for the epic: #14425 (inventory), #14426 (contracts, this file), #14427 (adaptation), #14428 (validation). ## Open questions for the cloud owners -1. Release channel: does v2 replace `@kilocode/cli@latest` (breaking the two - floating consumers concurrently) or ship under a new tag/name? This decides - whether the cloud adaptations in G6/G7 must land before v2 is published. -2. Binary contract: will the v2 CLI keep a `kilo` command and a - `kilo server listening` style readiness line, or is adoption of the `kilo2` - binary and `URL:`/`Password file:` output acceptable (G1/G2)? -3. Credentials: is `kilo2 import-v1 --auth --apply` an acceptable - migration for hosted/floating consumers, or does v2 need a first-class env - injection path for `KILOCODE_TOKEN`/`KILO_API_KEY` (G6)? -4. Session-ingest semantics: can session-ingest consume v2 self-contained +1. `next` channel mechanics: confirm where the `@kilocode/cli` dist-tag is set + (the in-repo `publish.yml` is the upstream `anomalyco/opencode` skeleton) and + that `latest` will hold the v1 line for the whole migration (G2). +2. Hosted credential injection: is writing an `auth.json` / running + `import-v1 --auth` acceptable in the sandbox image, or does v2 need a + first-class env/RPC for `KILOCODE_TOKEN`/`KILO_API_KEY` (G6)? +3. Session-ingest semantics: can session-ingest consume v2 self-contained `message` items without `part` rows, and how should `session_close`/diff/ status/notifications be carried (G9)? -5. Restore: is there a session-ingest export format both v1 and v2 can import, - or does the restore path need a v1→v2 transcript converter (G8)? -6. Deployed versions: #14425 recorded that exact dev/prod versions are - unverified; confirm the currently deployed `KILOCODE_CLI_VERSION` and whether - any floating image already resolves a v2 `latest`. +4. Restore: is a v1-export→`SessionTransfer.Data` converter (reusing the + migration transform) acceptable, or should session-ingest export v2 transfer + data (G8)? +5. Migration scope: should the kilo2 CLI expose the migration as an explicit + command, or (like upstream) run it automatically on first open of a copied v1 + store (G11)?