forked from cardosofelipe/fast-next-template
- Add ABANDONED value to core Outcome enum in types.py - Replace duplicate OutcomeType class in mcp/tools.py with alias to Outcome - Simplify mcp/service.py to use outcome directly (no more silent mapping) - Add migration 0006 to extend PostgreSQL episode_outcome enum - Add missing constraints to migration 0005 (ix_facts_unique_triple_global) This fixes the semantic issue where ABANDONED outcomes were silently converted to FAILURE, losing information about task abandonment. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
486 lines
14 KiB
Python
486 lines
14 KiB
Python
# app/services/memory/mcp/tools.py
|
|
"""
|
|
MCP Tool Definitions for Agent Memory System.
|
|
|
|
Defines the schema and metadata for memory-related MCP tools.
|
|
These tools are invoked by AI agents to interact with the memory system.
|
|
"""
|
|
|
|
from dataclasses import dataclass, field
|
|
from enum import Enum
|
|
from typing import Any
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
# OutcomeType alias - uses core Outcome enum from types module for consistency
|
|
from app.services.memory.types import Outcome as OutcomeType
|
|
|
|
|
|
class MemoryType(str, Enum):
|
|
"""Types of memory for storage operations."""
|
|
|
|
WORKING = "working"
|
|
EPISODIC = "episodic"
|
|
SEMANTIC = "semantic"
|
|
PROCEDURAL = "procedural"
|
|
|
|
|
|
class AnalysisType(str, Enum):
|
|
"""Types of pattern analysis for the reflect tool."""
|
|
|
|
RECENT_PATTERNS = "recent_patterns"
|
|
SUCCESS_FACTORS = "success_factors"
|
|
FAILURE_PATTERNS = "failure_patterns"
|
|
COMMON_PROCEDURES = "common_procedures"
|
|
LEARNING_PROGRESS = "learning_progress"
|
|
|
|
|
|
# ============================================================================
|
|
# Tool Argument Schemas (Pydantic models for validation)
|
|
# ============================================================================
|
|
|
|
|
|
class RememberArgs(BaseModel):
|
|
"""Arguments for the 'remember' tool."""
|
|
|
|
memory_type: MemoryType = Field(
|
|
...,
|
|
description="Type of memory to store in: working, episodic, semantic, or procedural",
|
|
)
|
|
content: str = Field(
|
|
...,
|
|
description="The content to remember. Can be text, facts, or procedure steps.",
|
|
min_length=1,
|
|
max_length=10000,
|
|
)
|
|
key: str | None = Field(
|
|
None,
|
|
description="Optional key for working memory entries. Required for working memory type.",
|
|
max_length=256,
|
|
)
|
|
importance: float = Field(
|
|
0.5,
|
|
description="Importance score from 0.0 (low) to 1.0 (critical)",
|
|
ge=0.0,
|
|
le=1.0,
|
|
)
|
|
ttl_seconds: int | None = Field(
|
|
None,
|
|
description="Time-to-live in seconds for working memory. None for permanent storage.",
|
|
ge=1,
|
|
le=86400 * 30, # Max 30 days
|
|
)
|
|
metadata: dict[str, Any] = Field(
|
|
default_factory=dict,
|
|
description="Additional metadata to store with the memory",
|
|
)
|
|
# For semantic memory (facts)
|
|
subject: str | None = Field(
|
|
None,
|
|
description="Subject of the fact (for semantic memory)",
|
|
max_length=256,
|
|
)
|
|
predicate: str | None = Field(
|
|
None,
|
|
description="Predicate/relationship (for semantic memory)",
|
|
max_length=256,
|
|
)
|
|
object_value: str | None = Field(
|
|
None,
|
|
description="Object of the fact (for semantic memory)",
|
|
max_length=1000,
|
|
)
|
|
# For procedural memory
|
|
trigger: str | None = Field(
|
|
None,
|
|
description="Trigger condition for the procedure (for procedural memory)",
|
|
max_length=500,
|
|
)
|
|
steps: list[dict[str, Any]] | None = Field(
|
|
None,
|
|
description="Procedure steps as a list of action dictionaries",
|
|
)
|
|
|
|
|
|
class RecallArgs(BaseModel):
|
|
"""Arguments for the 'recall' tool."""
|
|
|
|
query: str = Field(
|
|
...,
|
|
description="Search query to find relevant memories",
|
|
min_length=1,
|
|
max_length=1000,
|
|
)
|
|
memory_types: list[MemoryType] = Field(
|
|
default_factory=lambda: [MemoryType.EPISODIC, MemoryType.SEMANTIC],
|
|
description="Types of memory to search in",
|
|
)
|
|
limit: int = Field(
|
|
10,
|
|
description="Maximum number of results to return",
|
|
ge=1,
|
|
le=100,
|
|
)
|
|
min_relevance: float = Field(
|
|
0.0,
|
|
description="Minimum relevance score (0.0-1.0) for results",
|
|
ge=0.0,
|
|
le=1.0,
|
|
)
|
|
filters: dict[str, Any] = Field(
|
|
default_factory=dict,
|
|
description="Additional filters (e.g., outcome, task_type, date range)",
|
|
)
|
|
include_context: bool = Field(
|
|
True,
|
|
description="Whether to include surrounding context in results",
|
|
)
|
|
|
|
|
|
class ForgetArgs(BaseModel):
|
|
"""Arguments for the 'forget' tool."""
|
|
|
|
memory_type: MemoryType = Field(
|
|
...,
|
|
description="Type of memory to remove from",
|
|
)
|
|
key: str | None = Field(
|
|
None,
|
|
description="Key to remove (for working memory)",
|
|
max_length=256,
|
|
)
|
|
memory_id: str | None = Field(
|
|
None,
|
|
description="Specific memory ID to remove (for episodic/semantic/procedural)",
|
|
)
|
|
pattern: str | None = Field(
|
|
None,
|
|
description="Pattern to match for bulk removal (use with caution)",
|
|
max_length=500,
|
|
)
|
|
confirm_bulk: bool = Field(
|
|
False,
|
|
description="Must be True to confirm bulk deletion when using pattern",
|
|
)
|
|
|
|
|
|
class ReflectArgs(BaseModel):
|
|
"""Arguments for the 'reflect' tool."""
|
|
|
|
analysis_type: AnalysisType = Field(
|
|
...,
|
|
description="Type of pattern analysis to perform",
|
|
)
|
|
scope: str | None = Field(
|
|
None,
|
|
description="Optional scope to limit analysis (e.g., task_type, time range)",
|
|
max_length=500,
|
|
)
|
|
depth: int = Field(
|
|
3,
|
|
description="Depth of analysis (1=surface, 5=deep)",
|
|
ge=1,
|
|
le=5,
|
|
)
|
|
include_examples: bool = Field(
|
|
True,
|
|
description="Whether to include example memories in the analysis",
|
|
)
|
|
max_items: int = Field(
|
|
10,
|
|
description="Maximum number of patterns/examples to analyze",
|
|
ge=1,
|
|
le=50,
|
|
)
|
|
|
|
|
|
class GetMemoryStatsArgs(BaseModel):
|
|
"""Arguments for the 'get_memory_stats' tool."""
|
|
|
|
include_breakdown: bool = Field(
|
|
True,
|
|
description="Include breakdown by memory type",
|
|
)
|
|
include_recent_activity: bool = Field(
|
|
True,
|
|
description="Include recent memory activity summary",
|
|
)
|
|
time_range_days: int = Field(
|
|
7,
|
|
description="Time range for activity analysis in days",
|
|
ge=1,
|
|
le=90,
|
|
)
|
|
|
|
|
|
class SearchProceduresArgs(BaseModel):
|
|
"""Arguments for the 'search_procedures' tool."""
|
|
|
|
trigger: str = Field(
|
|
...,
|
|
description="Trigger or situation to find procedures for",
|
|
min_length=1,
|
|
max_length=500,
|
|
)
|
|
task_type: str | None = Field(
|
|
None,
|
|
description="Optional task type to filter procedures",
|
|
max_length=100,
|
|
)
|
|
min_success_rate: float = Field(
|
|
0.5,
|
|
description="Minimum success rate (0.0-1.0) for returned procedures",
|
|
ge=0.0,
|
|
le=1.0,
|
|
)
|
|
limit: int = Field(
|
|
5,
|
|
description="Maximum number of procedures to return",
|
|
ge=1,
|
|
le=20,
|
|
)
|
|
include_steps: bool = Field(
|
|
True,
|
|
description="Whether to include detailed steps in the response",
|
|
)
|
|
|
|
|
|
class RecordOutcomeArgs(BaseModel):
|
|
"""Arguments for the 'record_outcome' tool."""
|
|
|
|
task_type: str = Field(
|
|
...,
|
|
description="Type of task that was executed",
|
|
min_length=1,
|
|
max_length=100,
|
|
)
|
|
outcome: OutcomeType = Field(
|
|
...,
|
|
description="Outcome of the task execution",
|
|
)
|
|
procedure_id: str | None = Field(
|
|
None,
|
|
description="ID of the procedure that was followed (if any)",
|
|
)
|
|
context: dict[str, Any] = Field(
|
|
default_factory=dict,
|
|
description="Context in which the task was executed",
|
|
)
|
|
lessons_learned: str | None = Field(
|
|
None,
|
|
description="What was learned from this execution",
|
|
max_length=2000,
|
|
)
|
|
duration_seconds: float | None = Field(
|
|
None,
|
|
description="How long the task took to execute",
|
|
ge=0.0,
|
|
)
|
|
error_details: str | None = Field(
|
|
None,
|
|
description="Details about any errors encountered (for failures)",
|
|
max_length=2000,
|
|
)
|
|
|
|
|
|
# ============================================================================
|
|
# Tool Definition Structure
|
|
# ============================================================================
|
|
|
|
|
|
@dataclass
|
|
class MemoryToolDefinition:
|
|
"""Definition of an MCP tool for the memory system."""
|
|
|
|
name: str
|
|
description: str
|
|
args_schema: type[BaseModel]
|
|
input_schema: dict[str, Any] = field(default_factory=dict)
|
|
|
|
def __post_init__(self) -> None:
|
|
"""Generate input schema from Pydantic model."""
|
|
if not self.input_schema:
|
|
self.input_schema = self.args_schema.model_json_schema()
|
|
|
|
def to_mcp_format(self) -> dict[str, Any]:
|
|
"""Convert to MCP tool format."""
|
|
return {
|
|
"name": self.name,
|
|
"description": self.description,
|
|
"inputSchema": self.input_schema,
|
|
}
|
|
|
|
def validate_args(self, args: dict[str, Any]) -> BaseModel:
|
|
"""Validate and parse arguments."""
|
|
return self.args_schema.model_validate(args)
|
|
|
|
|
|
# ============================================================================
|
|
# Tool Definitions
|
|
# ============================================================================
|
|
|
|
|
|
REMEMBER_TOOL = MemoryToolDefinition(
|
|
name="remember",
|
|
description="""Store information in the agent's memory system.
|
|
|
|
Use this tool to:
|
|
- Store temporary data in working memory (key-value with optional TTL)
|
|
- Record important events in episodic memory (automatically done on session end)
|
|
- Store facts/knowledge in semantic memory (subject-predicate-object triples)
|
|
- Save procedures in procedural memory (trigger conditions and steps)
|
|
|
|
Examples:
|
|
- Working memory: {"memory_type": "working", "key": "current_task", "content": "Implementing auth", "ttl_seconds": 3600}
|
|
- Semantic fact: {"memory_type": "semantic", "subject": "User", "predicate": "prefers", "object_value": "dark mode", "content": "User preference noted"}
|
|
- Procedure: {"memory_type": "procedural", "trigger": "When creating a new file", "steps": [{"action": "check_exists"}, {"action": "create"}], "content": "File creation procedure"}
|
|
""",
|
|
args_schema=RememberArgs,
|
|
)
|
|
|
|
|
|
RECALL_TOOL = MemoryToolDefinition(
|
|
name="recall",
|
|
description="""Retrieve information from the agent's memory system.
|
|
|
|
Use this tool to:
|
|
- Search for relevant past experiences (episodic)
|
|
- Look up known facts and knowledge (semantic)
|
|
- Find applicable procedures for current task (procedural)
|
|
- Get current session state (working)
|
|
|
|
The query supports semantic search - describe what you're looking for in natural language.
|
|
|
|
Examples:
|
|
- {"query": "How did I handle authentication errors before?", "memory_types": ["episodic"]}
|
|
- {"query": "What are the user's preferences?", "memory_types": ["semantic"], "limit": 5}
|
|
- {"query": "database connection", "memory_types": ["episodic", "semantic", "procedural"], "filters": {"outcome": "success"}}
|
|
""",
|
|
args_schema=RecallArgs,
|
|
)
|
|
|
|
|
|
FORGET_TOOL = MemoryToolDefinition(
|
|
name="forget",
|
|
description="""Remove information from the agent's memory system.
|
|
|
|
Use this tool to:
|
|
- Clear temporary working memory entries
|
|
- Remove specific memories by ID
|
|
- Bulk remove memories matching a pattern (requires confirmation)
|
|
|
|
WARNING: Deletion is permanent. Use with caution.
|
|
|
|
Examples:
|
|
- Working memory: {"memory_type": "working", "key": "temp_calculation"}
|
|
- Specific memory: {"memory_type": "episodic", "memory_id": "ep-123"}
|
|
- Bulk (requires confirm): {"memory_type": "working", "pattern": "cache_*", "confirm_bulk": true}
|
|
""",
|
|
args_schema=ForgetArgs,
|
|
)
|
|
|
|
|
|
REFLECT_TOOL = MemoryToolDefinition(
|
|
name="reflect",
|
|
description="""Analyze patterns in the agent's memory to gain insights.
|
|
|
|
Use this tool to:
|
|
- Identify patterns in recent work
|
|
- Understand what leads to success/failure
|
|
- Learn from past experiences
|
|
- Track learning progress over time
|
|
|
|
Analysis types:
|
|
- recent_patterns: What patterns appear in recent work
|
|
- success_factors: What conditions lead to success
|
|
- failure_patterns: What causes failures and how to avoid them
|
|
- common_procedures: Most frequently used procedures
|
|
- learning_progress: How knowledge has grown over time
|
|
|
|
Examples:
|
|
- {"analysis_type": "success_factors", "scope": "code_review", "depth": 3}
|
|
- {"analysis_type": "failure_patterns", "include_examples": true, "max_items": 5}
|
|
""",
|
|
args_schema=ReflectArgs,
|
|
)
|
|
|
|
|
|
GET_MEMORY_STATS_TOOL = MemoryToolDefinition(
|
|
name="get_memory_stats",
|
|
description="""Get statistics about the agent's memory usage.
|
|
|
|
Returns information about:
|
|
- Total memories stored by type
|
|
- Storage utilization
|
|
- Recent activity summary
|
|
- Memory health indicators
|
|
|
|
Use this to understand memory capacity and usage patterns.
|
|
|
|
Examples:
|
|
- {"include_breakdown": true, "include_recent_activity": true}
|
|
- {"time_range_days": 30, "include_breakdown": true}
|
|
""",
|
|
args_schema=GetMemoryStatsArgs,
|
|
)
|
|
|
|
|
|
SEARCH_PROCEDURES_TOOL = MemoryToolDefinition(
|
|
name="search_procedures",
|
|
description="""Find relevant procedures for a given situation.
|
|
|
|
Use this tool when you need to:
|
|
- Find the best way to handle a situation
|
|
- Look up proven approaches to problems
|
|
- Get step-by-step guidance for tasks
|
|
|
|
Returns procedures ranked by relevance and success rate.
|
|
|
|
Examples:
|
|
- {"trigger": "Deploying to production", "min_success_rate": 0.8}
|
|
- {"trigger": "Handling merge conflicts", "task_type": "git_operations", "limit": 3}
|
|
""",
|
|
args_schema=SearchProceduresArgs,
|
|
)
|
|
|
|
|
|
RECORD_OUTCOME_TOOL = MemoryToolDefinition(
|
|
name="record_outcome",
|
|
description="""Record the outcome of a task execution.
|
|
|
|
Use this tool after completing a task to:
|
|
- Update procedure success/failure rates
|
|
- Store lessons learned for future reference
|
|
- Improve procedure recommendations
|
|
|
|
This helps the memory system learn from experience.
|
|
|
|
Examples:
|
|
- {"task_type": "code_review", "outcome": "success", "lessons_learned": "Breaking changes caught early"}
|
|
- {"task_type": "deployment", "outcome": "failure", "error_details": "Database migration timeout", "lessons_learned": "Need to test migrations locally first"}
|
|
""",
|
|
args_schema=RecordOutcomeArgs,
|
|
)
|
|
|
|
|
|
# All tool definitions in a dictionary for easy lookup
|
|
MEMORY_TOOL_DEFINITIONS: dict[str, MemoryToolDefinition] = {
|
|
"remember": REMEMBER_TOOL,
|
|
"recall": RECALL_TOOL,
|
|
"forget": FORGET_TOOL,
|
|
"reflect": REFLECT_TOOL,
|
|
"get_memory_stats": GET_MEMORY_STATS_TOOL,
|
|
"search_procedures": SEARCH_PROCEDURES_TOOL,
|
|
"record_outcome": RECORD_OUTCOME_TOOL,
|
|
}
|
|
|
|
|
|
def get_all_tool_schemas() -> list[dict[str, Any]]:
|
|
"""Get MCP-formatted schemas for all memory tools."""
|
|
return [tool.to_mcp_format() for tool in MEMORY_TOOL_DEFINITIONS.values()]
|
|
|
|
|
|
def get_tool_definition(name: str) -> MemoryToolDefinition | None:
|
|
"""Get a specific tool definition by name."""
|
|
return MEMORY_TOOL_DEFINITIONS.get(name)
|