Basic Memory is a local-first knowledge management system built on the Model Context Protocol (MCP). It enables bidirectional communication between LLMs (like Claude) and markdown files, creating a personal knowledge graph that can be traversed using links between documents.
See the README.md file for a project overview.
- Install:
just installorpip install -e ".[dev]" - Run all tests (SQLite + Postgres):
just test - Run all tests against SQLite:
just test-sqlite - Run all tests against Postgres:
just test-postgres(uses testcontainers) - Run unit tests (SQLite):
just test-unit-sqlite - Run unit tests (Postgres):
just test-unit-postgres - Run integration tests (SQLite):
just test-int-sqlite - Run integration tests (Postgres):
just test-int-postgres - Run impacted tests:
just testmon(pytest-testmon) - Run MCP smoke test:
just test-smoke - Fast local loop:
just fast-check - Local consistency check:
just doctor - Generate HTML coverage:
just coverage - Single test:
pytest tests/path/to/test_file.py::test_function_name - Run benchmarks:
pytest test-int/test_sync_performance_benchmark.py -v -m "benchmark and not slow" - Lint:
just lintorruff check . --fix - Type check:
just typecheckoruv run pyright - Type check (supplemental):
just typecheck-tyoruv run ty check src/ - Format:
just formatoruv run ruff format . - Run all code checks:
just check(runs lint, format, typecheck, test) - Create db migration:
just migration "Your migration message" - Run development MCP Inspector:
just run-inspector
Note: Project requires Python 3.12+ (uses type parameter syntax and type aliases introduced in 3.12)
Postgres Testing: Uses testcontainers which automatically spins up a Postgres instance in Docker. No manual database setup required - just have Docker running.
Doctor Note: just doctor runs with a temporary HOME/config so it won't touch your local Basic Memory settings. It leaves temp dirs in /tmp (safe to ignore or remove).
Testmon Note: When no files have changed, just testmon may collect 0 tests. That's expected and means no impacted tests were detected.
- Code: make changes.
- Test:
just fast-check(lint/format/typecheck + impacted tests + MCP smoke). - Verify:
just doctor(end-to-end file ↔ DB loop in a temp project). - Full gate (when needed):
just testorjust checkfor SQLite + Postgres.
If testmon is “cold,” the first run may be long. Subsequent runs get much faster.
tests/- Unit tests for individual components (mocked, fast)test-int/- Integration tests for real-world scenarios (no mocks, realistic)- Both directories are covered by unified coverage reporting
- Benchmark tests in
test-int/are marked with@pytest.mark.benchmark - Slow tests are marked with
@pytest.mark.slow - Smoke tests are marked with
@pytest.mark.smoke
- Line length: 100 characters max
- Python 3.12+ with full type annotations (uses type parameters and type aliases)
- Format with ruff (consistent styling)
- Import order: standard lib, third-party, local imports
- Naming: snake_case for functions/variables, PascalCase for classes
- Prefer async patterns with SQLAlchemy 2.0
- Use Pydantic v2 for data validation and schemas
- CLI uses Typer for command structure
- API uses FastAPI for endpoints
- Follow the repository pattern for data access
- Tools communicate to api routers via the httpx ASGI client (in process)
- Full file read before edits: Before editing any file, read it in full first to ensure complete context; partial reads lead to corrupted edits
- Minimize diffs: Prefer the smallest change that satisfies the request. Avoid unrelated refactors or style rewrites unless necessary for correctness
- No speculative getattr: Never use
getattr(obj, "attr", default)when unsure about attribute names. Check the class definition or source code first - Fail fast: Write code with fail-fast logic by default. Do not swallow exceptions with errors or warnings
- No fallback logic: Do not add fallback logic unless explicitly told to and agreed with the user
- No guessing: Do not say "The issue is..." before you actually know what the issue is. Investigate first.
Code should tell a story. Comments must explain the "why" and narrative flow, not just the "what".
Section Headers: For files with multiple phases of logic, add section headers so the control flow reads like chapters:
# --- Authentication ---
# ... auth logic ...
# --- Data Validation ---
# ... validation logic ...
# --- Business Logic ---
# ... core logic ...Decision Point Comments: For conditionals that materially change behavior (gates, fallbacks, retries, feature flags), add comments with:
- Trigger: what condition causes this branch
- Why: the rationale (cost, correctness, UX, determinism)
- Outcome: what changes downstream
# Trigger: project has no active sync watcher
# Why: avoid duplicate file system watchers consuming resources
# Outcome: starts new watcher, registers in active_watchers dict
if project_id not in active_watchers:
start_watcher(project_id)Constraint Comments: If code exists because of a constraint (async requirements, rate limits, schema compatibility), explain the constraint near the code:
# SQLite requires WAL mode for concurrent read/write access
connection.execute("PRAGMA journal_mode=WAL")What NOT to Comment: Avoid comments that restate obvious code:
# Bad - restates code
counter += 1 # increment counter
# Good - explains why
counter += 1 # track retries for backoff calculationSee docs/ARCHITECTURE.md for detailed architecture documentation.
Directory Structure:
/alembic- Alembic db migrations/api- FastAPI REST endpoints +container.pycomposition root/cli- Typer CLI +container.pycomposition root/deps- Feature-scoped FastAPI dependencies (config, db, projects, repositories, services, importers)/importers- Import functionality for Claude, ChatGPT, and other sources/markdown- Markdown parsing and processing/mcp- MCP server +container.pycomposition root +clients/typed API clients/models- SQLAlchemy ORM models/repository- Data access layer/schemas- Pydantic models for validation/services- Business logic layer/sync- File synchronization services +coordinator.pyfor lifecycle management
Composition Roots: Each entrypoint (API, MCP, CLI) has a composition root that:
- Reads
ConfigManager(the only place that reads global config) - Resolves runtime mode via
RuntimeModeenum (TEST > CLOUD > LOCAL) - Provides dependencies to downstream code explicitly
Typed API Clients (MCP):
MCP tools use typed clients in mcp/clients/ to communicate with the API:
KnowledgeClient- Entity CRUD operationsSearchClient- Search operationsMemoryClient- Context buildingDirectoryClient- Directory listingResourceClient- Resource readingProjectClient- Project management
Flow: MCP Tool → Typed Client → HTTP API → Router → Service → Repository
- MCP tools are defined in src/basic_memory/mcp/tools/
- MCP prompts are defined in src/basic_memory/mcp/prompts/
- MCP tools should be atomic, composable operations
- Use
textwrap.dedent()for multi-line string formatting in prompts and tools - MCP Prompts are used to invoke tools and format content with instructions for an LLM
- Schema changes require Alembic migrations
- SQLite is used for indexing and full text search, files are source of truth
- Testing uses pytest with asyncio support (strict mode)
- Unit tests (
tests/) use mocks when necessary; integration tests (test-int/) use real implementations - By default, tests run against SQLite (fast, no Docker needed)
- Set
BASIC_MEMORY_TEST_POSTGRES=1to run against Postgres (uses testcontainers - Docker required) - Each test runs in a standalone environment with isolated database and tmp_path directory
- CI runs SQLite and Postgres tests in parallel for faster feedback
- Performance benchmarks are in
test-int/test_sync_performance_benchmark.py - Use pytest markers:
@pytest.mark.benchmarkfor benchmarks,@pytest.mark.slowfor slow tests - Coverage must stay at 100%: Write tests for new code. Only use
# pragma: no coverwhen tests would require excessive mocking (e.g., TYPE_CHECKING blocks, error handlers that need failure injection, runtime-mode-dependent code paths)
MCP tools use get_project_client() for per-project routing:
from basic_memory.mcp.project_context import get_project_client
@mcp.tool()
async def my_tool(project: str | None = None, context: Context | None = None):
async with get_project_client(project, context) as (client, active_project):
# client is routed based on project's mode (local ASGI or cloud HTTP)
response = await call_get(client, "/path")
return responseCLI commands and non-project-scoped code use get_client() directly:
from basic_memory.mcp.async_client import get_client
async def my_cli_command():
async with get_client() as client:
response = await call_get(client, "/path")
return response
# Per-project routing (when project name is known):
async with get_client(project_name="research") as client:
...Do NOT use:
- ❌
from basic_memory.mcp.async_client import client(deprecated module-level client) - ❌ Manual auth header management
- ❌
inject_auth_header()(deleted) - ❌ Separate
get_client()+get_active_project()in MCP tools (useget_project_client()instead)
Key principles:
- Auth happens at client creation, not per-request
- Proper resource management via context managers
- Per-project routing: each project can be LOCAL or CLOUD independently
- Cloud projects use API key (
cloud_api_keyin config) as Bearer token - Routing priority: factory injection > force-local > per-project cloud > global cloud > local ASGI
- Factory pattern enables dependency injection for cloud consolidation
For cloud app integration:
from basic_memory.mcp import async_client
# Set custom factory before importing tools
async_client.set_client_factory(your_custom_factory)See SPEC-16 for full context manager refactor details.
- Entity: Any concept, document, or idea represented as a markdown file
- Observation: A categorized fact about an entity (
- [category] content) - Relation: A directional link between entities (
- relation_type [[Target]]) - Frontmatter: YAML metadata at the top of markdown files
- Knowledge representation follows precise markdown format:
- Observations with [category] prefixes
- Relations with WikiLinks [[Entity]]
- Frontmatter with metadata
Local Commands:
- Check sync status:
basic-memory status - Doctor check (file <-> DB loop):
basic-memory doctor - Import from Claude:
basic-memory import claude conversations - Import from ChatGPT:
basic-memory import chatgpt - Import from Memory JSON:
basic-memory import memory-json - Tool access:
basic-memory tool(provides CLI access to MCP tools)- Continue:
basic-memory tool continue-conversation --topic="search"
- Continue:
Project Management:
- List projects:
basic-memory project list - Add project:
basic-memory project add "name" ~/path - Project info:
basic-memory project info - Set cloud mode:
basic-memory project set-cloud "name" - Set local mode:
basic-memory project set-local "name" - One-way sync (local -> cloud):
basic-memory project sync - Bidirectional sync:
basic-memory project bisync - Integrity check:
basic-memory project check
Cloud Commands (requires subscription):
- Authenticate (global):
basic-memory cloud login - Logout (global):
basic-memory cloud logout - Check cloud status:
basic-memory cloud status - Setup cloud sync:
basic-memory cloud setup - Save API key:
basic-memory cloud set-key bmc_... - Create API key:
basic-memory cloud create-key "name" - Manage snapshots:
basic-memory cloud snapshot [create|list|delete|show|browse] - Restore from snapshot:
basic-memory cloud restore <path> --snapshot <id>
-
Basic Memory exposes these MCP tools to LLMs:
Content Management:
write_note(title, content, directory, tags)- Create/update markdown notes with semantic observations and relationsread_note(identifier, page, page_size)- Read notes by title, permalink, or memory:// URL with knowledge graph awarenessread_content(path)- Read raw file content (text, images, binaries) without knowledge graph processingview_note(identifier, page, page_size)- View notes as formatted artifacts for better readabilityedit_note(identifier, operation, content)- Edit notes incrementally (append, prepend, find/replace, replace_section)move_note(identifier, destination_path, is_directory)- Move notes or directories to new locations, updating database and maintaining linksdelete_note(identifier, is_directory)- Delete notes or directories from the knowledge base
Knowledge Graph Navigation:
build_context(url, depth, timeframe)- Navigate the knowledge graph via memory:// URLs for conversation continuityrecent_activity(type, depth, timeframe)- Get recently updated information with specified timeframe (e.g., "1d", "1 week")list_directory(dir_name, depth, file_name_glob)- Browse directory contents with filtering and depth control
Search & Discovery:
search_notes(query, page, page_size, search_type, types, entity_types, after_date)- Full-text search across all content with advanced filtering options
Project Management:
list_memory_projects()- List all available projects with their statuscreate_memory_project(project_name, project_path, set_default)- Create new Basic Memory projectsdelete_project(project_name)- Delete a project from configuration
Visualization:
canvas(nodes, edges, title, directory)- Generate Obsidian canvas files for knowledge graph visualization
ChatGPT-Compatible Tools:
search(query)- Search across knowledge base (OpenAI actions compatible)fetch(id)- Fetch full content of a search result document
-
MCP Prompts for better AI interaction:
ai_assistant_guide()- Guidance on effectively using Basic Memory tools for AI assistantscontinue_conversation(topic, timeframe)- Continue previous conversations with relevant historical contextsearch(query, after_date)- Search with detailed, formatted results for better context understandingrecent_activity(timeframe)- View recently changed items with formatted output
Basic Memory now supports cloud synchronization and storage (requires active subscription):
Authentication:
- JWT-based authentication with subscription validation
- Secure session management with token refresh
- Support for multiple cloud projects
Bidirectional Sync:
- rclone bisync integration for two-way synchronization
- Conflict resolution and integrity verification
- Real-time sync with change detection
- Mount/unmount cloud storage for direct file access
Cloud Project Management:
- Create and manage projects in the cloud
- Toggle between local and cloud modes
- Per-project sync configuration
- Subscription-based access control
Security & Performance:
- Removed .env file loading for improved security
- .gitignore integration (respects gitignored files)
- WAL mode for SQLite performance
- Background relation resolution (non-blocking startup)
- API performance optimizations (SPEC-11)
Per-Project Cloud Routing:
Individual projects can be routed through the cloud while others stay local, using an API key:
# Save API key and set project to cloud mode
basic-memory cloud set-key bmc_abc123...
basic-memory project set-cloud research # route through cloud
basic-memory project set-local research # revert to localMCP tools use get_project_client() which automatically routes based on the project's mode. Cloud projects use the cloud_api_key from config as Bearer token.
CLI Routing Flags (Global Cloud Mode):
When global cloud mode is enabled, CLI commands route to the cloud API by default. Use --local and --cloud flags to override:
# Force local routing (ignore cloud mode)
basic-memory status --local
basic-memory project list --local
# Force cloud routing (when cloud mode is disabled)
basic-memory status --cloud
basic-memory project info my-project --cloudKey behaviors:
- The local MCP server (
basic-memory mcp) automatically uses local routing - This allows simultaneous use of local Claude Desktop and cloud-based clients
- Some commands (like
project default,project sync-config,project move) require--localin cloud mode since they modify local configuration - Environment variable
BASIC_MEMORY_FORCE_LOCAL=trueforces local routing globally - Per-project cloud routing via API key works independently of global cloud mode
Basic Memory emerged from and enables a new kind of development process that combines human and AI capabilities. Instead of using AI just for code generation, we've developed a true collaborative workflow:
- AI (LLM) writes initial implementation based on specifications and context
- Human reviews, runs tests, and commits code with any necessary adjustments
- Knowledge persists across conversations using Basic Memory's knowledge graph
- Development continues seamlessly across different AI sessions with consistent context
- Results improve through iterative collaboration and shared understanding
This approach has allowed us to tackle more complex challenges and build a more robust system than either humans or AI could achieve independently.
Problem-Solving Guidance:
- If a solution isn't working after reasonable effort, suggest alternative approaches
- Don't persist with a problematic library or pattern when better alternatives exist
- Example: When py-pglite caused cascading test failures, switching to testcontainers-postgres was the right call
Basic Memory has taken AI-Human collaboration to the next level by integrating Claude directly into the development workflow through GitHub:
Using the GitHub Model Context Protocol server, Claude can now:
-
Repository Management:
- View repository files and structure
- Read file contents
- Create new branches
- Create and update files
-
Issue Management:
- Create new issues
- Comment on existing issues
- Close and update issues
- Search across issues
-
Pull Request Workflow:
- Create pull requests
- Review code changes
- Add comments to PRs
This integration enables Claude to participate as a full team member in the development process, not just as a code generation tool. Claude's GitHub account (bm-claudeai) is a member of the Basic Machines organization with direct contributor access to the codebase.
With GitHub integration, the development workflow includes:
- Direct code review - Claude can analyze PRs and provide detailed feedback
- Contribution tracking - All of Claude's contributions are properly attributed in the Git history
- Branch management - Claude can create feature branches for implementations
- Documentation maintenance - Claude can keep documentation updated as the code evolves
- Code Commits: ALWAYS sign off commits with
git commit -s - Pull Request Titles: PR titles must follow the semantic format enforced by
.github/workflows/pr-title.yml:type(scope): summary- Allowed types:
feat,fix,chore,docs,style,refactor,perf,test,build,ci - Allowed scopes:
core,cli,api,mcp,sync,ui,deps,installer - Example:
fix(cli): propagate cloud workspace routing
- Allowed types:
This level of integration represents a new paradigm in AI-human collaboration, where the AI assistant becomes a full-fledged team member rather than just a tool for generating code snippets.