kyo-experiment: a Kyo adapter for the one-shot query (slice 01-kyo-query) #80

Merged
mph merged 38 commits from kyo-experiment into main 2026-10-01 09:05:40 +00:00
Member

One --local slice of the kyo-experiment effort: 01-kyo-query.

Slice 01-kyo-query — Kyo one-shot query

Goal: run a one-shot query through a Kyo module against the real CLI, watch it stream, and read it beside zio's and direct's query well enough to write trusted verdict reasons on the three axes.
Fit test: Michal runs ./mill kyo.run "<prompt>", reads the code beside the baseline, and writes the first SDK-verdict reasons, each at file:line on both sides.
Fit read (2026-09-29): "the basic Kyo code is comparable and for me esthetically pleasing" — too soon to decide the stack; the effort continues.

Done

  • kyo Mill module, unpublished: Kyo 1.0.0-RC7 with kyo-system, Scala 3.9.0, Java 25 (KX-D6).
  • ClaudeCode.query as Stream[Message, Async & Abort[CLIError] & Scope]: the consumer's scope ends the CLI and its whole process tree (KX-D5); querySync / queryResult / ask over it.
  • CLI discovery through Env[ExecutableLookup]; stderr beside stdout; typed errors for exit code, parse errors, timeout (authoritative), cwd and environment checks.
  • A runner, ./mill kyo.run "<prompt>", one line per message as it arrives.
  • Tests on kyo-test: unit (kyo.test), stand-in process and live file-system integration plus real-CLI E2E (kyo.itest).
  • core, approved by Michal, shared by all adapters: CLIArgumentBuilder.buildQueryArgs (KX-D2), Message.answerText (KX-D3) answering with the run's result as claude -p does (KX-D4), Message and ContentBlock derive CanEqual.
  • Findings for the verdict, with file:line on both sides: project-management/efforts/kyo-experiment/slices/01-kyo-query/friction.md (friction log and reading index), incl. RC7's scope close waiting for fibers, and what kyo-ai already does with the Claude CLI.

Behaviour change in the published adapters: queryResult and ask in zio, direct and effectful now return the result message's text, else the last assistant text (KX-D4), where they returned the first assistant message's first text block.

Review

Close review with 8 skills: project-management/efforts/kyo-experiment/slices/01-kyo-query/review-slice-20260929-091125.md. One critical finding rejected with evidence (direct/effectful refuse structured input before any process). Fixed: 0f141b9 (prompt out of the start log), e30e4f1 (no root paths without HOME), 73c3570 and 1e3dd83 (tests pinning tree kill, stdin, a split character, every branch of answerText). Gate: __.compile, __.test, kyo.itest (24), __.checkFormat, no warnings.

Follow-ups (parked; flushed to the effort map)

  • Scala functions as tools through an in-process MCP server
  • Typed failure classes from the result event's api_error_status / error
  • Typed errors carry the whole command, so the prompt; stderr logged whole
  • answerText returns an isError result as the answer
  • Pure checks copied per adapter (cwdError, environmentError, query stdin, install paths) → core
  • Env names by deny list; launch failure as ProcessExecutionError(-1, …)
  • Per-adapter queryResult test and a release note for KX-D4; ARCHITECTURE.md: core stays on 3.3
  • kyo: timeout can win after a clean finish; which failing reads as "Node.js not found"; runner exits 0 on usage error; ClaudeCode.query wiring tested only by E2E; "timed out after 0 seconds"
  • Sessions, transcript archive → kyo-session
  • Filed: #79 (direct may leave the CLI running on early stop), iterative-works/iw-project-support#4 (house LTS version)

🤖 Generated with Claude Code

One `--local` slice of the `kyo-experiment` effort: `01-kyo-query`. ## Slice 01-kyo-query — Kyo one-shot query **Goal:** run a one-shot query through a Kyo module against the real CLI, watch it stream, and read it beside `zio`'s and `direct`'s `query` well enough to write trusted verdict reasons on the three axes. **Fit test:** Michal runs `./mill kyo.run "<prompt>"`, reads the code beside the baseline, and writes the first SDK-verdict reasons, each at file:line on both sides. **Fit read (2026-09-29):** "the basic Kyo code is comparable and for me esthetically pleasing" — too soon to decide the stack; the effort continues. ## Done - `kyo` Mill module, unpublished: Kyo 1.0.0-RC7 with `kyo-system`, Scala 3.9.0, Java 25 (KX-D6). - `ClaudeCode.query` as `Stream[Message, Async & Abort[CLIError] & Scope]`: the consumer's scope ends the CLI and its whole process tree (KX-D5); `querySync` / `queryResult` / `ask` over it. - CLI discovery through `Env[ExecutableLookup]`; stderr beside stdout; typed errors for exit code, parse errors, timeout (authoritative), cwd and environment checks. - A runner, `./mill kyo.run "<prompt>"`, one line per message as it arrives. - Tests on kyo-test: unit (`kyo.test`), stand-in process and live file-system integration plus real-CLI E2E (`kyo.itest`). - `core`, approved by Michal, shared by all adapters: `CLIArgumentBuilder.buildQueryArgs` (KX-D2), `Message.answerText` (KX-D3) answering with the run's result as `claude -p` does (KX-D4), `Message` and `ContentBlock` derive `CanEqual`. - Findings for the verdict, with file:line on both sides: `project-management/efforts/kyo-experiment/slices/01-kyo-query/friction.md` (friction log and reading index), incl. RC7's scope close waiting for fibers, and what `kyo-ai` already does with the Claude CLI. **Behaviour change in the published adapters:** `queryResult` and `ask` in `zio`, `direct` and `effectful` now return the result message's text, else the last assistant text (KX-D4), where they returned the first assistant message's first text block. ## Review Close review with 8 skills: `project-management/efforts/kyo-experiment/slices/01-kyo-query/review-slice-20260929-091125.md`. One critical finding rejected with evidence (`direct`/`effectful` refuse structured input before any process). Fixed: 0f141b9 (prompt out of the start log), e30e4f1 (no root paths without HOME), 73c3570 and 1e3dd83 (tests pinning tree kill, stdin, a split character, every branch of `answerText`). Gate: `__.compile`, `__.test`, `kyo.itest` (24), `__.checkFormat`, no warnings. ## Follow-ups (parked; flushed to the effort map) - [ ] Scala functions as tools through an in-process MCP server - [ ] Typed failure classes from the result event's `api_error_status` / `error` - [ ] Typed errors carry the whole command, so the prompt; stderr logged whole - [ ] `answerText` returns an `isError` result as the answer - [ ] Pure checks copied per adapter (`cwdError`, `environmentError`, query stdin, install paths) → `core` - [ ] Env names by deny list; launch failure as `ProcessExecutionError(-1, …)` - [ ] Per-adapter `queryResult` test and a release note for KX-D4; ARCHITECTURE.md: `core` stays on 3.3 - [ ] `kyo`: timeout can win after a clean finish; `which` failing reads as "Node.js not found"; runner exits 0 on usage error; `ClaudeCode.query` wiring tested only by E2E; "timed out after 0 seconds" - [ ] Sessions, transcript archive → `kyo-session` - Filed: #79 (`direct` may leave the CLI running on early stop), iterative-works/iw-project-support#4 (house LTS version) 🤖 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>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The stream owns the process: stdout is cut into lines by a Pipe, each line
is parsed with its number, stderr is read beside it, and the scope ends the
CLI's whole tree when the stream stops. A fired timeout is authoritative.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
./mill kyo.run "<prompt>" sends the prompt through ClaudeCode.query and
prints one line per message as it arrives.

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>
CLIArgumentBuilder.buildQueryArgs replaces four copies in zio, direct,
effectful and kyo. direct and effectful refuse structured input before
they use the arguments, so their behaviour is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Code compiled with strictEquality, as Kyo recommends, can then match
the model's case objects.

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>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Message.answerText replaces four copies in zio, direct, effectful and kyo,
with the rule they shared: the first text block of the first assistant
message, or empty.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The CLI sends each content block as its own assistant message, so a run
that thinks or calls a tool first opens with an assistant message holding
no text, and the answer rule returned an empty string. Message.answerText
now answers with the result message's text, as claude -p prints it; a run
without one falls back to its last assistant text. The tests run on lines
captured from the CLI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The kyo test modules use Kyo's own framework (kyo-test 1.0.0-RC6, sbt test
interface): a leaf is a Kyo computation, so the runAndBlock bridge goes away.

Running the leaves inside a longer-lived fiber exposed two defects the munit
bridge hid:

- query's stream held its own Scope.run. Kyo closes that scope only when the
  wrapped computation completes, so a consumer that stopped early (take) left
  the CLI and its tools running until its whole fiber ended. The stream now
  carries Scope in its effect row, as Kyo's own Process.stdout does; the
  consumer's scope ends the process, and the collectors close it.
- a timeout's kill made the CLI exit 137, and completion logged that exit as
  a failure of its own beside the timeout. An exit the timeout caused is no
  longer reported.

The timing leaf asserts the gap between the two arrivals, which does not
depend on how long the stand-in took to start.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A module compiled with strict equality can compare content blocks with ==,
as it already can messages. The Kyo parser test compares the parsed content
directly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RC7 moves Process, Command, Path and System into kyo-system, reads a Path
through the PathRead effect, adds a kyo.FileSystem that the wildcard import
brings over our own, and subtracts instants into a Maybe.

RC7's Fiber.init waits for the fiber to end when its scope closes. The
stderr reader sits in a blocking read that ends only when the process tree
has closed stderr, so the tree kill is now registered after the reader and
runs before it, and ends the CLI as well as its descendants.

New integration tests cover the live executable-file check, `which`, and
the working-directory check against real paths.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Approved by Michal (KX-D6).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The service finds a program to run: where a command resolves on PATH, and
whether a path is a file this user may run. RC7's kyo.FileSystem is a
storage backend for Path, so the old name read as Kyo's and needed an
import that hid Kyo's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Approved by Michal (KX-D6).

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 command ends with the prompt, and the start log printed it whole. It now
names the executable and the number of arguments.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A missing HOME became an empty string, so discovery probed /.local/bin/claude
and the like. The paths under HOME are now left out when it is not set.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The UTF-8 test now splits inside a character. The timeout test ends within
10 s only if the kill ends the CLI's child too. A structured query's user
message is read back from a real process's stdin. The stand-in's output file
helper is named for what it holds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
answerText's tests now compare exact text, cover a result with no text, text
blocks joined one per line, and the last of several results. The zio
ClaudeCode test no longer names the tests that moved to core.

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 1m16s
CI / Compile (pull_request) Successful in 2m7s
CI / Check Linting (pull_request) Successful in 1m29s
CI / Run Tests (pull_request) Successful in 1m46s
CI / Run Integration Tests (pull_request) Successful in 1m50s
CI / Check Bundle Size (pull_request) Successful in 46s
ae58023223
Follow-ups filed: 2 (iterative-works/iw-project-support#4, CC #79), commented: 0, struck: 0
Lessons: 1 approved, 0 rejected, 0 still pending

Promoted:
- Two Mill task names in one command run only the first → kanon note #399 (store: guide/mill_operations.md)

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 1m23s
CI / Check Formatting (pull_request) Successful in 42s
CI / Run Tests (pull_request) Successful in 1m45s
CI / Check Linting (pull_request) Successful in 1m19s
CI / Run Integration Tests (pull_request) Successful in 1m44s
CI / Check Bundle Size (pull_request) Successful in 48s
02c4c0e6ca
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mph merged commit 2f554a4b94 into main 2026-10-01 09:05:40 +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!80
No description provided.