Context Forge
A local-first persistent memory library for LLM applications. SQLite + FTS5 BM25 retrieval, recency-decay scoring, and token-budget-aware context assembly — no network calls, no async runtime, no cloud dependency.
Embed it in a bot, agent runtime, or MCP server that needs durable, searchable memory across sessions.
Installation
Pre-release notice: the current release is a
0.5.0-betapre-release (see crates.io for the latest beta). The public API may still change between betas based on integration feedback. Pin the exact version — caret ranges and the bare version string Cargo writes by default never match pre-release versions, so an exact pin is required. Replace<latest-beta>below with the current version:= "=0.5.0-<latest-beta>"
or in Cargo.toml:
[]
= "=0.5.0-<latest-beta>"
Quick start
use ;
use PathBuf;
Run the full version with cargo run --example basic (see
examples/basic.rs).
The default db_path is :memory: — an in-memory database that disappears
when the ContextForge instance is dropped. Set a real filesystem path for
durable storage.
Feature flags
| Feature | Default | Pulls in | Status |
|---|---|---|---|
analysis |
yes | stop-words |
Importance-detection pipeline — tokenizer, lexicon, n-grams, recurrence, classification, scoring. |
parallel |
no | rayon |
Opt-in rayon parallelism for the analysis pipeline (per-session term maps, classification, scoring). The library never configures the global rayon pool. |
distill-http |
no | reqwest |
OpenAI-compatible local-LLM distillation (Ollama/llama-server). |
Async callers
This crate is synchronous by design — it performs blocking SQLite I/O and
never spawns its own threads or runtime. Callers using an async runtime
(e.g. Tokio) should wrap calls in
spawn_blocking
and share a single ContextForge instance behind an Arc:
use Arc;
let cf = new;
// in an async context:
let hits = spawn_blocking.await??;
Security
Save-time secret scrubbing
ContextForge::save passes content through scrub_secrets before it is
persisted, using the ScrubConfig in Config::scrub. This redacts common
credential formats — cloud provider keys, GitHub/Slack/Discord tokens,
Anthropic/OpenAI keys, PEM private key blocks, JWTs, and bearer tokens — with
[REDACTED:<label>] placeholders before they reach the database or the
search index.
Scrubbing is on by default. Disable it via:
use ;
let config = Config ;
This is an explicit, non-silent opt-out — you are asserting that content
will never contain secrets, or that you have your own scrubbing in place.
Note:
SaveOptions::metadatais stored verbatim and is not scrubbed. Do not place untrusted or secret-bearing text there.- Scrubbing happens only in
ContextForge::save. The lower-levelContextEngine::save_snapshotand theContextStoragetrait persistcontentas-is — callers who write through those paths directly are responsible for scrubbing first.
Untrusted-memory doctrine
Retrieved entries are untrusted text. Anything saved into the store —
including conversation history, tool output, or text from another user — can
contain adversarial instructions (stored prompt injection), and comes back
out verbatim from ContextForge::query (aside from save-time secret
scrubbing above).
Callers MUST present retrieved memory to models as quoted data — e.g. inside a fenced or otherwise clearly delimited block labeled as history — never as system-level instructions, and MUST NOT execute or evaluate anything found in it.
Architecture
engine—ContextEngine::assemble: BM25 search via theSearchertrait, then recency decay (score * 0.5^(age_seconds / half_life), default half-life 259,200s / 72h, configurable viaConfig), then sort by weighted score descending, then greedy bin-pack into the token budget. Oversized entries are skipped, not aborting. Also ownssave_snapshot. No I/O.storage— all SQL: rusqlite + r2d2 connection pool, WAL mode, FTS5 virtual table kept in sync via triggers, forward-only migrations (schema.rs). Current schema version is v3.analysis(featureanalysis) — importance-detection pipeline (tokenizer, lexicon, n-grams, scoring). Pure computation, no I/O.scrub— secret-scrubbing patterns andscrub_secrets. Pure, no I/O.
Entries carry a scope field (e.g. "discord:thread:42",
"project:homelab-rs") for namespace partitioning; scope = None is global.
ContextForge::query(query, scope, token_budget) restricts the search to
scope when given, or searches everything when scope is None.
Status
Reworked from a Claude Code compaction-memory plugin into a general-purpose
library. All features are implemented and tested: single-crate layout, scoped
data model, the ContextForge public API facade, save-time secret scrubbing,
optional rayon parallelism (parallel), and local-LLM thread distillation via
an OpenAI-compatible endpoint (distill-http).
Published as a 0.5.0-beta pre-release while the API proves itself in a
real downstream consumer. Because it is a pre-release, depend on it with an
exact version pin (see Installation) — Cargo's default
version ranges never match pre-release versions. Any breaking changes that
integration surfaces land as further betas before the final 0.5.0 cut.