Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/basic_memory/mcp/prompts/continue_conversation.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
from basic_memory.mcp.async_client import get_client
from basic_memory.mcp.server import mcp
from basic_memory.mcp.tools.utils import call_post
from basic_memory.schemas.base import TimeFrame
from basic_memory.schemas.prompt import ContinueConversationRequest


Expand All @@ -24,7 +23,7 @@
async def continue_conversation(
topic: Annotated[Optional[str], Field(description="Topic or keyword to search for")] = None,
timeframe: Annotated[
Optional[TimeFrame],
Optional[str],
Field(description="How far back to look for activity (e.g. '1d', '1 week')"),
] = None,
) -> str:
Expand Down
168 changes: 40 additions & 128 deletions src/basic_memory/mcp/prompts/recent_activity.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,14 @@
These prompts help users see what has changed in their knowledge base recently.
"""

from textwrap import dedent
from typing import Annotated, Optional

from loguru import logger
from pydantic import Field

from basic_memory.mcp.prompts.utils import format_prompt_context, PromptContext, PromptContextItem
from basic_memory.mcp.server import mcp
from basic_memory.mcp.tools.recent_activity import recent_activity
from basic_memory.schemas.base import TimeFrame
from basic_memory.schemas.memory import GraphContext, ProjectActivitySummary
from basic_memory.schemas.search import SearchItemType


@mcp.prompt(
Expand All @@ -22,7 +19,7 @@
)
async def recent_activity_prompt(
timeframe: Annotated[
TimeFrame,
str,
Field(description="How far back to look for activity (e.g. '1d', '1 week')"),
] = "7d",
project: Annotated[
Expand All @@ -47,142 +44,57 @@ async def recent_activity_prompt(
"""
logger.info(f"Getting recent activity, timeframe: {timeframe}, project: {project}")

recent = await recent_activity.fn(
project=project, timeframe=timeframe, type=[SearchItemType.ENTITY]
# Call the tool function - it returns a well-formatted string
# Pass type as string values (not enum) to match the tool's expected input
activity_summary = await recent_activity.fn(
project=project, timeframe=timeframe, type="entity"
)

# Extract primary results from the hierarchical structure
primary_results = []
related_results = []

if isinstance(recent, ProjectActivitySummary):
# Discovery mode - extract results from all projects
for _, project_activity in recent.projects.items():
if project_activity.activity.results:
# Take up to 2 primary results per project
for item in project_activity.activity.results[:2]:
primary_results.append(item.primary_result)
# Add up to 1 related result per primary item
if item.related_results:
related_results.extend(item.related_results[:1]) # pragma: no cover

# Limit total results for readability
primary_results = primary_results[:8]
related_results = related_results[:6]

elif isinstance(recent, GraphContext):
# Project-specific mode - use existing logic
if recent.results:
# Take up to 5 primary results
for item in recent.results[:5]:
primary_results.append(item.primary_result)
# Add up to 2 related results per primary item
if item.related_results:
related_results.extend(item.related_results[:2]) # pragma: no cover

# Set topic based on mode
if project:
topic = f"Recent Activity in {project} ({timeframe})"
else:
topic = f"Recent Activity Across All Projects ({timeframe})"

prompt_context = format_prompt_context(
PromptContext(
topic=topic,
timeframe=timeframe,
results=[
PromptContextItem(
primary_results=primary_results,
related_results=related_results[:10], # Limit total related results
)
],
)
)

# Add mode-specific suggestions
first_title = "Recent Topic"
if primary_results and len(primary_results) > 0:
first_title = primary_results[0].title

if project:
# Project-specific suggestions
capture_suggestions = f"""
## Opportunity to Capture Activity Summary

Consider creating a summary note of recent activity in {project}:

```python
await write_note(
"{project}",
title="Activity Summary {timeframe}",
content='''
# Activity Summary for {project} ({timeframe})

## Overview
[Summary of key changes and developments in this project over this period]
# Build the prompt response
# The tool already returns formatted markdown, so we use it directly
# and add prompt-specific guidance
target = project if project else "all projects"

## Key Updates
[List main updates and their significance within this project]
prompt_guidance = dedent(f"""
# Recent Activity Context

## Observations
- [trend] [Observation about patterns in recent activity]
- [insight] [Connection between different activities]
This is a memory retrieval session showing recent activity from {target}.

## Relations
- summarizes [[{first_title}]]
- relates_to [[{project} Overview]]
''',
folder="summaries"
)
```
{activity_summary}

Summarizing periodic activity helps create high-level insights and connections within the project.
"""
else:
# Discovery mode suggestions
project_count = len(recent.projects) if isinstance(recent, ProjectActivitySummary) else 0
most_active = (
getattr(recent.summary, "most_active_project", "Unknown")
if isinstance(recent, ProjectActivitySummary)
else "Unknown"
)
---

capture_suggestions = f"""
## Cross-Project Activity Discovery
## Next Steps

Found activity across {project_count} projects. Most active: **{most_active}**
Based on this activity, you can:

Consider creating a cross-project summary:
1. **Explore specific items** - Use `read_note("permalink")` to dive deeper into any item
2. **Search for related content** - Use `search_notes("topic")` to find connected knowledge
3. **Build context** - Use `build_context("memory://path")` to see relationships

```python
await write_note(
"{most_active if most_active != "Unknown" else "main"}",
title="Cross-Project Activity Summary {timeframe}",
content='''
# Cross-Project Activity Summary ({timeframe})
## Capture Opportunity

## Overview
Activity found across {project_count} projects, with {most_active} showing the most activity.
If you notice patterns or insights from this activity, consider documenting them:

## Key Developments
[Summarize important changes across all projects]
```python
write_note(
title="Activity Insights - {timeframe}",
content='''
# Activity Insights

## Project Insights
[Note patterns or connections between projects]
## Patterns Observed
- [trend] [Pattern you noticed in the activity]

## Observations
- [trend] [Cross-project patterns observed]
- [insight] [Connections between different project activities]
## Key Developments
- [insight] [Important development worth tracking]

## Relations
- summarizes [[{first_title}]]
- relates_to [[Project Portfolio Overview]]
''',
folder="summaries"
)
```

Cross-project summaries help identify broader trends and project interconnections.
"""
## Relations
- summarizes [[Recent Work]]
''',
folder="insights",
project="{project or 'default'}"
)
```
""")

return prompt_context + capture_suggestions
return prompt_guidance
3 changes: 1 addition & 2 deletions src/basic_memory/mcp/prompts/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
from basic_memory.mcp.async_client import get_client
from basic_memory.mcp.server import mcp
from basic_memory.mcp.tools.utils import call_post
from basic_memory.schemas.base import TimeFrame
from basic_memory.schemas.prompt import SearchPromptRequest


Expand All @@ -23,7 +22,7 @@
async def search_prompt(
query: str,
timeframe: Annotated[
Optional[TimeFrame],
Optional[str],
Field(description="How far back to search (e.g. '1d', '1 week')"),
] = None,
) -> str:
Expand Down
3 changes: 1 addition & 2 deletions src/basic_memory/mcp/prompts/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from textwrap import dedent
from typing import List

from basic_memory.schemas.base import TimeFrame
from basic_memory.schemas.memory import (
normalize_memory_url,
EntitySummary,
Expand All @@ -25,7 +24,7 @@ class PromptContextItem:

@dataclass
class PromptContext:
timeframe: TimeFrame
timeframe: str
topic: str
results: List[PromptContextItem]

Expand Down
14 changes: 8 additions & 6 deletions tests/mcp/test_prompts.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,9 @@ async def test_recent_activity_prompt_discovery_mode(client, test_project, test_
result = await recent_activity_prompt.fn(timeframe="1w") # pyright: ignore [reportGeneralTypeIssues]

# Check the response contains expected discovery mode content
assert "Recent Activity Across All Projects" in result # pyright: ignore [reportOperatorIssue]
assert "Cross-Project Activity Discovery" in result # pyright: ignore [reportOperatorIssue]
assert "Recent Activity Context" in result # pyright: ignore [reportOperatorIssue]
assert "all projects" in result # pyright: ignore [reportOperatorIssue]
assert "Next Steps" in result # pyright: ignore [reportOperatorIssue]
assert "write_note" in result # pyright: ignore [reportOperatorIssue]


Expand All @@ -165,9 +166,9 @@ async def test_recent_activity_prompt_project_specific(client, test_project, tes
result = await recent_activity_prompt.fn(timeframe="1w", project=test_project.name) # pyright: ignore [reportGeneralTypeIssues]

# Check the response contains expected project-specific content
assert f"Recent Activity in {test_project.name}" in result # pyright: ignore [reportOperatorIssue]
assert "Opportunity to Capture Activity Summary" in result # pyright: ignore [reportOperatorIssue]
assert f"recent activity in {test_project.name}" in result # pyright: ignore [reportOperatorIssue]
assert "Recent Activity Context" in result # pyright: ignore [reportOperatorIssue]
assert test_project.name in result # pyright: ignore [reportOperatorIssue]
assert "Next Steps" in result # pyright: ignore [reportOperatorIssue]
assert "write_note" in result # pyright: ignore [reportOperatorIssue]


Expand All @@ -178,4 +179,5 @@ async def test_recent_activity_prompt_with_custom_timeframe(client, test_project
result = await recent_activity_prompt.fn(timeframe="1d") # pyright: ignore [reportGeneralTypeIssues]

# Check the response includes the custom timeframe
assert "Recent Activity Across All Projects (1d)" in result # pyright: ignore [reportOperatorIssue]
assert "1d" in result # pyright: ignore [reportOperatorIssue]
assert "Recent Activity Context" in result # pyright: ignore [reportOperatorIssue]
Loading
Loading