Skip to content

Drive crush through its server so runs are live, not a black box - #3

Merged
gurnben merged 1 commit into
mainfrom
feat/crush-server-mode
Oct 20, 2026
Merged

gurnben merged 1 commit into
mainfrom
feat/crush-server-mode

Conversation

@gurnben

@gurnben gurnben commented Oct 20, 2026

Copy link
Copy Markdown
Member

Summary

crush runs went through crush run, whose quiet stdout made the monitor look frozen for minutes and reported no token usage — so budget bands and spend declarations never had data, and aborts were process kills. This PR drives crush through crush server with our UI as the client:

  • New SessionRunner harness interface: the runner prefers a managed session backend over the one-shot process, sharing the same event pipeline (journal, sink, budget bands, needs-input detection), and falls back to the process path when the backend cannot start.
  • crush serve client: spawns crush server on a private Unix socket, creates a workspace + session, holds the SSE stream open (that attachment is what keeps the workspace alive — discovered from crush's backend lifecycle), and drives the prompt with a RunID.
  • Live events: tool calls with arguments, finished thinking blocks, assistant text, and cumulative token usage from session updates — all mapped to normalized events. run_complete (RunID-correlated; foreign runs ignored) terminates the turn with its final text or error.
  • Real budget + spend: usage events feed the budget bar and the registry models spend row — previously impossible with crush run.
  • Clean aborts: ctrl+c / budget refuse / needs-input now cancel the remote session via the cancel endpoint before the server is torn down.
  • Fail-closed permissions: permission and question requests cannot be answered unattended; the run exits 2 naming the tool, with a note pointing at pre-allowing tools in a .crush/crushrc at the harness root.
  • Model override: POST /config/model (scope 1, model_type large) with the provider resolved from the crush models listing — model ids contain slashes, so the provider is taken from the listing line, not by splitting.

Verification

  • API surface verified live against a real crush server (v0.96.1): workspace/session creation, SSE envelope and part shapes, token-bearing session updates, RunID echo on run_complete.
  • 8 new tests against an in-process fake speaking the same v1 API: happy path (tool/thinking/text/usage events, result text), permission fail-closed with remote cancel, run_complete errors, foreign-run filtering, model override request shape, and runner-level integration (events to sink, usage-driven spend row, exit codes 0/1/2).
  • Live end-to-end smoke through the real binary: server spawned and reaped, tool calls and usage streamed, a permission request correctly failed closed with exit 2, journal complete, no orphaned processes.
  • Full suite with race detector, vet, staticcheck clean (48 tests).

💘 Generated with Crush

crush runs previously went through `crush run`, whose quiet stdout made
the monitor look frozen for minutes at a time and reported no token
usage, so budget bands and spend declarations never had data and aborts
were process kills.

The crush adapter now implements the new SessionRunner interface: it
spawns `crush server` on a private Unix socket, creates a workspace and
session, holds the SSE event stream open (which is what keeps the
workspace alive), and drives the prompt through the agent endpoint with
a RunID. The event stream maps onto the runner's normalized events: live
tool calls with their arguments, finished thinking blocks, assistant
text, and cumulative token usage — so the monitor shows what the agent
is doing while it does it, the budget bar moves on real numbers, spend
rows are declared, and run_complete (correlated by RunID, foreign ones
ignored) terminates the run with its final text or error. Aborts and
budget refusals now cancel the remote session through the cancel
endpoint before tearing the server down.

Permission and question requests cannot be satisfied unattended; they
fail closed (exit 2) naming the tool, with a note pointing at
pre-allowing tools in a .crush/crushrc at the harness root. When the
server cannot start, the run falls back to the old `crush run` path.

The API surface was verified live against a real server (v0.96.1) and
the whole flow is tested against an in-process fake speaking the same
v1 API: happy path, permission fail-closed with remote cancel,
run_complete errors, foreign-run filtering, and model override via the
config/model endpoint (scope 1, model_type large, provider resolved
from the `crush models` listing).

💘 Generated with Crush

Assisted-by: Crush:rits/zai-org/glm-5-3
@gurnben
gurnben requested a review from a team as a code owner October 20, 2026 20:13
@gurnben
gurnben merged commit 194a45e into main Oct 20, 2026
2 checks passed
@gurnben
gurnben deleted the feat/crush-server-mode branch October 20, 2026 20:14
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.

1 participant