Repository navigation
fix(*): ship the cloud memory plugin with raven, not beside the release - #1030
Conversation
The release builds everos-cloud-memory's wheel and attaches it, but no installer and no upgrade named it, so the only way in was `uv tool install --with`, which the next `raven upgrade` undoes: uv replaces the tool's requirement set with what the command names, and the upgrade names the release's plugin list alone. A user who picked the cloud backend was told to install a plugin that had no install path. The live-service gate that kept it out is met: the six key-gated tests in tests/integration/test_everos_cloud_real_cloud.py ran against api.evermind.ai on 2026-10-09. So the distribution joins the list a complete install is made of -- release.yml, publish_beta.py for the beta channel, both installers' local-clone lists, and the Docker image's three plugin defaults. Installed is not selected: memory.backend still decides, doctor and the gateway build only the backend it names, and the one visible change is that `raven onboard` asks which memory backend to use rather than taking the only one installed. The rung that drops the product engines kept only everos-memory. That rung exists so long-term memory outlives a native build a platform refuses, so on such a machine every upgrade uninstalled the cloud backend from under a host configured to use it. install.sh, install.ps1 and the upgrade helper now keep both memory distributions, and the contract test reads which those are off the backend/distribution pairing rather than naming one by hand. Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
gloryfromca
left a comment
There was a problem hiding this comment.
Blocking: update the release-notes template so it no longer says the plugin list excludes cloud memory
Found one release-facing mismatch inline. Reviewed the full diff; the release, beta, local installer, Docker, and upgrade consumers; relevant history and callers; fallback and backward-compatibility behavior; test changes for weakening; and the applicable AGENTS.md and CONTEXT-MAP.md/CONTEXT.md constraints. No other defect survived refutation.
Verification: uv sync --extra dev && uv run pytest tests/test_release_plugin_list.py tests/test_updates_upgrade.py tests/test_install_script.py tests/test_publish_beta.py -x completed with 254 passed and 6 environment-based skips (PowerShell/truststore). The first fresh-environment attempt did not execute tests because everos-memory was not installed; an --all-extras bootstrap also stopped before tests because the optional segmenter dependency requires unavailable gcc.
The plugin list now carries everos-cloud-memory, but .github/release-notes-template.md still told readers the list "installs every plugin wheel but `everos_cloud_memory`". The release workflow copies that template into every draft, so the first release after the roster change would have advertised the opposite of what it does. The claim is a positive one now, and tests/test_release_plugin_list.py holds the template to the roster it describes: every shipped distribution has to be named, and the list's claim cannot carve out an exception again. Restoring the old sentence turns that case red. 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.
The prior release-notes mismatch is fixed and its thread is resolved. This round covered the revision delta and the full diff, the release-template caller, release/beta/local/Docker/upgrade roster consumers, relevant history, fallback and backward-compatibility behavior, test changes for weakening, and the applicable AGENTS.md and CONTEXT-MAP.md/CONTEXT.md constraints. No new finding survived refutation.
Verification: uv run pytest tests/test_release_plugin_list.py tests/test_updates_upgrade.py tests/test_install_script.py tests/test_publish_beta.py -x completed with 255 passed and 6 environment-based skips (PowerShell/truststore).
|
Not a blocker (review of Severity of this one finding, not a verdict on the pull request. Any blocking findings from this pass are review threads on the changed files; GitHub renders those collapsed, as a file name with no text. Two small points:
|
…ort (#1050) ## Summary Raven runs several tasks through its agents at once, and each result comes back as its own turn. The host then asks the user what to do next, and the user's reply often settles only some of those decisions. The rest used to drop out of the conversation. This PR keeps them in it. - The host role (`## Your role`) gains one bullet: a decision the host put to the user stays open until the user makes it, drops it or says to leave it for later; when a message settles only some, the host acts on those and ends the reply by asking about each one still open, a line each, on every reply. An offer the user passes over is declined, not open. - The reply rules (`## How you reply`) gain two lines. Open decisions close every reply, a line each, also after a one-line answer or a hand-off, and they are content rather than a recap or a sign-off; one the user dropped or put off is no longer open, and an offer the user passed over is declined. A reply that reports the results of several tasks gives each task its own section under a markdown heading on a line of its own (`### <task>`), with the decisions after the last section. - A `spawn` result carries the rule as this system's note after the result's untrusted fence (`render.open_decisions_note`, added to the `trusted_note` channel beside `worker_table_note`). The host writes its closing text right after it reads the receipt, and inside the fence the note would be data. Why two homes for one rule: with the role bullet alone, a one-line answer to an unrelated question closed with the open decisions in 0 of 6 runs, most likely because the simple-answer and stop rules in `## How you reply` read the reminder as a closing recap; the reply-rules line is what moved it. The reply rules also render where the role does not: the role needs a dispatch path, and the turn that generates a Persona, or a turn on an install that withholds both `spawn` and `run_subagent_dag`, holds none. There the reply-rules line is the rule's only statement, so it carries the declined-offer sentence in the role's own words. The tests pin both places, on both kinds of turn. Measured on glm-5.3-flash, the default host model, with synthetic conversations sent through the real per-turn context assembler and the live tool list (34 tools); tool calls were answered by stubs and never executed. n = 6 per cell unless stated: | Situation | Before | After | |---|---|---| | one reply asked three, the user settled one | 0/6 | 6/6 | | three replies asked one each, the user settled the latest | 3/6 | 6/6 | | a one-line answer to an unrelated question, decisions open | 1/12 | 5/6 | | a new result arrives while an older decision is open | 0/6 | 6/6 | | a question about one task, handed on to its instance | 4/6 | 6/6 | | an offer the user passed over, then an unrelated request | 6/6 | 6/6 | | all settled / "the rest later" / a fresh chat | 6/6 each | 6/6 each | | a report on three tasks, every task under a heading | 3/34 | 11/11 | | a single task's result, no heading added | 12/12 | 6/6 | The first six rows were re-run with the final wording, whose reply-rules line carries the declined-offer sentence; against the same prompt without that sentence no cell fell (two rows went from 5/6 to 6/6 and the hand-off pair from 3/6 and 5/6 to 4/6 and 6/6, which at n = 6 is noise, not a gain). The last three rows were measured before the sentence was added and were not re-run. The hand-off row compares the prompt with and without the spawn note, both with every tool result fenced as production fences it; the other rows came from runs whose stored tool results were not fenced, where the hand-off row read 1/6 without the note. Routing a question to its task's instance held at 6/6 in every arm. The heading row counts replies that reported all three tasks; the wording had to name the syntax, since "a short heading on a line of its own" kept the task name inline ("**Pricing** -- ...") in 4 of 7. The replay harness lives outside the repository because it calls a live model. ## Type - [ ] Fix - [x] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification Each new test was watched failing before the change that makes it pass. - After the rebase onto #1040, #1030, #1051 and #1054: `nice -n 19 .venv/bin/python -m pytest -n 0 -q -p no:randomly tests/test_segments.py tests/test_agent_loop_subagent_role.py tests/test_agent_loop_run_emit.py tests/test_agent_loop_token_budget.py tests/test_watch_work.py tests/test_security_untrusted_context.py tests/test_unified_playbook_e2e.py tests/test_release_plugin_list.py tests/test_skill_forge_loader_parity.py tests/test_skill_hub_tools.py tests/test_subagent_mirasim.py tests/test_subagent_probe.py tests/test_updates_upgrade.py`, the last six being the test files those four commits changed: 767 passed. - The full suite was last run before the declined-offer sentence and before that rebase, on a base that touched none of the files above; it was not re-run for the one-sentence prompt change, which the files above cover. `nice -n 19 .venv/bin/python -m pytest -q -p no:randomly -n 4` in a venv synced with `uv sync --frozen --python 3.12 --all-extras`: 34158 passed, 181 skipped, 2 xfailed, 6 failed. Three are this machine's standing failures, red on main here as well: `test_install_script.py::test_resolve_node_dir_answers_each_case_it_exists_for`, `test_knowledge_parser_excel.py::test_a_legacy_workbook_is_converted_and_read` and `test_subagent_node_runtime.py::test_what_cannot_be_read_names_nothing` (run as root). The other three were two xdist worker crashes and one timeout while other jobs held the load near 30; those three tests passed alone, and their files passed serially, 134 of 134. - `ruff check` and `ruff format --check` over the changed Python files: clean. - `scripts/check_large_files.py`, `scripts/check_source_language.py` and `scripts/check_commit_messages.py` over `origin/main..HEAD`, and `commitlint --from origin/main --to HEAD` plus the prospective squash header: all exit 0; a malformed control message makes commitlint exit 1. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [x] User-facing docs or screenshots are updated when needed ## Risk - Every host reply can now end with a short list of the decisions still open, on every lane the host replies on, including scheduled and result-announcement turns. A reply that reports several tasks now uses markdown headings, which a channel that does not render markdown shows as `###`. WeChat is one: its adapter sends the reply text as written. - Cost: 220 tokens on every host turn's identity (99 for the role bullet, 74 and 47 for the reply lines), and about 20 tokens on each `spawn` result, which stay on the stored message. - `available_history` in `tests/test_agent_loop_token_budget.py` goes from 126_000 to 125_500 (measured 125_984). #1031 lowers the same bound at the same lines for `context_retrieve`. This PR is to land first; #1031 then keeps both comment paragraphs and re-measures with both prompt additions in. - Not covered: `run_subagent_dag` results and the DAG result announcement carry no note, and the spawn result announcement keeps its "1-2 sentences" instruction. - Rollback: revert the squash commit. Nothing is migrated or stored in a new shape. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes The note uses the `trusted_note` channel, which only carries text a Raven module composed; no tool output passes through it. ## Related Issues N/A
…1053) ## Summary From a mainland-China line with no proxy, several hosts a Raven install and its runtime download from do not answer at all: huggingface.co resolves to an address that drops every packet, raw.githubusercontent.com and GitHub release downloads time out more often than not, and astral.sh, PyPI, nodejs.org or the npm registry can fail the same way. Each has a mirror inside the country that serves the same files. This change makes every download that has one fall back to it, and only when the default host cannot be reached at all. The rule, in one place for every download: - The default host is always asked first. Only a failure to reach it moves the download on: a name that does not resolve, no route to it, a connection refused, reset or left unanswered, a connection the machine itself refuses (a sandbox or a local firewall), TLS that never got going. An HTTP error is the host's answer and stays final, and so is a certificate that fails verification (whatever re-signs the host's traffic re-signs the mirror's too). Behind a proxy, the proxy's answer to the request for a tunnel speaks for the host: a 4xx (403 for a host its rules block, 407 for credentials it was not given) is final, since the mirror would be asked through the same proxy and would only route around its rules, while a 5xx says the proxy could not reach the host and counts as unreachable. A SOCKS proxy that cannot connect counts as unreachable whatever its reply. Two downloads do not say why they failed, uv's own installer and Playwright's Chromium download, so the hosts they download from are asked, and the mirror stands in only when none of them can be reached; the installers fetch uv's installer themselves, so astral.sh's answer to that fetch is final too. The LibreOffice dmg and the CJK font, checked against digests the script pins, try their mirror after their usual hosts fail in any way. - Five places apply that rule, each with the client it uses: the runtime (urllib for launches, httpx for downloads), the upgrade helper, `install.sh` (curl) and `install.ps1` (PowerShell). A test runs all five against the same loopback hosts and proxies and holds them to one table. - A host the user chose is never replaced: `HF_ENDPOINT`; a uv index in the environment, in a `uv.toml` or on the command line; where uv's installer downloads uv from (`UV_DOWNLOAD_URL`, `INSTALLER_DOWNLOAD_URL`, `UV_INSTALLER_GHE_BASE_URL`, `UV_INSTALLER_GITHUB_BASE_URL`); where uv fetches a Python from (`UV_PYTHON_INSTALL_MIRROR`, `UV_PYTHON_DOWNLOADS_JSON_URL`, `UV_ASTRAL_MIRROR_URL`, or `python-install-mirror` or `python-downloads-json-url` in a `uv.toml`); `PLAYWRIGHT_DOWNLOAD_HOST` or `PLAYWRIGHT_CHROMIUM_DOWNLOAD_HOST`; an npm registry set anywhere npm reads one. - No choice outlives the download it was made for. A fallback sets nothing that stays in a process which goes on to fetch packages for a user's project: the installers restore what they set before `raven web` starts; at runtime the mirror goes only on a uvx launch's own command line, or on the command line of the npm that installs an npx launch's packages into Raven's own directory. - `RAVEN_NO_MIRROR=1` (read like Raven's other opt-outs) keeps every download on its default host. It is read in the environment of the launch as well as in Raven's own, whatever the case of the name, so a user who sets it in an agent's own environment, or in the profile of the machine an agent runs on, means it there; a Raven node is told the host's own value, and the daemon reads the machine's own environment as well. Both installers' headers and the quick start (English and Chinese) say so. - Each switch says so, with the way to turn it off. The exceptions are quiet on purpose: the LibreOffice dmg and the CJK font, whose pinned digests mean no source can change what is installed, and the codex version lookup. Where it applies: - `raven resources`: the tokenizer dictionary through jsDelivr; the deepdoc models through hf-mirror.com, asked without the user's Hugging Face token, which belongs to huggingface.co. A host with a mirror behind it gets a 15 s connect and 30 s read timeout instead of 120 s, and the files after a switch go straight to the mirror. - `install.sh` and `install.ps1`: uv from the newest wheel on the PyPI mirror, or on the indexes the user named, asked in uv's order (each of `UV_INDEX`'s, then `UV_DEFAULT_INDEX` or `UV_INDEX_URL`, or else the default index a `uv.toml` names), when uv's installer could not reach its hosts, checked against the sha256 the index publishes; Node from npmmirror, checked against the SHASUMS256.txt beside it; uv's PyPI index and Python downloads; the npm registry for a source build's `npm ci`, after npm says which registry it reads, asked in the directory the install runs in, because npm reads the `.npmrc` of the project beside it, and set for that build alone; one Chromium retry through npmmirror when none of playwright's own hosts answers; and, in `install.sh` alone, the macOS LibreOffice dmg (TUNA) and the Linux CJK font (jsDelivr). The variables are set for the install's own downloads and restored before `raven web` starts, so the reader's PowerShell session gets its environment back under `irm | iex`. - `install.ps1` now runs uv's installer in Windows PowerShell, a process of its own, as uv documents (`powershell -ExecutionPolicy ByPass -c ...`): that installer ends a failed download with `exit 1`, which under `Invoke-Expression` ended this install too and, under `irm | iex`, the reader's whole session, before any fallback could run. What it prints, and what playwright prints, goes to the console rather than into the value the step hands back. - `raven upgrade`: the helper points uv at the PyPI and Python mirrors (decided once per upgrade; the relaunched Raven does not inherit them). - Runtime launches: ACP agents, CLI agents, MCP stdio servers and agents started on a registered machine, decided on the machine that runs the command, per launch, from a HEAD probe cached for 10 minutes, and only when the mirror itself answers, so a package already in the runner's cache still starts offline. - `uvx`: the mirror goes on uvx's own command line as `--default-index` and nowhere else. uv keeps a flag to itself (measured: given as `UV_DEFAULT_INDEX` an index reaches the child, and in a user's project makes `uv run` rewrite `uv.lock`; given as the flag it does not reach the child), and uvx reads no project's `uv.toml` (measured), so the flag replaces no choice a project made. - `npx`: npm hands its registry, however it was given (a variable, a flag or an npmrc; measured with npm 11.12), to everything npx starts, where as `npm_config_registry` it outranks the registry a project names in its own `.npmrc`. So npx does not run: the packages it would fetch are installed into `$RAVEN_HOME/cache/npx/<key>` (one directory per set of specs) by an npm given `--registry` on its own command line, and the bin npx would have run starts from there with the launch's own environment; on Windows through node, with the options the bin's first line names, as npm's own shims read it. An exact version is installed once; a range, a tag or a bare name is brought up to date at each such launch with `npm update`, as npx asks the registry each time, and a failed update starts the install made before. Launches of the same specs take turns on a file lock, and npm gets 10 minutes, past which its process group is ended. An install that was killed leaves a directory that looks installed and is not, so a marker file says whether the last run of npm finished and an install without one is done again; an npm that outlived the Raven that started it is waited for rather than run beside, and a Raven node keeps its installs under the node's own directory rather than in the home of the user it runs as. Left to npx as configured: any option other than `-y` and `-p`; a spec that is not a registry's (a path, URL, git or alias); a bin or package the project already has, or a bin among npm's global ones; a project whose `.npmrc` names a registry, scoped or not, which Raven's directory would not read; and an MCP server started in a sandbox. - On a Raven node the preparation is bounded: the daemon decides on the machine it runs on, and the host gives the answer 60 s, so a preparation past a budget the protocol names is abandoned and the command starts as it was configured. The answer comes in time and no agent is left running that no host holds. The clock starts before the preparation is queued, so the wait for a worker of the pool counts against the budget, and launches that outnumber the pool's workers are answered within it too: the whole preparation is bounded from the event loop, and the probes and the npm config read are bounded by the same deadline. - `uv run`, `npm install`, `npm exec`, pnpm and bun launches are left alone. The WhatsApp bridge's own `npm install`, which runs in Raven's own directory and starts nothing that stays, and the codex dist-tags lookup fall back as the installers do. Not covered: Raven's own release files (GitHub Releases has no mirror), the one-line installer URL itself, LibreOffice on Windows (winget) and nltk data. ## Type - [ ] Fix - [x] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification The full-suite figures below were measured before the review rounds, after the first of two rebases (every commit replayed unchanged) and before #1030 and #1051 landed on `main`. The review rounds changed `install.sh`, `install.ps1`, the quick start, the runtime's mirror module and its four launch sites, and the upgrade helper, each with its tests; their own checks follow, at the final head, rebased onto `main` at e8332e5. CI runs the Linux selection there too. Linux, in a python:3.12 uv container (16 vCPU), the whole default selection on head and on base (`origin/main`) in one container session, each tree committed into a repository of its own: - Head: 1 failed, 34361 passed, 243 skipped, 2 xfailed. Base: 1 failed, 34130 passed, 243 skipped, 2 xfailed. - The one failure is the same on both, `test_subagent_node_runtime.py::test_what_cannot_be_read_names_nothing`: the container runs as root, which reads a file whose mode forbids it. - An earlier head run of the same selection failed `test_cli_doctor_commands.py::test_doctor_empty_config_is_invalid` once (doctor printed no Config line). It did not come back in 96 further runs of that check on each tree at four workers, nor in the run above, and no mirror code runs there: the doctor subprocess inherits the suite's `RAVEN_NO_MIRROR=1`. - `scripts/coverage_gate.py diff --base-ref origin/main`: 99.67% (305 of 306 changed lines) at that point; the one uncovered line is the Windows branch of the `uv.toml` lookup. The upgrade helper is carried as text and run as a script of its own, which coverage does not measure; its tests run it, and the mutants below reach it. At the final head, in a Linux container with sh, curl 7.88, PowerShell 7.6 and Node 22 (npm 10.9): the test files this change reaches (`test_install_script`, `test_cli_onboard_commands`, `test_utils_mirrors`, `test_updates_upgrade`, `test_subagent_host_env`, `test_subagent_third_party`, `test_mcp_client_transport`, `test_node_server`, `test_channels_whatsapp`, `test_providers_codex_client_version`, `test_resources_fetch`), 1564 passed, 1 skipped (a case only macOS can run), and at the head of the last round, over the same files without `test_cli_onboard_commands`, 1345 passed, 2 skipped (a case only macOS can run, and the Windows PowerShell 5.1 case). The installer tests pass with a uv of the machine's own at `/usr/bin/uv` and with an `/etc/uv/uv.toml` that names an index, neither of which they read. Windows 11, native, over the same files with PowerShell 7 off PATH so its cases skip on both sides (they run on Linux above): head 25 failed, 1242 passed, 180 skipped; `main` 25 failed, 1001 passed, 25 skipped (it lacks this change's test files); the failing ids are the same on both sides, POSIX shell stand-ins and file modes, and nltk, which that environment does not have. At the head of the last round, over the same files with PowerShell 7 on PATH: 24 failed, 1038 passed, 165 skipped, and the 24 failing ids are the same as at the head of the previous round on this machine: nltk and the dictionary, the CLI backend's POSIX process groups, and two cases of the Windows console's own encoding. The real-npm test ran there too, with npm 11.12. Windows PowerShell 5.1, live: the reachability table through loopback proxies gives the expected answer in all 13 of its HTTP cases (5.1 has no SOCKS), where the previous head counted a proxy's 500, 502, 503 and 504 as answers; with uv's installer run in the child `powershell.exe`, an installer that works returns only uv's path and its output reaches the console; one that ends with `exit 1` leaves the script running and hands over to the mirror only when releases.astral.sh and GitHub cannot be reached; a fetch astral.sh answered stays final; with a host named for uv's installer nothing is probed and the mirror is not used; the Python mirror stays unset beside each Python source a user can name; the uv index is read as intended in 11 cases. Install-Chromium with a python that prints returns only its true or false. Mutation: 222 one-construct mutants, each run against the tests that guard it after those tests passed unmutated, and each turned them red: 2 for this round's changes (the mirror's answer reaching the fetch, and its answer being ignored), 27 for the last round's changes (the opt-out in the launch environment and in the case of its name, a connect the machine refuses and a file of our own, the budget of a launch and the wait for another install, the marker of a finished install, the npm of a dead Raven, the cache of a node, the node's own cache directory and the budget the host waits for, the host's own switch and the switch the host forwards, the mark of a `uv.toml` in the runtime, in the helper and in both installers, the quoted keys of a `uv.toml` in both installers, the directory npm is asked in and the scope of its fallback in both installers, and the npm fallback put back), and the earlier sets, re-run where their code still exists, re-anchored where it moved. At the final head: 57 for the last round's changes (the tunnel status in each of the five deciders, no route, the certificate carried as urllib's reason, each npx shape and each case left to npx, the install's refresh, lock and deadline, the bin's containment and start, and each launch site handing over where it runs); and the earlier sets, re-run where their code still exists, re-anchored where it moved: 24 of the 31 in the runtime's Python, 23 of the 25 in `install.sh` and 19 of the 20 in `install.ps1`, all 18 of the first review round's, 20 of the second's 23 and 32 of the third's 33 (the rest targeted code the review rounds removed, or make the same change as a newer mutant). This round changed the node's answer, the switch's case, the node's own install, what a proxy that could not be used counts as, the order an ACP launch asks in, and where the switch's other case is asked of it. The order reaches the paths that prefetch: the mirror is asked before the fetch, in the pool and in the local ping as well as in the direct launch, so a launch the mirror took over is never fetched from a registry it could not reach. The budget's clock started inside the worker, so the wait for one of the pool's workers did not count against it: on a small node, where the daemon admits more launches at once than the pool has workers, the launch that waited for one lost the machine's link and took the agents already running on it with it. The clock now starts before the work is queued, the whole preparation is bounded from the event loop, the probes and the npm config read are bounded by the same deadline, and a host whose probe ran out of time moves nothing at all. The switch is read in either case of its name now, in this process's own environment as well as in the launch's, so it turns the fallback off where Raven runs and travels to a node that runs the launch. The node's own install, which came with #1068, read the same switch by a name of its own and in one case only; it reads it through the runtime's reader now, so the two cannot read it differently. httpx cannot use a SOCKS proxy without the socksio package and says so with an ImportError, which the rule did not know and answered that the host had answered -- so the only copy of the rule that could use a SOCKS proxy was the only one that took the mirror for an outage it could not have seen. A proxy that could not be used at all counts: it was never asked, which is what the copies that cannot use a SOCKS proxy at all have always answered. The ACP launch asked the npx fetch before the fallback, and that fetch goes to the default registry, the very one the fallback decides about: so on a machine that cannot reach the registry the fetch failed first, the launch raised npm's own error, and the fallback that exists for that case was never consulted. The fallback is asked first now, and the fetch is given its answer: an npx launch the mirror took over is started from what the mirror installed, and there is nothing left for npx to fetch. Windows keeps one name for a variable, so a switch set in another case lands under the one name and the reader is never asked about it: the case that said the switch was read in either case passed on Windows for that reason rather than because the reader reads either case. The other case is asked where a system can hold it, on POSIX, and the name the install and the runtime share is asked everywhere. Windows 11, native, over the files this round reaches (`test_utils_mirrors`, `test_node_server`, `test_node_client`, `test_mcp_client_transport`, `test_subagent_host_env`, `test_subagent_third_party`): 560 passed, 41 skipped; with the install's own file added, 296 passed, 21 skipped. With the ACP files the fetch touches added, over the fifteen files of both rounds: 1406 passed, 188 skipped; the 43 failing ids are the same as at the head without this change (nltk and the dictionary, the CLI backend's POSIX process groups, the ACP journal's file cases, and two cases of the Windows console's own encoding). The table every copy of the rule answers alike, on a Windows install without the socksio package: 4 passed, 1 skipped, where it failed at the previous head on the two SOCKS cases; with the socksio package, in a Linux container, the same table passes (3 passed, 2 skipped). The ACP launch's own case, the fetch given the argv the fallback answered with, is in `test_subagent_host_env`. The switch's other case, asked on POSIX where a system can hold it, is in `test_utils_mirrors` and in `test_node_install`. Linux, in a python:3.12 uv container: `test_utils_mirrors` 184 passed, 1 skipped; `test_node_client` 94 passed; `test_node_install` with `test_utils_mirrors` 316 passed, 1 skipped; `test_node_server` 114 passed and 7 failed, the seven being the cases that read the process tree with `ps`, which that image does not have and which fail on the base tree as well. The daemon's own case, launches that outnumber the pool's workers and each answered within the budget, passes there. Nine one-construct mutants of this round each turn a case red: the bound from the event loop removed, the probe's own bound removed, a host that ran out of time moving anyway, and the worker taking a clock of its own instead of the caller's, the two readers of the switch reading the name in one case only, the rule counting a proxy that could not be used as one that answered, and the fetch asked before the fallback, and the switch's own environment read in one case only; the last five are caught where the name of an environment variable is case-sensitive, or where the socksio package is not installed. Also run: `ruff check` and `ruff format --check` clean; `lint-imports` 10 kept; `ty` reports the same diagnostics as base on every changed module (none in the new one); `deptry` matches base; no added line outside `docs-site/` is non-ASCII; the docs build passes with `--strict`; `commitlint`, `scripts/check_commit_messages.py`, `scripts/check_source_language.py` and `scripts/check_large_files.py` pass over the 19 commits, each beside a control commit it refuses; `install.ps1` parses under Windows PowerShell 5.1. Measured again on 2026-10-22 from the same China Unicom line with no proxy (three rounds): every host with a mirror either answered directly or its mirror did, in every round (huggingface.co and the dictionary on raw.githubusercontent.com in none, hf-mirror.com and jsDelivr in all); Raven's own release files on github.com answered in none or one of three. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [x] User-facing docs or screenshots are updated when needed ## Risk - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes A mirror is trusted as far as the host it stands in for, except where a digest is pinned in the script (LibreOffice, the CJK font). When a fallback fires, the mirror is the only integrity authority for what it serves: uvx packages from TUNA and npx packages from npmmirror carry no lockfile or digest tied to PyPI or registry.npmjs.org (npm checks each tarball against the integrity the mirror's own metadata lists), PlugHub-vetted MCP entries included, whose vetting refuses an entry that names an index or a registry itself so that the confirm dialog shows what runs: the fallback adds `--default-index` to a uvx launch, and starts an npx entry's bin from Raven's own install of the same package instead of through npx; the Node tarball is checked against the SHASUMS256.txt on the mirror, where `main` checked nodejs.org's own; the uv wheel against the digest the index lists beside its link, skipped when the index lists none; Chromium and Raven's own dependency closure come from the mirror as served. Those checks catch a corrupt download and not a dishonest mirror. Anything that makes the default host unanswerable triggers the switch, an on-path party included; the mirror hosts are constants and their TLS is verified, so the switch can be forced but not pointed elsewhere. Nothing of the user's credentials reaches a mirror: the Hugging Face token is withheld. With every host reachable nothing changes except one HEAD request per default host every 10 minutes before a `uvx` or `npx` launch; a host that drops packets costs that launch up to 5 s once per 10 minutes. `RAVEN_NO_MIRROR=1` restores the previous behaviour everywhere. Checked and left as they are: - The runtime's probe asks from the Raven process, through urllib's proxies (the environment's, or the system's on Windows and macOS when the environment names none), and `install.ps1` probes through PowerShell's; uv and npm in the launched process read only the environment's and their own configuration. Where those differ a fallback can be missed, which is the old behaviour, or taken needlessly, through a mirror that answered. - An npx launch's bin started from Raven's directory gets the launch's environment as it is, without what npx adds for the programs it starts: npm's `npm_*` variables and the `node_modules/.bin` directories on PATH. `$RAVEN_HOME/cache/npx` keeps one install per set of specs, as npx keeps its cache, and nothing removes them; a Raven node's installs are under the node's own directory instead, where its records are. A range, tag or bare name costs each such launch an `npm update` (about 1 to 3 s measured against npmmirror). On Windows a bin whose first line is not node's starts through npx as configured. - A SOCKS proxy's reply is not read: a refusal by its rules switches like a host it could not reach, since PowerShell has the reply only in an exception's text and urllib and Windows PowerShell 5.1 cannot use a SOCKS proxy at all. PowerShell 7 before 7.4 has no `HttpRequestError`, so there a refused tunnel and a certificate failure both count as unreachable. - uv records the index an install used in `uv-receipt.toml` (measured): after an installer fell back, a manual `uv tool upgrade raven` resolves from the mirror. The installers' and `raven upgrade`'s own `uv tool install --force` do not reuse it (measured). - The installers read `uv.toml` with patterns rather than a TOML parser. A `default = true` at the start of a line or after `{` or `,` counts as a chosen index, a commented-out one included, which errs toward leaving the user's setup alone. Its address is read from `index-url`, an `[[index]]` table or an inline entry with a bare `url` key; a key may be quoted, as TOML allows, and a file with a byte-order mark is read past it, as uv reads it. An index a `uv.toml` names any other way -- an address that spans lines, say -- keeps the mirror away from uv's own download without being used, and the warning says to set `UV_DEFAULT_INDEX`. - The hosts uv's installer and playwright download from are asked with a HEAD request after they failed; one that answers it while the download itself stalled keeps the mirror away, the old behaviour. - Any of uv's settings for where uv or a Python comes from counts as the user's choice whatever it names, releases.astral.sh or GitHub included. `UV_ASTRAL_MIRROR_URL` is counted though uv 0.13.4 leaves it out of its published settings reference, because uv acts on it. - Under Windows PowerShell 5.1 an npm that warns on stderr while answering reads as one that cannot say (measured with a stand-in `npm.ps1` whose child process warns; PowerShell 7 reads the answer), so a source build there keeps the registry it had. A release install builds nothing with npm. ## Related Issues N/A --------- Co-authored-by: Claude (claude-opus-5-5[1m]) <noreply@anthropic.com> Co-authored-by: MiniMax (minimax-m3.1-flash) <noreply@minimax.io>
Summary
everos-cloud-memoryhas shipped as a release asset since 0.2.5 with no installpath a user can follow. The one-line installer never named it, and the only
manual route --
uv tool install --with-- is undone by the nextraven upgrade, which reinstalls from the release's plugin list alone and lets uvreplace the tool's requirement set with what that list names. Meanwhile the
memory page tells a user who picked the cloud backend to "Install
everos-cloud-memory", with no instruction anywhere that says how.
The live-service gate that kept the distribution out of the list is met: the
six key-gated tests in
tests/integration/test_everos_cloud_real_cloud.pyran6/6 against
api.evermind.aion 2026-10-09 (recorded on #883). So the pluginjoins the list a complete install is made of:
.github/workflows/release.ymlraven_everos_cloudscripts/publish_beta.pyPLUGIN_DISTRIBUTIONScarries it, so the beta channel ships the same setinstall.sh,install.ps1Dockerfile,docker/.env,docker/docker-compose.ymlRAVEN_PLUGINSdefaults agree (raven and httpx only, so the image pays nothing for it)docs/memory-plugin-architecture.md,CHANGELOG.mdInstalled is not selected.
memory.backendstill decides;raven doctorand the gateway build only the backend it names, so an install that never picks
the cloud behaves exactly as before. The one visible change is that
raven onboardnow asks which memory backend to use instead of silently taking theonly one installed -- the chooser that
test_cli_onboard_commands.pyalreadypins.
A second defect this surfaced. The rung that drops the product engines kept
only
everos-memory. That rung exists so long-term memory outlives a nativebuild a platform refuses, so on such a machine every
raven upgradeuninstalled the cloud backend from under a host configured to use it, and
reported nothing.
install.sh,install.ps1and the upgrade helper now keepboth memory distributions, and the contract test reads which distributions
those are off
MEMORY_BACKEND_DISTRIBUTIONSinstead of naming one by hand, soa third memory backend the release ships cannot land on two of the three
implementations.
Type
Verification
The new rung case was mutation-checked: reverting the upgrade helper to
== "everos-memory"turns bothtest_upgrade_helper_keeps_both_memory_backends_when_the_engines_failandtest_the_helper_keeps_the_distributions_the_release_actually_shipsred, onthe cloud line missing from the rung's list. The helper source was restored and
compared byte for byte.
The full suite was not run locally; CI runs it.
Risk
Every new install and every
raven upgradenow carries one more distribution.It adds
httpx, which raven already depends on, and no code path builds thecloud backend unless
memory.backendnames it. The behaviour change a user cansee is the onboarding question, which is the direction the memory backend choice
was headed anyway.
Rollback is the inverse of the table above: drop the name from
PLUGIN_DISTRIBUTIONSand the release loop, and the list returns to threedistributions. An install that already carries the plugin keeps it until the
next upgrade, which then reinstalls without it -- the same mechanism this PR
exists to fix, used in reverse.
Related Issues
Fixes #1028