mirror of
https://github.com/alphaXiv/OpenResearch.git
synced 2026-11-11 09:59:34 +08:00
cec874e47cd22af0735db67cd24efb149ecb2908
172
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
cec874e47c | Simplify project files as artifacts (#145) | ||
|
|
07d3221f3e | Increase dashboard typography (#143) | ||
|
|
3e934765e1 |
Add Ray Jobs compute backend (v0.1.88) (#141)
* Add Ray Jobs as a first-class local compute backend. Submit via the Ray Jobs API with Settings → Compute → Ray, optional cpu/gpu/mem flavors, and the same supervise/log/cancel path as other backends. Co-authored-by: Cursor <cursoragent@cursor.com> * Review fixes: sync cloud skill, reject inapplicable flags, panic-safe log tail, fmt/clippy, dist rebuild (v0.1.87) * Round-1 review fixes: synced env for ray jobs, server-plane ray arm, help text, honest configured flag, resolver/preflight simplification, NaN guard, address validation * Round-2 review fixes: GONE debounce for purged ray jobs, cancel re-issue, prefix-guarded log tailer, drop SubmitResponse/Explicit, single URL normalization point * Round-3 nits: dirty-flag log tailer invariant, slurm-shaped cancel gate on transitions, doc accuracy * Rebase on v0.1.87; bump to v0.1.88 and rebuild ui/dist * Use official Ray and OpenResearch marks in BackendLogos --------- Co-authored-by: Sébastien Fabbro <sebfabbro@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>v0.1.88 |
||
|
|
0ddf5027b4 | docs: add OpenResearch account signup | ||
|
|
d5d1e02128 |
chore: ignore local agent skills (#142)
* chore: add repository-scoped agent skills * chore: keep agent skills local |
||
|
|
5e25afaf98 |
Surface silently-dropped chat turns (v0.1.87) (#139)
* Surface silently-dropped chat turns (v0.1.86) A sent message could vanish with no response and no error. Evidence from the live DB: user messages persisted with no assistant reply at all — the claude turn loop waits on child events with no watchdog, so a wedged resident child (seen: expired-OAuth refresh hang) spins forever; the user's only recourse, Stop/Escape, aborted the turn task without leaving any trace; and a failed send POST was swallowed by the UI catch. - claude adapter: watchdog the event wait (120s to first event, 30min between events, matching the codex TURN_WATCHDOG), exempting turns held on a pending mcp-gate approval; on trip, kill the child and surface a visible error (next message resumes via --resume). - ChatHost::interrupt_by_user: the Stop/Escape path now persists an 'Interrupted' marker row, so a stopped turn no longer reads as 'orx did nothing'. Internal interrupts (plan resume, deletes) stay silent. - UI: a failed send now renders a local error row instead of silently clearing busy; chat SSE reconnects re-seed sessions/busy and the active transcript (chat frames are edge-only, gap frames are lost). * Address review: verified send-failure surfacing, watchdog guard arm, shared consts - send() catch: restore draft/attachments even when session creation failed; an explicit 'session is busy' rejection always surfaces (that busy is someone else's turn); a network failure probes server-side busy first and undoes the restore when the turn is real. - claude watchdog: permission re-arm is a guard arm that no longer promotes the first-event deadline; error reports minutes on the mid-turn path and the login hint only on the first-event path. - TURN_WATCHDOG hoisted to harness/mod.rs, shared with codex. - interrupt_by_user: timestamp captured before the abort; WirePart::tool constructor (also used by push_error). - Reconnect repair: shared syncSessionList (deletedIds tombstones, contextUsage merge, seenTitles baseline), seedBusy scoped by known ids, transcript reseed retries once via msgGen when a flush raced it. * Guard claude turns against --resume replay output (echo boundary) Folds in (and supersedes) the unmerged daniel/fix-swallowed-messages branch: a --resume respawn can replay prior-session/startup output before touching the new message — fatally a stale result event, which ended the turn instantly with zero parts while the real reply streamed into a dropped channel. --replay-user-messages makes the child echo the submitted message; the turn loop now skips everything before the echo. Review round (2x empirical against claude 2.1.212): echo is byte-exact and stdin-scoped (no history replay, so no same-text false positive); pre-echo lines count as watchdog proof-of-life (API backoff after boot must not be killed at 120s; only total silence keeps the fast wedge budget + login hint); pre-echo system/init harvests the session id for first-turn interrupts; module docs pin the echo contract. * Reconcile deleted sessions on SSE reconnect; repair after a failed first connect Addresses review findings 2 and 3 on PR #139: - A session deleted while SSE was down lost its chat.session.deleted frame for good: syncSessionList removed the sidebar row but left the cached transcript, loadedSessions, activeId, and — via the known- scoped seedBusy merge — a stale busy flag. Now the pre-fetch snapshot of shown sessions is diffed against the authoritative list and every missing id gets the full forgetSession cleanup, before seedBusy so the ghost's flag can't be carried forward. The snapshot is taken before the fetch (a session created mid-flight can't be tombstoned), and the project-change reset clears the snapshot ref synchronously (the old project's rows must not read as deleted under the new one). - 'reconnected' now also fires when the first successful EventSource open follows a FAILED first connect (page loaded while the backend was briefly down) — the initial fetches likely failed too, so the panel repaired itself only on later drops, never on that first recovery. A clean first open still emits nothing. Verified live on the slot-8 dev stack: session row deleted from the DB while the backend was down; on reconnect (same page, no reload) the row, ghost transcript, active selection, and busy flag all clear.v0.1.87 |
||
|
|
286d9681d5 |
Stamp chatSessionId on server-plane experiment creation (#137)
Server-plane experiment creation now forwards the launching chat session so openresearch.sh can attribute experiments to the agent conversation that asked for them. Splits ORX_LOCAL_SESSION out of ORX_CHAT_SESSION_ID: the cloud box's opencode plugin exports the latter for attribution, which would otherwise have made in_local_session() true on every cloud box and served the Local skill bodies in place of the Full ones. |
||
|
|
bed1b31aaf |
Resolve chat file links via the files dir; fix \boxed math rendering (v0.1.86) (#140)
- FileViewer: a checkout-sourced tab whose path misses the worktree/clone now
falls back to the project's files-dir endpoints (agents link files-dir
reports with checkout-relative paths); renders via ReportMd with a note
naming the checkout that missed. Branch-ref tabs never fall back.
- Pin katex to ^0.16.22 so the imported stylesheet matches the katex version
rehype-katex actually renders with (0.16.47); the 0.18 stylesheet lacked
rules for 0.16 class names, collapsing \boxed{} to a 1px vertical line.
v0.1.86
|
||
|
|
33feb2e1ac |
Don't mint empty reasoning parts; fix tool-group rendering artifacts (v0.1.85) (#136)
* Don't mint empty reasoning parts; fix tool-group rendering artifacts (v0.1.85) Fable's encrypted thinking streams empty thinking_delta events (payload in signature_delta) and per-block assistant events with thinking:"" — orx stored these as invisible empty reasoning parts, which split consecutive tool calls into single-row groups; an expanded singleton group then painted the identical tool line twice (summary + row). - claude.rs: skip empty stream deltas and empty thinking blocks (main loop and sub-agent routing) so empty parts are never created - ChatPanel renderParts: parts that render nothing no longer break a tool run (repairs already-stored transcripts) - ToolGroup: a single-tool group renders its ToolRow directly — no duplicate summary line; has-error color parity via styles.css - test: encrypted_thinking_mints_no_empty_reasoning_parts (captured live from the claude CLI) * Multi-tool group summary is always the count A running group is always expanded, so a summary echoing the running tool's line always sat directly above the identical row. 'Used N tools' never duplicates. Rebuilt ui/dist. * Review round: shared visibility predicate, sub-agent per-block offset fix, nits - ChatPanel: extract partIsVisible as the single invisible-part predicate, shared by messageHasVisibleContent and renderParts; renderParts now also skips resolved permission cards (Codex inlines them into the turn's parts, where they split tool runs the same way empty reasoning parts did) - SubagentTranscript: gate the empty state on rendered output, not raw part count, so stored all-invisible transcripts read as empty - claude.rs: guard empty text blocks like thinking blocks (both loops); comment accuracy passes (delta guard scope, cross-layer back-reference, ToolGroup doc, CSS child-combinator note) - apply_subagent_blocks: fix per-block offset semantics — .skip(start) treated the cumulative message offset as an index into this event's (single-element) array, silently dropping every sub-agent block after a message's first; now mirrors the main loop's start+n id shift. Caught by the new subagent_empty_thinking_mints_no_child_parts test; also added offset assertion commentv0.1.85 |
||
|
|
5228228b2d |
Auto-generate and animate chat session titles (v0.1.84) (#135)
* Auto-generate chat session titles from the first message
Chat sessions were titled with a 64-char truncation of the first user
message. Now the first message also kicks off a short-lived headless
harness child that names the session, in parallel with the turn.
- store: new chat_sessions.title_source column ('fallback' / 'generated'
/ 'user'; NULL on legacy rows) via the best-effort ALTER pattern.
set_chat_session_title takes a source; set_chat_session_title_if_empty
becomes set_chat_session_title_if_placeholder — one conditional UPDATE
that lets a generated title replace the placeholder while a user
Rename, a legacy title, and an already-generated title all stand.
- harness: defaulted Harness::generate_title, implemented for Claude
(claude -p --model haiku --max-turns 1) and Codex (codex exec --json
at low reasoning effort). Both children run tool-less and MCP-less,
hermetically in a temp cwd, under a shared TITLE_TIMEOUT. Prompt and
output sanitizer live in the new harness/title.rs so both halves are
unit-testable; opencode and cursor keep the default.
- opencode fix: TurnCtx::set_title's if-empty check could never fire —
the placeholder write always beat it — so native opencode titles were
silently dropped. The provenance check revives them, and the seed
title opencode stamps on every new session ("New session - <ts>") is
now skipped so it can't latch and block the real one.
- chat: one emit_session helper for the re-read/resolve-busy/broadcast
sequence shared by set_archived, set_title and auto-titling; the
placeholder now takes the first non-empty line.
No UI changes: the existing chat.session SSE upsert re-renders Recents.
* Animate the reveal of auto-generated session titles
An auto-title used to pop into the sidebar with no acknowledgement that
anything happened. Now it materializes character by character.
- session_json gains "titleSource" so the UI can tell a harness-generated
title from a placeholder or a user rename.
- New TitleReveal component, used by both the Recents row and the chat
header so the effect is identical in each. Characters are aria-hidden
behind an aria-label — a screen reader hears one title, not forty
single-letter spans — and render as a plain string when not animating.
- The reveal plays only on a live chat.session event that changes a title
we already had on screen, and only for titleSource "generated": renames
and page loads swap silently. A per-session nonce remounts the spans so
a second retitle replays rather than sitting on a finished animation.
- CSS: 240ms per character, 14ms stagger capped at 500ms, fading in from
blurred and slightly below. No color, no scale, no layout shift, and
@media (prefers-reduced-motion: reduce) turns it off entirely.
* Trim the skills catalog from codex title requests
The plugin/skills catalog injected ~6k prompt tokens (24.3k -> 18k input
measured) into a one-line title request that ignores it.
* Strip the agent system prompt from claude title requests
--system-prompt replaces the ~8.5k-token Claude Code scaffolding with one
sentence (measured 8.5k -> 1.9k input tokens; wall time unchanged at ~3s,
which is node boot + one API round trip).
v0.1.84
|
||
|
|
1b9b62328f |
Agent | Project scope toggle for the Experiments pane (#134)
* Agent | Project scope toggle for the Experiments pane (v0.1.83) Attribute experiments to the chat session that created them (local_experiments.chat_session_id, stamped from ORX_CHAT_SESSION_ID like runs, immutable after creation) and add an Agent | Project toggle to the experiments pane. Agent scope filters the tree to the open session's experiments, collapsing foreign lineage regions into dashed "…" pills with counts, and scopes the runs table to the same experiments. * Show the Agent | Project toggle only in projects with attributed experiments Legacy projects (created before chat_session_id existed) have no attributed nodes, so the toggle would only ever show an empty Agent view there. Gate it on the project having at least one attributed experiment — no backfill needed, and pre-existing projects keep today's UI untouched. * Require full attribution before showing the Agent | Project toggle Any unattributed experiment (legacy or pre-feature) makes Agent scope misrepresent the tree, so the toggle only renders when every experiment in the project carries a chatSessionId. |
||
|
|
397dd48815 |
Stream Codex sub-agent activity into the chat transcript (#117)
* Stream Codex sub-agent activity into the chat transcript Sub-agents spawned via Codex collaboration now stream their own transcript live, nested under the spawn row, instead of showing an opaque collabAgentToolCall/subAgentActivity row. - WirePart gains a recursive children list (serde-default, no migration) - apply_item split into a pure item_to_part + thin streaming wrapper - turn loop classifies foreign-turn events: parent / known sub-agent / stale, routing sub-agent items+deltas into the spawn part's children; the aborted-predecessor drop is preserved - UI renders the nested transcript inline (recursive, auto-expands while running) plus a right-pane view tab * Address review: settle orphans on all exits, re-point sendInput, share tool-group CSS, trim spawn fields * Nits from review round 2 + version bump to 0.1.77 - drop dead .subagent-block class token - re-point grandchild threads on sendInput (symmetry with top-level) - fix stale doc comment on subagent_spawn_part - rebuild ui/dist, bump Cargo version * Fix un-collapsible running sub-agent row; add live header to view tab - SubagentBlock: expanded is now a user-toggleable state seeded from running, instead of `open || running` which made a running row impossible to collapse - SubagentTranscript: show a header (spawn label + status dot + "live" badge) so the right-pane tab reads as the sub-agent's own live view, not a duplicate of the inline block * Stream Claude and OpenCode sub-agent activity too Extends live sub-agent streaming (shipped for Codex) to the other two harnesses, so a Task/task sub-agent's transcript nests under its spawn row in all three. - Promote find_part_mut / upsert_preserving_children to chat/mod.rs and add TurnCtx::upsert_child / append_child_text — shared routing primitives - Claude: route stream_event/assistant/user events carrying parent_tool_use_id (= the Task tool_use's part id) into that Task part's children, instead of dropping the deltas and leaking the blocks flat - OpenCode: link a task-spawned child session (session.created carrying parentID) to its task row, and route that child session's parts/deltas into the row's children instead of filtering them out by sessionID - UI: a tool part with children renders as a spawn block regardless of tool name (Codex subagent / Claude Task / OpenCode task) - Tests: per-harness nesting tests; rebuilt ui/dist * Review fixes: preserve spawn-row children on re-upsert; filter sub-agent user parts - OpenCode final-message merge and Claude Task tool_use now use upsert_part_preserving_children so completing/re-emitting a spawn row doesn't wipe the sub-agent transcript that streamed into its children (blocker) - OpenCode: gate sub-session parts on owned_by_assistant so a child session's user prompt echo doesn't nest as sub-agent output; track sub-session assistant message ids - tests: assert Task/task row completes AND keeps children through the merge * Nits: use imported HashSet; document one-level nesting scope in OpenCode * Sub-agent row opens transcript in the right panel instead of expanding inline Per design feedback: the inline sub-agent spawn row is now a single clickable line (status dot + label + chevron) that opens its full transcript in the right-side panel — matching the Claude/Codex desktop apps. No inline expansion, no auto-expand while running (the row just pulses). Nested sub-agents in the panel are themselves click-to-open rows. * Size the sub-agent row like the transcript body text Match the spawn row's font-size (--fs-base) and color (--text) to the surrounding transcript prose, keeping the trailing chevron. * Sub-agent row: block-level flex so long labels truncate with ellipsis Review nit: inline-flex + flex:initial let a long label grow the row and push the chevron instead of truncating. Use block-level flex + width:100% and keep the base .tool-line flex:1/min-width:0 so the label ellipsizes and the chevron pins right (matching .tool-group-summary). Also refresh a stale doc comment. |
||
|
|
17633be723 |
Add a hover detail card to experiment tree nodes (v0.1.83) (#133)
* Add a hover detail card to experiment tree nodes (v0.1.83) Hovering a node in the experiment tree (350ms intent delay) opens a portal-anchored card with the full title/description, run stats (counts, backend, duration), a lazily fetched diffstat vs the parent branch, branch lineage, and the run command. Portaled to <body> so it neither scales with canvas zoom nor disturbs the fixed tree layout; dismissed on mouse-out or canvas wheel. * Review round 1: shared diff parser, viewport-change dismissal, honest counts - Parse the diffstat with react-diff-view's parseDiff + the Changes tab's countChanges/formatDiffFilePath (now exported) instead of a hand-rolled scanner; truncated diffs render as lower bounds. - Replace the global wheel listener with useOnViewportChange dismissal (covers drag-pan, pinch, fitView) and re-measure the anchor when tree data changes; position via the existing useMeasure/usePopoverPosition. - Fix duplicate failure text, 'passed'→'done' wording, reconciled run counts, live-run duration fallback, mono font-size leak, z-index tier, and reuse diff-stat-add/del classes. * Review round 2: reliable pan/zoom dismissal, truncation-safe parsing, side fallback - useOnViewportChange stores a single global callback, so per-node registrations overwrote each other; dismissal now flows from ReactFlow's onMoveStart through a module-level EventTarget every open card subscribes to. - Byte-capped diffs can cut mid-hunk and crash parseDiff; trim to the last complete file (or line) first, try/catch as a backstop, and hide the row for empty diffs. - On windows too narrow for either side placement, anchor above/below instead of letting the clamp sit the card on top of its own node. - Skip rect churn on SSE ticks that don't move the node. * Review round 3: reset diffstat on run change; label truncated file count The diffstat effect never cleared state, so when SSE flipped latestRun while a card was open the old run's counts lingered beside the new run's status (and a failed refetch kept them forever). Reset at the top of the effect. Also mark the file count as a lower bound on truncated diffs and align the cancellation-flag name with the codebase idiom. * Skip header-only diffstat remnant after truncation trim * Hover card feedback: expandable description, darker text, drop file names - Clamped descriptions get a Show more / Show less toggle; expanded text scrolls within the card instead of growing past the viewport. - Title, stats and git rows read in --text instead of --muted (the card was mostly grey); the foot row stays muted as tertiary metadata. - Diffstat keeps branch and +/− counts, drops the changed-file list. * Drop the description's recessed background for hairline rules The filled box was the only non-hairline surface on the card and read as a heavy slab on long results; the description is now plain text between the same hairline rules the rest of the app uses. * Post-feedback review nits: toggle hygiene and hairline polish Reset the expanded state when SSE swaps the description; type=button on the toggle; trim CSS the global button reset already provides; bottom padding on the expanded scroll body; comment accuracy.v0.1.83 |
||
|
|
04cae09219 |
Auto-grow the chat composer with multi-line input (v0.1.82) (#132)
* Auto-grow the chat composer with multi-line input (v0.1.82)
The composer textarea was fixed at two rows (rows={2}, resize: none), so
multi-line drafts scrolled inside a tiny box instead of expanding it
(issue #130). Grow it to fit content up to the existing 180px max-height
cap, past which it scrolls internally; an empty draft clears the inline
height so the resting two-row look is unchanged (and Chrome's
placeholder-in-scrollHeight quirk can't inflate an unsettled layout). A
width ResizeObserver re-measures on pane resizes since wrapping changes
the needed height.
* Rebind the autosize observer on composer remount; review nits
The composer textarea unmounts when a settings view opens (mainView !=
chat renders a different tree); the effect-based observer kept watching
the detached node and a remounted draft came back at two rows. Replace
the effect with a stable ref callback that rebinds the ResizeObserver
and re-measures on every mount. Also: skip measuring at zero width
(hidden/mid-layout scrollHeight is nonsense), drop the circular
useCallback around autosizeComposer, and document the width-only guard,
the CSS min/max-height coupling, and the field-sizing tradeoff.
* Floor the composer at its rows=2 resting height; comment polish
A one-line draft measured ~33px and clamped to the old 42px min-height,
so the box dipped ~10px on the first keystroke and jumped back when
cleared. Raise the floor to the rows=2 resting size (52px) so empty and
one-line drafts sit at the same height. Also soften the ref-callback
comment (an effect could work with a mount flag) and note the plan-strip
textarea sharing the CSS rule.
* Replace the JS autosize with CSS field-sizing: content
Drops the whole autogrow block from ChatPanel (measure function, layout
effect, ref callback, ResizeObserver) for one CSS declaration. Firefox
doesn't ship field-sizing yet and falls back to the previous fixed
two-row box; min-height stays 52px so the resting look is unchanged, and
the plan strip's revise textarea picks up the same growth for free.
v0.1.82
|
||
|
|
62609c020b |
Don't treat single dollars as inline math in chat/report markdown (v0.1.81) (#131)
Prose like "cost $8–18 across both nodes ($1.45 each)" was pairing its dollar signs into a KaTeX inline-math region, italicizing the span and collapsing spaces. remark-math's default singleDollarTextMath treats any $...$ as math; disable it and normalize agent-emitted \(...\) to $$...$$ so real math keeps rendering. |
||
|
|
30e2ab18f4 |
Make reasoning/effort choices model-aware (#123) (#127)
* Make reasoning/effort choices model-aware (#123) The composer exposed one static reasoning list per harness, which hid valid choices and silently overrode the CLI's own configured effort. Reasoning choices now ride on each ModelInfo, with the harness-wide list kept only as the fallback for a session with no model selected: * Codex — per-model tiers from a curated table (codex exposes no queryable effort metadata), kept in the same table as the model ids so a model can't be added without stating its tiers. Verified live against codex-cli 0.144: all three 5.6 models accept `ultra`, and 5.5 rejects `max` with a 400 — issue #123's table claims Luna stops at `max`, which did not reproduce. This matters because codex forwards the value unvalidated, so a wrong tier kills the turn. * OpenCode — real per-model `variants` parsed from `opencode models --verbose`, sent as a top-level `variant`. A model with an empty variants map hides the picker entirely, which is why absent and empty are distinct states. Unknown variants are forwarded rather than filtered: the catalog is the authority and already ships ids outside our vocabulary (`thinking` on minimax-m3). * Claude Code — `ultracode` gated on CLI >= 2.1.217. The installed 2.1.197 does not error on an unknown effort; it warns and silently runs at the default, so advertising it unconditionally would offer a choice that does nothing. Every list leads with a `default` sentinel that sends no override at all, and it is the default selection — so a configured `model_reasoning_effort = "max"` is no longer clobbered by an implicit per-turn `high`. Switching models reconciles a stored effort the new model doesn't support back to `default`, both in the composer and again in each harness mapper before it reaches a CLI. Deliberately no store migration for existing rows: every value older builds wrote is still a value the picker offers, so a blanket reset would be indistinguishable from a level the user just chose — and Store::open() is a per-request constructor, so it would re-run and clobber fresh picks. * Lower the ultracode gate to 2.1.203, the measured boundary The constant was 2.1.217 — the version issue #123 happened to report — with a comment admitting the true cutoff was somewhere in (197, 217] and that lowering it needed a version actually tested. It has now been tested: every published release in 2.1.197..=2.1.212 was installed in isolation and probed. 2.1.198-2.1.202 reject (warn on stderr, run at default effort); 2.1.203 and every version above accept. So a 2.1.212 install was being denied a choice its CLI supports. The acceptance is specific to this value rather than newer CLIs dropping the warning: on 2.1.206, `ultra`, `ultrathink`, and `ultracode123` all still warn while `ultracode` does not. `--help` lists only the five ordinary tiers on every version tested, so it can't be used to detect support. The tests now pin the 2.1.202/2.1.203 pair, so moving the constant without re-probing fails loudly. * Discover the Codex catalog from the CLI instead of curating it The model list and per-model effort tiers were hard-coded. That was wrong on the facts, not just the principle: the installed codex actually serves seven models (gpt-5.4, gpt-5.4-mini, gpt-5.3-codex-spark were missing), and the curated Luna entry advertised `ultra`, which the catalog does not offer — codex's own picker doesn't show it, so ours shouldn't either. (An earlier live `codex exec` turn on Luna tolerated `ultra`, which is what the table was built from; the catalog is authoritative for what to offer.) Detection now spawns `codex app-server`, does the initialize handshake, and calls `model/list` — the same data codex's TUI picker renders, with each model's `supportedReasoningEfforts`. This is the codex analogue of the opencode `models --verbose` path. The static table survives only as the fallback for a codex too old to answer (legacy exec era), with Luna corrected to the catalog's tiers. Send-time validation follows the opencode precedent for catalog-discovered models: a model outside the frozen fallback table forwards its level rather than clamping to the conservative intersection, because the composer only offered what `model/list` reported — clamping would drop genuinely supported tiers. A bad value still surfaces as codex's 400 in the chat. Claude stays gated rather than discovered, now with evidence there is nothing to query: no models command, a static `--help`, and the "Valid values" warning on 2.1.212 still omits `ultracode` even though that version accepts it — the CLI's only enumeration surface is inconsistent with its own parser. * Discover the Claude catalog and ultracode support from the CLI Claude Code was the last harness with hard-coded models and effort tiers. It turns out the CLI can be asked for both, via two surfaces: * `list_models` — a control request over `--print` stream-json (the channel run_turn already speaks for set_model), returning the same catalog the CLI's own /model menu renders: each model's alias, and its per-model `supportedEffortLevels`. This immediately corrected the static list a third time: Haiku supports no effort control at all, yet the table had been offering all five tiers on it. Its picker now hides, like a variant-less opencode model. * `--effort ultracode --version` — the CLI's own argument parser as a capability probe (~0.2s, no network). `--version` still runs the parser, which warns `Unknown --effort value` for a value it doesn't know; absence of the warning is acceptance. This replaces the bisected version constant entirely: the 2.1.202/2.1.203 boundary is now discovered per install rather than pinned. The parser is the only truthful surface — --help, the warning's own "Valid values" list, and list_models all omit ultracode on versions that accept it, because the CLI models it as a session mode, not an effort level. It is appended to models that reach xhigh (the mode is documented as xhigh + dynamic workflows), only when the probe passes. claude_effort now forwards any non-sentinel value, matching the catalog policy in codex_reasoning and opencode_variant: the composer only offers what the CLI reported, an allowlist would drop a future catalog tier, and Claude only warns-and-defaults on an unknown value rather than failing the turn. The static model/effort tables survive only as the fallback for a CLI too old to answer list_models, mirroring the codex and opencode fallbacks. * Carry the catalogs' display names and blurbs into the picker The Claude aliases the CLI's picker submits (`opus[1m]`, `sonnet`) are unversioned, so the model menu couldn't say which Opus or Sonnet you'd get. The catalogs already answer this: list_models has displayName + description (the description is where the resolved version lives — "Opus 4.8 with 1M context · …"), codex model/list has the same pair, and opencode's verbose output has name. ModelInfo now carries display_name/description, and the picker renders the catalog's own strings — the same text each CLI's native picker shows — with the raw id demoted to a hover title. Statically-listed fallback models keep deriving a label from the id. * Lead with versioned names, drop the Default row, allow free-form model ids Three picker fixes from using it: * The Claude rows read "Fable / Fable 5 · Most capable…" — the name repeated because the catalog's displayName is unversioned and the version lives in the description's first `·` segment. That segment is now promoted to the display name ("Fable 5") with the rest as the blurb, so rows lead with the resolved version, exactly once. * The per-harness "Default model · CLI configuration" row is gone wherever the CLI advertises a catalog — it was redundant noise next to a discovered list that already leads with the CLI's default. It survives only for custom providers, which advertise no models and where "let the CLI decide" is the only sensible row. New selections seed the catalog's first model instead of null. * Typing an id the catalog doesn't list offers "Use "<id>"". The catalogs are curated menus, not the set of accepted ids — `--model claude-opus-5` works today on a CLI whose menu still resolves `opus` to 4.8 — so the menu can't be the ceiling. A custom id gets no per-model effort list (unknowable), so the reasoning picker falls back to the conservative harness-wide tiers. * Preselect the effort that actually runs instead of a Default sentinel The reasoning menu led every list with "Default · CLI configuration" — a pseudo-value that told the user nothing about what would run. Replace it with the real answer wherever the CLI can give one: * Codex reports each model's defaultReasoningEffort, and config.toml's model_reasoning_effort outranks it (codex's own resolution order — verified: this machine's config sets "high" and the picker now preselects High on every model, not the catalog's low/medium). The sentinel row is gone for catalog models; the resolved tier is preselected and marked "· Default". Sending it explicitly is equivalent to sending nothing, by construction. * Claude reports no default tier because its unset default isn't one: the catalog advertises supportsAdaptiveThinking, and with no --effort the CLI scales effort per request. Preselecting a fixed tier would pin behavior the user never chose, so the sentinel stays — now labeled "Adaptive", which is what actually runs, instead of "Default". * OpenCode reports no per-model default, so its sentinel row stays as is. reasoningFor resolves in that order: the model's reported concrete default, then the sentinel when offered, then the harness-wide default. Stale stored "default" sentinels on codex sessions reconcile to the resolved tier. * Render a concrete default tier inline instead of pinning it Pinning above a separator now applies only to the `default` sentinel, which isn't a point on the tier ramp (Claude's Adaptive, opencode's model default). A concrete default — codex's resolved tier, a permission mode — stays in its natural ramp position with just the "· Default" marker, so the codex menu reads Low → Ultra in order. Side effect on the shared mode menu: Auto renders inline with the marker and the 1/2/3 number hints cover the full list again. * Make every picker change update the sticky new-session preference The mode/model/reasoning selection saved for new sessions only updated when the pickers were changed with no session open. A change made inside a session landed as that session's unsent tweak and the global preference never learned about it — so configuring auto/sonnet/high mid-session and clicking New session showed the old config. selectModel now always writes the merged selection to the sticky preference (state + localStorage), and additionally layers it onto the open session's override as before. New session therefore opens with whatever was chosen last, wherever it was chosen — verified across a full page reload.v0.1.81 |
||
|
|
85c1f7852b |
Fix duplicated assistant text for thinking models in the Claude harness (v0.1.80) (#129)
* Fix duplicated assistant text for thinking models in the Claude harness
The Claude CLI emits one 'assistant' event per completed content block,
each with a single-element content array. The fold keyed parts as
{mid}-{enumerate index}, so every block keyed to {mid}-0: a text block
following a thinking block clobbered the reasoning part while its own
delta-built part at {mid}-1 survived — every reply rendered twice for
models with visible thinking (Fable 5), and the thinking disappeared.
Track a running per-message block offset so each assistant event's
blocks recover their true index in the message and land on the same
part ids the stream deltas built. A full-content array in a single
event (the shape the old tests assumed) starts at offset 0 and behaves
as before.
* Address review round 1: subagent offset namespace, comments, tests
- Namespace the block-offset map by parent_tool_use_id so a subagent's
top-level assistant events can never advance the main message's offset
(their parts still fold — pre-existing behavior, unchanged).
- Document the exactly-once/ascending invariant the counter trusts, and
why the offset advances by blocks.len() rather than a render count.
- Tests: pin part ids in the per-block regression test; add coverage for
bridge-suppressed blocks still occupying their position and for
subagent offset isolation; rename the whole-message test and the
per-message-id keying test to match what they exercise.
- Nits: hoist HashMap use, unshadow the loop index, avoid the
unconditional format! allocation in the tool_use id fallback.
* Address review round 2 nits: single parent read, tighter docs, sharper tests
- Bind parent_tool_use_id once and use it for both the offset namespace
and the usage guard (one read, one null-semantics).
- Trim the field doc to the what + pointer; the assistant arm stays
canonical for the mechanism; restore a short named pointer from the
stream_event comment.
- Make the subagent test reuse the main message id so it actually
exercises the namespace branch; name the cross-test reference; drop
the CLI version stamp; assert last_text ordering in the per-mid test.
* Bump version to 0.1.80
v0.1.80
|
||
|
|
07a7e209ff |
Detect Codex and Claude Code behind custom API endpoints (#125)
Follow the active provider in $CODEX_HOME/config.toml so a provider with requires_openai_auth = false authenticates off its declared env_key, and offer the model it configures instead of the first-party catalog. Accept ANTHROPIC_AUTH_TOKEN for Claude Code, letting the env credential win over a stale ~/.claude.json only when ANTHROPIC_BASE_URL is set. Add a "Default model" picker entry that sends model: null so the CLI uses its own configured default. Closes #121 |
||
|
|
979d8e9362 |
Rework first-run onboarding and the New project modal (v0.1.79) (#126)
* Tighten the onboarding + new-project flows
Onboarding:
- Gate steps 1 and 2 (a signed-in agent, GitHub access) — both are hard
requirements for chat and project creation, so Continue is disabled
until they're met.
- Drop the data-dir step: the default suits ~everyone, it's write-once,
and Settings > Storage already exposes it with a move flow.
- Split harness notes into not-installed vs not-signed-in, each with the
real command (claude auth login / codex login / opencode auth login).
- Fix OpenCode reporting Connected while unauthenticated (agent_ready
ignored authentication).
New project modal:
- Auto-open on the onboarding hand-off.
- Only show the fork choice when the user can actually push (new
/api/github/repo-access), and say what happens when they can't.
- Name the real GitHub account in repo previews (new /api/github/account).
- Drop the branch picker; resolve the repo's real default branch server
side instead of assuming "main".
- Describe the copy honestly: it's a single-branch, history-less snapshot,
not a GitHub fork.
- Top-anchor the modal so the tab row doesn't move between tabs.
* Review round 1: fix onboarding lockouts, trapped name field, stale fork mode
- opencode: treat a non-empty `opencode models` list as authenticated, so
env-key installs (no auth.json) aren't locked out by the new step-1 gate.
Claude and Codex already had the equivalent fallback.
- Onboarding: surface a load error. A rejected probe left harnesses/git at
null forever, which the gates read as not-ready — disabling Continue with
no hint and no way out, on the screen that is the whole app at first boot.
- NewProjectForm: hoist the project-name field out of repoFields. A stalled
access check used to unmount it, and `valid` needs a name, so the form
became unsubmittable with no escape.
- NewProjectForm: reset canPush on every repo change and sync repoMode to
the answer, so a forced "fork" can't linger and silently copy a repo the
user can push to.
- github.rs: share one authed_get helper (with a 10s timeout — there was
none), URL-encode owner/repo, drop the now-dead has_push_access wrapper.
- Drop the unread `known`/`defaultBranch` fields from the repo-access
response and its TS type.
- ProjectsHome: consume the post-onboarding hand-off so a later remount
can't re-open the modal.
- Deduplicate the paper-repo hint and the two identical code-chip CSS rules;
tie the modal offset and max-height together with a --modal-top token.
* Review round 2: correct the opencode fallback, unblock SSH-only users
The round-1 opencode fix rested on a false premise — I claimed `opencode
models` only lists authenticated providers. Tested: under a signed-out HOME
it returns nothing, so the fallback never fired for the users it was meant to
rescue, and had the catalog ever been listed it would have marked signed-out
installs Connected. Replaced with an explicit provider env-key check, matching
claude.rs. Verified both directions: env key set -> ready, none -> gated.
- Onboarding: step 2 gets a "Skip — I use SSH keys" escape. ensure_clone
tries ssh first, so a token isn't actually required for the use-existing
path, and git_settings_json can't see SSH keys at all — the hard gate was
locking out a setup the CLI's own startup note calls supported. Also covers
the git-not-installed dead end, where the token form is unreachable.
- Onboarding: split the shared loadError into per-probe flags. A telemetry
failure was putting a connectivity error on unrelated gates and hiding the
actionable hint behind it.
- NewProjectForm: only force "fork" on denied access; the symmetric sync was
undoing selectPaper's deliberate copy default for writable paper repos.
Reset repoMode in onRepoChange instead, which is where the repo changes.
- github.rs: drop the deleted has_push_access's doc comment, left welded onto
authed_get and describing the wrong thing.
- Bump to v0.1.79 (+ Cargo.lock): ui/dist changes don't ship without it.
* Review round 3: detect synced-env keys, unstick the branch fallback
- Harness detection: resolve API keys through orx's synced env file as well
as the process env (new detect::api_key, used by claude.rs and opencode.rs).
A key pasted into Settings > Environment is handed to the harness child, so
chat works — but detect() couldn't see it, and step 1 is a hard gate.
Verified: key only in ~/.openresearch/env now reports ready for both.
- opencode: widen the provider key list (Gemini/Groq/xAI/DeepSeek). Measured
rather than assumed that `opencode models` can't stand in for a credential:
signed out with no keys it still lists 7 free/bundled models, so keying off
it would report signed-out installs as Connected.
- baseline_branch: fall back to `git ls-remote --symref` when the GitHub API
can't answer. Without a token the branch resolved to a hardcoded "main" and
the clone died on any master/dev repo — and the form no longer has a branch
field to recover with. git's own ssh/https credentials need no token.
- Step 1 no longer gates on a failed probe: detection fans out to per-binary
version/model commands that can stall, and there's no way out of onboarding.
- Step 2's skip is always offered and reworded "Skip for now": a stale gh
token still reads as connected, hiding the escape from the users who need
it most. Promoted to a real button rather than a muted link.
* Rebuild ui/dist after rebase
* Review round 4: stop 404 forcing a history-losing copy
- repo_meta: a 404 means "invisible to this token", which is also how a
private repo looks to an SSH-only user who can push to it fine. It was
reported as can_push=false, which force-forks — and this branch removed the
"Use this repo" button in that state, so the override was gone too. The
user would silently get a new repo with their history dropped. Report
unknown instead, matching the fallback used everywhere else.
- remote_default_branch: bound it with a 15s timeout. ls-remote tries ssh then
https, and a black-holed host blocks on TCP connect ~75s each — the exact
hazard authed_get got a timeout for last round, reintroduced on the git path
of the same request. A stall now degrades to "main" instead of hanging.
- git.rs: the new function had been inserted between ensure_clone's doc
comment and ensure_clone, so the comment described the wrong function and
ensure_clone was left undocumented.
- Never show a "Detecting…" spinner next to a fetch error: on a failed probe
both steps now render the retry copy in place of the card, instead of a
spinner that claims work is in progress beside an enabled Continue.
- onRepoChange no longer resets repoMode in paper mode, where editing the
auto-filled repo would quietly retarget pushes to the upstream paper repo.
* Review round 5: make step 2 an honest soft gate, fix stale-state contradictions
- Step 2 no longer pretends to gate. Cloning and pushing work over SSH keys,
so a disabled Continue beside an always-enabled "Skip for now" was two
buttons doing the same thing — one greyed out, which reads as a bug. One
Continue, always enabled, and the hint states what's missing instead of
blocking on it.
- A rejected probe now clears its state as well as setting the error flag.
Otherwise a failed *re-check* kept the old cards on screen: stale "Not
signed in" badges and a "sign in to continue" hint next to a Continue that
harnessError had just un-gated, with no indication the check had failed.
- Mode switches reset repoMode via a chooseMode helper. Leaving paper mode
carries the paper's repo into the field but not its copy default — and
because the field is pre-filled, onRepoChange never fired to reset it, so
"Private copy" stayed selected on a repo the user never chose it for.
- Deduplicate the project-name field and blank-repo hint, both of which were
inlined a third time in "New blank repo" (flagged since round 2).
* Review round 6: restore the Existing repo name field, kill the footgun
Round 5's nameField refactor turned it into a function but updated only two of
three call sites. `{parsed && nameField}` passes the function itself as a
React child, which renders nothing — so "Existing repo" had no Project name
input and Create stayed disabled. Both tsc and a grep of the call sites missed
it; three reviewers caught it.
Made the class of bug impossible rather than just fixing the call site: the
autoFocus parameter was the only reason nameField was a function, so it's a
plain element again like blankRepoHint. Verified in a browser this time —
name field present and submit enabled in every mode.
- Onboarding: the docstring still called step 2 a hard gate, which round 5
made false, and it sat below the RETRY_COPY const so it documented the wrong
symbol. Rewritten and moved.
- Onboarding: sequence-guard load(). A token save landing mid-refresh was
overwritten by the pre-save snapshot, leaving the card reading "Not
connected" until the next check; the save now supersedes in-flight probes.
* Review round 7: fix a Re-check lockout, share renderNote, trim comments
- load()'s .finally was sequence-guarded, so a token save (which bumps loadSeq
to supersede the in-flight probe) left `checking` true forever and both
Re-check buttons dead for the session. `checking` is "a load is running",
not data a stale response can corrupt — it shouldn't be guarded. Introduced
by round 6's guard; verified fixed by superseding a slow load in a browser.
- renderNote moved to its own module: ModelPicker, a core chat control, was
importing it from the first-run walkthrough.
- Drop the redundant {parsed && …} guard (repoFields already returns null),
a doubled CSS comment block, and two stacked rationale comments.
- Document the opencode agent_ready tightening where the behaviour changes:
installs with no auth.json and no provider key now read "Not signed in".
v0.1.79
|
||
|
|
c09ccb6808 |
Repair a provisional experiment instead of branching a child (#124)
* Repair a provisional experiment instead of branching a child
An agent that hit a dependency error on its root experiment created a
child node to fix it, then another for the next error — a 14-node chain of
pure engineering remediation with no scientific content. The cause was a
missing affordance, not a missing rule: rule 1 forbade editing a baseline,
the loop offered only refill/promote/stop, and `orx create-experiment` was
the only exit from a failed state.
A node is now an evidence contract, not a code revision. It stays
provisional — editable on its own branch — until one of its runs puts the
intended measurement in the log, and only then freezes. Getting code to
execute is setup, and setup happens on the node.
- SYSTEM_PROMPT.md (+5 net lines): rule 1 freezes on measurement and covers
every node, not just the baseline (the chain reproduced at depth 3 as
easily as at the root); rule 6 inherits that freeze test instead of
restating "completed non-failed run", a trigger a `done`-but-diagnostic
run satisfied with zero evidence; the loop gains a fourth move, Repair;
the 2-strike env rule becomes the named repair cap and closes the dodge
of resetting it by creating a node.
- orx-experiment-tree (both variants): the freeze test, "remediation is
neither width nor depth", and a classification table.
- orx-git: repair-in-place workflow, worktree branch-ownership handling,
and a safe sync recipe — `checkout -B <b> origin/<b>` silently discards
unpushed commits, which a repair flow hits when a push has failed.
- orx-compute (both) + k8s: provisioning failures never create nodes.
- orx-create and root SKILL.md: drop "frozen the moment it holds real
code", which contradicted the new model.
Also fixes `orx skill <name>` serving cloud bodies inside local sessions.
The playbook points there as the fallback, but `find` searched the Full set
only, so a local agent was told to use `orx artifact`/`query`/`chart` —
commands local mode does not have. It now resolves against the set implied
by ORX_CHAT_SESSION_ID.
* Review round 1: close repair loopholes, fix the -B hazard
Four independent reviewers found 15 blockers. The two that mattered most
were mine:
- The old `checkout -B <b> origin/<b>` code fences survived in orx-git and
orx-create while a new bullet warned against that exact command eight
lines below. An agent copies the fence, not the prose — so the hazard was
documented, not fixed. All fences now use `checkout` + `merge --ff-only`,
which fails loudly instead of discarding unpushed commits.
- The "safe form" I had added (`checkout X 2>/dev/null || checkout -b X`)
was itself wrong: plain `checkout` already DWIM-creates the tracking
branch, so the fallback only fired on the worktree-lock error, where the
redirect masked "already checked out at ..." as "already exists" and made
the triage block unreachable.
Also:
- orx-git:32 still said "Edit children, never the baseline" — the sentence
this change exists to remove, in the skill an agent loads to repair.
- The cap read "two consecutive ... the same error twice", so a sequence of
*distinct* errors (the actual 14-node case) never tripped it. It now says
different errors still count, and that flavor/provider/backend switches
are repairs — only a measured run resets the count.
- Freezing's permanence lived only in the on-demand skill; it is now in both
always-on rule 1 copies, where the anti-tampering defence belongs.
- Dropped "a wrong flag" from the Repair list: it licensed in-place knob
edits against cardinal rules 2 and 3.
- k8s said a GPU-type change is "a real experimental variable" while the
tree table called flavor changes "provisioning, not science". Reconciled
on the freeze boundary.
- Partial measurement (3 of 5 metrics) was undefined; any real measurement
now freezes, and the node's expected metrics belong in `orx exp desc`.
- Harness shims still enumerated the three-move loop.
Rust: module doc described the old always-Full lookup; the variant test
pinned an English sentence from a file this branch doesn't touch (replaced
with a dispatch check over all four swapping skills); the mode inference
moved to `chat::in_local_session()` next to the env contract it reads, with
a direct test — it was the crux of the fix and untested.
* Review round 2: make the freeze test evidential, not causal
Round 2's four reviewers found 10 blockers. The most serious was a
regression my round-1 compression introduced.
The classification row read "anything that stops the code MEASURING at all
-> Repair". That is a causal test where an evidential one was needed: a run
that diverges to NaN, OOMs on the node whose hypothesis IS "wider MLP", or
times out on the slower config never reaches its eval block — so an agent
would repair the branch and silently delete a real negative result. That is
worse than the bug this branch fixes, because a wrong repair destroys
evidence irrecoverably while a wrong child costs one cheap node.
The freeze test now asks whether the failure came from the node's own
change, with a mechanical discriminator: would this have happened on the
parent too? If no, it is a result — freeze it.
Two reviewers disagreed on partial metrics (3 of 5 printed): one wanted
FROZEN kept, one wanted provisional because it contradicted the definition
above it. Resolved on the same asymmetry — evidence-valid now reads "any of
the numbers this node owes", so partial output freezes and the definition
carries it instead of a patch line below the table.
Also:
- `merge --ff-only` had been added only to orx-git; the orx-create seed
recipe and both experiment-tree child recipes kept a bare `checkout`,
which silently resolves to a stale local branch on a reused clone — and
the seed recipe then rm -rf's the tree and pushes from it. Fixed at all
four sites. Flagged independently by three of the four reviewers.
- src/local/skills.rs:117, the live `/reproduce` slash template, still said
"never mutate an experiment root after it has been run" — the old model,
in a Rust string my markdown-only grep gate never covered. The gate now
spans src/ too.
- Both orx-compute bodies had zero mentions of frozen/provisional, so an
agent loading them after a failure got an unconditional repair with the
freeze concept out of scope.
- orx-compute-k8s cited "the repair cap" without ever stating its value.
- The cap lacked "on the same node" in compute and create, reading as a
global counter.
- Root SKILL.md had neither the four moves nor the cap.
- in_local_session's doc asserted "only local projects can own a chat
session" as fact; there is no FK enforcing it and it is not the operative
reason. Re-grounded on the actual mechanism.
- The variant test's ptr::eq loop was redundant with the assert_ne above
it; it now pins each body against the embedded const, outside the lookup
under test.
* Review round 3: stop classifying OOM both ways
Round 3's four reviewers found 17 blockers, converging hard on one: my
round-2 freeze test named "OOM on the bigger model" as the canonical FROZEN
case, while six other sites still listed a bare "OOM" as a repair trigger —
including the Repair bullet 190 lines below it in the same file, the
always-on prompt, and both compute bodies. The compute skill is the one an
agent loads when a job OOMs, and it said "OOM or timeout -> bigger flavor,
same node". So a node whose whole hypothesis was "the bigger model" would
have its result repaired away by weight of repetition. Every bare OOM is now
qualified by whether the node's own change caused it.
Round 2 also overshot on partial output. "Any of the numbers this node owes"
froze a node that printed `step 0 | loss 11.83` and then died on a missing
path — and since a child whose purpose is "finally find the checkpoint" is
explicitly not a node, the agent was deadlocked: forbidden to repair,
forbidden to branch. Evidence-valid is now floored on the metric the node's
`orx exp desc` names; a partial trace is not the measurement.
The counterfactual ("would this have happened on the parent too?") was also
undecidable as written — a claim about a run that never happened, with the
burden falling on proving the parent would have failed, which pushes
uncertainty toward Repair. It now points at the parent's actual run log and
states the tie-break explicitly: uncertainty freezes, because freezing
wrongly costs one node while repairing wrongly destroys a measurement.
Also:
- SYSTEM_PROMPT rule 1 and the freeze table both said an all-failed node is
provisional, unconditionally — contradicting the freeze test two lines
later. The always-on copy matters most: an agent that never loads the
skill only ever saw the permissive rule.
- orx-git:37 still said "Always fetch + reset before editing", the exact
idiom the -B removal was meant to eliminate, 19 lines above a bullet
forbidding it.
- The repair recipe itself — the flow explicitly about returning to a branch
you already left — was the one checkout site still missing
`merge --ff-only`. I had claimed all four were fixed; this was the fourth.
- legacy_root_warning used "frozen" for branch immutability, now a term of
art meaning "has produced evidence". Reworded to "immutable".
* Review round 4: a typo is not a hypothesis
Round 4's four reviewers found 17 blockers. The important one is a hole in
the model itself, not a stray sentence.
An agent's own coding bug had no classification. A NameError or a shape
mismatch in the hypothesis edit is literally "caused by the change this node
makes", so round 3's causal clause froze it — and the child inherits the same
typo, and the same reasoning freezes the child. That is the 14-node chain
reintroduced under a different label, and the "freezing wrongly costs one
extra node" accounting was wrong for exactly this case: the cost is a chain.
The freeze test now separates the change's semantics from its spelling —
freeze only when a *correct* implementation of the node's idea would have
failed the same way.
The freeze test also rested on a field nothing produced. Evidence-valid is
floored on "the metric its `orx exp desc` names", but every instruction and
all four worked examples told the agent to write only the *change*, and
orx-create called the description optional. Step 3, the examples, and
orx-create now carry the measurement.
Also:
- `cancelled` was classified two ways: a run cancelled after the metric
landed matched both the provisional row and the frozen row. Keyed on
content now, not status.
- A run with no usable log at all (spin-up failure, preemption, empty
output) fell through "uncertainty freezes" and froze, contradicting
orx-compute's transient-retry guidance. It is now explicitly not a result.
- The tie-break told the agent to read the parent's run — but a root has no
parent, so a baseline that OOMs on its first launch (which orx-create says
to *expect* and repair) would have frozen permanently.
- A seventh unqualified OOM survived round 3's sweep, in orx-compute's
sizing section: "OOM or hopelessly-slow -> move up a tier. That's expected,
not a mistake" — an unconditional imperative to do the thing the freeze
test forbids, 35 lines below the qualified bullet, in the file an agent
loads when a job OOMs.
- Root SKILL.md rule 1 never got the "own change" qualifier round 3 added to
SYSTEM_PROMPT.md rule 1 — the same defect in the same slot on the cloud
side.
- skills.rs:113 still said "Immutable `orx/*` branches hold experiment
nodes", contradicting line 117 four lines below it.
- `--baseline` was an unguarded cap-evasion route; the cap now says a second
baseline is a repair wearing a root's hat.
- The cap terminated in "ask the user" with no unattended fallback. The
agent now records the diagnosis and carries on with other nodes.
* Review round 5: bound the counterfactual to the diff, not the idea
Round 5's four reviewers found 17 blockers. The decisive one: my round-4
defect carve-out reopened round 3's bug through a new door.
The carve-out asked whether "a correct implementation of the same idea"
would have failed. But an idea is a phrase the agent itself wrote, so the
agent controls the abstraction level it reasons against — and for any
resource failure the natural reading favours repair: "a correct
implementation of 'wider MLP' wouldn't have OOMed, it would use gradient
checkpointing." Rounds 1-3 built an evidential wall; round 4 punched a
narrative door through it, and one narrative hatch outweighs four evidential
ones because an agent that wants to keep working needs only one.
The counterfactual is now over **the diff actually written**, not the idea:
ask "is this diff malformed?", never "could a better version have avoided
it?" Resource and dynamics failures — OOM, timeout, NaN, divergence — are
explicitly never defects. A kwarg the callee *rejects* is a typo; a kwarg
value you regret is the hypothesis. A shape mismatch inside the edited lines
is a typo; one that collides with code you didn't touch is the finding that
the idea doesn't fit.
Also:
- The carve-out lived only in the on-demand skill, so it was off by default
for any agent that hadn't loaded it. Added to the always-on Repair move.
- SYSTEM_PROMPT step 1 (the branch step the agent takes most often) created
nodes with no --description at all — producing exactly the un-judgeable
node the freeze test can't classify. Round 4 fixed the skills and missed
the prompt.
- No fallback existed for a node created without a named metric; now infer
from title and siblings, write it, and freeze if it still can't be pinned.
- orx-create's seed recipe had no `set -e`: after this branch replaced the
force-creating `checkout -B` with a fallible `checkout` + `merge --ff-only`,
a failed sync fell through to `rm -rf` + `cp -a` + push on whatever branch
the clone was on — usually the project's README/notebook surface. The
hardening introduced the hazard; it is now guarded.
- k8s never classified a provisional-phase GPU-type or node-count change.
Cuts (quality review): the permanence paragraph stated one rule four times;
orx-compute cross-referenced the freeze test twice in four lines; root
SKILL.md inlined the whole taxonomy it tells the reader to go read; the
always-turn cap paragraph and rule 1 carried gloss the skills already own.
Retired `execution-invalid` (coined once, never reused) and reworded
"nothing ran" for truncated output, which is factually wrong.
The env test now restores via a Drop guard, matching telemetry's EnvGuard,
so a failing assert can't leak the mutated var into the test binary.
* Review round 6: judge the evidence, not the cause
Round 6's four reviewers found 15 blockers, and one of them named the reason
this section has been rewritten in every single round: the test was
adjudicating **causation**. "Did this node's own change cause the failure?"
is not answerable from a log, so each round swung between too-permissive and
too-restrictive, and each swing needed a new qualifier to patch the last.
Round 5's swing was too restrictive. Bounding defects to a closed list of
four syntactic symptoms left every *semantic* self-inflicted defect
unclassified — an added import needing an incompatible torch, a config key
the edit references but never adds, a wrong path inside the edited code.
Those fall through "uncertainty freezes" and freeze; a child to fix them is
"not a node"; the agent is trapped. That is round 4's deadlock re-entering
through round 5's door. Meanwhile "resource and dynamics failures are never
defects" was written as an unconditional universal, so a node that changed
only an LR and OOMed on a pre-existing leak also froze — contradicting
orx-compute's own instruction to retry exactly that.
The freeze test now asks what the log **holds**, not what caused it:
- numbers (the measurement, or a NaN/OOM/timeout of what this node changed)
-> FROZEN, that is the answer
- an error and no numbers -> provisional, repair in place
- nothing to judge -> provisional, repair or relaunch
Causes are arguable; evidence is on disk. This collapses six contradictory
sites into one rule and kills the "a correct implementation wouldn't have
OOMed" argument structurally rather than by prohibition — the question is
what the log contains, so there is nothing to argue about.
Also fixed this round:
- The cap reset on every sibling, so one setup bug could burn 2N runs across
a round without ever asking. Added a project-wide backstop.
- A bare relaunch didn't obviously count toward the cap, leaving an
unbounded retry loop on infrastructure flakiness.
- "A mis-sized flavor" sat unqualified one row above the OOM row — a
standing licence to reclassify any OOM as a sizing problem.
- The round-5 warning I added to the seed recipe said "the lines below wipe
the tree"; they are above it. Folded into the `set -e` comment where it
travels with the code.
- Root SKILL.md had lost its freeze-side clause in round 5's cuts, leaving
that document with a repair trigger and no freeze rule.
- The "freezing wrongly costs one extra node" accounting was still wrong
(the cost is a chain when a child inherits the defect) and had become
load-bearing again.
- Deduped the classify table against the freeze table; trimmed k8s paste.
* Review round 7: delete the causal predicate, don't reword it
Round 7's four reviewers found 17 blockers and one of them landed the
diagnosis that matters: the oscillation was not resolved by round 6, it was
relocated. The freeze test's numbers bucket still read "a quantitative
failure of *the thing this node changed*" — so an OOM landed in FROZEN or
Repair on exactly the causal judgement round 6 declared unanswerable, and
the compute skills' complementary clause ("OOM the node's own change didn't
cause -> bigger flavor, same node") made the pair a coin flip. Round 6 said
the argument was unavailable, then handed it back two lines up.
The causal predicate is now deleted rather than reworded, at all six sites.
Numbers freeze a node **whatever ended the run**; the only question is
whether the run got far enough to emit them. "Numbers" is defined as the
run's own measurement stream — byte counts inside an allocator error are not
numbers, digits in an error string are part of the error. That was the other
half of the problem: every CUDA OOM message carries a watermark, so on a
literal reading every OOM froze.
Two escape hatches this opened, both closed:
- The cap creates a standing incentive to declare "the OOM is numbers" to
escape a hard debug and avoid asking the user. Named explicitly: if that
reading only became attractive after a repair, it is the wrong reading.
- "Not the measurement I owed" let an agent repair a node that produced real
metrics. Numbers that aren't the ones you named still freeze it; a missing
eval block is a reporting defect, fixed on a child.
Also:
- orx-evidence — the skill whose entire job is judging a finished run, loaded
at exactly the repair-vs-child decision point — had no mention of the rule,
not even a cross-reference. Added to both variants.
- orx-create's copy-paste examples omitted --description entirely, so the doc
that most often creates nodes created unjudgeable ones; its own rule sat 25
lines below the code an agent copies.
- The "nothing to judge" class existed in one skill pair only. A preempted
run had no documented classification for an agent on the always-on docs.
Added to SYSTEM_PROMPT and root SKILL.md.
- SYSTEM_PROMPT step 0 (baseline creation) named no metric and implied the
first launch succeeds — the local-mode gap, since orx-create is Full-only.
- Dropped the freeze table: after round 6 it restated the prose buckets
almost verbatim. Shipping both was the restructure half-applied.
* Review round 8: one bright line instead of two soft ones
Round 8's four reviewers found 9 blockers, converging from two directions on
the same root problem: the rule still had two discretionary predicates, and
each was one paraphrase away from the causation test round 7 deleted.
"the **intended** numbers" (cardinal rule 1 in both top-level docs, the
always-on prompt, orx-git, and a Rust prompt string) restores exactly the
adjudication the tree skill kills — were these the numbers I meant? no ->
repair. "any **substantive** metric" (root SKILL.md and both orx-evidence
variants, the skill loaded at the moment a log is judged) invites the mirror
abuse: rule real metrics insubstantial and repair a node that produced
results. The tree skill neutralised both locally; none of that reached the
five other sites.
Both are now one bright line, stated identically at all six: **two or more
metric lines this run computed**. One banner, one config echo, or a single
step-0 line is not numbers; two logged intervals are, whatever they say. The
agent cannot tune that boundary after reading the log.
That also fixed a hole round 7 opened: "a throughput line" counted as
numbers, and a tqdm bar emits `3.6it/s` — so a *preempted* run froze
permanently on a progress bar, which bucket 3 is supposed to route to
repair. Progress bars, ETAs, and figures carried over from a resumed
checkpoint are now explicitly excluded, along with allocator byte counts.
Deleted the partial-trace carve-out: it contradicted the bullet three lines
above it ("losses past warmup" freeze vs "a warmup value" doesn't), left
"warmup" undefined, and was the last place the agent picked a boundary
post-hoc.
Also:
- The over-freeze is now acknowledged rather than left implicit. A node that
OOMed on a pre-existing leak after emitting losses is frozen even though
its change was innocent; saying so stops an agent reading it as an
oversight to reason around.
- Stated the asymmetry that seven rounds kept re-deriving: a spurious freeze
costs one node, a spurious repair rewrites the branch a recorded
commit_sha points at. When ambiguous, freeze.
- "If the only available repair would change what the node is testing, it is
not a repair" — a 2x-wider-MLP node that OOMs at allocation cannot be
fixed by shrinking the width.
- The hard-debug guard asked the agent to date its own beliefs. Now
mechanical and two-directional: the count of metric lines does not change
between readings.
- The cap's reset clause was dead text (measuring something freezes the
node, so repair ends there anyway).
- k8s said a GPU-type change is a repair while cardinal rule 2 calls the
environment a fixed contract; reconciled.
* Review round 9: kind, not count — one metric freezes
Round 9's reviewers found that round 8's bright line broke the normal
success path, and both correctness reviewers landed on it independently.
"Two or more metric lines" made a clean eval that prints exactly one
`accuracy: 0.83` **provisional and editable in place** — as would a single
summary block carrying five metrics, or a run that completed one epoch.
Those are successful, complete runs. Worse, orx-evidence explicitly tells
agents to "print final metrics and a compact summary block at the end,
not just scattered mid-training lines" — so following the repo's own advice
produced exactly the log shape the freeze test then refused to protect. The
better a run's output hygiene, the less protected it was.
That inverted the whole risk gradient: a crashed run emitting two loss lines
froze, while a clean run emitting one final accuracy did not. It was also
the first version in nine rounds where a run that fully answered its node's
question could be repaired — the one harm the asymmetry paragraph exists to
prevent, contradicted by the rule it was attached to.
The threshold is now **one**, and the discrimination moved to kind: any
metric the run computed freezes the node, one line or many. The exclusion
list carries the weight — banners, config echoes, progress bars and it/s,
allocator byte counts, timeout durations, resumed-checkpoint figures are
never numbers however often they appear. That also dissolves three ways the
count was gameable (summary block vs lines, redrawn tqdm lines in the byte
stream, PTY wrapping) and retires a false axiom that claimed line counts
don't vary between readings — they do.
Also:
- "If the only available repair would change what the node is testing" was a
general-purpose freeze excuse: pinning a dep, fixing a driver, or
correcting a shape error all "change what it tests", so an agent could
freeze any node it didn't want to debug and branch a child — the 14-node
chain rebuilt with each link relabelled "frozen". It now applies only to a
node the freeze test already froze.
- Reconciled the over-freeze paragraph with "remediation is neither width
nor depth": a confound-fix child is depth only because the parent holds a
measurement.
- The always-on prompt asserted the rule with no definition at all, so the
default rule an agent ran under was undefined. Inlined it.
- The cloud variant judged the PTY log only, but cloud runs put metrics in
EVAL.md and W&B — a documented best practice would have left a node
provisional with its measurement sitting in the artifact store.
- Dropped the last two causal qualifiers ("of what this node changed").
* Review round 9 (quality): retire the dead metric-naming machinery
The two quality reviewers reviewed the committed state before the kind-not-
count fix landed, so most of their findings were already resolved. Two
survived, and both are real.
**orx-compute contradicted the exclusion list.** It said "if the run put
numbers there — an OOM at a known memory watermark, a timeout at a known
step rate — that *is* the node's result", while the freeze test says byte
counts inside an allocator error and durations in a timeout message are
*never* numbers. Same log, opposite verdicts, in the skill an agent loads
when a job OOMs — the OOM-both-ways loophole reopened at the one site most
likely to be read alone. It now defers to the freeze test instead of
restating a rival criterion.
**"The freeze test reads that metric" is false.** Round 4 added the metric-
naming chain when bucket 1 genuinely read the named metric. Round 8 made the
test metric-agnostic ("numbers that aren't the ones you named still freeze
it"), so six surfaces were left asserting a lookup that no longer happens —
including "a node without one cannot be judged", which is flatly untrue: a
node with an empty description is judged exactly like any other. Naming the
measurement is still worth doing, so the guidance stays with an honest
reason (a later reader needs to know what the node owed); the false
justification is gone.
Two clauses died with it:
- The "No metric named?" fallback resolved a lookup that no longer occurs,
and its own last sentence just restated the bright line.
- The reporting-defect guard duplicated "Numbers that aren't the ones you
named still freeze it — record what actually landed" one bucket above.
Neither deletion reopens the semantic-defect case; that bucket-1 sentence
closes it on its own.
* Review round 10: close the inclusion list, not the exclusion list
Round 10's four reviewers found 23 blockers. The structural one is a flaw in
how I had been writing the rule for three rounds.
The exclusion list was closed and the inclusion side open ("a loss, an eval
figure, a score" — plus anything else you'd call a metric). That is backwards,
because the tie-breaker already pushes toward freezing: the enumeration guarded
the disfavoured direction while the open list guarded the favoured one. So
`lr=3e-5`, gradient norm, `tokens/s`, `epoch took 42.1s`, and dataset counts
were all undecidable, and an agent that didn't want to debug an OOM could call
the LR trace "the metric my node owes" and freeze. That is the fifth home for
the same discretion: idea -> cause -> evidence class -> line count -> kind.
The inclusion side is now the closed one. Numbers means a **quality**
measurement — how good the model or its output is: loss, perplexity,
accuracy/F1/reward/win-rate, eval score. Nothing else, whatever it is called.
Operational figures are never numbers however central to the node's idea, and
a node whose stated `Measures:` is operational is mis-specified. That resolves
the LR-node scenario and every case the enumeration had missed.
Round 9's own regression, found here: I widened bucket 1 to read artifacts and
W&B but left buckets 2 and 3 saying "the log". On the normal cloud path — where
the run command writes EVAL.md — a run whose metrics landed in an artifact
satisfied bucket 1 (frozen) *and* bucket 2 (provisional, "repair it in place").
The destructive verb was on the wrong branch. Worse, widening where evidence
counts without widening where you must *look* made "I read the log, only a
traceback" fully compliant while EVAL.md held the metrics — a repair licence.
Buckets 2 and 3 now require all channels, and a "look before you classify" rule
makes the check mandatory. Same fix in orx-evidence and root SKILL.md; the
local variant states explicitly that the log is its only channel, so a later
round doesn't "harmonize" the divergence away.
Also:
- "produced from this node's own commit" was a re-entry point for the causal
argument (inherited code emitting a loss isn't "my change producing it").
Now "this run computed", with the irrelevance of authorship stated.
- orx-compute told the agent to bump a node's flavor while cardinal rule 2 and
orx-compute-k8s call hardware shape a fixed contract. Re-sizing is now a
repair only until some node in the round has measured.
- The project-wide cap existed only in the on-demand skill, so an agent on the
always-on prompt could repair node A twice, B twice, C twice indefinitely —
the "N node problems instead of one setup problem" the clause exists to stop.
- The deleted reporting-defect guard carried a remedy, not just a
classification; restored the remedy clause alone.
- Cut "Why this way round" and two other restatements: after the principle
landed they said what the opening line and the over-freeze paragraph already
say.
* Cut the review scar tissue: 681 -> 402 added lines
Ten review rounds each closed a loophole by adding a clause, and I let that
framing override the context budget. The result was a rule that argued with a
hypothetical adversarial reader instead of stating itself — an 86-line freeze
test for something that fits in three lines, with the definition restated
three times and six paragraphs of pre-empted rationalizations.
The rule is unchanged. What went is the arguing:
- Freeze test: 86 -> 25 lines. Kept the two buckets, the operational-figures
exclusion, permanence, and the ambiguity tie-break. Cut "Why this way
round", the re-reading guard, the over-freeze acknowledgement, the
hypothesis-repair clause, the per-value/per-line gloss, and two restatements
of the definition — every one of them written to pre-empt a reading rather
than to state the rule.
- Classify section: 38 -> 17. The table's first row was a pointer to the
freeze test; the cap prose restated itself twice.
- SYSTEM_PROMPT (every turn, 3 harnesses): +44 -> +24. The exclusion list and
the error taxonomy belong in the skill the rule already links to.
- orx-git: +78 -> +62. The repair section duplicated the sync recipe 60 lines
above it. The `merge --ff-only` fix and the worktree-lock case stay.
- orx-evidence: the new section was a 17-line restatement of the freeze test
in the file that links to it. Now a 7-line pointer.
- orx-create, orx-compute (x2), k8s: same treatment — one statement of the
rule at its home, cross-references elsewhere.
- Dropped session_env_tests: 45 lines of mutex, Drop guard, and save/restore
to test `launching_chat_session().is_some()` on a one-line wrapper. The
behaviour that actually changed is covered by
find_serves_the_requested_variant_body, which stays.
fmt, clippy, and 277 tests green; no old-model phrasing survives.
* Collapse the duplicate sections: markdown now +217/-173 (net +44)
The last pass shortened each section but kept the structure, so the same rule
was still taught in three places. The real cut is structural.
`orx-experiment-tree` had two sections — "A node is an evidence contract" and
"Classify before you create" — that both answered repair-vs-child, plus a
Repair bullet in the loop restating it a third time with its own cap. Merged
into one 23-line section; the loop's bullet is now a pointer. Both variants
go net negative: +39/-51 and +37/-50.
Same test applied everywhere else — is this the rule's home, or a restatement?
- orx-compute (x2): 18-line block re-teaching the freeze rule and cap -> 7
lines of the three compute-specific routes, deferring the rest.
- Root SKILL.md: cardinal rule 1 was 15 lines saying it twice -> 6; the
four-move paragraph restated the freeze rule -> dropped.
- orx-git: the repair section duplicated the sync recipe above it -> 10 lines
for what is genuinely git-specific (same branch, worktree lock).
- orx-create: prose trimmed to the baseline-specific facts.
Nothing lost: the freeze rule, the repair cap, `merge --ff-only`, and the
`set -e` seed guard are each stated once at their home and cross-referenced
elsewhere. fmt/clippy clean, 277 tests green.
* PR review: reframe on baseline/hypothesis, restore Shape the tree
Two review comments, both right.
**Restored "Shape the tree".** My slimming pass deleted the whole section —
the flat-fan/noodle/stacked-bushes diagram and the width-vs-depth rule — as
collateral from merging two adjacent sections. That is pre-existing content
unrelated to this PR and should never have been touched. Restored verbatim
from main in both variants.
**Dropped the metric framing.** The rule leaned on "did the log carry a
quality metric", which is a log-parsing heuristic standing in for the actual
criterion: every node exists to establish a baseline or test a hypothesis, and
a run that dies on an error does neither. That is *why* successive runs on one
node are right — nothing was established, so there is nothing to protect. The
section now says that directly, and the exclusion list of operational figures
goes away with it: unintended behaviour (OOM, timeout, a divergence from a
bug, a missing dep) is an implementation or hardware detail, not an answer —
unless the node's hypothesis is itself about memory or runtime.
Reconciled the same framing across all eleven sites that had said "metric":
SYSTEM_PROMPT, root SKILL.md, both tree variants, orx-git, and both compute
variants.
**k8s bullet** cut to five lines and OOMKilled moved to the repair side with
the other implementation details, per the comment.
Added markdown lines barely move (218 -> 228); the tree skills shrink
(+66/+63 -> +45/+43). Net rises to +134 only because restoring Shape the tree
gives back 79 deletions I had wrongly banked as savings.
* Stop editing skills that aren't about the experiment tree
Fair challenge on the PR: k8s and compute are about launching runs, not about
tree shape or node creation. Re-read those edits cold and most of them were
tree semantics squatting in the wrong file.
- **orx-compute-k8s: dropped entirely.** The bullet was pure tree vocabulary
("not a result", "same branch", "same expId") in a file whose job is the
manifest contract. A k8s failure routes through orx-compute anyway, and a
manifest fix is an ordinary branch edit — nothing k8s-specific to say.
- **orx-compute: 7 lines -> 3.** Kept one clause, because this skill's own
description says "use when a job OOMs" and its pre-existing bullet already
says "re-launch, or pick a different flavor or backend" — an agent following
that needs to know the node is still repairable. Everything else deferred.
- **Reverted "Let a real failure escalate you" outright.** I had rewritten a
sizing rule to carry my terminology; sizing advice doesn't need it.
- **orx-evidence: reverted both variants.** The section restated the tree rule
in the skill that links to it, and still used the metric framing dropped in
the last commit.
Also removed the "Measures: <metric>" scaffolding from the branch step, the
worked examples, and the exp desc bullet. That existed to feed the metric
test; with the rule now on baseline-vs-hypothesis it has no consumer, so the
create examples go back to their original form.
Markdown drops to +166/-75 across 8 files (from +228 across 11). The change
now touches only the tree skills, the playbook, orx-git, orx-create, and the
one compute clause.
|
||
|
|
f84bdf3b75 |
Catch "Permission denied (publickey)" before it costs a box (v0.1.78) (#119)
* feat: catch "Permission denied (publickey)" before it costs a box orx shells out to ssh with no -i, so it authenticates with whatever the local agent and ~/.ssh offer, while boxes authorize the keys registered on the account. A key registered from another computer leaves the box online but unreachable, and the user only finds out after paying to provision it. Compare the registered public keys against the locally-available ones at login, before launch, and on a failed launch, and add `orx ssh-key add|list` — the command the dashboard has always pointed at but which never existed. - login offers to register this computer's key ([Y/n], TTY-gated) - preflight blocks when nothing is registered, warns when we can't find a local match (ssh can still succeed via an IdentityFile elsewhere) - a publickey denial becomes one actionable line in `orx runs` - the settings badge distinguishes "on this computer" from "registered elsewhere", which the old boolean could not * fix: don't call the openresearch backend "Configured" without a usable key The compute row keyed "configured" off being signed in, so it showed a green badge on a backend that would refuse every connection — the same gap the launch failure exposed, one screen earlier. Gate it on holding a key the account has registered, and say which of the two things is missing. Also run the check when openresearch is made the default, since that's the moment the user is committing to it. Unknown (api unreachable, ~/.ssh unreadable) counts as ready: we couldn't tell, so don't cry wolf. * fix: say "Unknown" when the readiness check couldn't run Folding a failed check into "configured" put a green badge on a backend we never verified; folding it into "not configured" would show alarm whenever the api hiccups. Neither is true, so stop making the boolean answer a question it has no data for. Adds an optional `unverified` flag — absent for backends whose state is decidable locally, so no other row changes. * fix: name the key file we found instead of guessing one The compute row told everyone to run `orx ssh-key add ~/.ssh/id_ed25519.pub` — a path that doesn't exist on a machine whose key is named anything else, so the fix led to a second error. The readiness check already resolves the real file via preferred_local; carry it through instead of discarding it. Split the keyless case out, since `ssh-key add` alone can't help there, and surface the Unknown reason so that state stops being a dead end. * chore: v0.1.78 * fix: stop guessing ~/.ssh/id_ed25519.pub in the skill, notes and run failure Same bug as the compute row, in three more places. The skill was the worst of them: an agent reads it and runs the literal command, so on any machine whose key is named differently it fails with nothing to go on. - skill: tell the agent to read the path from `orx ssh-key list` rather than handing it one, and cover the no-key-at-all case - settings notes: use the .pub the check already found (new sshKeyPath in the payload), with separate copy when there's nothing to register - run failure: point at `orx ssh-key list`; the supervisor can't see ~/.ssh, so it has no path to namev0.1.78 |
||
|
|
fcba6a25d1 |
Show a loading state instead of the empty state while chat history loads (v0.1.77) (#118)
* Show a loading state instead of the empty state while chat history loads (v0.1.77) Switching between agent conversations briefly flashed the empty state (OpenResearch logo + 'Ask the agent to explore your codebase...') before the conversation's messages finished fetching. The empty vs transcript branch was gated purely on messages.length, which can't distinguish a not-yet-loaded session from a genuinely empty one. Add a distinct 'Loading conversation…' state, driven by key presence in messagesBySession (absent = still fetching). A failed fetch now seeds an empty transcript so it recovers to the usable empty state instead of a stuck spinner, and drops the loadedSessions guard so a switch-back retries. * Enlarge the chat loading spinner and text for legibility The 13px muted text + 13px wheel read as too small and hidden. Bump the wheel to 22px (thicker stroke), the label to 16px, and use --subtext for better contrast. * Guard failed-fetch recovery against clobbering streamed messages Review nit: the .catch seeded [] unconditionally, which could overwrite a message that streamed in via SSE during a failed history fetch (self-heals on the next re-broadcast, but briefly drops it). Add an onlyIfAbsent flag to the seed action so recovery no-ops when the transcript key already exists.v0.1.77 |
||
|
|
7940344d44 |
Open agent-inlined files-dir report links from chat (v0.1.76) (#116)
Clicking a report.md link an agent inlined into the chat transcript failed with "path must be repo-relative": the report lives in the files dir (<data dir>/files/<slug>/…), but every chat file-open routed through parseFilePath → FileViewer → the repo /file endpoint, which rejects absolute paths. parseFilePath had no files-dir case and FileViewer carried no source discriminator, so files-dir artifacts were unroutable. - Backend surfaces a (non-canonical) absolute filesDir on every project payload via a shared project_json helper, routed through all six emission sites including the SSE project.updated diff — enriching only list_projects would let a bare project clobber filesDir on the next visit. - parseFilePath gains a files-dir branch: exact-prefix match against filesDir (byte-for-byte with the path the agent inlines), plus a /files/<slug>/ suffix-regex fallback for symlink-divergent data dirs. Tabs it produces carry source:"files". - FileViewer branches its fetch by source: files-dir reports via /files/report, other files via /files/file, else the repo endpoint. Files-dir markdown renders through FilesTab's ReportMd (now exported) so report-relative image paths resolve; a missing file shows a friendly not-found; repo-only notes are suppressed. Verified end-to-end: the failing report.md chip now opens the report from the files endpoint with no error.v0.1.76 |
||
|
|
43852e42c0 |
Route run-completion notifications to the launching chat session (v0.1.75) (#115)
* Route run-completion notifications to the launching chat session Runs carried no owning-session id, so the run watcher guessed the recipient from project_id alone — the most-recently-touched, non-busy session in the project. With two agents in one project, the launcher was skipped by the is_busy guard (it was in its exp-wait loop) and an idle bystander got the '[orx] Run ... finished ... continue the loop' poke. Persist the launching chat session on the run (new runs.chat_session_id, migrated in), stamped from ORX_CHAT_SESSION_ID which every per-session harness child now exports (claude, codex app-server, codex sandboxed exec, opencode). The agent's 'orx exp run' subprocess inherits it, so run creation records the owner. watch_runs now routes to that session only (via notify_target); a busy owner is skipped as before, and an orphan run (CLI-launched / pre-migration) pokes nobody. Ownership is immutable: chat_session_id is excluded from the upsert ON CONFLICT clause, so a later status upsert never rewrites it. * Trim redundant routing-invariant comments (review nits) * Bump version to v0.1.75v0.1.75 |
||
|
|
7956d54af2 |
Per-session context-window usage meter (#114)
* Per-session context-window usage meter
Surface how much of the model's context window each chat session has used —
a compact percent pill beside the model picker, with a popover breaking it
down into a progress bar and token caption (amber >=80%, red >=95%).
Backend: harness adapters now parse the token usage the CLIs already emit and
discard today, normalize it into a ContextUsage (used = latest request's
input + cache + output, not cumulative), stream it live over a chat.usage SSE
event, and persist the final value to a new chat_sessions.context_usage_json
column so it survives restarts. session_json carries contextUsage thereafter.
- Claude: assistant message.usage (live) + result modelUsage.contextWindow
(window), falling back to the last usage.iterations entry.
- Codex: app-server turn/completed usage and legacy exec token_count
(input + output; cached_input is a subset). Window from model_context_window
when reported; static fallback intentionally empty (no invented numbers).
- OpenCode: assistant message.updated tokens (input+output+reasoning+cache);
window left unknown (provider config only), so the pill shows the count.
Frontend: ChatSession.contextUsage + fmtTokens helper; a chat.usage listener
that updates the session in place; a ContextMeter pill/popover; and a shared
ProgressBar extracted from SettingsPage and reused in both.
ui/dist rebuilt. A release version bump is still needed to ship.
* Restyle context meter: ring trigger + desktop-style popover
* Context meter: darker value text, more gap before send
|
||
|
|
cef4881032 |
Stop composer keystrokes from re-rendering the whole transcript (v0.1.74) (#113)
* Stop composer keystrokes from re-rendering the whole transcript Typing in the composer was laggy on long conversations: the draft state lives in ChatPanel, which also rendered the transcript inline, so every keystroke re-ran the full component body, re-filtered every message, and allocated + memo-compared all N Message elements (doubled by StrictMode in dev). PR #111 fixed the streaming path but left the typing path. - Extract the transcript map into a module-level memoized Transcript component; all its props are referentially stable across keystrokes, so typing now costs one shallow compare instead of O(messages) work. - Memoize the waiting-session scan (ran per session row per render, iterating messages x parts) into a useMemo'd Set keyed on reducer state, which only changes on SSE dispatches. * Review nits: mirror Message's onRespond type, state memo constraint * Ship rebuilt ui/dist; bump to v0.1.74v0.1.74 |
||
|
|
d0388778ae |
One persistent Claude Code child per chat session (#112)
* Persistent Claude Code child per chat session
Replace the spawn-per-turn + --resume fork with one long-lived
`claude --print --input-format stream-json` child per chat session
(new src/local/claude.rs ClaudeHost/ClaudeClient, mirroring codex.rs),
collapsing ~3-6s of per-turn spawn/config/MCP-handshake overhead.
- Respawn policy: child keyed by SpawnConfig; permission-mode/effort/
bridge change or a cleared model -> kill + respawn with --resume;
model-only change -> set_model control request; identical -> reuse.
- run_turn rewritten around a pure, store-free apply_event(ctx, state,
event) folded over the child's stream-json output; native-session-id
and flush stay in the caller.
- Gate token minted only at spawn (re-minting would strand a live
bridge child); process_group(0) so terminal SIGINT can't kill a
resident child mid-turn.
- ChatHost gains a claude host; interrupt/delete/shutdown and up.rs's
project-delete loop kill the resident child (it survives task-abort).
- Tests: child_action matrix, classify_line variants, apply_event
fixture tests over hand-written stream-json.
* Review round 1 fixes: drop ClaudeHost back-ref, fix achieved bridge_active, prune dead code
- spawn_client: force config.bridge_active = false before the plan block so a
failed bridge write records false (was inheriting wanted=true, which both
suppressed plan cards with no bridge to hold them and defeated the
respawn-to-retry recovery)
- SpawnSpec carries the ChatHost directly (transient, no cycle); deletes the
Weak attach/OnceLock apparatus and the duplicated up_port
- drop dead ClaudeHost::client_for; claude_effort back to private
- truthful docs: write_mcp_config and gate_tokens are per-spawn/per-child now
- new fixture tests: result without is_error falls back to subtype; errored
tool_result flips the tool part to error
* Comment nit from clean review round: gate token is per-child, not per-turn
* Stream partial text/thinking deltas into the transcript
Without --include-partial-messages the CLI only emits complete assistant
messages, so each text block landed at once instead of streaming. Deltas
build the part under the same {mid}-{index} id the final assistant event
upserts, so the authoritative text overwrites the accumulated one; a
subagent's nested stream (parent_tool_use_id) is ignored.
* Note the stream-protocol invariants the delta overwrite leans on
|
||
|
|
9fe3579c2d |
Stop re-rendering the whole transcript on every chat streaming tick (v0.1.73) (#111)
* Stop re-rendering the whole transcript on every streaming tick (v0.1.73) Long chat sessions crawled during streaming because each 150ms flush re-broadcast the full assistant message (with uncapped tool outputs) and the UI re-rendered — and re-parsed markdown/KaTeX for — every message in the session on every tick. Server: cap tool part output/error at 16k chars in TurnCtx::flush (one choke point covering all three harness adapters), with a visible truncation marker; the UI never rendered more than 20k chars anyway. UI: wrap Message, Md, ToolGroup and ToolRow in React.memo, and make the callback props (respond, openPlan, per-session file opens) referentially stable so the memoization holds. upsertMessage already preserves object identity for untouched messages, so only the actively streaming message re-renders now. * Review nits: drop inert memos, document the 16k/20k cap coupling ToolRow/ToolGroup get fresh part objects every streaming tick (the server re-broadcasts the whole message), so memo on them never skips — Message's memo is what short-circuits untouched messages. Keep memo only where it works (Message, Md). Also note on ToolRow's 20k slice that the backend caps at 16k so the slice must stay above it, and drop the render-site comment that moved to openFileInSession.v0.1.73 |
||
|
|
54d0be7521 |
Live session-worktree viewer (Worktree tab) (#110)
* Add live session-worktree viewer ("Worktree" tab)
A new right-pane "Worktree" tab shows, for the active chat session, what the
agent is changing in its private git worktree before any run/commit exists:
- Changes view: status-badged changed-file list (complete even when the diff
truncates) plus the unified diff of the worktree vs merge-base(origin/<baseline>,
HEAD), untracked files rendered as new-file diffs.
- Files view: the full live worktree tree (shared CodeTab tree components),
clicks opening the existing FileViewer against the session's worktree.
Backend: new read-only GET /api/chat/sessions/{id}/worktree endpoint and a
sessionId param on the code-tree endpoint (threaded through resolve_checkout_root
instead of a hard-coded None; mutually exclusive with ref). New git.rs helpers
merge_base, working_tree_diff_against, and changed_files are all read-only.
UI polls every 5s only while the session is busy (chat.busy SSE), refreshes once
on busy->idle, manual refresh always; keeps last-good data on transient errors.
Entry point is a FolderGit2 button in the chat header when a session is active.
* Review fixes: clear worktree tab on project switch/session delete, seed busy poll on mount, changed-files dedup
* Worktree tab feedback: diff-only Changes view matching the experiment drawer, styled tooltip on the header button
* Tooltip: View session worktree
|
||
|
|
0329d50e3a |
Keep markdown table cells from breaking words mid-word (v0.1.72) (#109)
* Keep markdown table cells from breaking words mid-word .md's word-break: break-word shrinks each cell's min-content width to a single character, so auto table layout squeezed short columns (e.g. a "Round" header) into vertical letter stacks. Restore normal word-breaking in cells with overflow-wrap: break-word for genuinely overflowing tokens, and give .md tables the same scroll-in-place treatment as .report-md tables so whole-word column minimums can't clip in narrow panes. * Rebuild ui/dist * Bump version to 0.1.72v0.1.72 |
||
|
|
e1b6c9c608 |
Render LaTeX math in chat and report markdown via KaTeX (v0.1.71) (#108)
* Render LaTeX math in chat and report markdown via KaTeX Agents emit \(...\) / \[...\] LaTeX which plain markdown mangled (backslash escapes stripped, underscores italicized). Add remark-math + rehype-katex to the shared Md renderer and the Files-tab report renderer, with a fence-aware normalizer that rewrites backslash delimiters to the dollar forms remark-math understands. Applies to all harnesses since every transcript text part renders through Md. * Bump version to 0.1.71v0.1.71 |
||
|
|
3661374314 |
Composer command chips, optional paper args, drop /hf (v0.1.70) (#107)
* Composer command chips: picked slash-skills render as a removable unit - Picking a skill (menu, typing /name<space>, or the empty-state suggest button) lifts the command out of the textarea into a chip; the textarea holds only the args, with the skill's arg hint as its placeholder. - Backspace at position 0 (or clicking the chip) pops it back into editable /name args text; send() reassembles the plain wire form, so server-side expansion is unchanged. - User bubbles render a leading known /command with the same chip. - /reproduce-paper args are now optional: bare invocation tells the agent to infer the paper from the current repo and only ask if unidentifiable; arg hint is bracketed to signal optionality. - Drop the /hf skill. - Fix a latent IME bug: Enter during composition no longer sends. * Review nits: rename activeSkill to pickedSkill (avoids collision with the menu's activeSkillIdx), fix stale pending-question comment * Chip inline with the input line; paper-aware optional placeholder - The chip and textarea now share a flex row (.composer-input), so the command and its arg hint read as one line, Claude-desktop style. - When the project has an attached paper, the /reproduce-paper placeholder says the args are optional and names the paper it defaults to. * Chip overlays the textarea with first-line indent so args wrap full-width The flex-row layout squeezed wrapped args into a column beside the chip. Instead the chip now sits absolutely on the textarea's first line, the first line is text-indented past its measured width, and the chip rides the textarea's scroll (clipped at the wrapper) so scrolled text is never hidden behind it. * Claude-style arg hints and chip semantics - Bracket every skill's arg hint ([topic], [paper on compute], ...) to match the Claude-desktop placeholder convention. - The chip is inert like inline text: pointer-events none so clicks land in the textarea beneath, and Backspace at the start deletes the command outright (args stay) instead of dissolving it into /name text. * Hint reads [paper] on [compute] — bracket each supplied part, not the phrase * Paper-attached hint keeps compute; extend to /paper-to-marimo - Placeholder on paper-attached projects now reads '[paper — optional, defaults to <id>] on [compute]' for both paper-reproduction skills — only the paper part is optional; compute is still expected. - Both templates and /paper-to-marimo's no_args now tell the agent to infer the paper from the repo when the args name none, so a compute-only invocation works too. * v0.1.70v0.1.70 |
||
|
|
a61e7af59f |
README: bullet-point intro and demo video embed (#106)
* README: bullet-point intro and demo video embed * README: link uploaded demo video * README: simpler benefit-focused bullets; name supported harnesses * README: final intro bullet copy * README: add contact callout at top * README: make tagline a header * README: h3 tagline, no underline |
||
|
|
a7cc9947a7 |
Codex: render every item type in the transcript; orx-lit-first routing rule (#105)
* Render every Codex item type in the transcript; route orx-lit first
The Codex chat adapter's apply_item whitelisted five ThreadItem variants
(agentMessage, reasoning, commandExecution, fileChange, plan) and dropped
everything else through a `_ => {}` catch-all. Codex reports its web
searches, MCP tool calls, and dynamic tool calls over the wire, so those —
and any future protocol addition — vanished from the transcript.
Replace the catch-all with explicit arms for webSearch, mcpToolCall, and
dynamicToolCall (rendered as tool parts, parity with the Claude harness), an
explicit skip for userMessage/hookPrompt (input echoes that would duplicate
the user bubble), and a generic fallback that renders any other item type as
a tool part named after its raw type — nothing is silently swallowed. The UI
toolLine now shows the WebSearch query and the WebFetch url. Rebuild ui/dist.
Also rewrite the orx-lit skill description as a routing rule: use it FIRST,
before any web search, for anything academic or research-flavored (paper,
author, blog post, model release). GPT skipped orx-lit on research queries
because the old description read as experiment-loop-only. Mirrored across the
code const, the skill frontmatter + body, SYSTEM_PROMPT.md, and root SKILL.md.
* Render codex web-tool actions (openPage/findInPage), not just search queries
The webSearch item's query is legitimately empty for openPage/findInPage
actions; the action object carries the url/pattern. Merge action fields
into the tool part input and label each variant in the UI.
* Label opaque codex web-tool actions as Browsed the web
codex 0.144 reports page visits as action {type:"other"} with no url —
the openPage/findInPage variants exist in the protocol but aren't emitted
by the hosted web tool yet. Stop calling those rows searches.
* orx-lit description: preferred literature tool, not a web-search sequencing rule
The 'use FIRST, then web-search what the corpus lacks' phrasing implied a
web search always follows. Intent: orx-lit is the preferred method for
literature search on academic/research work; web search is a fallback and
often unnecessary.
|
||
|
|
7cfef22bf6 |
Tour copy and sizing polish (v0.1.69) (#104)
* Tour copy and sizing polish Rewritten step descriptions (workspace pitch, prompt examples, worktree framing) with no em dashes and a single colon across the deck. Card sized between the original and the first enlargement: 390px wide, fs-lg title, fs-base body, full-size footer buttons. * Bump version to 0.1.69v0.1.69 |
||
|
|
2dd67bc2ca |
UI design-consistency pass: token scales, sentence case, sans-first, rounded everywhere (v0.1.68) (#103)
* ui: add type/weight/radius token scales, drop dead CSS rules * ui: drop all uppercase transforms and tracking, sentence-case status strings * ui: reserve mono for code, paths, ids and terminal output * ui: primary content uses --text; grays reserved for true secondary text * ui: normalize font weights onto the --fw-* scale * ui: normalize radii and type sizes onto token scales, round callouts and inputs * ui: pin badges to sans in mono contexts, un-mono instance durations; rebuild dist * ui: sentence-case remaining status strings, fix stale ModelPicker comment * ui: promote composer reasoning pill to --text * ui: shared Wordmark component — logo mark + all-black OpenResearch text * ui: match home wordmark weight to the landing page (800 -> 700) * chore: bump version to 0.1.68v0.1.68 |
||
|
|
0a42fb8b28 |
Onboarding tour: spotlight walkthrough of the workspace (#102)
* Onboarding tour: spotlight walkthrough of the workspace Seven-step tour (welcome, composer, model picker, Files, Compute, experiments panel, new session) shown once after first-run setup and replayable from a help button in the chat header. Ported from the openresearch.sh onboarding system: data-onboarding attribute targeting, ResizeObserver/MutationObserver bounds tracking, box-shadow spotlight dim, viewport-clamped card with pointer arrow. Completion persists to localStorage (orx:tour-done). Escape is captured so it can't interrupt a running agent turn. * Tour polish: exact-primary ring, data-tip tooltip on replay button The ring's 45%-alpha glow plus a 1.5px border read as a lighter red than the theme primary; a crisp 2px border with no glow matches it exactly. The header help button now uses the app's data-tip tooltip instead of a native title. |
||
|
|
726ce566b4 |
Local-mode skill variants, slimmer playbook, no-merge/rebase rule, first-run setup questions (v0.1.67) (#101)
* Local-mode bodies for compute + experiment-tree skills; slim and extend the playbook - agent-skills/orx-compute/SKILL.local.md and orx-experiment-tree/SKILL.local.md: local-mode variants (same public names) that drop managed-SKU compute, cache-dir clone flow, and locally-unavailable commands (orx experiments, exp cmd, artifacts, query, chart); orx-compute also dedupes the per-backend shared-contract boilerplate into one section. - src/local/agent_skills.rs: serve the local bodies in SkillSet::Local via _LOCAL/_CLOUD consts sharing one description each. - SYSTEM_PROMPT.md: fold 'Where runs execute' into cardinal rule 5, drop content now covered by skills (baseline para, lit para, report layout, supervise note); add cardinal rule 6 (never merge/rebase a branch with a completed non-failed run; child + merge commit; never rebase) and a 'Learn how the user runs their code' section (ask about env/deps before the first launch on a fresh project; two env failures -> stop and ask). The no-merge/rebase rule is echoed in orx-git's rules list. * chore: bump version to 0.1.67v0.1.67 |
||
|
|
aba90b6298 |
Files tab redesign: split explorer with inline preview (#100)
Replace the pill-header list + full-page report swap with a master-detail split: a resizable file tree on the left (folders open by default, report folders titled by their report, dir path demoted to a copyable footer) and a persistent preview pane that renders everything inline — reports and markdown as GFM docs with a view-source toggle, images and PDFs directly, other text as highlighted code via the new shared CodeView (also adopted by FileViewer), with an Open-raw fallback for binary/oversize files. Icon-only controls get a CSS data-tip tooltip primitive; empty states for no-files and nothing-selected; FilesTab keyed by project and PreviewPane keyed by path so state can't leak across switches. |
||
|
|
a72c4cdc2b |
feat(codex): native plan mode as a selectable permission mode (v0.1.66) (#98)
* feat(codex): native plan mode as a selectable permission mode
Codex ≥ 0.144 supports plan mode as an app-server collaboration mode.
Advertise Plan + Auto + Bypass for codex and wire the full flow:
- initialize sends capabilities.experimentalApi (unlocks collaborationMode)
- conditional per-turn collaborationMode mask: plan on plan turns; default
on a non-plan turn only when the thread may be sticky-planned (DB
prev_permission_mode threaded via TurnCtx + in-memory last-sent tracking);
otherwise no mask (a mask always injects a template)
- render streamed plan items (item/plan/delta + completed plan item) and
synthesize an end-turn plan card / PlanStrip
- requestUserInput -> question card, answerable inline or via composer;
per-kind server-request settle shapes (approval/userInput/other)
- resume_from_prompt: plan (send-message), permission/question (inline reply)
- store: keep 'plan' off the codex retire-list; reuse claude.rs
synthesize_resume / should_synthesize_plan via harness re-export
- UI: PlanStrip agentLabel; ChatPanel pendingQuestion gate includes codex
Legacy exec path (< 0.144) keeps Plan->read-only, with an updated agent note.
* fix(chat): keep prompt-resume scaffolding out of the transcript
Plan/permission card resumes send synthesized text the user never typed
("Implement the plan.", "The user approved that action. Continue.").
Persisting it as a user message misattributes it in the UI. Split the
transcript text from the harness text in send_message (the slash-skills
precedent): plan/permission resumes now record only the user's own note
— nothing at all when there isn't one; the resolved card tells the rest.
Question resumes still record the answer, which the user did choose.
* chore: bump version to 0.1.66
v0.1.66
|
||
|
|
9be46cd4dd |
Telemetry: delineate human vs automated installs; kill consent phantom persons (#99)
* Delineate human vs automated installs in telemetry; kill consent phantoms - Every event now carries an install_kind base property: "human" by default, or the machine context set via ORX_TELEMETRY_CONTEXT / the new `orx telemetry context <value>` (persisted in settings.json), so first-party automation (cloud agent boxes) can be excluded from human usage metrics centrally via PostHog's internal-users filter. - cli_telemetry_consent no longer mints a fresh UUID person per event: agrees use the persisted install id (joinable with the active-install population), declines collapse into one fixed sentinel id. * Reattach record_consent docs after helper extraction; doc nits |
||
|
|
087fde79af |
Contain file serving to the files dir even through symlinks (#97)
* Contain file serving to the files dir even through symlinks is_safe_rel_path only blocks lexical escapes (..); a symlink inside the files dir pointing outside it was followed by read_file, read_report_markdown, and the report-title scan, so anything reachable on disk could be served to whatever can reach the local API. Resolve every served path with canonicalize() and require it to stay under the canonicalized files dir. Internal symlinks keep working and now also appear in listings (DirEntry::metadata never follows links, so they were silently dropped before); escaping ones are skipped from the listing and fingerprint, and refused by the serve endpoints. Delete resolves parent segments the same way but removes a final-component symlink literally, never its target. * Review nits: shared followed_metadata helper, drop redundant pre-check Extract the symlink-aware metadata lookup shared by collect_tree and hash_dir, let resolve_contained handle read_report_markdown's path validation, and document the containment guarantee (and the accepted check-vs-use gap) at module level. |
||
|
|
66c8159511 |
Match the web app's favicon and retitle the tab to OpenResearch CLI (#96)
Ports favicon.svg from openresearch.sh so the embedded UI's browser tab shows the same brand mark, and retitles the tab "OpenResearch CLI" to distinguish it from the hosted web app. favicon.svg joins index.html in the no-cache branch since it's served under a fixed (unhashed) name. |
||
|
|
67190285ab | chore: support automated reproductions (#95) | ||
|
|
2c72297604 | chore: add marimo reproduction notebooks (#94) v0.1.65 | ||
|
|
99f358c5fe |
chore: neutral reproduction assessments (#93)
* chore: neutral reproduction assessments * chore: bump version to 0.1.64 * chore: plan reproductions to compute budgetv0.1.64 |
||
|
|
87df17ce0f |
chore: Report md heading styles (#91)
* Report pane: restore Markdown heading hierarchy in report-md Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Bump version to 0.1.63 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
3010c8cea6 |
Guide reproduce-paper toward visual reports (#92)
* Guide reproduce-paper toward visual reports * Encourage evidence-rich report figures |
||
|
|
1526d78a14 |
ControlPlane trait: plane-agnostic dual-mode commands (v0.1.63) (#90)
* ControlPlane trait: hoist dual-mode command bodies behind two planes Phase 3 (final) of the control-plane separation. Adds src/plane.rs — a #[async_trait(?Send)] ControlPlane trait with 14 verbs derived from the six dual-mode commands' paired arms — with ServerPlane (wraps client.rs, connects via require_credentials on the server arm only, with a sync ServerPlaceholder so pre-login guards keep firing before the login check) and LocalPlane (owns the Store; ?Send because rusqlite is !Sync — planes are driven inline under block_on and never spawned). The six commands collapse to resolve-once-then-verb; their local/server bodies move verbatim into the plane impls. Printing stays in the impl where the two planes differ (status blocks, launch recaps) and in the command where shared (runs table, logs footer). Domain type plane::Run (+RunLog/RunOrigin) merges the two run_failure_detail twins — output.rs no longer depends on client::Run. CLI-facing only: local::model (the up-API wire), the SQLite schema, and the client.rs DTOs are untouched, verified by byte-identical golden snapshots of the affected commands on a seeded isolated store. The plane lives outside src/local/ because local/mod.rs's invariant (nothing there calls client.rs) would otherwise break. up.rs and supervise.rs stay off the trait as the deliberate cloud-mirror bridges. * Bump version to 0.1.63v0.1.63 |
||
|
|
a0b6f3ea5f |
Persistent cross-session agent memory (user + project memory files, {memory} playbook token) (#89)
* Persistent cross-session agent memory: user + project memory files inlined via {memory} playbook token
* Mention files/ + memory/ in datadir module doc
|