Repository navigation
fix(*): smaller import batches, retried with backoff, and a dismissable failed finish - #586
Conversation
Against a real EverOS with a slower extraction model (qwen3.8-flash), batches of 50 memory-file messages took 2.4 to 7.4 minutes each and six of seven sources died on the six-minute bulk budget; the same batches took about 75s on claude-sonnet-4-5. Twenty keeps a batch inside the budget with room on either model. Not one: each add carries about 7s of fixed cost, so single messages would make a real-size import three to four times longer. The batch size takes part in the EverOS message id, so a run imported under the 50-message batch is not deduplicated against a re-import under 20; no installation has completed a real-size import under either, so nothing on disk depends on the old id. Co-authored-by: Claude (claude-fable-5-1) <noreply@anthropic.com>
A finished run with failures showed its count and a retry but no close, so the row stayed until a retry cleared every failure. The close is now offered on any finish; it hides that run by signature, and the failed entries stay in the state file for a retry from the wizard or the CLI. Co-authored-by: Claude (claude-fable-5-1) <noreply@anthropic.com>
The maintainer's call over twenty: a batch that finishes well inside the six-minute bulk budget on any extraction model matters more than the fixed cost of about 7s that every add carries. Under the slow model measured on 2026-09-21 a 50-message batch took up to 7.4 minutes; ten keeps the worst measured per-message cost at about a quarter of the budget. Co-authored-by: Claude (claude-fable-5-1) <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.
No findings survived refutation. The ten-message limit stays inside the existing count/character batching contract and preserves final-batch signaling; the failed-run close only records the finished status signature, while failed entries remain available to the existing wizard/CLI retry path. The title was also updated to say ten before this review was posted.
Coverage included AGENTS.md, CLAUDE.md, CONTEXT-MAP.md, and the Runtime/Web UI context; the full diff and the new-head delta; importer state, RPC, EverOS backend callers, and importSync store/source behavior; relevant history and blame; backward compatibility and message-id implications; test changes for weakening; and the documented architecture boundaries. No boundary or compatibility regression was found.
Verification:
uv run pytest tests/test_importer_*.py tests/integration/test_import_e2e.py -q: 272 passed on the current head.npm test --prefix ui-web: 190 files, 2550 tests passed; the web tree is unchanged by the final head commit.- Targeted web tests: 2 files, 28 tests passed.
npm run type-check --prefix ui-web: passed.npm run lint --prefix ui-web -- src/features/importSync/ImportSyncPage.tsx src/features/importSync/ImportSyncPage.test.tsx: 0 errors, 5 pre-existing warnings in unrelated files.- Ruff, source-language, commit-message, and
git diff --checkchecks: passed.
…source A batch the memory service refused failed its source at once, and the next source started at once. Against a real service that turned one two-minute rate-limit window at the extraction provider into five failed sources, since each one's first batch ran into the same wall; a parse failure on one model answer and one slow answer past the budget cost a source each the same way. A refused batch is now sent again after 30s, 60s and 120s before the source counts as failed, whether the backend returned False or raised. The wait polls the stop file every second, so a stop lands inside it and leaves the source unmarked for the next run, the way a stop between batches does. Co-authored-by: Claude (claude-fable-5-1) <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 new retry/backoff delta is consistent with the memory-store contract and the repository's existing store-pipeline retry semantics. Explicit import metadata keeps is_final stable across replay, an exhausted retry still leaves the source failed and resumable, and a stop during backoff leaves it unmarked. No concrete failure survived refutation.
Coverage for this revision included the repository rules and Runtime/Web UI contexts; the full target diff and the delta from e44e8d600627; importer, RPC, CLI, state, EverOS backend, and store-pipeline callers and history; backward compatibility and replay behavior; tests for weakening; and architecture boundaries. The new tests add transient-success, exhaustion, raised-error, and cancellation cases while preserving the existing batching and error-isolation coverage.
Verification:
uv run --extra dev pytest tests/test_importer_orchestrator.py tests/integration/test_import_e2e.py tests/test_everos_backend.py tests/test_rpc_import_sync.py tests/test_cli_import_commands.py tests/test_importer_phases.py tests/test_importer_state.py -q: 318 passed.- The first run without
--extra devstopped at collection after 147 passes because the optionalraven_everospackage was absent; the declared dev environment above resolved that setup issue. - Ruff check and format check on the touched Python files: passed.
- Source-language, commit-message, and
git diff --checkchecks: passed.
## Summary Second catch-up of `refactor/ui_web_architecture` with `main`, this time to `6ebc2baa` (#592): the 31 commits `main` gained since #529 took it to `dbe259f6`. Synced main to `6ebc2baa`; the next round starts from that SHA. #529 landed as a squash, so git still saw the two branches diverge at `5a29ea22` and re-raised every conflict #529 had already settled (68 paths instead of 33). The first commit here merges `dbe259f6` with `-s ours` -- tree unchanged, ancestry recorded -- so the second commit merges only what is new; a third takes #528 and a fifth takes #592 (READMEs only, no conflict), both of which landed on `main` while the earlier commits were being verified. The same steps work for every later round. Thirty-three paths conflicted in the second commit, one in the third; the fourth commit is what an independent read of the merge turned up (the sheet's Connect, `capabilities_wanted`'s fourth case, the dead styles); the sixth merges this branch's own tip (#584, #591, #586), where only the fixture-shape gate's UNSENT list met (both sides kept). How each conflict was settled: - 17 legacy ui-web files (`live/`, `demo/`, `features/xa`, `features/knowledge`, `scripts/boot-order.test.mjs`, `composer/open-conversation.test.ts`), edited on `main` for #542, #549 and #554: deleted on this branch, and the deletions stand. `main`'s `shell/workdir.ts` and its test, which git relocated into `lib/`, are dropped for the same reason -- see the follow-up below. - `main.tsx`, `page.html`, `state/session/registry.test.ts`: this branch's version. `main`'s edits there were the legacy chip wiring; the registry conflict was a rename git guessed from `scripts/page-switch-live.test.mjs`. - `raven/agent/subagent/probe.py`: both imports; Test records a snapshot for a preset too (`main`, #554) through `record_capabilities` (this branch, #559); a kept record carries `needs_auth`; the boot backfill re-measures an outdated model menu (this branch) and a recorded credential refusal (`main`). - `raven/rpc/methods/subagents.py`: `needs_auth` reads the snapshot this branch already loads per row. - `raven/agent/subagent/manager.py` (#528, third commit): both sides kept at both hunks -- `main`'s two shutdown refusals beside `SPAWN_REFUSED_PREFIX` with this branch's `_row_pin` after them, and the spawn announce that types its mark and carries `node_id` (this branch) then returns early when `_inject` was refused (`main`). - `tests/test_subagent_probe.py`, `test_subagent_acp.py`, `test_rpc_subagents.py`, `test_rpc_console.py`, `test_config_update.py`: both sides' new tests, in order. This branch's two backfill tests pin `_unconfigured_acp_preset_rows` the way `main`'s do, and the `_agent_home` helper both sides defined is one function passing a `Path`, as the config property does. - `README.md`, `README.zh-CN.md`: `main`'s figure and showcase section. - `ui-web/src/rpc/generated.ts`, `ui-tui/src/rpc/generated.ts`, `ui-tui/src/i18n/messages.generated.ts`: regenerated from the merged schema and catalogue. Carried onto the rebuilt tree, since the backend halves merged cleanly: - #554: the agent hub's rows, its sheet and the wizard step name a refused agent `Unauthorized` (disabled) instead of offering Connect; the sheet offers Test beside it, the press that re-measures the handshake and lets the row return to Connect after a sign-in. `capabilities_wanted` treats a recorded refusal as wanting a re-measure, the same fourth case the boot backfill gained from `main`. Four tests. - #542: rail rows carry the session's `workdir`, so the folder group `RailPage.tsx` gained in the merge can fill. The tag's class moves under the rail namespace (`rail-wdt`) with its rule in the new `features/rail/styles.css`, as check-class-namespace requires. - #549: the page-side upload ceiling follows `raven/rpc/files.py` to 100 MB. - The offline fixtures send `needs_auth`; `session.list.sessions[].workdir` is pinned unsent in fixture-shape until the chip lands. The folder chip's thirty lines of popover rules that `main` added to `styles/page.css` are dropped with the chip; the follow-up brings its own. Follow-up, not in this PR: the folder chip itself (#542's `shell/workdir.ts`: the composer chip, the `fs.dirs` browser, `workdir` on `session.create`) has to be rebuilt on this tree's chip pattern (`state/perm.ts` + `chrome/PermChip.tsx`). The backend, the i18n keys and the rail half are already here. ## Type - [ ] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [x] Other (catch-up merge) ## Verification Run in the worktree on the merged tree: - `uv run pytest tests/test_subagent_probe.py tests/test_subagent_acp.py tests/test_rpc_subagents.py tests/test_rpc_console.py tests/test_config_update.py tests/test_rpc_session.py tests/test_rpc_contract_shapes.py tests/test_rpc_schema_match.py tests/test_rpc_registration.py tests/test_i18n_boundary.py tests/test_subagent_manager.py tests/test_subagent_dag_runner.py tests/test_rpc_bootstrap.py tests/test_cli_tui_commands.py tests/test_cli_gateway_commands.py tests/test_rpc_transport.py tests/test_cli_serve_commands.py tests/test_importer_orchestrator.py tests/test_cli_gateway_page.py -q` on the final head -- 1893 passed (the same files, run per commit as the branch grew: 1141, 590, 501). - `make lint-python` -- ruff check clean; ruff format clean after formatting the three spliced test files. - `npm test` in `ui-web` on the final head -- 189 files, 2468 tests passed (before the sync merge: 193 files, 2609; the first run had failed two gates, check-class-namespace and fixture-shape, on the merged `.wdt` class and the three new optional wire fields, both fixed as described above). - `npm run type-check`, `npm run lint` (0 errors, 5 pre-existing warnings), `npm run gen:check` (193 methods) in `ui-web`. - `npm run lint:i18n`, `npm run lint:rpc`, `npm run type-check`, `npm test` in `ui-tui` -- all clean. - `make build-ui` on the final head -- page built, `boot-snapshot: OK` on both snapshots (235 nodes, the golden #591 set). - `scripts/check_source_language.py origin/refactor/ui_web_architecture..HEAD` and `scripts/check_large_files.py origin/refactor/ui_web_architecture..HEAD` -- both exit 0; `git merge-tree --write-tree origin/main HEAD` -- clean. The full Python suite is left to CI. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed ## Risk No source change of its own beyond the resolutions and the three carries above. On the page: a refused external agent now shows a disabled `Unauthorized` button where it showed Connect; conversations pinned to a folder group under their own rail heading with the folder's name on the row; uploads up to 100 MB are sent instead of refused at 25 MB. All three match what `main` ships today. This branch is squash-only, so the merge base stays at `5a29ea22` after this lands; the next round repeats the `-s ours 6ebc2ba` step first. Rollback is reverting the squash commit; the branch is back at `99c37e74`. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A. Unblocks #475. --------- Co-authored-by: xfng-sd <xufang@shanda.com> Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com> Co-authored-by: Handsome-wzw <68996445+Handsome-wzw@users.noreply.github.com> Co-authored-by: Dizhan Xue <dizhan.xue@evermind.ai> Co-authored-by: Kevin Hu <kevinhu.sh@gmail.com> Co-authored-by: admin <admin@SH-HuKai.local> Co-authored-by: 江国庆/Forrest <mr.jianggq@163.com> Co-authored-by: 江国庆 <guoqingjiang@deepglint.com> Co-authored-by: Codex <noreply@openai.com> Co-authored-by: ypflll <ypflll@163.com> Co-authored-by: yao pengfei <yaopengfei@shanda.com> Co-authored-by: zhanghui <huizhang1995@gmail.com> Co-authored-by: Zuyi Zhou <144661423+ZuyiZhou@users.noreply.github.com> Co-authored-by: Tong Li <litong02@shanda.com> Co-authored-by: litong <238663200+TongLi31@users.noreply.github.com> Co-authored-by: Tchen-data <176354753+Tchen-data@users.noreply.github.com> Co-authored-by: gloryfromca <23442919+gloryfromca@users.noreply.github.com> Co-authored-by: Dizhan Xue <xuedizhan17@mails.ucas.ac.cn> Co-authored-by: silverLXT <25195409+silverLXT@users.noreply.github.com> Co-authored-by: xiaotian.luo <xiaotian.luo@thetahealth.ai> Co-authored-by: userName20260323 <zhao.wang@evermind.ai> Co-authored-by: zhao.wang <270284818+userName20260323@users.noreply.github.com> Co-authored-by: arelchan <1239372199@qq.com> Co-authored-by: arelchan <204152633+arelchan@users.noreply.github.com>
…le failed finish (#586) ## Summary Three follow-ups from running the web cold-start import against a real EverOS with the maintainer's full `~/.claude` (19 memory-file sources, about 1,150 distinct messages). - **Import batches drop from 50 to 10 messages.** With a slower extraction model (`qwen/qwen3.8-flash` via OpenRouter) a 50-message batch took 2.4 to 7.4 minutes and six of seven memory-file sources died on the six-minute bulk budget; the same batches took about 75 s on `claude-sonnet-4-5`. Ten is the maintainer's call: a batch that finishes well inside the budget on any model matters more than the fixed cost of about 7 s that every add carries on the EverOS side (which is also why it is not one message per add). Under the slow model the worst measured per-message cost puts a batch of ten at about a quarter of the budget. The 30k-character bound is unchanged. - **A refused batch is retried with backoff before its source fails.** A batch the memory service refused failed its source at once, and the next source started at once; against the real service one two-minute rate-limit window at the extraction provider (OpenRouter 429, which EverOS retries only twice sub-second) took five sources down in 70 s, each one's first batch running into the same wall, and a parse failure on one model answer or one slow answer past the budget cost a source each the same way. A refused batch is now sent again after 30 s, 60 s and 120 s before the source counts as failed, whether the backend returned False or raised; the wait polls the stop file every second so a stop lands inside it. - **A finished import with failures can be dismissed.** The rail row showed the failure count and a retry but no close, so a run whose failures did not clear stayed in the rail indefinitely. The close is now offered on any finish; it hides that run by signature, and the failed entries stay in the state file so a retry from the wizard or the CLI still picks up exactly those. The batch size takes part in the EverOS message id, so a run imported under the 50-message batch is not deduplicated against a re-import under 10. No installation has completed a real-size import under either limit, so nothing on disk depends on the old id. ## Type - [x] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification - `uv run pytest tests/test_importer_orchestrator.py tests/integration/test_import_e2e.py tests/test_everos_backend.py tests/test_rpc_import_sync.py tests/test_cli_import_commands.py tests/test_importer_phases.py tests/test_importer_state.py -q`: 313 passed. The batching tests now pin 120 messages to twelve batches of 10 and 160 messages to sixteen, `is_final` only on the last. New retry tests: a batch refused twice lands on the third send with the recorded waits, one refused every time fails after the last wait with `after 4 attempts` in the error, a raised store error is retried the same way, a stop during a wait ends the run with the source unmarked, and `_pause` returns as soon as the stop file appears (286 passed over the importer / RPC / CLI / EverOS backend suites). - `uv run ruff check` and `ruff format --check` on the touched files: clean. - ui-web `npm test`: 2550 passed across 190 files; `npm run type-check` clean; `npm run lint` 0 errors (5 pre-existing warnings). - Reverse checks: with `_BATCH_MSG_LIMIT` set back to 50 the batching tests fail; with the retry loop reduced to a single send the retry tests fail; with the stop check removed from the wait the stop test fails; with the close restored to clean finishes only, the row test "offers a retry, and a dismiss that takes the row down" fails. - Real host: the 50-message failure mode was measured on a live gateway (isolated `RAVEN_HOME`, throwaway EverOS on :18893, the maintainer's real `~/.claude`): 6 of 7 sources timed out at exactly 360 s under `qwen3.8-flash`. Reruns on the same host at batch 10 without the retry: one 429 window took five sources, one 10-message batch still ran past the budget. A rerun with the retry is in progress and will be reported on this PR. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed ## Risk User-visible: imports make five times as many EverOS calls, each smaller; a source now checkpoints, and a stop lands, every 10 messages instead of 50. A refused batch costs up to 210 s of waiting before its source is given up on. The rail's finished-with-failures row gains a close button. The state file format and the RPC contract are unchanged. Rollback: revert the squash commit; no data migration is involved. - [x] Security impact considered (no new inputs, credentials or endpoints) - [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-fable-5-1) <noreply@anthropic.com>
Summary
Three follow-ups from running the web cold-start import against a real EverOS with the maintainer's full
~/.claude(19 memory-file sources, about 1,150 distinct messages).qwen/qwen3.8-flashvia OpenRouter) a 50-message batch took 2.4 to 7.4 minutes and six of seven memory-file sources died on the six-minute bulk budget; the same batches took about 75 s onclaude-sonnet-4-5. Ten is the maintainer's call: a batch that finishes well inside the budget on any model matters more than the fixed cost of about 7 s that every add carries on the EverOS side (which is also why it is not one message per add). Under the slow model the worst measured per-message cost puts a batch of ten at about a quarter of the budget. The 30k-character bound is unchanged.The batch size takes part in the EverOS message id, so a run imported under the 50-message batch is not deduplicated against a re-import under 10. No installation has completed a real-size import under either limit, so nothing on disk depends on the old id.
Type
Verification
uv run pytest tests/test_importer_orchestrator.py tests/integration/test_import_e2e.py tests/test_everos_backend.py tests/test_rpc_import_sync.py tests/test_cli_import_commands.py tests/test_importer_phases.py tests/test_importer_state.py -q: 313 passed. The batching tests now pin 120 messages to twelve batches of 10 and 160 messages to sixteen,is_finalonly on the last. New retry tests: a batch refused twice lands on the third send with the recorded waits, one refused every time fails after the last wait withafter 4 attemptsin the error, a raised store error is retried the same way, a stop during a wait ends the run with the source unmarked, and_pausereturns as soon as the stop file appears (286 passed over the importer / RPC / CLI / EverOS backend suites).uv run ruff checkandruff format --checkon the touched files: clean.ui-web
npm test: 2550 passed across 190 files;npm run type-checkclean;npm run lint0 errors (5 pre-existing warnings).Reverse checks: with
_BATCH_MSG_LIMITset back to 50 the batching tests fail; with the retry loop reduced to a single send the retry tests fail; with the stop check removed from the wait the stop test fails; with the close restored to clean finishes only, the row test "offers a retry, and a dismiss that takes the row down" fails.Real host: the 50-message failure mode was measured on a live gateway (isolated
RAVEN_HOME, throwaway EverOS on :18893, the maintainer's real~/.claude): 6 of 7 sources timed out at exactly 360 s underqwen3.8-flash. Reruns on the same host at batch 10 without the retry: one 429 window took five sources, one 10-message batch still ran past the budget. A rerun with the retry is in progress and will be reported on this PR.Relevant tests pass locally
Relevant lint / type checks pass locally
User-facing docs or screenshots are updated when needed
Risk
User-visible: imports make five times as many EverOS calls, each smaller; a source now checkpoints, and a stop lands, every 10 messages instead of 50. A refused batch costs up to 210 s of waiting before its source is given up on. The rail's finished-with-failures row gains a close button.
The state file format and the RPC contract are unchanged. Rollback: revert the squash commit; no data migration is involved.
Related Issues
N/A