Skip to content

refactor(cli): shared-process remote sessions with safe per-session exit - #12327

Merged
iscekic merged 4 commits into
mainfrom
feature/kilo-remote-process-spawn
Jul 24, 2026
Merged

refactor(cli): shared-process remote sessions with safe per-session exit#12327
iscekic merged 4 commits into
mainfrom
feature/kilo-remote-process-spawn

Conversation

@iscekic

@iscekic iscekic commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

Refactors remote CLI sessions to run concurrently inside one CLI process instead of spawning a dedicated process per remote-created session, and makes /exit a safe per-session detach rather than a whole-CLI kill.

This addresses @eshurakov's review: Kilo already supports multiple sessions in one process with per-call directory context, so process-per-session isolation was unnecessary.

What changed

  • One process owns concurrent sessions. create_session (with an absent session id) creates and attaches the session in the existing process against the connection's target directory. Removed the per-session spawner (session-spawner.ts), the KILO_REMOTE_ATTACH_SESSION attach-on-boot path + retry, and the child-advertisement gate. Retained the instance advertisement and an immediate advertisement heartbeat on (re)connect.
  • /exit detaches only the target session. The exit_cli wire command (name unchanged for deployed-client compatibility) now: verifies ownership, cancels the active prompt, clears the session's status, detaches it (awaiting the heartbeat fence so the relay/clients see it leave), then ACKs. History is preserved. On failure it rolls back and the CLI stays alive.
  • Lifecycle after detach is CLI-decided. If the detached session was the last one owned by an interactive CLI (which registers a RemoteExit callback), the CLI closes — but only after the ACK flushes. A headless kilo remote host never registers that callback, so it stays alive and advertising at zero sessions and can create new sessions again.
  • Presence can't immediately re-attach an exited session (suppression tombstone released only once presence itself drops the id).
  • Capability signal for clients. The list_commands v1 catalog gains an optional canExitSession: true, emitted independently of exitAvailable, so clients can detect the safe session-exit semantics. A companion cloud/mobile PR consumes it and fails closed against CLIs that don't advertise it.

Testing

  • Focused Bun suites for attached-state (detach + suppression + rollback), remote-command (canExitSession on interactive and headless paths), remote-sender (create_session in-process, exit_cli owns-check / detach / ACK-ordering / last-session-callback / headless-stays-alive / rollback), remote-ws, and an integration test proving the detach heartbeat fence resolves for busy/retry/offline sessions (fails without the status-clear fix).
  • tsgo typecheck, the opencode annotation check, and the promise-facade ratchet all pass on the exact head.

Companion PR (cloud SDK + mobile): Kilo-Org/cloud#4686

Comment thread packages/opencode/src/kilo-sessions/kilo-sessions.ts Outdated
Comment thread packages/opencode/src/kilo-sessions/session-spawner.ts Outdated
Comment thread packages/opencode/src/kilo-sessions/session-spawner.ts Outdated
Comment thread packages/opencode/test/kilocode/cli/cmd/remote.test.ts Outdated
@kilo-code-bot

kilo-code-bot Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

This incremental review covers only the diff since the last review pass (3 files, 260 lines changed, commit 41ff4efde2). All previously flagged CRITICAL/WARNING findings on attached-state.ts are fixed in this commit:

  • announce(id) no longer joins an in-flight detach(id); it now waits for the detach to settle and performs a real re-attach.
  • detach(id) no longer joins an in-flight announce(id); it now waits for the announce to settle and performs a real detach.
  • Failed-detach rollback now calls suppressed.delete(id), releasing the tombstone so the session stays adoptable by presence.
  • New tests cover both no-opposite-op-join scenarios, the rollback-then-setPresence case, and a stale-announce-after-reset() generation guard.
  • remote-ws.test.ts gained AC6f, exercising the negative-containment fence for detachSessionId with a fresh send that still contains the id.

No new bugs, style violations, or fork-hygiene concerns found in the changed lines.

Files Reviewed (3 files)
  • packages/opencode/src/kilo-sessions/attached-state.ts
  • packages/opencode/test/kilocode/sessions/attached-state.test.ts
  • packages/opencode/test/kilocode/sessions/remote-ws.test.ts
Previous Review Summaries (6 snapshots, latest commit 7433dfe)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 7433dfe)

Status: No Issues Found | Recommendation: Merge

This incremental review covers only the diff since the last review pass (1 file, 9 lines changed). The change moves SessionPrompt from a static import to a lazy Promise.all dynamic import inside cancelPrompt, matching the existing pattern already used in remote-command.ts, to break a module-load-order cycle with @/session/prompt. No new bugs, style violations, or fork-hygiene concerns found in the changed lines.

Files Reviewed (1 file)
  • packages/opencode/src/kilo-sessions/kilo-sessions.ts

Previous review (commit f1c6279)

Status: 5 Issues Found | Recommendation: Address before merge

This PR was rewritten since the last review (force-pushed): the process-per-session spawner design was replaced with in-process concurrent sessions and detach-only /exit semantics, per @eshurakov's review feedback. All previously reported findings targeted session-spawner.ts, which no longer exists — this is a fresh full review of the current diff.

Overview

Severity Count
CRITICAL 3
WARNING 2
SUGGESTION 0
Issue Details (click to expand)

CRITICAL

File Line Issue
packages/opencode/src/kilo-sessions/attached-state.ts 185 announce(id) can silently join an in-flight detach(id) promise and resolve successfully without actually re-attaching
packages/opencode/src/kilo-sessions/attached-state.ts 251 detach(id) can silently join an in-flight announce(id) promise and resolve successfully without actually detaching
packages/opencode/src/kilo-sessions/attached-state.ts 277 Failed-detach rollback restores presence/pending but leaves the suppression tombstone in place, so the next legitimate setPresence permanently drops the id

WARNING

File Line Issue
packages/opencode/test/kilocode/sessions/attached-state.test.ts 820 Rollback test doesn't verify a follow-up setPresence still keeps the id attached, masking the tombstone bug above
packages/opencode/src/kilo-sessions/remote-ws.ts 281 No test exercises the new detachSessionId negative-containment waiter path end-to-end
Files Reviewed (16 files)
  • .changeset/remote-instance-advertisement.md
  • packages/opencode/src/cli/cmd/remote.ts
  • packages/opencode/src/kilo-sessions/attached-state.ts - 3 issues
  • packages/opencode/src/kilo-sessions/kilo-sessions.ts
  • packages/opencode/src/kilo-sessions/remote-command.ts
  • packages/opencode/src/kilo-sessions/remote-protocol.ts
  • packages/opencode/src/kilo-sessions/remote-sender.ts
  • packages/opencode/src/kilo-sessions/remote-ws.ts - 1 issue
  • packages/opencode/test/kilocode/cli/cmd/remote.test.ts
  • packages/opencode/test/kilocode/kilo-sessions.test.ts
  • packages/opencode/test/kilocode/sessions/attached-state.test.ts - 1 issue
  • packages/opencode/test/kilocode/sessions/remote-command.test.ts
  • packages/opencode/test/kilocode/sessions/remote-protocol.test.ts
  • packages/opencode/test/kilocode/sessions/remote-sender.test.ts
  • packages/opencode/test/kilocode/sessions/remote-ws.test.ts
  • script/check-opencode-promise-facades.ts

Fix these issues in Kilo Cloud

Previous review (commit 145d472)

Status: No Issues Found | Recommendation: Merge

All 4 previously reported findings were fixed in the latest commits:

  • Attach-on-boot now retries (bounded, 3 attempts) before giving up (kilo-sessions.ts).
  • any[] in session-spawner.ts's Deps.log type replaced with a structured signature.
  • Log-file-handle close error is now logged instead of silently discarded.
  • remote.test.ts now tests real extracted behavior (shouldAdvertiseInstance, buildInstanceAdvertisement) instead of asserting on source text/regex.

No new issues found in the incremental diff.

Files Reviewed (incremental — this pass)
  • packages/opencode/src/cli/cmd/remote.ts
  • packages/opencode/src/kilo-sessions/kilo-sessions.ts
  • packages/opencode/src/kilo-sessions/session-spawner.ts
  • packages/opencode/test/kilocode/cli/cmd/remote.test.ts
  • packages/opencode/test/kilocode/kilo-sessions.test.ts
  • packages/opencode/test/kilocode/sessions/session-spawner.test.ts
  • script/check-opencode-promise-facades.ts

Previous review (commit 1193023)

Status: 4 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 2
Issue Details (click to expand)

WARNING

File Line Issue
packages/opencode/src/kilo-sessions/kilo-sessions.ts 411 Attach-on-boot failure after a successful spawn is only logged, no retry — can silently orphan a spawned session (the exact "no owner" failure this feature aims to prevent)
packages/opencode/src/kilo-sessions/session-spawner.ts 87 New any[] usage in the Deps.log type violates the repo's "avoid any" rule

SUGGESTION

File Line Issue
packages/opencode/src/kilo-sessions/session-spawner.ts 238 Log-file-handle close error is swallowed with no logging
packages/opencode/test/kilocode/cli/cmd/remote.test.ts 21 Test asserts on source-text/regex structure instead of driving real behavior
Files Reviewed (incremental — this pass)
  • No new commits since the last review. Current HEAD (1193023bec07d32c72b2fed22c77e5fcb9056e1f) matches the commit already anchoring all 4 existing inline comments, so there is nothing new to analyze this pass.

Incremental review: no new commits were pushed since the previous review pass. All 4 previously reported findings remain unresolved and are carried forward unchanged; no new issues were found and no duplicate comments were posted.

Fix these issues in Kilo Cloud

Previous review (commit 4f26c88)

Status: 4 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 2
Issue Details (click to expand)

WARNING

File Line Issue
packages/opencode/src/kilo-sessions/kilo-sessions.ts 411 Attach-on-boot failure after a successful spawn is only logged, no retry — can silently orphan a spawned session (the exact "no owner" failure this feature aims to prevent)
packages/opencode/src/kilo-sessions/session-spawner.ts 87 New any[] usage in the Deps.log type violates the repo's "avoid any" rule

SUGGESTION

File Line Issue
packages/opencode/src/kilo-sessions/session-spawner.ts 238 Log-file-handle close error is swallowed with no logging
packages/opencode/test/kilocode/cli/cmd/remote.test.ts 20 Test asserts on source-text/regex structure instead of driving real behavior
Files Reviewed (incremental — this pass)
  • packages/opencode/test/kilocode/kilo-sessions.test.ts - no new issues (fetch-mock hardening looks correct: replacing the blanket 200 stub with a throw for unexpected URLs is a good change, no other tests in the touched describe blocks rely on the old catch-all behavior)

Incremental review: only packages/opencode/test/kilocode/kilo-sessions.test.ts changed since the last review (narrowed the beforeEach fetch mocks in two describe blocks to reject unexpected URLs instead of returning a bogus 200). This is a correct, well-scoped test fix with no new issues. The 4 previously reported findings remain unresolved and are carried forward unchanged.

Fix these issues in Kilo Cloud

Previous review (commit d29521b)

Status: 4 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 2
Issue Details (click to expand)

WARNING

File Line Issue
packages/opencode/src/kilo-sessions/kilo-sessions.ts 411 Attach-on-boot failure after a successful spawn is only logged, no retry — can silently orphan a spawned session (the exact "no owner" failure this feature aims to prevent)
packages/opencode/src/kilo-sessions/session-spawner.ts 87 New any[] usage in the Deps.log type violates the repo's "avoid any" rule

SUGGESTION

File Line Issue
packages/opencode/src/kilo-sessions/session-spawner.ts 238 Log-file-handle close error is swallowed with no logging
packages/opencode/test/kilocode/cli/cmd/remote.test.ts 20 Test asserts on source-text/regex structure instead of driving real behavior
Files Reviewed (14 files)
  • .changeset/remote-instance-advertisement.md
  • packages/opencode/src/cli/cmd/remote.ts
  • packages/opencode/src/kilo-sessions/kilo-sessions.ts - 1 issue
  • packages/opencode/src/kilo-sessions/remote-protocol.ts
  • packages/opencode/src/kilo-sessions/remote-sender.ts
  • packages/opencode/src/kilo-sessions/remote-ws.ts
  • packages/opencode/src/kilo-sessions/session-spawner.ts - 2 issues
  • packages/opencode/test/kilocode/cli/cmd/remote.test.ts - 1 issue
  • packages/opencode/test/kilocode/kilo-sessions.test.ts
  • packages/opencode/test/kilocode/sessions/remote-protocol.test.ts
  • packages/opencode/test/kilocode/sessions/remote-sender.test.ts
  • packages/opencode/test/kilocode/sessions/remote-ws.test.ts
  • packages/opencode/test/kilocode/sessions/session-spawner.test.ts
  • script/check-opencode-promise-facades.ts

