Repository navigation
chore(*): catch the ui-web rebuild up to main - #529
Conversation
…ads (#463) ## Summary `main` is red on three jobs (`python lint`, `unit 2/4`, `unit 3/4`), and has been since #451 merged. All of it is fallout from one arrival: `image_search` came with a second switch beside its credential. `tools.web.search.images` decides whether the tool is registered at all (`raven/agent/loop/wiring.py`), so a lane that never places a picture keeps the tool face it had. The capability table was not told. `is_configured` answers on the vendor key alone, so with a Serper key and the switch left off the table ticked a capability the agent does not hold, and the two tests that drive a real `AgentLoop` caught exactly the disagreement that module exists to prevent. The switch belongs on `is_disabled`, not `is_configured`. That function's own docstring already names the reason: a switched-off tool usually has its credential set, and calling it unconfigured sends the deployer to set a key that is already there. `tools.disabledTools` and this flag are the same kind of answer, so `is_offered` needs no change and the doctor row reads "off" rather than "missing a key". The test configs that mean "everything supplied" now turn the switch on, the way `raven/core/runtime.py` passes it from the config, and a new case pins the combination that was wrong: key present, pictures off, nothing offered, table saying off. Two more pieces of the same arrival: - `ImageSearchTool` copied `WebSearchTool`'s key resolution without its two attribute annotations, so the untyped lambda `wiring.py` hands it left the `api_key` property returning `~AlwaysFalsy | str` against a declared `str`. The type checker pinned in #416 reports it; the annotations are the fix. - `WebSearchConfig` gained the `images` field, so the writer's validated subtree now carries it and two `test_config_update_tools` expectations that pin the written section by value went stale. Not changed here: `get_web_search` still reports `{provider, api_key, max_results}` and does not surface `images`, though `set_web_search` can write it. That is a gap in the read side of the config API, not a CI failure, and it wants its own change. ## Type - [x] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification Run on Windows 11 in a clean worktree cut from `main` (`5a29ea22`): - `uv run pytest tests/test_tool_capabilities.py tests/test_config_update_tools.py -q`: 71 passed. All four tests failing on `main` now pass, plus the new case. - `uv run --frozen --python 3.12 --all-extras ty check raven evolver agents plugins-dist scripts`: the `web.py:516` diagnostic is gone. The diagnostics that remain locally are unresolved imports for optional packages absent from this machine's environment (`boxlite`, `nio`); CI reported exactly one diagnostic before this change, the one fixed here. - `uv run --frozen --python 3.12 --extra dev ruff check raven tests`: all checks passed. - `ruff format --check` on the four changed files: already formatted. - `uv run pytest tests/test_cli_doctor_commands.py tests/test_rpc_console.py -q`, which consume `is_disabled`: the failing-test set is byte-identical before and after this change (8 pre-existing Windows-only failures around file modes and an absent browser package, none of them in the set CI reports). ## Risk `is_disabled` gains a second reason to answer true, so a deployment with a search key and `tools.web.search.images` off now reads as "switched off" in `raven doctor` and the console capability report, where it previously read as offered. That is the state the loop was already in; only the description changes. Nothing about registration, gating, or the tool's runtime behaviour moves. The other two changes are an annotation and two test expectations, with no runtime effect. Rollback is reverting this commit; it touches nothing that other work builds on. ## Related Issues N/A --------- Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
… busy runner (#466) ## Summary Since the idle ceiling became a gate (#428), a unit shard has failed on `main` and on most pull requests with every test passing: the ceiling reads wall clock minus CPU and calls the difference waiting, and a thread that is runnable and not on a CPU spends it the same way. With four workers, the controller and coverage on a four-vCPU runner that queueing reached three to four seconds inside one long test, so the shard died on whichever test was executing when contention peaked, a different one each run. Warming the catalogue (#452) did not stop it. #449 halves the workers per shard, which pays for the same measurement with every shard's wall clock. This changes the measurement instead. Idle now subtracts the time the test's threads spent queued for a CPU, read from the scheduler's own account in `/proc/self/task/<tid>/schedstat`: - every thread of the process is read, not only the one running the test, because the rpc model handlers do their catalogue reads on `asyncio.to_thread`; - a thread writes its own last reading on the way out, through a wrapper on `Thread._bootstrap_inner`, because the per-test event loop is closed at the end of the test and takes its executor threads with it, and `/proc` forgets a thread the moment it ends; - CPU stays on `os.times`, which keeps the CPU of ended threads and of reaped children; the scheduler's per-thread run time would have been lost the same way the queue time was. A sleeping thread is not on a run queue, so a test that waits on purpose is still charged the whole wait. Without `schedstat` (macOS, Windows) the arithmetic is the old one; the strict gate only runs on Linux, and a Linux-only test fails loudly if the runner's kernel stops keeping the statistics rather than letting the gate degrade quietly. `-n 4`, the 3 s ceiling and the four shards are unchanged. If this lands, #449 is not needed. ## Type - [ ] Fix - [ ] Feature - [ ] Docs - [x] CI / tooling - [ ] Refactor - [ ] Other ## Verification Unit tests for the clocks (12 on macOS, 13 on Linux, the last one reads the live kernel): ``` uv run --frozen --all-extras pytest -q tests/test_conftest_idle_ceiling.py macOS: 12 passed, 1 skipped Linux container: 13 passed ``` Full suite on macOS, where the fallback arithmetic runs: ``` uv run --frozen --all-extras pytest -q -p no:cacheprovider 23137 passed, 109 skipped in 291.30s ``` Contrast runs in a Linux container (Docker, linux/arm64, kernel 6.12), the checkout and venv on container-local storage, `main` at 2bdc4bd against this branch. Four workers pinned to two CPUs stands in for the four-vCPU runner; the ceiling is lowered to 0.05 s so the effect shows on one file: ``` docker run --cpuset-cpus=0-1 ... pytest tests/test_rpc_model.py --idle-ceiling 0.05 -n 4 main: 70 unmarked tests over the ceiling, worst 1.67s idle of 2.89s branch: 2 unmarked tests over the ceiling, worst 0.12s idle of 0.50s (0.19s queued) docker run --cpuset-cpus=0-3 ... pytest tests/test_rpc_model.py --idle-ceiling 0.05 -n 16 main: 81 over, worst 6.11s idle of 8.16s branch: 1 over, 0.13s idle of 1.11s (0.73s queued) an unmarked test that does time.sleep(0.5), on the branch: 0.53s idle of 1.70s (0.00s queued) # an honest wait is still charged in full ``` The CI shard command, same container, four workers pinned to two CPUs: ``` pytest -q --shard 3/4 --durations=25 --idle-ceiling-strict -n 4 --cov=raven --cov=raven_everos --cov-branch --cov-report= main: 5131 passed, 10 skipped; "1 unmarked test(s) waited more than 3s (failing the session)" 3.11s idle of 6.04s tests/test_ppt_engine_template_tool.py::test_binding_a_template_by_path; exit 1 branch: 5378 passed, 11 skipped; no idle report; exit 0 branch, shards 1/4, 2/4, 4/4: no idle report in any (three failures, all `git ls-files` exit 128 because the copied worktree's gitdir is not inside the container) ``` - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed (none; the option help text and the summary line say what is now subtracted) ## Risk - Behaviour change is confined to what the ceiling charges a test: less on a busy runner, the same for a real wait. Nothing under `raven/` changes. - `Thread._bootstrap_inner` is private CPython API (present from 3.8 through 3.13); the wrapper is installed once, guarded against a second import of the module, and pinned by a test. If a future interpreter drops it the module fails to import at collection, which is loud. - Rollback: revert the commit; the gate goes back to wall clock minus CPU. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues #428 made the ceiling a gate; #452 warmed the catalogue; #449 halves the workers instead. Co-authored-by: Claude (claude-fable-5-1) <noreply@anthropic.com>
…458) ## Summary `os.kill(pid, 0)` is the POSIX existence probe. On Windows, CPython implements `os.kill` as `TerminateProcess`, so signal 0 is not a probe there at all, and it fails in two directions depending on what the caller may do to the target: - a process the caller can terminate, which is any child of it, is killed and the call returns cleanly, so the probe reports "alive" about a process it has just destroyed; - a process it cannot, such as the detached `raven web` supervisor, raises `OSError` with `ERROR_INVALID_PARAMETER`, so the probe reports "dead" about a process that is running. The second is why `raven web --stop` did nothing on Windows. `_read_web_state` and `_read_serve_pid` both answered `None`, so `_stop_resident` signalled neither the supervisor nor the gateway, and printed "nothing to stop" while the engine went on serving the page. `raven/utils/pid.py` holds the platform question now. Windows asks `OpenProcess` plus a zero-timeout `WaitForSingleObject`, the pair `raven/updates/upgrade.py` already uses to watch a parent exit: a handle that fails to open for any reason but `ERROR_ACCESS_DENIED` is a dead process, and one that opens and then times out is a live one. The wait rather than `GetExitCodeProcess`, because a process whose real exit code is 259 cannot be told apart from `STILL_ACTIVE`. `_pid_alive` keeps its name in `serve_commands`, since `_gateway_page` imports it and the stop tests patch it; only the answer moves. Four other call sites carry the same probe and are left for their own change. The sharpest is `raven/agent/tools/background_exec.py`, which reaches it on the Windows branch specifically, against the agent's own background tasks. ## Type - [x] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification Run on Windows 11, in a clean worktree cut from `main`: - `uv run pytest tests/test_utils_pid.py tests/test_cli_serve_commands.py -q`: 77 passed, 3 failed. - the same 3 failures reproduce on `main` without this change (`test_the_state_file_stays_owner_only`, `test_sigterm_removes_the_state_file`, `test_sigterm_during_the_prologue_still_removes_the_state_file`): they are pre-existing Windows gaps in the supervisor tests, unrelated to this PR, and this branch adds no new failure. Baseline on `main` is 73 passed, 3 failed; the 4 extra passes are the new `tests/test_utils_pid.py`. The new test drives real subprocesses. Survival is asserted by a wait that must time out, not by a poll taken straight after the probe: termination is asynchronous, and the immediate read still says "running" for a process already on its way down. Forcing the POSIX branch on every platform fails two of the four cases. - [x] Relevant tests pass locally - [ ] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed ## Risk POSIX behaviour is unchanged: the helper keeps calling `os.kill(pid, 0)` there. On Windows, `raven web --stop` starts working and stops silently terminating child processes it only meant to look at, which is the point of the change. Rollback is reverting to the inline `os.kill(pid, 0)` in `raven/cli/serve_commands.py` and dropping `raven/utils/pid.py`. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A --------- Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
…es (#457) ## Summary A Windows checkout of this repo with the default `core.autocrlf=true` rewrites every text file to CRLF, and two byte-exact gates in `ui-web` then fail on a tree that is byte-identical to the one CI passes on Linux: - the boot-order and open-conversation node tests slice their source on `"\n}\n"`, which never matches a CRLF file, so the sliced function comes out empty and 9 tests fail with `ReferenceError`; - `gen-rpc-client --check` compares its own LF output against the file on disk and reports permanent drift in `src/rpc/generated.ts`. `.gitattributes` pins the checkout filter so the working tree matches what the repository already stores. The repository stores LF today, so this adds no renormalization diff: `git add --renormalize .` over the whole tree stages nothing beyond the two files in this PR. Only the checkout filter changes. `ui-web/build.py` is the other half of the same defect. It assembled the page under Python's default newline translation, so `dist/index.html` came out CRLF on Windows even from an LF source tree, and `check-page.mjs` could no longer extract the script payloads (its `<script>\n` anchor needs the LF to be the next byte). That artifact ships inside the wheel, so it must not depend on which platform assembled it. ## Type - [x] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification Run on Windows 11 with `core.autocrlf=true`, in a clean worktree cut from `main`: - `git add --renormalize .` after the change stages nothing (confirms no renormalization diff); - `file ui-web/src/rpc/generated.ts` reports CRLF on `main` and LF after a forced re-checkout on this branch; - `npm run gen:check` (ui-web): `generated.ts matches the contract (178 methods)`; - `npx vitest run` (ui-web): 108 files, 1836 tests passed; - `npm run --prefix ui-web build && python ui-web/build.py`: `dist/index.html` is LF; - `node ui-web/scripts/check-page.mjs`: `OK (1,482,975 bytes, 3 scripts parse)`; - `node ui-web/scripts/count-shared-globals.mjs`, `check-css.mjs`, `check-class-namespace.mjs`: all OK. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed ## Risk No runtime behaviour changes for users on Linux or macOS, where the checkout already produced LF. On Windows the working tree stops being rewritten to CRLF, so a contributor with existing local checkouts may see one large renormalization touch on the next checkout; the committed bytes do not move. The built `dist/index.html` is now LF on every platform, which is what the Linux-built wheel already shipped. Rollback is deleting `.gitattributes` and dropping the `newline=""` argument in `ui-web/build.py`. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A --------- Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
…here (#464) ## What A skill installed through the market had no way to be removed from the page, because `ext.list` decided "was this installed from the hub" by looking for one marker file, and the installer most skills arrive by writes a different one. Closes #456. Two installers put hub skills on disk: | | market module (`raven/skill_hub/hub.py`) | context engine + `use_skill` (`raven/agent/tools/skill_hub.py`, `raven/context_engine/segments/skills.py`) | | --- | --- | --- | | lands at | `<skills>/<name>/` | `<skills>/hub/<slug>@<version>/<name>/` | | stamps | `.skillhub.json` | `.install-meta.json` | `ext.list` tested only for the first, so the second -- the ordinary path -- reported `hub: false`. The page gates its removal control on that flag, so it appeared for the exception and never for the rule. On the machine this was found on, three of four installed skills were the second kind; the one that showed a remove button was the one that came through the market module. ## How Backend only; nothing under `ui-web/` or `ui-tui/` changes. The page already renders the control when `hub` is true, and already skips the market-detail fetch when `hub_id` is empty, so both halves light up on their own once the flag is right. - `raven/skill_hub/audit.py` names the stamp it writes (`INSTALL_META`) so the two readers test for the file the installer wrote rather than a spelling of their own. - `raven/rpc/methods/console.py` reports `hub: true` when either stamp is present. The bundle stamp carries a slug, not a market id, so that skill has an empty `hub_id` -- a state the page already handles. - `raven/skill_hub/hub.py` lets `skillhub.remove` find a bundle by the skill name inside it, resolving the wrapper folder the same way `SkillHubClient._bundle_root` did at install time, and removes the bundle whole: the bundle is the install unit, and the CLI's `skill remove` deletes the same folder. Only a stamped directory is eligible -- a folder placed under `hub/` by hand is left alone, which is the rule `MARKER` already enforced on the other layout. The second change is not optional given the first: reporting `hub: true` without it would put a remove button on a skill the handler then refuses, which is worse than no button. ## Verification - `test_ext_list_calls_a_skill_hub_installed_whichever_installer_stamped_it`: three skills, one per stamp and one with neither, asserting `(hub, hub_id)` for each -- `(True, "uuid-market")`, `(True, "")`, `(False, "")`. The marker names are pinned as literals in the test so a drift between writer and reader is a failure, not a pass. - `test_remove_reaches_a_bundle_the_other_installer_cached`: a stamped bundle is removed whole by its skill name; an unstamped folder under `hub/` is refused and left in place, and so is the neighbouring bundle. - Mutation-checked: dropping the second stamp from `ext.list` fails exactly the first test; dropping the bundle lookup from `remove` fails exactly the second. 139 passed in the two suites on the unmutated tree. - Dry-run against the real workspace: all four installed skills now report `hub: true`; the three bundles resolve to their `<slug>@v0` folders by skill name; the market-module skill and an unknown name resolve to none. - Ruff check and format clean. ## Not done here, on purpose Removal does not stop the context engine re-installing the skill on its next catalogue hit; the CLI documents the same limit and points at `skill block`. Whether the page should offer that beside removal is a UI question and out of scope while `ui-web/` is frozen. Also left as they were: the two installers keeping two layouts and two stamps -- this makes the readers agree with both writers rather than deciding which writer should win. Co-authored-by: Claude (claude-fable-5-1) <noreply@anthropic.com> --------- Co-authored-by: Claude (claude-fable-5-1) <noreply@anthropic.com> Co-authored-by: KT <74288668+0xKT@users.noreply.github.com>
… group (#455) ## Summary A conversation started in the TUI was listed by the served page and then could not be opened: clicking it produced an empty transcript. `SessionManager.session_path` looks for a transcript in this process's own group, and falls back to searching the other groups when it is not there. That fallback was gated on `project_slug`, so only a surface that groups by launch directory ever ran it. The gateway groups by channel and never did -- yet `list_sessions` scans every group, so the page offered a conversation filed under a launch directory's slug and then looked for it under `sessions/tui/`. `session.resume` missed the file, fell through to its fresh-mint branch, and handed the reader an empty transcript under a new id. The write verbs resolve through the same function, so the miss was not only a failed read. A rename, a pin or a turn from the page filed a second transcript under the channel group, splitting one conversation across two files: ``` sessions/-srv-alpha/abc.jsonl -> 2 messages sessions/tui/abc.jsonl -> 1 messages (written by the gateway) ``` Ungating the search is necessary but not sufficient, and the second half is the part worth reviewing. The glob matches on the chat_id, and a chat_id names no channel: `cli:<id>` and `tui:<id>` are two conversations, so an ungated search let a `tui:` save adopt the `cli:` file and append to it. A candidate is now taken only once its own metadata key says it is this session. A transcript carrying no key predates that field and is still adopted on the stem alone, which is the case the search was originally added for. Matching on the stored key rather than on the directory name is also what makes this correct on Windows. `key_from_path` tells a slug from a channel by a leading `-`, which holds only for POSIX paths; a Windows launch directory slugs to `C--Windows-system32`, with no leading separator to read. Root cause was confirmed against a control rather than by inspection: two `tui:` sessions, same handler, same manager, differing only in which group holds the file. The one under `sessions/tui/` resumed with its 26 messages; the one under a slug minted a fresh empty session. ## Type - [x] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification ``` uv run pytest tests/test_session_project_grouping.py -q 2 failed, 32 passed uv run pytest tests/test_cli_session_commands.py -q 43 passed uv run ruff check raven/session/manager.py tests/test_session_project_grouping.py All checks passed! uv run ruff format --check <same two files> 2 files already formatted uv run ty check raven/session/manager.py All checks passed! ``` The 2 failures in `test_session_project_grouping.py` are present on unmodified `main` in this Windows checkout and are not touched by this change: `test_project_dir_is_not_rewritten_by_a_later_run_elsewhere` and `test_slug_collides_for_a_separator_and_an_underscore` both build fixtures from POSIX paths such as `/srv/alpha`, which `Path` renders as `\srv\alpha` on Windows. Regression scope was measured rather than asserted. Running `pytest tests/ -k "session or transcript or workdir or resume"` (about 1085 tests) with and without the change gives **372 failures before and 372 after, with identical failure sets** -- no test changes state in either direction. The same comparison over the session-adjacent files alone gives 156 before and 156 after. Five tests were added to `tests/test_session_project_grouping.py`, extending the existing file rather than adding a new one. Two of them fail without the fix and pass with it: - `test_the_gateway_opens_a_session_filed_under_a_project_slug` -- the reported bug; - `test_the_gateway_appends_to_the_transcript_it_opened` -- the split-file half; - `test_two_channels_sharing_a_chat_id_keep_their_own_transcripts` -- the guard against over-correcting, and the case that caught the first attempt at this fix; - `test_a_keyless_transcript_is_still_adopted_across_groups` -- pre-key transcripts stay reachable; - `test_the_gateway_still_files_its_own_sessions_by_channel` -- reaching across groups is a fallback for an existing transcript, not a change of where this process writes. The first attempt, which ungated the search without the key check, broke `test_cross_channel_ambiguous_same_id_two_channels` in `tests/test_cli_session_commands.py`. That failure is what produced the key check. Fix verified against the real workspace that produced the report: the affected session now opens with both of its messages, resolving to its actual file under the launch directory's slug. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed No docs change: this corrects the behaviour the surrounding docstrings already described, and coins no domain term. ## Risk User-visible change: a surface can now open a transcript filed under another group. On the gateway this closes a gap rather than widening reach -- `list_sessions` already scans every group, so those sessions were already listed and offered; only opening them was broken. For a slug-grouped surface the search is unchanged apart from the new key check, which makes it strictly stricter: a candidate that would previously have been adopted on its stem alone is now refused when its stored key names a different session. Transcripts written before the metadata `key` field are unaffected -- they answer no key and are still adopted on the stem. `session_path` now reads the first line of each candidate when the direct path is absent. The read is one `readline` on a file the caller is deciding whether to open, and the candidate set for an exact chat_id is normally empty or a single file. Rollback is a revert of the single commit; the change is confined to one function and one new module-private helper, and writes nothing to disk that a revert would strand. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A --------- Co-authored-by: Claude (claude-opus-5[1m]) <noreply@anthropic.com> Co-authored-by: KT <74288668+0xKT@users.noreply.github.com>
…t moved on (#476) ## Summary The installers stop telling you what to run and run it. `install.sh` ends with `raven web --stop && raven web --foreground`, so a finished install is the page open in a browser rather than a hint to go and type something; first-run setup is walked through by the page's own onboarding. The capability summary, the upgrade-versus-first-run wording and the config.json probe that fed it go with it. `install.ps1` mirrors all of this, with one deliberate divergence: a non-zero page exit only warns there, because under `irm | iex` that is the reader's own interactive PowerShell and Ctrl-C, the ordinary way to end a foreground page, returns non-zero. Two gaps that only bite a source install are closed on the way: - `build_web_assets` compares each artifact against its sources instead of only testing that it exists. An editable install relinks Python and nothing else, so `ui-tui/dist/entry.js` and `ui-web/dist/index.html` kept serving whatever the tree held at first install while every `.py` beside them moved on. The page watches `i18n/` too, since `ui-web/build.py` inlines the catalogue. `node_modules`, `dist` and `.modern` are pruned: `npm ci` rewrites the first on every build, and the other two hold the artifact being judged. - A node packaged without npm now fetches a private runtime that carries one. Debian and Ubuntu split the two packages, so a system node >= 22 satisfied `ensure_node` and the build then found no npm with no private runtime ever provisioned. The download body is split out as `provision_private_node` and called from the build probe as well, subshelled so a failure there is a skipped build rather than a failed install. It stays lazy rather than moving into `ensure_node`, so a wheel install that never runs npm does not pay for a 50 MB download it has no use for. Both READMEs are reordered and the clone install is documented under Quick Start, where it was missing: `./install.sh` and a piped run do different things from inside a checkout, because local mode requires `$0` to be a real file, and `RAVEN_LOCAL_SRC` is the opt-out. The Chinese README also picks up the EverMind ecosystem table the English one had already moved to. One test changed rather than being deleted. `test_readme_quickstart_matches_the_installer_hint` required an `Onboard and run` heading in both READMEs; the installers no longer close with a first-run hint and neither README carries that section, so the pin had no subject left. It is repointed at what the two must still agree on, which is the clone install. `install.ps1` has not been parsed or executed. There is no pwsh on the machine this was written on and no PowerShell step in CI, so every Windows change here is reviewed by eye and pinned by text tripwires only. A real Windows run before release would be worth it. ## Type - [ ] Fix - [x] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification `uv run python -m pytest tests/ --ignore=tests/integration` on the merged tree: 23161 passed, 109 skipped. `uv run pytest tests/test_install_script.py`: 27 passed, including a functional test that extracts the real `is_stale` and `resolve_node_dir` from `install.sh` and runs them under `sh`. `is_stale` is checked across missing artifact, fresh artifact, touched page source, touched catalogue, and build output touched; `resolve_node_dir` across system node with npm, node without npm, node without npm and a failed fetch, and no node at all. Both were mutation-checked: removing the fallback line from `install.sh` turns the new tests red. `sh -n install.sh`, `make check-source-language`, `make check-large-files`, `ruff check`, `ruff format --check`: all clean. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [x] User-facing docs or screenshots are updated when needed ## Risk User-visible: a finished install now holds the terminal on a running page instead of returning to the prompt. That also means the installer's exit code is the gateway's, so on a clone where the page build only warned, `install.sh` now exits 1 with "No page is built" where it used to exit 0 with a working TUI. A re-run over a checkout that has moved on will rebuild the frontend, which costs an `npm ci` it used to skip. Rollback is per commit: the launch, the rebuild-on-change and the npm fallback are independent edits to `install.sh` and `install.ps1` and can be reverted separately. No runtime code outside the installers changed. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A --------- Co-authored-by: admin <admin@SH-HuKai.local> Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
## Summary Turn "bug fix to regression case" from a manual chore into a gated pipeline, and give trajectory regressions their own CI gate. - `raven trajectory replay --json`: stable machine-readable report (schema_version 1) with defensive JSON coercion; on a halted replay the full JSON is still emitted before exit code 2. - Case metadata contract: `case.yaml` requires non-blank issue/owner/why/re_record; residual-scan findings pass only with an explicit per-token human sign-off (`reviewed_residuals`: full-token sha256 plus a verbatim reason, nothing exempted automatically -- a machine-produced redaction report is not approval). - `raven trajectory regression validate [CASE_DIR|--all]`: static commit gate -- whole-directory discovery so a broken case cannot hide, both schemas, cassette completeness down to the replay contract (valid JSON is not a usable payload: missing payloads via the extracted replayability check, corrupt inner fields via `validate_recording`), rejection of files the residual scanner would silently skip, and a 256 KiB / 1 MiB size budget; every parse boundary collects problems instead of raising, so one malformed case cannot abort the --all sweep. - `raven trajectory regression init <source> --name <case>`: scaffold from a bundle directory, attempt id, trajectory report tarball, or bug report package (two-layer extraction; both passes accept only regular files/directories with root-contained names). Minimize lands in staging; residual findings are reviewed interactively (the full token is shown locally, files receive digest and reason); publication copies onto the destination filesystem under a `.init-` staging name, re-checks the case name, then renames on one device -- failures clean up only what the run created. - Vacuous-pass guard (`MIN_COMMITTED_CASES`, raised by hand) and a `trajectory` CI job: replay the committed cases, run `validate --all` even after a pytest failure so both gates report, upload per-case failure reports (replay --json schema plus check failures, mode from each case's expect.yaml) as a 14-day artifact. - Docs: the Trajectory Regression Case entry in CONTEXT.md extended in place; `tests/trajectories/README.md` documents the per-case layout and workflow. Also: `config_secrets_loaded` (a field name of redaction.json itself, a guaranteed self-referential false positive in every cassette) joins the residual scanner's benign literals, and both sample cases gain real `case.yaml` metadata. ## Type - [ ] Fix - [x] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification - `uv run pytest tests/ -q` (after rebasing onto latest main): 23149 passed; the 37 failures and 20 errors are environment-bound and reproduced identically on a pristine origin/main worktree without these changes. - `uv run pytest tests/test_trajectory_regressions.py tests/test_cli_trajectory_regression_commands.py tests/test_cli_trajectory_commands.py tests/test_trajectory_replay.py tests/test_trajectory_cassette.py tests/test_trajectory_redact.py -q`: 282 passed (about 90 new tests across the five steps). - `uv run ruff check .`, `uv run ruff format --check .`, `make lint-imports`, `make lint-deps`, `make check-large-files`, `make check-source-language`: pass. `make lint-types` reports one diagnostic in `raven/agent/tools/web.py`, a file this branch does not touch, pre-existing on main. - CI workflow YAML parses (`yaml.safe_load`); the first real run of the new `trajectory` job happens on this PR (see this PR's checks). - CLI assertion width-independence re-verified with `COLUMNS=60`. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [x] User-facing docs or screenshots are updated when needed ## Risk - All additive: the default `replay` output path is unchanged; `regression init/validate` are new subcommands; the one scanner change adds a fixed benign literal (any variation still flags). - The new `trajectory` CI job becomes a required-passing gate for PRs; to lift it in an emergency, revert the CI commit (`ci: add trajectory regression gate job and case-count guard`) -- the remaining commits are independent. - Tar extraction accepts only regular files and directories with root-contained relative names on both layers, with `filter="data"` kept as defense in depth. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A --------- Co-authored-by: 江国庆 <guoqingjiang@deepglint.com> Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
## Summary Present Raven's orchestration capabilities and four specialized agents with benchmark figures, and keep the English and Chinese READMEs aligned. - Add the Multi-Agent Orchestration Benchmark at the end of the introduction. Compare Hermes, Claude Code, and Raven with shared 0-1 scales for Node F1, Edge F1, Partial Order Accuracy, and Exact Match Rate. - Move Raven Agents before Benchmarks and add concise Research, Code, Design, and Oncall descriptions with their figures. - Replace the third-party agent table with a centered card image and explain how Raven connects to and orchestrates these agents. Host image files as GitHub attachments. - Standardize primary heading markers and align section order, captions, model settings paths, and the EverMind ecosystem catalog across both languages. ## Type - [ ] Fix - [ ] Feature - [x] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification Commands run after rebasing onto the latest main: ```text uv run --no-sync pytest tests/test_readme_scope_canon.py tests/test_living_docs.py -q -o addopts='' # 4 passed uv run --no-sync pre-commit run --files README.md README.zh-CN.md # All applicable checks passed make check-large-files # Passed make check-source-language # Passed env PYTHONPATH=. uv run --no-sync python scripts/check_commit_messages.py origin/main..HEAD # Passed git diff --check # Passed ``` Compared all 27 headings, 12 code blocks, image URLs and sizing, commands, paths, numeric values, and architecture graph structure across both languages. Verified all 24 orchestration scores against the supplied results and confirmed that the uploaded chart matches the local PNG byte for byte. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [x] User-facing docs or screenshots are updated when needed ## Risk This changes documentation structure, wording, and linked figures. Runtime behavior is unchanged. Figures depend on GitHub attachment availability. Roll back by reverting the changes to README.md and README.zh-CN.md. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues #462, #465, #474, #477, #479 --------- Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Claude (claude-opus-5[1m]) <noreply@anthropic.com>
…the roles (#468) ## Summary The agent loop already names four strategy roles (Memory, Planning, Capability, Action), but two judgements a turn makes still lived in the shell around them. This moves both onto the roles, in two dependent commits, with no behaviour change intended. **1. The charter reads through Memory and Action.** A dispatched sub-agent's playbook (`_meta["raven.playbook"]`) was read from four places inside the loop and the tool registry. `prompt` and `stopWhen` are now read by `DefaultMemory.assemble()`, which fills `task_brief` and `task_done_when` on the `TurnContext` the identity segment renders. `checks` and `code` are read by `DefaultAction.judge()`, which `ToolRegistry.execute` asks once per proposed call, the way it already asks the permission gate. `tools` stays in `withheld_names()` and the registry still decides what a refusal does, so a replaced role withholds nothing it could not already withhold. **2. Mid-turn window shrinking seats on Memory.** The five recoveries the loop ran inline go through one method, `MemoryModule.shrink(messages, pressure, state, model)`, with `WindowPressure`, `WindowState` and `ShrinkResult` as the carriers: - proactive compaction, once the last billed reading crosses the trigger - the standing image window - a provider's overflow error - a picture refused inside a tool result - pictures refused for size The retry mechanism stays in the shell. The six `continue` sites and the iteration rollback did not move, so a hook sees a retried call exactly as it saw the first. `WindowState` is a per-turn object the shell owns and hands to each call, because the role outlives the turn; `image_window` has no default, since 0 is a real value meaning "no picture stays". The pure pieces both sides need move to a new package, `raven/agent/window/`, which the import contracts treat as cargo that may not import the loop shell. `REASONING_EFFORT_LADDER` moves to `contracts/llm_provider.py` because the loop's empty-response descent and the window's head summary both read it and neither package may import the other. Contract papers: `CONTRACTS_VERSION` 26 to 28, `CONTRACTS_LINE_CEILING` 3,040 to 3,200, each bump with its own review paragraph in `tests/test_kernel_budget.py`. A follow-up PR carries the third step, which gives a sub-agent nine verbs in place of the six hook phases. It is kept separate because it touches every bundled plugin. ## Type - [ ] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [x] Refactor - [ ] Other ## Verification Gates, run at each of the two commits: ``` uv run ruff check --no-cache raven evolver agents plugins-dist tests scripts uv run ruff format --check --no-cache raven evolver agents plugins-dist tests scripts uv run lint-imports uv run pytest -q npx commitlint --from origin/main --to HEAD --config commitlint.config.cjs uv run python scripts/check_commit_messages.py origin/main..HEAD uv run python scripts/check_source_language.py origin/main..HEAD uv run python scripts/check_large_files.py origin/main..HEAD ``` Import contracts: 10 kept, 0 broken at both commits. The suite is green at both; the failures this machine shows are the same ones it shows on an unmodified `main` (missing browser and optional packages), and neither commit adds one. The loop's own e2e files, which the default selection leaves out, were run too (charter, playbook, truncation, direct chat, fallback chain, ACP stdio): no failure that `main` does not also show. Behaviour preservation. Each commit was run against a worktree of its own parent under the same scripted provider, outputs normalised for nonces and temp paths, then byte-compared. Four scenarios, eight comparisons, all identical: - a whole turn with no playbook: host turn, spawned sub-agent, tool calls and refusals - a playbook-on worker lane in process, including two charter refusals - six window-shrink scenarios: proactive compaction, the standing image window, overflow, a refused picture, oversized pictures, the effort ladder - the five-plugin hook seam, every phase's decision and metadata per iteration Mutation check on the second commit: breaking each of the five moves inside `DefaultMemory.shrink` turns exactly its own scenario red, so the comparison is not vacuous. Real model, Sonnet 4.5 through OpenRouter, at both commits: - playbook on, in-process lane: both workers receive the charter, write their files and finish - playbook on, ACP fork lane: the charter prompt crosses the process boundary, and a charter check blocks a `write_file` outside the fence - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed ## Risk No user-visible behaviour change is intended, and the comparisons above are byte-identical on every path they cover. For anyone replacing a harness role: `MemoryModule` gains `shrink`, so a replacement written against the old protocol needs it. `DefaultMemory.bind()` names the missing member at assembly rather than failing inside a turn. Rollback is a revert of the merge commit. Nothing persisted on disk changes shape. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A --------- Co-authored-by: yao pengfei <yaopengfei@shanda.com> Co-authored-by: Claude (claude-fable-5-1) <noreply@anthropic.com> Co-authored-by: KT <74288668+0xKT@users.noreply.github.com>
## Summary Synchronize the Chinese README with the updated English overview. The change aligns the Raven introduction and built-in agent section, including the Host Agent positioning, Raven Evolver description, built-in agent wording, and orchestration readiness statement. The existing benchmark charts, links, captions, and the remaining bilingual sections are preserved. ## Type - [ ] Fix - [ ] Feature - [x] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [x] User-facing docs or screenshots are updated when needed Commands run: - `uv run --no-sync pytest tests/test_readme_scope_canon.py tests/test_cli_onboard_commands.py -k readme_quickstart -q` (1 passed) - `uv run --no-sync pytest tests/test_docker_runtime.py -k container_exposes_no_engine_selector -q` (1 passed) - `UV_NO_SYNC=1 make check-large-files check-source-language PYTHON_VERSION=.venv/bin/python COMMIT_RANGE=HEAD` (passed) - `git diff --check` (passed) ## Risk - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes This is a documentation-only change. Reverting the commit restores the previous README wording. ## Related Issues N/A Co-authored-by: Claude (claude-opus-5[1m]) <noreply@anthropic.com>
## Summary Five upgrade-path and reporting defects in the embedding pin that #414 landed. Three were named as bounded follow-ups in that PR's final review; two came out of driving the acceptance environment afterwards, and both of those were invisible to the unit suite because every fixture already held the new shape. The two found by acceptance: - **A config still holding the retired endpoint shape does not load at all.** The block used to carry the address and the key; `EmbeddingConfig` forbids extras, so `raven doctor` ends in a traceback rather than in a degraded feature, and so does every other command that reads the extension blocks. The migration takes those keys now: the address is adopted onto whichever configured provider answers there, and when nothing answers the whole block goes rather than half of it, with the dropped address named in the log. A model left with no provider is a pin every reader resolves to nothing while every screen reads it as configured. - **A pin could not name a provider this config holds.** The provider half was checked against the registry alone, and Raven carries no spec for every vendor LiteLLM can reach. A pin the wizard had stored and every reader resolved came back from the settings page as a provider that does not exist, leaving the endpoint uneditable on the page that exists to edit it. A section the operator wrote counts as proof the vendor exists; a name nothing holds is still refused as a typo. The three from review: - **Clearing the pin was a no-op.** The picker's inherit option sends both halves empty and the writer dropped empty values before deciding anything, so the call returned applied while the file kept the old pair. - **Doctor reported a move it had not made.** The endpoint only moves when a configured provider answers at the retired address, and the return value was discarded. The unmade move stays in the remaining list now, with a line saying why. - **The canonical SkillForge glossary entry still said `skillForge.everos`** and attributed the local extraction pipeline to an embedded copy of the memory backend. ## Type - [x] Fix ## Verification - `uv run --all-extras pytest -q` -- 23058 passed, 110 skipped, 4 failed; the four are the same pre-existing failures main carries (`test_agent_loop_token_budget`, `test_agents_code_launcher`, `test_agents_oncall_launcher`, `test_ppt_engine_image_search`), matched by test id against a run on the merge base. - `uv run ruff check .` and `uv run ruff format --check .` -- clean. - `make check-commits`, `make check-source-language`, `make check-large-files` -- clean. - Every fix was reproduced before and after against a real config or a real gateway, not only through its test: the unloadable config came from the acceptance home itself and `raven doctor` was run on it before and after; the provider refusal was reproduced over a real WebSocket to a running gateway; clearing and the doctor report were each run end to end. - Each new test was checked by removing its fix and confirming it fails. - Wider acceptance on the running product, with the memory plugin and without it: a gateway with no memory plugin installed built a knowledge base whose width (4096) was measured from the live endpoint and answered a query sharing almost no keywords with the source (score 0.728); the real TUI recalled a fact stored in an earlier turn, with the recall payload in the audit artifact as evidence rather than the model's answer. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [x] User-facing docs or screenshots are updated when needed ## Risk Two changes widen what is accepted rather than narrowing it: a provider section counts alongside the registry, and a migration edits a block it previously left alone. The migration only runs for a config carrying the retired keys and is stamped, so it runs once; a config already in the current shape is untouched. Rolling back any single commit is safe -- they do not depend on each other. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ith (#483) ## Summary An EverOS agent case carries three fields: `task_intent`, `approach` and `key_insight`. Recall built `Memory.text` from the first and the last only, so the method a case was solved with never reached the prompt. The reader saw what was attempted and what was concluded, with the steps between them missing. The other two consumers of the same row already keep all three fields: the sub-agent memory record joins intent, approach and insight, and the memory page maps `approach` to a card's body. Only the recall path that feeds the model dropped it. This joins the three fields in field order. Two constraints shaped how: - Intent stays on the first line. The skill router takes this text's first line as a hit's display name and as its line in the search listing, so moving intent off line one would rename every recalled case. - Empty fields drop out, so an EverOS that answers without an `approach` still reads as prose instead of carrying a blank paragraph. ## Type - [x] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification ``` uv run --frozen pytest tests/test_everos_backend.py tests/test_skill_router_everos_source.py -q 182 passed in 17.69s make lint-python lint-types ruff check: All checks passed! ruff format: 1960 files already formatted ty check: All checks passed! make check-source-language clean, no findings ``` The case test asserts the exact joined text rather than substring membership, and it was run against the pre-change conversion first to confirm it fails there (AssertionError on the missing approach). A second test covers a row that arrives without an `approach`, which is the shape that would otherwise produce a blank paragraph. No user-facing doc states the recall text shape. The CONTEXT.md passage that lists all three case fields describes the sub-agent memory record, which is a different code path and is unchanged. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [x] User-facing docs or screenshots are updated when needed ## Risk Each recalled case now carries one more paragraph, so the agent track's share of a prompt grows by roughly the length of one `approach` per case hit. Nothing downstream parses this text: the skill router reads the first line for a display name and passes the remainder through untouched, and that first line does not move. Rollback is a revert of the single commit. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
## Summary Synchronize the Chinese README with the updated English README. The change aligns the Raven introduction, benchmark captions, built-in agent copy, third-party agent positioning, and the removal of the obsolete benchmark summary section. The change also adds the updated wording for data analysis, PowerPoint slide generation, and unattended AI4S benchmark captions while preserving all chart links and remaining bilingual sections. ## Type - [ ] Fix - [ ] Feature - [x] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [x] User-facing docs or screenshots are updated when needed Commands run: - `uv run --no-sync pytest tests/test_readme_scope_canon.py tests/test_cli_onboard_commands.py -k readme_quickstart -q` (1 passed) - `uv run --no-sync pytest tests/test_docker_runtime.py -k container_exposes_no_engine_selector -q` (1 passed) - `UV_NO_SYNC=1 make check-large-files check-source-language PYTHON_VERSION=.venv/bin/python COMMIT_RANGE=HEAD` (passed) - `git diff --check` (passed) ## Risk - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes This is a documentation-only change. Reverting the commit restores the previous README wording. ## Related Issues N/A --------- Co-authored-by: Claude (claude-opus-5[1m]) <noreply@anthropic.com>
…ch (#485) ## Summary Enabling an entrance from the page (weixin, for one) builds and starts the adapter on the spot through `ChannelManager.start_one`. The gateway command hands every channel its spine dispatch in one loop at launch, and that loop has already run by then, so a channel started this way has no `intake._submit`: it logs `no spine dispatch wired` once per message and drops every one, while the page draws it connected and the adapter's own login has succeeded. The outlet half of the same hazard was fixed earlier: `channels.on_started` registers an outlet so a hot-started channel can be replied to. The intake half stayed a launch-time loop, and `start_one` fires `on_started` and nothing else, so nothing after launch ever calls `set_submit` for a channel born later. The fix composes the intake onto the hook the outlet already uses. A module-level `_wire_channel_intake(channels, dispatch)` sets the submit on each channel the manager holds and wraps `channels.on_started` so a channel started later gets the outlet it already got plus the intake; the command calls it at the point where `_inbound_dispatch` is born. Channels present at launch are wired exactly as before. The helper is module-level rather than a closure in the command body so that it can be executed by tests, which the command body cannot be. Nothing under `ui-web/` or `ui-tui/` changes. ## Type - [x] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification - `.venv/bin/python -m pytest tests/test_cli_gateway_commands.py -q` on this branch rebased onto current `main`: 50 passed. Four new cases drive `_wire_channel_intake` through fake channels and a fake manager: every channel present at launch gets the dispatch; a channel the manager starts later gets it through `on_started` while the outlet hook that was already there still runs; a manager with no outlet hook still wires the intake; and the command body is pinned by source to call the helper with `_inbound_dispatch` and to have lost the launch-only loop. - Mutation-checked both ways: dropping `wire(ch)` from the composed hook fails exactly the two cases that start a channel late; dropping the outlet call fails exactly the outlet assertion of the hot-started case. - `python scripts/coverage_gate.py diff --base-ref upstream/main --threshold 90` after the file ran under `--cov=raven --cov-branch`: 91.67% (11/12 executable changed lines). The one uncovered line is the call inside the `gateway` command body, which no unit test executes, the same as every other line of that body. - `.venv/bin/ruff check` and `.venv/bin/ruff format --check` on `raven/cli/gateway_commands.py` and `tests/test_cli_gateway_commands.py`: clean. - The drop was reproduced on a live gateway before the fix (2026-09-14): enable, disable and re-enable weixin through the page's own RPC puts the adapter on the `start_one` path, and every inbound message was logged as `no spine dispatch wired` and dropped. - No user-facing docs change; the behaviour was always the documented one. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed ## Risk - Security: none. No new input path; the dispatch a hot-started channel now receives is the same one launch-time channels already had. - Backward compatibility: channels present at launch are wired by the same call as before; the only change is that a channel started later is wired too. - Rollback: revert the squash commit; the hook falls back to the outlet-only lambda. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
…es (#469) ## Summary Follows #468 (merged as `869ad78a`), which moved the charter reads and the window's mid-turn shrinking onto the harness roles. This branch is rebased onto `main` and carries only its own three commits. A sub-agent's product logic was an `AgentHook` subclass: six phase methods over a fourteen-field context it could write, with the turn's own state kept in the context's free-form `metadata` dict. Surveyed before this change, the five bundled plugins had 71 phase methods, 42 of them doing real work, reading fourteen context fields and using six decision fields. Every one of them fits a small set of verbs. This PR makes those verbs the contract, in three commits. **1. A sub-agent writes a conduct instead of six hook phases.** New factory-loop-tier paper `contracts/agent_conduct.py`: `AgentConduct` with nine optional verbs (`intake`, `select_tools`, `advise`, `system_addendum`, `review`, `salvage`, `outbound`, `archive`, `observe`), answered against a read-only `StepView` of fourteen fields, with `Intake` and `Verdict` (Accept / Resample / End) as the answers and a `ConductFactory` because the host builds one conduct per turn. It sits beside `loop_hooks` rather than replacing it. The six phases remain the loop's *timing* contract: when the loop asks, in what order, what a refusal does. This is the *judgement* contract: given one step, what does this agent say about it. `ConductHook` seats one in the other, so a plugin that still ships a plain `AgentHook` keeps working and the composite's ordering, merging and rollback are untouched. All five bundled plugins are ported: design-engine, ppt-engine, code-flow, oncall-flow and research-flow. Research keeps its six-phase gate chain: the gates now subclass a plugin-private `Gate` and read a `GateCtx` the conduct builds from its `StepView` and its own turn-private facts, so six thousand lines of gate bodies did not move and its wrappers still see the cross-gate state they read. The turn's conduct and the system-message addendum it splices live in the turn's own `metadata` dict, not on the hook: one hook instance serves every turn a process runs, and a system turn can overlap a user one on the same chain. **2. The conducts seat on the roles.** `MemoryModule.intake`, `PlanningModule.advise`, `ActionModule.review` and `ActionModule.salvage` each take this turn's conducts, so a replaced role decides what a plugin's judgement does rather than the seat deciding it. The composition rules live in `raven/agent/harness/conducts.py`: the first non-Accept verdict wins, advice joins in order, the first salvage wins, an intake threads the text through each conduct. `run_turn` binds the harness for the turn the way it binds the model, and the seat falls back to the same rules when nothing is bound, which is how a plugin test drives a hook directly. Contract papers: the conduct paper is factory-loop tier, so the contract-tier digest and `CONTRACTS_VERSION` do not move with it. `CONTRACTS_LINE_CEILING` 3,200 to 3,420, each bump with its own review paragraph in `tests/test_kernel_budget.py`. **3. What the review found.** Eleven threads, all resolved in the third commit. The ones that changed behaviour rather than prose: `CodeFlowHook` kept its `rolls_back_iterations = False` (inheriting `True` from the new base had it hold every reply behind the draft gate instead of streaming); `StepView` now publishes deep-frozen nested values, since freezing only the outer tuple left a conduct able to write through to `ctx.messages`; a turn's conducts are collected at the role boundary, so a replaced role sees one batch of the turn's judgements rather than one call per seat; an addendum is located by its own text rather than a saved offset, which two conducts appending system text used to shift out from under each other; `Planning` and `Action` gained binders, so a replacement role missing a new member is named at assembly rather than swallowed by the composite's `except Exception`. `CONTEXT.md` carries the Agent Conduct term and the Agent Hook entry no longer describes what this PR replaces. ## Type - [ ] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [x] Refactor - [ ] Other ## Verification Gates. CI runs these at the branch tip (17 pass, 1 skipped -- the skipped row is the disabled Claude Mention workflow). Locally, at each of the three commits: ``` uv run ruff check --no-cache raven evolver agents plugins-dist tests scripts uv run ruff format --check --no-cache raven evolver agents plugins-dist tests scripts uv run lint-imports uv run pytest -q npx commitlint --from origin/main --to HEAD --config commitlint.config.cjs uv run python scripts/check_commit_messages.py origin/main..HEAD uv run python scripts/check_source_language.py origin/main..HEAD uv run python scripts/check_large_files.py origin/main..HEAD ``` Import contracts: 10 kept, 0 broken at each of the three commits. The suite is green at each, and no commit adds a failure that `main` does not already show on this machine (this checkout has the heavy extras uninstalled, so the local run carries failures `main` carries too -- CI is the authority, and it is green). The loop's own e2e files, which the default selection leaves out, were run too (charter, playbook, truncation, direct chat, fallback chain, ACP stdio): no new failure. Behaviour preservation. The oracle for this change is the hook seam itself: all five plugins driven through every phase of a multi-iteration turn under the same scripted provider, recording each phase's decision and the observers it files. Outputs normalised for nonces and temp paths, then byte-compared. Re-run after the review fixes at the branch tip `81e6f423` (all three commits), against `main` at `c65c2059`: | scenario | bytes | | --- | --- | | five-plugin hook seam | 23,840 == 23,840 | | a whole turn with no playbook (host turn, spawned sub-agent, tool calls and refusals) | 12,821 == 12,821 | | six window-shrink scenarios | 133,371 == 133,371 | These cover the tip, not each commit separately. The seam comparison is the one that matters most for the third commit: its first fix changed `rolls_back_iterations` for code-flow, which decides whether replies stream or are held, and the seam oracle records the decision at every phase, so an unintended change there shows as a diff rather than as silence. A unit test proves the seat is a real decision point rather than a pass-through: with a lenient Action role bound, a conduct's Resample is not applied, and Planning's advice and Memory's intake are what the loop receives. Real model, Sonnet 4.5 through OpenRouter, at the branch tip: - playbook on, in-process lane: two workers, each receives the charter (3 checks), writes its file, finishes; 26 recorded model calls - playbook on, ACP fork lane: the charter prompt crosses the process boundary (the fork answers ZEBRA where the task text says PONG, so the brief reached the forked model through Memory), and a charter check blocks `write_file` to `notes/hi.txt` against a `pathPrefix: out/` rule - Raven-Research, a fork running the full gate chain: `plain-first: escalated_tool`, then `sufficiency-gate: released on the pages at iteration 4 after 2 searches / 2 pages` - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed ## Risk No user-visible behaviour change is intended, and the hook-seam comparison is byte-identical on every phase it covers. For plugin authors: the six-phase `AgentHook` is unchanged and a plugin shipping one keeps working. `ConductHook` is an adapter that seats a conduct in that same chain. For anyone replacing a harness role: the role protocols gain `intake`, `advise`, `review` and `salvage`, so a replacement written against the old protocol needs them. Rollback is a revert of the merge commit. Nothing persisted on disk changes shape. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A --------- Co-authored-by: yao pengfei <yaopengfei@shanda.com> Co-authored-by: Claude (claude-fable-5-1) <noreply@anthropic.com>
…res (#410) ## Summary Absorbs the first of two remaining commits from the research fork before it stopped iterating. This one lands instruments only: nothing here changes what a shipped run does, and the research flow label does not move because no model-visible text changed. `raven/agent/loop/dead_end.py` (new) asks whether a finished turn ended on the model's own prose. The test is structural, with a textual sub-label for the tail shape. Three decisions are worth review: - It lives under `raven/agent/loop/` rather than in the research plugin, because the loop is what would re-run a dead turn, and a host module cannot import a plugin without breaking `make lint-imports`. - The ask labeller is injected (`ask_kind`) rather than imported, because only the product that writes the harness asks knows how they are worded. With no labeller the failure mode is still recognised structurally and reported as `harness_ask_unknown`, so the boolean never depends on wording. - The fork marked its injected turns with a key of its own. This loop already had an equivalent in `_HOOK_INJECTED_KEY`, so the predicate reads that rather than adding a second marker for the same fact. The conditional rerun that reads the predicate arrived on this branch afterwards, in #418, along with the turn's wall clock. Both are supplied as data a hook writes onto the turn's metadata rather than as config the loop reads, so an agent whose hooks leave no budget runs exactly as it did before either existed: the rerun is unreachable for it and the clock is never consulted. The rerun starts from the original question rather than salvaging the failed attempt, because salvage was measured on 25 real duds to produce content 23 times and the right answer none of them, turning a detectable zero into a confident wrong answer. Three defects in that work were found by review and fixed on this branch, each with a reproduction that is red on the commit before it. The loop numbered episodes from an iteration count that restarts, so a rerun emitted indices [0, 1, 2, 0] and the TUI, which keys episode rows and their fold state by that index, had the rerun's first step collide with the first attempt's. Each attempt took its own clock reading, so a first attempt ending just under the limit handed the rerun a fresh full budget and a ten-second turn ran to eighteen. And the research hook opens its per-turn ledger at iteration 1, which a rerun reaches again, so the second open replaced the path the close reads and left the first attempt's file unreachable on disk. Reading that back afterwards turned up three more, none of them raised in review. The turn's end record carried the attempt's iteration count beside the turn's elapsed time with nothing saying which was which, so it gained ``attempt``. The hook contract described that record as "status and iteration count" while it carried five fields, which hid ``stopped_by`` from anyone writing a terminal gate against the contract. And the rerun's progress line named research in a loop that serves every agent, and called itself the first attempt when the budget allows more than one. A verbatim body sink (`RAVEN_VERBATIM_SINK`) records search and fetch results at three chokepoints. It is a second file rather than more columns on the ledger because a 250 KB row would leave no complete line in the 64 KB tail window `_resolve_session_seq` reads, which would silently reset the re-run split. Off unless the environment names a file, and a write failure is logged rather than raised. `DigestOutput` lets a digest return a write-only ledger annotation beside its text. The annotation is stripped from the tool result and merged into the fetch row, so a digest can never label its own output where the model can read the label. Nothing emits one yet; this is the pipe a later digest sidecar uses. `is_hard_tool_failure` was blind to the whole reader path. `web_fetch` never returns a bare string, it returns a JSON envelope, so a spent quota, a rejected URL and a failed validation all read as success and reset the streak `web_search` had accumulated. Transient markers are still tested first, so a 429 inside an envelope stays retryable. Forced finalize now books its own salvage spend. The phase fires on every run that produced no answer and burns up to `max_tokens` doing it, and it was counted nowhere. The Chinese refusal opener lives in `raven/i18n/zh_lexicon.py`, which is the exemption zone this repo keeps Chinese forms in, and the predicate imports it from there rather than carrying a literal outside the zone. Two defects found in the first review round are fixed on this head, both reproduced before being changed. The verbatim sink borrowed the ledger's `_resolve_session_seq`, which recovers the re-run split by reading the last 64 KB of the file and parsing the last complete line. That works for the ledger because its rows are small; this sink stores the large ones, so the first record wider than the window leaves nothing parseable and the sequence resets to 1, making two runs indistinguishable. Runs are now told apart by a tag minted in memory once per process and never recovered from disk, which removes the read a large row defeats. The argument for keeping this file separate from the ledger was already in its docstring; it applies to the sequence too, and now says so. `is_hard_tool_failure` started counting JSON envelopes while `failure_class` still read them as undifferentiated text. The streak key is `(tool, failure_class)` and the break threshold is two, so a blocked URL followed by a reader's HTTP refusal fired the stop-repeating nudge at a model that had changed both its cause and its approach. Both predicates now share one envelope parse, and an envelope is classified by its own `error` string rather than by a textual ladder that cannot see into one. Coarseness is for model prose, which varies without meaning anything; a tool writes its error from a small fixed vocabulary, so equal strings are the same failure and different ones are different. The parts that vary with the page travel in the envelope's other keys, so they cannot split a streak. The last commit removes an `isinstance` guard in the envelope reader that the diff-coverage report flagged as the only uncovered changed line. JSON has one shape that opens with a brace, so the branch was unreachable; removed rather than covered. ## Type - [ ] Fix - [x] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification Run at this head (95c975b), on `origin/main` with nothing to rebase onto. ``` uv run --frozen --python 3.12 --all-extras pytest -q tests/ 22132 passed, 43 skipped, 109 warnings in 388.67s ``` Integration and e2e are deselected by default (`-m "not integration and not e2e"`), so the run above does not include them. This change adds no integration surface. ``` make coverage make coverage-ratchet line current=88.66% baseline=87.30% delta=+1.36pp branch current=81.59% baseline=79.43% delta=+2.15pp Coverage ratchet passed with 0.05pp tolerance. make coverage-baseline-check Coverage baseline update is monotonic. COVERAGE_BASE_REF=origin/main make coverage-diff Diff coverage: 100.00% (126/126 executable changed lines) ``` Diff coverage reported 99.21% on the commit before last, with one uncovered changed line: the sentence the rerun says to the reader, which no test drove because none passed an `on_progress` into a retrying turn. The last commit pins it rather than waiving it. ``` make lint-python All checks passed! / 1946 files already formatted make lint-imports Contracts: 10 kept, 0 broken. make lint-deps Success! No dependency issues found. make check-source-language clean make check-large-files clean make check-commits clean ``` The kernel budget gate caught one thing worth naming: documenting the turn-end record in the hook contract put `raven/contracts` at 2808 lines against a ceiling of 2791. The per-field detail moved to the write site in `turn_path`, which is not budgeted, and the paper now names the fields and says where the rest is. `raven/contracts` is at 2788. Each of the three review fixes was proved against the commit it fixes rather than reasoned about: a detached worktree at that commit, the new tests copied in, red there and green here. They reproduce the reviewer's own numbers -- the episode list `[0, 1, 2, 0]`, the eighteen seconds, and the stranded ledger file. The strengthened comparison in `test_the_retry_starts_from_the_question_not_from_the_wreckage` is a tightened guarantee rather than a regression test and passes either way, which is why it is called out here. `agents/raven-research/.env` was moved aside for every suite run: it is gitignored and absent in CI, and two launcher tests that delete a key from the environment otherwise find one in the file. This machine has no search-provider key, so no research brief can actually be run here. The retrieval-path changes are verified by unit tests only, which is why the three chokepoints are pinned by tests that assert both the presence and the absence of a row rather than by an observed run. The same limit applies to the rerun: the clock and the dead-end trigger are driven by a deterministic clock in tests, never by a real turn. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed No user-facing surface changed, so there is nothing to document. The one operator-visible knob, `RAVEN_VERBATIM_SINK`, is an instrument that is off unless set and is documented in the module that reads it. ## Risk The `is_hard_tool_failure` change is the only unconditional one, and it reaches every agent rather than just research: any tool returning a JSON envelope with a non-empty top-level `error` now counts toward the tool-failure streak that triggers the change-approach nudge. That is the intended correction, and the direction is conservative because transient markers are still tested first. Reverting it is a single branch. Everything else is inert by default. The body sink writes nothing unless the environment names a file. `DigestOutput` has no emitter in this change, and a digest returning a plain string produces the same envelope it always did, which `test_a_bare_string_digest_still_works` pins. The dead-end predicate now has a caller, and it reaches only an agent whose hooks write a budget onto the turn's metadata. No agent on this branch does: `agents/raven-research` ships `wallClockSeconds` and inherits the rerun from its plugin's class default, and every other agent leaves the key unwritten and is bounded exactly as before. The turn's overrun is one budget plus at most one iteration, because the clock is read between iterations and never mid-generation: cancelling a call in flight would discard a finished generation and leave no answer at all. Rolling the rerun back is the budget key; rolling the clock back is the same. Rollback is the whole commit; nothing here is depended on by anything already on main. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A --------- Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com> Co-authored-by: KT <74288668+0xKT@users.noreply.github.com>
…es (#472) ## Summary After a conversation switches model from the page, the agent keeps introducing itself as the model it left. The switch itself works -- the session record, the request and the usage ledger all carry the new id -- but the system prompt's `You are running on model: ...` line was read from `agents.defaults.model`, so the agent was told it still runs on the configured default and repeated that back. From the page it is indistinguishable from a switch that never happened. The fix reads the id from the turn's active `ModelBinding` instead. The loop already opens `use_binding(binding)` around every turn (`raven/agent/loop/main.py`, around `_run_turn`), and that binding is the session's own `/model` pick when it has one (`binding_for_session` in `raven/agent/loop/wiring.py`), else the default. `render._resolved_model_id` now takes its model id from `active_binding()` when one is set and from config outside a turn, and runs either through the same storage-to-wire conversion (`providers.wire`) it always ran the default through. One source file changes; nothing is threaded through the assembly path. This is the approach the review suggested over the earlier revision of this PR, which carried the id down as a new contract field, and it is better on every point that revision was faulted for: - **Right spelling.** The earlier revision forwarded `session.metadata["model"]`, the storage spelling, and rendered it as-is; on Codex, Azure and every gateway install that is not the id the request carries. Here the bound id goes through `wire_model` exactly as the default does, so `openai-codex/gpt-5.1-codex` renders as `gpt-5.1-codex` and a gateway install keeps its `openrouter/` prefix, in both branches. The conversion stays on the one path that owns it, and a lazily built provider's identity `wire_model_id` is not relied on. - **The estimation path agrees by construction.** `ContextBuilder._get_identity` (`raven/agent/context/builder.py`) sizes the system prompt for the token budget and calls the same renderer with no arguments; it is invoked from `_assemble_context_messages`, inside the binding, so it now names the same model the assembled prompt does. A field handed down the assembly path reached the segment and not this caller. - **No contract change.** `AssemblyContext` and `TurnContext` are untouched, so there is no field position to argue about, no producer (the curator's `TurnContext` rebuild) that can forget to copy it, and `CONTRACTS_VERSION` stays where `main` has it. - **`stable = True` stays true.** A binding is per session, constant within it; the line changes once per switch, which is what a switch is. A router's per-message pick is not reflected in the line (the router is opt-in and off by default); that is the one case a contract field would have covered, and it is also the case that made the segment's stability claim false. - **No empty-string hole.** `ModelBinding` refuses an empty model id at construction, so the line cannot be deleted by a blank pick. A fallback hop further down a model chain still reads a prompt naming the primary: context is assembled once, before the call. Not changed here and not a regression -- the sibling `can_see_images` field documents the same caveat. Nothing under `ui-web/` or `ui-tui/` changes. ## Type - [x] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification Five new tests in `tests/test_segments.py::TestIdentityNamesTheBoundModel`: - `test_a_switched_conversation_is_told_the_model_it_is_bound_to` -- inside `use_binding` on a gateway config, `_resolved_model_id()` names the bound model with the gateway prefix and never the configured default. - `test_the_bound_id_goes_through_the_same_storage_to_wire_conversion` -- a codex-form id bound to the turn renders as the wire spelling the client sends, not the stored one; this is the assertion the earlier revision could not fail. - `test_outside_a_turn_the_configured_default_still_answers` -- no binding, config default, as before. - `test_the_segment_names_the_bound_model` -- the whole segment, through `IdentitySegmentBuilder.build`. - `test_the_estimation_prompt_and_the_turn_prompt_agree_on_a_switched_model` -- `ContextBuilder._get_identity()` equals the segment's text inside a binding and names the switched model. Commands and results: - Mutation-checked: replacing the binding read with the config read fails exactly the four tests that bind a model (4 failed, 46 passed in `tests/test_segments.py`); the outside-a-turn test and the three pre-existing wire-form tests stay green. - `.venv/bin/python -m pytest` over every test file that names `identity_text`, `_resolved_model_id`, `IdentitySegmentBuilder` or `ContextBuilder(`, plus `tests/test_contracts_two_tier_ledger.py`, `tests/test_kernel_budget.py` and `tests/test_context_stable_prefix.py` (11 files): 375 passed. - `python scripts/coverage_gate.py diff --base-ref upstream/main --threshold 90` after those suites ran under `--cov=raven --cov-branch`: 100% (3/3 executable changed lines). - `.venv/bin/ruff check`, `.venv/bin/ruff format --check` and `.venv/bin/ty check` on `raven/context_engine/segments/render.py`: clean. - The contracts gates pass unchanged: this revision touches nothing under `raven/contracts/`. - No user-facing docs change: the prompt line already documents itself as naming the model the turn runs on. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed ## Risk - Security: none. The binding's model id is already in every request and in the usage ledger; nothing new is read or sent. - Backward compatibility: outside a turn (no binding) the renderer behaves exactly as before. Inside a turn the line names the bound model instead of the configured default; for a conversation that never switched they are the same id, so the rendered prompt is byte-identical there. No wire, config or contract change. - User-visible change: after a `/model` switch the agent names the switched model in its identity line from the next turn on. The identity prefix changes once per switch, so a prompt-cache prefix keyed on it is invalidated once per switch, not per turn. - Rollback: revert the squash commit; one file, nothing else to reconcile. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues Fixes #470 Co-authored-by: Claude (claude-fable-5-1) <noreply@anthropic.com>
## Summary Update the hosted AI4AI (Nanochat 50M Pretraining) chart reference in both bilingual READMEs. - Replace the old image attachment URL in README.md and README.zh-CN.md. - Use the updated chart with the Bits Per Byte (BPB) label and the Lower Is Better annotation above the BPB values. - Keep the chart hosted as a GitHub attachment; no image binary is added to the repository. ## Type - [ ] Fix - [ ] Feature - [x] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [x] User-facing docs or screenshots are updated when needed Commands: - `git diff --check` - `UV_NO_SYNC=1 make check-large-files check-source-language PYTHON_VERSION=.venv/bin/python COMMIT_RANGE=HEAD` - `UV_NO_SYNC=1 uv run --no-sync pytest tests/test_readme_scope_canon.py tests/test_cli_onboard_commands.py -k readme_quickstart -q` - Downloaded attachment SHA-256 matches the local PNG. ## Risk - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes This is a documentation-only URL update. Reverting the commit restores the previous chart. ## Related Issues #465 Co-authored-by: Claude (claude-opus-5[1m]) <noreply@anthropic.com>
…ber like the fleet (#493) ## What Two defects in the shipped `agents/raven-code` profile, both inherited from the retired vendored twin's comparison hygiene and both silent at runtime. **The skill route was advertised but dead.** Trunk's skills are pull-discovery: a name+description menu rides the user envelope, and its footer tells the model, verbatim, *"Read one with read_skill(id); search differently with find_skill"* (`raven/context_engine/scent.py`). raven-code kept `read_skill`, `use_skill` and `find_skill` in its disable rows, so the menu taught a route that answered "unknown tool" -- while the router still ran, retrieval still happened, and every body it fetched was dropped unread, with nothing logged to say so. All three tools register off the skill registry alone (`raven/agent/loop/wiring.py`); a Hub endpoint only adds their `hub/` branch, and `hub` itself stays disabled. **Memory was double-off.** design, oncall, ppt and research all ship `memory.backend: "everos"`; raven-code alone shipped backend `null` **plus** the `everos-memory` plugin opt-out -- the one shipped agent that could not remember. The identity was already `raven-code` in all three places (`memory.userId/agentId`, the plugin slice, `subagent.json`); only the two off-switches go. The redundant `skillForge` block goes too: its two knobs (`rewriteEnabled`/`llmGateEnabled`) are only built under push discovery (`factory.py`), which no shipped agent runs, and the other four agents carry no such block. **Review follow-up (second commit):** enabling everos-memory also offers the plugin's `understand_media` tool through the entry-point lane, which the hermetic face fixture cannot see -- an unledgered 14th tool in production. Same remedy as the playbook rows that board past the fixture the same way: the disable row is the pin, and `PLUGIN_LANE_WITHHELD` puts the lane on the ledger. raven-research holds the same line; design, oncall and ppt serve the tool deliberately. ## Fleet posture after this change | | everos | read/use/find_skill | skillForge block | |---|---|---|---| | design / oncall / ppt / research | on | design serves all three | none | | **raven-code (before)** | **double-off** | **all three disabled** | explicit false/false | | **raven-code (after)** | on | all three served | none | ## Tests The tool face moves on its ledger, not by luck (`tests/test_agents_code_launcher.py`): `SKILL_LANE_TOOLS` joins the face arithmetic, `find_skill` leaves `TRUNK_NEW_WITHHELD` (five stay withheld), `PLUGIN_LANE_WITHHELD` pins the plugin lane, and the everos posture test is rewritten from *stays-factory-off* to *ships-on*. The face itself is hermetically rebuilt from the render: 13 tools = the previous 10 + the three skill tools, verified by name and schema. - `tests/test_agents_code_launcher.py`: 70 passed - reviewer's set (`test_agents_code_launcher` + `test_agent_loop_skill_tools` + `test_context_scent` + `test_everos_plugin_discovery`): 110 passed - ruff check + format: clean --------- Co-authored-by: litong <238663200+TongLi31@users.noreply.github.com>
## Summary
Add `.github/CODEOWNERS` with a single entry mapping `/raven/agent/` to
`@LivXue`, so a
pull request that changes a file under that directory automatically gets
a review request
from the directory's owner.
This only requests a review. The branch ruleset keeps
`require_code_owner_review` off, so
no approval requirement changes and no contributor's merge path is
affected. Making it a
merge gate would be a separate ruleset change, deliberately not made
here.
Two limits are inherent to the mechanism rather than gaps to close
later:
- GitHub never requests a review from the author of a pull request, so
one opened by the
owner does not assign them. The API answers that case with HTTP 422.
- A draft pull request gets no automatic request. It is sent when the
draft is marked
ready for review.
CODEOWNERS is read from the base branch, so this governs pull requests
opened after it
lands and does not apply retroactively. Ten of the sixteen pull requests
open at the time
of writing touch `raven/agent/`. Those were assigned manually, apart
from the one the
owner authored, which cannot take the assignment for the reason given
above.
## Type
- [ ] Fix
- [ ] Feature
- [ ] Docs
- [ ] CI / tooling
- [ ] Refactor
- [x] Other
## Verification
Gates run in a clean worktree branched from `origin/main`, each exit
code read rather than
inferred from empty output:
```
make check-large-files exit 0
make check-source-language exit 0
make check-commits exit 0
```
Behaviour checked against GitHub rather than by reading the file. The
CODEOWNERS validator
on the pushed branch returns no errors, which is what proves both that
the pattern parses
and that the named owner resolves to an account holding write access:
```
gh api "repos/EverMind-AI/Raven/codeowners/errors?ref=..."
{"errors":[]}
```
Not run, and why: `make lint` and `make test-python` analyse Python,
TypeScript and
JavaScript, and this diff adds one ten-line text file containing no
code, so neither
measures anything about the change. No test in the repository exercises
CODEOWNERS
resolution, which is why the validator above is cited instead of a test.
Separately
confirmed that nothing reads the directory this file lands in as a set:
of the three tests
mentioning `.github/`, one validates backticked documentation paths
carrying a known file
extension, one names a single workflow file, and one globs
`.github/workflows` only.
- [ ] Relevant tests pass locally
- [ ] Relevant lint / type checks pass locally
- [ ] User-facing docs or screenshots are updated when needed
## Risk
Nothing changes for any contributor: an added review request does not
gate a merge, and
CODEOWNERS confers no permission of its own. The owner handle named here
is already public
on every commit and pull request in this repository, so the file
discloses nothing new.
Rollback is deleting the file, or the one entry in it. Nothing depends
on it.
- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes
## Related Issues
N/A
Co-authored-by: Claude (claude-opus-5[1m]) <noreply@anthropic.com>
## Summary Replace the first image in README.md and README.zh-CN.md with the updated Raven banner, "One raven, a whole flock of specialists." Both versions use the same GitHub-hosted attachment. ## Type - [ ] Fix - [ ] Feature - [x] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [x] User-facing docs or screenshots are updated when needed - `uv run --frozen --extra dev pytest tests/test_readme_scope_canon.py -x -q`: 2 passed. - `uv run --frozen --extra dev pre-commit run --files README.md README.zh-CN.md`: all applicable hooks passed. - `git diff --check origin/main...HEAD`: passed. - `make check-commits check-large-files check-source-language`: passed. GitHub confirmed the attachment upload. Download timeouts prevented a complete comparison with the supplied PNG. Browser rendering at desktop and mobile widths has not been checked. ## Risk - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes This changes the README banner only. The image is hosted as a GitHub attachment. Reverting the two URL edits restores the previous banner. ## Related Issues Fixes #126 Co-authored-by: Codex <noreply@openai.com>
#507) ## Summary Stands up a bilingual MkDocs site under `docs-site/`, published to GitHub Pages by a workflow that builds on every pull request and deploys only from `main`. Fourteen topics ship in English and Chinese: the quick start, self-hosting, Docker, the WebUI, the sandbox manual, the runtime architecture, the command reference, the repository layout, the developer workflow, the proactivity design and reference pair, the self-evolution map and the tracing API. This pull request is additive. Both READMEs keep their prose and gain one link to the site in the header row; trimming them is deliberately a second pull request, so the site can be confirmed live before anything depends on it. Key decisions: - **`docs-site/`, not `docs/`.** `docs/README.md` defines `docs/` as design notes and dated records, and `docs/plans/` is an archive explicitly not held to today's layout. A user manual has the opposite lifecycle. - **Full bilingual parity**, via the `mkdocs-static-i18n` suffix structure (`page.md` beside `page.zh.md`). Parity decays immediately without a guard, so a test fails when a page has no twin. - **Anchors are pinned on the Chinese side.** A CJK heading slugifies to nothing, so Markdown numbers it `_1`, `_2`, and inserting one heading renumbers every anchor below it, silently breaking links readers already hold. Each Chinese heading therefore states the anchor its English twin gets for free: 133 heading anchors, identical across the two languages, none positional. - **A left rail, not a top bar.** The header carries the brand, the search box and the navigation tree down the left; the table of contents on the right is one continuous polyline clipped to the headings currently on screen. - **No template overrides.** Material customises through `theme.custom_dir` with Jinja partials, those partials are `.html`, and `scripts/check_large_files.py` holds `.html` in `BLOCKED_ASSET_EXTENSIONS`. The skin is therefore one stylesheet remapping Material's own custom properties, plus one script for the table-of-contents rail. The brand mark is a `data:` URI for the same reason. - **An owner-signed `docs-site/` exemption zone** in AGENTS.md section 1.3. The i18n config carries the Chinese navigation labels, and `mkdocs.yml` is not `*.md`, so the blanket suffix exemption does not reach it. A contract test holds the AGENTS.md list and `EXEMPT_PREFIXES` equal, so both moved together. - **The site never links out to a file it does not host.** Documents that stay in the repository are named as plain paths rather than linked, and a guard fails a page that links to an external webpage or a repository-relative path. Two defects found and closed here rather than after publication. The Chinese site rendered an English sidebar: navigation labels live in `mkdocs.yml`, not in any page, so comparing the two languages' Markdown finds nothing and only the rendered output shows it. And a control could be present, sized, animated and answer `querySelector` while being unclickable, because a popup Material sized for a top bar landed outside the viewport once the header became a rail. ## Type - [ ] Fix - [x] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification All commands run at the pushed head, rebased onto `origin/main`. ``` make check-large-files exit 0 make check-source-language exit 0 make docs-build exit 0, 0 errors docs-site/site/index.html and docs-site/site/zh/index.html both present mkdocs_static_i18n: Translated 17 navigation elements to zh uv run pytest tests/test_docs_site.py tests/test_source_language_check.py \ tests/test_readme_scope_canon.py tests/test_docker_runtime.py \ tests/test_living_docs.py 44 passed uv run pytest tests/integration/test_docs_site_controls_e2e.py 13 passed pre-commit run --files <changed files> all hooks pass git diff --check clean ``` Anchor parity was measured on the built HTML rather than assumed: 14 page pairs, 133 heading anchors compared at h2 through h6, every one identical between the two languages and none positional. The controls are hit-tested rather than queried. `document.elementFromPoint` at the exact point a reader clicks must return the control itself, which is the only check that catches an off-viewport popup; the language switcher, the search panel, the repository link and the table-of-contents rail each pass it. The brand lockup is measured, not eyeballed. A failing test was written first and reported the wordmark sitting 4.00px below the mark's centre line; it is 0.00px after the fix, and a 4x screenshot puts the residual optical offset at 0.25px. Every guard this branch adds was confirmed to fail first. The brand-class guard was mutated from both sides in turn: renaming `.em-eyebrow` in the stylesheet turned it red, renaming `class="em-standfirst"` in `index.md` turned it red, and it is green again with both restored. The anchor guard was shown to catch both failure modes, a missing anchor and a wrong one. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [x] User-facing docs or screenshots are updated when needed ## Risk No behaviour change to any shipped code path. Nothing imports `docs-site/`, the new Makefile targets are additive, and the only change outside `docs-site/` and its tests is one link added to each README's header row. Rollback is deleting the branch: the site does not exist until Pages is enabled with `Source: GitHub Actions`, and every other change is inert without it. Disclosed residue, checked and deliberately not changed: - The README header now links to `https://evermind-ai.github.io/Raven/`, which 404s until Pages is enabled. It is the same URL the Documentation section already carries. - The `deploy` job is skipped on pull requests by design, so a green check here does not prove deployment works. The first real evidence is the `deploy` job's own conclusion on the `main` run after merge. - GitHub Pages is not enabled on this repository. Measured: `GET /repos/.../pages` returns 404. Under `Deploy from a branch` the uploaded artifact is silently ignored while the workflow still reports success, so the source must be set to `GitHub Actions`. - `tests/integration/` is excluded by `norecursedirs`, so the browser suite is a local guard and never runs in CI. - The self-hosting page's Makefile target, docker path and port claims are unguarded. They were equally unguarded in the README they came from, and the owner ruled against adding a guard for them. - The self-evolution SOP and the benchmark contract stay in the repository and are named as plain paths from the pages that reference them. The SOP is a translation of an upstream project's internal document and carries redaction placeholders, which is a publication decision rather than a formatting one. - `docs/plans/` still shows the pre-restyle `mkdocs.yml`. CONTEXT.md defines that directory as an archive of the tree as it stood, with an explicit rule against updating an old plan to match a later change. - The Chinese pages drop the brand's italic serif accent word. Cormorant Garamond carries no CJK glyphs, so it would fall back to another face without any sign. - The code font is Material's default Roboto Mono. The brand names four typefaces and no monospace, so none was invented for it. ## Related Issues N/A --------- Co-authored-by: Claude (claude-opus-5[1m]) <noreply@anthropic.com>
… states (#503) ## Summary The overview says the four built-in agents are "Powered by the **Raven Evolver** engine". The architecture section of the same file says `evolver/` is a separate tool that consumes Raven as a library and that the runtime does not import Evolver, and `pyproject.toml` enforces that with an import-linter contract literally named "the runtime does not import the evolver". A reader gets two incompatible answers to whether Evolver is a runtime engine or an external development tool, and the contract settles which one is true. This was gloryfromca's blocking review finding on #486. The thread was resolved when that PR merged, but the text was never changed, so the contradiction is live on `main` today in both language mirrors. Both mirrors now put Evolver where it actually runs: refining the shared harness from outside, against benchmarks, developing the agents rather than running inside them. That is the reading the review offered for the case where the intended claim was that Evolver was used to develop the agents. The overview also takes the canonical spelling of harness self-evolution from the architecture section, as the review asked. Scope note: only the Evolver relationship changes. The SOTA sentence in the same paragraph is untouched - it was not part of the finding, and what it should say is a product call rather than a consistency fix. ## Type - [ ] Fix - [ ] Feature - [x] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification Prose only: two lines, one per language mirror, no code and no new files. - `git diff --stat` -> `README.md | 2 +-`, `README.zh-CN.md | 2 +-` - The two gates that apply here are satisfied by construction rather than by a command: `check-source-language` exempts `*.md` by suffix (AGENTS.md 1.3), and `check-large-files` sees no added or resized file. - Read back against the two places the claim has to agree with: the Evolver row of the agent table, and the harness self-evolution bullet in the architecture section. Both mirrors now match both. - [ ] Relevant tests pass locally - [ ] Relevant lint / type checks pass locally - [x] User-facing docs or screenshots are updated when needed ## Risk Documentation wording only. No runtime behaviour, no interface, no build. Rollback is a plain revert. The claim removed was the one the import-linter contract forbids being true, so nothing downstream depends on it. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A. Lands the finding from #486 that was resolved without a change. Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
) ## Summary `failure_class` keys the loop's tool-failure streak on an envelope's `error` string alone. That works only while the string comes from the small fixed vocabulary its producers are meant to keep, and it is a convention the module cannot enforce - the docstring added in #410 names exactly this failure mode: one cause counting as many. Every media tool broke it. `_format_http_error` interpolated up to 400 characters of vendor body (which carries a request id) into `error`; the unreadable-reference handlers interpolated the path; the four catch-alls put `str(e)` there, which spells the host. So a dead endpoint produced a class per call, `(tool, failure_class)` never repeated, the streak never reached `_LOOP_BREAK_THRESHOLD`, and the stop-repeating nudge was unreachable for `image_generate`, `text_to_speech` and `video_generate` alike. The URL gate in front of `web_fetch` had it too, in both copies of the tool: most reasons `validate_url_target` composes name the address they refused, so a model walking an internal range got one class per address. #410 fixed the handlers behind that gate and left the gate itself. `error` now carries only what a reader could enumerate. The variable part moves to `detail`, which keeps it in front of the model and inside `is_hard_tool_failure`'s transient-marker scan. Where the split gave the single call a further key, the batch reducer forwards that too: `hint`, which names the proxy a 403 can be routed through, and `url`, which names the reference a guarded fetch refused. A batched picture is told what the single call is told, which is the property the split has to preserve. Distinct causes stay distinct, which is the failure mode this trades against: two HTTP statuses, two exception types and the four vendor refusals each keep their own class, and the tests assert that direction too. Found by sweeping for the shape rather than by reading the code: every `"error":` whose value is not a literal is a candidate, and that grep is the cheapest guard against this recurring. ## Type - [x] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification - `uv run pytest tests/ -q -p no:randomly` -> 23443 passed, 109 skipped - `make lint-python` -> ruff check and format clean - `make lint-types` -> all checks passed - `make lint-imports` -> 10 contracts kept, 0 broken - `make check-source-language`, `make check-large-files` -> clean - Revert-to-red, per fix, restoring the source from a backup afterwards: reverting `media_gen.py` alone turns the four new class tests red and leaves `test_two_different_statuses_stay_two_classes` green; reverting either copy of `web.py` turns only that copy's gate test red and leaves the two #410 streak tests green. For the batch fold, each key is pinned on its own: reverting it to `detail` alone turns both new batch tests red, restoring `hint` alone leaves the reference test red, restoring `url` alone leaves the 403 test red. - After rebase onto 45203a4: `uv run pytest tests/test_media_gen_tool.py tests/test_security_web_ssrf.py -q -p no:randomly` -> 100 passed; `make lint-python`, `make lint-types`, `make check-source-language`, `make check-large-files` -> clean. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed ## Risk Tool output shape changes: a caller reading the reason out of `error` now finds it in `detail`. The readers in this repo are the model itself and the tests updated here; `tests/test_security_web_ssrf.py` is the one behavioural assertion that moved, and it still pins that a private address is refused for being private. Rollback is a plain revert of this commit - no state, no migration, no config. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A. Finishes the envelope work started in #410, which fixed the reader's handlers and left the gate in front of them and every media tool. --------- Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
…its (#490) ## Summary `raven gateway` exited with SIGSEGV (-11, or 139 in a shell) on every SIGTERM, so a systemd or docker stop recorded a crash instead of the clean stop it was. `raven tui` and `raven agent` had the same defect. Root cause: `LocalSkillCatalog` auto-starts `SkillFileWatcher`, a daemon thread that parks inside the Rust `watch()` of watchfiles. Daemon status does not make process exit safe while the thread sits in native code -- CPython runs `Py_FinalizeEx` under that call. Every Python shutdown step succeeds first, so the crash lands after the graceful chain is complete and the real exit code is masked. With faulthandler armed all three faults report `<no Python frame>`, i.e. no Python code is on the stack. Every owner now retires it: - the gateway's shutdown chain, for the generation still bound at exit; - the TUI's teardown; - the one-shot agent's teardown; - `RavenRuntime.dispose`, for a generation retired at a swap. `build_runtime` mints a fresh `AgentLoop` per generation, so each swap starts a new watcher and abandons the previous one; without this a reloaded gateway still segfaults on stop. The generation-organ roster grows the same entry, so the two halves of that contract stay in step; - the gateway's shutdown again, for a candidate staged by a reload that the serving loop never consumed. That generation is not the bound `agent`, so nothing else reaches it; - the gateway's shutdown once more, for a candidate `take()` handed out whose binding a cancelled unbind never finished. `_serve_generations` holds that candidate in a local across `await _unbind_generation()`, so a shutdown cancelling the await unwinds the coroutine and drops the local, leaving the generation staged nowhere and bound nowhere. `SwapCoordinator` now keeps it: `take()` records the candidate it hands out and `release()` clears it, which spans exactly handover to the moment the caller's own binding points at the new loop. The serving loop needed no new line, because it already calls both ends of that span. The replay runner already stopped the same watcher for the same reason; these were the other owners. The stop sits in the generation contract and in each command's teardown rather than in `AgentLoop.stop`, whose other caller is the generation swap -- that path keeps the process running and must keep skill auto-refresh with it. The gateway's sweep now lives in a module-level `_retire_generation_watchers` rather than inline in the serve closure. `run()` is a 550-line closure with no import seam, so the inline form could only be pinned by reading its source text -- which never executes it, and so earned no coverage on the lines that matter. The helper drains all three seats and keeps going when one stop raises, since a sweep that stops early still leaves a watcher parked in native code. Two comments describing a removed guard go with it: one in `agent_commands` pointing at an exit chokepoint in `raven.cli.commands.run`, and the test-side scaffolding that patched `os._exit` for an exit the command no longer makes. ## Type - [x] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification Process-level, faulthandler armed, throwaway home and a dummy key nothing calls: ``` before after raven gateway SIGTERM -11 0 (-11 on 5 of 5 runs) raven gateway SIGHUP then SIGTERM -11 0 (2 reloads after) raven tui PTY, then signal -11 0 raven agent ordinary one-shot exit 139 1 (1 is the turn's own auth failure, which 139 had been masking) ``` Controlled pair isolating the cause, on the loop factory the TUI uses -- build the loop, let the interpreter finalize: ``` watcher left running exit 139 stop_file_watcher() exit 0 ``` Suites, from the branch tree: ``` uv run pytest tests/integration/test_gateway_sigterm_smoke.py -q -n 0 -m integration 1 passed (this is the guard test that was failing) pytest tests/test_cli_agent_loop_parity.py tests/test_cli_gateway_commands.py \ tests/test_cli_gateway_health.py tests/test_cli_tui_commands.py \ tests/test_cli_agent_commands.py tests/test_gateway_spine.py \ tests/test_generation_swap.py tests/test_plugin_services_lifecycle.py \ tests/test_rpc_control.py tests/test_rpc_system.py \ tests/test_skill_watcher_cleanup.py tests/test_trajectory_replay.py \ tests/test_utils_asyncio_runner.py -q 307 passed, exit 0 ruff check / ruff format --check over the changed files clean ty check over the changed files no diagnostics on the changed lines scripts/check_large_files.py origin/main..HEAD exit 0 scripts/check_source_language.py origin/main..HEAD exit 0 commitlint --from origin/main exit 0 ``` Second round, after review found the in-transition window; rebased onto `origin/main` at 6d8c363: ``` pytest tests/test_cli_gateway_commands.py tests/test_core_runtime_swap.py \ tests/test_generation_swap.py tests/test_cli_tui_commands.py \ tests/test_cli_agent_commands.py -q 171 passed, exit 0 every test file importing raven.core.runtime 131 passed, exit 0 real gateway: boot, SIGTERM, exit code 0 on 3 of 3, no fault text ruff check / ruff format --check clean make check-source-language / make check-large-files exit 0 coverage_gate.py diff --threshold 90 95.83% (23/24), passed ``` Revert check on that round: removing the `_in_transition` record, the in-transition seat in the sweep, or the sweep's `try/except` each fails its own test and nothing else. The cancellation window itself is reproduced against real `SkillFileWatcher` threads, counted by thread name, and is red without the fix. Diff coverage on changed lines was 50% (4 of 8) before that round, because the gateway-side tests read the chain's source instead of running it. The one line still uncovered is the call site inside `run()`, which cannot execute without booting a real gateway; the wiring to the seam is pinned by source inspection. Each of the four regression tests was confirmed red before its fix. The TUI and agent stops were additionally re-confirmed afterwards by removing both again and watching those two tests go red, then restoring them. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed ## Risk User-visible change: these three commands now exit with their real status instead of a segfault. Anything reading the exit code sees a different value -- 0 where it used to see 139, and for a failed one-shot the turn's own non-zero code rather than 139. Rollback: revert the commits. The watcher is a cache-invalidation helper, so removing the stops restores the previous behaviour exactly, crash included. Checked and deliberately not changed: - The stops are not wrapped in try/except. `ContextBuilder.__init__` assigns `skills` unconditionally, `stop_file_watcher` is documented safe when no watcher was started, and the watcher's own contract is that neither `start` nor `stop` raises. This matches the existing production caller in the replay runner, which is also unwrapped. - `raven acp` reaches finalization the same way but calls `os._exit(0)` after a real session, so the fault is masked there rather than fixed. Left as is; making it consistent is a separate change. - No central gate was reinstated. A future command that builds a loop and exits normally would reintroduce this, and the source-inspection tests here only guard the retirement seats that exist today. - The watcher starts in `ContextBuilder.__init__`, which is construction-time work, and `RavenRuntime.discard` is contract-bound to stay call-free because a call there would mean an organ was started before FREEZE. So a staged or in-transition candidate is retired by the gateway's sweep rather than by `discard`, and the docstring records why. Moving the watcher out of construction would let `discard` own it; that is a separate change. - The shutdown-before-consumption window was not reproduced by timing: three runs sending SIGTERM 0.2s, 1.0s and 2.0s after a reload all completed the swap first and exited 0. The staged-candidate retirement is driven by forcing the state in a test rather than by racing for it. Known gap in the evidence: the two `raven tui` runs were ended with SIGTERM after Ctrl+C went unanswered, because the TUI never finished loading against a throwaway home. The ordinary-exit case is evidenced by the agent one-shot, which exits by itself with no signal involved. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A --------- Co-authored-by: Claude (claude-opus-5[1m]) <noreply@anthropic.com>
…ked again (#467) ## Summary Two regressions in the hand-built deck lanes (medium and high on Raven-Design), and the stream failure that made every run of them a coin toss. **The deck lane draws its own pages again.** PR #451 pointed the hand-built lane at the engine's helper modules (`ppt_layout`, `ppt_theme`, `ppt_shapes`), which were written for the max lane's script backend. Their defaults became the deck: `heading()` put a white surface band bled across every page, `card()` became the way anything was placed, text frames were top-anchored with the lower third empty, and a build script named three colours. Measured on the same request, the accent colour appeared 8 times across 20 pages. The skill, route note and assets reference go back to the pre-#451 text: the lane draws with python-pptx and takes only the icon set, `add_formula` and `math_runs` from the engine. On the reverted lane the accent appears 54 times, the deck carries 12 real pictures and the layouts differ page to page. **Colour is named beside the type sizes.** The skill listed sizes for every role and said nothing about colour, so a lane with the right palette still drew in one colour. One paragraph now says what colour is for; the layouts note no longer reads as one accent per page, which a lane took as a ceiling. Where `image_search` is not offered, the fallback no longer tells the lane to give the pictures up: `web_search` for the page, `web_fetch` it, and download where the fetch backend keeps image links. **A stall in a silent think is asked again.** Across two measured runs the lane's turn failed 28 times on `Upstream idle timeout exceeded`, every time in the one round that writes the whole build script into a tool argument. `stream_llm_call` refuses to retry after output a watcher has seen, and counted reasoning deltas and half-built tool-call fragments as that output, though neither is ever shown as the reply. So the configured retry ladder was never reached. `rendered()` now asks what a watcher could have seen; every retry path resets the buffers first (a slot left standing merged with the next attempt into a call the model never made); a stall handed back as an error response carries the usage it spent. The in-process builtin backend, whose comment claimed a retry ladder and passed none, now receives the deployment's `llmErrorRetryDelays` and `llmRetryAfterOutput` through the SubagentManager. No packaged product takes that path; all five run as ACP agents on the loop's own call site. **Two bundled templates edited in place**, by the maintainer's word. `beige_geometric_general_report` loses its page 20, a dark photograph cut by a circle under pale rounded cards; 23 pages become 22 and the two reference pages the lane borrows from it (17, 19) keep their numbers. `black_circuit_tech_launch` loses the 27 percent circuit texture on its master and the photograph on its Section Header layout, so its body and section pages sit on the master's plain `#000000`; cover and closing are unchanged. `templates.manifest.json` is repinned; `fetch_templates.py --verify` holds all ten pins. The registry archive was not re-cut. No configuration default changes. The only behaviour every lane sees: a stream that fails before any reply text reached a watcher is retried on the configured ladder instead of failing the turn. ## Type - [x] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification ``` uv run --frozen pytest tests/test_agent_loop_llm_error_patience.py tests/test_agent_loop_stream.py \ tests/test_subagent_manager.py tests/test_subagent_routing_backend.py -q 265 passed in 6.85s (after rebase onto main) uv run --frozen pytest tests/test_litellm_provider_stream.py tests/test_litellm_provider_stream_end.py \ tests/test_provider_stream_fallback.py tests/test_providers_stream_idle_budget.py -q 102 passed uv run --frozen ruff check raven/agent/loop/main.py raven/agent/subagent/backends/raven_loop.py \ raven/agent/subagent/manager.py raven/providers/streaming.py All checks passed! grep -nP "[^\x00-\x7F]" agents/raven-design/route-raven-ppt.md -> no matches (the note is pinned ASCII) ``` Both new tests were checked against the unfixed code: `test_a_stall_during_a_silent_think_is_asked_again` fails with the old `emitted` test, and `test_what_the_failed_attempt_left_behind_is_dropped` fails with the buffer resets removed. The failure itself was reproduced outside the runtime: the same 37-message history, six pictures and tool table, sent through litellm 1.101.0 to the same vendor, succeeds on a short answer every time and fails mid-stream one time in three when asked to write the full build script; healthy long streams show a maximum inter-chunk gap of 1.9s over 234s, so the cut is a dropped connection, not a slow model. Decks built on the changed lane, same request as the #451 acceptance run (glm-5.3-flash via OpenRouter): medium 21 pages with 12 pictures and 7 text colours, 48 gate findings (4 `excessive_whitespace`, down from 18); high 13 pages with 13 pictures and 9 text colours, 37 findings. The high run's page count came from the host agent's forwarded brief, which stated 10-14 pages the user never asked for; that is a separate issue. Template edits: `tests/test_ppt_engine_templates.py tests/test_ppt_engine_default_templates.py tests/test_ppt_engine_template_bands.py tests/test_ppt_engine_template_theme.py tests/test_ppt_engine_measure_geometry.py` -> 152 passed, 1 skipped; `make check-large-files` -> clean; both templates rendered and read page by page. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed ## Risk - [ ] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes User-visible: on every lane, a streamed call that fails during reasoning or mid tool-call, with no reply text yet shown, waits out the configured ladder (default 15/30/60s) and asks again instead of ending the turn. A call that already streamed reply text behaves as before. Reasoning tokens of the abandoned attempt are spent again and are now recorded in usage. Rollback: revert the `providers` commit alone to restore the old failure; the skill text and the builtin-backend plumbing are independent of it. ## Related Issues N/A
## Summary Space on an attempt row in the `raven trajectory` browser previously showed two truncated per-turn preview lines. This PR replaces it with an interactive full-conversation viewer. It re-lands the content of PR #382, which was auto-closed by the 2026-09-12 history linearization without being merged; the branch is rebased onto the linearized main, with one extra commit expressing the CJK display-width test fixtures as unicode escapes to satisfy the source-language gate that main gained since. - Data layer `raven/trajectory/conversation.py`: rebuilds an attempt's span snapshot into a causally ordered stream of labeled records (User input, LLM input/output, Tool input/output, skill, memory, and subagent events). Event-time semantics (inputs at span start, outputs at span end) plus nesting depth from parentSpanId keep the causal order. Full content comes from artifact files (paths restricted to the trace store, streamed 512 KiB cap per file); span previews are only a visible fallback. ERROR spans and expected-but-unreadable payloads always yield a placeholder record. LLM inputs omit only the item-by-item verified common message prefix against the previous call (omissions and rewrites are announced); tool_calls of unexecuted tools keep their full arguments. - Rendering: kind-colored labels aligned to one column, hanging-indent bodies with word-aware CJK-safe wrapping, degraded/error/meta note lines, single-pass turn separators (interleaved traces are never regrouped), and a stacked layout on narrow terminals that never drops characters. - Interactive viewer (prompt_toolkit full-screen, entered for every preview on a terminal): scrolling, a muted bottom bar with an 'h for help' hint, filter/collapsed state and the scroll range (laid out by display width, never overflowing any terminal), a scrollable help page reachable in full on small screens, a global collapse (s: bodies over five display lines fold to five plus an ellipsis; error and degradation notes never fold), and a label filter (%: real input line, case-insensitive, empty clears; filtered-out turn groups lose their separators). Resizes re-wrap and re-judge folding live; the content caret is hidden (only the filter input shows one). q/Esc returns to the list; Ctrl+C cancels the whole browser from any viewer state. A rebuild failure falls back to the legacy per-turn preview; without a terminal the preview degrades to direct printing. - Every dynamic field passes the sanitization gates (control characters, rich markup, and ANSI never reach the terminal). Adds the Conversation Record term to CONTEXT.md. ## Type - [ ] Fix - [x] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification - `uv run pytest tests/test_trajectory_conversation.py tests/test_cli_preview_viewer.py tests/test_cli_trajectory_browse.py tests/test_trajectory_store.py -q` -> 298 passed (viewer assertions run against really rendered screens) - `uv run pytest -q` (full, on the rebased branch) -> 23463 passed, 125 skipped; 33 failed + 20 errors reproduce with identical node ids on a clean origin/main worktree - environment baseline, not introduced here (passed-count delta of 96 equals the tests this branch adds) - `make check-source-language`, `make check-large-files`, `make lint-types`, `make lint-python`, `make lint-imports` -> all pass - Manual: rendered 6 real local attempts at widths 72/120/30 with zero over-width lines (largest attempt 7060 lines in 0.35s); drove the real TUI in a pty at normal and 44x12 sizes (viewer, help paging, collapse, label filtering, Ctrl+C cancel) - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [x] User-facing docs or screenshots are updated when needed ## Risk - Read/display side only; no persisted format changes. The untrusted input surface (artifact content, typed filter input) is contained by the store-prefix path check, the 512 KiB streamed cap, and per-field sanitization. - User-visible change: the Space preview is now a full-screen viewer; the legacy rendering remains as the automatic fallback when the rebuild fails, and non-terminal environments print directly. - Rollback: revert this PR, or restore the previous `_preview_screen` call site to get the old preview back. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A --------- Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
…e shell (#516) ## Summary The documentation site built one search index for both languages, so a reader on the English pages was offered Chinese ones: results they cannot read, behind links that leave the language they chose. A build hook now splits the merged index in two and points the Chinese pages at their own copy, which the theme also resolves every result link against. Where that split runs is the whole fix. The merge lands in a plugin's own `on_post_build`, so the split has to be the last thing that event does. Running it on `on_shutdown` looks equivalent and is not: `mkdocs serve` never shuts down, so the preview went on serving the merged index however often it rebuilt. The new guard makes the same assertion against `mkdocs build` and against `mkdocs serve`, because passing one says nothing about the other. The rest settles the shell against the reference layout: - the rail's foot is its own box the navigation cannot show through, and the tab icon is the EverMind mark; - the footer keeps to the content column instead of covering the fixed columns once the reader reaches the page foot; - the contents column reaches the screen foot with its scrollbar hidden behind a fade, and follows the reader by centring the first lit entry; - search opens as a centred dialog over the page. The form itself travels into that dialog, so what stays in the rail is drawn from the field's own metrics and its shape does not change on a click. Neither state animates: the theme transitions the two shapes into each other, and leaving search played that out in the corner of the page after the reader had moved on. ## Type - [x] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification - `uv run pytest tests/integration/test_docs_site_search_e2e.py` -> 2 passed. Without this change the preview case fails: the Chinese index is HTTP 404 and the English index carries 146 Chinese pages. - `uv run pytest tests/integration/test_docs_site_controls_e2e.py` -> 25 passed - `uv run pytest tests/test_docs_site.py tests/test_source_language_check.py` -> 31 passed - `make check-commits`, `make check-source-language`, `make check-large-files` -> exit 0; `ruff check` on the added files -> no findings - `make docs-build` -> the root index holds 149 entries and no Chinese page; the Chinese index holds 146, none of them addressed from the site root Driven in a real browser at 1560x820 in both languages: a word that appears in both trees returns 10 results and 0 from the other language, and the corner of the page is pixel-identical to its never-opened state at every frame after leaving search. Every page of the built site was checked at the page foot (28 pages, 0 faults) for a footer covering a column, for a column whose last entry cannot be reached, and for the rail's foot icons being clickable. No page content changed, so nothing else needed updating with it. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [x] User-facing docs or screenshots are updated when needed ## Risk User-visible: search no longer returns pages from the other language, and the search control, the footer and the two fixed columns change shape. The field left in the rail while the dialog is open is a drawn replica, and every rule behind it sits above the theme's desktop breakpoint, so narrow viewports keep the theme's own full-screen search untouched. Rollback: revert the commit. The hook is one file, and dropping it from the `hooks:` list in `docs-site/mkdocs.yml` restores the previous merged index on its own. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A --------- Co-authored-by: Claude (claude-opus-5[1m]) <noreply@anthropic.com>
) ## Summary An EverOS role points a section at a provider: the settings card writes the model and asks `lend_provider_credentials` for that provider's key and address to go beside it. The address was copied only when there was one to copy -- the reasoning being that a section with no lender address may be holding one the reader typed by hand. A section being pointed at a new lender is holding the OLD lender's address, though, and leaving the field out of the answer keeps it. Moving the memory role from an OpenRouter model to a DeepSeek one left DeepSeek's key at OpenRouter's address, which the far end refuses. DeepSeek is not a corner case here: every vendor LiteLLM routes carries no `default_api_base`, so the lend answers with no address for all of them. The lender answers about the address either way now, empty included. An empty value reads where it lands the way an unset one does: the EverOS writer sets the field, and the section stops carrying somebody else's endpoint. Found while running the settings page's acceptance case for the EverOS roles against a real gateway. The lend rule is from #414, not from the settings work that surfaced it. ## Type - [x] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification - `uv run pytest tests/test_config_update_providers.py tests/test_rpc_settings.py tests/test_cli_onboard_commands.py tests/test_rpc_console.py -q` -- 641 passed on the branch this was written on; 191 on the two closest suites here. - The new case fails without the change: the lender answers `{"api_key": ...}` where the section needs `{"api_key": ..., "base_url": ""}`. - `uv run ruff check` and `ruff format --check` over both files -- clean. - Real gateway, temp `RAVEN_HOME`, EverOS plugin present: `settings.everosSet` with `borrow_from: "deepseek"` writes the model and DeepSeek's key and leaves no `base_url` behind; the same call with `borrow_from: "openrouter"` writes OpenRouter's address back. Before the change the first of those left `base_url = "https://openrouter.ai/api/v1"` under DeepSeek's key. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed ## Risk - Two callers, both of which point a section at a provider: the EverOS card (`settings.everosSet`) and the CLI onboarding reuse step. For a lender that has an address, nothing changes. For one that has none, the borrowing section now stops carrying the previous lender's -- which is the fix. - A reader who typed an address by hand into an EverOS section and then borrowed from an addressless provider loses that typed value. That is the same event as the bug, read the other way; the section is being pointed somewhere else, and the card shows the resolved address. - Rollback: revert the commit. ## Related Issues N/A --------- Co-authored-by: gloryfromca <23442919+gloryfromca@users.noreply.github.com> Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
…write (#495) ## Summary #469 gave a bundled sub-agent nine verbs in place of six hook phases. Reviewing it turned up a list of things the contract said but did not yet do, and this PR closes that list. No behaviour change is intended: the point is that the same seam becomes something a role can replace and something a generated judgement can enter. **1. The roles are named apart from the verbs, and `observe` is gone.** `ActionModule.review` and the participant's `review` were two different things a line apart. The four delegating role methods now say what they do -- they ask -- and the verbs keep their own names, so `ask_review` asks `review`. `observe` is removed: a verb with no return value decides nothing and can only keep state a participant is forbidden to keep. `outbound` says on its own docstring that it is the one verb with no role seat, and `StepView` gains `phase`, so a verb asked at two moments reads which one it is rather than inferring it from whichever other field happens to be set (`tools_ran` becomes a property derived from it, so the two cannot disagree). **2. The three verbs the seat was applying itself get a seat.** `system_addendum`, `archive` and `select_tools` were rendered straight onto the hook decision, so each was the one judgement in the set with nowhere to compose two participants, nowhere to vet what came back, and nothing a replacement role could decide. They join the other four: `MemoryModule.ask_system_addendum` / `ask_archive` and `CapabilityModule.ask_select_tools`. The seat is not the point where narrowing takes effect -- `select` still is, and the registry still adjudicates every call -- so a participant narrows after the product has spoken and never instead of it. **3. A participant answers with plain data.** `review`, `intake` and `system_addendum` returned host classes (`Verdict`, `Intake`) and `salvage` returned `Any`. `Accept`, `Resample`, `End` and `Intake` are now builders returning plain mappings, the host reads one back with `read_verdict` / `read_intake`, and an answer it cannot read is silence rather than a refusal. That is what makes the third invariant real and what lets a judgement compiled from source answer the same verbs a plugin does -- it can build a `dict`; it cannot build a host class. **4. A dispatch's judgements are a participant.** `judge` was the one verb a role answered by reading the dispatch's own `checks` and `code` for itself. A Charter now enters as a participant like any other, `ActionModule.ask_judge` takes the list, and the merge is a veto so the first refusal decides. The participant list becomes the only route an external judgement takes, which is the thing step four needs: a judgement generated for one dispatch joins the same list without a second way in. **5. The rename.** `AgentConduct` is `AgentParticipant`, `contracts/agent_conduct.py` is `contracts/participant.py`, `agent/hook/conduct.py` is `agent/hook/participant.py`, and `agent/harness/conducts.py` is `agent/harness/participants.py`. "Conduct" was already taken in this tree -- raven-code's Coding Conduct prompts are a different thing -- and a reader hitting both had no way to tell them apart. The rename was done symbol by symbol, not by search and replace, for exactly that reason. **7. Review (LivXue, at 3a1bf07).** The `judge` disclosure had reached two of its four homes; the other two are `ActionModule.ask_judge` on the paper and `DefaultAction.ask_judge`, and they survived because they phrase the claim without naming any symbol the earlier fix touched. The test meant to pin that claim could not fail for the reason it was written -- it asserted the refusal rather than the argument, and built a `ParticipantHook` it dropped -- and now asserts what the registry hands the role, mutation-checked by wiring a participant through. `compose_judge` exploded a one-sentence `str` refusal into one refusal per character, because `str` satisfies `Sequence[str]` structurally; `charter_code.run_judge` coerced it where this composition used to live and the handling did not move with it. Two records corrected: the type-only `raven.contracts` -> `raven.agent` edge, the first in the tree and the price of moving `Verdict` and `Intake` off the paper, is now written beside the import contract it bends rather than left to be discovered; and the ledger entry for the 3,200 -> 3,410 bump is restored to the names and count it was written with. Not taken: reading `step.phase` at `research_flow/flow.py:1000` -- the gate chain's phase vocabulary and `StepView.phase` coincide on `after_iteration` only, and that call site wants `before_execute_tools`, which is the key `_phase` dispatches on. **6. What the paper was claiming and not doing.** Read back over the seam afterwards rather than over the diff. ``judge`` read as though a plugin's override were asked ahead of a dispatch's own rules; it is not, and cannot be as written, because the party that asks the verb is ``ToolRegistry.execute``, which is not the hook chain and holds no handle on this turn's participants. The merge order is real; nothing puts a plugin in the list. Wiring one through is a turn-scoped register this change does not add, and it would start refusing calls that run today, so it is named on the paper and pinned by a test instead. ``max_iterations`` had lost its docstring to the property inserted between the field and the string documenting it. No executable line moves in this commit: the module's AST, docstrings stripped, is identical to its parent's. CONTEXT.md's entry is rewritten to the end state, including the two kinds of role method, the two verbs that are exceptions, and the note that the Coding Conduct prompts are unrelated. One item from the same list is deliberately not here: a `planning.*` block on the dispatch parameters. It is not a contract closeout but a new model-facing field, and it needs the generator's schema and prompt to move with it. Contract papers: `participant.py` and `harness.py` are both factory-loop tier, so the contract-tier digest and `CONTRACTS_VERSION` do not move. `CONTRACTS_LINE_CEILING` 3,440 to 3,470 to 3,500 to 3,540 to 3,550 to 3,570, each bump with its own review paragraph in `tests/test_kernel_budget.py`. The chain was recomputed after rebasing onto #410, which added seven lines to `loop_hooks.py` inside the old headroom and left one commit here with one line of it; every "Measured at" figure in that file is the count at that commit. ## Type - [ ] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [x] Refactor - [ ] Other ## Verification Gates, run at each of the seven commits: ``` uv run ruff check --no-cache raven evolver agents plugins-dist tests scripts uv run ruff format --check --no-cache raven evolver agents plugins-dist tests scripts uv run lint-imports uv run pytest -q npx commitlint --from origin/main --to HEAD --config commitlint.config.cjs uv run python scripts/check_commit_messages.py origin/main..HEAD uv run python scripts/check_source_language.py origin/main..HEAD uv run python scripts/check_large_files.py origin/main..HEAD ``` Import contracts: 10 kept, 0 broken. The suite is green: the failures this machine shows are the same ones it shows on an unmodified `main` in the same worktree layout (missing browser and optional packages), and this branch adds none. Behaviour preservation. The oracle is the hook seam itself. `main` and this branch were run under the same scripted provider, outputs normalised for nonces and temp paths, then byte-compared. Four scenarios, all identical: - the five-plugin hook seam, every phase's decision and metadata per iteration (23,840 bytes) - a whole turn with no playbook: host turn, spawned sub-agent, tool calls and refusals - a playbook-on worker lane in process, including the charter refusals - six window-shrink scenarios This is the seam the change is about, and identical output across it is what "no behaviour change" means here -- it is not a claim about paths the four scenarios do not reach. Two claims had no assertion anywhere and now do. ``phase``, and the ``tools_ran`` derived from it, are what three of the five bundled plugins branch on -- ppt on its close, oncall on its counter, research on which gate runs -- and a mistyped phase string at one of the six call sites would have changed what they do with a green suite. The read-only test now also covers ``mode_overlay``, the one frozen field it skipped. All three assertions were mutation-checked: mislabelling a phase, deriving ``tools_ran`` from the wrong one, and dropping the freeze each turn exactly their own test red. New tests, each written against a claim the contract now makes rather than against the code: a participant cannot write through the `StepView` it is shown; an answer the host cannot read is silence, not a refusal; a participant that answers with a mapping it built itself is understood; every verb with a seat goes through its role rather than around it, asserted as a set so a new verb that bypasses its role fails; two seats in one chain each keep one addendum; two turns in flight keep their own participants; a bound role decides what a participant's verdict does; a plugin's own judgement speaks before the dispatch's, and a silent participant lets the next one speak. Real model, Sonnet 4.5 through OpenRouter, on this branch: a query generates a playbook, two sub-agents are instantiated and run, each writes its `summary.md` under its own fence, and both results are announced back to the originating session. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [x] User-facing docs or screenshots are updated when needed ## Risk For plugin authors: the six-phase `AgentHook` is untouched and a plugin shipping one keeps working. A plugin written against #469's `AgentConduct` needs the new names and, if it answered with `Verdict` or `Intake`, the builders instead -- the five bundled plugins are ported here and are the worked examples. One verb narrowed, and silently for a plugin that used the width. `salvage` was annotated `Any | None` and `compose_salvage` returned whatever was not None; it is now `str | None` and only a non-empty string is taken. A third-party plugin that salvages with a reply object rather than text stops being heard rather than raising -- which is the third invariant working as written, and worth knowing before upgrading. The narrowing is deliberate: `salvage` answers the text a turn sends when it produced none, and `Any` was not a contract. For anyone replacing a harness role: the four delegating methods are renamed and four more are added, so a replacement written against #469 needs them. `bind()` names the missing member at assembly rather than failing inside a turn. Rollback is a revert of the merge commit. Nothing persisted on disk changes shape. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues Follows #468 and #469. Generated with Claude Code (https://claude.com/claude-code) --------- Co-authored-by: yao pengfei <yaopengfei@shanda.com> Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
…tecture Conflicts and how they were settled: - ui-web/src/features/settings/SettingsPage.tsx, its test, and ui-web/src/live/120-settings.js: deleted on this branch, edited on main. The rebuilt settings surface replaces all three, so the deletions stand. - i18n/messages.json and ui-tui/src/i18n/messages.generated.ts: main's only change to either since the merge base is #506 dropping gui.set.chn.cron_to and cron_ph. This branch already drops the whole gui.set.chn.* group, so its side is the superset and wins. - raven/skill_hub/hub.py: each side added an import from skill_hub.audit. Both symbols are used and both exist after the merge, so both are imported. - ui-web/build.py: main rewrote the newline="" comment, this branch added flush=True to the print beneath it. Kept both. The cron timezone fix from #506 auto-merged into raven/rpc/methods/console.py and brings its tests with it. The rebuilt settings pages have no cron section, so that control has no UI on this branch. Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; this can merge as far as I am concerned.
Reviewed the full target-to-head diff and merge topology, then checked all seven explicit conflict resolutions plus the paths changed on both sides that Git merged automatically. I also covered AGENTS.md/CLAUDE.md, the Web UI architecture vocabulary and boundaries, relevant callers and source-commit history, backward compatibility, and whether tests were removed or weakened. The deleted legacy settings paths do not lose the embedding-model warning fix: the rebuilt settings source already carries that behavior. The cron-timezone control remains absent, as disclosed, but that absence predates this merge on the target branch.
Verification: npm test passed 181 files / 2,513 tests; the targeted Python suite passed 174 tests after installing the repository's documented all-extras test environment; npm run type-check, npm run lint, and npm run build exited 0 (lint reported five existing hook warnings); and uv run python ui-web/build.py built the page with both boot snapshots matching. The first Python attempt in a fresh default environment produced 85 setup errors because raven_everos was not installed; rerunning after uv sync --locked --all-extras --dev passed. The worktree remained clean.
The currently failed commit-message check is the inherited-main-history case already documented in the PR, not a defect introduced by this revision.
gloryfromca
left a comment
There was a problem hiding this comment.
Blocking: the merged ext.list test suite must be restored by updating the stale tool-registry stub.
Reviewed the delta from the previously assessed revision, the full target-to-head diff, the merge topology and source-commit history, AGENTS.md/CLAUDE.md, the Web UI context and architecture boundaries, affected callers, backward compatibility, and whether tests were weakened. The merge has no manual tree deviation from Git's clean recursive result, but the inline test-contract incompatibility is a semantic merge failure.
Verification on this exact head: npm test passed 181 files / 2,518 tests. uv run pytest tests/test_provider_rates.py tests/test_rpc_config.py tests/test_rpc_console.py tests/test_rpc_settings.py -q produced 325 passes and the one inline failure; the isolated failing test reproduced it again. The worktree remains clean.
… row share (#535) ## Summary Replaces the drawing in `ui-web/icon/raven.svg`. Nothing else moves: no path, no stylesheet rule and no caller changes. That one file has two consumers, because `ui-web/build.py` copies it over `dist/assets/raven.svg` after the rest of the asset tree is in place: - the browser tab icon, through `ui-web/src/page.html`; - the mark on the first row of the file-processing list in the knowledge base settings dialog, through `ravenIconPath()` in `ui-web/src/shell/provider-mark.tsx` and its one call site in `ui-web/src/features/knowledge/KnowledgePage.tsx`. The second one is easy to miss, so it was checked rather than assumed. The row sizes the mark at 17px through `.kbprocr .provider-icon` in `ui-web/src/styles/page.css`, not at the 20px the base `.provider-icon` rule gives, and it is the first row, so it renders at full opacity. The image carries no `data-tone` attribute, so none of the six `invert(1)` rules in that stylesheet matches it and the theme switch leaves it alone. Both were confirmed by rendering the built asset at 17px on the light and the dark surface. The new file keeps a `viewBox`, which is what lets one drawing serve a 17px row and a 32px high-dpi tab. This does not touch `ui-web/src/assets/agents/raven.svg`, the separate mark the agent rows draw. Those two files were already different drawings and still are. ## Type - [ ] Fix - [x] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification Gates, against this branch's own range rather than a stale one: - `scripts/check_large_files.py origin/main..HEAD` exit 0. `ui-web/` is exempt from the extension list by `scripts/check_large_files.py`, and the file is 1199 bytes, down from 2524. - `scripts/check_source_language.py origin/main..HEAD` exit 0. - `scripts/check_commit_messages.py origin/main..HEAD` exit 0. - `npx commitlint --from origin/main --to HEAD` exit 0. - `npm run gen:check --prefix ui-web` exit 0, 178 methods matched. - `npm run type-check --prefix ui-web` exit 0. Tests. CI does not run `make test-ui`, so the one test file that names this asset was run directly: - `npm test --prefix ui-web -- --run src/features/knowledge/KnowledgePage.test.tsx` gives 5 failed, 104 passed, 0 unhandled errors, exit 1. - All 5 are the `the recall test` cases, and node names the cause itself: `localStorage is not available because --localstorage-file was not provided`. - They are inherited, not introduced. `git diff origin/main..HEAD --name-only -- ui-web` lists this SVG and nothing else, so the JavaScript that run exercised is byte-identical to `origin/main`; the run was the baseline. No test imports or reads the file, so its bytes cannot reach an assertion. Rendering. The built asset was rendered in headless Chrome at 16, 32, 48 and 64 pixels on a light and a dark tab strip, and at the row's real 17px box on both themes. - [ ] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [x] User-facing docs or screenshots are updated when needed The first box is left unchecked deliberately. The relevant file reports 5 red, and while the evidence above shows all 5 are inherited and unreachable from this change, the suite does not come back green on this machine and saying it does would be false. On the third box: `grep` over the tracked tree finds no prose describing this drawing. The favicon sentences in `ui-web/src/styles/page.css` and `ui-web/src/shell/agent-mark.tsx` describe `miromind.svg` and the agent mark, both of which this leaves alone. ## Risk User-visible in two places, both cosmetic. Rollback is reverting the commit and rebuilding; `dist/` is generated, so nothing downstream holds a copy. Checked and deliberately not changed: - At 16px the halo ring largely disappears. Its stroke is 1.6 units on a 64 unit canvas, which is 0.4 of a physical pixel there, and a count of ring pixels falls from 245 at 64px to 17 at 16px. A high-dpi tab draws 32px, where it holds. Widening the stroke would change a drawing that was chosen as it is, so it is reported rather than edited. - No test pins the `viewBox` of this file, and none is added here. No icon in the tree has such a guard, and adding the first one is a wider decision than this change. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes The security note: a favicon is parsed by the browser directly, so the file was checked for active content. It has no `script`, `foreignObject`, `image`, `xlink:href`, `href`, `url(`, `@import` or event-handler attribute; the only external token is the SVG namespace declaration. ## Related Issues N/A Co-authored-by: Claude (claude-opus-5[1m]) <noreply@anthropic.com>
…534) ## Summary A presentation pass over both READMEs. No prose was rewritten and no benchmark number changed; all four changes are about how the Built-in Agents and EverMind Ecosystem sections read. 1. The four agent subheadings (Raven-Research, Raven-Code, Raven-Design, Raven-Oncall) now carry a single chevron, one step lighter than the double chevron the section headings already use. That gives depth by weight without introducing a second symbol family. 2. The six Raven-Code, Raven-Design and Raven-Oncall benchmark charts render at 95% of the line instead of full width. Both READMEs reference the same six assets, so the width is set in each file or the two language pages disagree about the same picture. 3. The EverMind Ecosystem section now opens with a brand banner, cropped to 2172x511 (4.25:1) from the 3.00:1 original. The empty field above and below the orbital composition is gone and the remaining margin is equal on both sides, which the original was not. The composition itself is untouched. 4. The two Raven-Oncall captions state the result their chart shows rather than naming the comparison. Checked and deliberately not changed: - The two Raven-Oncall charts label the same model differently: DS-V4.1-Flash on AI4AI, DS-V4-Flash on AI4S. Pre-existing, and correcting it means regenerating a chart rather than editing text. - The Raven-Research chart keeps its full width, and is now the only chart in that section which does. ## Type - [ ] Fix - [ ] Feature - [x] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification Gates, each against a non-empty branch range: make check-large-files exit 0 make check-source-language exit 0 scripts/check_commit_messages.py exit 0 Tests: uv run --frozen --all-extras pytest tests/test_living_docs.py \ tests/test_scope_canon.py tests/test_commit_lint.py -q 15 passed Assets: for each uploaded image the published URL was fetched with GET and its sha256 compared against the local file. Both matched. HEAD is not usable for this check, because user-attachments answers 403 to it whether or not the upload succeeded. Greps: - The superseded banner asset id has zero occurrences repo-wide. - The four agent heading anchors have zero references in either README, in the documentation site, or in the workflows, so the slug change the chevron prefix causes breaks nothing that links to them. - [ ] Relevant tests pass locally - [ ] Relevant lint / type checks pass locally - [x] User-facing docs or screenshots are updated when needed The first two boxes are blank on purpose. No test reads the two root READMEs: test_living_docs globs the package READMEs, test_scope_canon reads a documentation-site page, and test_external_consumer_surface reads a different README. The three suites above ran because they are the nearest documentation guards, not because they cover this change. Repository lint does not cover Markdown. ## Risk User-visible: the four agent subheadings gain a symbol, so their generated anchors change; six charts render slightly narrower; the ecosystem section gains a banner; two captions assert a result. Nothing outside the two README files changes. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes Security: no credential, endpoint or permission is touched. The uploaded image is a brand banner, public by intent. Backward compatibility: the heading anchors are the one break. Nothing in the repository links to them, but an external bookmark to one of the four would stop resolving. Rollback: revert the commit. Images are attachments rather than committed files, and superseded assets stay published, so any link already shared keeps resolving. ## Related Issues #530 hosts the ecosystem banner assets and stays open for that purpose. --------- Co-authored-by: Claude (claude-opus-5[1m]) <noreply@anthropic.com>
## Summary Raven speaks the Agent2Agent protocol, version 1.0, in both directions. The host agent is the peer: nothing here makes a sub-agent addressable from outside, and a raven launched as a sub-agent refuses to serve the face at all, because it is already reached over ACP by the host that started it. Inbound is an aiohttp face carrying the two cards and a JSON-RPC endpoint. It mounts on the gateway when `a2a.server.enabled` is set, and `raven a2a serve` runs it standalone for a headless deployment, where running the command is itself the opt-in. Outbound is a single tool, `a2a_send`, that takes a card URL and a message; it enters the tool list only when `a2a.peers` is non-empty, so a host with nowhere to send a message does not carry the schema. The two switches are deliberately independent: calling out and being called are separate deployments. Onboarding mints `a2a.server.token` and stops there. The face ships closed: finishing an unrelated install is not consent to open a second network surface, so `raven a2a enable` is what opens it, and it is the only writer of that flag. Enabling mints a missing token in the same command, because a face switched on with an empty token answers every caller with a 401 and reads as a broken deployment rather than the closed door it is. `raven a2a disable` closes it again and keeps the token, so reopening does not re-issue a credential callers already hold. The wizard never writes the flag at all, not even as false, so a repeat onboard leaves a face the operator opened by hand alone. A config nobody onboarded, and any `A2aConfig()` assembled in process, stays inert. What that opens is narrower than a network face. The app carrying it binds loopback at both call sites, hardcoded rather than read from config, and every request is checked against a freshly minted 256-bit bearer token. The minting write is also what fixes `config.json` to owner-only, from the temporary file's first byte rather than with a chmod afterwards; a later enable or disable adds no secret material, so it leaves whatever mode the operator chose. Nothing else narrows that file and it already held provider API keys, so the obligation predates this work and is merely discharged here. There are two cards because the protocol splits one description across two authentication states. The public card answers an unauthenticated GET, and has to: a caller reads it to learn which scheme to authenticate with, so requiring the credential first would be circular. It therefore names no installed agent. The extended card is a `GetExtendedAgentCard` RPC riding the same bearer check as every other method, and is the only one of the two that may say what this host can actually do. On that extended card the sub-agent roster rides `capabilities.extensions`, not `skills`. A skill is the protocol's word for something an agent can be asked to do, and a peer cannot ask for one named sub-agent: it can only send a message to this host, which then decides what to dispatch. One skill per sub-agent would advertise call targets that do not exist. What a peer can genuinely request is the orchestration, so that is a single skill, and the roster moves to the protocol's own extension point under a versioned URI. That is the only place a conformant card may carry a payload the spec does not define, because `AgentCard` is a closed set of fourteen protobuf fields: a custom top-level key is rejected by a strict parser and silently dropped by a lenient one. The entry declares `required` false, so a reader that has never heard of the URI ignores it and still talks to this host. Both cards derive `supportedInterfaces[].url` from the origin the caller arrived on. One face answers under every name that routes to it, and a URL fixed when the face is mounted is right for at most one of them. This matters more than it looks: a caller that finds an interface off the card's own origin must refuse it, since a card fetched from a trusted origin would otherwise redirect the credential elsewhere, and a relative URL reads as off-origin to that check. ## Type - [x] Feature ## Verification - `make test-python` at this head: 23729 passed, 112 skipped, 1 failed, in 288 s. The one failure is `tests/test_install_script.py::test_resolve_node_dir_answers_each_case_it_exists_for`, which fails with the same assertion on `origin/main` by itself; this branch changes no install or node file. - `uv run --frozen --all-extras pytest tests/integration/test_a2a_protocol_e2e.py -q`: 6 passed. That directory sits outside the default pytest scope, so it is run by hand rather than reached by the line above. - `make lint-imports`: 10 contracts kept, 0 broken. `make lint-types`: all checks passed. - `uv run ruff check raven/ tests/`: clean. `ruff format --check`: clean. - `make check-commits` over all 68 commits, `make check-source-language`, `make check-large-files`: all exit 0. - Live, over a real network boundary rather than in process: a raven in a Docker container and a raven on the host, each running this branch, called each other in both directions. Nineteen assertions per direction covering both cards, the four refusal paths, and a completed turn. The container publishes its face on a different port than it binds, so the card has to name the port the caller used and not the socket the server opened; both cards did, in both directions. That run predates the default-off change described above, which touches no file under `raven/a2a/` or `raven/a2a_client/`: both packages are byte-identical to the head it ran against. - Reverting each fix turns its own tests red, checked one fix at a time rather than all together, including the interface-URL fix mutated out inside the running container. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [x] User-facing docs or screenshots are updated when needed ## Risk Behaviour changes for an operator who runs `raven onboard` after this lands: a token is minted into `a2a.server.token`, and `config.json` is narrowed to owner-only. No listening face is opened. Opening one takes `raven a2a enable` and a gateway restart, so the second network surface only ever exists because somebody typed for it. Rolling an opened face back is `raven a2a disable` and a restart. The token stays in place and is never rotated, so callers already holding it keep working when the face is reopened, and a later onboard will not reopen it by itself. Nothing else in the install changes. Costs and limits, stated rather than left to be discovered: - A gateway that serves A2A imports `a2a-sdk`, measured at roughly 570 ms and 391 modules on top of the gateway's own stack, sqlalchemy and the OpenTelemetry api among them. It is paid once at mount, not per request, and an operator who switches the face off pays none of it. Moving it to the first request is recorded as deferred in the design note rather than done here. - `a2a_send` adds 157 tokens to the tool array of every turn, measured off a captured provider call, and only on a host with a peer configured. - The task store is in memory and unbounded, and inbound concurrency is not capped. A local face behind a bearer token is the deployment this is written for; a host exposed more widely should bound both first. - Pulling in `a2a-sdk` brings the OpenTelemetry api into the gateway's import graph. This is a trade accepted for the SDK's protocol types and handler, not an adoption of OpenTelemetry by this project. Checked during review and deliberately not fixed here: config validation accepts an agent row with an empty or whitespace name, which would reach the extended card's roster as an empty entry. It is not introduced by this work, the previous shape carried the same value, and closing it belongs with the agent schema rather than with the card. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A --------- Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
#513 added schema_hidden_names() to the tool registry and updated the stand-ins that existed on this branch; ext_list calls it to decide which tools are fixed. #464 landed a case on main with a stand-in of its own. The two sit in different places in the same file, so merging the branches raised no textual conflict and left the new call beside the old fake. Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
|
Fixed in tools=SimpleNamespace(tool_names=[], get=lambda _name: None, schema_hidden_names=frozenset),which is what the other stand-in in the same file (line 189) already carried. Where it came from: #513 added Bisected, so the arrival point is not a guess: Verified after the fix: |
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; this can merge as far as I am concerned.
The one-line delta supplies the registry method required by the merged ext_list() contract, matches the neighboring fake, and restores the prior finding without weakening its assertions. I checked the delta against the full target diff, affected caller and history, repository rules and Web UI architecture constraints, backward compatibility, and test integrity; no new issue remains.
Verification: the formerly failing test passes in isolation, and uv run pytest tests/test_provider_rates.py tests/test_rpc_config.py tests/test_rpc_console.py tests/test_rpc_settings.py -q passes 326/326. The prior review thread was replied to and resolved, and the worktree is clean.
## Summary A pull request whose base is not the default branch carries, in its `BASE..HEAD` range, every commit the default branch has gained since the two diverged. Both commit linters read that range, so they re-read history that already passed this gate when it landed and that nobody can rewrite now. #529 is the case that surfaced it: merging `main` into `refactor/ui_web_architecture` puts 38 `main` commits in range, one of which (`b01534d0`, #471) carries a `Co-authored-by` trailer with a non-ASCII name. The gate reports that PR red for a message it did not write. The same will happen to every future catch-up merge. `scripts/check_commit_messages.py` now takes several revision arguments and passes them to `git log`, so a caret exclusion works. Commitlint has no equivalent flag, so the workflow iterates the same revision list and lints one message at a time. On a pull request the workflow excludes the repository's default branch; a push to the default branch is unchanged and still lints what it pushed. This does not touch the offending commit on `main` and does not relax a rule: a commit that a PR actually introduces is linted exactly as before. ## Type - [ ] Fix - [ ] Feature - [ ] Docs - [x] CI / tooling - [ ] Refactor - [ ] Other ## Verification `uv run pytest tests/test_check_commit_messages.py tests/test_commit_lint.py -q` -- 15 passed. Three of those cases are new and cover a repo where the default branch carries a rejected message and the feature branch merges it in: the range fails without an exclusion, passes with one, and still fails when the bad commit is the branch's own. Mutation check on the new cases: replacing `_commit_messages(args)` with `_commit_messages(args[:1])`, which is the behaviour before this change, turns `test_excluding_the_default_branch_skips_the_history_it_carries` red and leaves the other three green. The pin fails when the fix is removed. Against the real history behind #529, with the checker run directly: - `check_commit_messages.py "bc74bf73..a066624"` -- exit 1, naming `b01534d02` exactly as CI does. - the same range plus `^github/main` -- exit 0. The workflow's new loop was run as written against both ranges: 0 commits to lint on the catch-up range, and a planted `Update stuff.` commit on a feature branch still sets the failure flag. `make lint-python` -- clean. `scripts/check_source_language.py` over the branch -- exit 0; the test's non-ASCII message is built from an escape, so the file itself stays ASCII. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed ## Risk Commitlint now starts once per commit instead of once per range, so a PR with many commits spends a second or so more in this job. The exclusion only ever removes commits that are already on the default branch, so nothing a PR introduces escapes either linter. Rollback is reverting this PR; the previous behaviour is one range argument and one commitlint invocation. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A. Unblocks the `commit messages` check on #529. --------- Co-authored-by: gloryfromca <23442919+gloryfromca@users.noreply.github.com> Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; this can merge as far as I am concerned.
Reviewed the new target-branch merge, its exact recursive-merge tree, the four files changed on both sides, relevant callers and source history, AGENTS.md/CLAUDE.md and Web UI context constraints, backward compatibility, and test integrity. The onboarding/import and DAG follow-up changes integrate without a manual merge deviation or a new cross-history failure; the test count increases despite the intentional retirement of the old DAG sheet tests.
Verification on this head: the affected Python set passes 377 tests; npm test passes 184 files / 2,530 tests; type-check, lint, and production build exit 0 (lint reports five unrelated existing warnings); and uv run python ui-web/build.py matches both boot snapshots. The worktree is clean.
…533) ## Summary Raven-Research capped its replies at 30000 characters; every other lane caps at 60000. The low number was vestigial, carried over from the vendored fork rather than chosen, and research writes the longest replies of the five. Raised to 60000. The branch briefly carried a run-once config migration (floor 10) that lifted a stored 30000 row to the new value on an upgraded install, added in response to the review finding that a stored row wins over the discovered manifest. It was reverted at the maintainers' request: no release has shipped, so there is no upgraded install with such a row, and the manifest change on its own is the whole fix. The branch diff against main is the one manifest line. A development config that registered the lane before this change keeps its stored `maxOutputChars` until that row is edited or removed (`merge_product_seeds` takes the whole stored row over the discovered one, by design). ## Type - [x] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification Head 5e00464: the manifest change, a merge of main, the migration and its revert. ``` uv run --frozen --python 3.12 --all-extras pytest -q tests/test_config_loader.py tests/test_agent_schemas.py tests/test_subagent_vendored_agents.py tests/test_subagent_acp.py tests/test_acp_modes.py tests/test_agents_research_flow_ask_user_surface.py 410 passed in 13.32s uv run --frozen --python 3.12 --extra dev ruff check raven/config/loader.py tests/test_config_loader.py All checks passed! uv run --frozen --python 3.12 --extra dev ruff format --check raven/config/loader.py tests/test_config_loader.py 2 files already formatted PYTHONPATH=. uv run --frozen --python 3.12 --extra dev python scripts/check_source_language.py origin/main...HEAD exit 0 make check-commits exit 0 git diff --stat origin/main...HEAD agents/raven-research/subagent.json | 2 +- git diff --stat dd8b58d HEAD -- raven/config/loader.py tests/test_config_loader.py (empty: the revert leaves both files byte-identical to the pre-migration commit) ``` The type checker was not rerun locally: the revert removes code only, and CI's python lint job runs it on the head. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed ## Risk A research reply between 30000 and 60000 characters now reaches the caller whole, costing that turn up to 30000 more characters of context. Rollback is the one value. A config that stored the lane's row before this change (development installs only, since nothing has shipped) keeps its stored cap until the row is edited or removed. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; this can merge as far as I am concerned.
Reviewed the new 57bb5d886d22..f2c4e5c20519 catch-up, including the seven files changed on both sides of the merge. The result is byte-for-byte Git’s automatic merge tree. I traced the first-run stack rebuild through dispatcher/emitter/deliverable callers and the live-setting changes through the turn boundary, provider pool, curator, skill gate, usage reporting, and settings fixtures; the two lines of work preserve their respective contracts when combined. I also checked the repository rules and Runtime/Web context documents, relevant history and callers, backward compatibility, and test changes; the tests add coverage for the new behavior rather than weakening existing assertions.
Verification: uv run pytest -q over the 14 affected Python test modules passed 641/641; npm test -- --run passed 184 files and 2,530/2,530 tests. The completed GitHub code, lint, page, RPC-contract, trajectory, TUI, bridge, repository, docs, Windows-upgrade, and three completed unit-shard checks are green. CI separately reports the commit-message gate failing on pre-existing commit b01534d02ae1 for a non-ASCII message body, while unit shard 1/4 is still pending; that machine-detected metadata issue is outside this code-review verdict.
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; this can merge as far as I am concerned.
Reviewed the new f2c4e5c20519..8e1032ff076e catch-up and the two files changed on both sides of the merge. The result is byte-for-byte Git’s automatic merge tree. I traced the ext.list built-in classification through the registry withholding rules, generated wire contract, fixtures, and settings-row mapping; the prior test-stub repair remains compatible now that this path no longer consults schema_hidden_names(). I also followed the anchored model picker and two-step update flow through their callers and retained state.
Covered the repository rules and Runtime/Web context constraints, the revision delta, callers and history, backward compatibility, and test integrity. Existing assertions were updated to the corrected contracts and supplemented with focused regression coverage rather than weakened.
Verification: the affected RPC/schema suite passed 546/546; the web suite passed 186 files and 2,544/2,544 tests; npm run type-check passed; npm run lint passed with the same five existing warnings.
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; this can merge as far as I am concerned.
Reviewed the new 8e1032f..dd1e8ae merge delta and the full target...HEAD diff under AGENTS.md, CLAUDE.md, and the relevant CONTEXT.md architecture rules. I checked the three files modified on both merge parents, their callers and history, backward compatibility of the A2A/config/loop wiring, the surfaces-law boundary, and whether tests were weakened. The merge tree is byte-identical to Git's clean automatic merge result, and the added tests expand rather than relax coverage.
Verification: the focused A2A/config/loop/integration set passed 692/692; ui-web passed 2,544/2,544; Ruff passed; git diff --check passed; and every hosted PR check is green, including all four Python 3.12 shards. The repository-wide local Python 3.13 run completed with 23,949 passed, 120 skipped, and one failure in tests/test_cli_theme.py::test_bold_accent_renders_styled_not_bare. That test, its theme source, and Rich 14.3.4 are identical on the target, so this is not introduced by this revision. mypy was unavailable locally, while the hosted Python lint job passed. No new findings.
## Summary Second catch-up of `refactor/ui_web_architecture` with `main`, this time to `6ebc2baa` (#592): the 31 commits `main` gained since #529 took it to `dbe259f6`. Synced main to `6ebc2baa`; the next round starts from that SHA. #529 landed as a squash, so git still saw the two branches diverge at `5a29ea22` and re-raised every conflict #529 had already settled (68 paths instead of 33). The first commit here merges `dbe259f6` with `-s ours` -- tree unchanged, ancestry recorded -- so the second commit merges only what is new; a third takes #528 and a fifth takes #592 (READMEs only, no conflict), both of which landed on `main` while the earlier commits were being verified. The same steps work for every later round. Thirty-three paths conflicted in the second commit, one in the third; the fourth commit is what an independent read of the merge turned up (the sheet's Connect, `capabilities_wanted`'s fourth case, the dead styles); the sixth merges this branch's own tip (#584, #591, #586), where only the fixture-shape gate's UNSENT list met (both sides kept). How each conflict was settled: - 17 legacy ui-web files (`live/`, `demo/`, `features/xa`, `features/knowledge`, `scripts/boot-order.test.mjs`, `composer/open-conversation.test.ts`), edited on `main` for #542, #549 and #554: deleted on this branch, and the deletions stand. `main`'s `shell/workdir.ts` and its test, which git relocated into `lib/`, are dropped for the same reason -- see the follow-up below. - `main.tsx`, `page.html`, `state/session/registry.test.ts`: this branch's version. `main`'s edits there were the legacy chip wiring; the registry conflict was a rename git guessed from `scripts/page-switch-live.test.mjs`. - `raven/agent/subagent/probe.py`: both imports; Test records a snapshot for a preset too (`main`, #554) through `record_capabilities` (this branch, #559); a kept record carries `needs_auth`; the boot backfill re-measures an outdated model menu (this branch) and a recorded credential refusal (`main`). - `raven/rpc/methods/subagents.py`: `needs_auth` reads the snapshot this branch already loads per row. - `raven/agent/subagent/manager.py` (#528, third commit): both sides kept at both hunks -- `main`'s two shutdown refusals beside `SPAWN_REFUSED_PREFIX` with this branch's `_row_pin` after them, and the spawn announce that types its mark and carries `node_id` (this branch) then returns early when `_inject` was refused (`main`). - `tests/test_subagent_probe.py`, `test_subagent_acp.py`, `test_rpc_subagents.py`, `test_rpc_console.py`, `test_config_update.py`: both sides' new tests, in order. This branch's two backfill tests pin `_unconfigured_acp_preset_rows` the way `main`'s do, and the `_agent_home` helper both sides defined is one function passing a `Path`, as the config property does. - `README.md`, `README.zh-CN.md`: `main`'s figure and showcase section. - `ui-web/src/rpc/generated.ts`, `ui-tui/src/rpc/generated.ts`, `ui-tui/src/i18n/messages.generated.ts`: regenerated from the merged schema and catalogue. Carried onto the rebuilt tree, since the backend halves merged cleanly: - #554: the agent hub's rows, its sheet and the wizard step name a refused agent `Unauthorized` (disabled) instead of offering Connect; the sheet offers Test beside it, the press that re-measures the handshake and lets the row return to Connect after a sign-in. `capabilities_wanted` treats a recorded refusal as wanting a re-measure, the same fourth case the boot backfill gained from `main`. Four tests. - #542: rail rows carry the session's `workdir`, so the folder group `RailPage.tsx` gained in the merge can fill. The tag's class moves under the rail namespace (`rail-wdt`) with its rule in the new `features/rail/styles.css`, as check-class-namespace requires. - #549: the page-side upload ceiling follows `raven/rpc/files.py` to 100 MB. - The offline fixtures send `needs_auth`; `session.list.sessions[].workdir` is pinned unsent in fixture-shape until the chip lands. The folder chip's thirty lines of popover rules that `main` added to `styles/page.css` are dropped with the chip; the follow-up brings its own. Follow-up, not in this PR: the folder chip itself (#542's `shell/workdir.ts`: the composer chip, the `fs.dirs` browser, `workdir` on `session.create`) has to be rebuilt on this tree's chip pattern (`state/perm.ts` + `chrome/PermChip.tsx`). The backend, the i18n keys and the rail half are already here. ## Type - [ ] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [x] Other (catch-up merge) ## Verification Run in the worktree on the merged tree: - `uv run pytest tests/test_subagent_probe.py tests/test_subagent_acp.py tests/test_rpc_subagents.py tests/test_rpc_console.py tests/test_config_update.py tests/test_rpc_session.py tests/test_rpc_contract_shapes.py tests/test_rpc_schema_match.py tests/test_rpc_registration.py tests/test_i18n_boundary.py tests/test_subagent_manager.py tests/test_subagent_dag_runner.py tests/test_rpc_bootstrap.py tests/test_cli_tui_commands.py tests/test_cli_gateway_commands.py tests/test_rpc_transport.py tests/test_cli_serve_commands.py tests/test_importer_orchestrator.py tests/test_cli_gateway_page.py -q` on the final head -- 1893 passed (the same files, run per commit as the branch grew: 1141, 590, 501). - `make lint-python` -- ruff check clean; ruff format clean after formatting the three spliced test files. - `npm test` in `ui-web` on the final head -- 189 files, 2468 tests passed (before the sync merge: 193 files, 2609; the first run had failed two gates, check-class-namespace and fixture-shape, on the merged `.wdt` class and the three new optional wire fields, both fixed as described above). - `npm run type-check`, `npm run lint` (0 errors, 5 pre-existing warnings), `npm run gen:check` (193 methods) in `ui-web`. - `npm run lint:i18n`, `npm run lint:rpc`, `npm run type-check`, `npm test` in `ui-tui` -- all clean. - `make build-ui` on the final head -- page built, `boot-snapshot: OK` on both snapshots (235 nodes, the golden #591 set). - `scripts/check_source_language.py origin/refactor/ui_web_architecture..HEAD` and `scripts/check_large_files.py origin/refactor/ui_web_architecture..HEAD` -- both exit 0; `git merge-tree --write-tree origin/main HEAD` -- clean. The full Python suite is left to CI. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed ## Risk No source change of its own beyond the resolutions and the three carries above. On the page: a refused external agent now shows a disabled `Unauthorized` button where it showed Connect; conversations pinned to a folder group under their own rail heading with the folder's name on the row; uploads up to 100 MB are sent instead of refused at 25 MB. All three match what `main` ships today. This branch is squash-only, so the merge base stays at `5a29ea22` after this lands; the next round repeats the `-s ours 6ebc2ba` step first. Rollback is reverting the squash commit; the branch is back at `99c37e74`. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A. Unblocks #475. --------- Co-authored-by: xfng-sd <xufang@shanda.com> Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com> Co-authored-by: Handsome-wzw <68996445+Handsome-wzw@users.noreply.github.com> Co-authored-by: Dizhan Xue <dizhan.xue@evermind.ai> Co-authored-by: Kevin Hu <kevinhu.sh@gmail.com> Co-authored-by: admin <admin@SH-HuKai.local> Co-authored-by: 江国庆/Forrest <mr.jianggq@163.com> Co-authored-by: 江国庆 <guoqingjiang@deepglint.com> Co-authored-by: Codex <noreply@openai.com> Co-authored-by: ypflll <ypflll@163.com> Co-authored-by: yao pengfei <yaopengfei@shanda.com> Co-authored-by: zhanghui <huizhang1995@gmail.com> Co-authored-by: Zuyi Zhou <144661423+ZuyiZhou@users.noreply.github.com> Co-authored-by: Tong Li <litong02@shanda.com> Co-authored-by: litong <238663200+TongLi31@users.noreply.github.com> Co-authored-by: Tchen-data <176354753+Tchen-data@users.noreply.github.com> Co-authored-by: gloryfromca <23442919+gloryfromca@users.noreply.github.com> Co-authored-by: Dizhan Xue <xuedizhan17@mails.ucas.ac.cn> Co-authored-by: silverLXT <25195409+silverLXT@users.noreply.github.com> Co-authored-by: xiaotian.luo <xiaotian.luo@thetahealth.ai> Co-authored-by: userName20260323 <zhao.wang@evermind.ai> Co-authored-by: zhao.wang <270284818+userName20260323@users.noreply.github.com> Co-authored-by: arelchan <1239372199@qq.com> Co-authored-by: arelchan <204152633+arelchan@users.noreply.github.com>
## Summary A pull request whose base is not the default branch carries, in its `BASE..HEAD` range, every commit the default branch has gained since the two diverged. Both commit linters read that range, so they re-read history that already passed this gate when it landed and that nobody can rewrite now. #529 is the case that surfaced it: merging `main` into `refactor/ui_web_architecture` puts 38 `main` commits in range, one of which (`b01534d0`, #471) carries a `Co-authored-by` trailer with a non-ASCII name. The gate reports that PR red for a message it did not write. The same will happen to every future catch-up merge. `scripts/check_commit_messages.py` now takes several revision arguments and passes them to `git log`, so a caret exclusion works. Commitlint has no equivalent flag, so the workflow iterates the same revision list and lints one message at a time. On a pull request the workflow excludes the repository's default branch; a push to the default branch is unchanged and still lints what it pushed. This does not touch the offending commit on `main` and does not relax a rule: a commit that a PR actually introduces is linted exactly as before. ## Type - [ ] Fix - [ ] Feature - [ ] Docs - [x] CI / tooling - [ ] Refactor - [ ] Other ## Verification `uv run pytest tests/test_check_commit_messages.py tests/test_commit_lint.py -q` -- 15 passed. Three of those cases are new and cover a repo where the default branch carries a rejected message and the feature branch merges it in: the range fails without an exclusion, passes with one, and still fails when the bad commit is the branch's own. Mutation check on the new cases: replacing `_commit_messages(args)` with `_commit_messages(args[:1])`, which is the behaviour before this change, turns `test_excluding_the_default_branch_skips_the_history_it_carries` red and leaves the other three green. The pin fails when the fix is removed. Against the real history behind #529, with the checker run directly: - `check_commit_messages.py "bc74bf73..a066624"` -- exit 1, naming `b01534d02` exactly as CI does. - the same range plus `^github/main` -- exit 0. The workflow's new loop was run as written against both ranges: 0 commits to lint on the catch-up range, and a planted `Update stuff.` commit on a feature branch still sets the failure flag. `make lint-python` -- clean. `scripts/check_source_language.py` over the branch -- exit 0; the test's non-ASCII message is built from an escape, so the file itself stays ASCII. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed ## Risk Commitlint now starts once per commit instead of once per range, so a PR with many commits spends a second or so more in this job. The exclusion only ever removes commits that are already on the default branch, so nothing a PR introduces escapes either linter. Rollback is reverting this PR; the previous behaviour is one range argument and one commitlint invocation. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A. Unblocks the `commit messages` check on #529. --------- Co-authored-by: gloryfromca <23442919+gloryfromca@users.noreply.github.com> Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
Summary
refactor/ui_web_architecturelast shared a commit withmainat5a29ea22(#451). This merges
main(dbe259f6) into it so the conflicts get settledonce, now, instead of all at once under #475, and so CI runs on the merged tree
rather than on a branch GitHub already reports as
CONFLICTING.Seven paths conflicted. How each was settled:
ui-web/src/features/settings/SettingsPage.tsx, its test, andui-web/src/live/120-settings.js-- deleted on this branch, edited onmain(modify/delete, so git leaves
main's copy in the tree). The rebuilt settingssurface replaces all three; the deletions stand.
i18n/messages.jsonandui-tui/src/i18n/messages.generated.ts--main'sonly change to either since the merge base is fix(*): let the web settings timezone control write, and drop the retired one #506 dropping
gui.set.chn.cron_toandcron_ph(8 and 4 lines, no additions). This branchalready drops the whole
gui.set.chn.*group, so its side is the superset andwins.
raven/skill_hub/hub.py-- each side added one import fromskill_hub.audit.write_install_metais called at line 621,INSTALL_METAat line 699, and both exist in the mergedaudit.py, so bothare imported.
ui-web/build.py--mainrewrote thenewline=""comment, this branchadded
flush=Trueto the print under it. Both kept.What came in quietly and is worth knowing: #506's cron timezone fix auto-merged
into
raven/rpc/methods/console.pyand brought its five cases intests/test_rpc_settings.pywith it. The rebuilt settings pages have no cronsection at all, so on this branch the backend can now write
cron.defaultTimezonecorrectly and nothing on the page does it. Whether thatcontrol gets ported, and in which PR, is a separate decision.
Type
Verification
Run in a worktree on the merged commit:
uv run pytest tests/test_rpc_settings.py tests/test_skill_hub_client.py tests/test_skill_hub_policy.py tests/test_skill_hub_source.py tests/test_skill_hub_tools.py tests/test_i18n_boundary.py -q-- 174 passed.npm testinui-web-- 181 files, 2513 tests, all passed.node ui-tui/scripts/gen-i18n.mjs-- regenerating the TUI catalogue leavesmessages.generated.tsbyte-identical to the resolved file, so the two i18nartifacts agree.
make lint-python-- 1986 files already formatted, clean.make lint-imports-- 10 contracts kept, 0 broken.make build-ui-- page built,boot-snapshot: OKon both snapshots.scripts/check_source_language.pyandscripts/check_large_files.pyoverbc74bf73..HEAD-- both exit 0.The full suite was left to CI.
commit messagesis red, and will be red on any PR that mergesmaininto abranch. Commitlint runs over
<base>..HEAD, which here includes every commitmaingained since the merge base. One of them,b01534d0(#471, merged09-18), carries a
Co-authored-bytrailer with a non-ASCII name, whichscripts/check_commit_messages.pyrejects. That is history onmain; thisbranch neither introduced it nor can rewrite it. Nothing in the ruleset makes
that check blocking.
Risk
This branch is squash-only, so merging this PR flattens the merge commit and
the merge base stays at
5a29ea22. The contents line up either way, but gitwill not know
mainwas taken in, and the next catch-up will re-raise thecontent conflicts in the two large i18n files. Landing it as a real merge
commit instead would move the merge base and avoid that; that needs someone who
can bypass the ruleset.
Rollback is resetting the branch to
bc74bf73; nothing here is a source changeof its own beyond the four resolutions above.
Related Issues
N/A. Unblocks #475.