Generated banner for WARM
Generated logo for WARM

WARM

Memory serverOfficial Writerslogic project

Created by Writerslogic

Published in writerslogic/warm. Publisher page

27 GitHub stars

What it is

WritersLogic Adaptive Retention & Memory (WARM): local document search (BM25 + embedding cosine) and vector-symbolic associative memory.
— the description by Writerslogic

AutoGineer overview, from the brain’s own files

WARM (WritersLogic Adaptive Retention & Memory) is a local-first memory engine designed to provide AI agents and applications with persistent storage and retrieval. It combines traditional document search—using stemmed BM25 and vector-based cosine similarity—with a vector-symbolic layer for associative memory and multi-hop queries. The system is built in Rust and can be deployed as an embedded library, an HTTP server, or an MCP server, allowing it to integrate with various agent hosts and development environments. It emphasizes local control, offering optional encryption and provenance features while avoiding external service calls or telemetry.

How it works

Memory model: Hybrid

AutoGineer overview, from the brain’s own files

The system stores text, vectors, and facts in a local directory. It uses a hybrid approach where document search combines chunking and BM25 with vector embeddings. The vector-symbolic layer utilizes sparse hypervectors to support associative memory, triples, and multi-hop queries. It provides an API for memory management including remember, recall, and forget operations, with support for source provenance and atomic storage updates.

Folders
FolderRoleFiles
.githubagent-config37
assetsattachments10
docsdocs1850
examplesexamples47
scriptsscripts77
srcsource-code327
teststests362
benchesfolder7
benchmarksfolder2898
bindingsfolder273
deployfolder18
figuresfolder6
fuzzfolder54
integrationsfolder225
interopfolder3
notebooksfolder3
npmfolder1
proptest-regressionsfolder1
schemasfolder9

The agent reads

Requirements

Runtimes: Node.js, Python >=3.9, Rust

Databases: SQLite

API keys (by name): WARM_API_KEY

Works with: Gemini CLI

Set it up

  1. This brain runs code automatically: 1 hook (claude-code), 1 script. Review benchmarks/results/sbg_positions_development/setup.sh, integrations/editors/claude-code/plugin/hooks/hooks.json before opening the folder in Claude Code. No step below turns a hook on for you.

    • benchmarks/results/sbg_positions_development/setup.sh: sh benchmarks/results/sbg_positions_development/setup.sh
    • integrations/editors/claude-code/plugin/hooks/hooks.json: node
  2. Not scanned by AutoGineerThe author’s README also describes installing it from a clone of its repository. Read its install section: AutoGineer did not scan that path. https://github.com/writerslogic/warm/blob/e176d5572dd86bf2233bcb120e3a485dc109ea3a/README.md#get-started
  3. Not scanned by AutoGineerThe author’s README also describes installing it as the npm package writerslogic-warm. Read its install section: AutoGineer did not scan that path. https://github.com/writerslogic/warm/blob/e176d5572dd86bf2233bcb120e3a485dc109ea3a/README.md#get-started
  4. Created by Writerslogic. Published in writerslogic/warm. Licence: Apache License 2.0.

Usage

AutoGineer overview, from the brain’s own files

  • Integrating memory into an AI agent via the MCP server.
  • Building local-first applications that require embedded search and storage.
  • Providing a multi-tenant memory service via the HTTP server.
  • Using the Rust library for in-process memory management.
“Remember that the project deadline is Friday.”
The agent stores the fact with associated metadata, making it available for future retrieval.
“Recall information about the project deadline.”
The agent retrieves the stored fact using hybrid search and provides the information with source provenance.

Documentation by Writerslogic

<img src="https://raw.githubusercontent.com/writerslogic/warm/main/assets/crystal-halo/crystal-detailed-static.png" alt="WARM logo" width="96" align="left">

WARM

Local memory for AI agents and applications.

WARM (WritersLogic Adaptive Retention & Memory) gives agents persistent memory, full-text and vector search, and source provenance in one embeddable Rust engine. Use it inside your application, through HTTP, or from an MCP client, with storage on infrastructure you control.

Image: CI Image: MSRV 1.89 Image: OpenSSF Scorecard Image: OpenSSF Best Practices Image: License: Apache-2.0 Image: WARM MCP server: quality and maintenance score on Glama

Website · Docs · Get started · Standards and provenance · Evaluation · Capabilities · Limits · Contribute

<br clear="left">

What it is

WARM is a Rust library that stores text, vectors and facts in a directory on your machine and finds them again. It ships with optional Node.js, C, WASM and UniFFI bindings, an HTTP server with portable JavaScript and Python clients, and an MCP server. The embedded engine makes no service calls or telemetry requests. HTTP clients connect to the server you configure; model stages are opt-in.

