Skip to content

fix(*): ship the cloud memory plugin with raven, not beside the release - #1030

Merged
0xKT merged 2 commits into
mainfrom
fix/cloud_memory_default_install
Oct 21, 2026
Merged

0xKT merged 2 commits into
mainfrom
fix/cloud_memory_default_install

Conversation

@gloryfromca

Copy link
Copy Markdown
Member

Summary

everos-cloud-memory has shipped as a release asset since 0.2.5 with no install
path a user can follow. The one-line installer never named it, and the only
manual route -- uv tool install --with -- is undone by the next raven upgrade, which reinstalls from the release's plugin list alone and lets uv
replace 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.py ran
6/6 against api.evermind.ai on 2026-10-09 (recorded on #883). So the plugin
joins the list a complete install is made of:

Place Change
.github/workflows/release.yml the wheel joins the plugin-list loop, the line-count guard goes to 4, and the pre-publish install gate imports raven_everos_cloud
scripts/publish_beta.py PLUGIN_DISTRIBUTIONS carries it, so the beta channel ships the same set
install.sh, install.ps1 the local-clone ladder installs it editable beside the local plugin
Dockerfile, docker/.env, docker/docker-compose.yml the three RAVEN_PLUGINS defaults agree (raven and httpx only, so the image pays nothing for it)
docs/memory-plugin-architecture.md, CHANGELOG.md the "Not shipped by default" row is replaced by what is true now

Installed is not selected. memory.backend still decides; raven doctor
and 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 onboard now asks which memory backend to use instead of silently taking the
only one installed -- the chooser that test_cli_onboard_commands.py already
pins.

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 native
build a platform refuses, so on such a machine every raven upgrade
uninstalled the cloud backend from under a host configured to use it, and
reported nothing. install.sh, install.ps1 and the upgrade helper now keep
both memory distributions, and the contract test reads which distributions
those are off MEMORY_BACKEND_DISTRIBUTIONS instead of naming one by hand, so
a third memory backend the release ships cannot land on two of the three
implementations.

Type

  • Fix

Verification

uv run --frozen --python 3.12 --extra dev pytest \
  tests/test_release_plugin_list.py tests/test_updates_upgrade.py \
  tests/test_install_script.py tests/test_publish_beta.py \
  tests/test_docker_runtime.py tests/test_updates_beta_channel.py \
  tests/test_cli_upgrade_commands.py tests/test_updates_install_guard.py \
  tests/test_constraints_export_contract.py tests/test_import_cycle_budget.py \
  tests/test_kernel_budget.py tests/test_cli_onboard_commands.py \
  tests/test_everos_cloud_onboard.py tests/test_everos_cloud_discover.py \
  tests/test_plugin_boundary.py tests/test_config_product_render.py \
  tests/test_living_docs.py
-> passed; the only skips are the 4 PowerShell cases (no pwsh on this machine,
   so install.ps1 is checked by CI alone) and the platform-gated Windows cases.

ruff check .            -> All checks passed!
ruff format --check .   -> 2600 files already formatted
scripts/check_large_files.py github/main..HEAD      -> clean
scripts/check_source_language.py github/main..HEAD  -> clean
npx commitlint --from github/main --to HEAD         -> exit 0
sh -n install.sh                                    -> clean

The new rung case was mutation-checked: reverting the upgrade helper to
== "everos-memory" turns both
test_upgrade_helper_keeps_both_memory_backends_when_the_engines_fail and
test_the_helper_keeps_the_distributions_the_release_actually_ships red, on
the 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.

  • Relevant tests pass locally
  • Relevant lint / type checks pass locally
  • User-facing docs or screenshots are updated when needed

Risk

Every new install and every raven upgrade now carries one more distribution.
It adds httpx, which raven already depends on, and no code path builds the
cloud backend unless memory.backend names it. The behaviour change a user can
see 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_DISTRIBUTIONS and the release loop, and the list returns to three
distributions. 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.

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

Related Issues

Fixes #1028

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

Comment thread .github/workflows/release.yml Resolved
@gloryfromca
gloryfromca requested a review from 0xKT October 20, 2026 14:11
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 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 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).

@0xKT

0xKT commented Oct 21, 2026

Copy link
Copy Markdown
Member

Not a blocker (review of 7138301a, merged onto 4ec77de0): installed is not selected, as the commit says. I loaded the plugin registry and built the memory backend with both memory plugins visible: explicit, absent and fresh memory.backend configs all build the local backend and make no connection, on this head and on main. The cloud backend is built only when memory.backend names it.

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:

  1. Nothing pins which memory plugins each clone-mode rung of install.sh carries.
    • With "$cloud_dir" removed from the memory-only rung (install.sh:379), tests/test_release_plugin_list.py still passes (7 passed).
    • The same file catches the remote-mode filter losing the cloud name (1 failed).
    • The local-mode check collects the plugins-dist/<name> mentions anywhere in the script, so a rung that installs a subset stays green.
    • install.ps1 reuses one $memoryPlugin array on its rungs (:471, :491-498) and does not have this gap.
  2. Two copies of the rung table still say "memory plugin". They are at install.sh:469-475 and install.ps1:534-540. The helper's copy in raven/updates/upgrade.py was updated to the plural.

@0xKT
0xKT merged commit 1596461 into main Oct 21, 2026
28 checks passed
@0xKT
0xKT deleted the fix/cloud_memory_default_install branch October 21, 2026 13:31
0xKT pushed a commit that referenced this pull request Oct 22, 2026
…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
0xKT pushed a commit that referenced this pull request Oct 26, 2026
…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>
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.

fix(plugins): everos-cloud-memory has no install a user can follow that survives raven upgrade

2 participants