neolata-mem
Graph-native memory engine for AI agents — hybrid vector+keyword search, biological decay, Zettelkasten linking, trust-gated conflict resolution, explainability, episodes, compression & consolidation. Zero dependencies. npm install and go.
安装 / 下载方式
TotalClaw CLI推荐
totalclaw install clawskills:clawskills~jeremiaheth-neolata-memcURL直接下载,无需登录
curl -fsSL https://skills.taituai.com/api/skills/clawskills%3Aclawskills~jeremiaheth-neolata-mem/file -o jeremiaheth-neolata-mem.md# neolata-mem — Agent Memory Engine
Graph-native memory for AI agents with hybrid search, biological decay, and zero infrastructure.
**npm package:** `@jeremiaheth/neolata-mem`
**Repository:** [github.com/Jeremiaheth/neolata-mem](https://github.com/Jeremiaheth/neolata-mem)
**License:** Elastic-2.0 | **Tests:** 367/367 passing (34 files) | **Node:** ≥18
## When to Use This Skill
Use neolata-mem when you need:
- **Persistent memory across sessions** that survives context compaction
- **Semantic search** over stored facts, decisions, and findings
- **Memory decay** so stale information naturally fades
- **Multi-agent memory** with cross-agent search and graph linking
- **Conflict resolution** — detect and evolve contradictory memories
Do NOT use if:
- You only need OpenClaw's built-in `memorySearch` (keyword + vector on workspace files)
- You want cloud-hosted memory (use Mem0 instead)
- You need a full knowledge graph database (use Graphiti + Neo4j)
## Install
```bash
npm install @jeremiaheth/neolata-mem
```
No Docker. No Python. No Neo4j. No cloud API required.
> **Supply-chain verification:** This package has zero runtime dependencies and no install scripts. Verify before installing:
> ```bash
> # Check for install scripts (should show only "test"):
> npm view @jeremiaheth/neolata-mem scripts
> # Check for runtime deps (should be empty):
> npm view @jeremiaheth/neolata-mem dependencies
> # Audit the tarball contents (15 files, ~40 kB):
> npm pack @jeremiaheth/neolata-mem --dry-run
> ```
> Source is fully auditable at [github.com/Jeremiaheth/neolata-mem](https://github.com/Jeremiaheth/neolata-mem).
## Security & Data Flow
**Default configuration is fully local** — JSON files on disk, no network calls, no embeddings, no external services.
Data only leaves the host if you **explicitly configure** one of these:
| Feature | What leaves | Where it goes | How to avoid |
|---------|------------|---------------|-------------|
| Embeddings (OpenAI/NVIDIA/Azure) | Memory text | Embedding API endpoint | Use `noop` embeddings or Ollama (local) |
| LLM (OpenAI/OpenClaw/Ollama) | Memory text for extraction/compression | LLM API endpoint | Don't configure `llm` option, or use Ollama |
| Supabase storage | All memory data | Your Supabase project | Use `json` or `memory` storage (default) |
| Webhook writethrough | Store/decay event payloads | Your webhook URL | Don't configure `webhookWritethrough` |
**Key security properties:**
- Only 2 env vars are read directly by code: `OPENAI_API_KEY` and `OPENCLAW_GATEWAY_TOKEN`. All others (Supabase, NVIDIA, Azure) are passed via explicit config objects.
- All provider URLs are validated against SSRF (private IPs blocked, cloud metadata blocked).
- Supabase: prefer anon key + RLS over service key. Service key bypasses row-level security.
- JSON storage uses atomic writes (temp file + rename) to prevent corruption.
- All user content sent to LLMs is XML-fenced with injection guards.
- Test safely with `storage: { type: 'memory' }` — nothing touches disk or network.
See `docs/guide.md § Security` for the full security model.
## Quick Start (Zero Config)
```javascript
import { createMemory } from '@jeremiaheth/neolata-mem';
const mem = createMemory();
await mem.store('agent-1', 'User prefers dark mode');
const results = await mem.search('agent-1', 'UI preferences');
```
Works immediately with local JSON storage and keyword search. No API keys needed.
## With Semantic Search
```javascript
const mem = createMemory({
embeddings: {
type: 'openai',
apiKey: process.env.OPENAI_API_KEY,
model: 'text-embedding-3-small',
},
});
// Agent IDs like 'kuro' and 'maki' are just examples — use any string.
await mem.store('kuro', 'Found XSS in login form', { category: 'finding', importance: 0.9 });
const results = await mem.search('kuro', 'security vulnerabilities');
```
Supports **5+ embedding providers**: OpenAI, NVIDIA NIM, Ollama, Azure, Together, or any OpenAI-compatible endpoint.
## Key Features
### Hybrid Search (Vector + Keyword Fallback)
Uses semantic similarity when embeddings are configured; falls back to tokenized keyword matching when they're not:
```javascript
// With embeddings → vector cosine similarity search
// Without embeddings → normalized keyword matching (stop word removal, lowercase, dedup)
const results = await mem.search('agent', 'security vulnerabilities');
```
Keyword search uses an inverted token index for O(1) lookups. When >500 memories exist, vector search pre-filters candidates using token overlap before cosine similarity (candidate narrowing).
### Biological Decay
Memories fade over time unless reinforced. Old, unaccessed memories naturally lose relevance:
```javascript
await mem.decay(); // Run maintenance — archive/delete stale memories
await mem.reinforce(id); // Boost a memory to resist decay
```
### Memory Graph (Zettelkasten Linking)
Every memory is automatically linked to related memories by semantic similarity:
```javascript
const links = await mem.links(memoryId); // Direct connections
const path = await mem.path(idA, idB); // Shortest path between memories
const clusters = await mem.clusters(); // Detect topic clusters
```
### Conflict Resolution & Quarantine
Detect contradictions before storing — with claim-based structural detection or LLM-based semantic detection:
```javascript
// Structural (no LLM needed): claim-based conflict detection
await mem.store('agent', 'Server uses port 443', {
claim: { subject: 'server', predicate: 'port', value: '443' },
provenance: { source: 'user_explicit', trust: 1.0 },
onConflict: 'quarantine', // low-trust conflicts quarantined for review
});
// Semantic (requires LLM): LLM classifies as conflict/update/novel
await mem.evolve('agent', 'Server now uses port 8080');
// Review quarantined memories
const quarantined = await mem.listQuarantined();
await mem.reviewQuarantine(quarantined[0].id, { action: 'activate' });
```
### Predicate Schema Registry
Define per-predicate rules for conflict handling, normalization, and deduplication:
```javascript
const mem = createMemory({
predicateSchemas: {
'preferred_language': { cardinality: 'single', conflictPolicy: 'supersede', normalize: 'lowercase_trim' },
'spoken_languages': { cardinality: 'multi', dedupPolicy: 'corroborate' },
'salary': { cardinality: 'single', conflictPolicy: 'require_review', normalize: 'currency' },
},
});
```
Options: `cardinality` (single/multi), `conflictPolicy` (supersede/require_review/keep_both), `normalize` (none/trim/lowercase/lowercase_trim/currency), `dedupPolicy` (corroborate/store).
### Explainability API
Understand why search returned or filtered specific memories:
```javascript
const results = await mem.search('agent', 'query', { explain: true });
console.log(results.meta); // query options, result count
console.log(results[0].explain); // retrieved, rerank, statusFilter details
const detail = await mem.explainMemory(memoryId);
// { id, status, trust, confidence, provenance, claimSummary }
```
### Multi-Agent Support
```javascript
await mem.store('kuro', 'Vuln found in API gateway');
await mem.store('maki', 'API gateway deployed to prod');
const all = await mem.searchAll('API gateway'); // Cross-agent search
```
### Episodes (Temporal Grouping)
Group related memories into named episodes:
```javascript
const ep = await mem.createEpisode('Deploy v2.0', [id1, id2, id3], { tags: ['deploy'] });
const ep2 = await mem.captureEpisode('kuro', 'Standup', { start: '...', end: '...' });
const results = await mem.searchEpisode(ep.id, 'database migration');
const { summary } = await mem.summarizeEpisode(ep.id); // requires LLM
```
### Memory Compression & Consolidation
Consolidate redundant memories into digests:
```javascript
await mem.compress([id1, id2, id3], { method: 'llm', archiveOriginals: true });
await mem.compressEpisode(episodeId);
await mem.autoCompress({ minClusterSize: