number: 0000 Author: wey-gu Status: Draft Date: 2025-09-19
Modern AI agents need both:
- Grounding — retrieval from external documents, transcripts, logs, etc.
- Memory — distilled, persistent insights from conversations, grounding resources, or user input
Current frameworks (Zep, SemTools) solve parts of this but often:
- Are multi-tenant/cloud-centric, not single-user/local
- Emphasize either memory or retrieval, not both
- Lack a consistent protocol bridging file-based workflows and graph/index backends
OpenKL aims to unify these needs:
- Local-first, open-source, transparent
- Minimal, composable tools (SemTools-style)
- File system ergonomics + Graph/Vector performance
- Auditable, portable Citations for provenance
- Memory: User/agent-authored notes. Temporal (by date) and topical (tags → symlinks)
- Grounding Store: Ingested external material. Canonical original (sources/), normalized text (normalized/*.ok.md). Supports ArXiv papers, web content (via Firecrawl), code repositories, and various document formats
- Knowledge Graph: Derived structure (entities, relationships, chunk nodes). Links back to Memory + Grounding via citations
openkl/
├─ store/ # Grounding Store (authored files + normalized text)
│ ├─ sources/ # originals: pdf, html, txt, srt, md, …
│ │ └─ <any tree you like>
│ └─ normalized/ # one *.ok.md per source (text-only, stable)
│ └─ <mirror of sources>/*.ok.md
├─ memories/
│ ├─ by_date/YYYY-MM/DD/<id>.md # authored notes (frontmatter+body)
│ └─ topics/<slug>/... # optional views/symlinks
├─ graph/ # derived/exports (optional, not persisted by default)
│ ├─ snapshots/*.graphml
│ ├─ entities.csv
│ └─ relations.csv
├─ citations/ # stable cite objects (JSONL)
│ └─ <id>.json
└─ .ok/ # internal
├─ kuzu/ # embedded Graph DB
├─ cache/
├─ mapping.jsonl # docID → current path mapping
└─ config.yml- Files are canonical for authored content (grep-friendly)
- Graph/index is derived (fast retrieval, not persisted by default)
- Renames/reorg safe: docID = content hash; only add/delete matter
- Hierarchical independence: Graph never depends on file tree structure
- Mapping file:
.ok/mapping.jsonltracks docID → current path for renames - Vector indexes: Created automatically when needed, not manually managed
- Canonical view:
memories/by_date/YYYY-MM/DD/<id>.md - Virtual views:
topics/<slug>/symlinks regenerated from tags - Frontmatter example:
---
id: m-20250918-abcd
ts: "2025-09-18T10:22:11Z"
tags: [throttling, scaling]
topics: [llm, infra]
---
Insight text…Engine: Kùzu DB (embedded, Cypher, HNSW vectors, FT index)
Schema (conceptual):
- MemoryNote(id, text, ts, tags, vec)
- Doc(id, path, sha256)
- Chunk(id, text, span, vec)
- Entity(id, name, type)
- Relations:
- (Doc)-[:HAS_CHUNK]->(Chunk)
- (Chunk)-[:MENTIONS]->(Entity)
- (Memory)-[:MENTIONS]->(Entity)
- (Memory)-[:DERIVED_FROM]->(Doc|Chunk)
- (Memory)-[:HAS_TOPIC]->(Topic)
Chunking: only in DB (default 512 tokens, stride 128). Optional dump for debugging.
Citations provide:
- Stable provenance: What document + where exactly
- Verifiability: Can re-open and see exact text in context
- Portability: Ship answer + cites to another machine, still verify
- Agent-safe anchoring: Agent can request cite, verify, then include in answer
- Doc ID:
sha256(bytes of normalized .ok.md)[:16](content-addressed, rename-safe) - Chunk ID:
docID#tok[start]-[end](token span) ordocID#char[start]-[end] - Memory ID:
m-<time>-<random4>(or content-hash for full determinism)
{
"type": "doc|chunk|memory",
"id": "8e1f9a62#tok1200-1450",
"path": "store/normalized/papers/xyz.ok.md",
"sha256": "…",
"loc": {"kind":"char","start":7231,"end":7615},
"quote": "Exact snippet…",
"context": {"pre":"…", "post":"…"},
"source": {"url":"file:///…/xyz.pdf","page":12},
"created_at": "2025-09-18T11:03:22Z"
}ok cite make <path> --lines 100-120→ writes cite JSON, prints URIok cite verify <id|uri>→ true/false + diagnosticsok cite open <id>→ open file and highlight span (TTY-friendly markers)
ok mem add/search/update/deleteok store ingest/arxiv/web/repo/searchok search "query"→ cross-surface hybrid, returns Cite objectsok graph cypher(RO default)ok cite make|verify|open|listok distill→ extract insights from grounding store to memoryok index,ok sync,ok doctor
mem_search,mem_addstore_search,store_ingest,store_citegraph_cypherfs_list,fs_readsh_grep(safe wrapper, rg if present, Python fallback otherwise)
Agent Integration Guide: See examples/agents.md for detailed integration patterns, examples, and best practices for AI agents using OpenKL.
Memory distillation is the process of extracting insights from grounding store content and creating structured memories. This bridges the gap between raw documents and actionable knowledge.
# 1. Search grounding store for relevant content
ok search "distributed systems patterns" --json
# 2. Create citations for important findings
ok cite make <chunk_id> --retention-class durable --tags "patterns,reliability"
# 3. Get distillation prompt for agent
ok distill get-prompt identify-patterns
# 4. Agent uses prompt with LLM to distill content
# (Agent evaluates prompt and generates distilled content)
# 5. Create memory from agent-distilled content
ok distill create "Distilled patterns and principles" <citation_ids> \
--tags "patterns,reliability"
# 6. Verify and refine
ok mem search "distributed patterns" --jsonStandard prompts for different types of distillation:
extract-facts: Extract factual information and key detailsidentify-patterns: Find recurring patterns and principlessummarize-insights: Create high-level summariesextract-relationships: Identify connections between conceptsextract-entities: Find important entities and their propertiesmemory-synthesis: Synthesize into actionable memory entry
Distillation automatically creates graph relationships:
(MemoryNote)-[:DerivedFrom]->(Chunk)- Links memory to source chunks(MemoryNote)-[:Mentions]->(Entity)- Links memory to extracted entities(MemoryNote)-[:HasTopic]->(Topic)- Links memory to topics
- Hybrid search: vector + FT, score fusion, dedupe per doc
- Output: Cite objects only (verifiable, portable)
- Re-ranking: promote diversity, limit per doc
- Agentic pipeline (standardized):
sh_grep(fast filter)store_search(semantic)graph_cypher(entity hops)citefor provenancedistillfor insight extractionmem_addfor persistent storage
- rg preferred for grep (fast, JSON output);
ok doctorchecks + bundles binary; fallback to Python - All writes explicit (
mem_add,store_ingest) - Logs for reproducibility (
--tee .ok/ops.log) - Agent tools capped (k, bytes, timeouts)
- Privacy: everything local; no cloud by default
openkl/
├─ __init__.py
├─ cli.py # Typer CLI entrypoint
├─ db.py # GraphDB wrapper + schema
├─ memory.py # memory operations
├─ store.py # grounding store ops
├─ graph.py # Cypher helpers
├─ citations.py # citation management (transient + persisted)
├─ vector_search.py # vector search with GraphDB HNSW indexes
├─ parsers.py # document parsing (PDF, HTML, Markdown)
├─ utils.py # utilities (embedding, chunking)
├─ api.py # API server (placeholder)
└─ server.py # HTTP server (placeholder)Install with uv:
uv sync
# Or install globally
uvx openkl-- Memory (384-dimensional vectors with FastEmbed)
CREATE NODE TABLE MemoryNote(id STRING PRIMARY KEY, text STRING, ts STRING, tags STRING[], vec FLOAT[384]);
-- Grounding Docs
CREATE NODE TABLE Doc(id STRING PRIMARY KEY, path STRING, sha256 STRING);
CREATE NODE TABLE Chunk(id STRING PRIMARY KEY, text STRING, span STRING, vec FLOAT[384]);
-- Entities
CREATE NODE TABLE Entity(id STRING PRIMARY KEY, name STRING, type STRING);
CREATE NODE TABLE Topic(id STRING PRIMARY KEY, name STRING);
-- Relations
CREATE REL TABLE HAS_CHUNK(FROM Doc TO Chunk);
CREATE REL TABLE Mentions(FROM Chunk TO Entity);
CREATE REL TABLE MemMentions(FROM MemoryNote TO Entity);
CREATE REL TABLE DerivedFrom(FROM MemoryNote TO Chunk);
CREATE REL TABLE HasTopic(FROM MemoryNote TO Topic);
-- Vector Indexes (HNSW)
CALL CREATE_VECTOR_INDEX('MemoryNote', 'memory_vec_idx', 'vec', metric := 'cosine');
CALL CREATE_VECTOR_INDEX('Chunk', 'chunk_vec_idx', 'vec', metric := 'cosine');✅ Implemented:
- Memory management with temporal organization (add, search, update, delete)
- Document ingestion with automatic chunking (PDF, HTML, Markdown via Kreuzberg)
- Vector search using GraphDB HNSW indexes with FastEmbed (384-dimensional)
- Citation system (transient and persisted modes with verification)
- Hybrid search across memory and grounding store
- Graph statistics and Cypher query interface
- ArXiv paper ingestion and processing
- Topic symlink regeneration and management
- Pre-commit hooks with ruff formatting and linting
- Memory update and delete operations
- Citation creation from search results (via
ok cite make) - Memory distillation from search results and citations (via
ok distill) - Graph relationship creation for distilled memories
🔄 In Progress:
- Entity extraction and relationship linking
- API server implementation (basic structure exists)
📋 Planned:
- Web content ingestion via Firecrawl (placeholder commands exist)
- Code repository ingestion and analysis (placeholder commands exist)
- Memory consolidation/forgetting
- Export commands
- Knowledge graph visualization and exploration
# Ingest documents
ok store ingest docs/whitepapers
# Ingest ArXiv papers
ok store arxiv 1706.03762
# Search across all knowledge
ok search "transformer architecture"# Search for information
ok search "transformer architecture" --json
# Create citations for important findings
ok search "transformer architecture" --json | jq -r '.[].id' | head -3 | \
xargs -I {} ok cite make {} --retention-class durable --tags "transformer,architecture"
# Verify citations
ok cite list# Search memory for insights
ok mem search "transformer" --json
# Explore graph relationships
ok graph cypher "MATCH (m:MemoryNote)-[:HasTopic]->(t:Topic) RETURN t.name, count(m) as memories"
# Find related concepts
ok graph cypher "MATCH (m:MemoryNote)-[:HasTopic]->(t:Topic {name: 'nlp'}) RETURN m.id, m.text"# Add insights to memory
ok mem add "Transformers use self-attention for parallel processing" \
--tags "transformer,attention,architecture" --topics "nlp,deep-learning"
# Update existing memory
ok mem update m-20250919-abc123 --text "Updated insight" --tags "new,tags"
# Delete outdated information
ok mem delete m-20250919-abc123# Find entities mentioned in transformer-related memories
ok graph cypher "MATCH (m:MemoryNote)-[:HasTopic]->(t:Topic {name: 'transformer'})-[:Mentions]->(e:Entity) RETURN e.name, e.type, count(m) as mentions ORDER BY mentions DESC"
# Discover relationships between concepts
ok graph cypher "MATCH (m1:MemoryNote)-[:Mentions]->(e:Entity)<-[:Mentions]-(m2:MemoryNote) WHERE m1.id <> m2.id RETURN e.name, collect(DISTINCT m1.id) as memories"
# Find related documents through entity connections
ok graph cypher "MATCH (m:MemoryNote)-[:DerivedFrom]->(c:Chunk)-[:HAS_CHUNK]->(d:Doc) WHERE m.text CONTAINS 'attention' RETURN d.path, c.text LIMIT 5"
# Explore topic hierarchies and relationships
ok graph cypher "MATCH (t1:Topic)-[:RelatedTo*1..2]->(t2:Topic) RETURN t1.name, t2.name, length(path) AS distance"# Web content ingestion (planned)
ok store web "https://example.com/article" --depth 2
ok store web "https://docs.example.com" --recursive --max-depth 3
# Code repository ingestion (planned)
ok store repo "https://github.com/user/repo" --branch main
ok store repo "/local/path/to/repo" --include "*.py,*.js,*.md"
# Multi-format document parsing
ok store ingest "document.pdf" --format pdf
ok store ingest "presentation.pptx" --format pptx
ok store ingest "spreadsheet.xlsx" --format xlsx- Scaffold Python package + CLI (
ok) - Implement:
mem add/searchstore ingest/search(txt, md, pdf)graph cypher(RO)cite make/verify/openindex,sync,doctor- GraphDB schema + embeddings (sentence-transformers)
- Entity extraction + graph linking
- Topic symlink regeneration
- API server exposing tools
- Export commands
- MCP prompts and tools based Protocol
- Memory consolidation/forgetting (later)
- Integrate with Apache OpenDAL for Cloud Agent support