Skip to content

fix(*): smaller import batches, retried with backoff, and a dismissable failed finish - #586

Merged
0xKT merged 4 commits into
refactor/ui_web_architecturefrom
fix/import_batch_twenty_dismiss_failed
Sep 21, 2026
Merged

0xKT merged 4 commits into
refactor/ui_web_architecturefrom
fix/import_batch_twenty_dismiss_failed

Conversation

@gloryfromca

@gloryfromca gloryfromca commented Sep 21, 2026 •

Copy link
Copy Markdown
Member

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

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

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

  • Security impact considered (no new inputs, credentials or endpoints)
  • Backward compatibility considered
  • Rollback path is clear for risky changes

Related Issues

N/A

gloryfromca and others added 3 commits September 21, 2026 14:53
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 gloryfromca changed the title fix(*): batch imports at twenty and let a failed finish be dismissed fix(*): batch imports at ten and let a failed finish be dismissed Sep 21, 2026

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

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 --check checks: 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 gloryfromca changed the title fix(*): batch imports at ten and let a failed finish be dismissed fix(*): smaller import batches, retried with backoff, and a dismissable failed finish Sep 21, 2026

@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 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 dev stopped at collection after 147 passes because the optional raven_everos package 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 --check checks: passed.

@0xKT
0xKT merged commit 99c37e7 into refactor/ui_web_architecture Sep 21, 2026
20 of 23 checks passed
@0xKT
0xKT deleted the fix/import_batch_twenty_dismiss_failed branch September 21, 2026 10:00
gloryfromca added a commit that referenced this pull request Sep 21, 2026
## 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>
gloryfromca added a commit that referenced this pull request Sep 21, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants