Persistent memory and context for AI.
π§ Work in progress. Infinitum is not a polished production product yet. Design critiques, use cases, and pull requests are welcome β open an issue and tell me what this should become.
Infinitum is a standalone Python 3 memory and context runtime for AI agents and LLM applications. It exposes an OpenAI-compatible API, maintains durable event-sourced memory, learns and consolidates useful long-term context, and injects only the most relevant memory into each request.
The newer AI services β OpenAI, Claude, and the rest β now ship their own memory. Their agents remember your decisions, your preferences, the things you already explained once, and that continuity makes them noticeably better at reasoning and recall and simply more useful to have around.
Local AI doesn't have that. Worse, the moment you want to try a new tool or switch between providers, frameworks, and models, you leave that "magic" memory behind β it's locked inside whichever service learned it. If you find yourself using and switching between multiple AI applications β OpenCode, Open WebUI, and others β Infinitum is the glue.
Infinitum moves the memory out of the service and into a runtime that sits in front of whatever you point it at. It learns from your interactions and improves the general intelligence of whatever tool or service you happen to want to use today. Bring your own model, bring your own agent framework, switch whenever you like β the memory keeps compounding. No memory exports, no tricks, no workarounds.
Current release: v0.6.3.
Full release history lives in CHANGELOG.md.
- Repository name:
infinitum - Short description: Persistent memory and context runtime for AI agents and LLMs with an OpenAI-compatible API.
- Python distribution:
infinitum - Python package:
infinitum - CLI:
infinitum
flowchart LR
C[OpenAI-compatible client] --> R[Infinitum]
R --> CC[Context Compiler]
CC --> M[(Global Memory)]
M --> CC
CC --> R
R --> U[Configured upstream]
U --> L[LLM]
L --> U
U --> R
R --> C
R --> E[(Immutable events)]
R --> W[Async learning worker]
W --> M
A client changes only its base_url:
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:8788/v1",
api_key="your-upstream-key",
)
response = client.chat.completions.create(
model="your-model",
messages=[{"role": "user", "content": "What database did we decide to use?"}],
)Infinitum retrieves useful memories, injects a bounded memory block, forwards the request, returns the upstream response, stores the raw interaction as events, then learns asynchronously.
The current memory pipeline has three layers:
Immutable events
-> detailed derived memories
-> topic summaries
Raw events are the source of truth. Memories are derived state and can be merged, reinforced, superseded, contested, or archived without destroying history.
Supported memory types:
factdecisionpreferencegoalprocedurelessonepisodic
Supported lifecycle states:
activesupersededcontestedarchived
Retrieval is not just nearest-vector search. Each active memory is scored from a configurable combination of:
- semantic similarity when embeddings are enabled
- lexical relevance
- memory importance
- confidence
- freshness
- topic relevance
- optional user/project/CWD provenance affinity after the memory is already relevant
Eligibility requires at least one genuine relevance signal (semantic, lexical, or topic) before the query-independent terms like importance, confidence, and freshness can qualify a memory. The same scorer, and hence the same gate, also backs the drill-down memory tools and POST /memory/search.
The Context Compiler then:
- retrieves broadly;
- ignores non-active memory;
- removes near duplicates;
- includes relevant topic summaries;
- ranks detailed memories;
- calculates the actual token budget remaining in the model window;
- injects only memories that fit and add value;
- records exactly which memories were injected into each request.
The configured memory budget is a ceiling, not a target.
The compiled block is built to be cache-stable across turns; the mechanics are in docs/ARCHITECTURE.md. Configuration keys are in docs/CONFIGURATION.md.
The compiler injects a bounded memory block per request. When the model needs more than that block shows, optional read-only tools let it drill deeper.
When enabled, Infinitum appends two function tools to the client's tool list on every memory-enabled request, as long as the client has not defined a tool of the same name:
infinitum_memory_search(query, limit)β ranked search over active memories (default limit 10, max 50);infinitum_memory_get(memory_id)β full current content plus provenance source event ids for one memory.
The tools use the same scorer as injection, so results are ranked identically. They read the same global namespace, and request user/project/CWD context stays a soft affinity, never an access-control filter.
The tool loop runs server-side and is transparent to the client: visible text, invisible machinery. The behavior contract, latency edges, and parser notes are in docs/API.md, and enabling the tools via memory.tools_enabled is documented in docs/CONFIGURATION.md.
Learning occurs after the user has already received the model response.
sequenceDiagram
participant Client
participant Runtime
participant Upstream
participant Worker
participant DB
Client->>Runtime: chat/completions
Runtime->>DB: retrieve memory
Runtime->>Upstream: augmented request
Upstream-->>Runtime: response/stream
Runtime-->>Client: response/stream
Runtime->>DB: write immutable events
Runtime->>Worker: durable learning job
Worker->>Worker: extract memory candidates
Worker->>DB: reinforce/create/supersede
Worker->>DB: mark changed topics dirty
Note over Worker,DB: Debounce/coalesce related changes
Worker->>DB: incrementally update topic summary
The extraction LLM is only allowed to propose memory candidates. Deterministic code performs database mutations. This prevents an extraction response from directly rewriting memory state.
Repeated evidence reinforces an existing memory instead of creating duplicate records. Explicit corrections can supersede older active memories while the source events remain available.
V0.2.0 continues to separate memory maintenance into three timescales so long-lived memory does not repeatedly resend the entire corpus to an LLM:
EVERY INTERACTION
current interaction + top nearby memories
-> extract durable memory candidates
-> deterministic create/reinforce/supersede
INCREMENTAL (implemented in v0.1.2+)
changed memories accumulate per topic
-> debounce/coalesce bursts
-> current topic summary + changed records + small context sample
-> update summary
PERIODIC (roadmap)
large topic/corpus maintenance pass
-> cluster / canonicalize / detect stale or conflicting state
-> rebuild derived summaries/indexes when justified
The incremental path is intentionally bounded. A topic summary is bootstrapped once from a limited sample, then later updates use the existing summary plus only a bounded batch of changed memories and a small active-topic context sample. If the same topic changes several times quickly, those changes are coalesced into one background summary job.
A newly created canonical memory starts with observation_count = 1. Later independent interactions increase that count only when the new candidate is judged equivalent to the same active memory. V0.2.0 uses the same guarded cascade introduced in V0.1.3:
exact type + topic compatibility
|
+-- high lexical equivalence ------> reinforce
|
+-- high embedding similarity ----> reinforce
|
+-- learner says reinforce
+ retrieval/similarity guards
+ optional reinforces_memory_id
|
+------------------> reinforce
otherwise -------------------------------> create a new memory
explicit correction / supersede ----------> never reinforce
observation_count is deliberately not treated as truth or authority. It means that multiple distinct interactions supported the current canonical memory. One newer explicit correction may still supersede a memory that has many older observations. Reprocessing the same source event IDs is idempotent and does not increment the count again.
Tuning knobs are documented in docs/CONFIGURATION.md; the first-class memory_observations evidence model is roadmap work (docs/ROADMAP.md).
The primary maintainer runs a local Qwen model on an NVIDIA DGX Spark, with Infinitum pointing directly at it. The full working YAML lives in docs/CONFIGURATION.md.
Python 3.12 or newer is required.
python3 -m venv .venv
source .venv/bin/activate
pip install -e .
cp config.example.yaml config.yamlOptional exact token counting:
pip install -e '.[tokenizer]'Start the service:
infinitum serve --config config.yamlor:
INFINITUM_CONFIG=config.yaml uvicorn infinitum.app:app --host 0.0.0.0 --port 8788Verify it's up:
curl -sS localhost:8788/health # health check: version and active_requests
curl -sS localhost:8788/v1/models # the OpenAI-compatible surface answersdocker run -d --name infinitum --restart unless-stopped \
--user $(id -u):$(id -g) \
-p 8788:8788 \
--add-host=host.docker.internal:host-gateway \
-v ~/infinitum/config.yaml:/config/config.yaml:ro \
-v ~/infinitum/db:/db \
-e UPSTREAM_API_KEY=replace-me \
ghcr.io/aspotton/infinitum:latestSetup, verification, and upgrade/backup details: docs/DOCKER.md. To pin a specific version, replace latest with a full release tag (v<MAJOR>.<MINOR>.<PATCH>, e.g. v1.2.3).
A minimal setup:
upstream:
base_url: http://localhost:4000/v1
api_key: ${UPSTREAM_API_KEY:-}Everything else has a tuned default baked into the program; the full key-by-key reference is in docs/CONFIGURATION.md#defaults-at-a-glance. passthrough_authorization is already true by default, so the inbound Authorization header is forwarded to the upstream; the background-learning key caveat is documented in docs/CONFIGURATION.md.
Further reference material in docs/CONFIGURATION.md:
- Request-context and OpenCode headers: canonical
X-Infinitum-*headers, plus the OpenCode/Headroom/LiteLLM integration aliases. - Embeddings are optional and configured there.
- Learning controls: a separate extraction model, timeouts, deferral while the upstream is busy, and incremental topic summaries.
Infinitum exposes an OpenAI-compatible surface (POST /v1/chat/completions, GET /v1/models) plus runtime endpoints for inspecting memories, events, topics, and request context. Internal X-Infinitum-* control headers are stripped before forwarding upstream. Full reference: docs/API.md.
The current global-memory prototype intentionally uses one SQLite database so the complete system can run with no external infrastructure, and existing databases are migrated in place. SQLite WAL mode is enabled, FTS5 is used when available, and embedding vectors are stored as float32 blobs and searched in-process. Table list and ER detail: docs/ARCHITECTURE.md.
Infinitum is independent of both. The configured upstream can be either one.
OpenCode -> Infinitum -> Headroom -> LiteLLM -> model
OpenCode -> Infinitum -> LiteLLM -> model
application -> Infinitum -> vLLM
The intended responsibility split is:
- Infinitum: what knowledge should the model know now?
- Headroom: how can the selected context be represented efficiently?
- LiteLLM: authentication, routing, provider policy, budgets, fallbacks.
Context forwarding is configured in docs/CONFIGURATION.md.
V0.2.0 is deliberately not yet a multi-user production memory service.
- all memory is global;
- request user/project/CWD context is provenance + ranking affinity only; there is still no user/project/org isolation;
- no ACL/RBAC system;
- SQLite and in-process vector scan are single-node choices;
- Chat Completions is implemented before the Responses API;
- streaming learning occurs only after a complete
[DONE]stream; - contradiction/supersession relies on conservative LLM candidate hints plus deterministic validation;
- topic summaries use the configured learning model and are incrementally maintained;
- raw request-message events may contain sensitive content and require appropriate storage controls;
- no document ingestion or graph retrieval yet.
See docs/ROADMAP.md for the intended evolution and enough design detail to implement it without changing the core event/memory model. The research papers and systems techniques that shaped these design choices are recorded in docs/REFERENCES.md.
- Events are truth; memory is derived.
- Retrieval scope must eventually be enforced before semantic search.
- The LLM may propose memory changes but deterministic code owns mutation.
- Current state can supersede old state without destroying history.
- Huge memory availability does not justify filling the model context window.
- Provenance is required for every derived memory.
- Learning must normally be off the critical response path.
- Failure of optional memory features should not silently broaden access.
- Model/provider infrastructure remains replaceable.
- The system should always be able to explain why a memory exists and why it was selected.