File-based persistent memory for AI agents. Zero dependencies.
Project description
Parsica-Memory
Persistent, intelligent memory for AI agents. Memory that picks up where you left off, anywhere. Zero dependencies. Pure Python. File-backed.
What Is This?
AI agents forget everything between sessions. parsica-memory fixes that. It gives any agent a persistent, searchable memory store that:
- Persists across sessions, servers, platforms, spawns, and restarts
- Retrieves the right memories through a 12-layer BM25+ search engine
- Decays old memories so signal stays high over time
- Enriches memories via optional LLM metadata at write time for much better recall
- Shares knowledge across multi-agent teams
- Learns from mistakes, facts, and procedures with specialized memory types
- Cross-session recall - facts and decisions surface across sessions automatically. Pulls x amount (configurable) of top ranked memories and recent memories from shared memory store across any session, server, or platform.
- Talk on Telegram and pick back up on Discord.
- Core features require no vector database, no API keys, and no external services. Optional enrichment can use an LLM provider or local compatible endpoint.
Full benchmarks and methodology
LOCOMO 2000-question benchmark Published benchmark details live on the benchmarks page above. The README benchmark summary below is a local package-level snapshot measured on a Mac Mini M4 with 9,896 live memories.
How to Read This README
This package has four layers. Knowing which one you need saves time:
- MemoryManager - the batteries-included wrapper for most applications. Easiest path.
- MemorySystem - the low-level engine with full control over ingest, search, graph, tiers, and maintenance.
- CLI - operational/admin commands for initializing, inspecting, rebuilding, and serving a workspace.
- Advanced internals - the 12-layer search engine, sharding, graph intelligence, shared pools, WAL, recency, and enrichment pipeline.
If you just want to ship something fast, start with MemoryManager. If you want full control, use MemorySystem. If you want to inspect or serve a store, use the CLI.
Quick Start
pip install parsica-memory
Using Parsica with OpenClaw
Parsica Memory ships with a native OpenClaw plugin. This gives your OpenClaw agent persistent memory across all sessions, channels, and platforms -- no extra setup beyond copying a folder.
What's included
The openclaw-plugin/ directory in this repo contains everything you need:
index.js-- the OpenClaw plugin entry point. Handles registration, command routing (/prune,/memory,/search), and lifecycle hooks (before_agent_start,agent_end).pipeline_bridge.py-- the Python bridge process that runs Parsica Memory's retrieval engine, enrichment pipeline, and memory ingestion. Communicates withindex.jsvia JSON-over-stdin/stdout.
Installation
- Install the Python library:
pip install parsica-memory
- Copy the plugin to your OpenClaw extensions:
cp -r openclaw-plugin/ ~/.openclaw/extensions/parsica-memory/
- Register the plugin in your
openclaw.json:
Add this to your plugins.entries section:
{
"plugins": {
"entries": {
"parsica-memory": {
"path": "~/.openclaw/extensions/parsica-memory",
"enabled": true,
"config": {
"memoryPath": "~/.openclaw/workspace/parsica_memory_store",
"searchLimit": 5
}
}
}
}
}
Enrichment is on by default and uses your gateway's LLM provider automatically. No extra API key needed unless you want to use a different provider for enrichment (see Advanced enrichment configuration below).
-
Restart your OpenClaw gateway.
-
Verify it's working:
After restart, send any message to your agent. Then check:
/prune sessions
If Parsica is running, you'll see a response listing your sessions. You can also check:
/search test
to confirm retrieval is working.
That's it. Parsica will now:
- Ingest every conversation turn into persistent memory
- Enrich each memory with tags, summaries, and search keywords via your gateway's LLM
- Recall relevant memories before each response (injected into context via
before_agent_start) - Cross-session recall -- memories from one channel surface in another when relevant
- Cross-platform recall -- talk on Telegram, pick back up on Discord
- Recency recall -- recent cross-channel memories are injected automatically
Configuration options
| Option | Default | What it does |
|---|---|---|
memoryPath |
./parsica_memory_store |
Where the memory store lives on disk |
searchLimit |
5 |
How many memories to inject per turn |
recencyEnabled |
true |
Inject recent cross-channel memories |
recencyWindow |
6.0 |
Hours of recency to consider |
recencyLimit |
5 |
Max recent memories to inject |
crossSessionRecall |
semantic |
Cross-session recall mode: semantic, all, or none |
Note on memory isolation: If you run multiple users on the same OpenClaw instance and need per-user memory isolation, use separate memoryPath values for each user. By default, all sessions share one memory store, which is what enables cross-session and cross-platform recall.
Plugin commands
Once installed, these commands are available in your OpenClaw chat:
/search <query>-- search the memory store/prune sessions-- clean up stale sessions/prune small|medium|large-- prune old/low-value memories (dry-run first, addconfirmto apply)/enrichment on-- enable LLM enrichment at ingest time/enrichment off-- disable enrichment/enrichment status-- show enrichment state, model, and coverage/decompose on-- enable fact decomposition at ingest time/decompose off-- disable decomposition (default)/decompose status-- show decomposition state/decompose run-- retroactively decompose existing memories
Enrichment
Enrichment is one of Parsica's most important features. When enabled (on by default), every ingested memory is processed by an LLM to generate:
- Tags -- topical labels for better retrieval matching
- Enriched summary -- a concise summary that surfaces the core facts
- Search keywords -- additional terms that bridge vocabulary gaps between how you stored something and how you'll search for it later
Why enrichment matters:
We benchmarked enrichment coverage against recall quality on a live 18,000-memory store:
| Coverage | R@1 | R@5 | MRR |
|---|---|---|---|
| 20% enriched | 37.6% | 55.6% | 0.459 |
| 60% enriched | 48.8% | 62.4% | 0.549 |
| 100% enriched | 56.0% | 67.2% | 0.609 |
R@1 nearly doubles from 20% to 100% coverage. MRR climbs from 0.459 to 0.609. This is one of the largest single levers in the system.
How it works out of the box:
Enrichment uses your gateway's default LLM provider automatically. No separate API key is needed. Any model works. Haiku is recommended for cost efficiency.
Toggle it live:
/enrichment off # disable without editing config
/enrichment on # re-enable
/enrichment status # check current state + coverage
Advanced enrichment configuration
For power users who want more control:
{
"config": {
"enrichModel": "anthropic/claude-haiku-4-5-20251001",
"anthropicApiKey": "sk-ant-...",
"openaiApiKey": "sk-...",
"googleApiKey": "AIza..."
}
}
You can provide API keys from multiple providers. Parsica will use the key that matches the model you specify. For security, consider using OpenClaw's auth-profiles system instead of putting keys directly in your config file.
Retroactive enrichment with multiple keys:
If you have an existing memory store and want to enrich it in bulk, use the CLI harness with multiple API keys:
parsica enrich --workspace ./store
parsica enrich --workspace ./store --keys key1,key2,key3,key4,key5
parsica enrich --workspace ./store --coverage
Multiple keys are used in round-robin sequence, distributing load across providers and reducing rate-limit pressure on large stores. True parallel execution is planned for a future release.
Fact decomposition
Decomposition breaks memories into atomic facts, which can improve evidence recall and detail extraction for certain workloads.
Decomposition is off by default. This is intentional. Decomposition is powerful but opinionated -- it changes how memories are stored and ranked. For some workloads (technical memory, project tracking, coding assistants), it's a clear win. For others (conversational assistants, journaling), it can fragment context in ways that hurt top-1 precision.
Toggle it live:
/decompose on # enable at ingest time
/decompose off # disable (default)
/decompose status # check current state
/decompose run # retroactively decompose existing memories
When to use decomposition:
- Your agent needs to recall specific facts from long conversations
- You care more about evidence recall than conversational context
- Your store contains dense, multi-fact memories that don't surface well as whole units
When to leave it off:
- Your agent is conversational and benefits from full-context memories
- You want memories to feel natural, not clinical
- You're not sure yet -- start without it, measure, then decide
This is what Parsica means by "tunable memory quality." Every lever is visible, every tradeoff is documented, and every control point is accessible.
What you get with zero dependencies
Even without enrichment, the core BM25+ retrieval engine provides:
- 12-layer retrieval with phrase matching, rarity boosting, field weighting, windowing, and more
- File-native storage (JSON, human-readable, grep-able)
- Zero runtime dependencies beyond Python stdlib
- 185 memories/sec ingest, 65.7ms p50 search
See the full 12-layer engine documentation for details on each retrieval layer.
Troubleshooting
Plugin loads but nothing happens:
- Check that your
openclaw.jsonuses"parsica-memory"as the plugin key (not"antaris-suite") - Make sure
pipeline_bridge.pyis in the same directory asindex.js - Check that
parsica-memoryis installed:pip show parsica-memory
Enrichment not working:
- Run
/enrichment statusto check if it's enabled - Verify your gateway has a working LLM provider configured
- Check gateway logs for bridge errors
Slow search on large stores:
- Stores under 1,000 memories: expect <70ms p50
- Stores over 10,000 memories: expect 100-500ms depending on query complexity
- Cold-cache first queries on large stores can take 2-4 seconds
Python version:
- Parsica Memory requires Python 3.9+
MemoryManager
MemoryManager is the batteries-included wrapper. It gives you everything you need to build a memory-enabled application without writing boilerplate.
What It Does
- Per-user isolation - each user gets their own memory store in a sub-directory
- Noise filtering - automatically skips junk (context packets, system messages, very short text)
- Cross-session and platform semantic recall - facts and decisions cross session/platform boundaries, episodic chatter stays scoped
- Auto-save - saves to disk after every ingest so nothing is lost on crash
- LLM enrichment - optional, pluggable, works with any provider
from parsica_memory import MemoryManager
# Basic - no enrichment (BM25-only, still good)
mm = MemoryManager("./store")
# With enrichment - pass any API key
mm = MemoryManager("./store", api_key="sk-ant-...", provider="anthropic")
# Or auto-detect from environment variables
mm = MemoryManager("./store", auto_enrich=True)
# Store a conversation
mm.ingest("user123", "What's the capital of France?", "The capital of France is Paris.")
# Recall relevant memories
results = mm.recall("user123", "France capital")
for r in results:
print(r)
# Search with full metadata
results = mm.search("user123", "Paris", explain=True)
for r in results:
print(f"{r['relevance']:.2f} - {r['content'][:80]}")
Option 2: Direct MemorySystem - The Raw Engine (Full Control)
No user isolation built in. You manage scope yourself. But you get access to everything: search params, graph intelligence, sharding, tiering, maintenance, shared pools, etc.
from parsica_memory import MemorySystem
mem = MemorySystem(workspace="./memory", agent_name="my-agent")
mem.load()
mem.ingest("Deployed v2.3.1 to production. All checks green.",
source="deploy-log", session_id="session-123")
results = mem.search(
"production deployment",
session_id="session-456",
cross_session_recall="semantic",
source_channel="discord:general"
)
results = mem.search("deployment", explain=True)
for r in results:
print(r.entry.created) # "2026-03-17T18:30:45.123456"
mem.save()
When to use which:
| MemoryManager | MemorySystem | |
|---|---|---|
| Multi-bot user | Yes - built for this | You'd have to manage users yourself |
| Single agent | Works but overkill | Perfect |
| Shared memory across runtimes | Not designed for this | Yes - point two runtimes at same workspace |
| Full search control | Limited (simplified API) | Full access to all params |
| Graph / tiering / maintenance | Hidden | Direct access |
Installation
pip install parsica-memory
Current package line: 2.x (parsica-memory currently ships as 2.7.0)
Requirements: Python 3.9+. Zero external dependencies. stdlib only.
Setup Wizard
Running python -m parsica_memory init launches an interactive wizard that guides you through the full setup flow:
# Interactive (prompts you through setup)
python -m parsica_memory init --workspace ./memory --agent-name my-agent
# Headless (skip wizard, use defaults)
python -m parsica_memory init --workspace ./memory --headless
# Automation-safe: non-TTY shells auto-fallback to headless mode
python -m parsica_memory init --workspace ./memory
Wizard Flow
1. Manager vs System - Choose between MemoryManager (batteries-included, per-user isolation) or MemorySystem (full low-level control). The wizard generates starter code tailored to your choice.
2. Import - Optionally import existing memories from another system. Six source options: mem0, Memvid, MEMO, Zep, LangMem, OpenAI (threads export), plus generic json, jsonl, sqlite, markdown, and csv formats.
3. Enrichment setup - If an API key is detected in your environment (ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_API_KEY, PARSICA_LLM_API_KEY), the wizard prompts to enable enrichment. Key prefix detection identifies the provider automatically - no manual provider selection needed.
4. Model selection - Pick from a recommended model list with cost and quality indicators. Example:
| Model | Provider | Quality | Cost per 1K |
|---|---|---|---|
| claude-haiku-4-5 | Anthropic | Good | ~$0.05 |
| claude-sonnet-4-6 | Anthropic | Best | ~$0.50 |
| gpt-4o-mini | OpenAI | Good | ~$0.15 |
| gemini-2.0-flash | Good | ~$0.05 | |
| llama3.2 (local) | Ollama | Variable | $0.00 |
5. Retroactive enrichment - If you have existing memories in the workspace (or just imported from another system), the wizard offers to enrich them now. It shows a time estimate based on store size and the selected model's typical throughput. If multiple API keys are available, it offers the multi-key option (see ParallelEnricher).
6. Background processing - Enrichment runs in the background with a live progress display (done/total counter). You can close the terminal; enrichment resumes on next startup if interrupted.
The wizard writes parsica_config.json to the workspace with your chosen settings and outputs a ready-to-run Python snippet.
Constructor
MemoryManager(
store_path="./store", # Root directory for all user stores
search_limit=10, # Max results per search
min_relevance=0.3, # Minimum score to include (0-1)
api_key=None, # LLM API key for enrichment (optional)
provider=None, # "anthropic", "openai", "gemini", "local", or "auto"
model=None, # Model override (uses sensible defaults)
auto_enrich=False, # Auto-detect enricher from env vars
agent_name="parsica", # Name for memory provenance
)
Methods
# Ingest a conversation turn (filters noise, enriches if configured, auto-saves)
mm.ingest(user_id, user_message, bot_response, session_id="", channel_id="")
# Ingest arbitrary content (documents, notes, facts)
mm.ingest_raw(user_id, content, source="manual", category="note")
# Recall relevant memories as plain strings
results = mm.recall(user_id, query, session_id="", limit=None, confidence_floor=0.0)
# Recall with metadata (provenance, confidence, etc.)
results = mm.recall_structured(user_id, query, session_id="", limit=None, confidence_floor=0.0)
# Search with full metadata (relevance, source, category, matched_terms)
results = mm.search(user_id, query, session_id="", limit=None, explain=False, confidence_floor=0.0)
# Batch-enrich all existing memories for a user
count = mm.enrich_all(user_id, overwrite=False, progress_fn=None)
# Batch-decompose all existing memories into atomic facts
# Note: this uses the underlying MemorySystem; MemoryManager does not expose
# a first-class decompose_all() wrapper.
count = mm._get_store(user_id).re_decompose(overwrite=False, progress_fn=None)
# Export a user's memories to JSON
count = mm.export(user_id, "./export.json")
# Get stats for a user or all users
stats = mm.stats(user_id)
# Delete all memories for a user
count = mm.clear(user_id)
Provider Auto-Detection
If you pass just an API key without specifying a provider, MemoryManager guesses from the key prefix:
| Key Prefix | Detected Provider | Default Model |
|---|---|---|
sk-ant- |
Anthropic | claude-sonnet-4-6 |
sk- |
OpenAI | gpt-4o-mini |
AI |
Google Gemini | gemini-2.0-flash |
Or set provider="auto" / auto_enrich=True to check environment variables (ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_API_KEY) in order. The provider-agnostic entry point PARSICA_LLM_API_KEY is also supported (legacy ANTARIS_LLM_API_KEY still works).
LLM Enrichment & Fact Decomposition: How It Works
-
Enrichment is the main reason parsica-memory outperforms vector-based memory systems at recall. When you ingest a memory, an optional LLM call generates additional tags, summaries, keywords, and search_queries for each memory.
-
Fact Decomposition - takes existing memories and breaks them into separate atomic fact entries with parent linking, fuzzy dedup, and individual search surface.
The Problems These Solve
Without either, a memory like:
"We decided to use FastAPI for the backend"
...can only be found by searching for words that appear in it: "FastAPI", "backend", "decided". But a user might search for "web framework", "API server", "http library", or "what tech stack did we pick?" - none of which appear in the original text.
How Enrichment and Fact Decomposition Fixes This
At ingest time, an LLM reads the memory and generates multiple new fields as metadata:
Enrichment
| Field | What It Contains | Search Weight |
|---|---|---|
tags |
5-10 semantic concept labels (web_framework, architecture_decision, python) |
Boosted in field matching |
summary |
One-sentence rephrase using DIFFERENT vocabulary than the original | 2x weight in search scoring |
keywords |
8-15 terms a person might type when looking for this memory | 2x weight in search scoring |
search_queries |
3 specific natural language questions ONLY this memory answers | 3x weight in search scoring |
Fact Decomosition
| Field | What It Contains | Search Weight |
|---|---|---|
Atomic Facts |
breaks memories down into multiple different facts, tied to keywords and context | Boosted in field matching |
Parent Linking |
Links each fact to the parent memory | 2x weight in search scoring |
Fuzzy Dedup |
Automatically removed duplicates from the store for efficiency | Boosted in field matching |
Individual Search Surface |
Gives more ways to link back to the correct memory based on new facts | 2x weight in search scoring |
For the FastAPI example, enrichment might produce:
{
"tags": ["web_framework", "architecture_decision", "python", "backend", "api_server"],
"summary": "The team selected FastAPI as their Python HTTP framework for the API layer",
"keywords": ["framework", "http", "api server", "asgi", "tech stack", "web library"],
"search_queries": [
"What web framework did we choose for the backend?",
"Why did we pick FastAPI over Flask or Django?",
"What is our Python API server technology?"
]
}
Now the memory is findable by ANY of those terms, not just the original words.
Enrichment at Ingest Time
When you configure the enricher and Fact Decomposer, every new memory gets enhanced automatically at write time:
from parsica_memory import MemoryManager
# Enrichment happens automatically on every ingest
mm = MemoryManager("./store", api_key="sk-ant-...", provider="anthropic")
mm.ingest("user1", "Let's use PostgreSQL for the database", "Good choice, PostgreSQL it is.")
# ^ This memory is now findable by: "database", "postgres", "sql",
# "what DB are we using", "data storage decision", etc.
Enriching and Re-Decomposing Existing Memories (Backfill)
Already have memories stored without enrichment? You can batch-process them through MemoryManager.enrich_all() and the underlying MemorySystem.re_decompose() store method:
from parsica_memory import MemoryManager
mm = MemoryManager("./store", api_key="sk-ant-...", provider="anthropic")
# Enrich all un-enriched memories for a user
count = mm.enrich_all("user1")
print(f"Enriched {count} memories")
# Force re-enrich everything (even already-enriched entries)
count = mm.enrich_all("user1", overwrite=True)
# With progress tracking
def progress(done, total):
print(f" {done}/{total}...")
count = mm.enrich_all("user1", progress_fn=progress)
# Fact decomposition for each existing memory uses the underlying store
count = mm._get_store("user1").re_decompose()
print(f"Created {count} new fact entries")
Using the Low-Level API for Backfill
If you're using MemorySystem directly instead of MemoryManager:
from parsica_memory import MemorySystem
from parsica_memory.enrichers import anthropic_enricher
# Create enricher
enrich = anthropic_enricher("sk-ant-...")
# Load existing store and attach enricher
mem = MemorySystem(workspace="./store", agent_name="my-agent", enricher=enrich)
mem.load()
# Metadata only (default) - adds tags, summary, keywords, search_queries
count = mem.re_enrich(
batch_size=50, # Progress callback fires every N entries
overwrite=False, # Skip already-enriched entries
decompose=False, # Default - metadata only, no fact decomposition
progress_fn=lambda done, total: print(f"{done}/{total}")
)
print(f"Enriched {count} memories")
# Combined pass - metadata enrichment + fact decomposition in one LLM call per entry
count = mem.re_enrich(
batch_size=50,
overwrite=False,
decompose=True, # Enrichment + decomposition in one pass
progress_fn=lambda done, total: print(f"{done}/{total}")
)
print(f"Enriched and decomposed {count} memories")
mem.save()
re_enrich(decompose=True) runs both enrichment and decomposition in a single LLM call per entry, which is cheaper than running re_enrich() followed by re_decompose() separately. Use it when you want both and haven't enriched the store yet.
Multi-Key Parallel Enrichment
For large stores, ParallelEnricher distributes work across multiple API keys in round-robin sequence. Each key gets an equal share of the entries and runs independently. Results merge back to the workspace automatically.
from parsica_memory.parallel_enricher import ParallelEnricher
enricher = ParallelEnricher(
workspace="./memory",
keys=[
{"key": "sk-ant-...", "model": "claude-haiku-4-5-20251001"},
{"key": "sk-proj-...", "model": "gpt-4o-mini"},
],
decompose=True,
)
result = enricher.run(progress_fn=lambda done, total: print(f"{done}/{total}"))
print(f"Enriched {result['enriched']} entries using {result['workers']} workers")
ParallelEnricher is also accessible through the setup wizard when multiple API keys are detected in the environment.
Fact Decomposition
Enrichment adds metadata to memories. Decomposition goes further: it breaks memories into separate atomic fact entries, each independently searchable.
Why Decomposition Matters
A single conversation turn often contains multiple distinct facts buried in one blob:
"We decided to use FastAPI for the backend, PostgreSQL for the database, and deploy on Railway"
With enrichment only, this is stored as one entry. Decomposition pulls each fact out:
- "The team chose FastAPI for the backend"
- "PostgreSQL was selected as the database"
- "Deployment target is Railway"
Each fact becomes its own memory with its own search surface. The difference in recall for specific-fact queries is significant.
Decomposed facts also get:
memory_type="fact"- crosses session boundaries automatically, high recall priorityprovenance="decomposed"- tracks that it was extracted, not created directlyparent_hash- links back to the original memoryconfidence=0.8- starts high- Fuzzy dedup - won't create duplicate facts if the same info already exists (Jaccard similarity >= 0.85 threshold)
How re_decompose() Works Internally
- Takes all existing memories (skips entries that are already
memory_type="fact") - Runs the LLM enricher on each one to get a
factsfield (list of atomic fact strings) - For each fact string - creates a new
MemoryEntrywithmemory_type="fact",provenance="decomposed",parent_hash=source.hash,confidence=0.8 - Dedup checks: exact hash match, normalized content match, then fuzzy similarity against all existing facts
- If a near-duplicate exists, bumps the existing entry's
sighting_countandconfidenceinstead of creating a duplicate - Saves to disk after all new facts are created
The overwrite=False default (recommended) skips any source entry whose hash already appears as a parent_hash somewhere in the store - meaning it won't re-decompose something it already processed.
Using re_decompose() with MemoryManager
from parsica_memory import MemoryManager
mm = MemoryManager("./store", api_key="sk-ant-...", provider="anthropic")
# Decompose all existing memories for a user
count = mm._get_store("user1").re_decompose()
print(f"Created {count} new fact entries")
# Force re-decompose (even entries that were already processed)
count = mm._get_store("user1").re_decompose(overwrite=True)
# With progress tracking
def progress(done, total, new_facts):
print(f" Processed {done}/{total}, created {new_facts} facts so far...")
count = mm._get_store("user1").re_decompose(progress_fn=progress)
Using re_decompose() with MemorySystem
from parsica_memory import MemorySystem
from parsica_memory.enrichers import anthropic_enricher
enrich = anthropic_enricher("sk-ant-...")
mem = MemorySystem(workspace="./store", agent_name="my-agent", enricher=enrich)
mem.load()
count = mem.re_decompose(
batch_size=50, # Progress callback fires every N entries
overwrite=False, # Skip entries already decomposed
progress_fn=lambda done, total, new_facts: print(f"{done}/{total} (+{new_facts} facts)")
)
print(f"Created {count} new fact entries")
mem.save()
Cost Estimate
Decomposition uses one LLM call per source memory, with a 600-character input cap and 256-token output cap - same as standard enrichment. But decomposition processes every non-fact memory in the store, so the total cost scales with store size. If you run re_enrich() and re_decompose() on the same store, you're paying for two passes.
Any provider's API key works. Approximate cost per 1,000 memories:
| Provider | Model | ~Cost per 1,000 memories |
|---|---|---|
| Anthropic | claude-sonnet-4-6 | ~$0.50 |
| OpenAI | gpt-4o-mini | ~$0.15 |
| gemini-2.0-flash | ~$0.05 | |
| Local (Ollama) | llama3.2 | $0.00 |
Core vs Enrichment
Core memory features work without any API key or external service: persistence, search, recency, sharding, graph features, CLI ops, and shared/team memory are all available locally.
LLM-powered enrichment and fact decomposition are optional add-ons. They improve recall by generating extra metadata (tags, summary, keywords, search_queries, facts) but they are not required to use the package.
Best Practice: Enrich First, Then Decompose
# Step 1: enrich (adds tags, summary, keywords, search_queries, facts field)
count = mem.re_enrich(batch_size=50, overwrite=False)
print(f"Enriched {count} memories")
# Step 2: decompose (uses the facts field from enrichment to create atomic entries)
count = mem.re_decompose(batch_size=50, overwrite=False)
print(f"Created {count} fact entries")
mem.save()
Running re_enrich() first ensures the facts field is populated before decomposition reads it. Running them in reverse order means decomposition is working from whatever partial enrichment already existed.
Pre-Built Enricher Templates
Four provider-ready enricher factories ship with Parsica-Memory. All use raw urllib from the standard library - zero SDK dependencies.
Anthropic (Claude)
from parsica_memory.enrichers import anthropic_enricher
enrich = anthropic_enricher(
api_key="sk-ant-...", # Or set ANTHROPIC_API_KEY env var
model="claude-sonnet-4-6", # Default
)
mem = MemorySystem("./store", agent_name="my-agent", enricher=enrich)
OpenAI (GPT)
from parsica_memory.enrichers import openai_enricher
enrich = openai_enricher(
api_key="sk-...", # Or set OPENAI_API_KEY env var
model="gpt-4o-mini", # Default (cheap and fast)
base_url="https://api.openai.com", # Change for Azure, etc.
)
Google Gemini
from parsica_memory.enrichers import gemini_enricher
enrich = gemini_enricher(
api_key="AI...", # Or set GOOGLE_API_KEY env var
model="gemini-2.0-flash", # Default
)
Local Models (Ollama, LM Studio, vLLM)
from parsica_memory.enrichers import local_enricher
enrich = local_enricher(
base_url="http://localhost:11434", # Ollama default
model="llama3.2",
timeout=60, # Longer timeout for local models
)
No API key needed. Works with anything that speaks the OpenAI Chat Completions format.
Auto-Detect
from parsica_memory.enrichers import auto_enricher
# Checks ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_API_KEY,
# and PARSICA_LLM_API_KEY (provider-agnostic) in order
enrich = auto_enricher()
if enrich:
mem = MemorySystem("./store", agent_name="my-agent", enricher=enrich)
Custom Enricher
Write your own - just return a dict:
def my_enricher(text: str) -> dict:
# Call whatever you want
return {
"tags": ["tag1", "tag2"],
"summary": "One sentence rephrase",
"keywords": ["term1", "term2", "term3"],
"search_queries": [
"Question 1?",
"Question 2?",
"Question 3?"
],
"facts": [
"Atomic fact extracted from the text.",
"Another atomic fact."
]
}
mem = MemorySystem("./store", agent_name="my-agent", enricher=my_enricher)
All fields are optional. The facts field is used by re_decompose(). Missing fields are skipped if omitted.
Provenance Tracking
Every memory carries provenance metadata - where it came from and how it got there:
| Provenance | Meaning |
|---|---|
self |
Created directly by this agent (default) |
decomposed |
Extracted as an atomic fact from a conversation |
shared |
Received from another agent via shared memory pool |
transferred |
Migrated from another memory system |
workspace |
Ingested from a workspace file |
manual |
Manually added by a user or admin |
# Structured recall includes provenance
results = mm.recall_structured("user1", "password")
for r in results:
print(f"{r['content']} (from: {r['provenance']}, confidence: {r['confidence']:.2f})")
Fuzzy Dedup and Canonicalization
When the same fact appears in slightly different phrasings, Parsica-Memory detects the overlap and links them instead of storing duplicates.
How it works:
- At ingest time, new facts are compared against existing facts using Jaccard word similarity
- If similarity >= 0.85 (configurable), the existing canonical fact is kept and its metadata is updated:
confidencebumped by +0.05 (capped at 1.0),sighting_countincremented,last_sightedtimestamp updated - The duplicate is NOT stored
Facts confirmed multiple times become more confident over time while the store stays clean.
# First mention
mm.ingest("user1", "I prefer dark mode", "Noted!")
# -> Stored as fact: "User prefers dark mode" (confidence: 0.8)
# Second mention (different phrasing)
mm.ingest("user1", "Can you enable dark mode? I always use it", "Done!")
# -> Recognized as same fact, sighting_count bumped, confidence: 0.85
# -> NO duplicate created
Confidence Threshold
MemorySystem.search() and the MemoryManager.recall(), MemoryManager.recall_structured(), and MemoryManager.search() wrappers support a confidence_floor parameter. If nothing scores above the threshold, the system returns an empty list instead of guessing.
# Normal search - returns best matches regardless of confidence
results = mm.recall("user1", "quantum physics experiments")
# -> Might return vaguely related results
# With confidence floor - only returns if confident
results = mm.recall("user1", "quantum physics experiments", confidence_floor=0.5)
# -> Returns [] if nothing truly matches
The floor is calculated as entry.confidence * search_relevance_score. Both the memory's inherent confidence AND its match quality must be high enough.
Timestamps and Recency
Every memory gets a timestamp at ingest time. Timestamps drive three distinct systems:
- Decay - older memories have their relevance score gradually reduced based on a configurable half-life (default: 7 days). This keeps signal high as stores grow.
- Tiering - hot (0-3 days), warm (3-14 days), cold (14+ days). Tier determines load behavior.
- Recency retrieval -
recall_recent()returns memories sorted by time regardless of semantic relevance.
recall_recent() - Time-Based Retrieval
# Get all memories from the last 6 hours
recent = mem.recall_recent(hours=6)
# Filter by session
recent = mem.recall_recent(hours=12, session_id="session-abc")
# Filter by channel
recent = mem.recall_recent(hours=6, source_channel="discord:dev-ops")
# Filter by both session and channel
recent = mem.recall_recent(
hours=12,
session_id="session-abc",
source_channel="discord:general"
)
The session_id and source_channel filters were added in v2.3.1.
When to Use Recency vs Semantic Search
Use search() (semantic) when:
- You want the best answer to a question
- You don't know when the relevant memory was created
- You're doing cross-session recall of facts and decisions
Use recall_recent() (temporal) when:
- You want a chronological view of what happened
- You're summarizing recent activity for a user
- You're building a "what did I do in this session" feature
- You need events from a specific time window regardless of their semantic content
# Best answer to a question
mem.search("what database did we choose?", cross_session_recall="semantic")
# Chronological recap of recent work
mem.recall_recent(hours=24, session_id="my-session")
Cross-Session and Cross-Channel Recall
Every memory tracks where it came from: which session created it and which channel it arrived on. This lets you control how memories flow between contexts.
Cross-Session Recall
results = mem.search(
"what's the API key format?",
session_id="session-B",
cross_session_recall="semantic" # "all" | "semantic" | "none"
)
"all"- search everything regardless of session (default)"semantic"- other sessions' memories only surface if they're facts/decisions, not episodic chatter"none"- strict session isolation
Cross-Channel Recall
Memories record their source_channel at ingest time. You can filter by channel on both search and recency:
# Only search memories from a specific channel
results = mem.search("deployment plan", source_channel="discord:dev-ops")
# Recent memories from a specific channel
recent = mem.recall_recent(hours=6, source_channel="telegram:alerts")
# Recent memories from a specific session AND channel
recent = mem.recall_recent(hours=12, session_id="abc", source_channel="discord:general")
This is how an agent running on multiple platforms (Discord, Telegram, Slack, terminal) keeps channel context clean while still sharing important knowledge across all of them.
How It Works Together
Channel A (Discord #dev) Channel B (Telegram DM) Channel C (Slack #ops)
| | |
v v v
ingest with ingest with ingest with
source_channel= source_channel= source_channel=
"discord:dev" "telegram:dm" "slack:ops"
| | |
+------------+---------------+---------------------------+
|
Shared Memory Store
|
+------------+---------------+---------------------------+
| | |
search from any channel can find memories from any other channel
(unless filtered by source_channel or cross_session_recall mode)
The default is open: all memories are searchable from everywhere. Use source_channel and cross_session_recall to narrow scope when you need isolation.
Context Packets
A context packet is a structured bundle of relevant memories, environment details, and task-specific context - designed to be injected into a sub-agent's prompt at spawn time.
The Problem Context Packets Solve
Cold-spawned sub-agents start with zero context. They don't know what decisions have been made, what tools are available, what failed last time, or what the current environment looks like. Without context, they make confident mistakes based on incomplete information.
Context packets solve this by pulling the relevant subset of memory into a structured block that goes directly into the sub-agent's system prompt.
Building a Context Packet
from parsica_memory import MemorySystem
mem = MemorySystem("./workspace")
mem.load()
# Build a packet for a specific task
packet = mem.build_context_packet(
task="Deploy the auth service to staging",
tags=["auth", "deployment", "staging"],
environment={"venv": "venv-prod", "python": "3.11", "host": "staging.example.com"},
instructions=["Do not modify the database schema", "Use the existing Dockerfile"],
max_memories=10,
max_tokens=3000,
include_mistakes=True, # Include known pitfalls from mistake memories
)
# Inject into sub-agent prompt
prompt = f"""
{packet.render()}
YOUR TASK: {task_description}
"""
Output Formats
# Markdown (default) - readable, good for most LLM prompts
text = packet.render()
text = packet.render(fmt="markdown")
# XML - structured, easier to parse programmatically
text = packet.render(fmt="xml")
# JSON - serialize/deserialize across process boundaries
text = packet.render(fmt="json")
data = packet.to_dict()
packet2 = ContextPacket.from_dict(data)
Multi-Query Packets
When a task spans multiple topics, use build_multi() to pull context from several angles:
packet = mem.build_context_packet_multi(
task="Refactor the payment service",
queries=["payment service architecture", "stripe integration", "database schema"],
max_memories=15,
max_tokens=4000,
)
Token Budget and Trimming
Packets automatically trim to fit within max_tokens. Known pitfalls (from mistake memories) are never trimmed - they have highest priority. Regular memories are trimmed from the tail (lowest relevance first).
print(f"Packet: ~{packet.estimated_tokens} tokens, {len(packet)} memories")
# Manually trim if needed
trimmed = packet.trim(max_tokens=2000)
Known Pitfalls
When include_mistakes=True (default), the packet surfaces relevant mistake memories as explicit warnings:
### Known Pitfalls
- Known Pitfall: Last time this was attempted, the migration ran twice due to missing lock.
Correction: Check for existing lock file before running.
These come from memories ingested via mem.ingest_mistake() and are scored against the task query.
Cross-Process Context Sharing
Context packets serialize cleanly to JSON, making them useful for cross-process handoff:
# Process A: build and serialize
packet = mem.build_context_packet(task="analyze logs")
with open("context.json", "w") as f:
json.dump(packet.to_dict(), f)
# Process B: load and use
from parsica_memory.context_packet import ContextPacket
with open("context.json") as f:
packet = ContextPacket.from_dict(json.load(f))
prompt_injection = packet.render()
12-Layer Search Engine
Every query runs through 12 scoring layers:
- BM25+ TF-IDF - baseline relevance with delta floor
- Exact Phrase Bonus - verbatim matches score higher
- Field Boosting - tags, source, category weighted; enriched fields get 2-3x weight
- Rarity and Proper Noun Boost - rare terms and names surface; proper nouns 3x, quoted strings 3x, version numbers 2.5x, code identifiers 2.5x
- Positional Salience - intro/conclusion bias
- Semantic Expansion - PPMI co-occurrence query widening
- Intent Reranker - temporal, entity, howto detection
- Qualifier and Negation - "failed" != "successful"
- Clustering Boost - coherent result groups score higher
- Embedding Reranker - optional local embeddings (no API)
- Pseudo-Relevance Feedback - top results refine the query
- Anti-Adjacency Penalty - penalizes topic-match without answer-match; when the query asks a specific question ("what is", "who", "which"), results are checked for concrete identifiers (proper nouns, quoted strings, version numbers). Results that discuss the topic without providing a specific answer get a 0.6x penalty.
Memory Types
| Type | Decay Rate | Importance | Cross-Session | Use Case |
|---|---|---|---|---|
episodic |
Normal | 1x | Same session only | General events, chat |
semantic |
Normal | 1x | Yes - crosses sessions | Facts, decisions |
fact |
10x slower | High recall | Yes | Verified/decomposed knowledge |
mistake |
10x slower | 2x | Yes | Never forget failures |
preference |
3x slower | 1x | Yes | User/agent preferences |
procedure |
3x slower | 1x | Yes | How-to knowledge |
Memories are automatically classified at ingest time. No manual tagging needed.
Shared / Team Memory
Multiple agents can share a memory pool with role-based access control:
from parsica_memory import SharedPool, AgentRole, AgentConfig
pool = mem.enable_shared_pool(
pool_dir="./shared",
pool_name="project-alpha",
agent_id="worker-1",
role=AgentRole.WRITER
)
mem.shared_write("Research complete: competitor uses GraphQL", namespace="research")
Graph Intelligence
Automatic entity extraction and knowledge graph:
path = mem.entity_path("payment-service", "database", max_hops=3)
triples = mem.graph_search(subject="PostgreSQL", relation="used_by")
Tiered Storage
| Tier | Age | Behavior |
|---|---|---|
| Hot | 0-3 days | Always loaded, fastest access |
| Warm | 3-14 days | Loaded on-demand |
| Cold | 14+ days | Requires include_cold=True |
Input Gating
P0-P3 priority classification drops noise before it enters the store:
mem.ingest_with_gating("ok thanks", source="chat") # dropped (P3 - noise)
mem.ingest_with_gating("Production outage: auth down", source="incident") # stored (P0 - critical)
MCP Server
parsica-memory-mcp --workspace ./memory --agent-name my-agent
Works with Claude Desktop and any MCP-compatible client.
CLI
Parsica-Memory ships a CLI for workspace operations. It is intentionally narrow: the CLI is for bootstrapping, inspection, rebuilding, serving, and import/export. The day-to-day interface lives in the Python API (MemoryManager / MemorySystem).
Command Reference
python -m parsica_memory init
Interactive setup wizard (or headless with --headless).
python -m parsica_memory init --workspace ./memory --agent-name moro
python -m parsica_memory init --workspace ./memory --force
python -m parsica_memory init --workspace ./memory --headless # non-interactive
What it does:
- runs the interactive setup wizard (if not
--headless) - creates the workspace if missing
- writes initial
parsica_config.json - creates
.consolidated/and.shared_pools/ - smoke-tests
MemorySystemV4
python -m parsica_memory status
Load the workspace, print live stats, and run health checks.
python -m parsica_memory status --workspace ./memory
What it shows:
- total entries, hot/warm/cold tier counts
- WAL pending count
- enrichment count and lifetime enrichment cost
- graph nodes/edges
- cache size
- workspace health checks
python -m parsica_memory rebuild-graph
Rebuild graph intelligence from loaded entries. Useful after imports, large migrations, or structural changes.
python -m parsica_memory rebuild-graph --workspace ./memory
python -m parsica_memory serve
Start the MCP server over stdio.
python -m parsica_memory serve --workspace ./memory --agent-name moro
python -m parsica_memory export
Export all memories to a JSON file.
python -m parsica_memory export --workspace ./memory --output ./backup.json
python -m parsica_memory import
Import memories from another system. Supports auto-detection or explicit source format.
# Auto-detect source format
python -m parsica_memory import --workspace ./memory --input ./export.json
# Explicit source (mem0, Memvid, MEMO, Zep, LangMem, OpenAI, or generic formats)
python -m parsica_memory import --workspace ./memory --input ./mem0_export.json --from mem0
python -m parsica_memory import --workspace ./memory --input ./zep_export.json --from zep
python -m parsica_memory import --workspace ./memory --input ./langmem.json --from langmem
python -m parsica_memory import --workspace ./memory --input ./openai_threads.json --from openai
python -m parsica_memory import --workspace ./memory --input ./data.csv --from csv
python -m parsica_memory import --workspace ./memory --input ./notes/ --from markdown
# Dry run - show what would be imported without writing
python -m parsica_memory import --workspace ./memory --input ./export.json --dry-run
# Replace instead of merge
python -m parsica_memory import --workspace ./memory --input ./export.json --replace
Supported --from values: auto, mem0, memvid, memo, zep, langmem, openai, json, jsonl, sqlite, markdown, csv.
CLI Workflows
Fresh workspace
python -m parsica_memory init --workspace ./memory --agent-name my-agent
python -m parsica_memory status --workspace ./memory
After importing or copying a store
python -m parsica_memory status --workspace ./memory
python -m parsica_memory rebuild-graph --workspace ./memory
Migrate from another system
python -m parsica_memory import --workspace ./memory --input ./old_store.json --from mem0 --dry-run
python -m parsica_memory import --workspace ./memory --input ./old_store.json --from mem0
python -m parsica_memory rebuild-graph --workspace ./memory
Serving the store to another runtime
python -m parsica_memory serve --workspace ./memory --agent-name shared-memory
CLI vs Python API
Use the CLI when you want to:
- initialize a workspace
- inspect health/stats
- rebuild graph structures
- expose the store over MCP
- import from or export to another system
Use MemoryManager when you want:
- per-user isolation
- app-ready memory with minimal code
- optional auto-enrichment
Use MemorySystem when you want:
- low-level control over ingest/search/tiering/graph/maintenance
- explicit session/channel/recency/shared-pool behavior
Full API Reference
MemorySystem
from parsica_memory import MemorySystem
mem = MemorySystem(
workspace="./memory", # Required - directory path
agent_name="my-agent", # Required - scopes the store
half_life=7.0, # Decay half-life in days
enricher=None, # LLM enrichment callable
tiered_storage=True, # Hot/warm/cold tiers
graph_intelligence=True, # Entity extraction + graph
semantic_expansion=True, # PPMI query expansion
)
# Lifecycle
mem.load() # Load from disk
mem.save() # Save to disk
mem.flush() # WAL to shards
mem.close() # Flush + release
# Ingestion
mem.ingest(content, source=..., session_id=..., channel_id=..., source_channel=...)
mem.ingest_fact(content, source=...)
mem.ingest_mistake(what_happened=..., correction=..., root_cause=..., severity=...)
mem.ingest_preference(content, source=...)
mem.ingest_procedure(content, source=...)
mem.ingest_file(path, category=...)
mem.ingest_url(url, depth=2)
# Search
mem.search(query, limit=10, cross_session_recall="semantic", explain=False, confidence_floor=0.0)
mem.recent(limit=20)
mem.recall_recent(hours=24, session_id=None, source_channel=None)
# Enrichment
mem.re_enrich(batch_size=50, overwrite=False, decompose=False, progress_fn=None) # metadata only
mem.re_enrich(batch_size=50, overwrite=False, decompose=True, progress_fn=None) # metadata + fact decomposition
mem.re_decompose(batch_size=50, overwrite=False, progress_fn=None)
# Graph
mem.graph_search(subject=..., relation=..., obj=...)
mem.entity_path(source, target, max_hops=3)
# Context packets
mem.build_context_packet(task=..., tags=None, environment=None, instructions=None,
max_memories=15, max_tokens=4000, include_mistakes=True)
# Maintenance
mem.compact()
mem.consolidate()
mem.reindex()
mem.forget(topic=..., before_date=...)
mem.stats()
ParallelEnricher
from parsica_memory.parallel_enricher import ParallelEnricher
enricher = ParallelEnricher(
workspace="./memory", # Required - workspace directory
keys=[ # List of {key, model} dicts
{"key": "sk-ant-...", "model": "claude-haiku-4-5-20251001"},
{"key": "sk-proj-...", "model": "gpt-4o-mini"},
],
decompose=True, # Also decompose into atomic facts (default: False)
batch_size=50, # Entries per progress callback
overwrite=False, # Skip already-enriched entries
)
# Run enrichment (blocking, returns summary dict)
result = enricher.run(progress_fn=lambda done, total: print(f"{done}/{total}"))
# result = {"enriched": int, "workers": int, "errors": int, "elapsed_s": float}
Each key processes entries in round-robin sequence. decompose=True runs combined enrichment + decomposition in one LLM call per entry per worker.
Benchmarks
Measured on Mac Mini M4 (10-core, 32GB), Python 3.14, 9,896 live memories:
| Metric | Result |
|---|---|
| LOCOMO Accuracy | 78.0% |
| doc2query R@1 | 88.5% |
| p50 Search Latency | 21ms |
| Dependencies | 0 |
| Requires API Key | No (enrichment optional) |
| Requires Vector DB | No |
For full benchmark methodology and comparisons, see our benchmarks page.
Architecture
Parsica-Memory is a local-first memory engine. It combines persistence, recall, enrichment, recency, graph intelligence, sharding, and team memory into one package.
High-Level Component Map
parsica-memory/
├── Core Engine
│ ├── MemorySystem / MemoryManager
│ ├── MemoryEntry
│ ├── WAL + versioning + recovery
│ └── maintenance / compaction / consolidation
│
├── Retrieval
│ ├── 12-layer BM25+ search
│ ├── cross-session recall
│ ├── source_channel filtering
│ ├── recency recall
│ └── query expansion / reranking
│
├── Intelligence Layers
│ ├── LLM enrichment
│ ├── fact decomposition
│ ├── entity extraction
│ ├── graph intelligence
│ ├── cooccurrence / clustering
│ └── confidence / calibration
│
├── Storage System
│ ├── shard manager
│ ├── hot / warm / cold tiers
│ ├── audit + provenance
│ └── namespace support
│
└── Multi-Agent / Ops
├── SharedPool / team memory
├── context packets
├── CLI (init, status, export, import, serve)
├── MCP server
└── instrumentation / health
Memory Lifecycle
Input
|
v
Gating / classification
|
v
MemoryEntry creation
|- session_id
|- source_channel
|- source / category / type
+- provenance metadata
|
v
Optional LLM enrichment
|- tags
|- summary
|- keywords
|- search_queries
+- facts
|
v
WAL append
|
v
Shard persistence
|
v
Indexes / graph / derived layers
|
v
Available for:
|- semantic search
|- recency recall
|- cross-session recall
|- context packets
|- shared pools
+- export / serving
Session IDs, Channel IDs, and Recency
Parsica-Memory tracks where a memory came from, not just what it says.
session_ididentifies the conversation/runtime sessionsource_channelidentifies the surface/channel (Discord, Telegram, DM, thread, etc.)cross_session_recallcontrols whether other sessions can surface facts or only local memoriesrecall_recent()provides a recency-first retrieval path independent of semantic scoring
Timestamps are set automatically at ingest time and are used for:
- decay scoring (half-life applied to relevance scores)
- tier assignment (hot/warm/cold based on age)
- recency retrieval (sorted by
created_atdescending) - session reconstruction (ordering events within a session)
Sharding and Tiering
Parsica-Memory shards memory for scale and retrieval efficiency, and classifies shards into temperature tiers:
| Tier | Age | Behavior |
|---|---|---|
| Hot | newest | eagerly loaded, fastest recall |
| Warm | medium age | available on demand |
| Cold | oldest | optional / lazy / archival-friendly |
This gives you faster search over large stores, easier archival/backup, better operational safety, and lower memory overhead for long-lived agents.
Retroactive Enrichment
If you migrated an old store, copied shards, or started without enrichment, you can backfill later:
mm.enrich_all("user123")
# or low-level
mem.re_enrich(batch_size=50, overwrite=False)
mem.re_decompose(batch_size=50, overwrite=False)
Enrichment is best from day one, but not locked to day one.
Changelog
v2.6.0 (2026-03-18)
- Setup wizard with guided mode: Manager vs System choice, import flow (6 source options), enrichment setup with key detection, model selection with cost/quality tags, retroactive enrichment with time estimates and multi-key option, background processing with progress display
re_enrich(decompose=True)- combined enrichment + fact decomposition in one pass (cheaper than two separate calls)- Multi-key enrichment via
ParallelEnricher- distributes work across API keys/models in round-robin - Import CLI with converters for mem0, Memvid, MEMO, Zep, LangMem, OpenAI threads export, plus generic json, jsonl, sqlite, markdown, csv
- Export CLI command (
python -m parsica_memory export) - Background enrichment with live progress display
- Rate limit handling with backoff across all enrichment paths
v3.1.0 (2026-03-26) — Quality Levers + Transparency
- Scoring transparency — every search result now includes a per-component score breakdown (
final_score,phrase_boost,tag_boost,category_boost,enriched_boost,doc2query_boost,structured_boost,anti_adjacency). All values are numeric multipliers, not booleans. parsica explain <query>CLI — run a search and see every scoring decision with provenance (source, timestamp, session, channel).parsica enrichCLI — batch enrichment with--keys key1,key2,key3for multi-key round-robin across providers.--coverageshows enrichment percentage./search <query>in OpenClaw — search results with provenance (source, timestamp, channel, session, score breakdown) formatted for Discord/chat./enrichment on|off|status— toggle enrichment live from chat without editing config. Enrichment is on by default./decompose on|off|status— toggle fact decomposition live from chat. Decomposition is off by default.- LEA (Lexical Entity Amplification) — entity detection with position bug fix (fallback position=-1 prevents false entity boosting).
- Heuristic gating — P0-P3 input classification filters noise at ingest time (+33 pts R@1 at zero API cost).
- Toggle wiring —
/enrichmentand/decomposetoggles now correctly gate ingest behavior in real-time. re_enrich()ungated — batch re-enrichment always works regardless of live toggle state.- PDF page_number fix — page 0 (zero-indexed) no longer silently drops its tag.
- README corrections — hook name unified to
before_agent_start, enrichment language corrected from "parallel" to "round-robin". - 1307 tests passing — 17 new v3.1 tests + decomposition test compatibility.
v2.3.1 (2026-03-17)
- Native channel/session/recency support -
search()now supportssource_channelfiltering;recall_recent()supports bothsession_idandsource_channelfilters. - Provider-agnostic enrichment -
smart_enricher()auto-detects Anthropic, OpenAI, Google, or OpenAI-compatible providers from key prefix.auto_enricher()now supportsPARSICA_LLM_API_KEYas a provider-agnostic entry point (legacyANTARIS_LLM_API_KEYstill works). - Bootstrap / health checks - startup/bootstrap integrity checks retained and documented more clearly.
v2.1.2 and earlier
See CHANGELOG.md for earlier release notes.
Relationship to the Antaris ecosystem
parsica-memory is the standalone memory package from Antaris Analytics. It also powers broader Antaris agent infrastructure, but it is documented and shipped here as an independent package with its own install surface.
License
Apache 2.0
Links
- PyPI: https://pypi.org/project/parsica-memory/
- GitHub: https://github.com/Antaris-Analytics-LLC/Parsica-Memory
- Antaris Analytics: https://antarisanalytics.ai
Built by Antaris Analytics LLC. Zero dependencies. Ships complete.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file parsica_memory-3.3.2.tar.gz.
File metadata
- Download URL: parsica_memory-3.3.2.tar.gz
- Upload date:
- Size: 2.1 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dc546172dc5180ecc8864840f941224956cc3a17999a1e5484fe9eb32d545472
|
|
| MD5 |
a31d5d56d7db92d7c5ebfcd8c6ff1c30
|
|
| BLAKE2b-256 |
46e5dce803602ef6437285c6603005d1eef9cc7a19e7b501e365e5d89c29225d
|
File details
Details for the file parsica_memory-3.3.2-py3-none-any.whl.
File metadata
- Download URL: parsica_memory-3.3.2-py3-none-any.whl
- Upload date:
- Size: 1.8 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ab7ef85e063d75dba64e189a40fdb76b6686f8cfe70f89636a609dfe3dfe58f6
|
|
| MD5 |
13e59475a454550befcec3fd703c294c
|
|
| BLAKE2b-256 |
4ecdb3909e01f424b5c8b17513c5a2e24add06284d37233e4365df41a43ae6b8
|