Repository navigation
fix(*): settle the lint and contract drift the replay left behind - #613
Conversation
Four files the replayed branch landed in a state CI rejects: - probe.py imported verify_agent for a preset branch that no longer exists, now that a preset records its capability snapshot like any other row. - manager.py and test_subagent_probe.py carried blank-line spacing ruff format rewrites; the merge resolutions produced them by hand. - generated.ts predated main's cancelled delegation status, so the committed bindings no longer matched rpc-schema/openrpc.json. Regenerated, which adds 'cancelled' to the three delegated-status unions. Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
There was a problem hiding this comment.
No blockers; this can merge as far as I am concerned.
Reviewed the complete four-file delta at 1972b8180e25. The unused import removal is safe because _test_acp calls record_capabilities, which retains the local verify_agent import. The three generated status unions now match both the Python wire models and rpc-schema/openrpc.json; this is an additive compatibility correction, and gen:check confirms the checked-in client is deterministic. The remaining edits are formatter-only, and no tests were weakened.
Coverage included AGENTS.md/CLAUDE.md and the relevant context map, the diff and surrounding callers, the schema/model/generator history, backward compatibility, test integrity, and the UI-to-runtime wire-schema architecture boundary.
Independent verification: Python lint and format passed across 2034 files; the focused subagent suite passed (465 tests); ty, all 10 import contracts, deptry, and the source-language gate passed; web RPC generation, type-check, and lint passed (zero errors, four pre-existing warnings); and all 188 web test files passed (2464 tests). GitHub merged the PR at this same head while the review was in progress.
…play (#618) ## Summary `unit (py3.12 / ubuntu-latest / 1/4)` is the one red left on main after #613. It fails on the runner and passes on a developer machine, which is the tell: the case reads the host it runs on. Two cases in `TestAutomaticSnapshotVerification` drive `schedule_snapshot_verification` and pin exactly which rows it re-verifies. That sweep walks unconfigured ACP presets as well as configured rows, so both cases patch `_unconfigured_acp_preset_rows` to answer with nothing -- every sibling case in the class does the same. Rebuilding this file around the replay's conflict dropped that one line from each, so the sweep started resolving whatever presets the machine happened to have: nothing here, three (`Claude Code`, `Codex`, `Pi`) on the runner, and `assert recorded == ["old-format"]` collected all four. The line is restored in both. Nothing else changes. ## Type - [x] Fix ## Verification - `uv run pytest tests/test_subagent_probe.py` -- 58 passed. - `make lint-python` -- exit 0. - Line-for-line audit of this file against the branch the cases came from (`refactor/ui_web_architecture`): 67 methods shared, 0 with a missing line. That audit is what found the second occurrence; only the first one had a failing shard to point at it. This cannot be reproduced on this host, which resolves no preset, so the argument is structural rather than a red-to-green run here: the failure was presets being verified, the dropped line is the one that stops that, and it is restored byte-identical to the source these cases came from. CI is the check that closes it. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally ## Risk Test-only. A stub restored to what the rest of its class already uses. Rollback: revert this commit. - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues Follows #613. Co-authored-by: gloryfromca <23442919+gloryfromca@users.noreply.github.com> Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
…'s target (#615) ## Summary Two independent defects a tester hit on a fresh install, both in the page the architecture branch just brought onto main. They are unrelated root causes and sit in one commit each; the PR is one because they are both one-domain fixes to the same page and share a verification run. **1. The rail's import row greeted readers who never imported anything.** The row above Settings was drawn from the importer's state file alone. That file outlives every run that wrote it (`ImportState`: "Entries outlive the run that wrote them"), so any install whose file is not empty showed "memory import finished" to its next reader, whether or not that reader had ever started an import or seen the onboarding wizard. A run the CLI started records no request, so `resumable` is false and the failure count has no retry behind it: the row said "1 did not make it", the click was dead, and the only affordance was the dismiss cross, remembered per browser in localStorage. The rule now: a settled run, finished or stopped short, earns a row only when its click can still act on it, or this page followed the run (asked for one, or found one already in flight). Everything live is unchanged. The wizard's data-sync step now asks for its run through the same store verb the rail's own click uses, instead of calling `import.run` a second way, so a run started from the wizard is one the page is following. Deliberate behaviour change, called out for review: a stopped run that cannot be resumed used to draw a permanently un-clickable "paused" row. It still draws on a page that followed the run; it no longer draws on a page that only found it in the file. Same disease as the finished row (visible, out of reach). Say so if you want that one kept and I will narrow the guard to the finished row. **2. A backticked workspace path opened onto "that file is no longer there".** `livePathOf` turned a path naming the workspace into that path made relative to the workspace, and the chip carried the relative form. The viewer's route used to root a relative path at agent home, which made it resolve; it roots one at the session's working directory now (`_workspace_root`), so a workspace path outside that directory was re-rooted onto the wrong parent. The chip now carries the path as the answer wrote it. The other half of the same function, a file this session changed, has always answered with the path as written, and shortening for display is the panel's own job (`shortPath`). This one predates the rebuilt frontend: the same line shipped in the old `ui-web/src/live/170-workspace.js`. **3. Review follow-up: the follow marker belongs to a run, not to a page.** The first commit's marker was page-lifetime state, so a tab that followed one import and stayed open would draw the receipt of the next run somebody settled from the CLI: set once, never cleared, inherited by any later settled status. It is per run now. A run in flight is `watching`; the read that finds it settled turns that into `followed`, the run's own signature, which is the same identity the dismissal already uses. A settled row draws when its click can act on it or when it IS that run. **4. Review follow-up: a refused import is not a run to follow.** `start` marked the page as watching before `import.run` answered, so a refusal ("nothing to import") left the mark set and the read that follows adopted whatever settled run was already on file. The mark is set only once the gateway says a run started. ## Type - [x] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification Commands and results, all from this branch at `d1a7ef1c2`: ``` ui-web $ npx vitest run Test Files 188 passed (188) Tests 2476 passed (2476) ui-web $ npx tsc --noEmit -p tsconfig.json -> exit 0 ui-web $ npx eslint . -> exit 0 (0 errors, 4 pre-existing warnings) $ npx commitlint --from github/main --to HEAD --config commitlint.config.cjs -> exit 0 $ python scripts/check_commit_messages.py github/main..HEAD -> exit 0 $ python scripts/check_source_language.py github/main..HEAD -> exit 0 $ python scripts/check_large_files.py github/main..HEAD -> exit 0 ``` Reverse verification: neutralising the `stale` guard turns exactly the six new gate cases red, including the one for a later run this page did not follow; marking a refused run as watched again turns the refusal case red; reverting the wizard wiring turns its own case red; reverting the one-line path change turns the three updated resolver cases red. Restored and hash-compared each time. Real-host run, both fixes, on a gateway started against an isolated `RAVEN_HOME` seeded with a leftover import (2 sources, 1 failed, no request recorded) and a real file under the configured workspace. Same gateway, only the built page swapped, `index.html` hashes compared each swap. Import row: | page | rail | | --- | --- | | main (`52eb09b25`) | `importSync-done importSync-warn :: memory import finished / 1 did not make it`, retry button `disabled: true` | | this branch | no row; the rest of the rail draws (re-run after the review follow-up, same result) | | this branch, after adding `tier` + `platforms` to the same leftover | `importSync-paused :: paused / 100%`, button enabled, labelled "resume" | The third row is the control: it proves the page was connected and reading `import.status`, so the empty rail in row two is a decision and not a dead page. Path chip, measured over the live route from the authenticated page, file present at `<home>/workspace/skills/quick-summarize/SKILL.md`: ``` GET /file?path=skills/quick-summarize/SKILL.md&session=s -> 404 <launch-dir>/skills/quick-summarize/SKILL.md GET /file?path=<home>/workspace/skills/quick-summarize/SKILL.md&session=s -> 200 ``` The first form is what the chip carried; the second is what it carries now. Note the same relative request answers 200 with no session, which is why this never showed up in a check that had no conversation open. Not verified end to end: rendering a chip on the real page needs a model turn, so the frontend half is covered by the resolver's own tests and the route half by the measurement above, each read at the layer that owns it. Base history: the branch now sits on `d50a13c1c`, which carries #613 (the lint and contract drift that made `main` red at `52eb09b25`) and #618 (the `test_subagent_probe.py` case that was the one job still red here). Both were `main`'s own failures, reproduced on `main`'s own runs at the same bases, and nothing in this PR touches Python. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed ## Risk User-visible changes, both in the page only. No contract, no backend, no persisted format. - The rail's import row disappears for a settled run this page did not follow. A reader who relied on the leftover row to notice a months-old failed import loses that notice; `raven import status` still reports it, with the per-item reasons the row never carried. - A path chip's target changes from workspace-relative to as-written. A chip that used to resolve only because the session happened to run in the workspace resolves the same way now, since the route resolves an absolute path without re-rooting. Rollback: revert either commit on its own. They touch disjoint files. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A --------- Co-authored-by: gloryfromca <23442919+gloryfromca@users.noreply.github.com> Co-authored-by: Claude (claude-opus-5[1m]) <noreply@anthropic.com>
…n config (#614) ## Summary EverOS's four memory roles -- llm, embedding, rerank and multimodal -- lived in `~/.everos/raven/everos.toml`, a file raven wrote and EverOS read once at startup. Three defects followed from that arrangement and none was fixable in place: - a model changed on the settings page did not take effect until something unrelated restarted the service, with nothing on screen saying so; - the rerank section needs a request shape (`deepinfra` / `vllm` / `dashscope`) that only the wizard's own vendor table knew, so reranking configured from the settings page posted to the wrong path against DeepInfra and SiliconFlow; - an EverOS root the user manages is read-only to raven, so those users had no way to configure a role at all. raven now records a **pin** per role -- a model and the vendor serving it, never a credential -- and resolves the address and key from that vendor at the moment they are needed. Rotating a key is one edit, and every role on that vendor follows. The values reach EverOS as `EVEROS_*` variables, emitted on every spawn and bound into raven's own process so the in-process multimodal tool reads the same models the service does. `everos.toml` keeps its runtime knobs, which EverOS merges per key; raven writes only `[api]` there now. **A save applies itself.** The spawn precheck runs before anything is stopped, so a restart that cannot succeed leaves the old server serving; a stop that does not finish never falls through to the spawn, which would otherwise adopt the old server and report success for a configuration that never took. The outcome reaches every session waiting on it as `memory.health` -- success included, since that frame is the only thing that clears the standing banner. **Upgrades migrate themselves.** An install upgrading into this still has its roles in the file. The migration runs on the first memory start: it names the vendor from the address and the key, creates the provider row when raven carries none, and leaves a role it cannot name unset with a notice rather than guessing. It is idempotent and never edits `everos.toml`. Measured against the real shape on a maintainer's machine, where two of the four roles point at DeepInfra, a vendor raven carries no provider spec for. Key decisions, recorded in `docs/specs/2026-09-21-everos-role-config-design.md`: - the pin holds no credential, so nothing on the settings wire carries one in either direction; - an operator who exports `EVEROS_<ROLE>__*` still outranks raven, and the slot says so rather than accepting a save that would never take effect; - the rerank request shape comes from the vendor table where it is known, and is recorded on the role only for a self-hosted endpoint no table can answer for; - the migration is scheduled from the plugin, not from raven's config migrations, because the host may know this plugin only through the plugin contract. Rebased onto `main` after #613, which settles the lint and contract drift the branch replay left behind. This branch briefly carried its own partial fix for that; it is dropped, and #613's is the one in force. ## Type - [x] Refactor ## Verification ``` uv run --all-extras pytest -q # 24517 passed, 4 failed, 111 skipped -- the 4 are pre-existing, confirmed by # checking out the branch base in the same environment and re-running them there uv run pytest <the 28 files that reference the changed modules> -q # 1516 passed, 1 skipped npm --prefix ui-web test # 2496 passed, 191 files npm --prefix ui-web run gen:check # generated.ts matches the contract (192 methods) npm --prefix ui-tui run lint:rpc # in sync npm --prefix ui-tui run lint:i18n # catalogue up to date uv run ruff check raven/ plugins-dist/ tests/ # All checks passed uv run ruff format --check raven/ plugins-dist/ tests/ # 1835 files already formatted make check-large-files # exit 0 ``` A real gateway was started on a fixture built from a maintainer's own config in the pre-upgrade shape. The migration moved all four roles, created the `deepinfra` provider row, resolved reranking to `/v1/inference` rather than the chat endpoint, and left `everos.toml` byte-identical (sha256 unchanged). That run is also what found the defect described in the next section. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed The remaining real-host acceptance cases in `docs/specs/2026-09-21-everos-role-config-acceptance.md` have not been run yet; they are the next step and will be reported on this PR. ## Risk User-visible changes: - the four role slots on the settings page now show the vendor as stored rather than deriving it from an address, and offer every vendor that can serve the role; - a save restarts the memory service, which takes a few seconds and reports its outcome in the standing banner; - a role whose endpoint comes from exported `EVEROS_*` variables is read-only, and so are all four on a root the user manages; - reranking on a self-hosted endpoint asks for its request shape once. Rollback: revert the branch. The migration only ever adds to raven's config and never edits `everos.toml`, so an install that has migrated still has its original file and falls back to it. One defect worth naming, because it was invisible to every unit test: an embedding role with no width pinned emitted `EVEROS_EMBEDDING__DIMENSIONS=""`, which EverOS's own settings model refuses as an integer -- the server exited before serving anything. Fixed, and the case that catches it now builds EverOS's real `Settings` from the emitted environment in a subprocess, which is the layer that owns the fact. - [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 CI went red on main after #612 landed. Four files, three causes, all of them drift the replay left rather than anything the rebuilt branch decides: - `probe.py` still imported `verify_agent` for the preset branch that the replay removed, once a preset started recording its capability snapshot like any other row. Ruff calls the import unused, and it is. - `manager.py` and `test_subagent_probe.py` carry blank-line spacing that `ruff format` rewrites. The merge resolutions produced those two by hand. - `ui-web/src/rpc/generated.ts` predated main's `cancelled` delegation status, so the committed bindings stopped matching `rpc-schema/openrpc.json`. Regenerated with `npm run gen`, which adds `cancelled` to the three delegated-status unions. ## Type - [x] Fix ## Verification - `make lint-python` -- exit 0 (ruff check and ruff format, 2034 files). - `make lint-types` -- exit 0. `make lint-imports` -- 10 contracts kept. `make lint-deps` -- no dependency issues. - `npm run gen:check` in ui-web -- generated.ts matches the contract, 192 methods. - `npm run type-check` in ui-web -- exit 0. `npm run lint` -- 0 errors. - `npm test` in ui-web -- 188 files, 2464 tests, all pass. - `uv run pytest tests/test_subagent_acp.py tests/test_subagent_probe.py tests/test_subagent_manager.py` -- 465 passed. The fourth red on that push, `check_source_language.py`, is not addressed here and does not need to be: it fired on the four CJK lines in `ui-web/src/rpc/fixtures/sessions.ts` that arrived with #591, which were added lines relative to the pre-merge main. They are carried lines now, so the gate passes on this range (`check_source_language.py 52eb09b..HEAD` exits 0) and on every push after it. Whether that text should be translated or covered by an exemption zone under AGENTS.md section 1.3 is a separate call. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally ## Risk `generated.ts` gains a status value the schema already declared; nothing reads a narrower union. The other three changes are an unused import and whitespace. Rollback: revert this commit; main returns to the state that fails the three checks above. - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues Follows #612. Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
…play (#618) ## Summary `unit (py3.12 / ubuntu-latest / 1/4)` is the one red left on main after #613. It fails on the runner and passes on a developer machine, which is the tell: the case reads the host it runs on. Two cases in `TestAutomaticSnapshotVerification` drive `schedule_snapshot_verification` and pin exactly which rows it re-verifies. That sweep walks unconfigured ACP presets as well as configured rows, so both cases patch `_unconfigured_acp_preset_rows` to answer with nothing -- every sibling case in the class does the same. Rebuilding this file around the replay's conflict dropped that one line from each, so the sweep started resolving whatever presets the machine happened to have: nothing here, three (`Claude Code`, `Codex`, `Pi`) on the runner, and `assert recorded == ["old-format"]` collected all four. The line is restored in both. Nothing else changes. ## Type - [x] Fix ## Verification - `uv run pytest tests/test_subagent_probe.py` -- 58 passed. - `make lint-python` -- exit 0. - Line-for-line audit of this file against the branch the cases came from (`refactor/ui_web_architecture`): 67 methods shared, 0 with a missing line. That audit is what found the second occurrence; only the first one had a failing shard to point at it. This cannot be reproduced on this host, which resolves no preset, so the argument is structural rather than a red-to-green run here: the failure was presets being verified, the dropped line is the one that stops that, and it is restored byte-identical to the source these cases came from. CI is the check that closes it. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally ## Risk Test-only. A stub restored to what the rest of its class already uses. Rollback: revert this commit. - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues Follows #613. Co-authored-by: gloryfromca <23442919+gloryfromca@users.noreply.github.com> Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
…'s target (#615) ## Summary Two independent defects a tester hit on a fresh install, both in the page the architecture branch just brought onto main. They are unrelated root causes and sit in one commit each; the PR is one because they are both one-domain fixes to the same page and share a verification run. **1. The rail's import row greeted readers who never imported anything.** The row above Settings was drawn from the importer's state file alone. That file outlives every run that wrote it (`ImportState`: "Entries outlive the run that wrote them"), so any install whose file is not empty showed "memory import finished" to its next reader, whether or not that reader had ever started an import or seen the onboarding wizard. A run the CLI started records no request, so `resumable` is false and the failure count has no retry behind it: the row said "1 did not make it", the click was dead, and the only affordance was the dismiss cross, remembered per browser in localStorage. The rule now: a settled run, finished or stopped short, earns a row only when its click can still act on it, or this page followed the run (asked for one, or found one already in flight). Everything live is unchanged. The wizard's data-sync step now asks for its run through the same store verb the rail's own click uses, instead of calling `import.run` a second way, so a run started from the wizard is one the page is following. Deliberate behaviour change, called out for review: a stopped run that cannot be resumed used to draw a permanently un-clickable "paused" row. It still draws on a page that followed the run; it no longer draws on a page that only found it in the file. Same disease as the finished row (visible, out of reach). Say so if you want that one kept and I will narrow the guard to the finished row. **2. A backticked workspace path opened onto "that file is no longer there".** `livePathOf` turned a path naming the workspace into that path made relative to the workspace, and the chip carried the relative form. The viewer's route used to root a relative path at agent home, which made it resolve; it roots one at the session's working directory now (`_workspace_root`), so a workspace path outside that directory was re-rooted onto the wrong parent. The chip now carries the path as the answer wrote it. The other half of the same function, a file this session changed, has always answered with the path as written, and shortening for display is the panel's own job (`shortPath`). This one predates the rebuilt frontend: the same line shipped in the old `ui-web/src/live/170-workspace.js`. **3. Review follow-up: the follow marker belongs to a run, not to a page.** The first commit's marker was page-lifetime state, so a tab that followed one import and stayed open would draw the receipt of the next run somebody settled from the CLI: set once, never cleared, inherited by any later settled status. It is per run now. A run in flight is `watching`; the read that finds it settled turns that into `followed`, the run's own signature, which is the same identity the dismissal already uses. A settled row draws when its click can act on it or when it IS that run. **4. Review follow-up: a refused import is not a run to follow.** `start` marked the page as watching before `import.run` answered, so a refusal ("nothing to import") left the mark set and the read that follows adopted whatever settled run was already on file. The mark is set only once the gateway says a run started. ## Type - [x] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification Commands and results, all from this branch at `d1a7ef1c2`: ``` ui-web $ npx vitest run Test Files 188 passed (188) Tests 2476 passed (2476) ui-web $ npx tsc --noEmit -p tsconfig.json -> exit 0 ui-web $ npx eslint . -> exit 0 (0 errors, 4 pre-existing warnings) $ npx commitlint --from github/main --to HEAD --config commitlint.config.cjs -> exit 0 $ python scripts/check_commit_messages.py github/main..HEAD -> exit 0 $ python scripts/check_source_language.py github/main..HEAD -> exit 0 $ python scripts/check_large_files.py github/main..HEAD -> exit 0 ``` Reverse verification: neutralising the `stale` guard turns exactly the six new gate cases red, including the one for a later run this page did not follow; marking a refused run as watched again turns the refusal case red; reverting the wizard wiring turns its own case red; reverting the one-line path change turns the three updated resolver cases red. Restored and hash-compared each time. Real-host run, both fixes, on a gateway started against an isolated `RAVEN_HOME` seeded with a leftover import (2 sources, 1 failed, no request recorded) and a real file under the configured workspace. Same gateway, only the built page swapped, `index.html` hashes compared each swap. Import row: | page | rail | | --- | --- | | main (`52eb09b25`) | `importSync-done importSync-warn :: memory import finished / 1 did not make it`, retry button `disabled: true` | | this branch | no row; the rest of the rail draws (re-run after the review follow-up, same result) | | this branch, after adding `tier` + `platforms` to the same leftover | `importSync-paused :: paused / 100%`, button enabled, labelled "resume" | The third row is the control: it proves the page was connected and reading `import.status`, so the empty rail in row two is a decision and not a dead page. Path chip, measured over the live route from the authenticated page, file present at `<home>/workspace/skills/quick-summarize/SKILL.md`: ``` GET /file?path=skills/quick-summarize/SKILL.md&session=s -> 404 <launch-dir>/skills/quick-summarize/SKILL.md GET /file?path=<home>/workspace/skills/quick-summarize/SKILL.md&session=s -> 200 ``` The first form is what the chip carried; the second is what it carries now. Note the same relative request answers 200 with no session, which is why this never showed up in a check that had no conversation open. Not verified end to end: rendering a chip on the real page needs a model turn, so the frontend half is covered by the resolver's own tests and the route half by the measurement above, each read at the layer that owns it. Base history: the branch now sits on `d50a13c1c`, which carries #613 (the lint and contract drift that made `main` red at `52eb09b25`) and #618 (the `test_subagent_probe.py` case that was the one job still red here). Both were `main`'s own failures, reproduced on `main`'s own runs at the same bases, and nothing in this PR touches Python. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed ## Risk User-visible changes, both in the page only. No contract, no backend, no persisted format. - The rail's import row disappears for a settled run this page did not follow. A reader who relied on the leftover row to notice a months-old failed import loses that notice; `raven import status` still reports it, with the per-item reasons the row never carried. - A path chip's target changes from workspace-relative to as-written. A chip that used to resolve only because the session happened to run in the workspace resolves the same way now, since the route resolves an absolute path without re-rooting. Rollback: revert either commit on its own. They touch disjoint files. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A --------- Co-authored-by: gloryfromca <23442919+gloryfromca@users.noreply.github.com> Co-authored-by: Claude (claude-opus-5[1m]) <noreply@anthropic.com>
…n config (#614) ## Summary EverOS's four memory roles -- llm, embedding, rerank and multimodal -- lived in `~/.everos/raven/everos.toml`, a file raven wrote and EverOS read once at startup. Three defects followed from that arrangement and none was fixable in place: - a model changed on the settings page did not take effect until something unrelated restarted the service, with nothing on screen saying so; - the rerank section needs a request shape (`deepinfra` / `vllm` / `dashscope`) that only the wizard's own vendor table knew, so reranking configured from the settings page posted to the wrong path against DeepInfra and SiliconFlow; - an EverOS root the user manages is read-only to raven, so those users had no way to configure a role at all. raven now records a **pin** per role -- a model and the vendor serving it, never a credential -- and resolves the address and key from that vendor at the moment they are needed. Rotating a key is one edit, and every role on that vendor follows. The values reach EverOS as `EVEROS_*` variables, emitted on every spawn and bound into raven's own process so the in-process multimodal tool reads the same models the service does. `everos.toml` keeps its runtime knobs, which EverOS merges per key; raven writes only `[api]` there now. **A save applies itself.** The spawn precheck runs before anything is stopped, so a restart that cannot succeed leaves the old server serving; a stop that does not finish never falls through to the spawn, which would otherwise adopt the old server and report success for a configuration that never took. The outcome reaches every session waiting on it as `memory.health` -- success included, since that frame is the only thing that clears the standing banner. **Upgrades migrate themselves.** An install upgrading into this still has its roles in the file. The migration runs on the first memory start: it names the vendor from the address and the key, creates the provider row when raven carries none, and leaves a role it cannot name unset with a notice rather than guessing. It is idempotent and never edits `everos.toml`. Measured against the real shape on a maintainer's machine, where two of the four roles point at DeepInfra, a vendor raven carries no provider spec for. Key decisions, recorded in `docs/specs/2026-09-21-everos-role-config-design.md`: - the pin holds no credential, so nothing on the settings wire carries one in either direction; - an operator who exports `EVEROS_<ROLE>__*` still outranks raven, and the slot says so rather than accepting a save that would never take effect; - the rerank request shape comes from the vendor table where it is known, and is recorded on the role only for a self-hosted endpoint no table can answer for; - the migration is scheduled from the plugin, not from raven's config migrations, because the host may know this plugin only through the plugin contract. Rebased onto `main` after #613, which settles the lint and contract drift the branch replay left behind. This branch briefly carried its own partial fix for that; it is dropped, and #613's is the one in force. ## Type - [x] Refactor ## Verification ``` uv run --all-extras pytest -q # 24517 passed, 4 failed, 111 skipped -- the 4 are pre-existing, confirmed by # checking out the branch base in the same environment and re-running them there uv run pytest <the 28 files that reference the changed modules> -q # 1516 passed, 1 skipped npm --prefix ui-web test # 2496 passed, 191 files npm --prefix ui-web run gen:check # generated.ts matches the contract (192 methods) npm --prefix ui-tui run lint:rpc # in sync npm --prefix ui-tui run lint:i18n # catalogue up to date uv run ruff check raven/ plugins-dist/ tests/ # All checks passed uv run ruff format --check raven/ plugins-dist/ tests/ # 1835 files already formatted make check-large-files # exit 0 ``` A real gateway was started on a fixture built from a maintainer's own config in the pre-upgrade shape. The migration moved all four roles, created the `deepinfra` provider row, resolved reranking to `/v1/inference` rather than the chat endpoint, and left `everos.toml` byte-identical (sha256 unchanged). That run is also what found the defect described in the next section. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed The remaining real-host acceptance cases in `docs/specs/2026-09-21-everos-role-config-acceptance.md` have not been run yet; they are the next step and will be reported on this PR. ## Risk User-visible changes: - the four role slots on the settings page now show the vendor as stored rather than deriving it from an address, and offer every vendor that can serve the role; - a save restarts the memory service, which takes a few seconds and reports its outcome in the standing banner; - a role whose endpoint comes from exported `EVEROS_*` variables is read-only, and so are all four on a root the user manages; - reranking on a self-hosted endpoint asks for its request shape once. Rollback: revert the branch. The migration only ever adds to raven's config and never edits `everos.toml`, so an install that has migrated still has its original file and falls back to it. One defect worth naming, because it was invisible to every unit test: an embedding role with no width pinned emitted `EVEROS_EMBEDDING__DIMENSIONS=""`, which EverOS's own settings model refuses as an integer -- the server exited before serving anything. Fixed, and the case that catches it now builds EverOS's real `Settings` from the emitted environment in a subprocess, which is the layer that owns the fact. - [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
CI went red on main after #612 landed. Four files, three causes, all of them
drift the replay left rather than anything the rebuilt branch decides:
probe.pystill importedverify_agentfor the preset branch that thereplay removed, once a preset started recording its capability snapshot like
any other row. Ruff calls the import unused, and it is.
manager.pyandtest_subagent_probe.pycarry blank-line spacing thatruff formatrewrites. The merge resolutions produced those two by hand.ui-web/src/rpc/generated.tspredated main'scancelleddelegation status,so the committed bindings stopped matching
rpc-schema/openrpc.json.Regenerated with
npm run gen, which addscancelledto the threedelegated-status unions.
Type
Verification
make lint-python-- exit 0 (ruff check and ruff format, 2034 files).make lint-types-- exit 0.make lint-imports-- 10 contracts kept.make lint-deps-- no dependency issues.npm run gen:checkin ui-web -- generated.ts matches the contract, 192 methods.npm run type-checkin ui-web -- exit 0.npm run lint-- 0 errors.npm testin ui-web -- 188 files, 2464 tests, all pass.uv run pytest tests/test_subagent_acp.py tests/test_subagent_probe.py tests/test_subagent_manager.py-- 465 passed.The fourth red on that push,
check_source_language.py, is not addressed hereand does not need to be: it fired on the four CJK lines in
ui-web/src/rpc/fixtures/sessions.tsthat arrived with #591, which were addedlines relative to the pre-merge main. They are carried lines now, so the gate
passes on this range (
check_source_language.py 52eb09b25..HEADexits 0) andon every push after it. Whether that text should be translated or covered by an
exemption zone under AGENTS.md section 1.3 is a separate call.
Risk
generated.tsgains a status value the schema already declared; nothing readsa narrower union. The other three changes are an unused import and whitespace.
Rollback: revert this commit; main returns to the state that fails the three
checks above.
Related Issues
Follows #612.