Commit Graph
59 Commits
Author SHA1 Message Date
Daniel Kim cbe099dfa9 Use native skill discovery for all OpenResearch agents (#826)
* Enable shared native skills for ACP agents

* Exercise ACP native skill installation end to end

* Confirm ACP disconnect before testing skill restore

* Combine native skill discovery and remove duplicate catalog
2026-10-29 15:24:01 -07:00
Myles AndersonandClaude Opus 5.5 08048f3f1f OR-626: Open papers from openresearch:// links in the macOS app (#797)
* Open papers from openresearch:// links in the macOS app

openresearch.sh's per-paper download page links to
openresearch://reproduce?arxiv=arXiv:<id>. The macOS bundle now claims
the scheme; the app opens /reproduce, which shows the New Project dialog
already set to that paper, and seeds the new project's first chat with a
"Reproduce the paper …" prompt (not sent).

A link reaching the running app navigates in-page so unsaved-work guards
still apply; a cold launch holds it until the dashboard server answers.
Windows and Linux registration follows separately.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Hold deep links until the page can take them; skip linked-paper warm-up

A link that arrives after the window shows but before the dashboard's
script has loaded now waits for the page-load Finished event instead of
being dispatched to a page with no listener. A paper filled in from a
link no longer pre-warms starter prompts (a model call) until the user
picks it themselves, so cancelling the dialog spends nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Deep-link papers by alphaXiv id, including alphaXiv exclusives

openresearch.sh now links to openresearch://reproduce/<paper id>, where
the id is alphaXiv's universal id: an arXiv id or an alphaXiv-exclusive
slug like 2610.learning-spectrum-matched-shellsort. The id rides in the
path (/reproduce/$) because TanStack reads ?id=2401.10000 as 2401.1.

The New Project dialog resolves the id directly, exclusive papers'
PDFs come from alphaxiv.org/abs/<id>.pdf, and version stripping follows
alphaXiv's rule (a v after a dash is part of the name).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 12:09:50 -07:00
Daniel Kim c51b9e7435 Run relevant PR checks and reserve packaging for releases (#763)
* Run relevant PR checks and reserve packaging for merged releases

* Select embedded JavaScript tests and skip platform documentation builds

* Fetch only comparison commits for CI metadata checks

* Parallelize Windows PR validation and isolated timeout fixtures

* Reuse cached Cargo resolution and consolidate CLI test harnesses

* Overlap ARM PR lint and native linking with timing diagnostics

* Drop unproven cache preparation and measure ARM codegen parallelism

* Keep measured CI wins and remove duplicate frontend generation
2026-10-22 21:15:36 -07:00
Daniel KimandAleksandr Rodionov 228231a0e2 Fix compaction across agent harnesses and clear completed progress (#761)
* Fix compaction lifecycle and completion across agent harnesses

* Keep unsupported compaction commands accessible from the keyboard

* Silently consume unsupported compaction commands

* Skip unsupported compaction before checking for a saved chat

* Credit the original Codex compaction contributor

Co-authored-by: Aleksandr Rodionov <rexarrior@yandex.ru>

---------

Co-authored-by: Aleksandr Rodionov <rexarrior@yandex.ru>
2026-10-22 18:58:11 -07:00
Myles AndersonandClaude Opus 5.5 dceecf929a Trackio: Spaces, hf runs, zero-setup local, Visualize view, and orx metrics (#756)
* Trackio: Spaces, hf runs, zero-setup local, embedded charts, and orx metrics

- TRACKIO_SPACE_ID support (wins over TRACKIO_SERVER_URL, as in Trackio), with a
  Settings check of the Space, its stage and HF_TOKEN write access.
- hf Jobs receive the Trackio settings (URL and write token as secrets).
- With nothing saved, local runs log to Trackio's local store and orx up starts
  `trackio show` on demand, stopping its process group on exit.
- Every run gets ORX_EXPERIMENT, ORX_EXPERIMENT_TITLE, ORX_PARENT_EXPERIMENT,
  ORX_COMMIT and TRACKIO_PROJECT (per pod on k8s).
- Visualize opens a run's Trackio charts in the right pane, only for runs that
  logged; private Spaces get HF's read-only __sign token.
- `orx metrics <runId>...` summarizes logged curves for agents; the compute and
  experiment-tree skills use it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Address review: orphan-proof local dashboard, Space caching, Visualize everywhere

- The local `trackio show` exits when orx's stdin pipe closes, so restarts,
  crashes, SIGKILL and Ctrl-C never orphan it; startup runs in its own task.
- Space lookups are cached (failures included) so polls never reach whoami;
  sign cache keyed by token; HF token only sent to private Spaces; 401/3xx
  reported plainly; HF_ENDPOINT honoured.
- Runs route returns 502 on failure so an open embed survives blips; a Space
  not created yet is a normal 200 with no dashboard.
- `orx metrics` reports per-run errors, exits non-zero when nothing could be
  read, and explains missing local data or Spaces.
- k8s: manifest values win over run context (except ORX_RUN_ID), listed first
  for $(VAR) expansion.
- Launch warns when remote runs would keep Trackio metrics on the remote box.
- UI: tree cards get a labelled Visualize button that opens the in-app view
  (cards widen to 336px), refetch once a run ends, embed follows the theme,
  loading/error states, gesture handlers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Fix Windows clippy and address Greptile review

- Box the local dashboard child: on Windows a tokio Child makes
  LocalState::Running trip clippy's large_enum_variant.
- An SSH workspace host offers no embed for its loopback-only local
  dashboard, which the user's browser cannot reach.
- k8s: TRACKIO_PROJECT joins ORX_RUN_ID as forced, since orx finds a run's
  Trackio data by both; other run-context vars still yield to the manifest.
- `orx metrics` reports a failed alert read instead of showing no alerts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 16:32:19 -07:00
Wu ShuwenandDaniel Kim f1b6c2d8f0 Use selected OpenCode models and remove AI skill screening (#670)
* fix: avoid inference during OpenCode detection

* Bind OpenCode auxiliary generation and remove AI skill screening

---------

Co-authored-by: Daniel Kim <sox8502@gmail.com>
2026-10-22 14:57:58 -07:00
Daniel Kim 3f5bac6541 Unify interactive questions across Codex, Claude, and OpenCode (#752)
* Unify interactive questions and repair harness approval handling

* Preserve answers across skipped and batched questions
2026-10-22 13:34:14 -07:00
Daniel Kim 2149a29467 OR-397: Improve onboarding demos, charts, and image previews (#593)
* Make the nanochat demo quick and render interactive experiment figures

* Fix chart ranges and bound inline preview sizing

* Refine demo results, chart interactions, and image carousels

* Split experiment evidence skills and address chart review feedback

* Preserve zoom selections and format small axis ticks

* Choose axis label precision from tick spacing

* Keep single training-step ticks exact after zoom

* Retain visible confidence bands and skip empty keyboard slices

* Load results guidance before experiment summaries and avoid duplicate comparisons
2026-10-09 01:20:57 -07:00
Daniel Kim ea3b9689c2 feat: add durable model and experiment telemetry (v0.2.14) (#482)
* feat: add durable model and experiment telemetry for v0.2.14

* Fix telemetry opt-out, partial accounting, and identity retention
2026-09-30 01:20:27 -07:00
Daniel Kim 35c687066f OR-304: Compute CLI, custom instructions, and Slurm recovery (#429)
* Add compute management CLI, custom guidance, and Slurm recovery

* Fix Windows test access to shared SSH request fields

* Clarify compute instruction updates and remove review checklist
2026-09-25 14:25:42 -07:00
Myles AndersonandClaude Opus 5 a15b3a61a4 Add /compact, which every coding agent can run (#411)
* Add composer commands that work the same on every coding agent

The composer's `/` menu now offers /new (alias /clear), /resume, /model,
/plan, /copy and /export on every harness. The dashboard runs each one
itself, so behavior no longer depends on what the selected agent's own CLI
supports — none of them act on slash text in the mode orx runs them in
except Claude Code, and only partially.

`planCommand.ts` becomes `composerCommands.ts`, a registry the menu, the
chips and the send path all read from. A skill whose bare name collides
with a command is dropped from the menu: the composer inserts, and the
server resolves, skills by bare name, so it would otherwise shadow.

Only /plan composes with a prompt. The rest run as a whole message, so
prose that merely mentions `/export` still reaches the agent, and a
command is chipped only where it would actually run.

/resume lists chats across every project: GET /api/chat/sessions now takes
`scope=all` (400 without it or a projectId) and returns the 500 newest.
Picking a chat in another project switches to it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Add /compact, which every coding agent can run (#384)

The composer's /compact (alias /summarize) shortens a chat's context on
every harness. Claude Code and Codex compact their own context in place
and OpenCode summarizes through its endpoint, so the session survives;
Cursor and the legacy `codex exec` path have nothing to call, so orx
summarizes the transcript itself and reseeds a fresh native session with
it through `bootstrap_context`, which is injected exactly while no native
session exists.

An agent that is merely not running is never traded for a summary: a
resumable session reports "send a message first" instead, since a summary
keeps only the newest 32 KiB of the chat.

Compaction runs as a turn. It holds the session's turn slot, reports
itself as a transcript row that shimmers while it works, and every write
is gated on still owning that slot — Stop cancels it, and a cancelled row
settles as failed rather than sitting at running. The row is the progress:
it survives a reload, reaches every open client, and carries the reason
when it fails. Codex waits for the compaction turn it started rather than
the RPC ack, filtering by that turn's own id; Claude retires its child if
its /compact fails, so an abandoned result cannot land in the next turn.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 11:03:23 -07:00
Daniel Kim 38a880f4be Show original paper figures inline and improve literature review (#367)
* Render local images inline in chat and guide literature-review figures

* Open inline figures in the viewer and separate contextual captions

* Polish inline figure viewer and streamline grounded literature review

* Restore literature retrieval procedure and prioritize comparative visual evidence

* Keep skill routing generic and simplify visual guidance

Preserve canonical descriptions in the generic skill list because crowded native catalogs can abbreviate them. This intentionally trades a small prompt increase for complete routing context. Keep the full retrieval procedure and clarify source PDF extraction.
2026-09-20 22:40:32 -07:00
Daniel Kim 05132f3c50 Fix harness setup recovery and release v0.2.6 (#393)
* Fix harness setup recovery and native installer verification

* Bump version to 0.2.6
2026-09-20 00:59:28 -07:00
Richard LuoandDaniel Kim 866c507ba3 ✨ feat(harness): add agy coding agent integration (#332)
* ✨ feat(harness): add agy coding agent integration

- Implement Harness trait for agy harness adapter
- Support streaming NDJSON turns via agy --output-format stream-json
- Support multi-turn conversation resumption via --conversation <id>
- Support project worktree directory and session skills via .agents/skills
- Support dynamic model enumeration and auth verification via agy models
- Support permission modes (ask, auto, bypass) and effort tiers (low, medium, high)
- Add agy alias to orx install-skills
- Add brand logo mark and model picker labels to web UI
- Include comprehensive unit and stream folding tests

Closes #223

* 🐛 fix(harness): handle Antigravity failure states

* Complete Antigravity approvals, cancellation, and product integration

---------

Co-authored-by: Daniel Kim <sox8502@gmail.com>
2026-09-18 13:21:42 -07:00
Daniel Kim 5fa74d1c3b Support OpenCode V2 alongside V1 with safe history migration (#348)
* Support OpenCode V2 alongside V1 with safe history migration

* Avoid guessing OpenCode V2 authentication method

* Remove OpenCode migration backups after validated upgrade
2026-09-15 16:49:19 -07:00
19f91fd26a Add Cursor as a first-class orx up harness (#313)
* feat: add Cursor as a first-class orx up harness

Spawn isolated Cursor CLI sessions from orx up like Claude Code, Codex, and OpenCode, so included Cursor usage can drive the auto-research loop.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: use Cursor's 2.5D cube mark in the model picker

The 2D silhouette collapsed to a diamond at 14px; the brand-kit cube stays recognizable.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Polish Cursor harness integration and shared chat interactions

* Make Cursor skill-path assertion respect XDG configuration

---------

Co-authored-by: ZeguanXiao <www.hainanxzg@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Daniel Kim <sox8502@gmail.com>
2026-09-11 21:15:57 -07:00
Daniel Kim eaba8668b6 OR-272: Improve skill picker and reusable library management (#306)
* Improve skill discovery, picker, and reusable library management

* Move customization guidance into bundled orx-customize skill
2026-09-10 21:25:13 -07:00
Myles AndersonandClaude Opus 5 9b05f9771c OR-199 Windows support for the CLI and dashboard (#299)
* Make orx build and resolve its tools on Windows

Block A of the Windows beta: the fixes that need no Windows machine to
write, so the branch is ready to clone the moment one is set up.

- Extend a bare binary name with PATHEXT when searching PATH. Every
  harness is looked up by bare name, so on Windows the picker reported
  Claude Code, Codex and OpenCode all absent and the dashboard could do
  nothing.
- Compose a child's PATH with `;` rather than a hardcoded `:`.
- Find bash through `git` rather than PATH. The `bash.exe` usually on
  PATH is the WSL launcher in System32, which sees a different
  filesystem than the run directory it would be handed.
- Give git an empty path it understands. `/dev/null` reads as a missing
  relative path on Windows, silently re-enabling repository hooks.
- Check a local run's liveness with a zero-timeout wait; there is no
  `ps` to shell out to.
- Link through native_store::create_symlink, which already has both
  branches, instead of calling the unix symlink directly.
- Add a windows-latest CI job so none of this regresses.

The Windows branches here have not been compiled yet — that is the next
step, on the machine.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Allow CI to be dispatched against a branch

* Compile the remaining unix-only code paths out on Windows

Round one of the Windows compile, from the first CI run: 25 diagnostics,
seven root causes.

- Give remote_host the `#[cfg(not(unix))]` twins its uid/mode helpers were
  missing, matching what `directory_writable` already does. Windows keeps
  only the symlink and directory checks in `ensure_private_dir`, which is
  a weaker guarantee than the unix path makes.
- Gate the control channel itself. It is a Unix domain socket, so
  `--remote-host` is now refused up front on Windows rather than starting
  a host nothing could attach to. Local `orx up` is untouched.
- Annotate `signal`, whose type came only from the cfg(unix) block.
- Gate the EXDEV rename fallback, the inode and mode assertions, and the
  shell-hook tests, all of which assert on things Windows does not have.
- Silence the parameters that are only read on unix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Gate the remote-host command surface off on Windows

Round two, from the cascade the unresolved socket import had been
suppressing: every caller of the six control-channel functions.

The module splits cleanly in two. `orx up` uses its types and its
dashboard locking — HostDescriptor, DashboardLock, canonical_data_dir —
and none of that needs a socket. The `orx remote-host` command surface is
socket-backed all the way down, so it is gated as a unit and the command
now refuses on Windows instead of failing somewhere further in.

Also: the two imports only the gated half reached, and `signal`, which
loses its one assignment on Windows and with it the need for `mut`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Keep the compiled-out remote-host half rather than gate it item by item

Round three was 24 dead-code and unused-import lints and no real compile
errors — the tail of removing a feature's entry points while its
machinery stays behind.

One scoped allow on the module says that plainly. Gating each constant,
helper and request type would be bulkier than the code it guards, would
have to be unpicked the moment the control channel gets a Windows
transport, and would say nothing the module comment does not.

updates.rs is the same shape at smaller scale: relaunch_target and
relaunch_args keep their tests on every platform and lose only their
caller in the binary.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Stop handing git a verbatim path, and let test cleanup fail

The Windows suite ran for the first time: 662 passed, 46 failed. Two
causes account for 27 of them.

`repository_git_dir` canonicalizes, and Windows answers canonicalize with
a `\\?\C:\…` verbatim path. Git rejects those outright — "not a git
repository" — so importing any existing repository failed. This is a real
bug, not a test artifact; the prefix now comes off for drive paths, which
are the only ones with a plain form.

The other 21 were `remove_dir_all(root).unwrap()` in test teardown, which
Windows refuses while any handle into the tree is still open. The
codebase already prefers a tolerant cleanup in 146 other places; these 86
now match. All of them are inside `#[cfg(test)]`.

Also: the probe fixtures asserted on POSIX PATHs, which are not absolute
on Windows and so were rejected by the parser under test. They are
parameterized rather than gated — the parsing they cover is not
platform-specific, only the fixture was.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Check out LF on every platform

Git for Windows defaults to core.autocrlf=true, and SYSTEM_PROMPT.md and
the agent-skills SKILL.md files are include_str!'d at build time. A
Windows build therefore embedded CRLF and then failed to parse it:
playbook_md() splits the template on `-->\n\n` to drop its leading HTML
comment, that split stopped matching, and `unwrap_or` kept the comment —
so every agent session would have been handed OpenResearch's internal
notes as the opening of its system prompt.

Two tests caught it, one of them by scanning the rendered playbook for
unresolved `{token}` placeholders rather than a fixed list. That is the
only reason this surfaced as a test failure and not as a shipped binary.

Nothing renormalizes: no tracked text file has CRLF in the index today,
so this only governs what lands in a working tree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Fix what Windows found: exec bits, remote paths, cancellation

Round two of the Windows suite: 691 passed, 17 failed. Four of those were
real bugs rather than test artifacts.

Demo onboarding rejected the demo it had just built. NTFS has no
executable bit, so runcpu.sh committed as 100644, the tree hash moved,
and the commit ids drifted off the BASELINE_SHA/EXPERIMENT_SHA the demo
validates itself against. The mode now reaches the tree through the
index, where it does not depend on the filesystem.

Remote paths were validated with local rules. `storage_root` asked
`Path::is_absolute` about `/home/me/.cargo/bin/orx`, which is false under
Windows, so a Windows client rejected every legitimate remote path — and
the PathBuf it built would have reached a Linux shell separated by
backslashes. Remote paths are POSIX whatever the client runs, so they are
handled as strings now.

`manage_local_file` took `reports/result.md` from the dashboard and
answered `reports\summary.md`, which the next request would have carried
straight back. Relative paths cross the API `/`-separated.

Cancelling a local run did nothing: there is no process group to TERM.
`taskkill /T` takes the tree instead, which is what TERMing the group was
for.

The rest were fixtures asserting on POSIX shapes, a filename using a
character Windows reserves, and two LaTeX tests that need a symlink and a
shebang.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Let the index own the demo's exec bit

Setting the mode through `update-index` fixed the commit ids but left the
index at 100755 against a working tree NTFS always reports as 0644. With
`core.filemode true` git believed the filesystem, read runcpu.sh as
modified, and refused to check main back out. Windows trusts the index
instead, which is where the mode came from.

The two telemetry failures are not diagnosed. HTTP delivery itself works
on Windows — `sender_posts_the_first_party_endpoint` asserts an
Acknowledged post and passes — so what fails is the removal that follows
it, and reading has not told me why. Both assertions now report what a
second removal says, which separates a blocked delete from a delete that
succeeded while the name lingered.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Keep the demo's origin path native, and judge cancel by liveness

Three down to two.

`join("demo-repos/nanochat.git")` embeds a separator the platform did not
choose, so on Windows the bare origin came out as
`...\data\demo-repos/nanochat.git` — mixed, and textually unequal to what
git echoes back for the remote it was given. Filesystem calls did not
care; comparing the recorded URL to git's answer did. Split into two
joins, in product code as well as the tests that caught it.

`taskkill /T` returns non-zero when any descendant has already exited on
its own, which says nothing about whether the run stopped. Cancellation
now reads the leader's liveness, which is the question actually being
asked.

The telemetry pair is one step from diagnosed. The retry in the previous
round answered Ok(()), so the file was there and freely deletable and the
removal was never reached — delivery must have come back Retryable. The
assertion now names the outcome, which separates a request that never
landed from a response classified unexpectedly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Read the request before answering it in the telemetry mock servers

The last two Windows failures, and they were the tests' fault rather than
delivery's.

Two of the three mock servers accepted a connection and wrote their
response without ever reading the request. Closing a socket that still
holds unread data aborts the connection on Windows instead of closing it
gracefully, so the client saw a reset rather than the reply it had
already been sent, and classified a 400 as Retryable — which left the
outbox file the assertion expected to be gone. The third server always
read, which is why it passed and misled the search toward the removal.

The read loop it already had is now shared by all three.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Canonicalize into paths a child process can actually use

An audit of the `orx up` paths no test exercises. Every bug this branch
has fixed so far was in code a test happened to touch; the dashboard's
own request handling was the largest area where that was not true.

Windows `canonicalize` answers with a verbatim `\\?\C:\…` path, and
`CreateProcessW` will not take one as a working directory. The dashboard
canonicalizes a session's checkout root and then runs git in it — twelve
call sites behind `resolve_checkout_root`, covering the code tree, file
reads and writes, and diffs — so none of that could have worked. The same
root reaches `run_shell_command` as the cwd for a `!` command, and Codex
receives canonicalized paths as its sandbox writable roots.

Consistency matters as much as the spelling: containment checks compare a
canonicalized child against a canonicalized root, and a mix of the two
forms denies access to paths that are genuinely inside. So every
canonicalization in the crate now goes through one helper rather than the
handful that happened to be reachable from a failing test — that is what
makes the property hold rather than the individual fixes.

The earlier one-off in `repository_git_dir`, which the import failure
found, is folded into it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Look for the harness binaries under the names they actually have

Second audit pass, over the spawn path.

`find_claude` and `find_opencode` fall back to their installers' drop
locations when PATH does not answer, and both spell them as bare names —
`~/.local/bin/claude`, `~/.opencode/bin/opencode` — which match no file
on Windows. `~/.local/bin` is exactly where Claude Code's own Windows
installer puts claude.exe, so the fallback was dead precisely where it
was needed. Both now look for the same names PATH does.

The rest of the spawn path holds up: config directories travel as
environment variables, where a raw path is correct; Codex's config.toml
is written through serde_json, whose backslash escape is the one TOML
wants; and the binaries themselves come from find_on_path, which learned
about PATHEXT earlier on this branch.

One gap left deliberately: the PATH guard writes zsh and bash startup
hooks and needs ZDOTDIR or HOME to place them, neither of which Windows
sets, so it returns early and the guard is simply absent there. It fails
closed — a session gets no hook rather than a broken one — and building a
Windows equivalent is worth more than a beta needs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Address review round one

Six blockers, all real.

`orx_bin_dir` dropped any directory whose name contains a colon, which on
Windows is every absolute path, so agents were never handed the running
orx on PATH — and the separator constant added earlier sat in a branch
that never ran. It filters on that constant now.

A dashboard shell command that hit its timeout killed nothing on Windows
and then waited on the child with no timeout, hanging the turn forever.
The non-unix arm terminates the tree, the shape latex already used.

BIBINPUTS/BSTINPUTS joined with a colon, so bibtex on Windows lost both
the paper's own directory and the system defaults.

`restore_local_repository` never pinned core.autocrlf, so a restored demo
clone was written CRLF against LF blobs and then failed the cleanliness
check that reads it with global config disabled — telling the user their
demo "is not the OpenResearch nanochat demo".

The remove_dir_all sweep had converted four setup removals along with the
teardown, letting tests pass while testing nothing.

The Windows uid/mode stubs were unreachable: their whole chain is rooted
in cfg(unix) entry points. Gated instead, stubs deleted, and with them a
comment claiming a weaker-but-working private runtime directory that does
not exist.

Also from review: prefix stripping now matches the parsed prefix, so a
non-UTF-8 name cannot keep `\\?\` while its root loses it and fail its own
containment check; UNC verbatim paths are decoded rather than passed
through. PATHEXT set-but-empty no longer reports every tool missing. The
bash fallback no longer resolves back to the WSL launcher it rejects.
pdfPath goes through the same API path rule as every other relative path.
The telemetry signature widened for a test message is reverted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Address review round two

Two blockers, both introduced by round one's own fixes.

Deleting the Windows uid/mode stubs was wrong: `open_lock` is ungated and
calls all three, and `DashboardLock::acquire` reaches it on every `orx
up`. That is a Windows compile error on the one platform this branch
exists for, and macOS cannot see it. The ownership and mode checks are
gated inside `open_lock` now, which keeps the file-type check everywhere
and states plainly what Windows does not enforce.

Teaching `orx_bin_dir` about the platform separator also handed
`ORX_BIN_DIR` to PATH_GUARD, a POSIX snippet that joins `PATH` on colons.
Under Git Bash a drive letter split it in two and the guard re-prepended
on every startup. The stamp is skipped off unix; `prepare_env` still
fronts the directory, which was the point.

Also: `cancel_job`'s cfg'd `return` is the function's last statement once
stripped, which the Windows clippy job would have rejected; the restore
path pins `core.eol` alongside autocrlf so a global `core.eol=crlf`
cannot reproduce the same dirty checkout; the verbatim UNC head is built
from `OsString` like the tail it is joined to; and the null-device
comment no longer claims git falls back to the repository's own hooks,
which it does not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Polish from review round three

No blockers in either round-three review. Taking the nits: `open_lock`
fstats once instead of twice; the Windows branch of the `ORX_BIN_DIR`
stamp is a `#[cfg]` rather than a runtime-false `cfg!`, so it stops
resolving the executable only to discard it; two doc comments that
described one platform now describe both; and the ownership comment names
the case it does not cover — a lock under a shared `ORX_DATA_DIR` has
only its ACL on Windows.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Keep the windows job out of the release gate

release.yml calls ci.yml and requires it to pass, so the windows job
added here would have held up a macOS or Linux release over a platform
those releases ship nothing for.

`plan` is passed only by release.yml, so skipping on it draws the line
where it belongs: Windows still gates pull requests, which is what keeps
it from rotting, and never gates a release.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Upload a release orx.exe, not a debug one

The artifact is what a tester is handed, and a debug build is slow enough
to colour their impression of the dashboard rather than the code's.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Turn SSH multiplexing off explicitly on Windows

Win32-OpenSSH implements no connection multiplexing: it accepts
ControlMaster and ControlPath and then never creates a master. Omitting
them is not enough, because the user's own ssh_config can still set a
ControlPath, and one it honours fails the connection outright with
"getsockname failed: Not a socket". Passing ControlMaster=no and
ControlPath=none is what overrides that.

The socket machinery goes with it — the control directory, its path hash
and its preparation are unix's, and `master_is_running` can only answer
false where no master is ever created. Nothing changes on unix, where the
shared master still covers later status, log and job commands.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Address review

Two tests asserted the unix option set unconditionally and would have
failed on Windows — the very platform this change is for, and one CI
never builds. Both derive the expectation from the platform now.

`PathBuf`'s only uses became unix-gated, so its import would have failed
the Windows clippy job under -D warnings.

git shells out to ssh with its own command line, which never carried the
override, so a user's ssh_config could still break git-over-SSH on
Windows for exactly the reason this change exists. Routed through one
helper.

The rest: a doc comment my new test had come between and its rightful
test; four comments still promising multiplexing unconditionally, the
`interactive_args` one worst because it states the invariant that is
false on Windows; the new test pinned to the exact vector the way its
neighbour already is; and BatchMode coverage split out, since it holds on
every platform and was about to be gated away with the ControlPath half.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Address review round two

No blockers in either round-two review. Taking the nits.

Deriving the option count from `multiplexing_opts` had quietly removed
the only assertion pinning the unix vector — ControlPersist could have
been deleted and nothing in the tree would have failed. Pinned alongside
the Windows one.

The rest: a test whose name promised reuse on a platform that reuses
nothing; `git_ssh_command` says which command it builds; two more doc
comments of the class round one fixed, in the two other files that carry
them; and the Windows sentence dropped from `ssh_opts`, where it was the
third statement of the same fact within 150 lines.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Point git's config paths at an empty file, not the null device

Onboarding died on Windows at the demo's first `git init`:

    fatal: unable to access 'NUL': Invalid argument

`NUL` is right for `diff --no-index`, which recognizes it before touching
the path, and wrong everywhere a path is actually read. Git skips a
config file that reports ENOENT and fatals on any other error, and
Windows cannot `access()` its null device by name at all.

A real empty file behaves the same on every platform, and this file
already had that idiom in `clone_public_repository`. Shared now, and
pointing `hooksPath` at it still disables hooks, since git finds no hook
inside a file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Give the spawned bash its own toolchain on PATH

A local run died at `run.sh: line 6: mkdir: command not found`. bash was
running; it just had no coreutils. Git for Windows keeps those in
`usr\bin` and puts only `cmd` on the Windows PATH, so a bash started from
a Windows process inherits a PATH with no Unix tools at all.

Fronted for the two bash spawns only — a local run's launcher and the
composer's `!` command. Doing it for every child would shadow Windows'
own `find` and `sort` with the MSYS ones, which is a worse bug than the
one being fixed.

prepare_env's PATH is now a function the bash spawn can extend, so the
running orx still comes first and the toolchain sits ahead of both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Fold the toolchain into the PATH run.sh exports

The previous commit set the toolchain PATH on the spawn, which run.sh
then overwrote on its second line: localrun exports the shell's PATH into
the script so a user's own command finds python and uv, and that export
wins over anything the process was started with.

Caught by the agent inside the dashboard, which read the generated run.sh
rather than the launcher and saw the hardcoded Windows PATH for what it
was.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Let the demo probe run under Git Bash

Two things differ there, and the probe hit both. uv ships no shell
installer for Windows, so the setup uses its PowerShell one; and uv lays
the venv out as `Scripts/` rather than `bin/`, so both activations are
tried — either platform can have either layout depending on how the venv
was made.

These are Rust constants rather than files in the demo repository, so the
commit ids the demo validates itself against do not move. `runs/runcpu.sh`
carries the same POSIX assumption and is committed, so making the
project's own run command portable is a separate change that regenerates
those ids.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Stop writing a Windows PATH into a bash script

The run script exported the process PATH so a user's command could find
python and uv. On Windows that value is semicolon-separated, and bash
splits PATH on `:` — so it collapsed at the first drive colon and nothing
resolved, `mkdir` included. Adding the Git toolchain to it, as the last
two commits did, only added more entries to the same unusable string.

Git Bash converts the PATH it inherits into POSIX form for itself, so the
fix is to leave it alone: the launcher sets the process PATH, and the
script no longer overwrites it.

The `cd` on the next line had the same shape — bash reads a quoted
`C:\…` literally — so the run directory is forward-slashed now.

Found by the agent running inside the dashboard, which read the generated
run.sh, spotted the separator mismatch, and verified a candidate PATH
against both splitters before proposing anything.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Hand the shell paths it can read, not Windows ones

`tar: Cannot connect to C: resolve failed` — GNU tar reads a colon before
the first slash as a `host:path` remote spec, so the snapshot archive's
Windows path made it try to resolve `C:` as a hostname. Forward slashes
would not have helped; the colon is what triggers it.

One conversion now covers both places a local path reaches the shell: the
archive tar unpacks, and the `cd` the launcher writes. `/c/Users/…` has
no colon at all. The HF backend passes a Linux container path and is
deliberately left alone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Give runs a UTF-8 stdout

A run died mid-training on `UnicodeEncodeError`: CPython encodes stdout in
the console's codepage, cp1252 on a default Windows install, and the
script printed a banner outside Latin-1. Nothing to do with the
experiment — it had already trained a tokenizer by then.

`PYTHONIOENCODING=utf-8` joins `PYTHONUNBUFFERED` as a default the author
can override, through the same helper every backend already calls, and
open-coded once more for kubernetes, whose env is a JSON array.

Set on every platform rather than only Windows: a Linux container with
`LANG=C` has the same ASCII default and would fail the same way.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Let git write paths past Windows' 260-character cap

Creating a session worktree for a deep repository failed partway through
with "Filename too long". Windows refuses paths over 260 characters
unless a program opts into the wide API, and a session worktree spends
about a hundred of them on `worktrees/<project>/chat_<session>` before
the repository's own paths begin.

`core.longpaths=true` on the invocations that write a working tree: the
shared helper every worktree and checkout goes through, the authenticated
fetch, and the anonymous clone. Read-only plumbing does not need it.

This was graded Minor in the original survey. It is a blocker for any
repository with a deep tree, which is most of them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Link the agent's config without asking Windows for a privilege

Windows grants symlinks only to an elevated process or one in Developer
Mode, so preparing the isolated agent home failed onboarding outright with
os error 1314. Fall back to the links that need no privilege: a junction
for a directory, which reads back exactly like a symlink, and a hard link
for a file, which does not — so the reconcile now recognizes one instead
of filing a conflict against it on every launch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Stop asking Windows' ssh to multiplex

Windows' OpenSSH cannot create the AF_UNIX socket ControlMaster needs, and
rather than decline the option it kills the connection outright with
"getsockname failed: Not a socket" — so every ssh call orx made from Windows
failed, whatever the destination. Drop the Control* options there and pay a
handshake per call.

Same option vector, same platform: `UserKnownHostsFile=/dev/null` is a name
Windows' ssh takes literally, creating a `\dev\null` on the current drive.
It now gets a scratch file of orx's own.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Count the Windows ssh opts the Windows way

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Revert the duplicate multiplexing fix

PR #302 already turns Windows' multiplexing off, and does it by setting
ControlMaster=no explicitly rather than by omission — which is what
overrides a ControlPath in the user's own ssh_config — and covers git's
ssh besides. Take that one instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Give Windows a real file for the host keys it must forget

`UserKnownHostsFile=/dev/null` is a name Windows' ssh takes literally,
creating a `\dev\null` on whichever drive is current. Point it at a
scratch file of orx's own instead; StrictHostKeyChecking=no is what makes
a recycled provider key acceptable, so the behaviour is unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Ship a Windows binary and a way to install it

The demo's own run command reached for a POSIX uv installer and
`.venv/bin/activate`, neither of which exists on Windows, so the nanochat
demo could not run there; both now branch at runtime. That changes the
experiment tree, hence the new EXPERIMENT_SHA.

Releases built no Windows artifact at all, which is what the CI job's
non-blocking rationale rested on. Add the target and a PowerShell installer
so a beta tester has something to install.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Tell Windows testers what they need before they need it

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Let Windows hold up a release now that a release ships it

The job skipped release runs because releases carried no Windows artifact.
They do now, so a regression on main would ship broken rather than fail
loudly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Say what signing will and will not fix

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Say Git for Windows is missing at startup, not at the first run

Without it there is no usable bash, so experiments and the demo die on
their first command with a spawn error naming a path the user has never
heard of. The agents check next to it already warns this way.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Make a second click reach the dashboard, not die on its port

Double-clicking orx.exe runs `orx up`. Doing it while orx is already
running failed the bind, printed an error into a console Explorer then
closed, and looked to the user like nothing happened at all. Open the
running dashboard instead, and hold the console open for whatever else
a startup failure has to say.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Make a failed start say so where a console cannot

The console Explorer opens for a double-clicked orx.exe dies with the
process, so both ways this program exits with something to say — a
returned error and a panic — reached a window that was already gone. Put
the message in a dialog instead, and only when no terminal is attached to
read the printed one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Start the dashboard when orx.exe is double-clicked

A bare `orx` prints usage and exits, which is right at a prompt and useless
from Explorer: the console it printed into closes with the process, so the
gesture looked like nothing happening. Owning the console is what tells the
two apart, and it now starts `up` — the same trade the macOS .app makes for
the same gesture.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Keep demo installs seeded before the Windows change working

Moving EXPERIMENT_SHA left every existing mac and linux install pointing
at a commit its repository does not contain. Each agent turn resolves the
session worktree from that commit, so all three demo chats would have
failed on their next message after upgrading. An install now starts its
sessions from whichever known experiment its demo branch descends from.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Address review round four

- Release verify job reads the Windows .zip and orx.exe; without it no
  release would have published for any platform.
- "Already running" reuse only for a double-click, and only of a
  dashboard speaking this build's protocol. On mac and linux a second
  `orx up` fails on the port again, as it did before this branch.
- An old demo install whose cache was wiped but origin kept restores its
  own experiment rather than building a sibling it cannot push.
- Same-file probe errors fall through to the old reconcile; the demo probe
  only puts ~/.local/bin on PATH after installing uv; the panic hook is
  Windows-only.
- Docs name the assets cargo-dist actually publishes.
- Comments cut to the one-or-two-line house rule; three doc comments moved
  back onto the functions they describe.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Address review round five

- A double-click keeps the flags it was given (`--no-telemetry` from a
  shortcut no longer turns telemetry back on).
- A kept demo origin is validated before a clone is restored from it, so a
  foreign or moved origin is named as the problem instead of the cache.
- The ssh opt-vector test reads the Windows known-hosts path once; it
  follows XDG_CONFIG_HOME, which telemetry tests mutate in parallel.
- Every comment this branch adds is now one or two lines.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Address review round six

- Remote storage paths accept an interior `.` again. `Path::components`
  skipped it on main, so `ORX_DATA_DIR=./orx` on a remote box — probed as
  `/home/u/./orx/orx.db` — broke `orx up --remote` on this branch.
- A foreign kept demo origin is rejected before anything is restored from
  it, now with a test; `install_repository` keeps main's shape, since the
  restore helper creates its own parent.
- Comments trimmed last round get back the why they lost, and the
  PYTHONIOENCODING one no longer claims the console codepage: redirected
  output uses the ANSI one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 18:26:35 -07:00
Daniel Kim 00fe5ab7b7 Add local endpoints to the OpenCode model picker (OR-245) (#300)
* Support local OpenCode models in onboarding

* Add local endpoint setup to OpenCode settings and model picker

* Handle recovered OpenCode context compaction without false failures

* Bump version to 0.1.122 for local model release
2026-09-10 01:44:03 -07:00
Daniel Kim 82aec9834b OR-267: Prefer uv through shared Python environment guidance (#291)
* Prefer uv through shared Python environment guidance

* Address Python guidance review feedback

* Finalize concise Python environment policy

* Keep Python policy in the shared prompt
2026-09-07 14:58:13 -07:00
Daniel Kim db0ca91d0c OR-263: Guide agents to organize project artifacts (#289) 2026-09-06 19:42:50 -07:00
Myles AndersonandClaude Opus 5 c5b49fe37f OR-247 feat: simplify the skills experience (#272)
* feat: simplify the skills experience

The Customize tab was five sections deep in choices that no longer earned
their place: a Global/This project scope picker on every card, a separate
"import from your agent" list, and two split skill lists.

- Skills installed in a coding agent are now mirrored automatically —
  read live from its skills dir on every listing and every session write,
  so a skill edited in Claude Code is the one the next session runs. The
  import step and its two endpoints are gone. A session hosted by the
  agent a skill came from is not handed a copy it already loads.
- Claude Code's installed plugins are mirrored too, discovered through
  installed_plugins.json so only real installs count.
- Every skill and LaTeX template is global. Project scope is removed
  from the store, the API, and the UI; anything already saved under it
  is migrated into the single store on first access.
- Skill frontmatter is parsed the way skills are actually written:
  folded and literal blocks, and values wrapped over indented lines. A
  skill whose frontmatter we cannot read is a skill the user never sees.
- ~/.agents/skills is read whenever it exists, rather than only when
  ~/.codex does.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: harden the skills mirror after review

Review of the mirroring change turned up defects worth fixing before it
ships:

- A plugin's skills are registered namespaced (`runpod:flash`), so the
  bare `/name` only resolves from a copy. They are no longer treated as
  natively loaded by the agent that installed them.
- Uploaded skills resolve through their folder name again, not the
  frontmatter `name` a hand-edit can change out from under them.
- The hosting agent's own skills are dropped only after they have won
  their `/name`, so a same-named skill from another agent can't take
  their place in the worktree while the dashboard shows the first.
- Session skill dirs are replaced and pruned only when the manifest says
  we wrote them, so a `.claude/skills` the project itself commits is
  left alone; manifest names are re-validated before any removal.
- A folder whose source is unchanged is not re-copied, folders over the
  upload budget are skipped, and neither the copy nor the size walk
  follows symlinks.
- The retired per-project store is emptied, never deleted: anything that
  can't move stays put as the user's only copy.
- A `#` comment no longer folds into the value above it, and a long
  description truncates instead of dropping the skill.
- The Customize tab distinguishes a failed skills fetch from an empty
  one, and ignores drops while an upload is in flight.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: keep one answer for what a skill name resolves to

Second review round:

- `source_dirs` resolves uploads through the same listing the dashboard
  reads, so a folder whose SKILL.md won't parse can't win a name in the
  worktree while the tab shows the mirrored skill it shadowed.
- A folder tally now carries a digest over every (path, size) pair, so a
  rename or a move inside a skill brings the session copy forward; each
  source and destination is walked once per turn instead of three times.
- A destination whose content already matches its source is adopted as
  ours, so a lost manifest heals instead of freezing that skill forever.
- A mirrored folder over the upload budget is left out of the listing
  too, rather than offering a `/name` that never reaches the worktree.
- LaTeX templates get the same skip-if-unchanged treatment.
- Archive junk under the retired per-project store no longer keeps it
  alive on every call.

The freshness test now watches the inode: `fs::copy` carries the mtime
across on macOS, so the timestamp it asserted on could never have failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: never let an adopted skill dir become prunable

Round three: adopting a destination whose content already matches its
source healed a lost manifest, but it also recorded a directory orx had
never written — so once the source went away, the prune deleted a skill
the project itself commits.

Adoption is now limited to the case it was for: no manifest at all. While
a manifest exists it stays the whole truth about what we own.

Also from that round: the upload budget moved into `source_dirs`, so the
menu, the hover preview and the session write agree on which `/name`
exists; a stray `.DS_Store` beside the retired per-project store no
longer keeps it alive; and the uncapped walks stopped pretending they can
fail.

The fingerprint test never reached the SKILL.md byte comparison it was
supposed to cover, and the migration test never asserted the retired tree
was gone — both fixed, and both verified by reverting the fix under them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* refactor: one budget answer for uploads too

The listing kept sizing uploads with an uncapped walk while the resolver
had started budgeting them, so a hand-edited store could list a skill no
session would be given.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 15:50:53 -07:00
Daniel Kim 01473da5d7 feat: capture richer CLI telemetry (#265) 2026-08-31 17:24:36 -07:00
Myles AndersonandClaude Opus 5 a9af660f9e OR-216 Add an orx-figures skill for publication-quality figures (#260)
* Add an orx-figures skill for publication-quality figures

Figures were left to matplotlib defaults: screen-sized, titled where a
caption belongs, and rasterized where the document wants vector. This adds
a bundled skill that routes by the question a figure answers and ships the
style layer rather than describing it.

`orx-figures` follows the `orx-compute` shape — one SKILL.md carrying the
non-negotiables, then exactly one of six references (curves, scaling,
comparison, pareto, matrix, diagram). Two assets install beside them:
`orx_figstyle.py`, vendored into the project so a figure stays reproducible
after the session ends, and a TikZ scaffold sharing its palette.

`save()` audits every figure and prints the result: printed width against
the known column widths, Type 42 font embedding, stray axes titles, missing
axis labels, sub-5pt text, overlapping text, and text running off the
canvas. Prose the agent can skip is how the first unusable figures shipped;
these checks run whether or not the guidance was read.

The playbook and the `orx skill` overview both direct figure work here — a
session with the skill installed still plotted raw matplotlib until they
did.

Content is calibrated against 39 popular recent alphaXiv papers (283
captions) and 10 arXiv e-print sources: 87% of real figure PDFs are built
wider than any single-column page, so the width check is the load-bearing
one; multi-panel is 27% of figures; and captions run a median of 28 words.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Fix review findings in the figures skill

Correctness:
- `label_ends` computed the reserved x-room in the pre-`set_xlim` scale, so
  the labels overhung the spine by need^2/(width+need) — about a quarter of a
  long label. Solve for the limit at which the original span occupies
  (axes width - label width) pixels instead.
- The audit's categorical-axis exemption keyed off whether tick text parsed as
  a number, but log formatters emit mathtext, so every log axis was classified
  categorical and escaped the missing-axis-label check. Decide by scale.
- `save()` audited before writing, and the audit needs an Agg-family renderer
  that is not guaranteed in the very case its first check reports. Write the
  files first; the audit can no longer be the reason a figure is lost.
- `curves.md` interpolated every seed onto the first seed's grid, and
  `np.interp` clamps, so a run that stopped early gained a fabricated flat
  tail. Clip to the range every seed reached.
- `comparison.md` hardcoded `ylim(0, 90)`: any score above it drew as a bar
  clipped at the axes edge, which is the dishonesty that reference is about.
- `scaling.md` zipped families against three markers, silently dropping a
  fourth family from the main panel while still plotting its residuals.
- Guards for degenerate input: zero-variance `diff_ci`, all-NaN heatmaps,
  empty confusion rows, resamples that fail to fit, empty label lists.

Consistency:
- `diagram.md` still taught the retired natural-size include, and its inline
  path dropped `\familydefault`, which would render a serif diagram beside
  sans plots. Both contradicted SKILL.md.
- `orx-paper` and `orx-reports` examples named a rasterized plot.
- The audit emits a font-fallback finding the docs never listed, and it is the
  one finding an agent should hand over rather than fix.

Tests now assert the rendered playbook rather than the raw template, and pin
the audit's problem strings rather than text that also appears in docstrings.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Fix round-two review findings

The round-one TikZ fix was worse than the bug: it told the agent to move
`\renewcommand{\familydefault}{\sfdefault}` into the paper's preamble, which
is document-wide and would have set the body text, headings, and captions of
a submission to sans. The face now rides in the `orx`, `edgelbl`, and
`stagelbl` styles, so it travels with the picture and touches nothing else.

Also from review:
- `diagram.md` still carried the broken relative `cp assets/...`, which does
  not resolve from a session's cwd.
- The new "write a report figure to the artifacts directory" note contradicted
  "keep the generating script beside its output"; both destinations now keep
  the pair together.
- A `WIDE` figure only gets a 6.75in `\linewidth` inside `figure*`; in a plain
  `figure` it silently halves, which non-negotiable #1 now says.
- Skipping all text on an axis-off axes also exempted the user's own content,
  which on such an axes is the figure. Exclude only the undrawn axis artists.
- `save()` leaked the figure when the audit raised, and did not create the
  stem's parent directory.
- A single seed drew a zero-height band that reads as certainty; `mean_ci` and
  `diff_ci` now warn, matching what the references demand of the caption.
- Guards for disjoint seed ranges and all-zero scores.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Close the round-three review findings

None were blockers, but three were real defects on secondary paths:

- The inline `\input` route omitted `\usepackage{tikz}` and the
  `\usetikzlibrary` line, so a paper following it literally would not compile:
  `trapezium`, `Stealth`, `fit=`, and `on background layer` each need one.
- Vendoring was hardcoded to `figs/`, so a report script written under the
  artifacts directory could not import the style module beside it.
- Removing `\familydefault` left no documented way back to a serif diagram
  while `use_style(family="serif")` still exists for plots.

The colorbar guard now accepts `ax.get_label() == "<colorbar>"` as well as the
private `_colorbar`; verified against matplotlib 3.11 that both hold today, so
a rename can no longer make `clean` unreachable for every heatmap.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 18:19:47 -07:00
Daniel Kim 2d54154b33 Isolate native agent chat stores (#248)
* Isolate native agent chat stores

* Fix legacy Codex rollout fallback

* Simplify native chat store isolation

* Handle missing native sessions without regexes

* Harden isolated native chat sessions
2026-08-25 16:02:02 -07:00
Daniel Kim 1304986749 Fix trusted run supervision and post-cancel Codex turns (#243)
* fix: launch run supervisors through orx up

* fix: restart Codex after interrupted turns
2026-08-24 02:19:02 -07:00
Daniel Kim 8996b8297b OR-184: Polish chat styling and skill workflows (#240)
* OR-184 Align composer controls and inline chips

* OR-184 Refine composer and panel styling

* OR-184 Polish chat styling and skill workflows
2026-08-24 00:20:32 -07:00
Daniel Kim 8b6b6d9b84 OR-220 Remove generated PROJECT.md (#238) 2026-08-21 19:50:21 -07:00
Myles AndersonandClaude Opus 5 0bfeb1ebbe Find the LaTeX toolchain on the user's PATH, and recommend Tectonic (#227)
* Give shell_env the one PATH search every lookup uses

`find_on_path` sat in `harness::detect` as `pub(super)`, so anything outside
the harness registry that needed a shell-PATH lookup wrote the loop again —
`find_opencode` and `resolves_on_path` both had a copy, the former commented
"Mirrors `find_on_path`". Move it to `shell_env`, which already owns
`search_path()` and whose module doc is about which PATH orx searches, and
point all four call sites at it.

The shared lookup now also drops *relative* PATH entries, not just empty ones.
A relative entry names no fixed directory: it resolved against orx's own cwd,
which for a Finder-launched bundle is never where a user's tool lives, and the
child that later ran it would resolve the same name somewhere else again.

`other_orx_on_path` in install_cli.rs keeps its own loop — it needs every
candidate and canonicalizes them, so it is a different search.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Find the LaTeX toolchain on the user's PATH, not launchd's

A `.tex` file opened in the macOS app reported "No LaTeX toolchain found on
PATH" on a machine with tectonic installed. The bundle is started by launchd
with `PATH=/usr/bin:/bin:/usr/sbin:/sbin`, where no TeX lives, and `latex.rs`
was the one lookup still resolving against the process environment — the
harnesses had gone through `shell_env`'s probed shell PATH for exactly this
reason. Verified against a bundle launched with launchd's environment:
`/api/latex/engine` returns `tectonic` where the installed build returns null.

Finding the tool is not enough on its own: latexmk finds the engine, biber and
bibtex on PATH itself, so the child is handed the same PATH we found it on.
The tool is spawned by its absolute path but symlinks are left unresolved —
TeX picks its format file from the name it was invoked as, so canonicalizing
`pdflatex` to `pdftex` would silently run plain TeX.

`on_path` no longer spawns `<binary> --version` to decide. It costs a probe
that a present-but-unrunnable binary would have caught, and buys back five
subprocesses on every `.tex` tab open.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Point a user with no LaTeX at Tectonic rather than MacTeX

The no-engine hint led with MacTeX and offered `brew install --cask mactex` as
the command to copy — a multi-gigabyte install for someone who just wants to
see their paper. Tectonic is one self-contained binary that fetches each
document's packages, so it is the install a user can actually finish, and it
is what the copyable command now installs.

The distribution stays named second, because Tectonic runs only XeTeX. The
hint says so: it is the last screen that can, since the hint disappears the
moment an engine exists and the amber substitution note only fires for a
document that names its engine explicitly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 14:28:22 -07:00
Myles AndersonandClaude Opus 5 51e510f1a2 Compile LaTeX papers to PDF in the file viewer (#225)
A `.tex` file in the live checkout now compiles on open and shows the real
PDF, with a toggle to a live source editor that recompiles on save.

Compilation targets Overleaf's behaviour: `latexmk` drives whichever engine
the document asks for via `% !TeX program`, running biber or bibtex and
repeating passes until references settle. Without latexmk the engine is driven
directly and that orchestration is done here; with neither, `tectonic` stands
in and says so, since it is XeTeX and cannot honour a LuaLaTeX document.

Users can upload their own templates — a conference class, a house style — as
a `.tex` or a `.zip` carrying its `.cls`/`.sty`/`.bst`. Templates are global or
scoped to one project, and are written into each session worktree so the agent
can start a paper from one. Uploading and managing them lives in the renamed
Customize tab.

The `orx-paper` skill and the `/write-paper` command teach the agent to write
papers as `.tex` in the working tree, follow an uploaded template when one
exists, and ground every reported number in a run rather than in memory.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 11:35:04 -07:00
Daniel Kim e6c1c6813d OR-196 Slim agent prompt and modularize skills (#224) 2026-08-20 21:47:35 -07:00
Myles AndersonandClaude Opus 5 a0c30df38a OR-129 Let an agent delegate a task to a second agent session (#221)
* Let an agent delegate a task to a second agent session

An agent could only do work itself or queue it behind the current turn.
`orx agent spawn "<task>"` now starts a helper in its own top-level
session — visible in Recents, its own transcript, its own worktree — and
resumes this chat with the helper's closing reply when it finishes.

The CLI only writes rows: the child's `chat_sessions` row (carrying a new
`parent_session_id`) and a `chat_spawns` record. The resident `orx up`
watcher starts the child's first turn and later reports back, the same
store-and-watcher split as `orx exp wake` and for the same reason — the
spawning `orx` is a short-lived subprocess with no harness to run a turn
on. It is a CLI verb rather than an MCP tool because the mcp-gate bridge
is Claude-only, while all three harnesses already shell out to `orx` with
`ORX_CHAT_SESSION_ID` exported.

Spawn rows walk pending → starting → running → notifying → done. The two
claimed states carry a token so a watcher that dies mid-step leaves work
reclaimable rather than lost, and a helper whose parent was deleted
retires quietly instead of stranding the row.

Spawned agents cannot spawn their own. A helper that can delegate turns
one request into an unbounded tree of paid sessions, and nothing
downstream bounds it.

Settings carry over only when parent and child share a harness: a model
or permission-mode id from one CLI means nothing to another. Plan never
carries over — a helper is spawned to do the task, not to plan it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Harden spawned agents against losing the delegated task

Review of the spawn feature found several ways a delegated task could be
accepted and then quietly not happen.

A helper's completion was read from `ChatHost::is_busy`, a map this
process owns. A second `orx up` — or the same one after a restart — sees
an empty map, so a helper working under it was reported "finished" within
3s and the delegation was abandoned. The gate now consults the durable
turn lease, and `finish_turn` stamps the spawn row, so an idle helper
with no stamp is reported as interrupted rather than as a false
completion carrying half-written text.

A failed start returned the row to Pending and retried every 3s forever.
The brief is persisted before `send_message_showing` can report the turn
didn't start, so each retry appended it again. Retries now skip the
already-recorded brief, stop after three attempts, and wake the parent
with "could not be started" — the CLI had promised it a wake-up.

Claude activates Plan through its permission mode, not the plan axis, so
clearing `plan_mode` still handed a planning parent's helper a mode that
only ever produces a plan. The plan permission id is now dropped when
inheriting.

The closing reply was read from the last assistant message. An answered
prompt card rides its own text-less message and becomes the branch tip,
so an opencode helper that hit one card reported "no written reply" and
its actual answer was lost. A turn that died on a harness error reported
the same thing, reading as "nothing to say" rather than "it failed". The
report now walks back to the newest message with text and distinguishes
a reply, a failure, and genuine silence.

Depth was capped at one level; breadth was not, so a looping parent could
create as many paid sessions and worktrees as it liked. Five in flight.

Also: the transcript's squash key ignored the spawned ids, collapsing two
adjacent spawns into one row and stranding the second helper; following a
spawn card set the one session filter that hides archived rows; the
delegation playbook told agents to hand helpers a node this session owns,
which cardinal rule 1 and one-branch-one-owner both forbid, and never
told them to bound a helper's compute.

The playbook placeholder test checked a hardcoded token list, so it could
not catch a newly added token — it now scans the rendered prompt, and
fails on the `{max_spawns}` this change introduced if its substitution is
removed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Never retire a delegation before its report is delivered

Round-two review found four more ways a spawned task could be lost.

The give-up notification was fire-and-forget: it returned silently when
the parent's turn slot was taken, then retired the row anyway. The parent
is busy in exactly the expected case — it spawned from inside a turn, and
the third failed attempt lands ~9s later — so the promised "could not be
started" usually never arrived. Both wake-ups now go through one
`deliver_wake_up`, which retires the row only once the message actually
started a turn and otherwise leaves it for the next tick.

`--no-wake` settled to Done the instant the turn started, and the
in-flight cap counts only rows that are not Done. A looping parent could
spawn five, wait one tick, and spawn five more forever. Fire-and-forget
rows now stay Running until the helper is genuinely idle, so "in flight"
means the same thing for both kinds.

`interrupted` was read from the listing snapshot while the liveness check
was live. A helper finishing cleanly while an earlier iteration awaited a
whole turn was reported as interrupted and its reply thrown away — the
same loss the stamp was added to prevent, inverted. It is re-read after
the liveness check.

A user pressing Stop reaches the same `finish_turn`, so a manually
stopped helper was stamped finished and its half-written text handed back
as a closing reply. `spawn_outcome` now recognizes the interrupt marker.

The wake-up also claimed the helper's commits were "on its own branch".
Session worktrees start detached, so a helper only has a branch if it ran
`orx create-experiment`, and for the delegations the playbook recommends
there are usually no commits at all. Restored the accurate wording, in
the report and the playbook, and the clause is omitted entirely when the
worktree isn't there.

Also: a reclaimed `starting` claim now counts as an attempt, so a watcher
that dies inside the send cannot retry forever; `ChatSpawn` no longer
carries a `state` no reader used, and the INSERT hardcodes `pending`;
branch-local dev slots get ALTERs for the renamed columns.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 10:48:41 -07:00
Daniel Kim 505b272317 refactor: keep research state local (#202) 2026-08-17 16:28:46 -07:00
Daniel Kim e9d322e547 Add lightweight project briefs and refresh onboarding (#200)
* feat: add lightweight project briefs and onboarding refresh

* fix: refine onboarding and project workspace interactions

* fix: harden demo onboarding state

* fix: satisfy current clippy lint

* chore: bump version to 0.1.105
2026-08-17 12:46:35 -07:00
Daniel Kim fab62eae90 refactor: retire server-managed experiment runs (#199)
* refactor: retire server-managed experiment runs

* refactor: unify bundled compute skill

* refactor: retire server project creation

* docs: make local workflow canonical

* chore: release 0.1.104
2026-08-17 00:45:14 -07:00
Myles AndersonandClaude Opus 5 46d359ff5e OR-173 Make the macOS app find harnesses, and stop it fighting a CLI install (#195)
* fix(app): detect harnesses in the macOS app by adopting the shell PATH

Finder launches OpenResearch.app through launchd, so the bundle starts with
PATH=/usr/bin:/bin:/usr/sbin:/sbin and no shell rc ever sourced. Harness
detection resolves `claude`/`codex`/`opencode` off that PATH, so a DMG user
with a Homebrew, nvm, or npm-global install saw every harness missing --
including an already-signed-in Claude Code session -- while a terminal
`orx up` on the same machine detected them all.

App mode now probes the user's shell once at startup and installs the result
via `local::search_path`, which harness lookup and harness children consult
instead of the process PATH. The probe is `$SHELL -ilc`: interactive, because
zsh reads .zshrc only for interactive shells and that is where PATH edits
live -- a login-only probe missed the ~/.local/bin holding `claude`. It runs
an inner `sh -c` so fish reports a colon-separated PATH rather than its own
list-valued $PATH, fences the value between per-probe nonce markers so rc-file
chatter cannot forge one, and requires an absolute directory in the result.
Best-effort throughout: a 5s cap, and every outcome logged.

The five detection probes that spawned a harness binary with no env prep
(`bin_version`, `claude_accepts_ultracode`, `claude_list_models`,
`codex_model_list`, `run_models`) now go through `prepare_env` like
`probe_auth` already did. Without that, a node-shebang install resolves via
the hydrated PATH but its `--version` fails for want of `node`, and
`gate_oauth_version` turns the missing version into `Unknown` -- telling a
signed-in user to sign in.

Verified end to end: a bundle run under launchd's PATH with an isolated
ORX_DATA_DIR reports Claude Code agentReady, and finds a `codex` reachable
only through the hydrated PATH -- codex has no fallback drop-location, so
that resolution can only have come from the override.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(app): stop the bundle and a CLI install from conflicting

Three ways the DMG app and a curl-installed `orx` stepped on each other.

`orx delete` takes an exclusive lifecycle lock and refuses when another
OpenResearch process holds it -- "Close `orx up`, `orx serve`, and any active
runs before trying again." CLI `orx up` holds the matching read lock via
`dispatch`, but app mode returns from `main` before `dispatch` runs, so it held
nothing: `orx delete` from a CLI install would wipe the store out from under a
live app. App mode now takes the same read lock for the life of the process.

Agents shell out to `orx`, and `chat::prepare_env` prepends the running
executable's directory so they get THIS build. In the bundle that directory
held only `OpenResearch`, so `orx` fell through to whatever CLI the user had --
a different version, or nothing at all for a DMG-only user. The bundle now
ships `Contents/MacOS/orx` as a symlink to the executable. Verified the symlink
survives `codesign --options runtime` + `--verify --strict`.

That alias would otherwise open a second dashboard, since a bare `orx` reaches
app mode's no-arguments condition. `launched_as_app_bundle` now also requires
that the process was invoked under the bundle executable's own name, which
keeps the alias a plain CLI while leaving a direct run of the executable path
-- the documented way to watch the app's logs -- in GUI mode. macOS
`current_exe` reports the launch path symlink and all, so it is canonicalized
before the comparison.

Verified safe already, and left alone: the app binds an ephemeral port rather
than 4791; the store is WAL with a 5s busy timeout; and run supervisors hold a
per-run exclusive lock, so a second server recovering the same active run exits
instead of double-driving it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(app): address review of the PATH and coexistence work

Blocker: `launched_as_app_bundle` had grown real logic -- four distinct
outcomes turning on a subtle platform fact -- with no coverage, and being
macOS-gated it could not get any, since CI runs ubuntu only. The decision is
now the pure `is_bundle_exe_launch(exe, argv0)`, compiled everywhere and
table-tested for all four cases; only the process-state read stays gated. Same
seam `extract_path` uses.

The lifecycle lock moves ahead of the PATH probe. Taking it afterwards left the
store unprotected for however long the probe ran -- up to 5s -- which is exactly
when an `orx delete` racing app startup would land. A failed `read()` is now
logged too; it was the one outcome in this module that could pass silently, and
silently is how the app would then run unlocked.

`orx delete` blocks on a running app now, so its message says so instead of
naming three things the user isn't running.

Probe diagnostics split the timeout from the spawn failure and name the shell,
since "which shell, and did it hang or fail to start" is the whole question a
report needs to answer.

`bin_version` gets the `NO_COLOR` guard its siblings already carry: it feeds
`parse_version`, an escape-laden version line parses as `None`, and
`gate_oauth_version` turns that into `Unknown` -- reproducing the bug this
branch exists to fix, via the synced env the probe now inherits.

`find_opencode` skips empty PATH components, matching `find_on_path`.

Packaging asserts the `orx` symlink survived staging. Every step preserves it
today, but a dereferencing copy would fail silently -- the app builds, signs,
notarizes and runs, and agents just quietly fall back to the user's own CLI.

Docs: corrected the same overstatement in `search_path`'s module doc that
DISTRIBUTION.md already fixed, and recorded the known gap that the probe
imports only PATH, so `ORX_DATA_DIR`/`XDG_*` from a shell rc reach the CLI but
not a Finder-launched app.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(app): import the shell's data and config dirs, not just PATH

`ORX_DATA_DIR`, `XDG_DATA_HOME`, and `XDG_CONFIG_HOME` exported from a shell rc
reached the CLI but never a Finder-launched app, so the two resolved different
directories: the dashboard opened the default database while `orx` on the same
machine used the user's, and the lifecycle lock added last commit guarded a file
neither of them shared. Same root cause as the PATH bug, one layer down.

`search_path` generalizes to `shell_env`: an allowlist (`IMPORTED`) the startup
probe reads, NUL-separated so a directory can contain anything but NUL, behind
`var()` -- process environment when no override is installed, so nothing changes
for a terminal `orx`. `store::env_path` and `config::config_dir` are the only
two places that resolve these, and both now go through it. The allowlist stays
short on purpose: these are the variables whose divergence makes the app and the
CLI act like separate installs, while credentials already reach harness children
via `prepare_env`.

This moves the lifecycle lock back after the probe, undoing last commit's
reordering. The reason is now structural rather than incidental: the lock path
is derived from `config_dir()`, so taking it before the probe would lock the
default path while the CLI locks the user's -- protecting nothing. A correct
lock a few hundred milliseconds late beats an immediate lock on the wrong file.

Verified with the app's process environment stripped of both variables while the
probe shell exported them: `orx.db` lands in the shell's data dir and
`orx.lifecycle.lock` in the shell's config dir, and the HOME-derived defaults
are never created.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(app): carry the imported environment into orx child processes

Importing the shell's directories fixed the app's own resolution but not its
children's, which made things worse rather than better for anyone exporting
`ORX_DATA_DIR`. `spawn_detached_supervise` and the harness adapters spawn plain
CLI processes; those never probe, so they re-resolved to the default store while
the app read the user's. A run launched from the app would have been supervised
against a different database than the dashboard was showing — before this
branch both landed on the default and at least agreed.

`shell_env::export_to` hands the imported variables to a child. `prepare_env`
applies it, so an agent's `orx exp run` shares the dashboard's store, and
`spawn_detached_supervise` applies it plus PATH, which also reaches the run
payload it launches (`localbox` spawns `bash run.sh` with an inherited env, so
it was losing the user's PATH entirely — no `python`, no `uv`, no `conda`).

`opencode::spawn_agent` had hand-rolled a copy of `prepare_env`; it now calls
it rather than growing a third copy of the same fix.

Verified with the app's process environment stripped of both variables while the
probe shell exported them: a harness child is handed ORX_DATA_DIR and
XDG_CONFIG_HOME, and an `orx` run with that environment creates no default
store.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(cli): print a runnable command in the hints the bundle breaks

A DMG user has no `orx` anywhere on PATH, so every hint telling them to run
`orx exp wait …` names a command their shell cannot find. Agents are already
covered -- `prepare_env` puts the bundle's `orx` first on their PATH -- but a
human driving the bundle's CLI is not.

`invocation::orx()` resolves the spelling once: plain `orx` when it is on PATH,
which is every CLI install and every agent, otherwise this binary's own path,
shell-quoted when it needs it. Nothing changes for anyone who installed the CLI.

Applied to the three hints the report named, each of which had been copied per
backend: the post-launch "Follow it with …" line (nine copies), the missing
run-command error (eight), and the outdated-version warning. Both hint bodies
move into `invocation`, so the eight backends now share one line each -- a net
deletion, and one place to fix the next time the wording moves.

`warning_for` takes the spelling as an argument rather than reading process
state, keeping its tests deterministic.

Not covered: the long tail of `orx login`-style hints in error strings, which
still print a literal `orx`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(app): close the environment gaps review found

A dashboard-synced `ORX_DATA_DIR`/`XDG_*` silently beat the shell-imported one
for harness children: `prepare_env` exported the imported vars, then the synced
loop overwrote any whose key was absent from the *process* env -- which in app
mode is exactly these. The app read one store while claude and opencode wrote
another. The guard now consults `shell_env::var`, so the imported value counts
as present. codex escaped this only because it re-pins `ORX_DATA_DIR` after
`prepare_env`; claude and opencode have no such pin.

Local run payloads were never actually fixed. The previous commit claimed the
supervisor passed its environment to `bash run.sh`, but the supervisor does not
spawn it -- `localbox::run_job` does, in the launching process, from
`LocalJobSpec::env`. A run started from the app still executed the user's script
under launchd's PATH, with no python, uv, or conda. The env now carries the
imported PATH and directories, and the supervisor comment drops the wrong claim.

Three more readers of these same variables were still going to the process env,
and two of them made harness detection itself diverge between app and terminal:
`harness::xdg_config_home` (whose doc claims to mirror `config::config_dir`) and
`opencode_auth_path`, which would report opencode signed out in the app while
the CLI saw it signed in. `invocation::resolves_on_path` was the third -- it
decides whether a hint can say plain `orx`, and the reader pastes that into the
shell whose PATH we imported.

Also: `orx login` -- the first hint a fresh DMG user meets -- now resolves like
the rest; `export_to` iterates in `IMPORTED` order so the startup log is
diffable; and the doc drift is repaired, including the octal-escape invariant
that makes the nonce's leading underscore load-bearing, the reopened
`orx delete` window, and the scope note listing what still inherits the process
environment (`git`, `gh`, `kubectl`, `ssh`, `publish-branch`).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(app): allow dead_code for the launch check off macOS

`is_bundle_exe_launch` is deliberately un-gated so its tests run on CI's Linux
runner, but its only non-test caller is `#[cfg(target_os = "macos")]`, so the
Linux bin target sees it as dead. `mod local` carries a blanket
`#[allow(dead_code)]`, which is why `shell_env::parse_probe` -- same shape --
did not trip; `mod commands` does not.

Verified by rebuilding with every `target_os = "macos"` in `src/` swapped to
`"linux"`, which reproduces exactly what the runner cfgs out. Clean, and no
other item on this branch is dead off macOS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 17:27:10 -07:00
Daniel Kim c43180cf68 OR-161: make experiment wake-ups opt-in (#192)
* OR-161: make experiment wake-ups opt-in

* Bump version to 0.1.101
2026-08-13 19:59:33 -07:00
Daniel Kim 4cffa77922 OR-171: Make file references clickable (#191)
* OR-171: require clickable file references

* OR-171: restore evidence citation guidance
2026-08-13 19:32:47 -07:00
Daniel Kim 82ea072291 OR-158 Decouple compute runs from GitHub
Ships direct source snapshots across compute backends, optional GitHub experiment publication, and CLI v0.1.98.
2026-08-11 13:10:24 -07:00
Myles AndersonandClaude Opus 4.8 4738715e94 OR-134 Add Skills tab: upload, import, and /-invoke agent skills (#175)
* Add Skills tab: upload, /-invoke, and import agent skills

Adds a left-rail Skills section where users manage custom agent skills:
- Upload a SKILL.md or a .zip of a skill folder (Global or per-project).
- Import skills already installed in the user's coding agents.
- Uploaded skills are written into each session worktree beside the
  built-in orx-* skills and surfaced in the composer's / menu.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Rebuild ui/dist for the Skills tab

Regenerated committed assets (`pnpm build`) after the Skills tab UI and
the merge with main's Tailwind migration, per AGENTS.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Address code review on the Skills tab

- Prune deleted/renamed user skills from session worktrees via a manifest,
  and replace (not merge) each skill dir so shadows and re-uploads are clean.
- Bound zip entry reads to the extract budget (deflate-bomb guard).
- Accept a quoted SKILL.md `name:`; import keys strictly off the frontmatter
  name so folder==name always holds.
- Dedupe the composer `/` menu (project shadows global); drop dead helpers.
- Surface delete errors, guard re-entrant uploads, a11y + timeAgo tweaks.
- Add tests for prune, clean shadow, zip caps, and quoted names.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Rebuild ui/dist after review fixes

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-10 18:27:51 -07:00
Daniel Kim 7dc5155084 OR-148: Make agent guidance UI-agnostic (#160)
* Fix stale UI guidance in agent prompts

* Clarify local compute default guidance
2026-08-06 16:58:46 -07:00
Daniel Kim 6e4f998597 OR-131: make local file projects the default (#150)
* feat: make local projects the default

* fix: complete local-first project workflow
2026-08-04 18:15:32 -07:00
Daniel Kim cec874e47c Simplify project files as artifacts (#145) 2026-07-31 16:51:56 -07:00
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>
2026-07-30 20:43:33 -07:00
Daniel Kim 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.75
2026-07-22 18:44:33 -07:00
Daniel Kim 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
2026-07-22 12:15:21 -07:00
Daniel Kim 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
2026-07-17 19:53:25 -07:00
Daniel Kim f633b80f9c Native modular agent skills: per-session worktree install + orx skill modules (v0.1.62) (#87)
* Native modular agent skills: per-session worktree install + orx skill modules (v0.1.62)

- Factor the 66KB SKILL.md into 10 embedded modules under agent-skills/
- Write the Local set as native SKILL.md skills into each orx up session
  worktree (harness-specific dir), fresh every turn beside the playbook
- Slim the playbook: compute/k8s/report detail moves to skills, generated
  skills index added; cardinal rules and command surface stay inline
- orx skill <name> serves bundled modules (API fallback unchanged);
  no-arg prints the slim root + module index
- orx install-skills --full writes the Full set globally (cloud box);
  Codex shim migrates to ~/.agents/skills (native, model-discoverable),
  legacy prompt kept

* agent-skills: store modules as literal SKILL.md files, installed verbatim

Each module is now a complete, GitHub-readable skill at
agent-skills/<name>/SKILL.md (frontmatter included); local-mode body
variants live as SKILL.local.md siblings. The render() step is gone —
files are embedded and installed byte-for-byte, and the frontmatter
drift test now validates the files against the code's name/description.

* Revert incidental ui/dist rebuild (no UI source changes in this branch)

* Drop leftover rebuilt dist assets — ui/dist now matches origin/main exactly

* agent-skills: trigger-rich descriptions with explicit liberal 'Use when' cues

Descriptions are the model's trigger surface; err toward false-positive
loads. Bound raised 150 -> 400 chars (Codex ambient budget is ~8k across
the set).

* Playbook: dedupe against skills + extract to literal SYSTEM_PROMPT.md

The system prompt is now a GitHub-readable template at repo root; playbook_md
strips the leading doc comment and substitutes {tokens} (project facts,
compute default, skills index). Content duplicated by the installed skills is
trimmed to pointers: the five per-backend exp-run table rows collapse to one,
the loop keeps only its skeleton, and the sizing/analysis sections fold into
one-liners. The prompt now explicitly tells the agent to load the relevant
skill before acting in its area (long-horizon staleness guard). Placeholder
test extended to all ten tokens + comment-stripping.

* orx-compute skill: add missing slurm + openresearch backend sections

The playbook trim removed the slurm/openresearch table rows, but orx-compute's
body (extracted from the old SKILL.md) never had sections for them — the
detail existed only in the playbook. Content moved from the pre-PR playbook
rows in the same per-backend section style. Also restore the shell-tool
time-limit rationale on the wait loop step in SYSTEM_PROMPT.md.
2026-07-17 17:47:24 -07:00
Daniel Kim 7450249cec Compute tab redesign: unified target list + default compute target (#86)
* Settings: persist default compute target (backend + flavor) with pure resolver

* Launch paths: resolve the default compute target; widen HTTP run to all 7 backends

* API: compute-targets summary, default-target POST, local hardware + openresearch status endpoints

* Agent guidance: reveal the default compute target in the playbook, SKILL.md, and skill templates

* Compute tab: unified target accordion with This-machine + OpenResearch rows and default picker

* cargo fmt

* Polish: summary casing, a11y-clean row controls, instances subtitle

* Review round 1: coherent no-flavor default prompt, 500 on settings io errors, UI dedup + summary refresh, doc fixes

* Clean-round nits: refetch race guard, aligned token-source labels, flavor-required const, doc accuracy

* Quiet the agent-disclosure footnote: muted text instead of amber callout

* Info icon on the agent-disclosure footnote
2026-07-16 23:38:04 -07:00
Daniel Kim 7d1aed6f7d Plan mode: synthesized plan card fallback + accept-edits option (#81)
* Plan mode: synthesize a plan card when ExitPlanMode never fires + accept-edits option

Observed failure: a plan-mode session ended with the plan as plain chat
text and no ExitPlanMode call — and since only a plan-card answer
switches the resume mode, the user's chat 'yes' resumed still in plan
mode, stranding the session read-only. Root cause of the missing call:
the shared playbook still said 'Never call an interactive
question/elicitation tool — it will hang', guidance that predates the
prompt-card work in #37/#40.

- run_turn tracks (plan_mode, saw_prompt, turn_errored, last_text) and
  synthesizes a WirePrompt{kind:plan, synthesized:true} from the final
  text when a plan-mode turn ends card-less and error-free; the pure
  condition fn is unit-tested
- WirePrompt/ChatPrompt gain ; the UI softens the header to
  'Plan mode — ready to proceed?' for synthesized cards
- playbook: interactive tools surface as cards now; in plan mode, always
  present the finished plan via ExitPlanMode
- plan card gains 'Approve & accept edits' (resumeMode accept-edits —
  synthesize_resume already honors it)

* chore: drop stray npm lockfile (ui uses pnpm)
2026-07-16 11:05:40 -07:00