Compare commits

...

8 Commits

Author SHA1 Message Date
phernandez 616c1f0710 feat: switch from Docker Hub to GitHub Container Registry
🏴 Fighting the power! No more $15/month Docker Hub fees.

- Use ghcr.io/basicmachines-co/basic-memory for container images
- Native GitHub integration with GITHUB_TOKEN (no external secrets)
- Update all documentation and examples to use GHCR
- Remove Docker Hub description update step (not needed for GHCR)
- Completely free solution for public repositories

Docker users can now:
docker pull ghcr.io/basicmachines-co/basic-memory:latest
2025-06-20 15:57:49 -05:00
Paul Hernandez 74847cc380 feat: implement Docker CI workflow for automated image publishing (#159)
Co-authored-by: Claude <noreply@anthropic.com>
2025-06-20 15:42:55 -05:00
phernandez d3b6c85184 docs: add v0.13.8 changelog entry
Documents recent fixes and features including:
- Docker container support with volume mounting
- #151: Reset command project configuration fix
- #148: MCP/CLI project state consistency fix
- FastMCP compatibility improvements
- Comprehensive integration testing

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-19 22:13:32 -05:00
Paul Hernandez af44941d5a fix: reset command now clears project configuration (#152)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-06-19 21:55:50 -05:00
Paul Hernandez 35e4f73ae8 fix: resolve project state inconsistency between MCP and CLI (#149)
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com>
2025-06-19 21:24:51 -05:00
Drew Cain 7be001ca68 fix: fastmcp deprecation warning (#150)
Signed-off-by: Drew Cain <groksrc@gmail.com>
Signed-off-by: Paul Hernandez <60959+phernandez@users.noreply.github.com>
Co-authored-by: Paul Hernandez <60959+phernandez@users.noreply.github.com>
2025-06-19 19:59:17 -05:00
Paul Hernandez 3269a2f33a feat: add Docker container support with volume mounting (#131)
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: phernandez <phernandez@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-06-19 19:57:30 -05:00
Drew Cain b8191d090f chore: update version to 0.13.7 for v0.13.7 release 2025-06-18 22:32:53 -05:00
68 changed files with 1152 additions and 6397 deletions
+60
View File
@@ -0,0 +1,60 @@
# Git files
.git/
.gitignore
.gitattributes
# Development files
.vscode/
.idea/
*.swp
*.swo
*~
# Testing files
tests/
test-int/
.pytest_cache/
.coverage
htmlcov/
# Build artifacts
build/
dist/
*.egg-info/
__pycache__/
*.pyc
*.pyo
*.pyd
.Python
# Virtual environments (uv creates these during build)
.venv/
venv/
.env
# CI/CD files
.github/
# Documentation (keep README.md and pyproject.toml)
docs/
CHANGELOG.md
CLAUDE.md
CONTRIBUTING.md
# Example files not needed for runtime
examples/
# Local development files
.basic-memory/
*.db
*.sqlite3
# OS files
.DS_Store
Thumbs.db
# Temporary files
tmp/
temp/
*.tmp
*.log
-55
View File
@@ -1,55 +0,0 @@
# OAuth Configuration for Basic Memory MCP Server
# Copy this file to .env and update the values
# Enable OAuth authentication
FASTMCP_AUTH_ENABLED=true
# OAuth provider type: basic, github, google, or supabase
# - basic: Built-in OAuth provider with in-memory storage
# - github: Integrate with GitHub OAuth
# - google: Integrate with Google OAuth
# - supabase: Integrate with Supabase Auth (recommended for production)
FASTMCP_AUTH_PROVIDER=basic
# OAuth issuer URL (your MCP server URL)
FASTMCP_AUTH_ISSUER_URL=http://localhost:8000
# Documentation URL for OAuth endpoints
FASTMCP_AUTH_DOCS_URL=http://localhost:8000/docs/oauth
# Required scopes (comma-separated)
# Examples: read,write,admin
FASTMCP_AUTH_REQUIRED_SCOPES=read,write
# Secret key for JWT tokens (auto-generated if not set)
# FASTMCP_AUTH_SECRET_KEY=your-secret-key-here
# Enable client registration endpoint
FASTMCP_AUTH_CLIENT_REGISTRATION_ENABLED=true
# Enable token revocation endpoint
FASTMCP_AUTH_REVOCATION_ENABLED=true
# Default scopes for new clients
FASTMCP_AUTH_DEFAULT_SCOPES=read
# Valid scopes that can be requested
FASTMCP_AUTH_VALID_SCOPES=read,write,admin
# Client secret expiry in seconds (optional)
# FASTMCP_AUTH_CLIENT_SECRET_EXPIRY=86400
# GitHub OAuth settings (if using github provider)
# GITHUB_CLIENT_ID=your-github-client-id
# GITHUB_CLIENT_SECRET=your-github-client-secret
# Google OAuth settings (if using google provider)
# GOOGLE_CLIENT_ID=your-google-client-id
# GOOGLE_CLIENT_SECRET=your-google-client-secret
# Supabase settings (if using supabase provider)
# SUPABASE_URL=https://your-project.supabase.co
# SUPABASE_ANON_KEY=your-anon-key
# SUPABASE_SERVICE_KEY=your-service-key # Optional, for admin operations
# SUPABASE_JWT_SECRET=your-jwt-secret # Optional, for token validation
# SUPABASE_ALLOWED_CLIENTS=client1,client2 # Comma-separated list of allowed client IDs
+61
View File
@@ -0,0 +1,61 @@
name: Docker Image CI
on:
push:
tags:
- 'v*' # Trigger on version tags like v1.0.0, v0.13.0, etc.
workflow_dispatch: # Allow manual triggering for testing
env:
REGISTRY: ghcr.io
IMAGE_NAME: basicmachines-co/basic-memory
jobs:
docker:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
platforms: linux/amd64,linux/arm64
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
-42
View File
@@ -1,42 +0,0 @@
# OAuth Quick Start
Basic Memory supports OAuth authentication for secure access control. For detailed documentation, see [OAuth Authentication Guide](docs/OAuth%20Authentication%20Guide.md).
## Quick Test with MCP Inspector
```bash
# 1. Set a consistent secret key
export FASTMCP_AUTH_SECRET_KEY="test-secret-key"
# 2. Start server with OAuth
FASTMCP_AUTH_ENABLED=true basic-memory mcp --transport streamable-http
# 3. In another terminal, get a test token
export FASTMCP_AUTH_SECRET_KEY="test-secret-key" # Same key!
basic-memory auth test-auth
# 4. Copy the access token and use in MCP Inspector:
# - Server URL: http://localhost:8000/mcp
# - Transport: streamable-http
# - Custom Headers:
# Authorization: Bearer YOUR_ACCESS_TOKEN
# Accept: application/json, text/event-stream
```
## OAuth Endpoints
- `GET /authorize` - Authorization endpoint
- `POST /token` - Token exchange endpoint
- `GET /.well-known/oauth-authorization-server` - OAuth metadata
## Common Issues
1. **401 Unauthorized**: Make sure you're using the same secret key for both server and client
2. **404 Not Found**: Use `/authorize` not `/auth/authorize`
3. **Token Invalid**: Tokens don't persist across server restarts with basic provider
## Documentation
- [OAuth Authentication Guide](docs/OAuth%20Authentication%20Guide.md) - Complete setup guide
- [Supabase OAuth Setup](docs/Supabase%20OAuth%20Setup.md) - Production deployment
- [External OAuth Providers](docs/External%20OAuth%20Providers.md) - GitHub, Google integration
+59
View File
@@ -1,5 +1,64 @@
# CHANGELOG
## v0.13.8 (2025-06-20)
### Features
- **Docker Container Support** - Complete Docker integration with volume mounting for Obsidian directories
([`3269a2f`](https://github.com/basicmachines-co/basic-memory/commit/3269a2f33a7595f6d9e5207924062e2542f46759))
- Docker Compose configuration for easy deployment
- Volume mounting for persistent data and Obsidian integration
- Comprehensive Docker documentation and setup guides
- Streamlined container-based workflow
### Bug Fixes
- **#151**: Fix reset command project configuration persistence issue
([`af44941`](https://github.com/basicmachines-co/basic-memory/commit/af44941d5aa57b5ad7fcc6af4ed700f49bdb6d4d))
- Reset command now properly clears project configuration from `~/.basic-memory/config.json`
- Eliminates issue where projects would be recreated after database reset
- Ensures clean slate when resetting Basic Memory installation
- **#148**: Resolve project state inconsistency between MCP and CLI
([`35e4f73`](https://github.com/basicmachines-co/basic-memory/commit/35e4f73ae8a65501da4d48258ed702f957184c92))
- Fix "Project not found" errors when switching default projects
- MCP session now automatically refreshes when project configuration changes
- Eliminates need to restart MCP server after project operations
- Ensures consistent project state across CLI and MCP interfaces
- **FastMCP Compatibility** - Resolve deprecation warnings for FastMCP integration
([`7be001c`](https://github.com/basicmachines-co/basic-memory/commit/7be001ca6834b3344bb6160cbe537b36bcbaa579))
- Update FastMCP usage patterns to eliminate deprecation warnings
- Improve future compatibility with FastMCP library updates
- Clean up entity repository and service layer code
### Technical Improvements
- **Comprehensive Integration Testing** - New test suites for critical user workflows
- Full integration tests for database reset functionality
- End-to-end project state synchronization testing
- Real MCP client-server communication validation
- Direct config file validation without complex mocking
- **Code Quality** - Enhanced error handling and validation
- Improved project state management across system components
- Better session refresh patterns for configuration changes
- Streamlined Docker setup with reduced image size
### Documentation
- **Docker Integration Guide** - Complete documentation for container deployment
- Step-by-step Docker Compose setup instructions
- Volume mounting configuration for Obsidian workflows
- Container-based development environment setup
## v0.13.7 (2025-06-19)
### Bug Fixes
- **Homebrew Integration** - Automatic Homebrew formula updates
- **Documentation** - Add git sign-off reminder to development guide
## v0.13.6 (2025-06-18)
### Bug Fixes
+26 -10
View File
@@ -1,16 +1,32 @@
# Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
FROM python:3.12-slim
FROM python:3.12-slim-bookworm
# Copy uv from official image
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
# Set environment variables
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1
# Copy the project into the image
ADD . /app
# Sync the project into a new environment, asserting the lockfile is up to date
WORKDIR /app
RUN uv sync --locked
# Copy the project files
COPY . .
# Create data directory
RUN mkdir -p /app/data
# Install pip and build dependencies
RUN pip install --upgrade pip \
&& pip install . --no-cache-dir --ignore-installed
# Set default data directory and add venv to PATH
ENV BASIC_MEMORY_HOME=/app/data \
PATH="/app/.venv/bin:$PATH"
# Expose port if necessary (e.g., uv might use a port, but MCP over stdio so not needed here)
# Expose port
EXPOSE 8000
# Use the basic-memory entrypoint to run the MCP server
CMD ["basic-memory", "mcp"]
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD basic-memory --version || exit 1
# Use the basic-memory entrypoint to run the MCP server with default SSE transport
CMD ["basic-memory", "mcp", "--transport", "sse", "--host", "0.0.0.0", "--port", "8000"]
+25
View File
@@ -411,6 +411,31 @@ Development versions are automatically published on every commit to main with ve
pip install basic-memory --pre --force-reinstall
```
### Docker
Run Basic Memory in a container with volume mounting for your Obsidian vault:
```bash
# Clone and start with Docker Compose
git clone https://github.com/basicmachines-co/basic-memory.git
cd basic-memory
# Edit docker-compose.yml to point to your Obsidian vault
# Then start the container
docker-compose up -d
```
Or use Docker directly:
```bash
docker run -d \
--name basic-memory-server \
-v /path/to/your/obsidian-vault:/data/knowledge:rw \
-v basic-memory-config:/root/.basic-memory:rw \
ghcr.io/basicmachines-co/basic-memory:latest
```
See [Docker Setup Guide](docs/Docker.md) for detailed configuration options, multiple project setup, and integration examples.
## License
AGPL-3.0
-1419
View File
File diff suppressed because it is too large Load Diff
+83
View File
@@ -0,0 +1,83 @@
# Docker Compose configuration for Basic Memory
# See docs/Docker.md for detailed setup instructions
version: '3.8'
services:
basic-memory:
# Use pre-built image (recommended for most users)
image: ghcr.io/basicmachines-co/basic-memory:latest
# Uncomment to build locally instead:
# build: .
container_name: basic-memory-server
# Volume mounts for knowledge directories and persistent data
volumes:
# Persistent storage for configuration and database
- basic-memory-config:/root/.basic-memory:rw
# Mount your knowledge directory (required)
# Change './knowledge' to your actual Obsidian vault or knowledge directory
- ./knowledge:/app/data:rw
# OPTIONAL: Mount additional knowledge directories for multiple projects
# - ./work-notes:/app/data/work:rw
# - ./personal-notes:/app/data/personal:rw
# You can edit the project config manually in the mounted config volume
# The default project will be configured to use /app/data
environment:
# Project configuration
- BASIC_MEMORY_DEFAULT_PROJECT=main
# Enable real-time file synchronization (recommended for Docker)
- BASIC_MEMORY_SYNC_CHANGES=true
# Logging configuration
- BASIC_MEMORY_LOG_LEVEL=INFO
# Sync delay in milliseconds (adjust for performance vs responsiveness)
- BASIC_MEMORY_SYNC_DELAY=1000
# Port exposure for HTTP transport (only needed if not using STDIO)
ports:
- "8000:8000"
# Command with SSE transport (configurable via environment variables above)
# IMPORTANT: The SSE and streamable-http endpoints are not secured
command: ["basic-memory", "mcp", "--transport", "sse", "--host", "0.0.0.0", "--port", "8000"]
# Container management
restart: unless-stopped
# Health monitoring
healthcheck:
test: ["CMD", "basic-memory", "--version"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
# Optional: Resource limits
# deploy:
# resources:
# limits:
# memory: 512M
# cpus: '0.5'
# reservations:
# memory: 256M
# cpus: '0.25'
volumes:
# Named volume for persistent configuration and database
# This ensures your configuration and knowledge graph persist across container restarts
basic-memory-config:
driver: local
# Network configuration (optional)
# networks:
# basic-memory-net:
# driver: bridge
-414
View File
@@ -1,414 +0,0 @@
---
title: CLI Reference
type: note
permalink: docs/cli-reference
---
# CLI Reference
Basic Memory provides command line tools for managing your knowledge base. This reference covers the available commands and their options.
## Core Commands
### auth (New in v0.13.0)
Manage OAuth authentication for secure remote access:
```bash
# Test authentication setup
basic-memory auth test-auth
# Register OAuth client
basic-memory auth register-client
```
Supports multiple authentication providers:
- **Basic Provider**: For development and testing
- **Supabase Provider**: For production deployments
- **External Providers**: GitHub, Google integration framework
See [[OAuth Authentication Guide]] for complete setup instructions.
### sync
Keeps files and the knowledge graph in sync:
```bash
# Basic sync
basic-memory sync
# Watch for changes
basic-memory sync --watch
# Show detailed sync information
basic-memory sync --verbose
```
Options:
- `--watch`: Continuously monitor for changes
- `--verbose`: Show detailed output
**Note**:
As of the v0.12.0 release syncing will occur in real time when the mcp process starts.
- The real time sync means that it is no longer necessary to run the `basic-memory sync --watch` process in a a terminal to sync changes to the db (so the AI can see them). This will be done automatically.
This behavior can be changed via the config. The config file for Basic Memory is in the home directory under `.basic-memory/config.json`.
To change the properties, set the following values:
```
~/.basic-memory/config.json
{
"sync_changes": false,
}
```
Thanks for using Basic Memory!
### import (Enhanced in v0.13.0)
Imports external knowledge sources with support for project targeting:
```bash
# Claude conversations
basic-memory import claude conversations
# Claude projects
basic-memory import claude projects
# ChatGPT history
basic-memory import chatgpt
# Memory JSON format
basic-memory import memory-json /path/to/memory.json
# Import to specific project (v0.13.0)
basic-memory --project=work import claude conversations
```
**New in v0.13.0:**
- **Project Targeting**: Import directly to specific projects
- **Real-time Sync**: Imported content available immediately
- **Unified Database**: All imports stored in centralized database
> **Note**: Changes sync automatically - no manual sync required in v0.13.0.
### status
Shows system status information:
```bash
# Basic status check
basic-memory status
# Detailed status
basic-memory status --verbose
# JSON output
basic-memory status --json
```
### project (Enhanced in v0.13.0)
Manage multiple projects with the new unified database architecture. Projects can now be switched instantly during conversations without restart.
```bash
# List all configured projects with status
basic-memory project list
# Create a new project
basic-memory project create work ~/work-basic-memory
# Set the default project
basic-memory project set-default work
# Delete a project (doesn't delete files)
basic-memory project delete personal
# Show detailed project statistics
basic-memory project info
```
**New in v0.13.0:**
- **Unified Database**: All projects share a single database for better performance
- **Instant Switching**: Switch projects during conversations without restart
- **Enhanced Commands**: Updated project commands with better status information
- **Project Statistics**: Detailed info about entities, observations, and relations
#### Using Projects in Commands
All commands support the `--project` flag to specify which project to use:
```bash
# Sync a specific project
basic-memory --project=work sync
# Run MCP server for a specific project
basic-memory --project=personal mcp
```
You can also set the `BASIC_MEMORY_PROJECT` environment variable:
```bash
BASIC_MEMORY_PROJECT=work basic-memory sync
```
### tool (Enhanced in v0.13.0)
Direct access to MCP tools via CLI with new editing and file management capabilities:
```bash
# Create notes
basic-memory tool write-note --title "My Note" --content "Content here"
# Edit notes incrementally (v0.13.0)
echo "New content" | basic-memory tool edit-note --title "My Note" --operation append
# Move notes (v0.13.0)
basic-memory tool move-note --identifier "My Note" --destination "archive/my-note.md"
# Search notes
basic-memory tool search-notes --query "authentication"
# Project management (v0.13.0)
basic-memory tool list-projects
basic-memory tool switch-project --project-name "work"
```
**New in v0.13.0:**
- **edit-note**: Incremental editing (append, prepend, find/replace, section replace)
- **move-note**: File management with database consistency
- **Project tools**: list-projects, switch-project, get-current-project
- **Cross-project operations**: Use `--project` flag with any tool
### help
The full list of commands and help for each can be viewed with the `--help` argument.
```
✗ basic-memory --help
Usage: basic-memory [OPTIONS] COMMAND [ARGS]...
Basic Memory - Local-first personal knowledge management system.
╭─ Options ─────────────────────────────────────────────────────────────────────────────────╮
│ --project -p TEXT Specify which project to use │
│ [env var: BASIC_MEMORY_PROJECT] │
│ [default: None] │
│ --version -V Show version information and exit. │
│ --install-completion Install completion for the current shell. │
│ --show-completion Show completion for the current shell, to copy it or │
│ customize the installation. │
│ --help Show this message and exit. │
╰───────────────────────────────────────────────────────────────────────────────────────────╯
╭─ Commands ────────────────────────────────────────────────────────────────────────────────╮
│ auth OAuth authentication management (v0.13.0) │
│ sync Sync knowledge files with the database │
│ status Show sync status between files and database │
│ reset Reset database (drop all tables and recreate) │
│ mcp Run the MCP server for Claude Desktop integration │
│ import Import data from various sources │
│ tool Direct access to MCP tools via CLI │
│ project Manage multiple Basic Memory projects │
╰───────────────────────────────────────────────────────────────────────────────────────────╯
```
## Initial Setup
```bash
# Install Basic Memory
uv install basic-memory
# First sync
basic-memory sync
# Start watching mode
basic-memory sync --watch
```
> **Important**: You need to install Basic Memory via `uv` or `pip` to use the command line tools, see [[Getting Started with Basic Memory#Installation]].
## Regular Usage
```bash
# Check status
basic-memory status
# Import new content
basic-memory import claude conversations
# Sync changes
basic-memory sync
# Sync changes continuously
basic-memory sync --watch
```
## Maintenance Tasks
```bash
# Check system status in detail
basic-memory status --verbose
# Full resync of all files
basic-memory sync
# Import updates to specific folder
basic-memory import claude conversations --folder new
```
## Using stdin with Basic Memory's `write_note` Tool
The `write-note` tool supports reading content from standard input (stdin), allowing for more flexible workflows when creating or updating notes in your Basic Memory knowledge base.
### Use Cases
This feature is particularly useful for:
1. **Piping output from other commands** directly into Basic Memory notes
2. **Creating notes with multi-line content** without having to escape quotes or special characters
3. **Integrating with AI assistants** like Claude Code that can generate content and pipe it to Basic Memory
4. **Processing text data** from files or other sources
### Basic Usage
#### Method 1: Using a Pipe
You can pipe content from another command into `write_note`:
```bash
# Pipe output of a command into a new note
echo "# My Note\n\nThis is a test note" | basic-memory tool write-note --title "Test Note" --folder "notes"
# Pipe output of a file into a new note
cat README.md | basic-memory tool write-note --title "Project README" --folder "documentation"
# Process text through other tools before saving as a note
cat data.txt | grep "important" | basic-memory tool write-note --title "Important Data" --folder "data"
```
#### Method 2: Using Heredoc Syntax
For multi-line content, you can use heredoc syntax:
```bash
# Create a note with heredoc
cat << EOF | basic-memory tool write_note --title "Project Ideas" --folder "projects"
# Project Ideas for Q2
## AI Integration
- Improve recommendation engine
- Add semantic search to product catalog
## Infrastructure
- Migrate to Kubernetes
- Implement CI/CD pipeline
EOF
```
#### Method 3: Input Redirection
You can redirect input from a file:
```bash
# Create a note from file content
basic-memory tool write-note --title "Meeting Notes" --folder "meetings" < meeting_notes.md
```
## Integration with Claude Code
This feature works well with Claude Code in the terminal:
### cli
In a Claude Code session, let Claude know he can use the basic-memory tools, then he can execute them via the cli:
```
⏺ Bash(echo "# Test Note from Claude\n\nThis is a test note created by Claude to test the stdin functionality." | basic-memory tool write-note --title "Claude Test Note" --folder "test" --tags "test" --tags "claude")…
  ⎿  # Created test/Claude Test Note.md (23e00eec)
permalink: test/claude-test-note
## Tags
- test, claude
```
### MCP
Claude code can also now use mcp tools, so it can use any of the basic-memory tool natively. To install basic-memory in Claude Code:
Run
```
claude mcp add basic-memory basic-memory mcp
```
For example:
```
➜ ~ claude mcp add basic-memory basic-memory mcp
Added stdio MCP server basic-memory with command: basic-memory mcp to project config
➜ ~ claude mcp list
basic-memory: basic-memory mcp
```
You can then use the `/mcp` command in the REPL:
```
/mcp
⎿ MCP Server Status
• basic-memory: connected
```
## Version Management (New in v0.13.0)
Basic Memory v0.13.0 introduces automatic version management and multiple installation options:
```bash
# Stable releases
pip install basic-memory
# Beta/pre-releases
pip install basic-memory --pre
# Latest development builds (auto-published)
pip install basic-memory --pre --force-reinstall
# Check current version
basic-memory --version
```
**Version Types:**
- **Stable**: `0.13.0` (manual git tags)
- **Beta**: `0.13.0b1` (manual git tags)
- **Development**: `0.12.4.dev26+468a22f` (automatic from commits)
## Troubleshooting Common Issues
### Sync Conflicts
If you encounter a file changed during sync error:
1. Check the file referenced in the error message
2. Resolve any conflicts manually
3. Run sync again
### Import Errors
If import fails:
1. Check that the source file is in the correct format
2. Verify permissions on the target directory
3. Use --verbose flag for detailed error information
### Status Issues
If status shows problems:
1. Note any unresolved relations or warnings
2. Run a full sync to attempt automatic resolution
3. Check file permissions if database access errors occur
## Relations
- used_by [[Getting Started with Basic Memory]] (Installation instructions)
- complements [[User Guide]] (How to use Basic Memory)
- relates_to [[Introduction to Basic Memory]] (System overview)
-107
View File
@@ -1,107 +0,0 @@
---
title: Canvas Visualizations
type: note
permalink: docs/canvas
tags:
- visualization
- mapping
- obsidian
---
# Canvas Visualizations
Basic Memory can create visual knowledge maps using Obsidian's Canvas feature. These visualizations help you understand relationships between concepts, map out processes, and visualize your knowledge structure.
## Creating Canvas Visualizations
Ask Claude to create a visualization by describing what you want to map:
```
You: "Create a canvas visualization of my project components and their relationships."
You: "Make a concept map showing the main themes from our discussion about climate change."
You: "Can you make a canvas diagram of the perfect pour over method?"
```
![[Canvas.png]]
## Types of Visualizations
Basic Memory can create several types of visual maps:
### Document Maps
Visualize connections between your notes and documents
### Concept Maps
Create visual representations of ideas and their relationships
### Process Diagrams
Map workflows, sequences, and procedures
### Thematic Analysis
Organize ideas around central themes
### Relationship Networks
Show how different entities relate to each other
## Visualization Sources
Claude can create visualizations based on:
### Documents in Your Knowledge Base
```
You: "Create a canvas showing the connections between my project planning documents"
```
### Conversation Content
```
You: "Make a canvas visualization of the main points we just discussed"
```
### Search Results
```
You: "Find all my notes about psychology and create a visual map of the concepts"
```
### Themes and Relationships
```
You: "Create a visual map showing how different philosophical schools relate to each other"
```
## Visualization Workflow
1. **Request a visualization** by describing what you want to see
2. **Claude creates the canvas file** in your Basic Memory directory
3. **Open the file in Obsidian** to view the visualization
4. **Refine the visualization** by asking Claude for adjustments:
```
You: "Could you reorganize the canvas to group related components together?"
You: "Please add more detail about the connection between these two concepts."
```
## Technical Details
Behind the scenes, Claude:
1. Creates a `.canvas` file in JSON format
2. Adds nodes for each concept or document
3. Creates edges to represent relationships
4. Sets positions for visual clarity
5. Includes any relevant metadata
The resulting file is fully compatible with Obsidian's Canvas feature and can be edited directly in Obsidian.
## Tips for Effective Visualizations
- **Be specific** about what you want to visualize
- **Specify the level of detail** you need
- **Mention the visualization type** you want (concept map, process flow, etc.)
- **Start simple** and ask for refinements
- **Provide context** about what documents or concepts to include
## Relations
- enhances [[Obsidian Integration]] (Using Basic Memory with Obsidian)
- visualizes [[Knowledge Format]] (The structure of your knowledge)
- complements [[User Guide]] (Ways to use Basic Memory)
-335
View File
@@ -1,335 +0,0 @@
# Claude.ai Integration Guide
This guide explains how to connect Basic Memory to Claude.ai, enabling Claude to read and write to your personal knowledge base.
## Overview
When connected to Claude.ai, Basic Memory provides:
- Persistent memory across conversations
- Knowledge graph navigation
- Note-taking and search capabilities
- File organization and management
## Prerequisites
1. Basic Memory MCP server with OAuth enabled
2. Public HTTPS URL (or tunneling service for testing)
3. Claude.ai account (Free, Pro, or Enterprise)
## Quick Start (Testing)
### 1. Start MCP Server with OAuth
```bash
# Enable OAuth with basic provider
export FASTMCP_AUTH_ENABLED=true
export FASTMCP_AUTH_PROVIDER=basic
# Start server on all interfaces
basic-memory mcp --transport streamable-http --host 0.0.0.0 --port 8000
```
### 2. Make Server Accessible
For testing, use ngrok:
```bash
# Install ngrok
brew install ngrok # macOS
# or download from https://ngrok.com
# Create tunnel
ngrok http 8000
```
Note the HTTPS URL (e.g., `https://abc123.ngrok.io`)
### 3. Register OAuth Client
```bash
# Register a client for Claude
basic-memory auth register-client --client-id claude-ai
# Save the credentials!
# Client ID: claude-ai
# Client Secret: xxx...
```
### 4. Connect in Claude.ai
1. Go to Claude.ai → Settings → Integrations
2. Click "Add More"
3. Enter your server URL: `https://abc123.ngrok.io/mcp`
4. Click "Connect"
5. Authorize the connection
### 5. Use in Conversations
- Click the tools icon (🔧) in the chat
- Select "Basic Memory"
- Try commands like:
- "Create a note about our meeting"
- "Search for project ideas"
- "Show recent notes"
## Production Setup
### 1. Deploy with Supabase Auth
```bash
# .env file
FASTMCP_AUTH_ENABLED=true
FASTMCP_AUTH_PROVIDER=supabase
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_KEY=your-service-key
```
### 2. Deploy to Cloud
Options for deployment:
#### Vercel
```json
// vercel.json
{
"functions": {
"api/mcp.py": {
"runtime": "python3.9"
}
}
}
```
#### Railway
```bash
# Install Railway CLI
brew install railway
# Deploy
railway init
railway up
```
#### Docker
```dockerfile
FROM python:3.12
WORKDIR /app
COPY . .
RUN pip install -e .
CMD ["basic-memory", "mcp", "--transport", "streamable-http"]
```
### 3. Configure for Organization
For Claude.ai Enterprise:
1. **Admin Setup**:
- Go to Organizational Settings
- Navigate to Integrations
- Add MCP server URL for all users
- Configure allowed scopes
2. **User Permissions**:
- Users connect individually
- Each user has their own auth token
- Scopes determine access level
## Security Best Practices
### 1. Use HTTPS
- Required for OAuth
- Encrypt all data in transit
- Use proper SSL certificates
### 2. Implement Scopes
```bash
# Configure required scopes
FASTMCP_AUTH_REQUIRED_SCOPES=read,write
# User-specific scopes
read: Can search and read notes
write: Can create and update notes
admin: Can manage all data
```
### 3. Token Security
- Short-lived access tokens (1 hour)
- Refresh token rotation
- Secure token storage
### 4. Rate Limiting
```python
# In your MCP server
from fastapi import HTTPException
from slowapi import Limiter
limiter = Limiter(key_func=get_remote_address)
@app.get("/mcp")
@limiter.limit("100/minute")
async def mcp_endpoint():
# Handle MCP requests
```
## Advanced Features
### 1. Custom Tools
Create specialized tools for Claude:
```python
@mcp.tool()
async def analyze_notes(topic: str) -> str:
"""Analyze all notes on a specific topic."""
# Search and analyze implementation
return analysis
```
### 2. Context Preservation
Use memory:// URLs to maintain context:
```python
@mcp.tool()
async def continue_conversation(memory_url: str) -> str:
"""Continue from a previous conversation."""
context = await build_context(memory_url)
return context
```
### 3. Multi-User Support
With Supabase, each user has isolated data:
```sql
-- Row-level security
CREATE POLICY "Users see own notes"
ON notes FOR SELECT
USING (auth.uid() = user_id);
```
## Troubleshooting
### Connection Issues
1. **"Failed to connect"**
- Verify server is running
- Check HTTPS is working
- Confirm OAuth is enabled
2. **"Authorization failed"**
- Check client credentials
- Verify redirect URLs
- Review OAuth logs
3. **"No tools available"**
- Ensure MCP tools are registered
- Check required scopes
- Verify transport type
### Debug Mode
Enable detailed logging:
```bash
# Server side
export FASTMCP_LOG_LEVEL=DEBUG
export LOGURU_LEVEL=DEBUG
# Check logs
tail -f logs/mcp.log
```
### Test Connection
```bash
# Test OAuth flow
curl https://your-server.com/mcp/.well-known/oauth-authorization-server
# Should return OAuth metadata
{
"issuer": "https://your-server.com",
"authorization_endpoint": "https://your-server.com/auth/authorize",
...
}
```
## Best Practices
1. **Regular Backups**
- Export your knowledge base
- Use version control
- Multiple storage locations
2. **Access Control**
- Principle of least privilege
- Regular token rotation
- Audit access logs
3. **Performance**
- Index frequently searched fields
- Optimize large knowledge bases
- Use caching where appropriate
4. **User Experience**
- Clear tool descriptions
- Helpful error messages
- Quick response times
## Examples
### Creating Notes
```
User: Create a note about the meeting with the product team
Claude: I'll create a note about your meeting with the product team.
[Uses write_note tool]
Note created: "Meeting with Product Team - 2024-01-15"
Location: Work/Meetings/
I've documented the meeting notes. The note includes the date, attendees, and key discussion points.
```
### Searching Knowledge
```
User: What did we discuss about the API redesign?
Claude: Let me search for information about the API redesign.
[Uses search_notes tool]
I found 3 relevant notes about the API redesign:
1. "API Redesign Proposal" (2024-01-10)
- RESTful architecture
- Version 2.0 specifications
- Migration timeline
2. "Technical Review: API Changes" (2024-01-12)
- Breaking changes documented
- Backwards compatibility plan
3. "Meeting: API Implementation" (2024-01-14)
- Team assignments
- Q1 deliverables
```
## Next Steps
1. Set up production deployment
2. Configure organizational access
3. Create custom tools for your workflow
4. Implement advanced security features
5. Monitor usage and performance
## Resources
- [Basic Memory Documentation](../README.md)
- [OAuth Setup Guide](OAuth%20Authentication.md)
- [MCP Specification](https://modelcontextprotocol.io)
- [Claude.ai Help Center](https://support.anthropic.com)
+334
View File
@@ -0,0 +1,334 @@
# Docker Setup Guide
Basic Memory can be run in Docker containers to provide a consistent, isolated environment for your knowledge management
system. This is particularly useful for integrating with existing Dockerized MCP servers or for deployment scenarios.
## Quick Start
### Option 1: Using Pre-built Images (Recommended)
Basic Memory provides pre-built Docker images on GitHub Container Registry that are automatically updated with each release.
1. **Use the official image directly:**
```bash
docker run -d \
--name basic-memory-server \
-p 8000:8000 \
-v /path/to/your/obsidian-vault:/app/data:rw \
-v basic-memory-config:/root/.basic-memory:rw \
ghcr.io/basicmachines-co/basic-memory:latest
```
2. **Or use Docker Compose with the pre-built image:**
```yaml
version: '3.8'
services:
basic-memory:
image: ghcr.io/basicmachines-co/basic-memory:latest
container_name: basic-memory-server
ports:
- "8000:8000"
volumes:
- /path/to/your/obsidian-vault:/app/data:rw
- basic-memory-config:/root/.basic-memory:rw
environment:
- BASIC_MEMORY_DEFAULT_PROJECT=main
restart: unless-stopped
```
### Option 2: Using Docker Compose (Building Locally)
1. **Clone the repository:**
```bash
git clone https://github.com/basicmachines-co/basic-memory.git
cd basic-memory
```
2. **Update the docker-compose.yml:**
Edit the volume mount to point to your Obsidian vault:
```yaml
volumes:
# Change './obsidian-vault' to your actual directory path
- /path/to/your/obsidian-vault:/app/data:rw
```
3. **Start the container:**
```bash
docker-compose up -d
```
### Option 3: Using Docker CLI
```bash
# Build the image
docker build -t basic-memory .
# Run with volume mounting
docker run -d \
--name basic-memory-server \
-v /path/to/your/obsidian-vault:/app/data:rw \
-v basic-memory-config:/root/.basic-memory:rw \
-e BASIC_MEMORY_DEFAULT_PROJECT=main \
basic-memory
```
## Configuration
### Volume Mounts
Basic Memory requires several volume mounts for proper operation:
1. **Knowledge Directory** (Required):
```yaml
- /path/to/your/obsidian-vault:/app/data:rw
```
Mount your Obsidian vault or knowledge base directory.
2. **Configuration and Database** (Recommended):
```yaml
- basic-memory-config:/root/.basic-memory:rw
```
Persistent storage for configuration and SQLite database.
You can edit the basic-memory config.json file located in the /root/.basic-memory/config.json after Basic Memory starts.
3. **Multiple Projects** (Optional):
```yaml
- /path/to/project1:/app/data/project1:rw
- /path/to/project2:/app/data/project2:rw
```
You can edit the basic-memory config.json file located in the /root/.basic-memory/config.json
## CLI Commands via Docker
You can run Basic Memory CLI commands inside the container using `docker exec`:
### Basic Commands
```bash
# Check status
docker exec basic-memory-server basic-memory status
# Sync files
docker exec basic-memory-server basic-memory sync
# Show help
docker exec basic-memory-server basic-memory --help
```
### Managing Projects with Volume Mounts
When using Docker volumes, you'll need to configure projects to point to your mounted directories:
1. **Check current configuration:**
```bash
docker exec basic-memory-server cat /root/.basic-memory/config.json
```
2. **Add a project for your mounted volume:**
```bash
# If you mounted /path/to/your/vault to /app/data
docker exec basic-memory-server basic-memory project create my-vault /app/data
# Set it as default
docker exec basic-memory-server basic-memory project set-default my-vault
```
3. **Sync the new project:**
```bash
docker exec basic-memory-server basic-memory sync
```
### Example: Setting up an Obsidian Vault
If you mounted your Obsidian vault like this in docker-compose.yml:
```yaml
volumes:
- /Users/yourname/Documents/ObsidianVault:/app/data:rw
```
Then configure it:
```bash
# Create project pointing to mounted vault
docker exec basic-memory-server basic-memory project create obsidian /app/data
# Set as default
docker exec basic-memory-server basic-memory project set-default obsidian
# Sync to index all files
docker exec basic-memory-server basic-memory sync
```
### Environment Variables
Configure Basic Memory using environment variables:
```yaml
environment:
# Default project
- BASIC_MEMORY_DEFAULT_PROJECT=main
# Enable real-time sync
- BASIC_MEMORY_SYNC_CHANGES=true
# Logging level
- BASIC_MEMORY_LOG_LEVEL=INFO
# Sync delay in milliseconds
- BASIC_MEMORY_SYNC_DELAY=1000
```
## File Permissions
### Linux/macOS
Ensure your knowledge directories have proper permissions:
```bash
# Make directories readable/writable
chmod -R 755 /path/to/your/obsidian-vault
# If using specific user/group
chown -R $USER:$USER /path/to/your/obsidian-vault
```
### Windows
When using Docker Desktop on Windows, ensure the directories are shared:
1. Open Docker Desktop
2. Go to Settings → Resources → File Sharing
3. Add your knowledge directory path
4. Apply & Restart
## Troubleshooting
### Common Issues
1. **File Watching Not Working:**
- Ensure volume mounts are read-write (`:rw`)
- Check directory permissions
- On Linux, may need to increase inotify limits:
```bash
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
```
2. **Configuration Not Persisting:**
- Use named volumes for `/root/.basic-memory`
- Check volume mount permissions
3. **Network Connectivity:**
- For HTTP transport, ensure port 8000 is exposed
- Check firewall settings
### Debug Mode
Run with debug logging:
```yaml
environment:
- BASIC_MEMORY_LOG_LEVEL=DEBUG
```
View logs:
```bash
docker-compose logs -f basic-memory
```
## Security Considerations
1. **Docker Security:**
The container runs as root for simplicity. For production, consider additional security measures.
2. **Volume Permissions:**
Ensure mounted directories have appropriate permissions and don't expose sensitive data.
3. **Network Security:**
If using HTTP transport, consider using reverse proxy with SSL/TLS and authentication if the endpoint is available on
a network.
4. **IMPORTANT:** The HTTP endpoints have no authorization. They should not be exposed on a public network.
## Integration Examples
### Claude Desktop with Docker
The recommended way to connect Claude Desktop to the containerized Basic Memory is using `mcp-proxy`, which converts the HTTP transport to STDIO that Claude Desktop expects:
1. **Start the Docker container:**
```bash
docker-compose up -d
```
2. **Configure Claude Desktop** to use mcp-proxy:
```json
{
"mcpServers": {
"basic-memory": {
"command": "uvx",
"args": [
"mcp-proxy",
"http://localhost:8000/mcp"
]
}
}
}
```
## Support
For Docker-specific issues:
1. Check the [troubleshooting section](#troubleshooting) above
2. Review container logs: `docker-compose logs basic-memory`
3. Verify volume mounts: `docker inspect basic-memory-server`
4. Test file permissions: `docker exec basic-memory-server ls -la /root`
For general Basic Memory support, see the main [README](../README.md)
and [documentation](https://memory.basicmachines.co/).
## GitHub Container Registry Images
### Available Images
Pre-built Docker images are available on GitHub Container Registry at [`ghcr.io/basicmachines-co/basic-memory`](https://github.com/basicmachines-co/basic-memory/pkgs/container/basic-memory).
**Supported architectures:**
- `linux/amd64` (Intel/AMD x64)
- `linux/arm64` (ARM64, including Apple Silicon)
**Available tags:**
- `latest` - Latest stable release
- `v0.13.8`, `v0.13.7`, etc. - Specific version tags
- `v0.13`, `v0.12`, etc. - Major.minor tags
### Automated Builds
Docker images are automatically built and published when new releases are tagged:
1. **Release Process:** When a git tag matching `v*` (e.g., `v0.13.8`) is pushed, the CI workflow automatically:
- Builds multi-platform Docker images
- Pushes to GitHub Container Registry with appropriate tags
- Uses native GitHub integration for seamless publishing
2. **CI/CD Pipeline:** The Docker workflow includes:
- Multi-platform builds (AMD64 and ARM64)
- Layer caching for faster builds
- Automatic tagging with semantic versioning
- Security scanning and optimization
### Setup Requirements (For Maintainers)
GitHub Container Registry integration is automatic for this repository:
1. **No external setup required** - GHCR is natively integrated with GitHub
2. **Automatic permissions** - Uses `GITHUB_TOKEN` with `packages: write` permission
3. **Public by default** - Images are automatically public for public repositories
The Docker CI workflow (`.github/workflows/docker.yml`) handles everything automatically when version tags are pushed.
-355
View File
@@ -1,355 +0,0 @@
---
title: Getting Started with Basic Memory
type: note
permalink: docs/getting-started
---
# Getting Started with Basic Memory
This guide will help you install Basic Memory, configure it with Claude Desktop, and create your first knowledge notes
through conversations.
Basic Memory uses the [Model Context Protocol](https://modelcontextprotocol.io/introduction) (MCP) to connect with LLMs.
It can be used with any service that supports the MCP, but Claude Desktop works especially well.
## Installation
### Prerequisites
The easiest way to install basic memory is via `uv`. See the [uv installation guide](https://docs.astral.sh/uv/getting-started/installation/).
### 1. Install Basic Memory
**v0.13.0 offers multiple installation options:**
```bash
# Stable release (recommended)
uv tool install basic-memory
# or: pip install basic-memory
# Beta releases (new features, testing)
pip install basic-memory --pre
# Development builds (latest changes)
pip install basic-memory --pre --force-reinstall
```
**Version Information:**
- **Stable**: Latest tested release (e.g., `0.13.0`)
- **Beta**: Pre-release versions (e.g., `0.13.0b1`)
- **Development**: Auto-published from git commits (e.g., `0.12.4.dev26+468a22f`)
> **Important**: You need to install Basic Memory using one of the commands above to use the command line tools.
Using `uv tool install` will install the basic-memory package in a standalone virtual environment. See the [UV docs](https://docs.astral.sh/uv/concepts/tools/) for more info.
### 2. Configure Claude Desktop
Edit your Claude Desktop config, located at `~/Library/Application Support/Claude/claude_desktop_config.json`:
```json
{
"mcpServers": {
"basic-memory": {
"command": "uvx",
"args": [
"basic-memory",
"mcp"
]
}
}
}
```
**Restart Claude Desktop**. You should see Basic Memory tools available in the "tools" menu in Claude Desktop (the little hammer icon in the bottom-right corner of the chat interface). Click it to view available tools.
#### Fix Path to uv
If you get an error that says `ENOENT` , this most likely means Claude Desktop could not find your `uv` installation. Make sure that you have `uv` installed per the instructions above, then:
**Step 1: Find the absolute path to uvx**
Open Terminal and run:
```bash
which uvx
```
This will show you the full path (e.g., `/Users/yourusername/.cargo/bin/uvx`).
**Step 2: Edit Claude Desktop Configuration**
Edit the Claude Desktop config:
```json
{
"mcpServers": {
"basic-memory": {
"command": "/absolute/path/to/uvx",
"args": [
"basic-memory",
"mcp"
]
}
}
}
```
Replace `/absolute/path/to/uvx` with the actual path you found in Step 1.
**Step 3: Restart Claude Desktop**
Close and reopen Claude Desktop for the changes to take effect.
### 3. Sync changes in real time
> **Note**: The service will sync changes from your project directory in real time so they available for the AI assistant.
To disable realtime sync, you can update the config. See [[CLI Reference#sync]].
### 4. Staying Updated
To update Basic Memory when new versions are released:
```bash
# Update stable release
uv tool upgrade basic-memory
# or: pip install --upgrade basic-memory
# Update to latest beta (v0.13.0)
pip install --upgrade basic-memory --pre
# Get latest development build
pip install --upgrade basic-memory --pre --force-reinstall
```
**v0.13.0 Update Benefits:**
- **Fluid project switching** during conversations
- **Advanced note editing** capabilities
- **Smart file management** with move operations
- **Enhanced search** with frontmatter tag support
> **Note**: After updating, restart Claude Desktop for changes to take effect. No sync restart needed in v0.13.0.
### 5. Multi-Project Setup (Enhanced in v0.13.0)
By default, Basic Memory creates a project in `~/basic-memory`. v0.13.0 introduces **fluid project management** - switch between projects instantly during conversations.
```
# Create a new project
basic-memory project create work ~/work-basic-memory
# Set the default project
basic-memory project set-default work
# List all projects with status
basic-memory project list
# Get detailed project information
basic-memory project info
```
**New in v0.13.0:**
- **Instant switching**: Change projects during conversations without restart
- **Unified database**: All projects in single `~/.basic-memory/memory.db`
- **Better performance**: Optimized queries and reduced file I/O
- **Session context**: Maintains active project throughout conversations
## Troubleshooting Installation
### Common Issues
#### Claude Says "No Basic Memory Tools Available"
If Claude cannot find Basic Memory tools:
1. **Check absolute paths**: Ensure you're using complete absolute paths to uvx in the Claude Desktop configuration
2. **Verify installation**: Run `basic-memory --version` in Terminal to confirm Basic Memory is installed
3. **Restart applications**: Restart both Terminal and Claude Desktop after making configuration changes
4. **Check sync status**: You can view the sync status by running `basic-memory status
.
#### Permission Issues
If you encounter permission errors:
1. Check that Basic Memory has access to create files in your home directory
2. Ensure Claude Desktop has permission to execute the uvx command
## Creating Your First Knowledge Note
1. **Open Claude Desktop** and start a new conversation.
2. **Have a natural conversation** about any topic:
```
You: "Let's talk about coffee brewing methods I've been experimenting with."
Claude: "I'd be happy to discuss coffee brewing methods..."
You: "I've found that pour over gives more flavor clarity than French press..."
```
3. **Ask Claude to create a note**:
```
You: "Could you create a note summarizing what we've discussed about coffee brewing?"
```
4. **Confirm note creation**:
Claude will confirm when the note has been created and where it's stored.
5. **View the created file** in your `~/basic-memory` directory using any text editor or Obsidian.
The file structure will look similar to:
```markdown
---
title: Coffee Brewing Methods
permalink: coffee-brewing-methods
tags: [coffee, brewing, equipment] # v0.13.0: Now searchable!
---
# Coffee Brewing Methods
## Observations
- [method] Pour over provides more clarity...
- [technique] Water temperature at 205°F...
## Relations
- relates_to [[Other Coffee Topics]]
```
**v0.13.0 Improvements:**
- **Real-time sync**: Changes appear immediately, no background sync needed
- **Searchable tags**: Frontmatter tags are now indexed for search
- **Better file organization**: Enhanced file management capabilities
## Using Special Prompts
Basic Memory includes special prompts that help you start conversations with context from your knowledge base:
### Continue Conversation
To resume a previous topic:
```
You: "Let's continue our conversation about coffee brewing."
```
This prompt triggers Claude to:
1. Search your knowledge base for relevant content about coffee brewing
2. Build context from these documents
3. Resume the conversation with full awareness of previous discussions
### Recent Activity
To see what you've been working on:
```
You: "What have we been discussing recently?"
```
This prompt causes Claude to:
1. Retrieve documents modified in the recent past
2. Summarize the topics and main points
3. Offer to continue any of those discussions
### Search
To find specific information:
```
You: "Find information about pour over coffee methods."
```
Claude will:
1. Search your knowledge base for relevant documents
2. Summarize the key findings
3. Offer to explore specific documents in more detail
See [[User Guide#Using Special Prompts]] for further information.
## Using Your Knowledge Base
### Referencing Knowledge
In future conversations, reference your existing knowledge:
```
You: "What water temperature did we decide was optimal for coffee brewing?"
```
Or directly reference notes using memory:// URLs:
```
You: "Take a look at memory://coffee-brewing-methods and let's discuss how to improve my technique."
```
### Building On Previous Knowledge (Enhanced in v0.13.0)
Basic Memory enables continuous knowledge building:
1. **Reference previous discussions** in new conversations
2. **Edit notes incrementally** without rewriting entire documents
3. **Move and organize notes** as your knowledge base grows
4. **Switch between projects** instantly during conversations
5. **Search by tags** to find related content quickly
6. **Create connections** between related topics
7. **Follow relationships** to build comprehensive context
### v0.13.0 Workflow Examples
**Incremental Editing:**
```
You: "Add a section about espresso to my coffee brewing notes"
Claude: [Uses edit_note to append new section]
```
**File Organization:**
```
You: "Move my old meeting notes to an archive folder"
Claude: [Uses move_note with database consistency]
```
**Project Switching:**
```
You: "Switch to my work project and show recent activity"
Claude: [Switches projects and shows work-specific content]
```
## Importing Existing Conversations
Import your existing AI conversations:
```bash
# From Claude
basic-memory import claude conversations
# From ChatGPT
basic-memory import chatgpt
```
After importing, changes sync automatically in real-time. You can see project statistics by running `basic-memory project info`.
## Quick Tips
### General Usage
- Basic Memory syncs changes in real-time (no manual sync needed)
- Use special prompts (Continue Conversation, Recent Activity, Search) to start contextual discussions
- Build connections between notes for a richer knowledge graph
- Use direct `memory://` URLs with permalinks for precise context
- Review and edit AI-generated notes for accuracy
### v0.13.0 Features
- **Switch projects instantly**: "Switch to my work project" - no restart needed
- **Edit notes incrementally**: "Add a section about..." instead of rewriting
- **Organize with moves**: "Move this to my archive folder" with database consistency
- **Search by tags**: Frontmatter tags are now searchable
- **Try beta builds**: `pip install basic-memory --pre` for latest features
## Next Steps
After getting started, explore these areas:
1. **Read the [[User Guide]]** for comprehensive usage instructions
2. **Understand the [[Knowledge Format]]** to learn how knowledge is structured
3. **Set up [[Obsidian Integration]]** for visual knowledge navigation
4. **Learn about [[Canvas]]** visualizations for mapping concepts
5. **Review the [[CLI Reference]]** for command line tools
6. **Explore [[OAuth Authentication Guide]]** for secure remote access (v0.13.0)
7. **Set up multiple projects** for different knowledge areas (v0.13.0)
-207
View File
@@ -1,207 +0,0 @@
---
title: Knowledge Format
type: note
permalink: docs/knowledge-format
tags:
- architecture
- patterns
- knowledge
- design
---
# Knowledge Format
Basic Memory uses standard Markdown with simple semantic patterns to create a knowledge graph. This document details the file structure and patterns used to organize knowledge.
## File-First Architecture
All knowledge in Basic Memory is stored in plain text Markdown files:
- Files are the source of truth for all knowledge
- Changes to files automatically update the knowledge graph
- You maintain complete ownership and control
- Files work with git and other version control systems
- Knowledge persists independently of any AI conversation
## Core Document Structure
Every document uses this basic structure:
```markdown
---
title: Document Title
type: note
tags: [tag1, tag2]
permalink: custom-path
---
# Document Title
Regular markdown content...
## Observations
- [category] Content with #tags (optional context)
## Relations
- relation_type [[Other Document]] (optional context)
```
### Frontmatter
The YAML frontmatter at the top of each file defines essential metadata:
```yaml
---
title: Document Title # Used for linking and references
type: note # Document type
tags: [tag1, tag2] # For organization and searching
permalink: custom-link # Optional custom URL path
---
```
The title is particularly important as it's used to create links between documents.
### Observations
Observations are facts or statements about a topic:
```markdown
## Observations
- [tech] Uses SQLite for storage #database
- [design] Follows local-first architecture #architecture
- [decision] Selected bcrypt for passwords #security (Based on audit)
```
Each observation contains:
- **Category** in [brackets] - classifies the information type
- **Content text** - the main information
- Optional **#tags** - additional categorization
- Optional **(context)** - supporting details
Common categories include:
- `[tech]`: Technical details
- `[design]`: Architecture decisions
- `[feature]`: User capabilities
- `[decision]`: Choices that were made
- `[principle]`: Fundamental concepts
- `[method]`: Approaches or techniques
- `[preference]`: Personal opinions
### Relations
Relations connect documents to form the knowledge graph:
```markdown
## Relations
- implements [[Search Design]]
- depends_on [[Database Schema]]
- relates_to [[User Interface]]
```
You can also create inline references:
```markdown
This builds on [[Core Design]] and uses [[Utility Functions]].
```
Common relation types include:
- `implements`: Implementation of a specification
- `depends_on`: Required dependency
- `relates_to`: General connection
- `inspired_by`: Source of ideas
- `extends`: Enhancement
- `part_of`: Component relationship
- `contains`: Hierarchical relationship
- `pairs_with`: Complementary relationship
## Knowledge Graph
Basic Memory automatically builds a knowledge graph from your document connections:
- Each document becomes a node in the graph
- Relations create edges between nodes
- Relation types add semantic meaning to connections
- Forward references can link to documents that don't exist yet
This graph enables rich context building and navigation across your knowledge base.
## Permalinks and memory:// URLs
Every document in Basic Memory has a unique permalink that serves as its stable identifier:
### How Permalinks Work
- **Automatically assigned**: The system generates a permalink for each document
- **Based on title**: By default, derived from the document title
- **Always unique**: If conflicts exist, the system adds a suffix to ensure uniqueness
- **Stable reference**: Remains the same even if the file moves in the directory structure
- **Used in memory:// URLs**: Forms the basis of the memory:// addressing scheme
You can specify a custom permalink in the frontmatter:
```yaml
---
title: Authentication Approaches
permalink: auth-approaches-2024
---
```
If not specified, one will be generated automatically from the title, if the note has has a frontmatter section.
By default a notes' permalink value will not change if the file is moved. It's a **stable** identifier :). But if you'd rather permalinks are always updated when a file moves, you can set the config setting in the global config.
The config file for Basic Memory is in the home directory under `.basic-memory/config.json`.
To change the behavior, set the following value:
```
~/.basic-memory/config.json
{
"update_permalinks_on_move": true
}
```
### Using memory:// URLs
The memory:// URL scheme provides a reliable way to reference knowledge:
```
memory://auth-approaches-2024 # Direct access by permalink
memory://Authentication Approaches # Access by title (automatically resolves)
memory://project/auth-approaches # Access by path
```
Memory URLs support pattern matching for more powerful queries:
```
memory://auth* # All documents with permalinks starting with "auth"
memory://*/approaches # All documents with permalinks ending with "approaches"
memory://project/*/requirements # All requirements documents in the project folder
memory://docs/search/implements/* # Follow all implements relations from search docs
```
This addressing scheme ensures content remains accessible even as your knowledge base evolves and files are reorganized.
## File Organization
Organize files in any structure that suits your needs:
```
docs/
architecture/
design.md
patterns.md
features/
search.md
auth.md
```
You can:
- Group by topic in folders
- Use a flat structure with descriptive filenames
- Tag files for easier discovery
- Add custom metadata in frontmatter
The system will build the semantic knowledge graph regardless of how you organize your files.
## Relations
- implemented_by [[User Guide]] (How to work with this format)
- relates_to [[Getting Started with Basic Memory]] (Setup instructions)
- explained_in [[Introduction to Basic Memory]] (Overview of the system)
-259
View File
@@ -1,259 +0,0 @@
# OAuth Authentication Guide
Basic Memory MCP server supports OAuth 2.1 authentication for secure access control. This guide covers setup, testing, and production deployment.
## Quick Start
### 1. Enable OAuth
```bash
# Set environment variable
export FASTMCP_AUTH_ENABLED=true
# Or use .env file
echo "FASTMCP_AUTH_ENABLED=true" >> .env
```
### 2. Start the Server
```bash
basic-memory mcp --transport streamable-http
```
### 3. Test with MCP Inspector
Since the basic auth provider uses in-memory storage with per-instance secret keys, you'll need to use a consistent approach:
#### Option A: Use Environment Variable for Secret Key
```bash
# Set a fixed secret key for testing
export FASTMCP_AUTH_SECRET_KEY="your-test-secret-key"
# Start the server
FASTMCP_AUTH_ENABLED=true basic-memory mcp --transport streamable-http
# In another terminal, register a client
basic-memory auth register-client --client-id=test-client
# Get a token using the same secret key
basic-memory auth test-auth
```
#### Option B: Use the Built-in Test Endpoint
```bash
# Start server with OAuth
FASTMCP_AUTH_ENABLED=true basic-memory mcp --transport streamable-http
# Register a client and get token in one step
curl -X POST http://localhost:8000/register \
-H "Content-Type: application/json" \
-d '{"client_metadata": {"client_name": "Test Client"}}'
# Use the returned client_id and client_secret
curl -X POST http://localhost:8000/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET"
```
### 4. Configure MCP Inspector
1. Open MCP Inspector
2. Configure:
- Server URL: `http://localhost:8000/mcp/` (note the trailing slash!)
- Transport: `streamable-http`
- Custom Headers:
```
Authorization: Bearer YOUR_ACCESS_TOKEN
Accept: application/json, text/event-stream
```
## OAuth Endpoints
The server provides these OAuth endpoints automatically:
- `GET /authorize` - Authorization endpoint
- `POST /token` - Token exchange endpoint
- `GET /.well-known/oauth-authorization-server` - OAuth metadata
- `POST /register` - Client registration (if enabled)
- `POST /revoke` - Token revocation (if enabled)
## OAuth Flow
### Standard Authorization Code Flow
1. **Get Authorization Code**:
```bash
curl "http://localhost:8000/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=http://localhost:8000/callback&response_type=code&code_challenge=YOUR_CHALLENGE&code_challenge_method=S256"
```
2. **Exchange Code for Token**:
```bash
curl -X POST http://localhost:8000/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code&code=AUTH_CODE&client_id=CLIENT_ID&client_secret=CLIENT_SECRET&code_verifier=YOUR_VERIFIER"
```
3. **Use Access Token**:
```bash
curl http://localhost:8000/mcp \
-H "Authorization: Bearer ACCESS_TOKEN"
```
## Production Deployment
### Using Supabase Auth
For production, use Supabase for persistent auth storage:
```bash
# Configure environment
FASTMCP_AUTH_ENABLED=true
FASTMCP_AUTH_PROVIDER=supabase
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_KEY=your-service-key
# Start server
basic-memory mcp --transport streamable-http --host 0.0.0.0
```
### Security Requirements
1. **HTTPS Required**: OAuth requires HTTPS in production (localhost exception for testing)
2. **PKCE Support**: Claude.ai requires PKCE for authorization
3. **Token Expiration**: Access tokens expire after 1 hour
4. **Scopes**: Supported scopes are `read`, `write`, and `admin`
## Connecting from Claude.ai
1. **Deploy with HTTPS**:
```bash
# Use ngrok for testing
ngrok http 8000
# Or deploy to cloud provider
```
2. **Configure in Claude.ai**:
- Go to Settings → Integrations
- Click "Add More"
- Enter: `https://your-server.com/mcp`
- Click "Connect"
- Authorize in the popup window
## Debugging
### Common Issues
1. **401 Unauthorized**:
- Check token is valid and not expired
- Verify secret key consistency
- Ensure bearer token format: `Authorization: Bearer TOKEN`
2. **404 on Auth Endpoints**:
- Endpoints are at root, not under `/auth`
- Use `/authorize` not `/auth/authorize`
3. **Token Validation Fails**:
- Basic provider uses in-memory storage
- Tokens don't persist across server restarts
- Use same secret key for testing
### Debug Commands
```bash
# Check OAuth metadata
curl http://localhost:8000/.well-known/oauth-authorization-server
# Enable debug logging
export FASTMCP_LOG_LEVEL=DEBUG
# Test token directly
curl http://localhost:8000/mcp \
-H "Authorization: Bearer YOUR_TOKEN" \
-v
```
## Provider Options
- **basic**: In-memory storage (development only)
- **supabase**: Recommended for production
- **github**: GitHub OAuth integration
- **google**: Google OAuth integration
## Example Test Script
```python
import httpx
import asyncio
from urllib.parse import urlparse, parse_qs
async def test_oauth_flow():
"""Test the full OAuth flow"""
client_id = "test-client"
client_secret = "test-secret"
async with httpx.AsyncClient() as client:
# 1. Get authorization code
auth_response = await client.get(
"http://localhost:8000/authorize",
params={
"client_id": client_id,
"redirect_uri": "http://localhost:8000/callback",
"response_type": "code",
"code_challenge": "test-challenge",
"code_challenge_method": "S256",
"state": "test-state"
}
)
# Extract code from redirect URL
redirect_url = auth_response.headers.get("Location")
parsed = urlparse(redirect_url)
code = parse_qs(parsed.query)["code"][0]
# 2. Exchange for token
token_response = await client.post(
"http://localhost:8000/token",
data={
"grant_type": "authorization_code",
"code": code,
"client_id": client_id,
"client_secret": client_secret,
"code_verifier": "test-verifier",
"redirect_uri": "http://localhost:8000/callback"
}
)
tokens = token_response.json()
print(f"Access token: {tokens['access_token']}")
# 3. Test MCP endpoint
mcp_response = await client.post(
"http://localhost:8000/mcp",
headers={"Authorization": f"Bearer {tokens['access_token']}"},
json={"method": "initialize", "params": {}}
)
print(f"MCP Response: {mcp_response.status_code}")
asyncio.run(test_oauth_flow())
```
## Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| `FASTMCP_AUTH_ENABLED` | Enable OAuth authentication | `false` |
| `FASTMCP_AUTH_PROVIDER` | OAuth provider type | `basic` |
| `FASTMCP_AUTH_SECRET_KEY` | JWT signing key (basic provider) | Random |
| `FASTMCP_AUTH_ISSUER_URL` | OAuth issuer URL | `http://localhost:8000` |
| `FASTMCP_AUTH_REQUIRED_SCOPES` | Required scopes (comma-separated) | `read,write` |
## Next Steps
- [Supabase OAuth Setup](./Supabase%20OAuth%20Setup.md) - Production auth setup
- [External OAuth Providers](./External%20OAuth%20Providers.md) - GitHub, Google integration
- [MCP OAuth Specification](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization) - Official spec
-146
View File
@@ -1,146 +0,0 @@
---
title: Obsidian Integration
type: note
permalink: docs/obsidian-integration
---
# Obsidian Integration
Basic Memory integrates seamlessly with [Obsidian](https://obsidian.md), providing powerful visualization and navigation capabilities for your knowledge graph.
## Setup
### Creating an Obsidian Vault
1. Download and install [Obsidian](https://obsidian.md)
2. Create a new vault
3. Point it to your Basic Memory directory (~/basic-memory by default)
4. Enable core plugins like Graph View, Backlinks, and Tags
## Visualization Features
### Graph View
Obsidian's Graph View provides a visual representation of your knowledge network:
- Each document appears as a node
- Relations appear as connections between nodes
- Colors can be customized to distinguish types
- Filters let you focus on specific aspects
- Local graphs show connections for individual documents
### Backlinks
Obsidian automatically tracks references between documents:
- View all documents that reference the current one
- See the exact context of each reference
- Navigate easily through connections
- Track how concepts relate to each other
### Tag Explorer
Use tags to organize and filter content:
- View all tags in your knowledge base
- See how many documents use each tag
- Filter documents by tag combinations
- Create hierarchical tag structures
## Knowledge Elements
Basic Memory's knowledge format works natively with Obsidian:
### Wiki Links
```markdown
## Relations
- implements [[Search Design]]
- depends_on [[Database Schema]]
```
These display as clickable links in Obsidian and appear in the graph view.
### Observations with Tags
```markdown
## Observations
- [tech] Using SQLite #database
- [design] Local-first #architecture
```
Tags become searchable and filterable in Obsidian's tag pane.
### Frontmatter
```yaml
---
title: Document Title
type: note
tags: [search, design]
---
```
Frontmatter provides metadata for Obsidian to use in search and filtering.
## Canvas Integration
Basic Memory can create [Obsidian Canvas](https://obsidian.md/canvas) files:
1. Ask Claude to create a visualization:
```
You: "Create a canvas showing the structure of our project components."
```
2. Claude generates a .canvas file in your knowledge base
3. Open the file in Obsidian to view and edit the visual representation
4. Canvas files maintain references to your documents
## Recommended Plugins
These Obsidian plugins work especially well with Basic Memory:
- **Dataview**: Query your knowledge base programmatically
- **Kanban**: Organize tasks from knowledge files
- **Calendar**: View and navigate temporal knowledge
- **Templates**: Create consistent knowledge structures
## Workflow Suggestions
### Daily Notes
```markdown
# 2024-01-21
## Progress
- Updated [[Search Design]]
- Fixed [[Bug Report 123]]
## Notes
- [idea] Better indexing #enhancement
- [todo] Update docs #documentation
## Links
- relates_to [[Current Sprint]]
- updates [[Project Status]]
```
### Project Tracking
```markdown
# Current Sprint
## Tasks
- [ ] Update [[Search]]
- [ ] Fix [[Auth Bug]]
## Tags
#sprint #planning #current
```
## Relations
- enhances [[Introduction to Basic Memory]] (Overview of system)
- relates_to [[Canvas]] (Visual knowledge mapping)
- complements [[User Guide]] (Using Basic Memory)
-311
View File
@@ -1,311 +0,0 @@
# Supabase OAuth Setup for Basic Memory
This guide explains how to set up Supabase as the OAuth provider for Basic Memory MCP server in production.
## Prerequisites
1. A Supabase project (create one at [supabase.com](https://supabase.com))
2. Basic Memory MCP server deployed
3. Environment variables configuration
## Overview
The Supabase OAuth provider offers:
- Production-ready authentication with persistent storage
- User management through Supabase Auth
- JWT token validation
- Integration with Supabase's security features
- Support for social logins (GitHub, Google, etc.)
## Setup Steps
### 1. Get Supabase Credentials
From your Supabase project dashboard:
1. Go to Settings > API
2. Copy these values:
- `Project URL``SUPABASE_URL`
- `anon public` key → `SUPABASE_ANON_KEY`
- `service_role` key → `SUPABASE_SERVICE_KEY` (keep this secret!)
- JWT secret → `SUPABASE_JWT_SECRET` (under Settings > API > JWT Settings)
### 2. Configure Environment Variables
Create a `.env` file:
```bash
# Enable OAuth
FASTMCP_AUTH_ENABLED=true
FASTMCP_AUTH_PROVIDER=supabase
# Your MCP server URL
FASTMCP_AUTH_ISSUER_URL=https://your-mcp-server.com
# Supabase configuration
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_KEY=your-service-key
SUPABASE_JWT_SECRET=your-jwt-secret
# Allowed OAuth clients (comma-separated)
SUPABASE_ALLOWED_CLIENTS=web-app,mobile-app,cli-tool
# Required scopes
FASTMCP_AUTH_REQUIRED_SCOPES=read,write
```
### 3. Create OAuth Clients Table (Optional)
For production, create a table to store OAuth clients in Supabase:
```sql
CREATE TABLE oauth_clients (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
client_id TEXT UNIQUE NOT NULL,
client_secret TEXT NOT NULL,
name TEXT,
redirect_uris TEXT[],
allowed_scopes TEXT[],
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Create an index for faster lookups
CREATE INDEX idx_oauth_clients_client_id ON oauth_clients(client_id);
-- RLS policies
ALTER TABLE oauth_clients ENABLE ROW LEVEL SECURITY;
-- Only service role can manage clients
CREATE POLICY "Service role can manage clients" ON oauth_clients
FOR ALL USING (auth.jwt()->>'role' = 'service_role');
```
### 4. Set Up Auth Flow
The Supabase OAuth provider handles the following flow:
1. **Client Authorization Request**
```
GET /authorize?client_id=web-app&redirect_uri=https://app.com/callback
```
2. **Redirect to Supabase Auth**
- User authenticates with Supabase (email/password, magic link, or social login)
- Supabase redirects back to your MCP server
3. **Token Exchange**
```
POST /token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=xxx&client_id=web-app
```
4. **Access Protected Resources**
```
GET /mcp
Authorization: Bearer <access_token>
```
### 5. Enable Social Logins (Optional)
In Supabase dashboard:
1. Go to Authentication > Providers
2. Enable desired providers (GitHub, Google, etc.)
3. Configure OAuth apps for each provider
4. Users can now log in via social providers
### 6. User Management
Supabase provides:
- User registration and login
- Password reset flows
- Email verification
- User metadata storage
- Admin APIs for user management
Access user data in your MCP tools:
```python
# In your MCP tool
async def get_user_info(ctx: Context):
# The token is already validated by the OAuth middleware
user_id = ctx.auth.user_id
email = ctx.auth.email
# Use Supabase client to get more user data if needed
user = await supabase.auth.admin.get_user_by_id(user_id)
return user
```
### 7. Production Deployment
1. **Environment Security**
- Never expose `SUPABASE_SERVICE_KEY`
- Use environment variables, not hardcoded values
- Rotate keys periodically
2. **HTTPS Required**
- Always use HTTPS in production
- Configure proper SSL certificates
3. **Rate Limiting**
- Implement rate limiting for auth endpoints
- Use Supabase's built-in rate limiting
4. **Monitoring**
- Monitor auth logs in Supabase dashboard
- Set up alerts for suspicious activity
## Testing
### Local Development
For local testing with Supabase:
```bash
# Start MCP server with Supabase auth
FASTMCP_AUTH_ENABLED=true \
FASTMCP_AUTH_PROVIDER=supabase \
SUPABASE_URL=http://localhost:54321 \
SUPABASE_ANON_KEY=your-local-anon-key \
bm mcp --transport streamable-http
```
### Test Authentication Flow
```python
import httpx
import asyncio
async def test_supabase_auth():
# 1. Register/login with Supabase directly
supabase_url = "https://your-project.supabase.co"
# 2. Get MCP authorization URL
response = await httpx.get(
"http://localhost:8000/authorize",
params={
"client_id": "web-app",
"redirect_uri": "http://localhost:3000/callback",
"response_type": "code",
}
)
# 3. User logs in via Supabase
# 4. Exchange code for MCP tokens
# 5. Access protected resources
asyncio.run(test_supabase_auth())
```
## Advanced Configuration
### Custom User Metadata
Store additional user data in Supabase:
```sql
-- Add custom fields to auth.users
ALTER TABLE auth.users
ADD COLUMN IF NOT EXISTS metadata JSONB DEFAULT '{}';
-- Or create a separate profiles table
CREATE TABLE profiles (
id UUID REFERENCES auth.users PRIMARY KEY,
username TEXT UNIQUE,
avatar_url TEXT,
bio TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
```
### Row Level Security (RLS)
Protect user data with RLS:
```sql
-- Users can only access their own data
CREATE POLICY "Users can view own profile" ON profiles
FOR SELECT USING (auth.uid() = id);
CREATE POLICY "Users can update own profile" ON profiles
FOR UPDATE USING (auth.uid() = id);
```
### Custom Claims
Add custom claims to JWT tokens:
```sql
-- Function to add custom claims
CREATE OR REPLACE FUNCTION custom_jwt_claims()
RETURNS JSON AS $$
BEGIN
RETURN json_build_object(
'user_role', current_setting('request.jwt.claims')::json->>'user_role',
'permissions', current_setting('request.jwt.claims')::json->>'permissions'
);
END;
$$ LANGUAGE plpgsql;
```
## Troubleshooting
### Common Issues
1. **Invalid JWT Secret**
- Ensure `SUPABASE_JWT_SECRET` matches your Supabase project
- Check Settings > API > JWT Settings in Supabase dashboard
2. **CORS Errors**
- Configure CORS in your MCP server
- Add allowed origins in Supabase dashboard
3. **Token Validation Fails**
- Verify tokens are being passed correctly
- Check token expiration times
- Ensure scopes match requirements
4. **User Not Found**
- Confirm user exists in Supabase Auth
- Check if email is verified (if required)
- Verify client permissions
### Debug Mode
Enable debug logging:
```bash
export FASTMCP_LOG_LEVEL=DEBUG
export SUPABASE_LOG_LEVEL=debug
```
## Security Best Practices
1. **Secure Keys**: Never commit secrets to version control
2. **Least Privilege**: Use minimal required scopes
3. **Token Rotation**: Implement refresh token rotation
4. **Audit Logs**: Monitor authentication events
5. **Rate Limiting**: Protect against brute force attacks
6. **HTTPS Only**: Always use encrypted connections
## Migration from Basic Auth
To migrate from the basic auth provider:
1. Export existing user data
2. Import users into Supabase Auth
3. Update client applications to use new auth flow
4. Gradually transition users to Supabase login
## Next Steps
- Set up email templates in Supabase
- Configure password policies
- Implement MFA (multi-factor authentication)
- Add social login providers
- Create admin dashboard for user management
-243
View File
@@ -1,243 +0,0 @@
---
title: Technical Information
type: note
permalink: docs/technical-information
---
# Technical Information
This document provides technical details about Basic Memory's implementation, licensing, and integration with the Model Context Protocol (MCP).
## Architecture
Basic Memory consists of:
1. **Core Knowledge Engine**: Parses and indexes Markdown files
2. **SQLite Database**: Provides fast querying and search
3. **MCP Server**: Implements the Model Context Protocol
4. **CLI Tools**: Command-line utilities for management
5. **Sync Service**: Monitors file changes and updates the database
The system follows a file-first architecture where all knowledge is represented in standard Markdown files and the database serves as a secondary index.
## Model Context Protocol (MCP)
Basic Memory implements the [Model Context Protocol](https://github.com/modelcontextprotocol/spec), an open standard for enabling AI models to access external tools:
- **Standardized Interface**: Common protocol for tool integration
- **Tool Registration**: Basic Memory registers as a tool provider
- **Asynchronous Communication**: Enables efficient interaction with AI models
- **Standardized Schema**: Structured data exchange format
Integration with Claude Desktop uses the MCP to grant Claude access to your knowledge base through a set of specialized tools that search, read, and write knowledge.
## Licensing
Basic Memory is licensed under the [GNU Affero General Public License v3.0 (AGPL-3.0)](https://www.gnu.org/licenses/agpl-3.0.en.html):
- **Free Software**: You can use, study, share, and modify the software
- **Copyleft**: Derivative works must be distributed under the same license
- **Network Use**: Network users must be able to receive the source code
- **Commercial Use**: Allowed, subject to license requirements
The AGPL license ensures Basic Memory remains open source while protecting against proprietary forks.
## Source Code
Basic Memory is developed as an open-source project:
- **GitHub Repository**: [https://github.com/basicmachines-co/basic-memory](https://github.com/basicmachines-co/basic-memory)
- **Issue Tracker**: Report bugs and request features on GitHub
- **Contributions**: Pull requests are welcome following the contributing guidelines
- **Documentation**: Source for this documentation is also available in the repository
## Data Storage and Privacy
Basic Memory is designed with privacy as a core principle:
- **Local-First**: All data remains on your local machine
- **No Cloud Dependency**: No remote servers or accounts required
- **Telemetry**: Optional and disabled by default
- **Standard Formats**: All data is stored in standard file formats you control
## Implementation Details
Knowledge in Basic Memory is organized as a semantic graph:
1. **Entities** - Distinct concepts represented by Markdown documents
2. **Observations** - Categorized facts and information about entities
3. **Relations** - Connections between entities that form the knowledge graph
This structure emerges from simple text patterns in standard Markdown:
```markdown
---
title: Coffee Brewing Methods
type: note
permalink: coffee/coffee-brewing-methods
tags:
- '#coffee'
- '#brewing'
- '#methods'
- '#demo'
---
# Coffee Brewing Methods
An exploration of different coffee brewing techniques, their characteristics, and how they affect flavor extraction.
## Overview
Coffee brewing is both an art and a science. Different brewing methods extract different compounds from coffee beans,
resulting in unique flavor profiles, body, and mouthfeel. The key variables in any brewing method are:
- Grind size
- Water temperature
- Brew time
- Coffee-to-water ratio
- Agitation/turbulence
## Observations
- [principle] Coffee extraction follows a predictable pattern: acids extract first, then sugars, then bitter compounds
#extraction
- [method] Pour over methods generally produce cleaner, brighter cups with more distinct flavor notes #clarity
## Relations
- requires [[Proper Grinding Technique]]
- affects [[Flavor Extraction]]
```
Becomes
```json
{
"entities": [
{
"permalink": "coffee/coffee-brewing-methods",
"title": "Coffee Brewing Methods",
"file_path": "Coffee Notes/Coffee Brewing Methods.md",
"entity_type": "note",
"entity_metadata": {
"title": "Coffee Brewing Methods",
"type": "note",
"permalink": "coffee/coffee-brewing-methods",
"tags": "['#coffee', '#brewing', '#methods', '#demo']"
},
"checksum": "bfa32a0f23fa124b53f0694c344d2788b0ce50bd090b55b6d738401d2a349e4c",
"content_type": "text/markdown",
"observations": [
{
"category": "principle",
"content": "Coffee extraction follows a predictable pattern: acids extract first, then sugars, then bitter compounds #extraction",
"tags": [
"extraction"
],
"permalink": "coffee/coffee-brewing-methods/observations/principle/coffee-extraction-follows-a-predictable-pattern-acids-extract-first-then-sugars-then-bitter-compounds-extraction"
},
{
"category": "method",
"content": "Pour over methods generally produce cleaner, brighter cups with more distinct flavor notes #clarity",
"tags": [
"clarity"
],
"permalink": "coffee/coffee-brewing-methods/observations/method/pour-over-methods-generally-produce-cleaner-brighter-cups-with-more-distinct-flavor-notes-clarity"
}
],
"relations": [
{
"from_id": "coffee/coffee-bean-origins",
"to_id": "coffee/coffee-brewing-methods",
"relation_type": "pairs_with",
"permalink": "coffee/coffee-bean-origins/pairs-with/coffee/coffee-brewing-methods",
"to_name": "Coffee Brewing Methods"
},
{
"from_id": "coffee/flavor-extraction",
"to_id": "coffee/coffee-brewing-methods",
"relation_type": "affected_by",
"permalink": "coffee/flavor-extraction/affected-by/coffee/coffee-brewing-methods",
"to_name": "Coffee Brewing Methods"
}
],
"created_at": "2025-03-06T14:01:23.445071",
"updated_at": "2025-03-06T13:34:48.563606"
}
]
}
```
Basic Memory understands how to build context via its semantic graph.
### Entity Model
Basic Memory's core data model consists of:
- **Entities**: Documents in your knowledge base
- **Observations**: Facts or statements about entities
- **Relations**: Connections between entities
- **Tags**: Additional categorization for entities and observations
The system parses Markdown files to extract this structured information while preserving the human-readable format.
### Files as Source of Truth
Plain Markdown files store all knowledge, making it accessible with any text editor and easy to version with git.
```mermaid
flowchart TD
User((User)) <--> |Conversation| Claude["Claude or other LLM"]
Claude <-->|API Calls| BMCP["Basic Memory MCP Server"]
subgraph "Local Storage"
KnowledgeFiles["Markdown Files - Source of Truth"]
KnowledgeIndex[(Knowledge Graph SQLite Index)]
end
BMCP <-->|"write_note() read_note()"| KnowledgeFiles
BMCP <-->|"search_notes() build_context()"| KnowledgeIndex
KnowledgeFiles <-.->|Sync Process| KnowledgeIndex
KnowledgeFiles <-->|Direct Editing| Editors((Text Editors & Git))
User -.->|"Complete control, Privacy preserved"| KnowledgeFiles
class Claude primary
class BMCP secondary
class KnowledgeFiles tertiary
class KnowledgeIndex quaternary
class User,Editors user`;
```
### Sqlite Database
A local SQLite database maintains the knowledge graph topology for fast queries and semantic traversal without cloud dependencies. It contains:
- db tables for the knowledge graph schema
- a search index table enabling full text search across the knowledge base
### Sync Process
The sync process:
1. Detects changes to files in the knowledge directory
2. Parses modified files to extract structured data
3. Updates the SQLite database with changes
4. Resolves forward references when new entities are created
5. Updates the search index for fast querying
### Search Engine
The search functionality:
1. Uses a combination of full-text search and semantic matching
2. Indexes observations, relations, and content
3. Supports wildcards and pattern matching in memory:// URLs
4. Traverses the knowledge graph to follow relationships
5. Ranks results by relevance to the query
## Relations
- relates_to [[Welcome to Basic memory]] (Overview)
- relates_to [[CLI Reference]] (Command line tools)
- implements [[Knowledge Format]] (File structure and format)
-657
View File
@@ -1,657 +0,0 @@
---
title: User Guide
type: note
permalink: docs/user-guide
---
# User Guide
This guide explains how to effectively use Basic Memory in your daily workflow, from creating knowledge through
conversations to building a rich semantic network.
## Basic Memory Workflow
Using Basic Memory follows a natural cycle:
1. **Have conversations** with AI assistants like Claude
2. **Capture knowledge** in Markdown files
3. **Build connections** between pieces of knowledge
4. **Reference your knowledge** in future conversations
5. **Edit files directly** when needed
6. **Sync changes** automatically
## Creating Knowledge
### Through Conversations
To create knowledge during conversations with Claude:
```
You: We've covered several authentication approaches. Could you create a note summarizing what we've discussed?
Claude: I'll create a note summarizing our authentication discussion.
```
This creates a Markdown file in your `~/basic-memory` directory with semantic markup.
### Direct File Creation
You can create files directly:
1. Create a new Markdown file in your `~/basic-memory` directory
2. Add frontmatter with title, type, and optional tags
3. Structure content with observations and relations
4. Save the file
5. Run `basic-memory sync` if not in watch mode
## Using Special Prompts
Basic Memory includes several special prompts that help you leverage your knowledge base more effectively. In apps like
Claude Desktop, these prompts trigger specific tools to search and analyze your knowledge base.
### Continue Conversation
When you want to pick up where you left off on a topic:
```
You: Let's continue our conversation about authentication systems.
```
Behind the scenes:
- Claude searches your knowledge base for content about "authentication systems"
- It retrieves relevant documents and their relations
- It analyzes the context to understand where you left off
- It builds a comprehensive picture of what you've previously discussed
- It can then resume the conversation with all that context
This is particularly useful when:
- Starting a new session days or weeks after your last discussion
- Switching between multiple ongoing projects
- Building on previous work without repeating yourself
### Recent Activity
To get an overview of what you've been working on:
```
You: What have we been discussing recently?
```
Behind the scenes:
- Claude retrieves documents modified recently
- It analyzes patterns and themes
- It summarizes the key topics and changes
- It offers to continue working on any of those topics
This is useful for:
- Coming back after a break
- Getting a quick reminder of ongoing projects
- Deciding what to work on next
### Search
To find specific information in your knowledge base:
```
You: Find information about JWT authentication in my notes.
```
Behind the scenes:
- Claude performs a semantic search for "JWT authentication"
- It retrieves and ranks the most relevant documents
- It summarizes the key findings
- It offers to explore specific areas in more detail
This is useful for:
- Finding specific information quickly
- Exploring what you know about a topic
- Starting work on an existing topic
### Example
Choose "Continue Conversation"
![[prompt 1.png|500]]
Enter a topic
![[prompt2.png|500]]
Give instructions
![[prompt3.png|500]]
Claude Desktop lets you send a prompt to provide context. You can use this at the beginning of a chat to preload context
without needing to copy paste all the time. By using one of the supplied prompts, Basic Memory will search the knowledge
base and give the AI instructions for how to build context.
Choose "Continue Conversation":
![[prompt 1.png|500]]
Enter a topic:
![[prompt2.png|500]]
Give optional additional instructions:
![[prompt3.png|500]]
Claude can build context from the supplied topic. This works independently of Claude Project information. All the
context comes from your local knowledge base.
![[prompt4.png|500]]
## Searching Your Knowledge Base
Basic Memory provides multiple ways to search and explore your knowledge base:
### Natural Language Search
The simplest way to search is to ask Claude directly:
```
You: What do I know about authentication methods?
```
Claude will search your knowledge base semantically and return relevant information.
### Search Prompt
Use the dedicated search prompt for more focused searches:
```
You: Search for "JWT authentication"
```
This triggers a specialized search that returns precise results with document titles, relevant excerpts, and offers to
explore specific documents.
### Boolean Search
For more precise searches, use boolean operators to refine your queries:
```
You: Search for "authentication AND OAuth NOT basic"
```
Basic Memory supports standard boolean operators:
- **AND**: Find documents containing both terms
```
You: Search for "python AND flask"
```
This finds documents containing both "python" and "flask"
- **OR**: Find documents containing either term
```
You: Search for "python OR javascript"
```
This finds documents containing either "python" or "javascript"
- **NOT**: Exclude documents containing specific terms
```
You: Search for "python NOT django"
```
This finds documents containing "python" but excludes those containing "django"
- **Grouping with parentheses**: Control operator precedence
```
You: Search for "(python OR javascript) AND web"
```
This finds documents about web development that mention either Python or JavaScript
Boolean search is particularly useful for:
- Narrowing down results in large knowledge bases
- Finding specific combinations of concepts
- Excluding irrelevant content from search results
- Creating complex queries for precise information retrieval
### Memory URL Pattern Matching
For advanced searches, use memory:// URL patterns with wildcards:
```
You: Look at memory://auth* and summarize all authentication approaches.
```
Pattern matching supports:
- **Wildcards**: `memory://auth*` matches all permalinks starting with "auth"
- **Path patterns**: `memory://project/*/auth` matches auth documents in any project subfolder
- **Relation traversal**: `memory://auth-system/implements/*` finds all documents that implement the auth system
### Combining Search with Context Building
The most powerful searches build comprehensive context by following relationships:
```
You: Search for JWT authentication and then follow all implementation relations.
```
This builds a complete picture by:
1. Finding documents about JWT authentication
2. Following implementation relationships from those documents
3. Building a complete picture of how JWT is implemented across your system
### Search Best Practices
For effective searching:
1. **Be specific** with search terms and phrases
2. **Use boolean operators** to refine searches and find precise information
3. **Use technical terms** when searching for technical content
4. **Follow up** on search results by asking for more details about specific documents
5. **Combine approaches** by starting with search and then using memory:// URLs for precision
6. **Use relation traversal** to explore connected concepts after finding initial documents
## Referencing Knowledge
### Using memory:// URLs
Reference specific knowledge directly:
```
You: Please look at memory://authentication-approaches and suggest which approach would be best for our mobile app.
```
### Natural Language References
Reference knowledge conversationally:
```
You: What did we decide about authentication for the project?
```
### Advanced References
Follow connections across your knowledge graph:
```
You: Look at memory://project-architecture and check related documents to give me a complete picture.
```
## Working with Files
### File Location and Organization
By default, Basic Memory stores files in `~/basic-memory`:
- Browse this directory in your file explorer
- Organize files into subfolders
- Use git for version control
### File Format
Each knowledge file follows this structure:
```markdown
---
title: Authentication Approaches
type: note
tags: [security, architecture]
permalink: authentication-approaches
---
# Authentication Approaches
A comparison of authentication methods.
## Observations
- [approach] JWT provides stateless authentication #security
- [limitation] Session tokens require server-side storage #infrastructure
## Relations
- implements [[Security Requirements]]
- affects [[User Login Flow]]
```
### Editing Files
Modify files in any text editor:
1. Open the file in your preferred editor
2. Make changes to content, observations, or relations
3. Save the file
4. Basic Memory detects changes automatically when running in watch mode
## Building a Knowledge Graph
The value of Basic Memory comes from connections between pieces of knowledge.
### Creating Relations
When creating or editing notes, build connections:
```markdown
## Relations
- implements [[Security Requirements]]
- depends_on [[User Authentication]]
```
Relations can be:
- Hierarchical (part_of, contains)
- Directional (implements, depends_on)
- Associative (relates_to, similar_to)
- Temporal (precedes, follows)
Relations are also created via regular wiki-link style links within the body text.
### Forward References
Reference documents that don't exist yet:
```markdown
- will_impact [[Future Feature]]
```
These references resolve automatically when you create the referenced document.
## Conversation Continuity
Basic Memory maintains context across different conversations.
### Starting New Sessions with Context
When starting a new conversation with Claude, you can:
1. **Use special prompts** like "Continue conversation about..." or "What were we working on?"
2. **Reference specific documents** with memory:// URLs
3. **Ask about recent work** with "What have we been discussing recently?"
4. **Search for specific topics** with "Find information about..."
### Long-Term Projects
Maintain context for complex projects over time:
1. **Document key decisions** as you make them
2. **Create relationships** between project components
3. **Reference past decisions** when implementing features
4. **Update documentation** as the project evolves
### Tips for Effective Continuity
1. **Be specific about topics** when continuing a conversation
2. **Reference documents directly** with memory:// URLs for precision
3. **Create summary notes** after important discussions
4. **Update existing notes** rather than creating duplicates
5. **Build robust connections** between related topics
## Advanced Features
### Note Editing (New in v0.13.0)
**Edit notes incrementally without rewriting entire documents:**
```
💬 "Add a new section about deployment to my API documentation"
🤖 [Uses edit_note to append new section]
💬 "Update the date at the top of my meeting notes"
🤖 [Uses edit_note to prepend new timestamp]
💬 "Replace the implementation section in my design doc"
🤖 [Uses edit_note to replace specific section]
```
Available editing operations:
- **Append**: Add content to end of notes
- **Prepend**: Add content to beginning of notes
- **Replace Section**: Replace content under specific headers
- **Find & Replace**: Simple text replacements with validation
### File Management (New in v0.13.0)
**Move and organize notes with full database consistency:**
```
💬 "Move my old meeting notes to the archive folder"
🤖 [Uses move_note with automatic folder creation and database updates]
💬 "Reorganize my project files into a better structure"
🤖 [Moves files while maintaining search indexes and links]
```
Move operations include:
- **Database Consistency**: Updates file paths, permalinks, and checksums
- **Search Reindexing**: Maintains search functionality after moves
- **Folder Creation**: Automatically creates destination directories
- **Project Isolation**: Moves are contained within the current project
- **Rollback Protection**: Ensures data integrity during failed operations
### Enhanced Search (New in v0.13.0)
**Frontmatter tags are now searchable:**
```yaml
---
title: Coffee Brewing Methods
tags: [coffee, brewing, equipment]
---
```
Now searchable by: "coffee", "brewing", "equipment", or "Coffee Brewing Methods"
### Importing External Knowledge
Import existing conversations:
```bash
# From Claude
basic-memory import claude conversations
# From ChatGPT
basic-memory import chatgpt
# Target specific projects (v0.13.0)
basic-memory --project=work import claude conversations
```
After importing, changes sync automatically in real-time.
### Obsidian Integration
Use with [Obsidian](https://obsidian.md):
1. Point Obsidian to your `~/basic-memory` directory
2. Use Obsidian's graph view to visualize your knowledge network
3. All changes sync back to Basic Memory
### Canvas Visualizations
Create visual knowledge maps:
```
You: Could you create a canvas visualization of our project components?
```
This generates an Obsidian canvas file showing the relationships between concepts.
### Advanced Memory URI Patterns
Use wildcards and patterns:
```
You: Review memory://project/*/requirements to summarize all project requirements.
```
## Command Line Interface
### Sync Commands
```bash
# One-time sync
basic-memory sync
# Watch for changes
basic-memory sync --watch
```
### Status and Information
```bash
# Check system status
basic-memory status
# View CLI help
basic-memory --help
```
### Import Commands
```bash
# Import from Claude
basic-memory import claude conversations
# Import from ChatGPT
basic-memory import chatgpt
```
## Multiple Projects (v0.13.0)
Basic Memory v0.13.0 introduces **fluid project management** - the ability to switch between projects instantly during conversations without restart. This allows you to maintain separate knowledge graphs for different purposes while seamlessly switching between them.
### Instant Project Switching (New in v0.13.0)
**Switch projects during conversations:**
```
💬 "What projects do I have?"
🤖 Available projects:
• main (current, default)
• work-notes
• personal-journal
• code-snippets
💬 "Switch to work-notes"
🤖 ✓ Switched to work-notes project
Project Summary:
• 47 entities
• 125 observations
• 23 relations
💬 "What did I work on yesterday?"
🤖 [Shows recent activity from work-notes project]
```
### Project-Specific Operations (New in v0.13.0)
Some MCP tools support optional project parameters for targeting specific projects:
```
💬 "Create a note about this meeting in my personal-notes project"
🤖 [Creates note in personal-notes project]
💬 "Switch to my work project"
🤖 [Switches project context, then all operations work within that project]
```
**Note**: Operations like search, move, and edit work within the currently active project. To work with content in different projects, switch to that project first or use the project parameter where supported.
### Managing Projects
```bash
# List all configured projects
basic-memory project list
# Add a new project
basic-memory project create work ~/work-basic-memory
# Set the default project
basic-memory project set-default work
# Remove a project (doesn't delete files)
basic-memory project delete personal
# Show current project statistics
basic-memory project info
```
### Using Projects in Commands
All commands support the `--project` flag to specify which project to use:
```bash
# Sync a specific project
basic-memory --project=work sync
# Run MCP server for a specific project
basic-memory --project=personal mcp
```
You can also set the `BASIC_MEMORY_PROJECT` environment variable:
```bash
BASIC_MEMORY_PROJECT=work basic-memory sync
```
### Unified Database Architecture (New in v0.13.0)
Basic Memory v0.13.0 uses a unified database architecture:
- **Single Database**: All projects share `~/.basic-memory/memory.db`
- **Project Isolation**: Proper data separation with project context
- **Better Performance**: Optimized queries and reduced file I/O
- **Easier Backup**: Single database file contains all project data
- **Session Context**: Maintains active project throughout conversations
## Workflow Tips
### General Workflow
1. **Project Organization**: Use multiple projects to separate different areas (work, personal, research)
2. **Session Context**: Switch projects during conversations without restart (v0.13.0)
3. **Real-time Sync**: Changes sync automatically - no need to run watch mode
4. **Review Content**: Edit AI-created content for accuracy
5. **Build Connections**: Create rich relationships between related ideas
6. **Use Special Prompts**: Start conversations with context from your knowledge base
### v0.13.0 Workflow Enhancements
7. **Incremental Editing**: Use edit_note for small changes instead of rewriting entire documents
8. **File Organization**: Move and reorganize notes as your knowledge base grows
9. **Project-Specific Creation**: Create notes in specific projects using project parameters
10. **Search Tags**: Use frontmatter tags to improve content discoverability
11. **Project Statistics**: Monitor project growth and activity with project info commands
## Troubleshooting
### Sync Issues
If changes aren't showing up:
1. Run `basic-memory status` to check system state
2. Try a manual sync with `basic-memory sync`
### Missing Content
If content isn't found:
1. Check the exact path and permalink
2. Try searching with more general terms
3. Verify the file exists in your knowledge base
### Relation Problems
If relations aren't working:
1. Ensure exact title matching in [[WikiLinks]]
2. Check for typos in relation types
3. Verify both documents exist
## Relations
- implements [[Knowledge Format]] (How knowledge is structured)
- relates_to [[Getting Started with Basic Memory]] (Setup and first steps)
- relates_to [[Canvas]] (Creating visual knowledge maps)
- relates_to [[CLI Reference]] (Command line tools)
- enhanced_in_v0.13.0 [[OAuth Authentication Guide]] (Production authentication)
- enhanced_in_v0.13.0 [[Project Management]] (Multi-project workflows)
-68
View File
@@ -1,68 +0,0 @@
---
title: Introduction to Basic Memory
type: docs
permalink: docs/introduction
tags:
- documentation
- index
- overview
---
# BASIC MEMORY
Basic Memory is a knowledge management system that allows you to build a persistent semantic graph from conversations
with AI assistants. All knowledge is stored in standard Markdown files on your computer, giving you full control and
ownership of your data.
Basic Memory connects you and AI assistants through shared knowledge:
1. **Captures knowledge** from natural conversations with AI assistants
2. **Structures information** using simple semantic patterns in Markdown
3. **Enables knowledge reuse** across different conversations and sessions
4. **Maintains persistence** through local files you control completely
Both you and AI assistants like Claude can read from and write to the same knowledge base, creating a continuous
learning environment where each conversation builds upon previous ones.
## Pick up your conversation right where you left off
- AI assistants can load context from local files in a new conversation
- Notes are saved locally as Markdown files in real time
- No project knowledge or special prompting required
![[Claude-Obsidian-Demo.mp4]]
Basic Memory uses:
- **Files as the source of truth** - Everything is stored in plain Markdown files
- **Git-compatible storage** - All knowledge can be versioned, branched, and merged
- **Local SQLite database** - For fast indexing and searching only (not primary storage)
- **Model Context Protocol (MCP)** - For seamless AI assistant integration
Basic Memory gives you complete control over your knowledge:
- **Local-first storage** - All knowledge lives on your computer
- **Standard file formats** - Plain Markdown compatible with any editor
- **Directory organization** - Knowledge stored in `~/basic-memory` by default
- **Version control ready** - Use git for history, branching, and collaboration
- **Edit anywhere** - Modify files with any text editor or Obsidian
Changes to files automatically sync with the knowledge graph, and AI assistants can see your edits in conversations.
## Documentation Map
Continue exploring Basic Memory with these guides:
- Installation and setup [[Getting Started with Basic Memory]]
- Comprehensive usage instructions [[User Guide]]
- Detailed explanation of knowledge structure [[Knowledge Format]]
- Obsidian integration guide [[Obsidian Integration]]
- Canvas visualization guide [[Canvas]]
- Command line tool reference [[CLI Reference]]
- Reference for AI assistants using Basic Memory [[AI Assistant Guide]]
- Technical implementation details [[Technical Information]]
## Next Steps
Start with the [[Getting Started with Basic Memory]] guide to install Basic Memory and configure it with your AI
assistant.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 374 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 908 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 302 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 337 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 277 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

-5
View File
@@ -1,5 +0,0 @@
var analyticsScript = document.createElement('script');
analyticsScript.defer = true;
analyticsScript.setAttribute('data-website-id', '8d51086e-5c67-401e-97b0-b24706a6d4f3');
analyticsScript.src = 'https://cloud.umami.is/script.js';
document.head.appendChild(analyticsScript);
-3
View File
@@ -1,3 +0,0 @@
{
"promptDelete": false
}
-1
View File
@@ -1 +0,0 @@
{}
-3
View File
@@ -1,3 +0,0 @@
[
"optimize-canvas-connections"
]
-31
View File
@@ -1,31 +0,0 @@
{
"file-explorer": true,
"global-search": true,
"switcher": true,
"graph": true,
"backlink": true,
"canvas": true,
"outgoing-link": true,
"tag-pane": true,
"properties": false,
"page-preview": true,
"daily-notes": true,
"templates": true,
"note-composer": true,
"command-palette": true,
"slash-command": false,
"editor-status": true,
"bookmarks": true,
"markdown-importer": false,
"zk-prefixer": false,
"random-note": false,
"outline": true,
"word-count": true,
"slides": false,
"audio-recorder": false,
"workspaces": false,
"file-recovery": true,
"publish": true,
"sync": true,
"webviewer": false
}
@@ -1,158 +0,0 @@
/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source, please visit the github repository of this plugin
*/
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// main.ts
var main_exports = {};
__export(main_exports, {
default: () => OptimizeCanvasConnectionsPlugin
});
module.exports = __toCommonJS(main_exports);
var import_obsidian = require("obsidian");
var OptimizeCanvasConnectionsPlugin = class extends import_obsidian.Plugin {
async onload() {
this.addCommand({
id: "optimize-preserve-axes-selection",
name: "Optimize selection (preserve axes)",
checkCallback: (checking) => {
const canvasView = app.workspace.getActiveViewOfType(import_obsidian.ItemView);
if ((canvasView == null ? void 0 : canvasView.getViewType()) == "canvas") {
if (!checking) {
this.optimize("preserve-axes");
}
return true;
}
return false;
}
});
this.addCommand({
id: "optimize-shortest-path-selection",
name: "Optimize selection (shortest path)",
checkCallback: (checking) => {
const canvasView = app.workspace.getActiveViewOfType(import_obsidian.ItemView);
if ((canvasView == null ? void 0 : canvasView.getViewType()) == "canvas") {
if (!checking) {
this.optimize("shortest-path");
}
return true;
}
return false;
}
});
}
onunload() {
}
async optimize(option) {
const canvasView = app.workspace.getActiveViewOfType(import_obsidian.ItemView);
const canvas = canvasView == null ? void 0 : canvasView.canvas;
const currentSelection = canvas == null ? void 0 : canvas.selection;
let selectedIDs = new Array();
currentSelection.forEach(function(selection) {
selectedIDs.push(selection.id);
});
let applyToAll = false;
if (selectedIDs.length == 0) {
applyToAll = true;
}
for (let [edgeKey, edge] of canvas["edges"]) {
let fromNode = edge["from"]["node"];
let toNode = edge["to"]["node"];
let fromPossibilities = [edge["from"]["side"]];
if (applyToAll || selectedIDs.includes(fromNode["id"])) {
switch (option) {
case "shortest-path":
fromPossibilities = ["top", "bottom", "left", "right"];
break;
case "preserve-axes":
switch (edge["from"]["side"]) {
case "top":
case "bottom":
fromPossibilities = ["top", "bottom"];
break;
case "left":
case "right":
fromPossibilities = ["left", "right"];
break;
}
}
}
let toPossibilities = [edge["to"]["side"]];
if (applyToAll || selectedIDs.includes(toNode["id"])) {
switch (option) {
case "shortest-path":
toPossibilities = ["top", "bottom", "left", "right"];
break;
case "preserve-axes":
switch (edge["to"]["side"]) {
case "top":
case "bottom":
toPossibilities = ["top", "bottom"];
break;
case "left":
case "right":
toPossibilities = ["left", "right"];
break;
}
}
}
let distances = [];
for (const fromSide of fromPossibilities) {
let fromPoint = { "x": 0, "y": 0 };
if (fromSide == "top") {
fromPoint = { "x": fromNode["x"] + fromNode["width"] / 2, "y": fromNode["y"] };
} else if (fromSide == "bottom") {
fromPoint = { "x": fromNode["x"] + fromNode["width"] / 2, "y": fromNode["y"] + fromNode["height"] };
} else if (fromSide == "left") {
fromPoint = { "x": fromNode["x"], "y": fromNode["y"] + fromNode["height"] / 2 };
} else if (fromSide == "right") {
fromPoint = { "x": fromNode["x"] + fromNode["width"], "y": fromNode["y"] + fromNode["height"] / 2 };
}
for (const toSide of toPossibilities) {
let toPoint = { "x": 0, "y": 0 };
if (toSide == "top") {
toPoint = { "x": toNode["x"] + toNode["width"] / 2, "y": toNode["y"] };
} else if (toSide == "bottom") {
toPoint = { "x": toNode["x"] + toNode["width"] / 2, "y": toNode["y"] + toNode["height"] };
} else if (toSide == "left") {
toPoint = { "x": toNode["x"], "y": toNode["y"] + toNode["height"] / 2 };
} else if (toSide == "right") {
toPoint = { "x": toNode["x"] + toNode["width"], "y": toNode["y"] + toNode["height"] / 2 };
}
distances.push({
"fromSide": fromSide,
"toSide": toSide,
"distance": (toPoint.x - fromPoint.x) ** 2 + (toPoint.y - fromPoint.y) ** 2
});
}
}
distances = distances.sort(function(a, b) {
return a.distance - b.distance;
});
edge["from"]["side"] = distances[0]["fromSide"];
edge["to"]["side"] = distances[0]["toSide"];
edge.render();
}
canvas.requestSave();
}
};
/* nosourcemap */
@@ -1,10 +0,0 @@
{
"id": "optimize-canvas-connections",
"name": "Optimize Canvas Connections",
"version": "1.0.0",
"minAppVersion": "1.1.9",
"description": "An Obsidian plugin that declutters a canvas by reconnecting notes using their nearest edges.",
"author": "Félix Chénier",
"authorUrl": "https://felixchenier.uqam.ca",
"isDesktopOnly": false
}
-6
View File
@@ -1,6 +0,0 @@
{
"siteId": null,
"host": null,
"included": [],
"excluded": []
}
@@ -1,83 +0,0 @@
---
title: Brewing Equipment
type: note
permalink: coffee/brewing-equipment
tags:
- '#coffee'
- '#equipment'
- '#gear'
- '#brewing'
- '#demo'
---
# Brewing Equipment
Essential tools and equipment for brewing coffee, their characteristics, and how they affect the brewing process.
## Overview
The equipment used to brew coffee plays a crucial role in determining the final cup quality. From grinders to brewers to kettles, each piece of equipment contributes to different aspects of the brewing process.
## Observations
- [principle] Equipment quality often has a bigger impact on consistency than on absolute quality potential #quality
- [principle] Good grind consistency is the most important technical factor in extraction quality #grind
- [investment] A good burr grinder is often the most important investment for improving home coffee #gear
- [technique] Equipment maintenance and cleaning significantly impact flavor consistency over time #maintenance
## Grinders
- [equipment] Burr grinders crush beans between two abrasive surfaces for more consistent particle size #grinders
- [equipment] Blade grinders chop beans unevenly, leading to inconsistent extraction #grinders
- [equipment] Flat burr grinders produce very consistent particle size but generate more heat #burrs
- [equipment] Conical burr grinders create slightly less uniform grounds but with less heat and noise #burrs
- [feature] Grind adjustment mechanisms range from stepped to stepless for different precision levels #adjustment
- [feature] Retention (grounds trapped in grinder) affects dose consistency and freshness #retention
- [price] Hand grinders offer excellent value, with models like Timemore C2 and 1Zpresso JX providing excellent results around $100-150 #budget
- [price] Entry-level electric burr grinders like Baratza Encore start around $170 but provide significant improvement over blade grinders #value
## Brewers
### Pour Over Brewers
- [equipment] Hario V60 uses a conical design with spiral ridges to control flow rate #pourover
- [equipment] Kalita Wave has a flat bottom with three small holes for more consistent extraction #pourover
- [equipment] Chemex combines brewer and server with thick proprietary filters for ultra-clean cup #pourover
- [material] Ceramic brewers retain heat better than plastic but are more fragile #materials
- [material] Glass brewers provide neutral flavor but less heat retention #materials
- [material] Plastic brewers are inexpensive, durable, and surprisingly good for heat retention #materials
### Immersion Brewers
- [equipment] French Press uses a metal mesh to separate grounds, allowing oils and fine particles to pass #immersion
- [equipment] AeroPress uses pressure and paper filter for clean, versatile brewing #immersion
- [equipment] Clever Dripper combines immersion and drip methods with a valve mechanism #hybrid
- [material] Glass French presses look elegant but break easily and have poor heat retention #materials
- [material] Stainless steel or ceramic French presses offer better durability and heat retention #materials
### Pressure Brewers
- [equipment] Espresso machines use 9 bars of pressure, requiring significant investment for good results #espresso
- [equipment] Moka pot uses steam pressure for strong, concentrated coffee at affordable price #moka
- [equipment] Manual lever machines like Flair or Robot provide espresso-style coffee with manual control #manual_espresso
## Kettles
- [equipment] Gooseneck kettles provide precision pouring control essential for pour over methods #kettles
- [feature] Variable temperature kettles allow precise temperature control for different roast levels #temp_control
- [feature] Flow restrictors can help beginners maintain consistent pour rates #pour_control
- [material] Electric kettles offer convenience and temperature stability #convenience
- [material] Stovetop kettles may be more durable but offer less temperature control #durability
## Accessories
- [equipment] Coffee scale with 0.1g precision helps maintain consistent ratios #measurement
- [equipment] Timer ensures consistent extraction times #consistency
- [equipment] Quality filters significantly impact flavor clarity and body #filters
- [equipment] Storage containers with one-way valves help preserve bean freshness #storage
- [equipment] Blind shaker or dosing cup reduces grinder mess and improves workflow #workflow
## Relations
- improves [[Coffee Brewing Methods]]
- affects [[Flavor Extraction]]
- requires [[Proper Maintenance]]
- enhances [[Home Coffee Setup]]
- part_of [[Coffee Knowledge Base]]
@@ -1,78 +0,0 @@
---
title: Coffee Bean Origins
type: note
permalink: coffee/coffee-bean-origins
tags:
- '#coffee'
- '#origins'
- '#beans'
- '#regions'
- '#demo'
---
# Coffee Bean Origins
An exploration of coffee-growing regions around the world and how geography, climate, and processing methods affect flavor profiles.
## Overview
Coffee beans are grown in various regions around the world, primarily in what's known as the "Coffee Belt" - the area between the Tropics of Cancer and Capricorn. The flavor characteristics of coffee beans are influenced by:
- Geographic region and climate
- Altitude
- Soil composition
- Variety of coffee plant
- Processing method
- Harvest and sorting practices
## Observations
- [principle] Higher altitude generally produces harder, denser beans with more complex acidity #altitude
- [region] Ethiopian beans often feature bright, fruity notes with floral aromatics #ethiopia
- [region] Colombian coffee typically offers balanced acidity with caramel sweetness and nutty undertones #colombia
- [region] Guatemalan coffee presents complex acidity with chocolate notes and sometimes spice characteristics #guatemala
- [region] Brazilian coffee tends toward nutty, chocolate notes with lower acidity and fuller body #brazil
- [region] Kenyan coffee is known for bright, wine-like acidity and berry or citrus notes #kenya
- [processing] Natural (dry) processing tends to create fruitier, more fermented flavors #processing
- [processing] Washed (wet) processing generally results in cleaner, brighter cups with more clarity #processing
- [processing] Honey processing creates a middle ground with some fruity notes while maintaining clarity #processing
- [factor] Shade-grown coffee typically develops more slowly, resulting in more complex flavors #cultivation
- [factor] Soil volcanic soil often imparts distinctive mineral characteristics to coffee #terroir
- [variety] Gesha/Geisha variety is known for exceptional floral and tea-like qualities #varieties
- [variety] Bourbon varieties often feature sweet, complex cup profiles #varieties
- [variety] Robusta beans have higher caffeine content but generally less complex flavor than Arabica #varieties
## Major Growing Regions
- [africa] Ethiopian coffees: Yirgacheffe, Sidamo, Harrar regions each with distinctive profiles #ethiopia
- [africa] Kenyan coffees: Often categorized by grade (AA, AB, etc.) based on bean size #kenya
- [americas] Colombian regions: Huila, Nariño, Antioquia each with unique characteristics #colombia
- [americas] Central American producers: Guatemala, Costa Rica, Panama known for balanced profiles #central_america
- [americas] Brazilian regions: Cerrado, Sul de Minas, Mogiana with varying profiles #brazil
- [asia] Indonesian islands: Sumatra, Java, Sulawesi producing earthy, full-bodied coffees #indonesia
- [asia] Vietnamese coffee: World's largest Robusta producer, often used in blends and commercial coffee #vietnam
## Processing Methods
- [natural] Beans dried inside the fruit, creating fruity, fermented notes and heavier body #processing
- [washed] Fruit removed before drying, resulting in cleaner cup with more pronounced acidity #processing
- [honey] Some fruit mucilage left on during drying, creates balanced sweetness and body #processing
- [wet-hulled] Unique to Indonesia, creates earthy, herbal, low-acid profiles #processing
- [experimental] Anaerobic fermentation, wine-yeast inoculation, and other newer methods #innovation
## Tasting Notes by Region
- [ethiopia] Blueberry, jasmine, bergamot, stone fruit, citrus #flavor_notes
- [kenya] Blackcurrant, tomato, tropical fruit, wine-like acidity #flavor_notes
- [colombia] Caramel, nuts, red apple, chocolate, balanced acidity #flavor_notes
- [guatemala] Chocolate, spice, green apple, balanced #flavor_notes
- [brazil] Nuts, chocolate, low acidity, full body #flavor_notes
- [indonesia] Earthy, herbal, spice, cedar, full body, low acidity #flavor_notes
## Relations
- influences [[Flavor Extraction]]
- pairs_with [[Coffee Brewing Methods]]
- affects [[Tasting Notes]]
- relates_to [[Specialty Coffee]]
- part_of [[Coffee Knowledge Base]]
@@ -1,70 +0,0 @@
---
title: Coffee Brewing Methods
type: note
permalink: coffee/coffee-brewing-methods
tags:
- '#coffee'
- '#brewing'
- '#methods'
- '#demo'
---
# Coffee Brewing Methods
An exploration of different coffee brewing techniques, their characteristics, and how they affect flavor extraction.
## Overview
Coffee brewing is both an art and a science. Different brewing methods extract different compounds from coffee beans, resulting in unique flavor profiles, body, and mouthfeel. The key variables in any brewing method are:
- Grind size
- Water temperature
- Brew time
- Coffee-to-water ratio
- Agitation/turbulence
## Observations
- [principle] Coffee extraction follows a predictable pattern: acids extract first, then sugars, then bitter compounds #extraction
- [method] Pour over methods generally produce cleaner, brighter cups with more distinct flavor notes #clarity
- [method] Immersion methods like French press create fuller body and more rounded flavors #body
- [technique] Water at 195-205°F (90-96°C) extracts optimal flavor compounds for most brewing methods #temperature
- [technique] Grind size directly correlates with ideal extraction time (finer = shorter, coarser = longer) #grind
- [preference] Medium-light roasts often showcase more origin characteristics in pour over methods #roast
- [equipment] Burr grinders produce more consistent particle size than blade grinders, resulting in more even extraction #gear
- [ratio] 1:15 to 1:17 coffee-to-water ratio (by weight) works well for most brew methods #brewing
- [science] Different brewing temperatures extract different chemical compounds from the beans #chemistry
- [technique] Bloom phase (pre-infusion with small amount of water) allows CO2 to escape and improves extraction #bloom
## Pour Over Methods
- [method] V60 produces very clean cup with excellent clarity of flavor #pourover
- [method] Chemex uses thicker filter paper, resulting in even cleaner cup with fewer oils #pourover
- [method] Kalita Wave provides more consistent extraction due to flat bottom design #pourover
- [technique] Concentric circular pouring pattern ensures even saturation of grounds #technique
- [timing] Most pour over methods complete in 2:30-3:30 total brew time #brewing
## Immersion Methods
- [method] French Press creates full-bodied cup with rich mouthfeel due to metal filter allowing oils to pass #immersion
- [method] AeroPress is versatile, capable of producing both espresso-like and filter-style coffee #immersion
- [method] Cold brew uses time instead of heat to extract, resulting in lower acidity #immersion
- [technique] French press ideal steep time is 4-5 minutes before plunging #timing
- [technique] AeroPress inverted method prevents dripping during extraction phase #technique
## Pressure Methods
- [method] Espresso uses 9 bars of pressure to force water through finely ground coffee #pressure
- [method] Moka pot uses steam pressure to push water through grounds, creating strong, concentrated coffee #pressure
- [technique] Espresso requires very fine grind, almost powder-like consistency #grind
- [timing] Espresso shots typically extract in 25-30 seconds #timing
- [principle] Pressure methods can extract compounds that aren't soluble in regular brewing methods #extraction
## Relations
- requires [[Proper Grinding Technique]]
- affects [[Flavor Extraction]]
- pairs_with [[Coffee Bean Origins]]
- uses [[Brewing Equipment]]
- influences [[Tasting Notes]]
- part_of [[Coffee Knowledge Base]]
@@ -1,89 +0,0 @@
---
title: Coffee Flavor Map
type: note
permalink: coffee/coffee-flavor-map
tags:
- '#coffee'
- '#visualization'
- '#canvas'
- '#demo'
---
# Coffee Flavor Map
A visual mapping of coffee flavor attributes, brewing methods, and their relationships. This note describes a canvas visualization that could be generated to demonstrate Basic Memory's visualization capabilities.
## Overview
The Coffee Flavor Map provides a visual representation of how different brewing methods, coffee origins, and equipment choices affect flavor outcomes. This canvas visualization helps users understand the complex relationships in coffee brewing and tasting.
## Canvas Visualization Elements
### Core Nodes
- **Flavor Attributes**: Acidity, Sweetness, Body, Clarity, Bitterness, Complexity
- **Brewing Methods**: Pour Over, French Press, AeroPress, Espresso, Moka Pot, Cold Brew
- **Origin Regions**: Ethiopia, Kenya, Colombia, Brazil, Guatemala, Indonesia
- **Equipment Elements**: Grinder Quality, Water Temperature, Brewing Device, Filter Type
### Node Connections
- Lines connecting brewing methods to their typical flavor outcomes
- Arrows showing how equipment choices affect extraction variables
- Connections between origins and their characteristic flavor profiles
- Highlighting of optimal brewing methods for different origins
### Visual Organization
- Flavor outcomes in the center
- Brewing methods on the left side
- Origins on the right side
- Equipment variables at the bottom
- Color coding by category (methods, origins, equipment, flavors)
## Using This Visualization
### For Coffee Exploration
- Identify which brewing methods might highlight the characteristics you prefer
- See which origins naturally pair well with your preferred brewing method
- Understand how equipment changes can modify flavor outcomes
- Visualize the complex interplay between all coffee variables
### As a Basic Memory Demo
- Demonstrates Canvas visualization capabilities
- Shows how relations can be visually mapped
- Illustrates complex knowledge organization
- Provides an intuitive way to navigate coffee knowledge
## How To Generate This Canvas
In a conversation with Claude, you could request:
```
Please create a canvas visualization mapping the relationships between coffee brewing methods, origins, and flavor outcomes. Show how different equipment and techniques influence extraction and resulting flavor profiles.
```
This would generate a `.canvas` file in your Basic Memory directory that could be opened with Obsidian for an interactive visualization of these coffee relationships.
## Example Visualization Snippets
### Pour Over Method Node
- Connected to: High Clarity, Bright Acidity, Medium Body
- Best pairs with: Ethiopian and Kenyan beans
- Equipment dependencies: Gooseneck Kettle, Paper Filter, Burr Grinder
### Ethiopian Coffee Node
- Characteristic flavors: Floral, Fruity, Bright
- Best brewing methods: Pour Over, AeroPress
- Challenging with: French Press (loses clarity of delicate notes)
### Grind Size Node
- Affects: Extraction Rate, Flavor Balance
- Fine grind increases: Extraction Speed, Surface Area
- Coarse grind increases: Flow Rate, Reduces Bitter Compounds
## Relations
- visualizes [[Coffee Knowledge Base]]
- relates_to [[Coffee Brewing Methods]]
- relates_to [[Coffee Bean Origins]]
- relates_to [[Flavor Extraction]]
- relates_to [[Tasting Notes]]
- demonstrates [[Canvas]]
@@ -1,73 +0,0 @@
---
title: Coffee Knowledge Base
type: note
permalink: coffee/coffee-knowledge-base
tags:
- '#coffee'
- '#index'
- '#demo'
- '#knowledge'
---
# Coffee Knowledge Base
A comprehensive collection of coffee knowledge, from bean origins to brewing methods to tasting notes. This knowledge base demonstrates Basic Memory's ability to organize and connect information in a meaningful way.
## Overview
This Coffee Knowledge Base captures key information about coffee, structured with semantic observations and relations that connect different aspects of coffee knowledge. It serves as both a useful reference for coffee enthusiasts and a demonstration of how Basic Memory organizes information.
## Key Topics
### Core Coffee Knowledge
- [[Coffee Brewing Methods]] - Different techniques for preparing coffee
- [[Coffee Bean Origins]] - Where coffee comes from and how region affects flavor
- [[Brewing Equipment]] - Tools and devices used to prepare coffee
- [[Flavor Extraction]] - The science of dissolving flavor compounds from coffee
- [[Tasting Notes]] - How to taste and describe coffee flavors
### Brewing Techniques
- Proper grinding is fundamental to good extraction
- Water quality significantly impacts flavor
- Different brewing methods highlight different characteristics
- Time, temperature, and grind size are the key variables to control
- Freshness of beans dramatically affects quality
### Coffee Preferences
- Light roasts preserve more origin characteristics and acidity
- Dark roasts emphasize body and chocolatey/roasted flavors
- Pour over methods highlight clarity and distinct flavor notes
- Immersion methods create fuller body and rounded flavor
- Personal preference matters more than "correctness"
## Using This Knowledge Base
### For Learning
Use this knowledge base to:
- Understand coffee fundamentals
- Explore connections between brewing methods and flavor outcomes
- Learn how different origins produce distinct flavor profiles
- Discover how equipment affects the brewing process
- Develop a vocabulary for describing coffee experiences
### As a Demo
This knowledge base demonstrates:
- Semantic knowledge organization with categories and relations
- Building connections between related concepts
- Creating a navigable knowledge graph
- Structuring information in a way both humans and AI assistants can understand
- How Basic Memory enables persistent knowledge across conversations
## Relations
- contains [[Coffee Brewing Methods]]
- contains [[Coffee Bean Origins]]
- contains [[Brewing Equipment]]
- contains [[Flavor Extraction]]
- contains [[Tasting Notes]]
- demonstrates [[Basic Memory Capabilities]]
@@ -1,79 +0,0 @@
---
title: Flavor Extraction
type: note
permalink: coffee/flavor-extraction
tags:
- '#coffee'
- '#extraction'
- '#brewing'
- '#science'
- '#demo'
---
# Flavor Extraction
Understanding the science of coffee extraction, how different compounds dissolve at different rates, and how to control extraction to achieve desired flavor profiles.
## Overview
Coffee extraction is the process of dissolving flavor compounds from ground coffee into water. The science of extraction is key to producing a balanced, flavorful cup. Extraction is affected by numerous variables including grind size, water temperature, contact time, agitation, and pressure.
## Observations
- [science] Coffee contains over 1,000 aroma compounds and hundreds of flavor compounds #chemistry
- [principle] Extraction occurs in a predictable sequence: acids → sugars → bitter compounds #extraction_order
- [principle] Under-extraction results in sour, bright, thin coffee lacking sweetness and body #under_extraction
- [principle] Over-extraction results in bitter, hollow, astringent flavors #over_extraction
- [principle] The goal is typically balanced extraction (18-22% of coffee solubles dissolved) #balanced_extraction
- [technique] Finer grind size increases extraction rate due to greater surface area #grind_size
- [technique] Higher water temperature increases extraction rate and solubility of compounds #temperature
- [technique] Longer contact time allows more complete extraction #brew_time
- [technique] Agitation (stirring, turbulence) increases extraction rate by preventing saturation zones #agitation
- [technique] Pressure (as in espresso) can extract compounds that aren't water-soluble at atmospheric pressure #pressure
## Factors Affecting Extraction
- [factor] Grind size: Finer = faster extraction, coarser = slower extraction #grind
- [factor] Water temperature: Higher = faster extraction, lower = slower extraction #temperature
- [factor] Contact time: Longer = more extraction, shorter = less extraction #time
- [factor] Agitation: More = faster extraction, less = slower extraction #agitation
- [factor] Coffee-to-water ratio: More coffee = lower extraction percentage #ratio
- [factor] Water quality: Mineral content affects extraction of different compounds #water
- [factor] Roast level: Darker roasts extract more easily than lighter roasts #roast
- [factor] Bean density: Denser beans (typically high-altitude) require more effort to extract #density
- [factor] Freshness: Freshly roasted coffee extracts differently than aged coffee #freshness
- [factor] Brewing method: Different methods extract different compounds at different rates #method
## Signs of Extraction Levels
- [under] Sour, bright, lack of sweetness, thin body, quick finish #flavor
- [under] Typically from: too coarse grind, too cool water, too short brew time #causes
- [balanced] Sweet, bright but not sour, rich but not bitter, pleasing finish #flavor
- [balanced] Achieved through proper ratio of variables for given coffee #technique
- [over] Bitter, hollow, astringent, dry finish, sometimes papery #flavor
- [over] Typically from: too fine grind, too hot water, too long brew time #causes
## Measuring Extraction
- [method] Total Dissolved Solids (TDS) meters measure concentration of coffee solution #measurement
- [method] Extraction yield = percentage of coffee grounds dissolved in the final brew #calculation
- [preference] Specialty coffee typically targets 18-22% extraction yield #standards
- [preference] Some specialty light roasts may taste best at higher extraction percentages #speciality
## Controlling Extraction
- [technique] Adjust grind size as primary extraction control #basics
- [technique] Use water temperature to fine-tune extraction #fine_tuning
- [technique] Modify pour technique to control agitation level #technique
- [technique] Adjust coffee-to-water ratio to balance strength and extraction #ratio
- [technique] Pre-infusion (blooming) helps achieve even extraction #blooming
- [technique] Pulse pouring creates different extraction dynamics than continuous pour #pour_technique
## Relations
- affected_by [[Coffee Brewing Methods]]
- influenced_by [[Coffee Bean Origins]]
- enhanced_by [[Brewing Equipment]]
- determines [[Tasting Notes]]
- requires [[Water Quality]]
- part_of [[Coffee Knowledge Base]]
@@ -1,161 +0,0 @@
{
"nodes":[
{
"id":"node-5",
"type":"text",
"text":"## Main Pour Phase\n- Use concentric circles from center outward\n- Maintain steady, controlled flow rate\n- Avoid pouring directly on filter walls\n- Keep water level consistent\n- Pulse pour in 2-3 stages (or continuous pour)\n- Total brew time target: 2:30-3:30",
"position":{"x":450,"y":200},
"x":530,
"y":-100,
"width":300,
"height":200,
"color":"1"
},
{
"id":"node-8",
"type":"text",
"text":"## Drawdown\n- Allow water to fully drain\n- Flat bed indicates even extraction\n- Total brew time should be ~2:30-3:30\n- Remove filter promptly after brewing",
"position":{"x":450,"y":700},
"x":540,
"y":375,
"width":300,
"height":150,
"color":"1"
},
{
"id":"node-6",
"type":"text",
"text":"## Pour Pattern\n\nConcentric circles ensure even saturation of coffee grounds. Begin at the center and work outward, avoiding filter edges. Pour height of 1-2 inches above coffee bed.",
"position":{"x":250,"y":450},
"x":960,
"y":25,
"width":300,
"height":150,
"color":"5"
},
{
"id":"node-12",
"type":"text",
"text":"## Tasting Notes\n\n- Balanced extraction: sweet, bright, complex\n- Under-extraction: sour, lacking sweetness\n- Over-extraction: bitter, astringent, hollow\n\nTake notes on each brew to track improvements and preferences.",
"position":{"x":-250,"y":700},
"x":1020,
"y":420,
"width":300,
"height":150,
"color":"6"
},
{
"id":"node-9",
"type":"text",
"text":"## Troubleshooting\n\n- Too sour/weak: Grind finer, water hotter, increase brew time\n- Too bitter/strong: Grind coarser, water cooler, decrease brew time\n- Uneven extraction: Improve pour technique, better grinder\n- Channeling: More careful pouring, better bloom\n- Slow drawdown: Coarser grind, less agitation\n- Fast drawdown: Finer grind, more careful pouring",
"position":{"x":100,"y":700},
"x":30,
"y":570,
"width":300,
"height":200,
"color":"6"
},
{
"id":"node-3",
"type":"text",
"text":"## Preparation\n- Heat water to 195-205°F (90-96°C)\n- Measure coffee (1:15 to 1:17 ratio)\n- Medium-fine grind (sea salt consistency)\n- Rinse filter with hot water\n- Discard rinse water\n- Add ground coffee to filter\n- Level coffee bed",
"position":{"x":-250,"y":200},
"x":30,
"y":-500,
"width":300,
"height":200,
"color":"3"
},
{
"id":"node-1",
"type":"text",
"text":"# Perfect Pour Over Method\n\nA systematic approach to brewing exceptional pour over coffee by controlling key variables and following proper technique.",
"position":{"x":0,"y":0},
"x":-580,
"y":-760,
"width":400,
"height":120,
"color":"4"
},
{
"id":"node-10",
"type":"text",
"text":"## Grinding Parameters\n\n- V60: Medium-fine (sea salt)\n- Chemex: Medium (slightly coarser than V60)\n- Kalita Wave: Medium (between V60 and Chemex)\n\nConsistent particle size is critical; use quality burr grinder.",
"position":{"x":-250,"y":450},
"x":30,
"y":-910,
"width":300,
"height":150,
"color":"5"
},
{
"id":"node-2",
"type":"text",
"text":"## Equipment Setup\n- Clean V60/Chemex/Kalita Wave\n- Paper filter (rinsed)\n- Server/mug\n- Scale with timer\n- Gooseneck kettle\n- Burr grinder\n- Fresh coffee beans",
"position":{"x":-600,"y":200},
"x":-530,
"y":-500,
"width":300,
"height":200,
"color":"3"
},
{
"id":"node-4",
"type":"text",
"text":"## The Bloom\n- Start timer\n- Pour 2-3x coffee weight water\n- Ensure all grounds are saturated\n- Gentle stir or swirl if needed\n- Allow 30-45 seconds for degassing\n- Look for bubbling and dome formation",
"position":{"x":100,"y":200},
"x":530,
"y":-500,
"width":300,
"height":200,
"color":"1"
},
{
"id":"node-13",
"type":"text",
"text":"## Coffee-to-Water Ratio\n\n- Standard: 1:15 to 1:17 (coffee:water)\n- Stronger cup: 1:15 (67g/L)\n- Medium cup: 1:16 (62.5g/L)\n- Lighter cup: 1:17 (58.8g/L)\n\nExample: For 300ml water, use ~18-20g coffee",
"position":{"x":-600,"y":700},
"x":30,
"y":-100,
"width":300,
"height":150,
"color":"6"
},
{
"id":"node-7",
"type":"text",
"text":"## Brew Time Guideline\n\n- Bloom: 30-45 seconds\n- First pour: 1:00-1:15\n- Second pour: 1:45-2:00\n- Final pour: 2:15-2:30\n- Drawdown complete: 2:45-3:30\n\nAdjust for taste: shorter for lighter, longer for stronger",
"position":{"x":600,"y":450},
"x":-80,
"y":220,
"width":300,
"height":200,
"color":"5"
},
{
"id":"node-11",
"type":"text",
"text":"## Water Quality\n\n- Clean, filtered water\n- No strong odors or flavors\n- Ideal TDS: 75-150 ppm\n- Ideal pH: 7.0-7.5\n- Avoid distilled water (lacks minerals)\n- Avoid hard water (scaling issues)",
"position":{"x":-600,"y":450},
"x":-780,
"y":-125,
"width":300,
"height":150,
"color":"5"
}
],
"edges":[
{"id":"edge-1","fromNode":"node-1","fromSide":"bottom","toNode":"node-2","toSide":"top","label":"Step 1"},
{"id":"edge-2","fromNode":"node-2","fromSide":"right","toNode":"node-3","toSide":"left","label":"Step 2"},
{"id":"edge-3","fromNode":"node-3","fromSide":"right","toNode":"node-4","toSide":"left","label":"Step 3"},
{"id":"edge-4","fromNode":"node-4","fromSide":"bottom","toNode":"node-5","toSide":"top","label":"Step 4"},
{"id":"edge-5","fromNode":"node-5","fromSide":"bottom","toNode":"node-8","toSide":"top","label":"Step 5"},
{"id":"edge-6","fromNode":"node-5","fromSide":"left","toNode":"node-7","toSide":"right","label":"Timing"},
{"id":"edge-7","fromNode":"node-5","fromSide":"right","toNode":"node-6","toSide":"left","label":"Technique"},
{"id":"edge-8","fromNode":"node-8","fromSide":"left","toNode":"node-9","toSide":"right","label":"if problems"},
{"id":"edge-9","fromNode":"node-3","fromSide":"top","toNode":"node-10","toSide":"bottom","label":"Grinding details"},
{"id":"edge-10","fromNode":"node-2","fromSide":"bottom","toNode":"node-11","toSide":"right","label":"Water details"},
{"id":"edge-11","fromNode":"node-8","fromSide":"right","toNode":"node-12","toSide":"left","label":"Evaluate"},
{"id":"edge-12","fromNode":"node-3","fromSide":"bottom","toNode":"node-13","toSide":"top","label":"Ratio details"}
]
}
-84
View File
@@ -1,84 +0,0 @@
---
title: Tasting Notes
type: note
permalink: coffee/tasting-notes
tags:
- '#coffee'
- '#tasting'
- '#flavor'
- '#cupping'
- '#demo'
---
# Tasting Notes
How to taste and evaluate coffee, identify flavor characteristics, and develop a personal coffee palate.
## Overview
Coffee tasting, or "cupping" in professional contexts, is the practice of observing the tastes and aromas of brewed coffee. Developing a coffee palate helps identify preferences, communicate about coffee experiences, and better understand how brewing variables affect the cup.
## Observations
- [principle] Flavor perception includes taste, aroma, mouthfeel, and retronasal perception #sensory
- [principle] Our taste buds can only perceive sweet, sour, salty, bitter, and umami #taste
- [principle] Most of what we call "flavor" is actually aroma detected retronasally #aroma
- [technique] Professional coffee tasting (cupping) uses a standardized protocol for consistency #cupping
- [technique] Slurping coffee aerates it and spreads it across all taste receptors #technique
- [technique] Allowing coffee to cool reveals different flavor notes at different temperatures #temperature
## Coffee Flavor Wheel
- [tool] The SCA Coffee Flavor Wheel provides a standardized vocabulary for describing coffee #flavor_wheel
- [category] Primary categories include: Fruity, Floral, Sweet, Nutty/Cocoa, Spice, Roasted, Other #categories
- [subcategory] Fruity breaks down into: Berry, Dried Fruit, Citrus Fruit, Stone Fruit, Tropical Fruit, etc. #fruit_notes
- [subcategory] Floral includes: Floral, Black Tea, Chamomile, Rose, Jasmine, etc. #floral_notes
- [subcategory] Sweet includes: Brown Sugar, Molasses, Honey, Maple Syrup, Vanilla, etc. #sweet_notes
- [subcategory] Nutty/Cocoa includes: Nut, Cocoa, Dark Chocolate, Chocolate, etc. #nutty_notes
- [subcategory] Spice includes: Brown Spice, Pepper, Anise, Nutmeg, Cinnamon, etc. #spice_notes
## Basic Tasting Components
- [component] Acidity: The bright, tangy quality (not sourness from under-extraction) #acidity
- [component] Sweetness: The pleasant, sugary quality balancing other elements #sweetness
- [component] Body: The physical mouthfeel and weight of the coffee #body
- [component] Finish/Aftertaste: The flavor that lingers after swallowing #finish
- [component] Balance: How well all elements work together #balance
- [component] Complexity: The range and layers of distinct flavors #complexity
- [component] Cleanliness: Absence of defects or off-flavors #cleanliness
## Common Flavor Notes by Origin
- [ethiopia] Blueberry, jasmine, bergamot, lemon, tea-like #flavor_notes
- [kenya] Blackcurrant, grapefruit, tomato-like acidity, winey #flavor_notes
- [colombia] Caramel, red apple, nuts, chocolate, balanced acidity #flavor_notes
- [guatemala] Chocolate, spice, apple, medium acidity #flavor_notes
- [brazil] Nuts, chocolate, low-to-medium acidity, full body #flavor_notes
- [indonesia] Earthy, herbal, spice, cedar, full body, low acidity #flavor_notes
- [costa_rica] Clean, bright, citrus, balanced, light chocolate #flavor_notes
## Developing Your Palate
- [technique] Taste coffees side-by-side to identify differences #comparison
- [technique] Try describing flavors before looking at roaster's notes #blind_tasting
- [technique] Keep a coffee journal with detailed notes about each coffee #journaling
- [technique] Explore different processing methods of the same origin #processing
- [technique] Try the same coffee brewed with different methods #brewing_comparison
- [technique] Use reference flavors (actual fruits, chocolates, etc.) to calibrate your palate #calibration
## Personal Coffee Experiences
- [experience] Ethiopian Yirgacheffe prepared as pour over: intense blueberry, jasmine aromatics, tea-like body
- [experience] Sumatra Mandheling in French press: earthy, cedar, herbal, tobacco, full body
- [experience] Panama Gesha as pour over: intense floral notes, jasmine, bergamot, delicate body
- [experience] Brazil Cerrado as espresso: nutty, chocolate, caramel, low acidity, great crema
- [experience] Kenya AA as pour over: bright blackcurrant, tomato-like acidity, winey finish
## Relations
- determined_by [[Flavor Extraction]]
- influenced_by [[Coffee Bean Origins]]
- varies_with [[Coffee Brewing Methods]]
- enhanced_by [[Proper Grinding Technique]]
- documented_in [[Coffee Journal]]
- part_of [[Coffee Knowledge Base]]
@@ -1,69 +0,0 @@
---
title: Test Note Creation - Basic Functionality
type: note
permalink: testing/test-note-creation-basic-functionality
tags:
- '["testing"'
- '"core-functionality"'
- '"note-creation"]'
---
---
title: Test Note Creation - Basic Functionality
tags: [testing, core-functionality, note-creation, edited]
test_status: active
last_edited: 2025-06-01
---
# Test Note Creation - Basic Functionality
## Test Status: COMPREHENSIVE TESTING IN PROGRESS
Testing basic note creation with various content types and structures.
## Content Types Tested
- Plain text content ✓
- Markdown formatting **bold**, *italic*
- Lists:
- Bullet points
- Numbered items
- Code blocks: `inline code`
```python
# Block code
def test_function():
return "Hello, Basic Memory!"
```
## Special Characters
- Unicode: café, naïve, résumé
- Emojis: 🚀 🔬 📝
- Symbols: @#$%^&*()
## Frontmatter Testing
This note should have proper frontmatter parsing.
## Relations to Test
- connects_to [[Another Test Note]]
- validates [[Core Functionality Tests]]
## Observations
- [success] Note creation initiated
- [test] Content variety included
- [validation] Special characters included
## Edit Test Results
- [success] Note reading via title lookup ✓
- [success] Search functionality returns relevant results ✓
- [success] Special characters (unicode, emojis) preserved ✓
- [test] Now testing append edit operation ✓
## Performance Notes
- Note creation: Instantaneous
- Note reading: Fast response
- Search: Good relevance scoring
## Next Tests
- Edit operations (append, prepend, find_replace)
- Move operations
- Cross-project functionality
-36
View File
@@ -1,36 +0,0 @@
#!/bin/bash
set -e
echo "Welcome to Basic Memory installer"
# 1. Install uv if not present
if ! command -v uv &> /dev/null; then
echo "Installing uv package manager..."
curl -LsSf https://github.com/astral-sh/uv/releases/download/0.1.23/uv-installer.sh | sh
fi
# 2. Configure Claude Desktop
echo "Configuring Claude Desktop..."
CONFIG_FILE="$HOME/Library/Application Support/Claude/claude_desktop_config.json"
# Create config directory if it doesn't exist
mkdir -p "$(dirname "$CONFIG_FILE")"
# If config file doesn't exist, create it with initial structure
if [ ! -f "$CONFIG_FILE" ]; then
echo '{"mcpServers": {}}' > "$CONFIG_FILE"
fi
# Add/update the basic-memory config using jq
jq '.mcpServers."basic-memory" = {
"command": "uvx",
"args": ["basic-memory"]
}' "$CONFIG_FILE" > "$CONFIG_FILE.tmp" && mv "$CONFIG_FILE.tmp" "$CONFIG_FILE"
echo "Installation complete! Basic Memory is now available in Claude Desktop."
echo "Please restart Claude Desktop for changes to take effect."
echo -e "\nQuick Start:"
echo "1. You can run sync directly using: uvx basic-memory sync"
echo "2. Optionally, install globally with: uv pip install basic-memory"
echo -e "\nBuilt with ♥️ by Basic Machines."
+1 -1
View File
@@ -1,7 +1,7 @@
"""basic-memory - Local-first knowledge management combining Zettelkasten with knowledge graphs"""
# Package version - updated by release automation
__version__ = "0.13.6"
__version__ = "0.13.7"
# API version for FastAPI - independent of package version
__api_version__ = "v0"
+8 -1
View File
@@ -1,13 +1,14 @@
"""Database management commands."""
import asyncio
from pathlib import Path
import typer
from loguru import logger
from basic_memory import db
from basic_memory.cli.app import app
from basic_memory.config import app_config
from basic_memory.config import app_config, config_manager
@app.command()
@@ -25,6 +26,12 @@ def reset(
db_path.unlink()
logger.info(f"Database file deleted: {db_path}")
# Reset project configuration
config_manager.config.projects = {"main": str(Path.home() / "basic-memory")}
config_manager.config.default_project = "main"
config_manager.save_config(config_manager.config)
logger.info("Project configuration reset to default")
# Create a new empty database
asyncio.run(db.run_migrations(app_config))
logger.info("Database reset complete")
+2
View File
@@ -78,6 +78,7 @@ def mcp(
if transport == "stdio":
mcp_server.run(
transport=transport,
log_level="INFO",
)
elif transport == "streamable-http" or transport == "sse":
mcp_server.run(
@@ -85,4 +86,5 @@ def mcp(
host=host,
port=port,
path=path,
log_level="INFO",
)
+3 -7
View File
@@ -120,7 +120,7 @@ def set_default_project(
try:
project_name = generate_permalink(name)
response = asyncio.run(call_put(client, f"projects/{project_name}/default"))
response = asyncio.run(call_put(client, f"/projects/{project_name}/default"))
result = ProjectStatusResponse.model_validate(response.json())
console.print(f"[green]{result.message}[/green]")
@@ -128,12 +128,8 @@ def set_default_project(
console.print(f"[red]Error setting default project: {str(e)}[/red]")
raise typer.Exit(1)
# Reload configuration to apply the change
from importlib import reload
from basic_memory import config as config_module
reload(config_module)
# The API call above should have updated both config and MCP session
# No need for manual reload - the project service handles this automatically
console.print("[green]Project activated for current session[/green]")
+5 -4
View File
@@ -95,11 +95,12 @@ async def get_or_create_db(
if _engine is None:
_engine, _session_maker = _create_engine_and_session(db_path, db_type)
# Run migrations automatically unless explicitly disabled
if ensure_migrations:
if app_config is None:
from basic_memory.config import app_config as global_app_config
app_config = global_app_config
await run_migrations(app_config, db_type)
@@ -170,12 +171,12 @@ async def run_migrations(
): # pragma: no cover
"""Run any pending alembic migrations."""
global _migrations_completed
# Skip if migrations already completed unless forced
if _migrations_completed and not force:
logger.debug("Migrations already completed in this session, skipping")
return
logger.info("Running database migrations...")
try:
# Get the absolute path to the alembic directory relative to this file
@@ -206,7 +207,7 @@ async def run_migrations(
# initialize the search Index schema
# the project_id is not used for init_search_index, so we pass a dummy value
await SearchRepository(session_maker, 1).init_search_index()
# Mark migrations as completed
_migrations_completed = True
except Exception as e: # pragma: no cover
+16 -1
View File
@@ -8,7 +8,7 @@ from dataclasses import dataclass
from typing import Optional
from loguru import logger
from basic_memory.config import ProjectConfig, get_project_config
from basic_memory.config import ProjectConfig, get_project_config, config_manager
@dataclass
@@ -64,6 +64,21 @@ class ProjectSession:
self.current_project = self.default_project # pragma: no cover
logger.info(f"Reset project context to default: {self.default_project}") # pragma: no cover
def refresh_from_config(self) -> None:
"""Refresh session state from current configuration.
This method reloads the default project from config and reinitializes
the session. This should be called when the default project is changed
via CLI or API to ensure MCP session stays in sync.
"""
# Reload config to get latest default project
current_config = config_manager.load_config()
new_default = current_config.default_project
# Reinitialize with new default
self.initialize(new_default)
logger.info(f"Refreshed project session from config, new default: {new_default}")
# Global session instance
session = ProjectSession()
@@ -12,10 +12,6 @@ from basic_memory.mcp.server import mcp
)
async def sync_status_prompt() -> str:
"""Get sync status with AI assistant guidance.
This prompt provides detailed sync status information along with
recommendations for how AI assistants should handle different sync states.
Returns:
Formatted sync status with AI assistant guidance
"""
-1
View File
@@ -105,6 +105,5 @@ auth_settings, auth_provider = create_auth_config()
# Create the shared server instance
mcp = FastMCP(
name="Basic Memory",
log_level="DEBUG",
auth=auth_provider,
)
@@ -102,14 +102,14 @@ class EntityRepository(Repository[Entity]):
async def upsert_entity(self, entity: Entity) -> Entity:
"""Insert or update entity using a hybrid approach.
This method provides a cleaner alternative to the try/catch approach
for handling permalink and file_path conflicts. It first tries direct
for handling permalink and file_path conflicts. It first tries direct
insertion, then handles conflicts intelligently.
Args:
entity: The entity to insert or update
Returns:
The inserted or updated entity
"""
@@ -117,29 +117,28 @@ class EntityRepository(Repository[Entity]):
async with db.scoped_session(self.session_maker) as session:
# Set project_id if applicable and not already set
self._set_project_id_if_needed(entity)
# Check for existing entity with same file_path first
existing_by_path = await session.execute(
select(Entity).where(
Entity.file_path == entity.file_path,
Entity.project_id == entity.project_id
Entity.file_path == entity.file_path, Entity.project_id == entity.project_id
)
)
existing_path_entity = existing_by_path.scalar_one_or_none()
if existing_path_entity:
# Update existing entity with same file path
for key, value in {
'title': entity.title,
'entity_type': entity.entity_type,
'entity_metadata': entity.entity_metadata,
'content_type': entity.content_type,
'permalink': entity.permalink,
'checksum': entity.checksum,
'updated_at': entity.updated_at,
"title": entity.title,
"entity_type": entity.entity_type,
"entity_metadata": entity.entity_metadata,
"content_type": entity.content_type,
"permalink": entity.permalink,
"checksum": entity.checksum,
"updated_at": entity.updated_at,
}.items():
setattr(existing_path_entity, key, value)
await session.flush()
# Return with relationships loaded
query = (
@@ -150,15 +149,17 @@ class EntityRepository(Repository[Entity]):
result = await session.execute(query)
found = result.scalar_one_or_none()
if not found: # pragma: no cover
raise RuntimeError(f"Failed to retrieve entity after update: {entity.file_path}")
raise RuntimeError(
f"Failed to retrieve entity after update: {entity.file_path}"
)
return found
# No existing entity with same file_path, try insert
try:
# Simple insert for new entity
session.add(entity)
await session.flush()
# Return with relationships loaded
query = (
select(Entity)
@@ -168,36 +169,37 @@ class EntityRepository(Repository[Entity]):
result = await session.execute(query)
found = result.scalar_one_or_none()
if not found: # pragma: no cover
raise RuntimeError(f"Failed to retrieve entity after insert: {entity.file_path}")
raise RuntimeError(
f"Failed to retrieve entity after insert: {entity.file_path}"
)
return found
except IntegrityError:
# Could be either file_path or permalink conflict
await session.rollback()
# Check if it's a file_path conflict (race condition)
existing_by_path_check = await session.execute(
select(Entity).where(
Entity.file_path == entity.file_path,
Entity.project_id == entity.project_id
Entity.file_path == entity.file_path, Entity.project_id == entity.project_id
)
)
race_condition_entity = existing_by_path_check.scalar_one_or_none()
if race_condition_entity:
# Race condition: file_path conflict detected after our initial check
# Update the existing entity instead
for key, value in {
'title': entity.title,
'entity_type': entity.entity_type,
'entity_metadata': entity.entity_metadata,
'content_type': entity.content_type,
'permalink': entity.permalink,
'checksum': entity.checksum,
'updated_at': entity.updated_at,
"title": entity.title,
"entity_type": entity.entity_type,
"entity_metadata": entity.entity_metadata,
"content_type": entity.content_type,
"permalink": entity.permalink,
"checksum": entity.checksum,
"updated_at": entity.updated_at,
}.items():
setattr(race_condition_entity, key, value)
await session.flush()
# Return the updated entity with relationships loaded
query = (
@@ -208,7 +210,9 @@ class EntityRepository(Repository[Entity]):
result = await session.execute(query)
found = result.scalar_one_or_none()
if not found: # pragma: no cover
raise RuntimeError(f"Failed to retrieve entity after race condition update: {entity.file_path}")
raise RuntimeError(
f"Failed to retrieve entity after race condition update: {entity.file_path}"
)
return found
else:
# Must be permalink conflict - generate unique permalink
@@ -218,14 +222,13 @@ class EntityRepository(Repository[Entity]):
"""Handle permalink conflicts by generating a unique permalink."""
base_permalink = entity.permalink
suffix = 1
# Find a unique permalink
while True:
test_permalink = f"{base_permalink}-{suffix}"
existing = await session.execute(
select(Entity).where(
Entity.permalink == test_permalink,
Entity.project_id == entity.project_id
Entity.permalink == test_permalink, Entity.project_id == entity.project_id
)
)
if existing.scalar_one_or_none() is None:
@@ -233,11 +236,11 @@ class EntityRepository(Repository[Entity]):
entity.permalink = test_permalink
break
suffix += 1
# Insert with unique permalink (no conflict possible now)
session.add(entity)
await session.flush()
# Return the inserted entity with relationships loaded
query = (
select(Entity)
+2 -2
View File
@@ -302,7 +302,7 @@ class EntityService(BaseService[EntityModel]):
Creates the entity with null checksum to indicate sync not complete.
Relations will be added in second pass.
Uses UPSERT approach to handle permalink/file_path conflicts cleanly.
"""
logger.debug(f"Creating entity: {markdown.frontmatter.title} file_path: {file_path}")
@@ -310,7 +310,7 @@ class EntityService(BaseService[EntityModel]):
# Mark as incomplete because we still need to add relations
model.checksum = None
# Use UPSERT to handle conflicts cleanly
try:
return await self.repository.upsert_entity(model)
+11 -5
View File
@@ -21,9 +21,9 @@ async def initialize_database(app_config: BasicMemoryConfig) -> None:
Args:
app_config: The Basic Memory project configuration
Note:
Database migrations are now handled automatically when the database
Database migrations are now handled automatically when the database
connection is first established via get_or_create_db().
"""
# Trigger database initialization and migrations by getting the database connection
@@ -50,7 +50,9 @@ async def reconcile_projects_with_config(app_config: BasicMemoryConfig):
# Get database session - migrations handled centrally
_, session_maker = await db.get_or_create_db(
db_path=app_config.database_path, db_type=db.DatabaseType.FILESYSTEM, ensure_migrations=False
db_path=app_config.database_path,
db_type=db.DatabaseType.FILESYSTEM,
ensure_migrations=False,
)
project_repository = ProjectRepository(session_maker)
@@ -71,7 +73,9 @@ async def reconcile_projects_with_config(app_config: BasicMemoryConfig):
async def migrate_legacy_projects(app_config: BasicMemoryConfig):
# Get database session - migrations handled centrally
_, session_maker = await db.get_or_create_db(
db_path=app_config.database_path, db_type=db.DatabaseType.FILESYSTEM, ensure_migrations=False
db_path=app_config.database_path,
db_type=db.DatabaseType.FILESYSTEM,
ensure_migrations=False,
)
logger.info("Migrating legacy projects...")
project_repository = ProjectRepository(session_maker)
@@ -140,7 +144,9 @@ async def initialize_file_sync(
# Load app configuration - migrations handled centrally
_, session_maker = await db.get_or_create_db(
db_path=app_config.database_path, db_type=db.DatabaseType.FILESYSTEM, ensure_migrations=False
db_path=app_config.database_path,
db_type=db.DatabaseType.FILESYSTEM,
ensure_migrations=False,
)
project_repository = ProjectRepository(session_maker)
@@ -154,6 +154,15 @@ class ProjectService:
logger.info(f"Project '{name}' set as default in configuration and database")
# Refresh MCP session to pick up the new default project
try:
from basic_memory.mcp.project_session import session
session.refresh_from_config()
except ImportError: # pragma: no cover
# MCP components might not be available in all contexts (e.g., CLI-only usage)
logger.debug("MCP session not available, skipping session refresh")
async def _ensure_single_default_project(self) -> None:
"""Ensure only one project has is_default=True.
@@ -274,6 +283,15 @@ class ProjectService:
logger.info("Project synchronization complete")
# Refresh MCP session to ensure it's in sync with current config
try:
from basic_memory.mcp.project_session import session
session.refresh_from_config()
except ImportError:
# MCP components might not be available in all contexts
logger.debug("MCP session not available, skipping session refresh")
async def update_project( # pragma: no cover
self, name: str, updated_path: Optional[str] = None, is_active: Optional[bool] = None
) -> None:
-480
View File
@@ -1,480 +0,0 @@
# AI Assistant Guide for Basic Memory
This guide helps AIs use Basic Memory tools effectively when working with users. It covers reading, writing, and
navigating knowledge through the Model Context Protocol (MCP).
## Overview
Basic Memory allows you and users to record context in local Markdown files, building a rich knowledge base through
natural conversations. The system automatically creates a semantic knowledge graph from simple text patterns.
- **Local-First**: All data is stored in plain text files on the user's computer
- **Real-Time**: Users see content updates immediately
- **Bi-Directional**: Both you and users can read and edit notes
- **Semantic**: Simple patterns create a structured knowledge graph
- **Persistent**: Knowledge persists across sessions and conversations
## The Importance of the Knowledge Graph
**Basic Memory's value comes from connections between notes, not just the notes themselves.**
When writing notes, your primary goal should be creating a rich, interconnected knowledge graph:
1. **Increase Semantic Density**: Add multiple observations and relations to each note
2. **Use Accurate References**: Aim to reference existing entities by their exact titles
3. **Create Forward References**: Feel free to reference entities that don't exist yet - Basic Memory will resolve these
when they're created later
4. **Create Bidirectional Links**: When appropriate, connect entities from both directions
5. **Use Meaningful Categories**: Add semantic context with appropriate observation categories
6. **Choose Precise Relations**: Use specific relation types that convey meaning
Remember: A knowledge graph with 10 heavily connected notes is more valuable than 20 isolated notes. Your job is to help
build these connections!
## Core Tools Reference
**Writing knowledge - THE MOST IMPORTANT TOOL!**
```
write_note(
title="Search Design",
content="# Search Design\n\n## Overview\nSearch functionality design and implementation.\n\n## Observations\n- [requirement] Must support full-text search #search\n- [decision] Using vector embeddings for semantic search #technology\n\n## Relations\n- implements [[Search Requirements]]\n- part_of [[API Specification]]",
folder="specs",
tags=["search", "design"]
)
```
**Reading knowledge:**
```
read_note("Search Design") # By exact title
read_note("specs/search-design") # By permalink
read_note("memory://specs/search") # By memory URL
```
**Viewing notes as formatted artifacts (Claude Desktop):**
```
view_note("Search Design") # Creates readable artifact
view_note("specs/search-design") # By permalink
view_note("memory://specs/search") # By memory URL
```
**Incremental editing (v0.13.0) - REQUIRES EXACT IDENTIFIERS:**
```
edit_note(
identifier="Search Design", # Must be EXACT title/permalink
operation="append",
content="\n## Implementation Notes\n- Added caching layer for performance"
)
edit_note(
identifier="API Documentation",
operation="replace_section",
section="## Authentication",
content="Updated authentication using JWT tokens with refresh capability."
)
```
**File organization (v0.13.0) - REQUIRES EXACT IDENTIFIERS:**
```
move_note(
identifier="Old Meeting Notes", # Must be EXACT title/permalink
destination_path="archive/2024/meeting-notes.md"
)
```
**Searching for knowledge:**
```
search_notes(
query="authentication system",
page=1,
page_size=10
)
```
**Building context from the knowledge graph:**
```
build_context(
url="memory://specs/search",
depth=2,
timeframe="1 month"
)
```
**Checking recent changes:**
```
recent_activity(
timeframe="1 week",
depth=1
)
```
**Creating knowledge visualizations:**
```
canvas(
nodes=[
{"id": "search", "x": 100, "y": 100, "width": 200, "height": 100, "type": "text", "text": "Search Design"},
{"id": "api", "x": 400, "y": 100, "width": 200, "height": 100, "type": "text", "text": "API Specification"}
],
edges=[
{"id": "link1", "fromNode": "search", "toNode": "api"}
],
title="System Architecture",
folder="diagrams"
)
```
**Monitoring sync status:**
```
sync_status() # Check overall system status
sync_status(project="work-notes") # Check specific project status
```
## memory:// URLs Explained
Basic Memory uses a special URL format to reference entities in the knowledge graph:
- `memory://title` - Reference by title
- `memory://folder/title` - Reference by folder and title
- `memory://permalink` - Reference by permalink
- `memory://path/relation_type/*` - Follow all relations of a specific type
- `memory://path/*/target` - Find all entities with relations to target
## Semantic Markdown Format
Knowledge is encoded in standard markdown using simple patterns:
**Observations** - Facts about an entity:
```markdown
- [category] This is an observation #tag1 #tag2 (optional context)
```
**Relations** - Links between entities:
```markdown
- relation_type [[Target Entity]] (optional context)
```
**Common Categories & Relation Types:**
- Categories: `[idea]`, `[decision]`, `[question]`, `[fact]`, `[requirement]`, `[technique]`, `[recipe]`, `[preference]`
- Relations: `relates_to`, `implements`, `requires`, `extends`, `part_of`, `pairs_with`, `inspired_by`,
`originated_from`
## When to Record Context
**Always consider recording context when**:
1. Users make decisions or reach conclusions
2. Important information emerges during conversation
3. Multiple related topics are discussed
4. The conversation contains information that might be useful later
5. Plans, tasks, or action items are mentioned
**Protocol for recording context**:
1. Identify valuable information in the conversation
2. Ask the user: "Would you like me to record our discussion about [topic] in Basic Memory?"
3. If they agree, use `write_note` to capture the information
4. If they decline, continue without recording
5. Let the user know when information has been recorded: "I've saved our discussion about [topic] to Basic Memory."
## Understanding User Interactions
Users will interact with Basic Memory in patterns like:
1. **Creating knowledge**:
```
Human: "Let's write up what we discussed about search."
You: I'll create a note capturing our discussion about the search functionality.
[Use write_note() to record the conversation details]
```
2. **Referencing existing knowledge**:
```
Human: "Take a look at memory://specs/search"
You: I'll examine that information.
[Use build_context() to gather related information]
[Then read_note() to access specific content]
```
3. **Finding information**:
```
Human: "What were our decisions about auth?"
You: Let me find that information for you.
[Use search_notes() to find relevant notes]
[Then build_context() to understand connections]
```
## Key Things to Remember
1. **Files are Truth**
- All knowledge lives in local files on the user's computer
- Users can edit files outside your interaction
- Changes need to be synced by the user (usually automatic)
- Always verify information is current with `recent_activity()`
2. **Building Context Effectively**
- Start with specific entities
- Follow meaningful relations
- Check recent changes
- Build context incrementally
- Combine related information
3. **Writing Knowledge Wisely**
- Using the same title+folder will overwrite existing notes
- Structure content with clear headings and sections
- Use semantic markup for observations and relations
- Keep files organized in logical folders
## Common Knowledge Patterns
### Capturing Decisions
```markdown
# Coffee Brewing Methods
## Context
I've experimented with various brewing methods including French press, pour over, and espresso.
## Decision
Pour over is my preferred method for light to medium roasts because it highlights subtle flavors and offers more control
over the extraction.
## Observations
- [technique] Blooming the coffee grounds for 30 seconds improves extraction #brewing
- [preference] Water temperature between 195-205°F works best #temperature
- [equipment] Gooseneck kettle provides better control of water flow #tools
## Relations
- pairs_with [[Light Roast Beans]]
- contrasts_with [[French Press Method]]
- requires [[Proper Grinding Technique]]
```
### Recording Project Structure
```markdown
# Garden Planning
## Overview
This document outlines the garden layout and planting strategy for this season.
## Observations
- [structure] Raised beds in south corner for sun exposure #layout
- [structure] Drip irrigation system installed for efficiency #watering
- [pattern] Companion planting used to deter pests naturally #technique
## Relations
- contains [[Vegetable Section]]
- contains [[Herb Garden]]
- implements [[Organic Gardening Principles]]
```
### Technical Discussions
```markdown
# Recipe Improvement Discussion
## Key Points
Discussed strategies for improving the chocolate chip cookie recipe.
## Observations
- [issue] Cookies spread too thin when baked at 350°F #texture
- [solution] Chilling dough for 24 hours improves flavor and reduces spreading #technique
- [decision] Will use brown butter instead of regular butter #flavor
## Relations
- improves [[Basic Cookie Recipe]]
- inspired_by [[Bakery-Style Cookies]]
- pairs_with [[Homemade Ice Cream]]
```
### Creating Effective Relations
When creating relations, you can:
1. Reference existing entities by their exact title
2. Create forward references to entities that don't exist yet
**Example workflow for creating notes with effective relations:**
1. **First, search for existing entities to reference:**
```
search_notes(query="travel")
```
2. **Check recent activity for current topics:**
```
recent_activity(timeframe="1 week")
```
3. **Create the note with both existing and forward references:**
```
write_note(
title="Tokyo Neighborhood Guide",
content="# Tokyo Neighborhood Guide
## Overview
Details about different Tokyo neighborhoods and their unique characteristics.
## Observations
- [area] Shibuya is a busy shopping district #shopping
- [transportation] Yamanote Line connects major neighborhoods #transit
- [recommendation] Visit Shimokitazawa for vintage shopping #unique
- [tip] Get a Suica card for easy train travel #convenience
## Relations
- references [[Packing Tips]] # Forward reference (will be linked when created)
- part_of [[Japan Travel Guide]] # Existing reference (if found in search)
- relates_to [[Transportation Options]] # Recent reference (if found in activity)
- located_in [[Tokyo]] # Forward reference
- visited_during [[Spring 2023 Trip]] # Forward reference",
folder="travel",
tags=["tokyo", "neighborhoods", "travel"]
)
```
**Key points:**
- Use exact titles from search results for existing entities: `[[Exact Title Found]]`
- Forward references are fine - they'll be linked automatically when target notes are created
- Check recent activity to reference currently active topics
- Use meaningful relation types: `part_of`, `located_in`, `visited_during` vs generic `relates_to`
## Error Handling
Common issues to watch for:
**1. Missing Content - Use Search as Fallback**
```
# If read_note fails, try search instead
search_notes(query="Document")
# Then use exact result from search:
read_note("Exact Document Title Found")
```
**2. Strict Mode for Edit/Move Operations (v0.13.0)**
❌ **This might fail if identifier isn't exact:**
```
edit_note(identifier="Meeting Note", operation="append", content="new content")
```
✅ **Safe approach - search first, then use exact result:**
```
# 1. Search first to find exact identifier
search_notes(query="meeting")
# 2. Use exact title from search results
edit_note(identifier="Meeting Notes 2024", operation="append", content="new content")
# Same pattern for move_note:
search_notes(query="old note")
move_note(identifier="Old Meeting Notes", destination_path="archive/old-notes.md")
```
**3. Forward References (Unresolved Relations)**
Forward references are a **feature, not an error!** Basic Memory automatically links them when target notes are created.
When you see unresolved relations in the response:
- Inform users: "I've created forward references that will be linked when you create those notes"
- Optionally suggest: "Would you like me to create any of these notes now to complete the connections?"
**4. Sync Issues**
If information seems outdated:
```
recent_activity(timeframe="1 hour")
```
If no recent activity shows, check sync status first:
```
sync_status()
```
If sync is pending or failed, suggest: "You might need to run `basic-memory sync`"
**5. Understanding Sync Status**
The `sync_status()` tool provides essential information about Basic Memory's operational state:
```
sync_status() # Check overall system readiness
sync_status(project="work-notes") # Check specific project context
```
**When to use sync_status:**
- At the start of conversations to verify system readiness
- When operations seem slow or fail unexpectedly
- Before working with large knowledge bases
- When switching between projects
- To provide users context about background processing
**What sync_status tells you:**
- **System Ready**: Whether all files are indexed and tools are operational
- **Active Processing**: Which projects are currently syncing with progress indicators
- **Project Status**: Individual project sync states (👁️ watching, ✅ completed, 🔄 syncing, ❌ failed, ⏳ pending)
- **Error Details**: Specific error messages for failed sync operations
- **Guidance**: Next steps when issues are detected
**Using sync_status effectively:**
- Check status if tools return unexpected results
- Use project parameter when working in multi-project setups
- Share status with users when explaining delays
- Monitor progress during initial setup or large imports
## Best Practices
1. **Proactively Record Context**
- Offer to capture important discussions
- Record decisions, rationales, and conclusions
- Link to related topics
- Ask for permission first: "Would you like me to save our discussion about [topic]?"
- Confirm when complete: "I've saved our discussion to Basic Memory"
2. **Create a Rich Semantic Graph**
- **Add meaningful observations**: Include at least 3-5 categorized observations in each note
- **Create deliberate relations**: Connect each note to at least 2-3 related entities
- **Use existing entities**: Before creating a new relation, search for existing entities
- **Verify wikilinks**: When referencing `[[Entity]]`, use exact titles of existing notes
- **Check accuracy**: Use `search_notes()` or `recent_activity()` to confirm entity titles
- **Use precise relation types**: Choose specific relation types that convey meaning (e.g., "implements" instead
of "relates_to")
- **Consider bidirectional relations**: When appropriate, create inverse relations in both entities
3. **Structure Content Thoughtfully**
- Use clear, descriptive titles
- Organize with logical sections (Context, Decision, Implementation, etc.)
- Include relevant context and background
- Add semantic observations with appropriate categories
- Use a consistent format for similar types of notes
- Balance detail with conciseness
4. **Navigate Knowledge Effectively**
- Start with specific searches
- Follow relation paths
- Combine information from multiple sources
- Verify information is current
- Build a complete picture before responding
5. **Help Users Maintain Their Knowledge**
- Suggest organizing related topics
- Identify potential duplicates
- Recommend adding relations between topics
- Offer to create summaries of scattered information
- Suggest potential missing relations: "I notice this might relate to [topic], would you like me to add that
connection?"
Built with ♥️ b
y Basic Machines
-115
View File
@@ -1,115 +0,0 @@
---
title: JSON Canvas Spec
version: 1.0
url: https://raw.githubusercontent.com/obsidianmd/jsoncanvas/refs/heads/main/spec/1.0.md
---
# JSON Canvas Spec
<small>Version 1.0 — 2024-03-11</small>
## Top level
The top level of JSON Canvas contains two arrays:
- `nodes` (optional, array of nodes)
- `edges` (optional, array of edges)
## Nodes
Nodes are objects within the canvas. Nodes may be text, files, links, or groups.
Nodes are placed in the array in ascending order by z-index. The first node in the array should be displayed below all
other nodes, and the last node in the array should be displayed on top of all other nodes.
### Generic node
All nodes include the following attributes:
- `id` (required, string) is a unique ID for the node.
- `type` (required, string) is the node type.
- `text`
- `file`
- `link`
- `group`
- `x` (required, integer) is the `x` position of the node in pixels.
- `y` (required, integer) is the `y` position of the node in pixels.
- `width` (required, integer) is the width of the node in pixels.
- `height` (required, integer) is the height of the node in pixels.
- `color` (optional, `canvasColor`) is the color of the node, see the Color section.
### Text type nodes
Text type nodes store text. Along with generic node attributes, text nodes include the following attribute:
- `text` (required, string) in plain text with Markdown syntax.
### File type nodes
File type nodes reference other files or attachments, such as images, videos, etc. Along with generic node attributes,
file nodes include the following attributes:
- `file` (required, string) is the path to the file within the system.
- `subpath` (optional, string) is a subpath that may link to a heading or a block. Always starts with a `#`.
### Link type nodes
Link type nodes reference a URL. Along with generic node attributes, link nodes include the following attribute:
- `url` (required, string)
### Group type nodes
Group type nodes are used as a visual container for nodes within it. Along with generic node attributes, group nodes
include the following attributes:
- `label` (optional, string) is a text label for the group.
- `background` (optional, string) is the path to the background image.
- `backgroundStyle` (optional, string) is the rendering style of the background image. Valid values:
- `cover` fills the entire width and height of the node.
- `ratio` maintains the aspect ratio of the background image.
- `repeat` repeats the image as a pattern in both x/y directions.
## Edges
Edges are lines that connect one node to another.
- `id` (required, string) is a unique ID for the edge.
- `fromNode` (required, string) is the node `id` where the connection starts.
- `fromSide` (optional, string) is the side where this edge starts. Valid values:
- `top`
- `right`
- `bottom`
- `left`
- `fromEnd` (optional, string) is the shape of the endpoint at the edge start. Defaults to `none` if not specified.
Valid values:
- `none`
- `arrow`
- `toNode` (required, string) is the node `id` where the connection ends.
- `toSide` (optional, string) is the side where this edge ends. Valid values:
- `top`
- `right`
- `bottom`
- `left`
- `toEnd` (optional, string) is the shape of the endpoint at the edge end. Defaults to `arrow` if not specified. Valid
values:
- `none`
- `arrow`
- `color` (optional, `canvasColor`) is the color of the line, see the Color section.
- `label` (optional, string) is a text label for the edge.
## Color
The `canvasColor` type is used to encode color data for nodes and edges. Colors attributes expect a string. Colors can
be specified in hex format e.g. `"#FF0000"`, or using one of the preset colors, e.g. `"1"` for red. Six preset colors
exist, mapped to the following numbers:
- `"1"` red
- `"2"` orange
- `"3"` yellow
- `"4"` green
- `"5"` cyan
- `"6"` purple
Specific values for the preset colors are intentionally not defined so that applications can tailor the presets to their
specific brand colors or color scheme.
+158
View File
@@ -0,0 +1,158 @@
"""Integration test for database reset command.
This test validates the fix for GitHub issue #151 where the reset command
was only removing the SQLite database but leaving project configuration
intact in ~/.basic-memory/config.json.
The test verifies that the reset command now:
1. Removes the SQLite database
2. Resets project configuration to default state (main project only)
3. Recreates empty database
"""
import json
import pytest
import tempfile
from pathlib import Path
from unittest.mock import patch
@pytest.mark.asyncio
async def test_reset_config_file_behavior(config_manager):
"""Test that reset command properly updates the config.json file."""
# Step 1: Set up initial state with multiple projects in config
original_projects = {
"project1": "/path/to/project1",
"project2": "/path/to/project2",
"user-project": "/home/user/documents",
}
config_manager.config.projects = original_projects.copy()
config_manager.config.default_project = "user-project"
# Step 2: Save the config to a temporary file to simulate the real config file
with tempfile.TemporaryDirectory() as temp_dir:
temp_config_file = Path(temp_dir) / "config.json"
config_manager.config_file = temp_config_file
config_manager.save_config(config_manager.config)
# Step 3: Verify the config file contains the multiple projects
config_json = json.loads(temp_config_file.read_text())
assert len(config_json["projects"]) == 3
assert config_json["default_project"] == "user-project"
assert "project1" in config_json["projects"]
assert "project2" in config_json["projects"]
assert "user-project" in config_json["projects"]
# Step 4: Simulate the reset command's configuration reset behavior
# This is the exact fix for issue #151
with patch("pathlib.Path.home") as mock_home:
mock_home.return_value = Path("/home/testuser")
# Apply the reset logic from the reset command
config_manager.config.projects = {"main": str(Path.home() / "basic-memory")}
config_manager.config.default_project = "main"
config_manager.save_config(config_manager.config)
# Step 5: Read the config file and verify it was properly reset
updated_config_json = json.loads(temp_config_file.read_text())
# Should now only have the main project
assert len(updated_config_json["projects"]) == 1
assert "main" in updated_config_json["projects"]
assert updated_config_json["projects"]["main"] == "/home/testuser/basic-memory"
assert updated_config_json["default_project"] == "main"
# All original projects should be gone from the file
assert "project1" not in updated_config_json["projects"]
assert "project2" not in updated_config_json["projects"]
assert "user-project" not in updated_config_json["projects"]
# This validates that issue #151 is fixed:
# Before the fix, these projects would persist in config.json after reset
# After the fix, only the default "main" project remains
@pytest.mark.asyncio
async def test_reset_command_source_code_validation():
"""Validate that the reset command source contains the required fix."""
# This test ensures the fix for issue #151 is present in the source code
reset_source_path = (
Path(__file__).parent.parent.parent / "src" / "basic_memory" / "cli" / "commands" / "db.py"
)
reset_source = reset_source_path.read_text()
# Verify the key components of the fix are present
required_lines = [
"# Reset project configuration",
'config_manager.config.projects = {"main": str(Path.home() / "basic-memory")}',
'config_manager.config.default_project = "main"',
"config_manager.save_config(config_manager.config)",
'logger.info("Project configuration reset to default")',
]
for line in required_lines:
assert line in reset_source, f"Required fix line not found: {line}"
# Verify the fix is in the correct location (after database deletion, before recreation)
lines = reset_source.split("\n")
# Find key markers
db_deletion_line = None
config_reset_line = None
db_recreation_line = None
for i, line in enumerate(lines):
if "db_path.unlink()" in line:
db_deletion_line = i
elif "config_manager.config.projects = {" in line:
config_reset_line = i
elif "asyncio.run(db.run_migrations" in line:
db_recreation_line = i
# Verify the order is correct
assert db_deletion_line is not None, "Database deletion code not found"
assert config_reset_line is not None, "Config reset code not found"
assert db_recreation_line is not None, "Database recreation code not found"
# Config reset should be after db deletion and before db recreation
assert db_deletion_line < config_reset_line < db_recreation_line, (
"Config reset is not in the correct order in the reset command"
)
@pytest.mark.asyncio
async def test_config_reset_behavior_simulation(config_manager):
"""Test the specific configuration reset behavior that fixes issue #151."""
# Step 1: Set up the problem state (multiple projects in config)
original_projects = {
"project1": "/path/to/project1",
"project2": "/path/to/project2",
"user-project": "/home/user/documents",
}
config_manager.config.projects = original_projects.copy()
config_manager.config.default_project = "user-project"
# Verify the problem state
assert len(config_manager.config.projects) == 3
assert config_manager.config.default_project == "user-project"
# Step 2: Apply the reset fix (simulate what reset command does)
with patch("pathlib.Path.home") as mock_home:
mock_home.return_value = Path("/home/testuser")
# This is the exact code from the reset command that fixes issue #151
config_manager.config.projects = {"main": str(Path.home() / "basic-memory")}
config_manager.config.default_project = "main"
# Note: We don't call save_config in test to avoid file operations
# Step 3: Verify the fix worked
assert len(config_manager.config.projects) == 1
assert "main" in config_manager.config.projects
assert config_manager.config.projects["main"] == "/home/testuser/basic-memory"
assert config_manager.config.default_project == "main"
# Step 4: Verify original projects are gone
for project_name in original_projects:
assert project_name not in config_manager.config.projects
+1
View File
@@ -148,6 +148,7 @@ def config_manager(app_config: BasicMemoryConfig, config_home, monkeypatch) -> C
# Patch the config_manager in all locations where it's imported
monkeypatch.setattr("basic_memory.config.config_manager", config_manager)
monkeypatch.setattr("basic_memory.services.project_service.config_manager", config_manager)
monkeypatch.setattr("basic_memory.mcp.project_session.config_manager", config_manager)
return config_manager
@@ -0,0 +1,173 @@
"""Integration test for project state synchronization between MCP session and CLI config.
This test validates the fix for GitHub issue #148 where MCP session and CLI commands
had inconsistent project state, causing "Project not found" errors and edit failures.
The test simulates the exact workflow reported in the issue:
1. MCP server starts with a default project
2. Default project is changed via CLI/API
3. MCP tools should immediately use the new project (no restart needed)
4. All operations should work consistently in the new project context
"""
import pytest
from fastmcp import Client
@pytest.mark.asyncio
async def test_project_state_sync_after_default_change(mcp_server, app, config_manager):
"""Test that MCP session stays in sync when default project is changed."""
async with Client(mcp_server) as client:
# Step 1: Verify initial state - MCP should show test-project as current
initial_result = await client.call_tool("get_current_project", {})
assert len(initial_result) == 1
assert "Current project: test-project" in initial_result[0].text
# Step 2: Create a second project that we can switch to
create_result = await client.call_tool(
"create_memory_project",
{
"project_name": "minerva",
"project_path": "/tmp/minerva-test-project",
"set_default": False, # Don't set as default yet
},
)
assert len(create_result) == 1
assert "" in create_result[0].text
assert "minerva" in create_result[0].text
# Step 3: Change default project to minerva via set_default_project tool
# This simulates the CLI command `basic-memory project default minerva`
set_default_result = await client.call_tool(
"set_default_project", {"project_name": "minerva"}
)
assert len(set_default_result) == 1
assert "" in set_default_result[0].text
assert "minerva" in set_default_result[0].text
# Step 4: Verify MCP session immediately reflects the change (no restart needed)
# This tests the fix - session.refresh_from_config() should have been called
updated_result = await client.call_tool("get_current_project", {})
assert len(updated_result) == 1
# The fix should ensure these are consistent now:
updated_text = updated_result[0].text
assert "Current project: minerva" in updated_text
# Step 5: Verify config manager also shows the new default
assert config_manager.default_project == "minerva"
# Step 6: Test that note operations work in the new project context
# This validates that the identifier resolution works correctly
write_result = await client.call_tool(
"write_note",
{
"title": "Test Consistency Note",
"folder": "test",
"content": "# Test Note\n\nThis note tests project state consistency.\n\n- [test] Project state sync working",
"tags": "test,consistency",
},
)
assert len(write_result) == 1
assert "Test Consistency Note" in write_result[0].text
# Step 7: Test that we can read the note we just created
read_result = await client.call_tool("read_note", {"identifier": "Test Consistency Note"})
assert len(read_result) == 1
assert "Test Consistency Note" in read_result[0].text
assert "project state sync working" in read_result[0].text.lower()
# Step 8: Test that edit operations work (this was failing in the original issue)
edit_result = await client.call_tool(
"edit_note",
{
"identifier": "Test Consistency Note",
"operation": "append",
"content": "\n\n## Update\n\nEdit operation successful after project switch!",
},
)
assert len(edit_result) == 1
assert "added" in edit_result[0].text.lower() and "lines" in edit_result[0].text.lower()
# Step 9: Verify the edit was applied
final_read_result = await client.call_tool(
"read_note", {"identifier": "Test Consistency Note"}
)
assert len(final_read_result) == 1
final_content = final_read_result[0].text
assert "Edit operation successful" in final_content
# Clean up - switch back to test-project
await client.call_tool("switch_project", {"project_name": "test-project"})
@pytest.mark.asyncio
async def test_multiple_project_switches_maintain_consistency(mcp_server, app, config_manager):
"""Test that multiple project switches maintain consistent state."""
async with Client(mcp_server) as client:
# Create multiple test projects
for project_name in ["project-a", "project-b", "project-c"]:
await client.call_tool(
"create_memory_project",
{
"project_name": project_name,
"project_path": f"/tmp/{project_name}",
"set_default": False,
},
)
# Test switching between projects multiple times
for project_name in ["project-a", "project-b", "project-c", "test-project"]:
# Set as default
set_result = await client.call_tool(
"set_default_project", {"project_name": project_name}
)
assert "" in set_result[0].text
# Verify MCP session immediately reflects the change
current_result = await client.call_tool("get_current_project", {})
assert f"Current project: {project_name}" in current_result[0].text
# Verify config is also updated
assert config_manager.default_project == project_name
# Test that operations work in this project
note_title = f"Note in {project_name}"
write_result = await client.call_tool(
"write_note",
{
"title": note_title,
"folder": "test",
"content": f"# {note_title}\n\nTesting operations in {project_name}.",
"tags": "test",
},
)
assert note_title in write_result[0].text
# Clean up - switch back to test-project
await client.call_tool("set_default_project", {"project_name": "test-project"})
@pytest.mark.asyncio
async def test_session_handles_nonexistent_project_gracefully(mcp_server, app):
"""Test that session handles attempts to switch to nonexistent projects gracefully."""
async with Client(mcp_server) as client:
# Try to switch to a project that doesn't exist
switch_result = await client.call_tool(
"switch_project", {"project_name": "nonexistent-project"}
)
assert len(switch_result) == 1
result_text = switch_result[0].text
# Should show an error message
assert "Error:" in result_text
assert "not found" in result_text.lower()
assert "Available projects:" in result_text
assert "test-project" in result_text # Should list available projects
# Verify the session stays on the original project
current_result = await client.call_tool("get_current_project", {})
assert "Current project: test-project" in current_result[0].text
+16 -22
View File
@@ -418,59 +418,53 @@ async def test_write_note_preserves_content_frontmatter(app):
@pytest.mark.asyncio
async def test_write_note_permalink_collision_fix_issue_139(app):
"""Test fix for GitHub Issue #139: UNIQUE constraint failed: entity.permalink.
This reproduces the exact scenario described in the issue:
1. Create a note with title "Note 1"
1. Create a note with title "Note 1"
2. Create another note with title "Note 2"
3. Try to create/replace first note again with same title "Note 1"
Before the fix, step 3 would fail with UNIQUE constraint error.
After the fix, it should either update the existing note or create with unique permalink.
"""
# Step 1: Create first note
result1 = await write_note.fn(
title="Note 1",
folder="test",
content="Original content for note 1"
title="Note 1", folder="test", content="Original content for note 1"
)
assert "# Created note" in result1
assert "permalink: test/note-1" in result1
# Step 2: Create second note with different title
result2 = await write_note.fn(
title="Note 2",
folder="test",
content="Content for note 2"
)
result2 = await write_note.fn(title="Note 2", folder="test", content="Content for note 2")
assert "# Created note" in result2
assert "permalink: test/note-2" in result2
# Step 3: Try to create/replace first note again
# This scenario would trigger the UNIQUE constraint failure before the fix
result3 = await write_note.fn(
title="Note 1", # Same title as first note
folder="test", # Same folder as first note
content="Replacement content for note 1" # Different content
folder="test", # Same folder as first note
content="Replacement content for note 1", # Different content
)
# This should not raise a UNIQUE constraint failure error
# It should succeed and either:
# 1. Update the existing note (preferred behavior)
# 2. Create a new note with unique permalink (fallback behavior)
assert result3 is not None
assert ("Updated note" in result3 or "Created note" in result3)
assert "Updated note" in result3 or "Created note" in result3
# The result should contain either the original permalink or a unique one
assert ("permalink: test/note-1" in result3 or "permalink: test/note-1-1" in result3)
assert "permalink: test/note-1" in result3 or "permalink: test/note-1-1" in result3
# Verify we can read back the content
if "permalink: test/note-1" in result3:
# Updated existing note case
content = await read_note.fn("test/note-1")
assert "Replacement content for note 1" in content
else:
# Created new note with unique permalink case
# Created new note with unique permalink case
content = await read_note.fn("test/note-1-1")
assert "Replacement content for note 1" in content
# Original note should still exist
@@ -22,7 +22,7 @@ async def test_upsert_entity_new_entity(entity_repository: EntityRepository):
)
result = await entity_repository.upsert_entity(entity)
assert result.id is not None
assert result.title == "Test Entity"
assert result.permalink == "test/test-entity"
@@ -60,7 +60,7 @@ async def test_upsert_entity_same_file_update(entity_repository: EntityRepositor
)
result2 = await entity_repository.upsert_entity(entity2)
# Should update existing entity (same ID)
assert result2.id == original_id
assert result2.title == "Updated Title"
@@ -92,20 +92,20 @@ async def test_upsert_entity_permalink_conflict_different_file(entity_repository
title="Second Entity",
entity_type="note",
permalink="test/shared-permalink", # Same permalink
file_path="test/second-file.md", # Different file_path
file_path="test/second-file.md", # Different file_path
content_type="text/markdown",
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc),
)
result2 = await entity_repository.upsert_entity(entity2)
# Should create new entity with unique permalink
assert result2.id != first_id
assert result2.title == "Second Entity"
assert result2.permalink == "test/shared-permalink-1" # Should get suffix
assert result2.file_path == "test/second-file.md"
# Original entity should be unchanged
original = await entity_repository.get_by_permalink("test/shared-permalink")
assert original is not None
@@ -117,30 +117,30 @@ async def test_upsert_entity_permalink_conflict_different_file(entity_repository
async def test_upsert_entity_multiple_permalink_conflicts(entity_repository: EntityRepository):
"""Test upserting multiple entities with permalink conflicts."""
base_permalink = "test/conflict"
# Create entities with conflicting permalinks
entities = []
for i in range(3):
entity = Entity(
project_id=entity_repository.project_id,
title=f"Entity {i+1}",
title=f"Entity {i + 1}",
entity_type="note",
permalink=base_permalink, # All try to use same permalink
file_path=f"test/file-{i+1}.md", # Different file paths
file_path=f"test/file-{i + 1}.md", # Different file paths
content_type="text/markdown",
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc),
)
result = await entity_repository.upsert_entity(entity)
entities.append(result)
# Verify permalinks are unique
expected_permalinks = ["test/conflict", "test/conflict-1", "test/conflict-2"]
actual_permalinks = [entity.permalink for entity in entities]
assert set(actual_permalinks) == set(expected_permalinks)
# Verify all entities were created (different IDs)
entity_ids = [entity.id for entity in entities]
assert len(set(entity_ids)) == 3
@@ -151,7 +151,7 @@ async def test_upsert_entity_race_condition_file_path(entity_repository: EntityR
"""Test that upsert handles race condition where file_path conflict occurs after initial check."""
from unittest.mock import patch
from sqlalchemy.exc import IntegrityError
# Create an entity first
entity1 = Entity(
project_id=entity_repository.project_id,
@@ -163,26 +163,26 @@ async def test_upsert_entity_race_condition_file_path(entity_repository: EntityR
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc),
)
result1 = await entity_repository.upsert_entity(entity1)
original_id = result1.id
# Create another entity with different file_path and permalink
entity2 = Entity(
project_id=entity_repository.project_id,
title="Race Condition Test",
entity_type="note",
entity_type="note",
permalink="test/race-entity",
file_path="test/different-file.md", # Different initially
content_type="text/markdown",
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc),
)
# Now simulate race condition: change file_path to conflict after the initial check
original_add = entity_repository.session_maker().add
call_count = 0
def mock_add(obj):
nonlocal call_count
if isinstance(obj, Entity) and call_count == 0:
@@ -192,12 +192,12 @@ async def test_upsert_entity_race_condition_file_path(entity_repository: EntityR
# This should trigger IntegrityError for file_path constraint
raise IntegrityError("UNIQUE constraint failed: entity.file_path", None, None)
return original_add(obj)
# Mock session.add to simulate the race condition
with patch.object(entity_repository.session_maker().__class__, 'add', side_effect=mock_add):
with patch.object(entity_repository.session_maker().__class__, "add", side_effect=mock_add):
# This should handle the race condition gracefully by updating the existing entity
result2 = await entity_repository.upsert_entity(entity2)
# Should return the updated original entity (same ID)
assert result2.id == original_id
assert result2.title == "Race Condition Test" # Updated title
@@ -210,24 +210,24 @@ async def test_upsert_entity_gap_in_suffixes(entity_repository: EntityRepository
"""Test that upsert finds the next available suffix even with gaps."""
# Manually create entities with non-sequential suffixes
base_permalink = "test/gap"
# Create entities with permalinks: "test/gap", "test/gap-1", "test/gap-3"
# (skipping "test/gap-2")
permalinks = [base_permalink, f"{base_permalink}-1", f"{base_permalink}-3"]
for i, permalink in enumerate(permalinks):
entity = Entity(
project_id=entity_repository.project_id,
title=f"Entity {i+1}",
title=f"Entity {i + 1}",
entity_type="note",
permalink=permalink,
file_path=f"test/gap-file-{i+1}.md",
file_path=f"test/gap-file-{i + 1}.md",
content_type="text/markdown",
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc),
)
await entity_repository.add(entity) # Use direct add to set specific permalinks
# Now try to upsert a new entity that should get "test/gap-2"
new_entity = Entity(
project_id=entity_repository.project_id,
@@ -239,10 +239,10 @@ async def test_upsert_entity_gap_in_suffixes(entity_repository: EntityRepository
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc),
)
result = await entity_repository.upsert_entity(new_entity)
# Should get the next available suffix - our implementation finds gaps
# so it should be "test/gap-2" (filling the gap)
assert result.permalink == "test/gap-2"
assert result.title == "Gap Filler"
assert result.title == "Gap Filler"
+19 -19
View File
@@ -46,18 +46,18 @@ async def test_migration_deduplication_single_call(
# First call should run migrations
await db.run_migrations(app_config)
# Verify migrations were called
mock_alembic_command.upgrade.assert_called_once_with(mock_alembic_config, "head")
mock_search_repository.init_search_index.assert_called_once()
# Reset mocks for second call
mock_alembic_command.reset_mock()
mock_search_repository.reset_mock()
# Second call should skip migrations
await db.run_migrations(app_config)
# Verify migrations were NOT called again
mock_alembic_command.upgrade.assert_not_called()
mock_search_repository.init_search_index.assert_not_called()
@@ -75,18 +75,18 @@ async def test_migration_force_parameter(
# First call should run migrations
await db.run_migrations(app_config)
# Verify migrations were called
mock_alembic_command.upgrade.assert_called_once_with(mock_alembic_config, "head")
mock_search_repository.init_search_index.assert_called_once()
# Reset mocks for forced call
mock_alembic_command.reset_mock()
mock_search_repository.reset_mock()
# Forced call should run migrations again
await db.run_migrations(app_config, force=True)
# Verify migrations were called again
mock_alembic_command.upgrade.assert_called_once_with(mock_alembic_config, "head")
mock_search_repository.init_search_index.assert_called_once()
@@ -99,10 +99,10 @@ async def test_migration_state_reset_on_shutdown():
db._migrations_completed = True
db._engine = AsyncMock()
db._session_maker = AsyncMock()
# Shutdown should reset state
await db.shutdown_db()
# Verify state was reset
assert db._migrations_completed is False
assert db._engine is None
@@ -123,11 +123,11 @@ async def test_get_or_create_db_runs_migrations_automatically(
engine, session_maker = await db.get_or_create_db(
app_config.database_path, app_config=app_config
)
# Verify we got valid objects
assert engine is not None
assert session_maker is not None
# Verify migrations were called
mock_alembic_command.upgrade.assert_called_once_with(mock_alembic_config, "head")
mock_search_repository.init_search_index.assert_called_once()
@@ -147,11 +147,11 @@ async def test_get_or_create_db_skips_migrations_when_disabled(
engine, session_maker = await db.get_or_create_db(
app_config.database_path, ensure_migrations=False
)
# Verify we got valid objects
assert engine is not None
assert session_maker is not None
# Verify migrations were NOT called
mock_alembic_command.upgrade.assert_not_called()
mock_search_repository.init_search_index.assert_not_called()
@@ -169,19 +169,19 @@ async def test_multiple_get_or_create_db_calls_deduplicated(
# First call should create engine and run migrations
await db.get_or_create_db(app_config.database_path, app_config=app_config)
# Verify migrations were called
mock_alembic_command.upgrade.assert_called_once_with(mock_alembic_config, "head")
mock_search_repository.init_search_index.assert_called_once()
# Reset mocks for subsequent calls
mock_alembic_command.reset_mock()
mock_search_repository.reset_mock()
# Subsequent calls should not run migrations again
await db.get_or_create_db(app_config.database_path, app_config=app_config)
await db.get_or_create_db(app_config.database_path, app_config=app_config)
# Verify migrations were NOT called again
mock_alembic_command.upgrade.assert_not_called()
mock_search_repository.init_search_index.assert_not_called()
mock_search_repository.init_search_index.assert_not_called()