Repository navigation
refactor(plugins): serve the four EverOS memory roles from raven's own config - #614
Conversation
3ed4b35 to
4e24395
Compare
gloryfromca
left a comment
There was a problem hiding this comment.
Blocking: two settings controls have no successful backend path.
I reviewed the full github/main...HEAD change and the docs-only follow-up at the current head; that follow-up does not affect either finding. Coverage included the AGENTS/CLAUDE and CONTEXT rules, the diff and commit sequence, role migration and backward compatibility, provider resolution, process restart, RPC/schema and generated clients, web callers, plugin/host architecture boundaries, and whether tests were weakened. The focused tests are green, but the two inline cases fall between them: one test proves a self-hosted rerank provider is offered without selecting it, and no test asserts that a required role lacks a clear control.
Verification:
uv run pytest tests/test_everos_config.py tests/test_config_update_everos.py tests/test_config_update_providers.py tests/test_everos_backend.py tests/test_everos_server.py tests/test_rpc_settings.py tests/test_rpc_contract_shapes.py tests/test_plugin_boundary.py -q: 639 passed.npm test -- --run src/features/settings/providers/Roles.test.tsx src/features/settings/source.test.ts: 33 passed.npm run gen:checkandnpm run type-check: passed.npm run lint: passed with four pre-existing warnings outside this diff.- Source-language, large-file, and
git diff --checkgates passed for the current head.
The first Python attempt ran before the declared EverOS dev extra was installed and failed collection with missing raven_everos/everos; after uv sync --frozen --extra dev --dev, the same suite produced the passing result above.
gloryfromca
left a comment
There was a problem hiding this comment.
Blocking: the two existing settings-path defects still have to change.
This revision rebases the same substantive change onto the newer main; the only net tree delta from the previously reviewed head is an unrelated three-line comment removal in raven/agent/subagent/probe.py. Re-checking the full diff and both affected call chains shows the existing findings still stand unchanged: Roles.tsx still offers self-hosted rerank providers without passing the protocol that settings_everosSet requires, and it still renders a clear control for embedding while the RPC rejects clearing every REQUIRED_ROLES member. The existing inline threads remain the complete blockers, so no duplicate findings are added and neither thread is resolved.
Review coverage: repository rules and domain terms; full diff and replay history; UI callers through RPC validation and plugin writers; legacy migration and backward compatibility; generated wire clients; restart lifecycle and plugin/host architecture boundaries; and whether tests were weakened or merely missed these interactions.
Verification on this head:
- Focused Python suite: 639 passed.
- Relevant web suite: 33 passed.
- Web RPC generation and TypeScript checks: passed.
- Source-language, large-file, and diff checks: passed.
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; this can merge as far as I am concerned.
The two earlier failures are fixed at this revision: a fresh self-hosted rerank choice is withheld until the wizard has recorded its request shape, while an existing pin stays editable; and clearability now follows the required-role contract returned by the server. Both reviewer-owned threads are resolved.
Coverage included the AGENTS/CLAUDE rules and the mapped Runtime, Web UI, and TUI context constraints; the 4e24395..8eff440 fix delta in the context of github/main...HEAD; affected RPC/UI callers and provider auth-shape mapping; commit history; backward compatibility of the defaulted wire field; confirmation that tests were added rather than weakened; and the shared RPC-schema boundary across both generated clients.
Verification: uv run pytest tests/test_everos_config.py tests/test_config_update_everos.py tests/test_config_update_providers.py tests/test_everos_backend.py tests/test_everos_server.py tests/test_rpc_settings.py tests/test_rpc_contract_shapes.py tests/test_plugin_boundary.py -q (639 passed); focused Vitest role/source suite (35 passed); npm run gen:check; npm run type-check; source-language and large-file gates; and git diff --check github/main...HEAD.
EverOS's llm, embedding, rerank and multimodal settings lived in `~/.everos/raven/everos.toml`, which raven wrote and EverOS read at startup. Three things followed and none was fixable in place: a model changed on the settings page did not take effect until something unrelated restarted the service; the rerank section needed a request shape nothing but the wizard knew, so reranking configured from the page posted to the wrong path; and a root the user manages is read-only, so those users could not 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. 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. The file keeps its runtime knobs, which EverOS merges per key; raven writes only `[api]` there now. An install upgrading into this still has its roles in the file. `migrate_roles` moves them on the first memory start: it names the vendor from the address and the key, creates the provider row when raven carries none -- DeepInfra is that case on a real machine, for two of the four roles -- and leaves a role it cannot name unset with a notice rather than guessing. Idempotent, and it never edits everos.toml. Scheduled from the plugin rather than from raven's config migrations: the host may know this plugin only through the plugin contract. Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
`settings.everosSet` took loose fields and a `borrow_from` that copied a lender's key into the EverOS file. It takes a pair now -- model and provider -- plus the rerank request shape for a self-hosted endpoint, which is the one thing no table can answer for. No credential is on this wire in either direction. The save applies itself. `restart_for_config_change` runs the spawn precheck before it stops anything, 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 -- that frame is the only thing that clears the standing banner. Two saves in flight serialise and end at the last one written. With no session connected the restart is refused synchronously rather than run with nowhere to report. The role slots stop reverse-looking-up a vendor from an address, which answered blank for any endpoint that did not match one character for character. They offer every vendor that can serve the role -- the curated table's job is the rerank shape, not the guest list -- and go read-only for a root the user manages or a role set from exported variables, both of which outrank anything saved here. Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
CONTEXT.md gains the three terms the change introduces: EverOS role, role pin, and rerank protocol -- the last with the confusion that made it worth naming, since raven's `provider` means a vendor and EverOS's means a request shape. The design document carries the constraints and the numbered acceptance items; the companion carries the cases that check them on a real host. Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
C9 said all four roles are emitted on every spawn. A role an operator exported for themselves is skipped whole instead -- blanking what somebody else set is the one thing worse than ignoring it. C14 said raven's config loader schedules the migration. It cannot: the host may know this plugin only through the plugin contract, so the schedule lives in the backend's own start. Both were changed while building and recorded there; the spec is where someone reads them. Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
Both were reachable from the picker and both ended in an error toast. The rerank slot offered a self-hosted vendor, and a first save of one is refused: reranking against somebody's own box needs a request shape no vendor table can name, and this page has nowhere to ask for one. It now offers such a vendor only when that vendor is already pinned there, which the wizard does and which keeps the model editable afterwards. The clear control was drawn on every populated role, including embedding, whose clear the write refuses -- every stored vector was written under it. The page was mirroring which roles are required and had drifted, so the server sends the list instead. Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
8eff440 to
27f6fa2
Compare
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; this can merge as far as I am concerned.
This revision rebases the already reviewed five-commit series onto current main (d50a13c1c). git range-diff reports all five commits patch-equivalent, and the trees differ from the prior reviewed head only by the upstream test-stub repair now present on main. The two earlier findings remain fixed and their threads remain resolved.
Coverage rechecked the repository rules and mapped Runtime/Web/TUI constraints, github/main...HEAD, callers and history, wire backward compatibility, test changes, and the shared RPC-schema architecture boundary. No tests were weakened in the rebase.
Verification: focused EverOS/RPC/backend suite (639 passed); tests/test_subagent_probe.py (58 passed); focused web role/source suite (35 passed); generated RPC contract check; TypeScript type-check; source-language and large-file gates; and git diff --check github/main...HEAD.
The diff-coverage gate named them, and each one is a path a caller depends on being absorbed rather than raised: the spawn precheck and the stop asked directly, a restart chain cancelled or raising mid-flight, a session whose socket closed while the outcome was being reported, the four refusals `settings.everosSet` owes a reader, a vendor lookup that cannot read the config, a pin naming a provider raven no longer carries, and a missing host import. Two of them passed for the wrong reason first: `resolve_role` returns None before it reaches either the provider lookup or the import when no pin exists, so both cases had to pin one and assert it resolves before breaking what they name. Also drops the wizard's "a hand-typed address with no vendor" branch. Every entry in the source picker is a vendor now, including the self-hosted slots, so nothing reaches it. 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 27f6fa2..8b41c14 delta in the context of github/main...HEAD. The new commit adds direct coverage for the role migration, restart, validation, and failure-reporting paths. Its only production change removes an obsolete missing-provider branch: the picker has one successful return path, that path always carries the selected vendor as provider, and every back/cancel path exits before the read. The earlier reviewer-owned threads remain resolved.
Coverage included the AGENTS/CLAUDE rules and mapped Runtime/Web/TUI constraints, the diff and affected callers, commit history, backward compatibility, confirmation that assertions were added rather than weakened, and architecture boundaries. The RPC wire schema and both generated clients are unchanged.
Verification: uv run pytest tests/test_cli_onboard_commands.py tests/test_everos_backend.py tests/test_everos_config.py tests/test_everos_server.py tests/test_rpc_settings.py -q (752 passed); source-language and large-file gates; and git diff --check github/main...HEAD.
The case demanded exactly two everos pids across two saves, on the reasoning that a queued save is absorbed into the running restart. It cannot be: a save that arrives after a chain has read the configuration needs a chain of its own, or the older configuration stays in force. Three pids is the right outcome. What A21 asks is that the two chains never run at the same time and that the survivor booted with the second save's value. Both are observable in the chains' own logged intervals, which is what the case reads now. Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
9f04eca to
ccf650f
Compare
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; this can merge as far as I am concerned.
The current head is tree-identical to the reviewed 9f04ecab revision; only the commit subject was amended. The acceptance update now matches A21 and the implementation: quick successive saves queue serialized restart chains, the later chain rereads the final configuration, and a third PID is expected when that later chain is needed to apply the second save.
Coverage included the repository rules and mapped Runtime context, the one-line github/main...HEAD diff, restart callers and history, compatibility, confirmation that no tests were weakened, and the existing RPC/plugin architecture boundary.
Verification: uv run pytest tests/test_rpc_settings.py::TestTheSaveRestartsTheService::test_two_saves_run_one_after_the_other_and_the_last_one_wins -q (1 passed); source-language and large-file gates; and git diff --check github/main...HEAD.
T6.5 asked for two halves on the settings page. The second one, a key-set indicator on the role card that follows the provider's credential, has no element to observe: Roles.tsx reads model, provider, env_managed, supports and owned from the contract and never api_key_set. A18's own check column is "the RPC response against a provider with and without a key", which is T6.4, so the case was stricter than the item it verifies. The first half ran on a real host and passed: with every non-custom provider carrying apiBase null, all four role cards render their stored provider, where the old reverse lookup drew blank. Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; suggestions only, and they are marked inline.
The T6.5 correction is directionally right: the page has no credential-state element, so that half could not be observed there, and the implementation/tests still enforce the RPC contract.
Named nonblocking follow-up: after removing that half from T6.5, A18's api_key_set behavior has no real-host observation. T6.4 is marked contract, and this document says contract cases do not constitute acceptance; because Roles.tsx ignores api_key_set, a live gateway could report the wrong value while T6.5 still passes. The later statement that A18 is accepted on a real host is therefore no longer true. Either observe api_key_set from the live socket in a real-host case or state that this half of A18 remains contract-only. This does not block the implementation or merge.
Coverage included the repository rules and Runtime/Web context, the one-line github/main...HEAD diff, the role-card caller, A18/T6.4/T6.5 mappings and history, compatibility, confirmation that no tests were weakened, and the RPC/UI boundary.
Verification: the two focused RPC credential-state tests passed; the Roles UI suite passed (20 tests); source-language and large-file gates passed; and git diff --check github/main...HEAD passed.
Removing T6.5's unobservable half left api_key_set with only the contract case T6.4, and this document says a contract case does not constitute acceptance on its own. Roles.tsx never reads the field, so a live gateway could have reported it wrongly with nothing watching. T6.6 reads it where it is computed: settings.everos over the page's own websocket, before and after model.disconnect on that provider, with no edit to any role. Run on a real host: all four roles read true, then all four read false, and not one role block on disk changed between the two readings. The same pair also shows rerank.provider holding a vendor and never a request shape. Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
|
The premise checks out, and it was a hole I opened. Removing T6.5's second half left Taking the first of your two options, in Run on a real host just now -- a seeded and the role blocks on disk are byte-identical across the two readings -- no edit to any role, which is what the flag has to mean if it means anything. The same pair also covers A18's other clause: The coverage note is corrected too: A18's real-host case is now named as T6.6, with the reason spelled out -- T6.5 only covers the rendered provider name, and the page does not read The probe is re-runnable by anyone: |
|
No blockers; this can merge as far as I am concerned. Verified at 111c7de. T6.6 now observes both halves of A18 through the running gateway: |
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; this can merge as far as I am concerned.
T6.6 closes the only follow-up from the prior revision. It observes A18 at the live RPC boundary before and after the page's own disconnect operation, verifies that the credential flag follows the provider rather than a role edit, and confirms that rerank's provider remains the vendor rather than the EverOS protocol. The coverage table now correctly names T6.6 as A18's real-host case.
Coverage included the repository rules and Runtime/Web context, the 33b6a6c..111c7de6 delta in github/main...HEAD, RPC/UI callers and history, compatibility, confirmation that no tests were weakened, and the architecture boundary. The author's supplied real-host evidence was checked against the documented trigger and observation point. The earlier reviewer-owned threads remain resolved.
Verification: the two focused RPC credential-state tests passed; source-language and large-file gates passed; and git diff --check github/main...HEAD passed.
## Summary Follow-up to #614, which merged with a blocking finding open. Two consequences of one shape, both confirmed by reproducing them on the merged branch before anything was touched: a rule enforced at one door while another door reaches the same operation. **`clear_role` never consulted `REQUIRED_ROLES`.** The RPC door did, and refused to clear `llm` or `embedding`. The wizard reached the same function through its own older answer, whose table says `optional: true` for embedding -- which answers "may be left unset", not "may be erased". Clicking Skip on a configured embedding role called `set_embedding_endpoint({"model": "", "provider": ""})` and emptied raven's own top-level block, the one endpoint every knowledge base embeds with. Reproduced: ``` before {'model': 'text-embedding-3-small', 'provider': 'openrouter'} CONTROL clear_role(rerank) block unchanged SUBJECT clear_role(embedding) not refused; block = None log: "config/update: embedding endpoint cleared" ``` The rule now lives with the operation. `clear_role` raises `RoleRequiredError`; the RPC translates it instead of restating it, so there is one rule rather than two copies that drifted; the wizard stops offering Skip for a role that is set and required, while still offering it for one that is merely unset. A test that asserted the old behaviour is replaced -- it pinned the defect as the contract. **`restart_for_config_change` never asked `everos_owned`.** `_require_owned` sits on the write primitives "so a new caller cannot opt out"; the restart is a new caller and is not a write primitive, so it did. The embedding role is exempt from the ownership gate by design, so a save of it on a user-managed root fell through to `stop_for_reload` -> `stop_pid`, which SIGTERMs whatever `lock_holder` names for that root: the user's own server. Reproduced as far as the exemption, which is the part that can be driven without harming a running service: ``` owned=False llm rerank multimodal -> REFUSED (EverosRootNotOwnedError) embedding -> got past the ownership gate ``` That contradicts this feature's own written rule three times: `everos_owned`'s docstring ("never start or stop the process"), the file header, and C13 in the design document. The chain now refuses before it stops anything, and says the root is theirs to restart. Also, smaller, all named in the same review: the design document used "role block", which the CONTEXT entry it ships beside puts on its `_Avoid_` list; the vendor table spelled the rerank protocol `rerank_provider`, the word that entry says to avoid, while EverOS's own `rerank.provider` field and the `EVEROS_RERANK__PROVIDER` variable keep their names; and A18 stated a criterion that is false about the code, since the protocol legitimately reads `deepinfra` and `dashscope` where it belongs. ## Type - [x] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification - `uv run --frozen --python 3.12 --all-extras pytest tests/test_everos_config.py tests/test_everos_backend.py tests/test_everos_server.py tests/test_everos_discover.py tests/test_config_update_everos.py tests/test_config_update_providers.py tests/test_plugin_boundary.py tests/test_rpc_contract_shapes.py tests/test_rpc_settings.py tests/test_cli_onboard_commands.py -q` -- 1018 passed, 1 skipped - `ruff check raven/ tests/ plugins-dist/` -- all checks passed - `ruff format --check raven/ tests/ plugins-dist/` -- 1839 files already formatted - `make check-source-language` over this range -- clean Each fix was mutated back to the defect to confirm the new cases bite: - `clear_role` stops consulting `REQUIRED_ROLES` -- reddens the config case and the RPC case, which is what says the RPC now depends on the one rule rather than carrying a second copy - the wizard offers Skip for a required role again -- reddens the wizard case - the restart stops asking `everos_owned` -- reddens the unowned-root case Each mutation was reverted and the file hash compared afterwards. ## Risk User-visible: Skip disappears from the wizard menu for an embedding role that is already configured, and a save of the embedding role against a user-managed root now reports that the server was not restarted instead of restarting it. Both are the behaviour the feature's own documents already describe. Rollback: one commit, reverts cleanly. ## Related Issues Follow-up to #614. --------- Co-authored-by: gloryfromca <23442919+gloryfromca@users.noreply.github.com> Co-authored-by: Claude (claude-opus-5) <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 Follow-up to #614, which merged with a blocking finding open. Two consequences of one shape, both confirmed by reproducing them on the merged branch before anything was touched: a rule enforced at one door while another door reaches the same operation. **`clear_role` never consulted `REQUIRED_ROLES`.** The RPC door did, and refused to clear `llm` or `embedding`. The wizard reached the same function through its own older answer, whose table says `optional: true` for embedding -- which answers "may be left unset", not "may be erased". Clicking Skip on a configured embedding role called `set_embedding_endpoint({"model": "", "provider": ""})` and emptied raven's own top-level block, the one endpoint every knowledge base embeds with. Reproduced: ``` before {'model': 'text-embedding-3-small', 'provider': 'openrouter'} CONTROL clear_role(rerank) block unchanged SUBJECT clear_role(embedding) not refused; block = None log: "config/update: embedding endpoint cleared" ``` The rule now lives with the operation. `clear_role` raises `RoleRequiredError`; the RPC translates it instead of restating it, so there is one rule rather than two copies that drifted; the wizard stops offering Skip for a role that is set and required, while still offering it for one that is merely unset. A test that asserted the old behaviour is replaced -- it pinned the defect as the contract. **`restart_for_config_change` never asked `everos_owned`.** `_require_owned` sits on the write primitives "so a new caller cannot opt out"; the restart is a new caller and is not a write primitive, so it did. The embedding role is exempt from the ownership gate by design, so a save of it on a user-managed root fell through to `stop_for_reload` -> `stop_pid`, which SIGTERMs whatever `lock_holder` names for that root: the user's own server. Reproduced as far as the exemption, which is the part that can be driven without harming a running service: ``` owned=False llm rerank multimodal -> REFUSED (EverosRootNotOwnedError) embedding -> got past the ownership gate ``` That contradicts this feature's own written rule three times: `everos_owned`'s docstring ("never start or stop the process"), the file header, and C13 in the design document. The chain now refuses before it stops anything, and says the root is theirs to restart. Also, smaller, all named in the same review: the design document used "role block", which the CONTEXT entry it ships beside puts on its `_Avoid_` list; the vendor table spelled the rerank protocol `rerank_provider`, the word that entry says to avoid, while EverOS's own `rerank.provider` field and the `EVEROS_RERANK__PROVIDER` variable keep their names; and A18 stated a criterion that is false about the code, since the protocol legitimately reads `deepinfra` and `dashscope` where it belongs. ## Type - [x] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification - `uv run --frozen --python 3.12 --all-extras pytest tests/test_everos_config.py tests/test_everos_backend.py tests/test_everos_server.py tests/test_everos_discover.py tests/test_config_update_everos.py tests/test_config_update_providers.py tests/test_plugin_boundary.py tests/test_rpc_contract_shapes.py tests/test_rpc_settings.py tests/test_cli_onboard_commands.py -q` -- 1018 passed, 1 skipped - `ruff check raven/ tests/ plugins-dist/` -- all checks passed - `ruff format --check raven/ tests/ plugins-dist/` -- 1839 files already formatted - `make check-source-language` over this range -- clean Each fix was mutated back to the defect to confirm the new cases bite: - `clear_role` stops consulting `REQUIRED_ROLES` -- reddens the config case and the RPC case, which is what says the RPC now depends on the one rule rather than carrying a second copy - the wizard offers Skip for a required role again -- reddens the wizard case - the restart stops asking `everos_owned` -- reddens the unowned-root case Each mutation was reverted and the file hash compared afterwards. ## Risk User-visible: Skip disappears from the wizard menu for an embedding role that is already configured, and a save of the embedding role against a user-managed root now reports that the server was not restarted instead of restarting it. Both are the behaviour the feature's own documents already describe. Rollback: one commit, reverts cleanly. ## Related Issues Follow-up to #614. --------- Co-authored-by: gloryfromca <23442919+gloryfromca@users.noreply.github.com> Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
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:
unrelated restarted the service, with nothing on screen saying so;
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;
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 boundinto raven's own process so the in-process multimodal tool reads the same models
the service does.
everos.tomlkeeps its runtime knobs, which EverOS merges perkey; 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, sincethat 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 amaintainer'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:either direction;
EVEROS_<ROLE>__*still outranks raven, and the slotsays so rather than accepting a save that would never take effect;
recorded on the role only for a self-hosted endpoint no table can answer for;
because the host may know this plugin only through the plugin contract.
Rebased onto
mainafter #613, which settles the lint and contract drift thebranch 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
Verification
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
deepinfraprovider row, resolved reranking to/v1/inferencerather than thechat endpoint, and left
everos.tomlbyte-identical (sha256 unchanged). That runis also what found the defect described in the next section.
The remaining real-host acceptance cases in
docs/specs/2026-09-21-everos-role-config-acceptance.mdhave not been run yet;they are the next step and will be reported on this PR.
Risk
User-visible changes:
than deriving it from an address, and offer every vendor that can serve the
role;
outcome in the standing banner;
EVEROS_*variables is read-only,and so are all four on a root the user manages;
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 originalfile 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
Settingsfrom the emitted environment in a subprocess, which is the layerthat owns the fact.
Related Issues
N/A