CapabilityWhat you get
Document searchChunking, stemmed BM25 over blocked postings, Boolean and phrase queries, hybrid fusion with embeddings you supply, metadata filters, byte offsets
Vector indexGraph index with a memory-mapped vertex file, an updatable delta for inserts and deletes, optional query-aware routing trained on sample queries
Memory APIremember, recall, forget and explain with time validity and supersession; conversation memory with source provenance
StorageAtomic replace and delete, crash-safe journal and checkpoints, verified compaction, one writer per store
SecurityOptional AES-256-GCM storage encryption with documented coverage and limits, signed audit log, COSE/SCITT provenance
Vector-symbolic layerSparse hypervectors with bind, bundle and permute, triples with multi-hop queries, Hopfield cleanup

Who it's for

You are buildingWhat you get
An agent or assistant that needs memoryA memory API with forgetting that removes facts from the store, and an MCP server for editors and agent hosts
A local-first or offline appEmbedded search and storage in one directory, with no server process and optional encryption
A service that needs vector or text searchwarm-server over HTTP with tenants and scoped keys, or the library in-process

Get started

For an editor or agent host, start with the MCP integration. For an application, choose the Rust library or HTTP server below. WARM runs locally or on your own infrastructure; this repository does not provide a managed cloud service.

To try the offline memory demo from source:

git clone https://github.com/writerslogic/warm
cd warm
cargo run --release --example demo-offline-agent

<details open> <summary><strong>Rust</strong></summary>

From your own Rust project, add the current Apache-2.0 source. The crate is warm-rust; the library is imported as writerslogic_warm. The MSRV is Rust 1.89; local-models and --all-features need Rust 1.94 or newer.

cargo add warm-rust

The historical v0.6.1 source remains AGPL-3.0-or-later; it is available from that Git tag, not as a warm-rust 0.6.1 release on crates.io.

use writerslogic_warm::{DocumentInput, WarmCore, SearchOptions};

let dir = tempfile::tempdir()?;
let warm = WarmCore::new(4096, Some(dir.path().display().to_string()), None)?;
warm.memorize_document(DocumentInput {
    id: "backups".into(),
    text: "Restore deleted documents from verified backups.".into(),
    source_uri: Some("operations.md".into()),
    version: Some("1".into()),
    metadata: Some(serde_json::json!({ "project": "alpha" })),
    chunk_words: None, overlap_words: None, store_text: None, embeddings: None, title_weight: None,
})?;
let hits = warm.search_documents("restore backups", &SearchOptions::default())?;
println!("{} {:?}", hits[0].document_id, hits[0].source_uri);

Rust API change in 0.6.6 (0.6.5 returns these values directly): WarmCore::query, query_batch, and analyze_components return Result. Handle the result with ? or an explicit error branch; deferred-encoding failures no longer become successful partial query results. get_document, list_documents, get_document_info, and list_document_info also return Result; retained-text and checkpoint decoding failures propagate instead of panicking, and reads no longer hydrate the entire checkpoint or hold store locks during text I/O.

</details>

<details> <summary><strong>JavaScript and TypeScript: portable client</strong></summary>

warm-sdk 0.2.1 is the portable HTTP client for a separately running warm-server. It has no native binaries or runtime dependencies and uses standard Fetch and Web Streams APIs (Node 18 or newer). Installing or importing it does not load the embedded engine or download models. For a published release, install the client in your application:

npm install [email protected]

Check the available npm versions. To build the client from this checkout:

npm ci --prefix bindings/js/warm-sdk
npm run build --prefix bindings/js/warm-sdk

In your application, install that directory with npm install /absolute/path/to/warm/bindings/js/warm-sdk.

Start the server in a separate terminal:

cargo build --release --features server --bin warm-server
WARM_API_KEY=change-me target/release/warm-server --input-dim 384
import { WarmClient } from 'warm-sdk'

const warm = new WarmClient({
  url: process.env.WARM_URL ?? 'http://127.0.0.1:8080',
  apiKey: process.env.WARM_API_KEY, tenantId: 'demo',
})
await warm.memory.remember('Restore deleted documents from verified backups.', {
  id: 'backups', metadata: { project: 'alpha' },
})
const hits = await warm.memory.recall('restore backups', { filter: { project: 'alpha' } })
console.log(hits[0]?.text)

Set WARM_API_KEY to the server's key before running the client. This memory example uses lexical retrieval and needs no embedding model. A runnable source example also checks retrieval and deletion in an isolated namespace:

WARM_URL=http://127.0.0.1:8080 WARM_API_KEY=change-me \
  node examples/quickstart/js-client/quickstart.mjs

See the client API and release availability. Browser deployments must permit the application's origin at the HTTP service boundary.

</details>

<details> <summary><strong>Node.js: optional embedded engine</strong></summary>

writerslogic-warm embeds the Rust engine in Node. For a published engine release, install it explicitly with npm's optional dependencies enabled:

npm install [email protected]

Check the available npm versions. To build from the cloned repository, with Rust installed:

npm ci --omit=optional
npm run build

The portable client and embedded engine are separate packages with different APIs. listDocuments supports scoped pruning. See the integration guide for source provenance, namespace isolation, dry runs and deletion limits.

const { WarmMemorySystem } = require('writerslogic-warm')
const { DocumentMemory } = require('writerslogic-warm/semantic')

async function main() {
  const dir = require('node:fs').mkdtempSync(require('node:path').join(require('node:os').tmpdir(), 'warm-readme-'))
  const memory = new DocumentMemory(new WarmMemorySystem(4096, dir))
  await memory.memorize({
    id: 'backups', text: 'Restore deleted documents from verified backups.',
    sourceUri: 'operations.md', version: '1', metadata: { project: 'alpha' },
  })
  const [hit] = await memory.search('restore backups', { filter: { project: 'alpha' } })
  console.log(hit.documentId, hit.sourceUri, hit.text)
  await memory.flush()
}
main().catch(error => { console.error(error); process.exit(1) })

</details>

<details> <summary><strong>Server and Python</strong></summary>

The Python package warm-sdk is an HTTP client for warm-server, which stores vectors you supply. (warm-python, imported as writerslogic_warm, provides native vector indexes and phase vectors, but no document search.)

cargo build --release --features server --bin warm-server
WARM_API_KEY=change-me target/release/warm-server --input-dim 384 &
uv pip install warm-sdk
from writerslogic_warm_sdk import WarmClient

client = WarmClient(url="http://127.0.0.1:8080", api_key="change-me", tenant_id="demo")
client.add_documents([{"id": "a", "vector": [0.1] * 384, "text": "hello", "metadata": {}}])
print(client.query([0.1] * 384, top_k=1)["matches"][0]["id"])

Endpoints, flags, tenants and limits are in the server docs. A runnable Python quickstart with a toy embedder is in examples/quickstart/python.

</details>

<details> <summary><strong>MCP</strong></summary>

warm-mcp serves a store to any Model Context Protocol client over stdio or HTTP:

npx -y writerslogic-warm-mcp                                # stdio; downloads a verified binary on first run
npx -y writerslogic-warm-mcp --transport http               # http://127.0.0.1:8765/mcp

The MCP package is a separate launcher. To build the server from source:

cargo build --release --features mcp,security --bin warm-mcp   # add local-models for semantic recall
./target/release/warm-mcp                                       # stdio
./target/release/warm-mcp --transport http                      # http://127.0.0.1:8765/mcp

Client setup, data directory and the one-writer rule: MCP integration.

For normalized content from multiple applications, see the organizational knowledge adapter.

</details>

The five-minute quickstart walks through ingest, search, a SQLite FTS5 comparison and encryption in Rust, Python and Node, and CI runs that code on every pull request. For a whole agent memory with no embeddings and no network, run cargo run --example demo-offline-agent.

<a id="headline-results"></a> <a id="where-its-behind"></a>

Evaluation

Recorded results and limitations cover memory quality, text retrieval and vector search. Each result retains its workload, hardware, model, source revision and known losses. The full comparison and reproduction protocol provide the evidence for evaluating WARM on your own workload.

What ships today

CapabilityStatusDocumented limit
Hybrid document search (BM25 + exact cosine, filters, byte offsets)ShipsExact scan of eligible chunks; benchmark your corpus before assuming capacity (guide)
Boolean lexical search (+required, -excluded, quoted phrases)ShipsFlat queries only, no nesting; an empty or only-excluded query matches nothing (design)
Atomic replace/delete, crash-safe journal, verified compaction, process lockShipsOne writer per store; compaction is not secure erasure
Vector index (qgraph vertex graph, memory-mapped)ShipsBuild time and serving RSS behind hnswlib on SIFT-1M (gaps)
Updatable vector index (durable delta for inserts and deletes)ShipsSingle writer; format in VECTOR-UPDATES.md
Query-aware graph routing (trained on sample queries)Ships (opt-in)Needs representative training queries; tested on VIBE llama and yi (report)
Memory API: remember, recall, forget, explainShipsForgetting removes a fact from the store; it does not cover what a model inferred (docs)
Conversation memory (conversation-memory)ShipsBounded context with exact source provenance; native recall not yet benchmarked on LongMemEval end to end
Meaning memory: triples, multi-hop, Hopfield cleanupShips (runtime opt-in)Tested on synthetic and 2,120-fact sets (research_bench_16384_256.json)
Union-bundle membership (experimental)ResearchAt D = 16,384: positive gap to N = 500 (4% false positives), tested to 2,500 (benchmark_scaling_results.json)
Sparse vector index (memorize_vector)ExperimentalNot competitive with HNSW (results)
Encrypted stores (security)ShipsAES-256-GCM, Argon2id; covers the log, index caches and the text segment. Metadata, file names and sizes are not encrypted
Signed audit log, credential-gated access, COSE/SCITT/C2PA (provenance)ShipsHost must authenticate DIDs; PoSME, RATS, did:web not implemented
On-device models (local-models: Qwen3 embed/rerank/LLM)ShipsLocal weights only; never downloads
warm-server HTTP API, multi-tenant (server)ShipsSingle node; TLS, per-tenant scoped keys (guide)
MCP server (mcp: warm-mcp, stdio and HTTP)ShipsEncryption needs security; semantic recall needs local-models
JavaScript/TypeScript client (warm-sdk 0.2.1)Source and release buildsPortable HTTP client; requires a separately running server
Embedded Node.js (writerslogic-warm 0.6.8)Source and release buildsEight optional native platform packages; separate from the client
C, Go, .NET, Java, Kotlin, Swift and WebAssembly bindingsSource availableRequire local builds; the language packages and native bundles are not yet published (runtime guides)
PythonHTTP client (warm-sdk); native vector indexes and phase vectors (warm-python)No in-process document search
Query-private search (private-search)ExperimentalFor searching an index someone else holds; hides the query vector from the scorer only (notes)

Optional engine capabilities use Cargo features or runtime configuration; Cargo features lists the build choices.

The compatibility policy defines API stability, deprecation windows and storage-format migration rules.

Architecture

flowchart TB
  subgraph bindings["Bindings and servers"]
    rust["Rust crate"]
    node["Node: writerslogic-warm (napi)"]
    ffi["C, WASM, UniFFI"]
    clients["JavaScript and Python: warm-sdk"]
    server["warm-server (HTTP)"]
    mcp["warm-mcp (stdio, HTTP)"]
  end
  subgraph engine["Engine API"]
    core["WarmCore: documents, vectors, facts, mutation gate"]
    prov["Security and provenance: signed audit log, COSE/SCITT"]
  end
  subgraph indexes["Indexes and memory"]
    text["Text index: blocked postings, BM25, Boolean and phrase"]
    vector["Vector index (qgraph): vertex file, updatable delta, query-aware projection"]
    memory["Memory API: remember, recall, forget, conversation memory"]
    vsa["Vector-symbolic layer: bind, bundle, Hopfield cleanup"]
  end
  subgraph storage["Storage"]
    journal["Journal and checkpoints"]
    segments["Append-only segments (CRC32, LZ4)"]
    mapped["Memory-mapped files"]
    crypto["AES-256-GCM encryption (optional)"]
  end
  clients --> server
  rust --> core
  node --> core
  ffi --> core
  server --> core
  mcp --> core
  core --> text
  core --> vector
  core --> memory
  core --> vsa
  core --> prov
  memory --> text
  text --> journal
  vector --> journal
  memory --> journal
  journal --> segments
  segments --> mapped
  segments --> crypto

Module map: docs/ARCHITECTURE.md.

Limits

  • Not anonymous. Hypervector sketches and term counts are not encryption; anyone with a store can encode candidate text and compare. storeText: false keeps term counts, IDs and embeddings, so it is not a privacy control.

  • Document search is conventional. It does not use the hypervectors; the holographic parts are the algebra and the meaning memory.

  • One writer per store. A second writing process fails at open with a lock error.

  • Capacity numbers are points, not guarantees, and are stated only over the tested range (capacity).

  • Benchmark results are single machines. Conditions are in each results file; the single-core ann-benchmarks run and the VIBE high-recall points are losses (see Where it's behind).

  • Masking is obfuscation. Client-side vector masking preserves every pairwise similarity.

  • A store is a directory and WarmCore::new(.., None, ..) uses the current directory.

Full statement: docs/PRIVACY.md. Report vulnerabilities privately via SECURITY.md.

Contributing

CONTRIBUTING.md: setup, the checks CI runs, DCO sign-off (git commit -s, no CLA). Start with a good first issue or Discussions. See the Code of Conduct. Benchmark changes regenerate the results file they touch; the README cites files, not remembered figures.

License

Apache-2.0. See LICENSE. Use, modify and embed it in open-source or proprietary software, including network services, with no copyleft obligation. The historical v0.6.1 source remains available under its AGPL-3.0-or-later license.

docs/SERVER.md


search: boost: 1.6

warm-server

warm-server is the HTTP service for WARM. It implements the endpoints used by the portable warm-sdk clients for JavaScript/TypeScript (bindings/js/warm-sdk) and Python (bindings/python/warm-sdk), wrapping one WarmCore store per tenant, and adds what a service needs: scoped per-tenant authentication, limits and backpressure, observability, TLS and unix sockets, snapshots, compaction and graceful shutdown. The clients are separate from the optional embedded Node package, writerslogic-warm.

DocumentContents
this pageRun it, the API at a glance, behavior and limits
server/openapi.jsonThe OpenAPI 3.1 contract, generated from the server (also served at /openapi.json)
server/configuration.mdEvery flag and WARM_* variable, defaults, meaning
server/auth.mdKeys, scopes, tenant binding, rotation
server/errors.mdThe RFC 9457 error model and every error code
server/versioning.mdWhat may change within /api/v1 and how breaking changes ship
server/operations.mdDeploying, TLS, metrics, logs, backup and restore, shutdown, measured behavior
../deploy/Dockerfile, Compose file, Helm chart, systemd unit, benchmark harness

Build and run

cargo build --release --features server --bin warm-server

# One key valid for every tenant (the original setup):
WARM_API_KEY="$(openssl rand -hex 32)" ./target/release/warm-server --input-dim 384

# Per-tenant keys with scopes (docs/server/auth.md):
./target/release/warm-server keygen --id acme-rw --tenants acme --scopes read,write --keys-file keys.json
./target/release/warm-server --keys-file keys.json --input-dim 384

# Local development without credentials (loopback or a unix socket only):
./target/release/warm-server --no-auth --input-dim 384

The server refuses to start without credentials unless --no-auth is given, and refuses --no-auth on a non-loopback address. It prints warm-server listening on <addr> on stdout once bound; everything else goes to stderr as JSON log lines.

from writerslogic_warm_sdk import WarmClient
client = WarmClient(url="http://127.0.0.1:8080", api_key="<token>", tenant_id="acme")

API keys

Every request carries an API key. WARM_API_KEY sets one key valid for every tenant; --keys-file loads per-tenant API keys with read, write or admin scopes, created with warm-server keygen. Key format, scopes and rotation are in server/auth.md.

API at a glance

The contract is server/openapi.json. Every operation lists the scope it needs (read, write, admin) as its security requirement. Errors are application/problem+json (server/errors.md).

RequestScopeSuccess
POST /api/v1/documents one documentwrite200 {"added": 1}
POST /api/v1/documents/batch 1..=max-batch documents, one atomic commitwrite200 {"added": n}
POST /api/v1/documents/stream NDJSON, committed every --stream-batch lineswrite200 {"added","rejected","errors":[{"line","message"}],"errors_truncated"}
DELETE /api/v1/documents/{id}[?purge=true] (percent-encoded; purge also rewrites the journal and scrubs the deleted data)write200 {"deleted": true}
GET /api/v1/documents?limit&after ids and metadata, id cursorread200 {"documents":[{"id","metadata"}],"next_cursor","count"}
GET /api/v1/documents/{id}/chunks/{index} retained chunk, zero-based indexread200 {"revision","document","index","id","startByte","endByte","text"}; 404 if absent
GET /api/v1/statsread200 {"tenant","documents","vectors","documentIndexTrained","usedBytes","capacityBytes"}
POST /api/v1/queryread200 {"matches":[{"id","text","metadata","score"}]} best first
POST /api/v1/searchread200 {"hits":[{"id","score"}]} best first (BM25; model stages when configured)
POST /api/v1/index/maintainwrite200 {"documentIndexTrained": bool}
POST /api/v1/memory/{remember,recall,explain,forget,get,history,applyForgetting,consolidate}write / readthe memory API, docs/memory-api
GET /api/v1/compaction, POST /api/v1/compactionread / adminstatus, run now
POST /api/v1/snapshots, GET /api/v1/snapshots, DELETE /api/v1/snapshots/{id}adminserver-side snapshots
GET /api/v1/snapshots/{id}/archive, POST /api/v1/snapshots/{id}/restore, PUT /api/v1/restoreadmintar download, restore from a snapshot or an uploaded archive
POST /api/v1/admin/keys/reload, POST /api/v1/admin/tls/reload, GET /api/v1/admin/tenantsadmin, all tenantsreload credentials, list tenants
GET /healthz, GET /readyz, GET /metrics, GET /openapi.jsonnone (/metrics: admin unless --metrics-public)liveness, readiness, Prometheus, spec
Documents and queries

A document is {"id": str, "vector": [float], "text"?: str, "metadata"?: object}. vector has exactly --input-dim finite f64 values, not all zero; query vectors follow the same rule. Normalization handles very large and very small finite magnitudes without narrowing to f32. id is 1 to 256 bytes with no control characters and not starting with hms:. text must fit one engine chunk (at most 4096 words and 64 KiB), because one vector describes one stored chunk; split longer documents and send one vector per part. metadata is at most 64 KiB. Adding an existing id replaces it.

GET /api/v1/documents/{id}/chunks/{index} reads one retained chunk with document identity, metadata, version, source URI and chunkCount. It requires read scope and the selected tenant. text: null means text was not retained; a missing document or out-of-range index returns 404. HTTP-ingested documents have one chunk; native stores restored into the server may have more. A chunk contains at most 8 MiB of UTF-8 text (the native whole-document limit), plus bounded document metadata. JSON escaping and serialization copies can make the response and peak allocation larger.

Chunks preserve their stored passages, including internal whitespace. They do not recover omitted leading/trailing whitespace, gaps between nonoverlapping chunks, unretained text, or original pre-normalization embedding values. Recorded byte spans describe the ingested chunk; vector-only HTTP records use an unretained placeholder, so their spans are not evidence of original source text. Overlapping chunks must not be concatenated blindly.

revision is an opaque string captured with the chunk. When assembling multiple reads, require the same revision throughout and retry if it changes. It is not the user-supplied document version and is not a durable cross-restart sequence number. Concurrent replacement/deletion or text-segment rebuilding triggers bounded internal retries; repeated changes return 503 concurrent_mutation with Retry-After. Storage failures remain errors, not missing text. Chunk reads support ETag and If-None-Match for the complete returned representation.

POST /api/v1/query takes {"query_vector": [...], "top_k"?: 1..=max-top-k (default 3), "filter"?: object}. filter keeps documents whose metadata contains every filter key with an equal JSON value; it is applied before ranking, so it never reduces the number of matches below top_k when enough documents match. score is the exact cosine similarity between the query and the stored embedding.

POST /api/v1/search takes {"query": "...", "top_k"?: 1..=max-top-k (default 10), "include"?: ["text", "metadata"]} and ranks the tenant's documents, memory facts included, by BM25 (through the on-device model stages instead when the server has them), one hit per document. Hits carry only id and score unless include names stored text or metadata; it applies no memory semantics (validity time, supersession), for which use memory/recall.

Batch totals are limited to 8 MiB of ids and text, 8 MiB of metadata and 1,048,576 embedding values; the native prepared transaction is limited to 32 MiB. All documents are validated before one atomic commit; repeated ids keep their last occurrence while added stays the incoming count.

index routes an unfiltered query: auto (the default; --query-index sets it) uses the graph index when one is built and the exact scan otherwise, exact always scans, graph answers 409 index_unavailable until maintenance has built one. ef is the beam width of a graph query (--query-ef, default 128). The graph index is persisted with the tenant store: every document ingested with a vector is appended to the tenant's vector log before the engine commits it, index maintenance builds the graph over those rows and writes it next to the store, and a restart opens the written graph instead of rebuilding it. Maintenance after a restart is a no-op until something changed; scheduled compaction rebuilds after 64 mutations. A graph file that does not read back is ignored and its rows still answer, exactly, until the next maintenance; after a stop that was not clean the index checks each of its documents against the engine before serving. The graph covers documents ingested through the documents API; while a tenant also holds embeddings written through the memory API, its unfiltered queries go to the engine's own snapshot instead.

Index maintenance also builds the tenant's optional document index over stored embeddings and reports whether one is trained; a store too small to train returns false. Ingest does not trigger training. If the store changes during training, maintenance returns a fixed internal error and can be retried.

Bulk ingest

POST /api/v1/documents/stream with Content-Type: application/x-ndjson reads one document object per line (blank lines ignored, the last line may lack a newline) and groups every --stream-batch nonblank lines into one batch, independent of transport frames. Valid documents in each batch commit in one transaction. Rejected lines are reported with their 1-based line number (the first 100) and do not stop the stream. The stream is not atomic across batches: after a failure mid-stream, the committed batches stay. Size limits are --max-stream-bytes and --max-stream-line-bytes, independent of --max-body-bytes; a stalled upload times out after --body-timeout-seconds of silence.

Retries and conditional requests
  • Idempotency-Key on POST /api/v1/documents, /documents/batch and the memory operations replays the recorded status and response bytes for the same key, tenant, method, path and body (Idempotent-Replayed: true), including after restart. A different request with the same key gets 422 idempotency_key_reused; a running key gets 409 idempotency_in_flight. The server durably reserves the key before executing work and persists the response, or a bounded reconciliation marker, before acknowledging it. An interrupted request whose outcome was not recorded gets 409 idempotency_reconciliation_required; it is never automatically reexecuted. Known transient failures (5xx, 429, 408) release the key unless the mutation committed, its outcome is uncertain, or erasure invalidated the request. Repeated key headers are rejected before execution. The receipt and business mutation are separate commits: this prevents blind retries across the crash gap but does not make arbitrary operations exactly once.

  • GET /api/v1/stats, GET /api/v1/documents, and retained-chunk reads return a weak ETag derived from the serialized response and process epoch, and answer matching If-None-Match conditions with 304. Validators use weak comparison across repeated header lines and quoted lists; a lone * matches an existing representation. Malformed conditions are ignored rather than used to produce 304. See RFC 9110 §13.1.2. Validators are response-specific, not tenant revision tokens: unchanged pages may retain their tag after unrelated writes. Conditional reads render and hash the response before deciding whether to omit its body; this prevents reused validators during concurrent writes but adds work to 304 responses. Chunk validators include the captured revision; they are not whole-document snapshot handles.

Tenants

X-Tenant-ID selects the store. Repeated fields, even identical ones, return 400 invalid_tenant before store access. Ids are 1 to 64 characters of [A-Za-z0-9_-], anything else is 400; they are case-sensitive. A key bound to one tenant needs no header (see server/auth.md); otherwise the default is default. Each tenant maps to <data-dir>/tenants/<hex of id>/, so ids never reach the filesystem as paths.

Stores open on first use. Simultaneous first requests share one initialization; pending initializations count toward --max-tenants; filesystem work runs outside the registry lock; a failed initialization releases its reservation. A store idle for --tenant-idle-seconds is flushed and closed, so --max-tenants bounds open stores, not tenants ever served.

Limits and backpressure

  • --max-concurrent-work bounds admitted requests reading bodies or running engine work. When full, a request is answered 503 server_busy with Retry-After immediately, or, with --max-queue above zero, waits in a bounded queue for up to --queue-timeout-ms first. warm_queue_depth and warm_work_slots_in_use expose the saturation.

  • --rate-limit-rps/--rate-limit-burst is a per-tenant token bucket (429 rate_limited, Retry-After). --max-documents-per-tenant is a per-tenant document quota (429 quota_exceeded). Both are off by default.

  • --max-connections caps simultaneous connections; --header-read-timeout-seconds drops clients that dribble headers; --body-timeout-seconds bounds body reads. A disconnected client releases body admission immediately, but a running blocking operation keeps its work slot until it finishes.

  • Parsing, validation, store initialization, ingest, search and maintenance run on blocking workers; the async runtime only moves bytes.

Document ingestion and memory remember serialize quota checks through commit, so concurrent requests share the same limit. Supply explicit memory ids for retries at a full quota: requests without ids are conservatively counted as new facts. Summaries created by an opt-in consolidation policy currently bypass quota admission.

Shutdown

SIGINT or SIGTERM makes /readyz fail at once. With --shutdown-delay-seconds the server keeps serving for that long so a load balancer can drain it; then the listener closes, open connections get --shutdown-grace-seconds to finish, admitted work (including operations whose clients disconnected) runs to completion, every open store is flushed and the metadata logs are synced, and the process exits 0. An interrupted flush exits non-zero.

On-device model stages (local-models)

Built with --features server,local-models, the server can run the model stages itself instead of requiring client vectors. Every stage is off unless its flag is given; with none given the server behaves exactly as described above.

FlagEnvironmentStage
--embed-model DIR --embed-revision SHAWARM_EMBED_MODEL, WARM_EMBED_REVISIONQwen3-Embedding. Documents may omit vector (the text is embedded); --input-dim must equal the model's hidden size (1024 for 0.6B).
--rerank-model DIR --rerank-revision SHAWARM_RERANK_MODEL, WARM_RERANK_REVISIONQwen3-Reranker over the fused top 20 of a text query.
--llm-model DIR --llm-revision SHAWARM_LLM_MODEL, WARM_LLM_REVISIONQwen3 instruct LLM (greedy decoding).
--extract-factsWARM_EXTRACT_FACTSAt ingest, the LLM lists the personal facts in each document; they are indexed with it (lexical and embedding key), the stored text is unchanged.
--rewrite-queriesWARM_REWRITE_QUERIESThe LLM rewrites each text query; the original and the rewrite are searched and merged by RRF.

A query may then be {"query": str, "today"?: str, "top_k"?, "filter"?, "query_vector"?}: hybrid BM25 + cosine per query form, weighted RRF across forms, then the re-rank, with the tuned LongMemEval parameters. score is then the fused rank score, not a cosine. Vector-only queries keep the cosine path.

What runs where. Every stage runs in the server process: candle on the CPU, on the GPU through Metal on macOS, or through CUDA in builds made with RUSTFLAGS="--cfg warm_cuda". Weights are read from the given local directories, which must record the given revision (a REVISION file or the metadata hf download --revision <sha> --local-dir <dir> writes); the server never downloads a model and makes no network calls, so no document or query text leaves the machine. Fact extraction adds one LLM generation per document and query rewriting one per text query; see docs/PUBLIC-BENCHMARKS.md for measured on-device costs.

Storage

Each tenant is one engine store. Documents (embedding, text and metadata) go through the engine's document API. A batch writes its final distinct ids in one transaction; a validation failure leaves existing documents unchanged. Embeddings are kept at full precision, so the sparse-code path measured in public benchmarks does not apply to this service.

The store records the embedding model, revision and input dimension (--embedding-model, --embedding-revision, --input-dim). Reopening a store with different values is refused, so vectors from different models are never mixed.

<tenant>/vindex/ holds the persisted graph index: rows.log (one checksummed frame per inserted, replaced or deleted vector, synced per request at the engine's durability class), the snapshot maintenance last published (CURRENT, graph-<epoch>.vertex, rows-<epoch>.bin, ids-<epoch>.bin, each written through a temporary file and a rename) and dirty, present between the first mutation after open and a clean close. Snapshots copy the directory with the store.

Snapshots, compaction and restore are described in server/operations.md.

New vector snapshots record a checksum of rows-<epoch>.bin in CURRENT; a mismatch disables the snapshot so queries fall back to the engine. Older snapshots remain readable and gain the checksum on the next maintenance run.

Admission limits preserve both running and unexpired completed idempotency keys. At --idempotency-capacity, new keys receive 503 before execution; existing keys can still replay. Size this capacity for the number of distinct keys admitted during the TTL plus in-flight work: the defaults retain 10,000 keys for 600 seconds, so a sustained stream of new keys above roughly 16 per second requires a larger capacity or shorter TTL. --idempotency-max-bytes independently bounds retained serialized response-buffer allocations (64 MiB by default). A response that does not fit is still returned to the original caller, but subsequent use of its key requires reconciliation. This limit does not cover response construction, active HTTP sends, key/record overhead, or total process RSS. The key-count limit bounds record overhead separately. Response clones share one serialized buffer instead of retaining and repeatedly serializing a JSON tree.

Retry receipts live in DATA_DIR/http-retries, outside tenant snapshots, under one exclusive process lease. Checksummed private files contain tenant/key digests, request fingerprints, status, and response bytes; request bodies and credentials are not copied. Responses can contain source text. Like the server's other unencrypted files, receipts require an encrypted volume when encryption at rest is needed. Copying or deleting this directory independently of the server's data can discard retry protection.

Expiry uses a monotonic clock. Restart begins a fresh retention window at least as long as the configured TTL and the longest recorded TTL; this deliberately extends retention and prevents a clock change or a shorter startup setting from releasing protected keys early. Running keys never expire in-process; interrupted reservations become reconciliation markers on restart. Startup fails if receipt integrity, ownership, or configured storage bounds cannot be satisfied.

Before forget, forgetting maintenance, consolidation, document deletion, or tenant restore, a durable tenant barrier blocks new keyed calls and scrubs previously retained response bodies. Protected key fingerprints remain as reconciliation markers. In-flight completions cannot restore those bodies; the current keyed erasure operation can retain its own result. The barrier spans the operation and survives interruption. Startup finishes interrupted receipt scrubbing before admitting requests; cleanup failure leaves protection unavailable. Restore invalidates receipts once replacement begins, even if it subsequently rolls back. Other tenants are unaffected. Receipt scrubbing does not alter independent backups.

The rate limiter holds at most 10,000 tenant buckets; when full, requests for a new tenant receive 429 until idle buckets can be reclaimed. Existing tenant buckets continue operating normally.

What it does not provide

  • Single node: no replication, clustering or HA. A tenant store is owned by one process.

  • No encryption at rest (use an encrypted volume) and no audit or attestation features at this layer.

  • Keys are bearer tokens: use TLS (--tls-cert, or a terminating proxy) when the listener is not loopback or a unix socket.

  • The SDK's zero_trust_key vector masking is obfuscation, not encryption. The server sees masked vectors, learns every pairwise similarity, and stores text and metadata in plaintext.

  • No mutual TLS, no OIDC, no per-key rate limits; limits are per tenant.

512 files · 481 not served here

Advantages

  • Supports local-first operation with no external service calls or telemetry. evidence: README.md
  • Provides a memory API that allows for explicit fact removal via a forget operation. evidence: README.md

Limitations

Security

Risk level: Medium risk

This brain runs

Install scripts (1)

  • benchmarks/results/sbg_positions_development/setup.sh: sh benchmarks/results/sbg_positions_development/setup.sh

MCP servers (3)

  • integrations/editors/claude-code/plugin/.mcp.json (writerslogic-warm): npx -y writerslogic-warm-mcp
  • integrations/editors/claude-desktop/claude_desktop_config.json (writerslogic-warm): npx -y writerslogic-warm-mcp
  • integrations/editors/gemini-cli/gemini-extension.json (writerslogic-warm): npx -y writerslogic-warm-mcp

Hooks (1)

  • integrations/editors/claude-code/plugin/hooks/hooks.json (SessionStart): node
  • Medium risk: runs-mcp-server-unpinned-runner (integrations/editors/claude-code/plugin/.mcp.json)
  • Medium risk: runs-mcp-server-unpinned-runner (integrations/editors/claude-desktop/claude_desktop_config.json)
  • Medium risk: runs-mcp-server-unpinned-runner (integrations/editors/gemini-cli/gemini-extension.json)
  • Low risk: scripts-present (benchmarks/results/sbg_positions_development/setup.sh)

Attribution and licence

Created by
Writerslogic
Declared authors
  • WritersLogic, Inc. (declared in package.json)
  • WritersLogic, Inc. (declared in pyproject.toml)
  • WritersLogic Contributors (declared in CITATION.cff)
Publisher
Writerslogic (Official Writerslogic project)
Source
writerslogic/warm · commit e176d55
Licence
Apache License 2.0 (files served here)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Writerslogic.

Install from source

481 of its files are not served here, so there is no ZIP. writerslogic/warm at e176d55