Skip to content

dreamer curate: scope per memory category (one category per run, rotating) #451

Description

@Sugaroverdose

Short description

Scope the curate dreamer task to one memory category per run (rotating across runs) instead of sending the entire pool in a single turn.

What problem does this solve?

The curate turn is scoped to the whole active pool — and that scope is the problem, not the model. A full-pool run puts ~250 memories in the prompt and takes ~150 model calls, so the turn's wall time is 5–19 min depending on server load (admission queuing under concurrent sessions; the decode rate itself is 200+ tok/s). The client fetch layer, however, kills the request at ~300 s (OpenCode client SDK's stock undici headers timeout — tracked at anomalyco/opencode #49044), so every full-pool curate run dies at ~302 s:

Run Pool Result
run A 256 killed at 302 s, committed 7 items, whole pool re-scanned
run B 250 killed at 302 s, committed 1 item
small pool 39 completes in 67 s

Each killed run re-scans the whole pool and commits 1–8 items, so the pool barely drains and the load is self-reinforcing (the scans themselves add to the server pressure that causes the stalls).

The key insight: curate's decisions are all category-local. Merges are category-locked (the system rejects cross-category merges — "Every id in a merge MUST share the same category"), archiving requires a surviving memory "in the same project and category", splits are 1→2 within the same category, rewrites are single-item. No curate decision needs cross-category visibility, so a per-category scope loses zero decision-relevant information.

Proposed solution

  • Send one whole category per run (not a fixed N-item slice — see alternatives).
  • Rotate the category across runs: a persisted cursor in the task schedule state, or simply floor(lastRunAt / 3600e3) % categoryCount (no schema change); a retry in the same time window should re-attempt the same category so a killed run's work isn't skipped.
  • Prompt scope line: "this is the whole of the <CATEGORY> category (the other categories run in later windows)".
  • Optional per-task config knob (e.g. curate.scope: "category" | "full") for users who have configured longer client fetch timeouts.

Measured on a v0.42.3 setup (local 27B model, pool of 246 across 5 categories: 87 / 61 / 56 / 31 / 11):

  • 31-item category: 166.6 s, completes clean
  • 61-item category: 133.7 s, completes clean
  • full pool (249): never completes (302 s kill, ×3)

Per-call latency is load-driven, not model-driven (2.1–7.6 s/call observed on the same full-pool runs at different concurrency levels) — and at any measured pace a single-category turn stays inside the window: the 61-item category is ≈37 calls, 80–280 s.

Alternatives considered

  1. Rely on a client fetch timeout fix (opencode side) — tracked separately (anomalyco/opencode #49044), but not a dependency of this proposal: opencode is moving rapidly toward v2 (v1 now reads V2 config fields for compatibility), so the current client fetch layer may be superseded before such a fix lands. Per-category scoping stands on its own — it fits the window as it exists today. The optional curate.scope: "full" knob keeps the full-pool option for users who configure a longer fetch budget (or whose v2 client handles it).
  2. Fixed N-item slice per run (tried first) — the memory loader orders by category ASC, updated_at DESC, so slice(0, N) starves the larger categories (60% of a 246-item pool never seen) and re-polishes the same N items (their own updates pin them to the top of the ordering). Rejected.
  3. Per-task model/timeout tuning — doesn't change scope; the structural over-window problem remains.

Area

Memory / historian / dreamer

Additional context

  • Plugin v0.42.3, OpenCode Desktop 1.18.31, macOS 26.6.2, local 27B model (200+ tok/s).
  • The ~302 s kill itself is the OpenCode client SDK's stock 300 s undici headers default on the non-streaming session RPC (headers arrive only at turn completion); no config surface reaches that layer — tracked at anomalyco/opencode #49044 (independent of this proposal; opencode's v2 transition may supersede that client layer).
  • Implementation sketch: in the dreamer task runner's curate branch, group loadActiveMemoryPromptMemories by category, pick one per run via rotation state in task_schedule_state, and pass the category name to buildCuratePrompt for the scope line. ~15 lines, no schema change (or an optional knob).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions