Skip to content

perf(memo): memo lists past 500 rows, first page with and without sources - #344

Merged
leemour merged 1 commit into
mainfrom
perf/memo-lists-paging
Nov 9, 2026
Merged

leemour merged 1 commit into
mainfrom
perf/memo-lists-paging

Conversation

@leemour

@leemour leemour commented Nov 9, 2026

Copy link
Copy Markdown
Member

Task

Publish the timings behind memo's --page on memories, facts, actions and agents list (NEED-2497) with the scripts that produced them, as the rule for results asks.

Why

The numbers were only in the body of cli-core #2811, and the scripts sat in a session folder that is deleted with the session.

What

  • performance/memo/lists-paging/README.md: why it was run, setup, the command, results, what it showed, what was not measured.
  • seed.mjs fills a throwaway store with 2000 synthetic memories, facts and evidence-findings batches. bench.sh times each command as a whole process. Both refuse the real store or HOME.
  • A new performance/memo/README.md area, linked from performance/README.md.

Headline: a later page costs the same as the first, at about 300 to 650 ms, mostly Node start. --json=<fields> halves the bytes but saves no time. actions list --brief already drops the sources, so memo needs no new flag.

Testing

biome check performance/memo, markdownlint-cli2 and cspell on the new pages, and pnpm docs:check pass. The scripts ran as published, apart from the guards and formatting added here.

🤖 Generated with Claude Code

…rces

WireCat Desktop's Memory page could read only the newest 500 memories,
facts or actions (NEED-2497). With cli-core's offset (#2811) and memo's
--page, this run times the first and a later page on a synthetic store
of 2000 rows of each, and what --brief and --json=<fields> save.

A later page costs the same as the first; Node start is most of the
time. --json=<fields> halves the bytes but saves no time, and
actions list --brief already drops the sources, so no new flag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@leemour
leemour merged commit 4c45ab4 into main Nov 9, 2026
1 check passed
leemour added a commit to WireCatLabs/cli-memo that referenced this pull request Nov 10, 2026
… codes per request; cli-core 0.627.0 (#788)

## Task

WireCat Desktop's Memory page cannot read past 500 memories, facts or
actions of a kind. The owner ruled that these lists page like
`relationships list`, with `--page` (NEED-2497 A). Also: a command
server that answers several requests at once must not give one request's
exit code to another (cli-core #2812).

## Why

- `memories list`, `facts list`, `actions list` and `agents list` took
only `--limit`, up to 500. cli-core 0.627.0 adds an `offset` to their
store lists ([cli-core
#2811](WireCatLabs/cli-core#2811)).
- `sync`, `doctor`, `digest run` and `digest backfill` wrote
`process.exitCode` directly. Under `commands --serve`, up to 8 requests
run at once, and Node has one `process.exitCode`, so the code went to
the request that ended next.

## What

- **`--page <n>` on the four lists**, as `relationships list` has it.
  - The offset is `(page - 1)` times the limit after the 500 clamp.
- The answer keeps `page.next_page`, and its `page.next` step repeats
`--limit` and the query.
- At the 500 cap the list reads one row past the page, so `has_more` is
exact. It used to be `true` for any full page of 500.
- `actions list` decides `has_more` on the store's rows before the group
lock filters them, so a filtered page does not end the paging early.
- **No new brief flag.** `actions list --brief` already leaves out the
sources with their excerpts. Memories and facts carry only evidence
references. `--json=<fields>` trims bytes but not time (timings below).
- **Exit codes per request.** `createProgram` takes `startCli`'s
`entry.setExitCode`, keeps it on the root command (`src/exit-code.ts`),
and the four commands set their code through it. A program built without
it, as in tests, still sets `process.exitCode`.
- **cli-core 0.625.0 → 0.627.0.** This brings cli-core's shared `models
ai` command. Its 7 command-lint findings belong to cli-core: the verbs
`off` and `forget`, the resource `ai`, three options with no role in the
vocabulary, and an example with three arguments. They go to
`lint/baseline` here.
- Regenerated `docs/commands.md` and `docs/dev/test-matrix.md`. The
changelog fragment is `changes/feat-memory-lists-paging.md`.

## Testing

- `free -g` first, then the full suite on one worker: `pnpm exec vitest
run --maxWorkers=1` gives 136 files and 1368 tests passed.
- New tests:
- `src/memories/paging.test.ts`: 520 memories and facts across the 500
boundary, each row once and newest first, and page 2 of actions and
agents.
- `src/page.test.ts`: an over-large `--limit`, the exact `has_more` at
the cap, and a filter that drops rows.
  - `src/exit-code.test.ts`: the code goes to the run's own setter.
- `typecheck`, `generate`, `test:matrix:check`, `docs:check`, `lint` and
`lint:commands` pass.
- Checked by hand on a throwaway store (`HOME`, XDG folders and
`MESSAGING_STORE` in a temp folder,
`DBUS_SESSION_BUS_ADDRESS=disabled:`), with the built
`dist/bin/memo.js`:
- `memo commands --serve` prints
`{"ready":true,"protocol":1,"version":"0.131.75","concurrent":8}`.
- `memo people list kind:stranger --json` answers a list, empty here,
with `page` and a `people.kinds` step.
- `memo memories list --limit 2 --page 1000 --json` gives the last two
of 2000 memories, with `has_more: false`.

First-page timings on 2000 synthetic rows of each kind, as medians of 9
runs. The full page and the scripts are in [cli-testing
#344](WireCatLabs/cli-testing#344)
(`performance/memo/lists-paging`).

| command | time | answer |
|---|---|---|
| `memories list --limit 1` (start, the floor) | 346 ms | 1 KB |
| `memories list --limit 500` | 383 ms | 241 KB |
| `memories list --limit 500 --page 4` | 316 ms | 239 KB |
| `actions list --limit 500` | 657 ms | 965 KB |
| `actions list --limit 500 --brief` | 622 ms | 766 KB |

A later page costs the same as the first.

## For WireCat Desktop

In `src-tauri/src/memory.rs`, `list()`, per kind:

1. Loop `memo <noun> list --limit 500 --page N --json --no-record`, with
`--brief` for actions, while `page.has_more` is true.
2. Remove duplicates by `ref`.
3. Stop after about 40 pages, and set `more` only when that limit
stopped the loop. The test fake always answers `has_more: true` for
facts.
4. An older memo refuses `--page`: retry without it through
`unsupported(&e)`.

Keep `agents list --limit 30` as it is.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant