Purpose
Pool-split retrieval is used in retrieval scenarios for agent memory. When a single scoring function is used to rank a mixed-type memory store and only one token budget is given, high-priority entry types monopolize the context window, and low-priority types can never appear. In the system where this pattern arose, measurement data before reservation showed that growth-type entries loaded 0 times across 4 test queries, and skill-type entries loaded only once across 30.
There is also a more dangerous converse to this failure mode: a large amount of new, freshly scored content may push a long-standing behavioral correction out of scope, exactly when the agent is about to repeat the mistake that correction was meant to prevent. A correction that only loads on quiet turns is worthless.
Main capabilities
Pool (reserved budget)
Each pool has a token cap and an entry cap for a set of types. Pools are filled independently, so a high-priority type in one pool cannot consume the budget of a low-priority type in another pool, and low-priority types get guaranteed space.
Pool(name, max_tokens, max_entries, types=None) is a frozen dataclass describing a reserved budget:
name: the unique key used when returning pool results.max_tokens: the token cap for context (non-always) entries.max_entries: the cap on the number of context entries; always-scope entries are not counted.types: the tuple of entry types this pool draws from; None means accept all types.
max_tokens or max_entries being negative raises ValueError.
Always-scope unconditional loading
Entries with scope="always" (behavioral corrections) load first and bypass the token cap and entry cap. No matter how much new content arrives, it will never be pushed out. This is the core invariant for which this library exists.
retrieve function
retrieve(entries, pool, context=None, *, scorer, token_counter, is_active, now) -> List[dict] fills a pool with the following semantics:
- Filter out active entries, then filter out this pool's types.
- Split into always-scope (
scope == "always") and context entries. - All always-scope entries load first, bypassing
max_tokensandmax_entries. - Context entries are sorted by a fully deterministic key:
(-score, created-or-"", id-or-"", original-index). Input order never changes the result, except by breaking remaining ties via original-index. - Fill the budget greedily. For an entry's cost, prefer
entry["token_count"](when present and a positive integer), otherwise usetoken_counter(content), never use 0 when the count is missing. - The returned entries are the same dict objects (no copies), in the order always-scope (input order) followed by context (sorted order).
- Never modify entries.
retrieve_pools function
retrieve_pools(entries, pools, context=None, *, ...) -> Dict[str, List[dict]] Populate multiple pools from the same set of entries. Pools are processed in order; entries selected by an earlier pool are excluded from subsequent pools, so each entry lands in at most one pool. Deduplication is based on object identity (id()), so even if an entry lacks the "id" key it will still be deduplicated correctly. Duplicate pool names raise ValueError.
Default scorer
default_scorer(entry, context, *, now=None) -> float 's scoring formula is: base priority × recency decay (90-day linear, lower bound 0.3) × tag overlap × project match × message content word overlap.now can be injected to achieve deterministic scoring. There is no access count/frequency term. Scoring is lexical and fully pluggable, and a custom scorer can be passed in.
Default counter and active determination
default_token_counter(text) -> int returns max(1, len(text) // 4). Deliberately coarse and pluggable, never returns 0.
default_is_active(entry) -> bool: returns True when status is "active" (the default when the key is missing) and there is no superseded_by pointer.
Usage
This is a single-file, dependency-free module. It supports embedded installation by copying poolsplit.py into a project and importing it.
Development (testing) requires Python 3.9 or newer, with no runtime dependencies.
Suitable scenarios
Suitable for systems that need to retrieve for agent memory and want to avoid a single budget causing type starvation or important corrections being squeezed out. Especially suitable for scenarios containing multiple entry types, needing to ensure visibility of low-priority types, and needing behavior corrections to be loaded unconditionally.
Limitations (explicitly what it does not do)
- Persistence: poolsplit only reads and writes lists of dicts; it is not responsible for loading, saving, locking, or migrating storage.
- Semantic scoring: the default scorer is lexical (tag and word overlap) and does not include embedding vectors. It is fully pluggable, and you can provide your own scorer such as vector similarity.
- Access frequency tracking: reading does not modify any entries, there is no access count, and there is no frequency term in scoring. This is intentional: the frequency feedback loop (retrieval raises the count, the count raises the score, and the score causes it to be retrieved again) once made about 97% of the content in the original storage unreachable under ordinary queries. Ranking is kept independent of retrieval history.
- Asynchronous: everything is synchronous and in-process.
This library was clean-room extracted and re-expressed from the retrieval layer of a personal AI system that has been in production since early 2026.
Licensed under the MIT License, see LICENSE for details.