No command/shell injection issues found — the spawn engine builds argv arrays (no shell string interpolation) and only forwards schema-validated SessionIDs. Changeset present and appropriately user-facing. No new Config.Info fields requiring a cloud schema mirror.

Fix these issues in Kilo Cloud


Reviewed by claude-sonnet-5 · Input: 24 · Output: 8.6K · Cached: 668.1K

Review guidance: REVIEW.md from base branch main

@eshurakov

Copy link
Copy Markdown
Contributor

the handler pre-creates the session row and spawns a new CLI process to serve it

@iscekic why do we need to spawn a new CLI. Kilo already supports running multiple sessions at the same time and you can pass a project dir for it to work on. That's how VSCode and Agent Manager works from what I know.

@iscekic

iscekic commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator Author

@eshurakov I wanted process isolation, but I think you may have a good point. I'll double-check and refactor.

…on exit

Consolidate remote session handling into a single CLI process instead of
spawning one process per remote-created session (addresses the PR review):

- restore in-process create_session (accepts an absent sessionId and targets
  the connection directory); remove the session spawner, the
  KILO_REMOTE_ATTACH_SESSION attach-on-boot path, the child-advertisement gate,
  and their tests
- retain instance advertisement and fire one immediate out-of-band heartbeat on
  (re)connect when advertising, so a headless `kilo remote` host is discoverable
  without delay

Make /exit (wire command exit_cli, unchanged for compatibility) detach only the
target session instead of terminating the CLI:

- AttachedState.detach with a presence-suppression tombstone; detach also clears
  the target's SessionStatus so the negative-containment heartbeat fence resolves
  deterministically for busy/retry/offline sessions
- exit_cli handler verifies ownership, cancels the active prompt, detaches and
  awaits the detach heartbeat, then ACKs; the interactive RemoteExit callback is
  invoked only after the ACK when the last owned session exits; a headless
  `kilo remote` host stays alive and advertising at zero sessions
- add an optional canExitSession boolean to the list_commands v1 catalog
  (always true, independent of exitAvailable) so clients can detect safe
  session-exit semantics

History and stored sessions are preserved on exit.
@iscekic
iscekic force-pushed the feature/kilo-remote-process-spawn branch from 145d472 to 3df3157 Compare July 22, 2026 19:08
@iscekic iscekic changed the title feat(cli): spawn parallel CLI sessions as dedicated processes from kilo remote refactor(cli): shared-process remote sessions with safe per-session exit Jul 22, 2026
Integrate #12394 (mobile file attachments). Resolved additive heartbeat-wire
conflicts in remote-protocol.ts / remote-ws.ts by keeping both the K1
instance-advertisement field and the #12394 capabilities.attachments field;
combined the round-trip tests. Typecheck + focused kilo-sessions suites green.
@iscekic

iscekic commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator Author

(bot) Addressed — I refactored away the process-per-session spawner. kilo remote now owns concurrent sessions in a single process, exactly as you pointed out: create_session (with no session id) creates and attaches the session in-process against the connection's target directory, so the dedicated-process/KILO_REMOTE_ATTACH_SESSION machinery is gone.

As part of the same change, /exit (wire command exit_cli, name kept for deployed-client compatibility) now detaches only the target session instead of terminating the CLI: it verifies ownership, cancels the active prompt, clears status, detaches (awaiting the heartbeat fence so the relay/clients see it leave), then ACKs — history preserved, rollback on failure. The CLI only closes if the detached session was the last one owned by an interactive CLI; a headless kilo remote host stays alive and advertising at zero sessions. A canExitSession capability was added to the catalog so clients can detect the safe semantics (companion PR: Kilo-Org/cloud#4686).

The PR title/description and diff have been updated accordingly; the spawner and its tests are removed.

Comment thread packages/opencode/src/kilo-sessions/attached-state.ts Outdated
Comment thread packages/opencode/src/kilo-sessions/attached-state.ts Outdated
Comment thread packages/opencode/src/kilo-sessions/attached-state.ts
Comment thread packages/opencode/test/kilocode/sessions/attached-state.test.ts
Comment thread packages/opencode/src/kilo-sessions/remote-ws.ts
iscekic added 2 commits July 22, 2026 22:01
The K1 in-process exit_cli seam added a static `import { SessionPrompt }`
to kilo-sessions.ts. @/session/prompt evaluates KiloSessionPrompt at module
load, so the new static edge raced that init and left the namespace in TDZ,
crashing unrelated test files with 'undefined is not an object (evaluating
KiloSessionPrompt.shouldAskPlanFollowup)'. Defer to a dynamic import at the
single call site, mirroring remote-command.ts.
Address review findings on the shared-process session lifecycle:

- announce/detach no longer join the OPPOSITE in-flight operation. Joining
  detach's negative-containment fence made announce resolve success for a
  detached id (and vice versa: detach joined announce and resolved success
  while still attached, which exit_cli treats as license to ACK/close). Each
  path now joins only a same-kind in-flight op and, when the opposite op is
  in flight, awaits it to settle and then performs the real work.
- Failed-detach rollback now releases the suppression tombstone, so a
  still-attached session is not dropped by the next setPresence (the tombstone
  loop would otherwise remove the still-present id and never clear).
- Both catch/rollback branches now honor the lifecycle generation guard
  (mirroring the success path); a stale in-flight op that rejects after
  reset() no longer mutates the new lifecycle's presence/pending/suppressed
  sets (reset clears the same Set instances).

Adds regression tests for each fix, plus AC6f covering the remote-ws
detachSessionId negative-containment waiter.
@iscekic
iscekic enabled auto-merge (squash) July 22, 2026 23:58
@iscekic
iscekic merged commit aa22680 into main Jul 24, 2026
30 checks passed
@iscekic
iscekic deleted the feature/kilo-remote-process-spawn branch July 24, 2026 07:39
t7tran pushed a commit to t7tran/kilocode that referenced this pull request Aug 14, 2026
…xit (Kilo-Org#12327)

* refactor(cli): run remote sessions in one process with safe per-session exit

Consolidate remote session handling into a single CLI process instead of
spawning one process per remote-created session (addresses the PR review):

- restore in-process create_session (accepts an absent sessionId and targets
  the connection directory); remove the session spawner, the
  KILO_REMOTE_ATTACH_SESSION attach-on-boot path, the child-advertisement gate,
  and their tests
- retain instance advertisement and fire one immediate out-of-band heartbeat on
  (re)connect when advertising, so a headless `kilo remote` host is discoverable
  without delay

Make /exit (wire command exit_cli, unchanged for compatibility) detach only the
target session instead of terminating the CLI:

- AttachedState.detach with a presence-suppression tombstone; detach also clears
  the target's SessionStatus so the negative-containment heartbeat fence resolves
  deterministically for busy/retry/offline sessions
- exit_cli handler verifies ownership, cancels the active prompt, detaches and
  awaits the detach heartbeat, then ACKs; the interactive RemoteExit callback is
  invoked only after the ACK when the last owned session exits; a headless
  `kilo remote` host stays alive and advertising at zero sessions
- add an optional canExitSession boolean to the list_commands v1 catalog
  (always true, independent of exitAvailable) so clients can detect safe
  session-exit semantics

History and stored sessions are preserved on exit.

* fix(cli): break module-load cycle in remote session prompt-cancel

The K1 in-process exit_cli seam added a static `import { SessionPrompt }`
to kilo-sessions.ts. @/session/prompt evaluates KiloSessionPrompt at module
load, so the new static edge raced that init and left the namespace in TDZ,
crashing unrelated test files with 'undefined is not an object (evaluating
KiloSessionPrompt.shouldAskPlanFollowup)'. Defer to a dynamic import at the
single call site, mirroring remote-command.ts.

* fix(cli): correct AttachedState announce/detach concurrency and rollback

Address review findings on the shared-process session lifecycle:

- announce/detach no longer join the OPPOSITE in-flight operation. Joining
  detach's negative-containment fence made announce resolve success for a
  detached id (and vice versa: detach joined announce and resolved success
  while still attached, which exit_cli treats as license to ACK/close). Each
  path now joins only a same-kind in-flight op and, when the opposite op is
  in flight, awaits it to settle and then performs the real work.
- Failed-detach rollback now releases the suppression tombstone, so a
  still-attached session is not dropped by the next setPresence (the tombstone
  loop would otherwise remove the still-present id and never clear).
- Both catch/rollback branches now honor the lifecycle generation guard
  (mirroring the success path); a stale in-flight op that rejects after
  reset() no longer mutates the new lifecycle's presence/pending/suppressed
  sets (reset clears the same Set instances).

Adds regression tests for each fix, plus AC6f covering the remote-ws
detachSessionId negative-containment waiter.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants