Skip to content

Commit ed94877

Browse files
phernandezclaude
andauthored
feat: enable default_project_mode by default (#560)
Signed-off-by: phernandez <paul@basicmachines.co> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8df88e4 commit ed94877

12 files changed

Lines changed: 164 additions & 70 deletions

src/basic_memory/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ class BasicMemoryConfig(BaseSettings):
8181
description="Name of the default project to use",
8282
)
8383
default_project_mode: bool = Field(
84-
default=False,
84+
default=True,
8585
description="When True, MCP tools automatically use default_project when no project parameter is specified. Enables simplified UX for single-project workflows.",
8686
)
8787

src/basic_memory/mcp/project_context.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,17 @@ async def resolve_project_parameter(
2929
default_project_mode: Optional[bool] = None,
3030
default_project: Optional[str] = None,
3131
) -> Optional[str]:
32-
"""Resolve project parameter using three-tier hierarchy.
32+
"""Resolve project parameter using unified linear priority chain.
3333
3434
This is a thin wrapper around ProjectResolver for backwards compatibility.
3535
New code should consider using ProjectResolver directly for more detailed
3636
resolution information.
3737
38-
if cloud_mode:
39-
project is required (unless allow_discovery=True for tools that support discovery mode)
40-
else:
41-
Resolution order:
42-
1. Single Project Mode (--project cli arg, or BASIC_MEMORY_MCP_PROJECT env var) - highest priority
43-
2. Explicit project parameter - medium priority
44-
3. Default project if default_project_mode=true - lowest priority
38+
Resolution order (same for local and cloud modes):
39+
1. ENV_CONSTRAINT: BASIC_MEMORY_MCP_PROJECT env var (highest priority)
40+
2. EXPLICIT: project parameter passed directly
41+
3. DEFAULT: default project when default_project_mode=true
42+
4. Fallback: cloud → CLOUD_DISCOVERY or ValueError; local → NONE
4543
4644
Args:
4745
project: Optional explicit project parameter

src/basic_memory/mcp/prompts/ai_assistant_guide.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def ai_assistant_guide() -> str:
3232

3333
# Add mode-specific header
3434
mode_info = ""
35-
if config.default_project_mode: # pragma: no cover
35+
if config.default_project_mode:
3636
mode_info = f"""
3737
# 🎯 Default Project Mode Active
3838

src/basic_memory/mcp/resources/ai_assistant_guide.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ await write_note("Note", "Content", "folder")
4343
await write_note("Note", "Content", "folder", project="main")
4444
```
4545

46-
When `default_project_mode=false` (default):
46+
When `default_project_mode=false`:
4747
```python
4848
# Project required:
4949
await write_note("Note", "Content", "folder", project="main") #

src/basic_memory/mcp/tools/build_context.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,9 @@ async def build_context(
5050
a rich context graph of related information.
5151
5252
Project Resolution:
53-
Server resolves projects in this order: Single Project Mode → project parameter → default project.
54-
If project unknown, use list_memory_projects() or recent_activity() first.
53+
Server resolves projects using a unified priority chain (same in local and cloud modes):
54+
Single Project Mode → project parameter → default project.
55+
Uses default project automatically. Specify `project` parameter to target a different project.
5556
5657
Args:
5758
project: Project name to build context from. Optional - server will resolve using hierarchy.

src/basic_memory/mcp/tools/read_note.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ async def read_note(
3030
returning the raw markdown content including observations, relations, and metadata.
3131
3232
Project Resolution:
33-
Server resolves projects in this order: Single Project Mode → project parameter → default project.
34-
If project unknown, use list_memory_projects() or recent_activity() first.
33+
Server resolves projects using a unified priority chain (same in local and cloud modes):
34+
Single Project Mode → project parameter → default project.
35+
Uses default project automatically. Specify `project` parameter to target a different project.
3536
3637
This tool will try multiple lookup strategies to find the most relevant note:
3738
1. Direct permalink lookup

src/basic_memory/mcp/tools/write_note.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@ async def write_note(
3232
Creates or updates a markdown note with semantic observations and relations.
3333
3434
Project Resolution:
35-
Server resolves projects in this order: Single Project Mode → project parameter → default project.
36-
If project unknown, use list_memory_projects() or recent_activity() first.
35+
Server resolves projects using a unified priority chain (same in local and cloud modes):
36+
Single Project Mode → project parameter → default project.
37+
Uses default project automatically. Specify `project` parameter to target a different project.
3738
3839
The content can include semantic observations and relations using markdown syntax:
3940
@@ -79,12 +80,7 @@ async def write_note(
7980
- Session tracking metadata for project awareness
8081
8182
Examples:
82-
# Assistant flow when project is unknown
83-
# 1. list_memory_projects() -> Ask user which project
84-
# 2. User: "Use my-research"
85-
# 3. write_note(...) and remember "my-research" for session
86-
87-
# Create a simple note
83+
# Create a simple note (uses default project automatically)
8884
write_note(
8985
project="my-research",
9086
title="Meeting Notes",

src/basic_memory/project_resolver.py

Lines changed: 33 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33
This module provides a single canonical implementation of project resolution
44
logic, eliminating duplicated decision trees across the codebase.
55
6-
The resolution follows a three-tier hierarchy:
7-
1. Constrained mode: BASIC_MEMORY_MCP_PROJECT env var (highest priority)
8-
2. Explicit parameter: Project passed directly to operation
9-
3. Default project: Used when default_project_mode=true (lowest priority)
6+
The resolution follows a unified linear priority chain that works
7+
identically in both local and cloud modes:
108
11-
In cloud mode, project is required unless discovery mode is explicitly allowed.
9+
1. ENV_CONSTRAINT: BASIC_MEMORY_MCP_PROJECT env var (highest priority)
10+
2. EXPLICIT: Project passed directly to operation
11+
3. DEFAULT: Default project when default_project_mode=true
12+
4. Fallback: cloud → CLOUD_DISCOVERY or ValueError; local → NONE
1213
"""
1314

1415
import os
@@ -68,7 +69,7 @@ class ProjectResolver:
6869
used by MCP tools, API routes, and CLI commands.
6970
7071
Args:
71-
cloud_mode: Whether running in cloud mode (project required)
72+
cloud_mode: Whether running in cloud mode
7273
default_project_mode: Whether to use default project when not specified
7374
default_project: The default project name
7475
constrained_project: Optional env-constrained project override
@@ -110,13 +111,13 @@ def resolve(
110111
project: Optional[str] = None,
111112
allow_discovery: bool = False,
112113
) -> ResolvedProject:
113-
"""Resolve project using the three-tier hierarchy.
114+
"""Resolve project using a unified linear priority chain.
114115
115-
Resolution order:
116-
1. Cloud mode check (project required unless discovery allowed)
117-
2. Constrained project from env var (highest priority in local mode)
118-
3. Explicit project parameter
119-
4. Default project if default_project_mode=true
116+
The same resolution order applies in both local and cloud modes:
117+
1. ENV_CONSTRAINT — BASIC_MEMORY_MCP_PROJECT env var (highest priority)
118+
2. EXPLICIT — project parameter passed directly
119+
3. DEFAULT — default project when default_project_mode=true
120+
4. Fallback — cloud: CLOUD_DISCOVERY or ValueError; local: NONE
120121
121122
Args:
122123
project: Optional explicit project parameter
@@ -127,31 +128,10 @@ def resolve(
127128
ResolvedProject with project name, resolution mode, and reason
128129
129130
Raises:
130-
ValueError: If in cloud mode and no project specified (unless discovery allowed)
131+
ValueError: If in cloud mode and no project could be resolved
132+
(unless allow_discovery=True)
131133
"""
132-
# --- Cloud Mode Handling ---
133-
# In cloud mode, project is required unless discovery is explicitly allowed
134-
if self.cloud_mode:
135-
if project:
136-
logger.debug(f"Cloud mode: using explicit project '{project}'")
137-
return ResolvedProject(
138-
project=project,
139-
mode=ResolutionMode.CLOUD_EXPLICIT,
140-
reason=f"Explicit project in cloud mode: {project}",
141-
)
142-
elif allow_discovery:
143-
logger.debug("Cloud mode: discovery mode allowed, no project required")
144-
return ResolvedProject(
145-
project=None,
146-
mode=ResolutionMode.CLOUD_DISCOVERY,
147-
reason="Discovery mode enabled in cloud",
148-
)
149-
else:
150-
raise ValueError("No project specified. Project is required for cloud mode.")
151-
152-
# --- Local Mode: Three-Tier Hierarchy ---
153-
154-
# Priority 1: CLI constraint overrides everything
134+
# --- Priority 1: ENV constraint overrides everything ---
155135
if self.constrained_project:
156136
logger.debug(f"Using CLI constrained project: {self.constrained_project}")
157137
return ResolvedProject(
@@ -160,16 +140,17 @@ def resolve(
160140
reason=f"Environment constraint: BASIC_MEMORY_MCP_PROJECT={self.constrained_project}",
161141
)
162142

163-
# Priority 2: Explicit project parameter
143+
# --- Priority 2: Explicit project parameter ---
164144
if project:
145+
mode = ResolutionMode.CLOUD_EXPLICIT if self.cloud_mode else ResolutionMode.EXPLICIT
165146
logger.debug(f"Using explicit project parameter: {project}")
166147
return ResolvedProject(
167148
project=project,
168-
mode=ResolutionMode.EXPLICIT,
149+
mode=mode,
169150
reason=f"Explicit parameter: {project}",
170151
)
171152

172-
# Priority 3: Default project mode
153+
# --- Priority 3: Default project mode ---
173154
if self.default_project_mode and self.default_project:
174155
logger.debug(f"Using default project from config: {self.default_project}")
175156
return ResolvedProject(
@@ -178,12 +159,23 @@ def resolve(
178159
reason=f"Default project mode: {self.default_project}",
179160
)
180161

181-
# No resolution possible
162+
# --- Fallback: mode-dependent behavior ---
163+
if self.cloud_mode:
164+
if allow_discovery:
165+
logger.debug("Cloud mode: discovery mode allowed, no project required")
166+
return ResolvedProject(
167+
project=None,
168+
mode=ResolutionMode.CLOUD_DISCOVERY,
169+
reason="Discovery mode enabled in cloud",
170+
)
171+
raise ValueError("No project specified. Project is required for cloud mode.")
172+
173+
# Local mode: no resolution possible
182174
logger.debug("No project resolution possible")
183175
return ResolvedProject(
184176
project=None,
185177
mode=ResolutionMode.NONE,
186-
reason="No project specified and default_project_mode is disabled",
178+
reason="No project specified and no default project configured",
187179
)
188180

189181
def require_project(

test-int/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ def app_config(
243243
env="test",
244244
projects=projects,
245245
default_project="test-project",
246-
default_project_mode=False, # Match real-world usage - tools must pass explicit project
246+
default_project_mode=False, # Explicit False for test isolation - tests pass project explicitly
247247
update_permalinks_on_move=True,
248248
cloud_mode=False, # Explicitly disable cloud mode
249249
sync_changes=False, # Disable file sync in tests - prevents lifespan from starting blocking task

tests/mcp/test_project_context.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,14 @@
1010

1111

1212
@pytest.mark.asyncio
13-
async def test_cloud_mode_requires_project_by_default(config_manager, monkeypatch):
13+
async def test_cloud_mode_requires_project_when_no_default(config_manager, monkeypatch):
1414
from basic_memory.mcp.project_context import resolve_project_parameter
1515

1616
cfg = config_manager.load_config()
1717
cfg.cloud_mode = True
18+
# default_project_mode defaults to True, so explicitly disable it
19+
# to test the "no default available" path
20+
cfg.default_project_mode = False
1821
config_manager.save_config(cfg)
1922

2023
with pytest.raises(ValueError) as exc_info:
@@ -30,6 +33,8 @@ async def test_cloud_mode_allows_discovery_when_enabled(config_manager):
3033

3134
cfg = config_manager.load_config()
3235
cfg.cloud_mode = True
36+
# Disable default_project_mode so discovery fallback is reached
37+
cfg.default_project_mode = False
3338
config_manager.save_config(cfg)
3439

3540
assert await resolve_project_parameter(project=None, allow_discovery=True) is None
@@ -101,3 +106,20 @@ async def test_local_mode_returns_none_when_no_resolution(config_manager, monkey
101106

102107
monkeypatch.delenv("BASIC_MEMORY_MCP_PROJECT", raising=False)
103108
assert await resolve_project_parameter(project=None) is None
109+
110+
111+
@pytest.mark.asyncio
112+
async def test_cloud_mode_uses_default_project(config_manager, config_home, monkeypatch):
113+
"""In cloud mode with default_project_mode=True, default project is resolved."""
114+
from basic_memory.mcp.project_context import resolve_project_parameter
115+
116+
cfg = config_manager.load_config()
117+
cfg.cloud_mode = True
118+
cfg.default_project_mode = True
119+
(config_home / "cloud-default").mkdir(parents=True, exist_ok=True)
120+
cfg.projects["cloud-default"] = str(config_home / "cloud-default")
121+
cfg.default_project = "cloud-default"
122+
config_manager.save_config(cfg)
123+
124+
monkeypatch.delenv("BASIC_MEMORY_MCP_PROJECT", raising=False)
125+
assert await resolve_project_parameter(project=None) == "cloud-default"

0 commit comments

Comments
 (0)