A universal memory middleware for LLM agents — file-based, scope-aware, BYO LLM.
Project description
bobo-memory
Universal memory middleware for LLM agents — file-based, scope-aware, BYO LLM.
bobo-memory gives any Python LLM project a persistent, visible, governable memory system without requiring a database, a vector store, or an embedded LLM. The entire memory lives as Markdown files you can open in Obsidian.
Getting started: follow the tutorial (Chinese) from init through agent integration.
For coding agents / integrators: integration guide · one-page brief · multi-user Web / SaaS
Core philosophy
| Principle | Implementation |
|---|---|
| Files are memory | All memories are *.md files in .bobo/memory/ |
| Index-first prompting | MEMORY.md acts as the prompt entry-point (hard-capped at 200 lines / 25 KB) |
| BYO LLM | Zero LLM calls — the module exposes rules + tools, the agent decides what to write |
| Four engineering rails | Every write: policy → guard → atomic_write → audit |
| Scope isolation | user / project / local scopes for agent memory |
| Source citation | Memories trace back to raw/ source files |
| Distributable snapshots | Agent memory packaged as a versioned asset |
Installation
pip install bobo-memory
Optional extras:
pip install "bobo-memory[mcp]" # MCP stdio server for Claude Code / IDE agents
pip install "bobo-memory[watch]" # directory watching: watchdog
pip install "bobo-memory[viewer]" # web UI + read-only REST API: fastapi + uvicorn
pip install "bobo-memory[embedding]" # vector recall: sentence-transformers
Quick start
1. Initialise
cd your-project/
bobo-memory init --agent-type my-agent --scope project
This creates .bobo/config.yaml and the memory directory structure.
2. Integrate with your agent
from bobo_memory import MemoryClient
mem = MemoryClient(
project_root=".",
agent_type="my-agent",
scope="project",
)
# Inject memory context into system prompt
system_prompt = mem.build_system_prompt("You are a helpful assistant.")
# Pass tools to your LLM call
tools = mem.to_openai_tools() # or to_anthropic_tools() / to_langchain_tools()
# Dispatch tool calls coming back from the LLM
result = mem.dispatch_tool_call(tool_name, arguments)
3. Receive external information streams
# Ingest a file (lands in raw/ + staging/ — no LLM called)
mem.ingest(adapter="markdown", path="article.md")
# Agent picks it up next turn (task is leased, not deleted)
result = mem.dispatch_tool_call("ingest_next", {})
# → returns raw content + instruction to integrate into wiki/memory
# Confirm when done — unconfirmed tasks are retried after the lease expires
mem.dispatch_tool_call("ingest_done", {"source_id": result["task"]["source_id"]})
4. Automatic memory capture (zero extra LLM calls)
memory_nudge() scans recent messages with pure rules (no LLM) for
corrections, preferences, decisions and explicit "remember this" phrases.
When something fires, it returns a one-line reminder to append to your NEXT
request's system prompt — the main model then saves the memory inside its
normal turn, so no additional API call is ever made:
nudge = mem.memory_nudge(messages) # "" most of the time
if nudge:
system_prompt += "\n\n" + nudge
# Optional dedup check before saving (BM25, no LLM):
similar = mem.find_similar("integration tests must pass before deploy")
Memory layers
| Layer | What it stores | Scope |
|---|---|---|
agent |
Agent-type specific long-term memory | user / project / local |
auto |
Project-wide persistent facts | project |
wiki |
LLM-maintained knowledge base (entities, concepts, sources) | project |
session |
Current-session summary for context compaction | ephemeral |
team |
Git-tracked shared team knowledge | repo |
Available tools (exposed to the LLM)
| Tool | Description |
|---|---|
memory_save |
Save a new memory file + update index |
memory_update |
Update an existing memory file |
memory_list |
Read the MEMORY.md index |
memory_read |
Read a specific memory file |
memory_recall |
BM25 search across memory layers → ContextPack |
memory_forget |
Soft-delete (move to .trash/) |
memory_restore |
Restore from trash |
memory_purge |
Permanent delete (audit log kept) |
wiki_link |
Bidirectional cross-reference between wiki pages |
wiki_log |
Append to wiki/log.md timeline |
ingest_next |
Lease the next task from staging/pending.json (retried if not confirmed) |
ingest_done |
Confirm an ingest task is integrated — removes it from staging |
Policy configuration (.bobo/config.yaml)
policy:
write_mode: direct # "direct" or "proposal"
layers:
wiki:
require_citation: true # wiki pages must cite raw sources
write_mode: proposal # wiki writes go to proposals/ for review
team:
require_secret_scan: true
forbidden_patterns: # applied globally on every write
- "api[_-]?key\\s*=\\s*['\"]?[A-Za-z0-9]{20,}"
- "-----BEGIN (RSA |EC |DSA )?PRIVATE KEY-----"
max_file_size_kb: 100
trash:
retention_days: 30
allow_purge: true
Session memory & context compaction
# Check if it's time to extract a session summary
if mem.session.should_extract(messages):
extract_prompt = mem.session.build_extract_prompt(messages)
# Fork a sandboxed subagent with this prompt
# Context compaction (no LLM call if session memory exists)
if mem.compact.should_compact(messages, token_budget=180_000):
summary = mem.compact.compact_with_session_memory(messages)
keep = mem.compact.calculate_keep_index(messages)
keep = mem.compact.adjust_index_to_preserve_invariants(messages, keep)
new_messages = [
messages[0], # keep system
mem.compact.create_compact_boundary(summary), # boundary msg
*messages[keep:], # tail
*mem.compact.build_post_compact_attachments(tool_specs=mem.tool_specs()),
]
Snapshots — distributable agent memory
# Export current agent memory as a snapshot (ship with your project)
bobo-memory snapshot save --scope project
# Apply a snapshot to a fresh environment
bobo-memory snapshot apply --scope project
# Check snapshot sync status
bobo-memory snapshot status
Proposal queue
When write_mode: proposal, writes go to .bobo/proposals/ for review:
bobo-memory proposal list
bobo-memory proposal accept --id <id>
bobo-memory proposal reject --id <id>
CLI reference
bobo-memory init [--agent-type NAME] [--scope SCOPE]
bobo-memory status
bobo-memory audit [--date YYYY-MM-DD] [--limit N]
bobo-memory lint
bobo-memory ingest --file PATH [--adapter markdown]
bobo-memory proposal list / accept --id X / reject --id X
bobo-memory snapshot save / apply / status
bobo-memory serve [--port 8765]
bobo-memory mcp [--agent-type NAME] [--scope SCOPE]
Disk layout
.bobo/
├── config.yaml
├── memory/
│ ├── auto/ MEMORY.md + *.md (Auto Memory)
│ ├── agent/<type>/ project/ local/ user/ (Agent Memory)
│ ├── session/ <session_id>.md (Session summaries)
│ ├── team/ MEMORY.md + *.md (Team Memory)
│ └── wiki/ index.md log.md entities/ concepts/ sources/
├── raw/ <YYYY-MM-DD>/<source_id>.md (immutable originals)
├── staging/ pending.json (ingest queue)
├── proposals/ <layer>/<topic>.<uuid>.md
├── audit/ audit-<YYYY-MM-DD>.jsonl
└── snapshots/ <agent_type>/snapshot.json + *.md
~/.bobo/ (user-scoped agent memory, cross-project)
MCP server
Expose all 12 memory tools to any MCP host (Claude Code, IDE agents, …):
pip install "bobo-memory[mcp]"
bobo-memory mcp --agent-type my-agent # stdio transport
Claude Code .mcp.json example:
{
"mcpServers": {
"bobo-memory": { "command": "bobo-memory", "args": ["mcp"] }
}
}
Every MCP tool call runs through the same policy → guard → atomic → audit pipeline.
Read-only REST API (viewer)
bobo-memory serve (requires [viewer]) binds to 127.0.0.1 and exposes:
| Endpoint | Returns |
|---|---|
GET /status / GET /storage |
system status / disk usage |
GET /layers |
enabled layers + directories |
GET /memories/{layer} |
the layer's MEMORY.md index |
GET /memory?file=<rel-path> |
one memory file (guard-checked) |
GET /recall?query=&k= |
BM25 recall as a ContextPack |
GET /audit / /lint / /proposals |
audit log / wiki health / proposal queue |
Framework integrations
Works with any LLM framework that supports function calling:
# OpenAI
tools = mem.to_openai_tools()
# Anthropic
tools = mem.to_anthropic_tools()
# LangChain
tools = mem.to_langchain_tools()
# Any custom framework
for spec in mem.tool_specs():
print(spec.name, spec.parameters)
Requirements
- Python 3.10+
pydantic >= 2.0pyyaml >= 6.0rank-bm25 >= 0.2.2filelock >= 3.12watchdog >= 4.0(optional,pip install "bobo-memory[watch]"— only needed forwatch_directory())
Development
git clone https://github.com/WinWin405/bobo-memory.git
cd bobo-memory
python -m venv .venv
# Windows: .venv\Scripts\activate
# macOS/Linux: source .venv/bin/activate
pip install -e ".[viewer]"
pytest bobo_memory/tests/
Contributing
See CONTRIBUTING.md. Issues and pull requests are welcome on GitHub.
License
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 bobo_memory-0.2.0.tar.gz.
File metadata
- Download URL: bobo_memory-0.2.0.tar.gz
- Upload date:
- Size: 85.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6c4ccabcd90c335f550d700500454f81d031e0fa6b7918f45c502c2f290307a8
|
|
| MD5 |
6750b7ebfbd66f5419feb74cd3de4dc7
|
|
| BLAKE2b-256 |
cd16809b0234a1d24f04c0e290a1a00589ab71a570b8b1ebaccbb2311398a2f4
|
Provenance
The following attestation bundles were made for bobo_memory-0.2.0.tar.gz:
Publisher:
release.yml on WinWin405/bobo-memory
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
bobo_memory-0.2.0.tar.gz -
Subject digest:
6c4ccabcd90c335f550d700500454f81d031e0fa6b7918f45c502c2f290307a8 - Sigstore transparency entry: 2071033066
- Sigstore integration time:
-
Permalink:
WinWin405/bobo-memory@14db24eebcf29bf09c055fb9351b2ea25b45b777 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/WinWin405
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@14db24eebcf29bf09c055fb9351b2ea25b45b777 -
Trigger Event:
push
-
Statement type:
File details
Details for the file bobo_memory-0.2.0-py3-none-any.whl.
File metadata
- Download URL: bobo_memory-0.2.0-py3-none-any.whl
- Upload date:
- Size: 104.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
573465ed95b0a9e9f10d995030083a4d04599d7c57c01491b3a54ee17f8fc0f9
|
|
| MD5 |
78def4ba1d09b30ed900c651e25743da
|
|
| BLAKE2b-256 |
15d7810af83e73c2dc6cf3ce02b5eb657c66957607726fc06103bc5052b96665
|
Provenance
The following attestation bundles were made for bobo_memory-0.2.0-py3-none-any.whl:
Publisher:
release.yml on WinWin405/bobo-memory
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
bobo_memory-0.2.0-py3-none-any.whl -
Subject digest:
573465ed95b0a9e9f10d995030083a4d04599d7c57c01491b3a54ee17f8fc0f9 - Sigstore transparency entry: 2071033071
- Sigstore integration time:
-
Permalink:
WinWin405/bobo-memory@14db24eebcf29bf09c055fb9351b2ea25b45b777 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/WinWin405
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@14db24eebcf29bf09c055fb9351b2ea25b45b777 -
Trigger Event:
push
-
Statement type: