Conversation
…ck --wait
Implements the four §3 fixes from the check-wait design doc:
- §3.1 Transport keepalive: long-poll RPCs (orchestration.check --wait) emit
`{"_keepalive":true}` frames every 10s so neither server nor client tears
the socket down on idle. A `longPoll` admission counter capped at 16 fails
fast with `runtime_busy` when saturated; an AbortController wired through
the RPC dispatcher cancels the inner waiter the moment the socket closes.
- §3.2 delivered_at split: push-on-idle now stamps `delivered_at` instead of
flipping `read`, so the check caller remains the sole consumer of its
queue. Adds a synchronous idempotent schema migration that hard-fails on
error.
- §3.3 inbox/check parity: `orchestration inbox --terminal <handle>` and
`orchestration check --all` agree on the same rows (sequence DESC, no
mark-read). `check --unread=false` kept for one release as a compat shim.
- §3.4 CLI heartbeat: `orca orchestration check --wait` emits JSON heartbeat
lines to stderr every 15s so Claude Code's Bash tool sees continuous
output and doesn't auto-background the subprocess.
Tests: extends runtime-rpc, orca-runtime, envelope-schema, orchestration
method, and formatter suites; adds a subprocess test that spawns the built
CLI and verifies stderr line-flushing, heartbeat cadence, and stdout
cleanliness end-to-end.
Co-authored-by: Orca <help@stably.ai>
- Preamble (#7, #15, #9): worker_done body ("3-sentence summary" + reportPath), BEHAVIOR RULE #1 forbidding AskUserQuestion, heartbeat every 5 minutes with taskId+dispatchId payload, AFTER YOU SEND grace window. - Schema v2 migration: adds 'heartbeat' to messages.type CHECK, adds dispatch_contexts.last_heartbeat_at, gated by user_version PRAGMA with transactional rebuild + explicit CREATE INDEX to avoid silent perf regress. - DB helpers: recordHeartbeat (dispatched-only), getStaleDispatches, getThreadMessagesFor (thread+handle scoped for ask). Co-authored-by: Orca <help@stably.ai>
Handle incoming 'heartbeat' messages by calling recordHeartbeat keyed on payload.dispatchId (strict — log-and-skip if missing, no taskId fallback so a straggler heartbeat from a previously-failed dispatch cannot mask a hung retry per §5.3.4). On every tick after the 10-minute threshold, emit one log per stale dispatched row — no auto-fail. Also threads dispatchId through buildDispatchPreamble so workers can attribute their heartbeats back to the correct dispatch context. Co-authored-by: Orca <help@stably.ai>
Adds a CLI verb that sends a decision_gate message and blocks on the coordinator's reply, scoped to the outbound message's thread. Group addresses (@ALL, @idle, …) are rejected — fan-out questions must use send --type decision_gate explicitly. --json emits bare single-line JSON (bypassing printResult) so workers can pipe `orca orchestration ask … --json | jq -r .answer` without unwrapping an RPC envelope; human mode prints just the answer. On timeout the verb exits 1 and returns {answer: null, timedOut: true}. This is the CLI surface BEHAVIOR RULE #1 in the dispatch preamble points workers at instead of AskUserQuestion. Co-authored-by: Orca <help@stably.ai>
…ispatch cross-ref, inbox --full Addresses four items from ORCHESTRATOR_FEEDBACK: - #5 preamble visibility: `dispatch-show --preamble` regenerates the preamble text for a task; `dispatch --inject --dry-run` previews without mutating state; `dispatch --return-preamble` echoes the injected preamble in the JSON response so coordinators can audit what a worker received. - #6 status enum validation: CLI rejects unknown `task-update --status` values with `invalid status '<x>', expected one of: pending, ready, dispatched, completed, failed, blocked` before the RPC's generic Zod message. Valid statuses are listed under Notes in `task-update --help`. - #13 task-list dispatch cross-ref: `task-list --json` now includes `assignee_handle` and `dispatch_id` for tasks in status=dispatched via a read-only LEFT JOIN on dispatch_contexts. Non-dispatched rows keep their legacy shape so existing consumers are unaffected. - #14 inbox body visibility: `inbox --full` prints body + payload verbatim; default output is unchanged (id/from/to/subject only). No DB migrations; join-only change on dispatch_contexts so the sibling preamble PR's `last_heartbeat_at` column addition will not conflict. Co-authored-by: Orca <help@stably.ai>
Addresses feedback #16 per DESIGN_DOC_STALE_BASE_FIX.md §0. Four v1 components coordinated by a single shared fetch cache on the runtime: 1. Concurrent-fetch-with-gate in UI create path: `createLocalWorktree` fires `git fetch` BEFORE the suffix loop / PR probe / path resolution, then awaits right before `addWorktree` so the new branch always spawns from a fresh remote tip. Renderer sees a two-phase spinner via the new `createWorktree:progress` IPC event. The cache is a `Map<repoPath::remote, Promise<void>>` + 30s success-only timestamp on `OrcaRuntimeService` (§7.1 — shared with dispatch). 2. Dispatch pre-flight drift guard in `Coordinator.dispatchTask`: probes `rev-list --left-right --count` against the target worktree and silently returns (preserves `ready`, no circuit-breaker burn) when `behind > 20` unless the task spec carries `allow-stale-base: true`. Parsing strips the flag so it never leaks into the worker's `--- TASK ---` block. 3. Preamble drift section: populated only when dispatch detected drift. Workers see `--- BASE DRIFT ---` with the N-most-recent subjects they don't have, so they can pull them in before running. 4. §3.3 Lifecycle: `.finally()` evicts Map entries on BOTH success and rejection; timestamp is written ONLY on success. Prevents a single DNS hiccup from wedging every future create on the repo until restart, and keeps the freshness window honest. Defers the DB `allow_stale_base` column (§0.2) and the create-time warn toast; both can layer in later without migration. Tests: 35 new/updated unit tests covering drift preamble, dispatch refusal, spec-text flag parsing, fetch Map eviction after rejection, freshness-window short-circuit, and concurrent-caller serialization. Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Orca <help@stably.ai>
This was referenced May 4, 2026
After consolidating the schema bump, fresh DBs are initialized directly at v3 via createTables(), so the v2→v3 ALTER TABLE is skipped on new installs and the prior test's stub never fired. Seed a v2-shape file on disk so the guarded ALTER actually runs and the "simulated migration failure" stub propagates as intended. Co-authored-by: Orca <help@stably.ai>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Bundles four orchestration improvement PRs into a single branch with merge conflicts already resolved and validated end-to-end against dev Orca.
Supersedes (to be closed in favor of this PR):
feat(orchestration): fix check --wait + read/unread semanticsfix(worktree): prevent stale-base worktree creation and dispatchfeat(orchestration): preamble hardening + heartbeat detector + ask verbfeat(orchestration): QoL bundle — preamble visibility, status enum, dispatch xref, inbox --fullWhy a bundle: the four PRs all touched
src/main/runtime/orchestration/db.ts(schema migrations),src/main/runtime/rpc/methods/orchestration.ts,src/cli/handlers/orchestration.ts, andsrc/cli/specs/orchestration.ts. Merging them one-by-one produced conflicts that required the same manual resolution in each subsequent PR. Shipping the already-resolved bundle avoids reviewers re-resolving the same conflicts and gives one clean review surface.What each commit contributes
2fad7bd5 feat(orchestration): transport keepalive + delivered_at split for check --wait— PR feat(orchestration): fix check --wait + read/unread semantics #1389d8b48699 feat(orchestration): preamble rules + heartbeat schema— PR feat(orchestration): preamble hardening + heartbeat detector + ask verb #1391 (part 1)1e77c830 feat(orchestration): coordinator heartbeat + stale detector— PR feat(orchestration): preamble hardening + heartbeat detector + ask verb #1391 (part 2)7e1c4a2b feat(orchestration): orca orchestration ask verb— PR feat(orchestration): preamble hardening + heartbeat detector + ask verb #1391 (part 3)cd4483c4 feat(orchestration): QoL bundle — preamble visibility, status enum, dispatch cross-ref, inbox --full— PR feat(orchestration): QoL bundle — preamble visibility, status enum, dispatch xref, inbox --full #13924df4b712 fix(worktree): prevent stale-base worktree creation and dispatch— PR fix(worktree): prevent stale-base worktree creation and dispatch #1390a8378053 merge: check-wait fixes— conflict resolutioneae7e1a2 merge: stale-base worktree fixes— conflict resolutionbf6e8612 merge: preamble + heartbeat + ask verb— conflict resolutionc1c06441 merge: QoL bundle— conflict resolutionKey conflict resolutions
db.tsschema: bumpedSCHEMA_VERSIONto 3 and composed migrations: v1→v2 adds heartbeat CHECK widening +last_heartbeat_at, v2→v3 addsdelivered_atfor DBs that reached v2 standalone. SingleBEGIN/COMMITwraps both steps. BothgetAllMessagesForHandle(check-wait) andgetThreadMessagesFor(ask verb) retained.orchestration.tsdispatch handler: preamble construction moved afterctxcreation sodispatchIdis the realctx.id(heartbeat attribution); dry-run path uses'ctx_dryrun'placeholder;dispatchShow --preambleusesctx?.id ?? 'ctx_preview'.orchestration.tsCLI handlers: kept bothstartCheckHeartbeat(stderr JSON keepalive) andTASK_STATUS_VALUESenum preflight; removed a duplicate'orchestration dispatch-show'map key introduced during conflict resolution.preamble.ts: kept preamble PR's worker framing (coordinator handle + task ID embedded), extended with stale-base PR'sbaseDriftoptional section appended before=== TASK ===.coordinator.tsdispatch call site: merged call passes bothdispatchId: dispatch.id(preamble PR) andtaskSpec: strippedSpec(stale-base PR, removesallow-stale-base: truefrom spec) and optionalbaseDrift.Test plan
pnpm exec vitest run src/main/runtime/orchestration— 109/109 pass (5 files)pnpm exec vitest run src/cli— 41/41 pass (5 files)pnpm exec vitest run src/main/runtime/orchestration/coordinator.test.ts— 23/23 pass (includes 6 stale-base cases)pnpm typecheck— clean across node/cli/web projectsorca-improvements-combinedworktree, dev runtime pid 66327):check --waitblocks until message arrives (verified: 5s wake-up on delayed send)ORCA_HEARTBEAT_INTERVAL_MSduring wait, stdout stays single JSON payloadBEHAVIOR RULE #1, 3×AskUserQuestionmentions (rule + rationale + closing), 5-minute cadence, heartbeat × 5, worker_done × 3, dispatchId × 2devModerendering switches preamble toorca-devCLI nameaskverb end-to-end: asker sends decision_gate, answerer replies, asker receives{answer, timedOut:false}— no thread_id bugcheck --allreturns read messages without re-markinginbox --terminal --fullshows body + payload per rowtask-update --status garbagefails preflight with clear "expected one of" enum messagedispatch-show --preamblerenders real preamble textdispatch --dry-run --return-preamblereturns preamble without creating a dispatch contextPost-merge cleanup
Made with Orca 🐋