kyo-experiment: a Kyo session with real interrupt, and the SDK verdict (slice 02-kyo-session) #88

Merged
mph merged 28 commits from kyo-experiment into main 2026-10-16 09:18:51 +00:00
Member

Slice 02-kyo-session (kyo-experiment, step 1: the SDK)

Goal. Hold a live, multi-turn conversation with the real Claude CLI through a Kyo session (send while a turn runs, interrupt a turn for real and keep talking, see completion as state), then read the Kyo code beside zio's session and write the SDK verdict.

Fit test. Michal chats and interrupts in the terminal runner (./mill -i kyo.runMain works.iterative.claude.kyo.ChatRunner), reads Kyo beside zio, and writes the verdict with file:line on both sides. Fit called by Michal on 2026-10-01.

Verdict (Michal). Hold the adoption of Kyo for now. Watch Kyo for updates in the areas where it is missing (a live stdin write, a sliding fan-out, an exact wait on a Signal), and possibly contribute. Go on to the Kyo UI slice. The evidence is the reading index at the top of project-management/efforts/kyo-experiment/slices/02-kyo-session/friction.md.

Done

  • Kyo Session: send, interrupt over the control protocol, state / resultsSeen, awaitResultAfter / sendAndAwait, info, terminated, events, stateChanges, death with the stderr tail, and the process tree ended with the scope. Spawn and tree kill are shared with the query path (CliProcess).
  • LossyBroadcast: each events subscriber has its own buffer and drops its oldest messages. Kyo's back-pressured Hub stalled every reader behind one slow reader.
  • SignalAwait: one wait on a Signal with a repair interval, used by the session and the chat. The chat waits on changes, with no sleep loops.
  • Terminal chat runner (Chat, ChatCommand, ChatRunner) with /interrupt and /quit.
  • Tests: SessionIntegrationTest (18), SessionErrorIntegrationTest (2), SessionInterruptIntegrationTest (6) and ChatIntegrationTest (12) against bash stand-ins. Real-CLI SessionE2ETest (4) and StopAndKeepTalkingE2ETest (1), each in an isolated config directory that is deleted after the test. New unit suites for LossyBroadcast, SignalAwait, SessionRules, ChatCommand and SessionDisplay.
  • core: SessionState and InterruptOutcome derive CanEqual (approved, no change in behaviour).

Review

Seven skills: style, testing, security, scala3, composition, architecture and error-handling. They found no critical issues and 11 warnings. Five were fixed in 0bbc81d:

  • A reader panic was read as the end of input.
  • The E2E tests left a copy of the login in /tmp.
  • Three itests are now exact.
  • The resumed query must stay in the stopped session.

The other warnings are filed as issues. Report: project-management/efforts/kyo-experiment/slices/02-kyo-session/review-slice-20261001-1525.md.

Follow-ups

  • #81 Session and stream itests: timing-dependent and always-true assertions
  • #82 Session errors do not say what happened (a refused interrupt reads as success). Affects zio.
  • #83 A session whose stdout reader fails while the CLI lives can wait for ever on its end (related to #75)
  • #84 CLI output reaches the terminal and the logs uncleaned
  • #85 Kyo adapter boundaries

Parked (moved to the effort map)

  • Conversation archive → kyo-archive
  • Sessions host registry, and the "is the CLI working?" activity state → kyo-ui-chat
  • Extract the control-request protocol out of SessionProcess (large refactor)
  • direct session parity bar, effectful baseline, publication, full zio unit-suite port

Gates

  • __.compile, __.test and kyo.itest (66/66, including the real-CLI E2E tests) pass with no warnings.
  • In a parallel __.itest run, timing tests in effectful and direct fail under load and pass alone (#81). This branch does not change effectful or direct.

🤖 Generated with Claude Code

## Slice 02-kyo-session (kyo-experiment, step 1: the SDK) **Goal.** Hold a live, multi-turn conversation with the real Claude CLI through a Kyo session (send while a turn runs, interrupt a turn for real and keep talking, see completion as state), then read the Kyo code beside `zio`'s session and write the SDK verdict. **Fit test.** Michal chats and interrupts in the terminal runner (`./mill -i kyo.runMain works.iterative.claude.kyo.ChatRunner`), reads Kyo beside `zio`, and writes the verdict with file:line on both sides. Fit called by Michal on 2026-10-01. **Verdict (Michal).** Hold the adoption of Kyo for now. Watch Kyo for updates in the areas where it is missing (a live stdin write, a sliding fan-out, an exact wait on a `Signal`), and possibly contribute. Go on to the Kyo UI slice. The evidence is the reading index at the top of `project-management/efforts/kyo-experiment/slices/02-kyo-session/friction.md`. ## Done - Kyo `Session`: `send`, `interrupt` over the control protocol, `state` / `resultsSeen`, `awaitResultAfter` / `sendAndAwait`, `info`, `terminated`, `events`, `stateChanges`, death with the stderr tail, and the process tree ended with the scope. Spawn and tree kill are shared with the query path (`CliProcess`). - `LossyBroadcast`: each `events` subscriber has its own buffer and drops its oldest messages. Kyo's back-pressured `Hub` stalled every reader behind one slow reader. - `SignalAwait`: one wait on a `Signal` with a repair interval, used by the session and the chat. The chat waits on changes, with no sleep loops. - Terminal chat runner (`Chat`, `ChatCommand`, `ChatRunner`) with `/interrupt` and `/quit`. - Tests: `SessionIntegrationTest` (18), `SessionErrorIntegrationTest` (2), `SessionInterruptIntegrationTest` (6) and `ChatIntegrationTest` (12) against bash stand-ins. Real-CLI `SessionE2ETest` (4) and `StopAndKeepTalkingE2ETest` (1), each in an isolated config directory that is deleted after the test. New unit suites for `LossyBroadcast`, `SignalAwait`, `SessionRules`, `ChatCommand` and `SessionDisplay`. - `core`: `SessionState` and `InterruptOutcome` derive `CanEqual` (approved, no change in behaviour). ## Review Seven skills: style, testing, security, scala3, composition, architecture and error-handling. They found no critical issues and 11 warnings. Five were fixed in `0bbc81d`: - A reader panic was read as the end of input. - The E2E tests left a copy of the login in `/tmp`. - Three itests are now exact. - The resumed query must stay in the stopped session. The other warnings are filed as issues. Report: `project-management/efforts/kyo-experiment/slices/02-kyo-session/review-slice-20261001-1525.md`. ## Follow-ups - [ ] #81 Session and stream itests: timing-dependent and always-true assertions - [ ] #82 Session errors do not say what happened (a refused interrupt reads as success). Affects `zio`. - [ ] #83 A session whose stdout reader fails while the CLI lives can wait for ever on its end (related to #75) - [ ] #84 CLI output reaches the terminal and the logs uncleaned - [ ] #85 Kyo adapter boundaries ## Parked (moved to the effort map) - Conversation archive → `kyo-archive` - `Sessions` host registry, and the "is the CLI working?" activity state → `kyo-ui-chat` - Extract the control-request protocol out of `SessionProcess` (large refactor) - `direct` session parity bar, `effectful` baseline, publication, full `zio` unit-suite port ## Gates - `__.compile`, `__.test` and `kyo.itest` (66/66, including the real-CLI E2E tests) pass with no warnings. - In a parallel `__.itest` run, timing tests in `effectful` and `direct` fail under load and pass alone (#81). This branch does not change `effectful` or `direct`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Session, SessionProcess (one stdout reader, state as a Signal, events over a Hub),
ClaudeCode.session in the caller's Scope, the process helpers shared with the
query path, and the session integration tests against stand-in CLIs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ChatRunner (KyoApp) reads lines from stdin, sends them as user input, prints each
message and state change as it arrives, and leaves on /quit or end of input once
what was said has been answered. Line parsing, state display and the conversation
loop are tested apart from the terminal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The interrupt registers a pending request, re-checks liveness, writes the control
request and awaits the response with a timeout; the reader routes control responses
and fails pending requests when the session ends. The chat's /interrupt shows the
outcome and keeps reading.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Kyo's Hub is back-pressured, so one consumer that stopped reading stalled
every other one, and its close discarded what each listener still held.
LossyBroadcast gives each subscriber its own Channel; a publish never waits
and a full buffer loses its oldest value. A stalled-subscriber itest and five
unit tests, all run red against the Hub first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SignalAwait.until holds the read-then-wait loop with its repair interval,
the form Kyo's own Signal.observe uses, since the safe Signal API has no
exact wait. The session's changedFrom calls it. The chat drops its 50 ms
and 20 ms sleep loops: answered is a filter on stateChanges, quiet is a
timeout on the next change of one SignalRef, and shown uses SignalAwait.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The two death cases move out of SessionIntegrationTest into their own file
with zio's names. The mid-turn exit now also asserts that terminated resolves
and that the state records the end, as zio does. Both fail when the end is
recorded without its exit code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SessionE2ETest ports zio's four cases: one turn, info after a turn, context
across two turns, and a real interrupt the session survives.
StopAndKeepTalkingE2ETest stops a query as its first tool call opens, with
takeWhile ending the stream and its scope ending the CLI, then resumes the
session and gets an answer. All five pass against the real CLI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The friction log opens with each session concern at file:line in zio and in
Kyo, the friction entry that holds the evidence, the sizes, and the test
suites side by side. Two entries record what the E2E ports showed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Kyo's Signal compares values to notify only on a change, so the Kyo session
had to give SessionState a local CanEqual. core now derives it for both
session model types; behaviour is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Chat.speak re-raises a panic from the reader instead of reading it as the
end of input (new itest, red without the fix). The E2E tests run in a config
directory of their own that is deleted with its scope and holds the login
copy owner-only; the resumed query must answer in the stopped session. Three
itests assert what their names promise: the exact error of an ended
session's interrupt, one result for two merged sends, and a blank line
checked against a real line answered after it. Findings shared with zio, or
needing core, are in the effort's FOLLOW-UPS.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
workflow: Triage lessons for kyo-experiment
All checks were successful
CI / Check Formatting (pull_request) Successful in 1m2s
CI / Compile (pull_request) Successful in 2m5s
CI / Check Linting (pull_request) Successful in 1m36s
CI / Run Tests (pull_request) Successful in 1m58s
CI / Run Integration Tests (pull_request) Successful in 1m57s
CI / Check Bundle Size (pull_request) Successful in 45s
ce8b521133
Follow-ups filed: 5 (CC-81, CC-82, CC-83, CC-84, CC-85; 12 lines grouped), commented: 1 (#75), struck: 0
Lessons: 0 approved, 1 rejected, 0 still pending

Rejected:
- A compile after an interactive Mill run fails with "Connection refused": cause not understood

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
workflow(sl): close kyo-experiment batch (flush parked items)
All checks were successful
CI / Compile (pull_request) Successful in 1m30s
CI / Check Formatting (pull_request) Successful in 44s
CI / Check Linting (pull_request) Successful in 1m35s
CI / Run Tests (pull_request) Successful in 1m58s
CI / Check Bundle Size (pull_request) Successful in 52s
CI / Run Integration Tests (pull_request) Successful in 1m46s
a91208ccda
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mph merged commit 807cc2cd9b into main 2026-10-16 09:18:51 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
iterative-works/claude-code-query!88
No description provided.