feat(memory): scope coding + tools record_lesson/get_context_pack + branchement git_sync

This commit is contained in:
2026-07-30 08:36:21 +00:00
parent 211b8fb989
commit 906acfb1f9
+60 -1
View File
@@ -8,7 +8,8 @@ from mcp.server.sse import SseServerTransport
from mcp.types import Tool, TextContent from mcp.types import Tool, TextContent
from app.auth import get_current_agent from app.auth import get_current_agent
from app.scopes import has_scope_access, ALL_SCOPES from app.scopes import has_scope_access, ALL_SCOPES
from app.models import get_rule, get_all_rules, get_all_ports, get_port, set_rule, get_agent_by_key, update_key_last_used from app.models import (get_rule, get_all_rules, get_all_ports, get_port, set_rule, get_agent_by_key, update_key_last_used, add_memory_entry, list_memory_entries)
from app.git_sync import push_scope_markdown
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -89,6 +90,38 @@ async def handle_list_tools() -> list[Tool]:
}, },
"required": ["scope", "agent", "data"] "required": ["scope", "agent", "data"]
} }
),
Tool(
name="record_lesson",
description=("Record a typed, durable memory entry (decision/constraint/best-practice/"
"common-error/do-not-use) in the coding scope. Call after a meaningful "
"decision, a repeated correction, or a rejected approach worth remembering."),
inputSchema={
"type": "object",
"properties": {
"scope": {"type": "string", "description": "Scope, actuellement: coding"},
"project": {"type": "string", "description": "Nom du repo/projet, omettre si transverse"},
"type": {"type": "string", "enum": ["decision", "constraint", "best-practice", "common-error", "do-not-use"]},
"title": {"type": "string", "description": "Resume court, 1 ligne"},
"body": {"type": "string", "description": "1-3 phrases"},
"tags": {"type": "array", "items": {"type": "string"}}
},
"required": ["scope", "type", "title", "body"]
}
),
Tool(
name="get_context_pack",
description=("Load only the memory entries relevant to the current task (selective "
"loading, not the whole scope) — call before substantial coding work."),
inputSchema={
"type": "object",
"properties": {
"scope": {"type": "string", "description": "Scope, actuellement: coding"},
"project": {"type": "string", "description": "Filtrer par projet ; omettre pour les entrees transverses"},
"type": {"type": "string", "enum": ["decision", "constraint", "best-practice", "common-error", "do-not-use"]}
},
"required": ["scope"]
}
) )
] ]
@@ -162,6 +195,32 @@ async def handle_call_tool(name: str, arguments: dict) -> list[TextContent]:
await set_rule(scope, existing) await set_rule(scope, existing)
return [TextContent(type="text", text=json.dumps({"status": "ok", "scope": scope, "updated_keys": list(data.keys())}))] return [TextContent(type="text", text=json.dumps({"status": "ok", "scope": scope, "updated_keys": list(data.keys())}))]
elif name == "record_lesson":
scope = arguments.get("scope")
if not has_scope_access(caller_agent, scope):
return [TextContent(type="text", text=json.dumps({"error": f"Agent {caller_agent} forbidden on scope {scope}"}))]
type_ = arguments.get("type")
title = arguments.get("title")
body = arguments.get("body")
if not (type_ and title and body):
return [TextContent(type="text", text=json.dumps({"error": "type, title et body sont requis"}))]
entry = await add_memory_entry(
scope=scope, type_=type_, title=title, body=body, created_by=caller_agent,
project=arguments.get("project"), tags=arguments.get("tags")
)
git_result = await push_scope_markdown(scope)
entry["_git"] = git_result
return [TextContent(type="text", text=json.dumps(entry))]
elif name == "get_context_pack":
scope = arguments.get("scope")
if not has_scope_access(caller_agent, scope):
return [TextContent(type="text", text=json.dumps({"error": f"Agent {caller_agent} forbidden on scope {scope}"}))]
entries = await list_memory_entries(
scope=scope, project=arguments.get("project"), type_=arguments.get("type"), status="active"
)
return [TextContent(type="text", text=json.dumps(entries))]
return [TextContent(type="text", text=json.dumps({"error": "Unknown tool"}))] return [TextContent(type="text", text=json.dumps({"error": "Unknown tool"}))]
# FastMCP / SSE Integration # FastMCP / SSE Integration