Skip to content

feat(sleep-cycle): memory budgeting — per-agent warm_tier hard cap (Sprint E)#103

Merged
salishforge merged 1 commit into
mainfrom
feat/memory-budgeting
Apr 18, 2026
Merged

feat(sleep-cycle): memory budgeting — per-agent warm_tier hard cap (Sprint E)#103
salishforge merged 1 commit into
mainfrom
feat/memory-budgeting

Conversation

@salishforge
Copy link
Copy Markdown
Owner

Summary

Adds memory budgeting per the ROADMAP Phase 2 milestone: "Hard limits on warm-tier size per agent with intelligent eviction. When an agent reaches capacity, the lowest-value memories are archived, not the oldest."

Opt-in via `WARM_TIER_MAX_PER_AGENT` env var. Default (unset or 0) = no cap, existing deployments unaffected.

Design

  • Cap is per-agent, cross-namespace. Per-namespace caps are a future enhancement.
  • Value = importance. Sleep cycle Phase 1 already computes importance as a weighted blend (recency, frequency, centrality, reflection, stability). Reusing it avoids duplicate scoring.
  • Graduated memories are NOT exempt. Cap is a hard limit; graduation affects retrieval scoring, not budgeting. Documented inline.
  • Runs as Phase 2b in the sleep cycle, immediately after threshold-based eviction (Phase 2). Kept separate from Phase 2 because the two have different invariants — threshold is "absolute below X", capacity is "relative rank bottom N" — and the log events / result fields are independently meaningful.
  • Target state: count exactly equals cap. Simple invariant.

No schema migration

The existing `warm_tier_importance_idx (agent_id, importance DESC)` already supports the bottom-N-by-importance query efficiently.

Changes

  • `src/sleep-cycle.ts` — new `phaseCapacityEviction` method, called after `phaseTriage`. Uses the same CTE pattern as threshold eviction (`WITH candidates / moved / deleted`), preserving namespace on cold rows.
  • `src/types.ts` — `SleepCycleConfig.warmTierMaxPerAgent?: number`; `SleepCycleResult.capacity_evicted?: number` (optional, set only when cap configured).
  • `src/server.ts` — parse `WARM_TIER_MAX_PER_AGENT` env var.
  • `.env.example` — doc with opt-in + lowest-importance-first semantics.

Tests (`tests/memory-budgeting.test.ts`, 307 lines, 7 cases)

  1. Cap disabled (default) → no evictions.
  2. Below cap → no evictions.
  3. Exactly at cap → no evictions.
  4. Over cap → evicts exactly (count - cap) rows, bottom-importance first, with namespace preserved in cold_tier.
  5. Cross-namespace cap — per-agent applies across all namespaces.
  6. Threshold + capacity interaction — both passes run, counts sum correctly.
  7. Graduated row gets capacity-evicted when it is lowest-importance.

All tests use explicit timestamps — no sleeps.

Test plan

  • CI `type-check`, `lint`, `build` pass
  • CI `test-integration` runs the new budgeting suite
  • Existing sleep cycle tests still pass (behavior unchanged when `warmTierMaxPerAgent` is unset/0)

Out of scope

  • Per-namespace caps — simpler per-agent cap first per ROADMAP wording.
  • Dynamic cap adjustment based on memory health — belongs in Sprint F adaptive scheduling.
  • Hot_tier caps — hot_tier drains via consolidation; only warm_tier needs budgeting.

…e eviction

Adds memory budgeting: an optional WARM_TIER_MAX_PER_AGENT env var that
caps warm_tier size per agent. When exceeded, the sleep cycle archives
the lowest-importance rows (not the oldest) to cold_tier until the
count matches the cap.

Opt-in: default (unset or 0) preserves existing behavior. Existing
deployments are unaffected.

Design:
- Capacity eviction runs as Phase 2b in the sleep cycle, immediately
  after the threshold-based eviction in Phase 2. The two passes have
  distinct invariants: threshold eviction operates on absolute
  importance (evict below X), capacity eviction operates on relative
  rank (evict bottom N). Keeping them separate makes log events and
  result fields independently meaningful.
- Value = importance. Sleep cycle Phase 1 already computes importance
  as a weighted blend (recency, frequency, centrality, reflection,
  stability). Reusing it avoids duplicate scoring logic.
- Graduated memories are NOT exempt from capacity eviction. Cap is a
  hard limit; graduation affects retrieval scoring, not budgeting.
  Documented inline.
- Eviction preserves namespace on the archived row (cold_tier has a
  namespace column as of migration-v3.1).
- New SleepCycleResult.capacity_evicted field is optional — set only
  when a cap is configured, keeping existing callers unaffected.

Config:
- WARM_TIER_MAX_PER_AGENT env var (server.ts).
- SleepCycleConfig.warmTierMaxPerAgent (types.ts).
- .env.example documents the opt-in with lowest-importance-first note.

No schema migration. The existing warm_tier_importance_idx
(agent_id, importance DESC) already supports the "bottom N by
importance" query efficiently.

Tests (tests/memory-budgeting.test.ts, 307 lines, 7 cases):
1. Cap disabled (default) — no evictions.
2. Below cap — no evictions.
3. Exactly at cap — no evictions.
4. Over cap — evicts exactly (count - cap) rows, bottom-importance
   first, with namespace preserved in cold_tier.
5. Cross-namespace cap — cap is per-agent across all namespaces.
6. Threshold + capacity interaction — both passes run, counts sum.
7. Graduated row gets capacity-evicted when it is lowest-importance.
@salishforge salishforge merged commit b7c58ab into main Apr 18, 2026
12 checks passed
@salishforge salishforge deleted the feat/memory-budgeting branch April 18, 2026 03:58
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