Skip to content

refactor(plugins): serve the four EverOS memory roles from raven's own config - #614

Merged
gloryfromca merged 9 commits into
mainfrom
refactor/everos_role_config_to_raven
Sep 22, 2026
Merged

gloryfromca merged 9 commits into
mainfrom
refactor/everos_role_config_to_raven

Conversation

@gloryfromca

@gloryfromca gloryfromca commented Sep 21, 2026 •

Copy link
Copy Markdown
Member

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

  • 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.

  • Relevant tests pass locally
  • 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.

  • Security impact considered
  • Backward compatibility considered
  • Rollback path is clear for risky changes

Related Issues

N/A

@gloryfromca
gloryfromca requested a review from LivXue as a code owner September 21, 2026 17:14
@gloryfromca
gloryfromca force-pushed the refactor/everos_role_config_to_raven branch from 3ed4b35 to 4e24395 Compare September 21, 2026 17:24

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:check and npm run type-check: passed.
  • npm run lint: passed with four pre-existing warnings outside this diff.
  • Source-language, large-file, and git diff --check gates 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.

Comment thread ui-web/src/features/settings/providers/Roles.tsx
Comment thread ui-web/src/features/settings/providers/Roles.tsx Outdated

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 gloryfromca left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

gloryfromca and others added 5 commits September 22, 2026 01:37
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>
@gloryfromca
gloryfromca force-pushed the refactor/everos_role_config_to_raven branch from 8eff440 to 27f6fa2 Compare September 21, 2026 17:38

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 gloryfromca left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@gloryfromca
gloryfromca force-pushed the refactor/everos_role_config_to_raven branch from 9f04eca to ccf650f Compare September 21, 2026 19:05

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 gloryfromca left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@gloryfromca

Copy link
Copy Markdown
Member Author

The premise checks out, and it was a hole I opened. Removing T6.5's second half left api_key_set with only T6.4, which this document itself says does not constitute acceptance; Roles.tsx never reads the field, so nothing on a real host was watching it.

Taking the first of your two options, in 111c7de6: T6.6, which reads the flag where it is computed rather than where it is displayed.

Run on a real host just now -- a seeded RAVEN_HOME with four roles pinned on openrouter, a real gateway (pid 79138) and its everos child (79142), over ws://127.0.0.1:18898/rpc, the same socket the page uses:

settings.everos, key present:
  llm         provider='openrouter'  api_key_set=True
  embedding   provider='openrouter'  api_key_set=True
  rerank      provider='openrouter'  api_key_set=True
  multimodal  provider='openrouter'  api_key_set=True

model.disconnect {"slug": "openrouter"} -> {"disconnected": true}

settings.everos, same connection:
  llm         provider='openrouter'  api_key_set=False
  embedding   provider='openrouter'  api_key_set=False
  rerank      provider='openrouter'  api_key_set=False
  multimodal  provider='openrouter'  api_key_set=False

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. model.disconnect is the page's own Disconnect action, so the trigger is a real user path rather than a file edit behind the server's back.

The same pair also covers A18's other clause: rerank.provider reads openrouter in both readings and never deepinfra / vllm / dashscope.

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 api_key_set, so without T6.6 a running gateway could report it wrongly unobserved.

The probe is re-runnable by anyone: probe_ws.py <RAVEN_HOME> [read|disconnect], kept beside the other acceptance evidence.

@gloryfromca

Copy link
Copy Markdown
Member Author

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: api_key_set changes after the real model.disconnect path without a role edit, and rerank continues to report the vendor rather than an EverOS request protocol. The coverage note now accurately identifies T6.6 as A18's real-host case, so the previously named follow-up is closed.

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread plugins-dist/everos-memory/raven_everos/config.py
@gloryfromca
gloryfromca merged commit 96d3b88 into main Sep 22, 2026
27 checks passed
@gloryfromca
gloryfromca deleted the refactor/everos_role_config_to_raven branch September 22, 2026 07:43
0xKT pushed a commit that referenced this pull request Sep 22, 2026
## 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>
LivXue pushed a commit that referenced this pull request Oct 12, 2026
…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>
LivXue pushed a commit that referenced this pull request Oct 12, 2026
## 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants