Skip to content

fix(*): settle the lint and contract drift the replay left behind - #613

Merged
gloryfromca merged 1 commit into
mainfrom
fix/post_replay_lint_and_contract
Sep 21, 2026
Merged

gloryfromca merged 1 commit into
mainfrom
fix/post_replay_lint_and_contract

Conversation

@gloryfromca

Copy link
Copy Markdown
Member

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

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

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

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

Related Issues

Follows #612.

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>
@gloryfromca
gloryfromca requested a review from LivXue as a code owner September 21, 2026 17:13
@gloryfromca
gloryfromca merged commit c7f0a04 into main Sep 21, 2026
20 of 21 checks passed
@gloryfromca
gloryfromca deleted the fix/post_replay_lint_and_contract branch September 21, 2026 17:18

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

gloryfromca added a commit that referenced this pull request Sep 21, 2026
…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>
0xKT pushed a commit that referenced this pull request Sep 22, 2026
…'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>
gloryfromca added a commit that referenced this pull request Sep 22, 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

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>
LivXue pushed a commit that referenced this pull request Oct 12, 2026
…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>
LivXue pushed a commit that referenced this pull request Oct 12, 2026
…'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>
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>
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