Initial commit of current state
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
"""Tools that let the agent improve itself between conversations.
|
||||
|
||||
Three kinds of thing get learned in a voice conversation, and they belong in
|
||||
different places:
|
||||
|
||||
- **How a word was misheard** is about this microphone and this recogniser. It
|
||||
goes to `corrections.txt` in the workspace — local, because it would be
|
||||
meaningless on another machine.
|
||||
- **A lasting preference** — "stop explaining so much" — goes to the personal
|
||||
brain's `preferences.md`, because it is true of Adolfo regardless of which
|
||||
assistant is listening.
|
||||
- **A technique or lesson** goes to the brain's `notes.md`, same reasoning.
|
||||
|
||||
This module is only the mechanism. *When* to record something, and which file
|
||||
it belongs in, is personal setup rather than a property of the agent, so that
|
||||
lives in a skill at `~/Workspace/.claude/skills/memory/`. Editing the discipline
|
||||
means editing that file, not this one.
|
||||
|
||||
Everything written lands in a git-tracked file or the brain, so it can be
|
||||
reviewed and undone.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from claude_agent_sdk import create_sdk_mcp_server, tool
|
||||
from loguru import logger
|
||||
|
||||
SERVER_NAME = "memory"
|
||||
|
||||
# The tool names Claude sees, and must be allowed to call.
|
||||
TOOL_NAMES = [
|
||||
f"mcp__{SERVER_NAME}__remember_correction",
|
||||
f"mcp__{SERVER_NAME}__remember_preference",
|
||||
f"mcp__{SERVER_NAME}__remember_note",
|
||||
]
|
||||
|
||||
def build_server(*, workspace: Path, brain=None):
|
||||
"""Create the in-process MCP server exposing the memory tools."""
|
||||
corrections_file = workspace / "corrections.txt"
|
||||
|
||||
@tool(
|
||||
"remember_correction",
|
||||
"Record that speech recognition misheard a word, so it is fixed from now on.",
|
||||
{"heard": str, "intended": str},
|
||||
)
|
||||
async def remember_correction(args):
|
||||
heard = (args.get("heard") or "").strip()
|
||||
intended = (args.get("intended") or "").strip()
|
||||
if not heard or not intended or heard.lower() == intended.lower():
|
||||
return {"content": [{"type": "text", "text": "Nothing to record."}]}
|
||||
|
||||
# A one-word rule risks firing on ordinary speech; a phrase is safer.
|
||||
rule = f"{heard} => {intended}"
|
||||
existing = corrections_file.read_text() if corrections_file.exists() else ""
|
||||
if rule.lower() in existing.lower():
|
||||
return {"content": [{"type": "text", "text": "Already known."}]}
|
||||
|
||||
with corrections_file.open("a") as f:
|
||||
if not existing.endswith("\n"):
|
||||
f.write("\n")
|
||||
f.write(f"{rule}\n")
|
||||
logger.info(f"Learned correction: {rule}")
|
||||
return {"content": [{"type": "text", "text": f"Recorded: {rule}"}]}
|
||||
|
||||
@tool(
|
||||
"remember_preference",
|
||||
"Record a lasting preference about how Adolfo wants to be worked with.",
|
||||
{"preference": str},
|
||||
)
|
||||
async def remember_preference(args):
|
||||
return _to_brain(brain, "preference", args.get("preference"))
|
||||
|
||||
@tool(
|
||||
"remember_note",
|
||||
"Record a technique, lesson or fact worth having in future sessions.",
|
||||
{"note": str},
|
||||
)
|
||||
async def remember_note(args):
|
||||
return _to_brain(brain, "note", args.get("note"))
|
||||
|
||||
return create_sdk_mcp_server(
|
||||
name=SERVER_NAME,
|
||||
tools=[remember_correction, remember_preference, remember_note],
|
||||
)
|
||||
|
||||
|
||||
def _to_brain(brain, kind: str, text: str | None) -> dict:
|
||||
text = (text or "").strip()
|
||||
if not text:
|
||||
return {"content": [{"type": "text", "text": "Nothing to record."}]}
|
||||
if brain is None or not brain.reachable:
|
||||
logger.warning(f"Brain unreachable; dropped {kind}: {text[:60]}")
|
||||
return {"content": [{"type": "text", "text": "Memory unavailable right now."}]}
|
||||
|
||||
ok, detail = brain.append(kind, f"- {text}")
|
||||
message = f"Recorded to {detail}." if ok else f"Could not record: {detail}"
|
||||
return {"content": [{"type": "text", "text": message}]}
|
||||
Reference in New Issue
Block a user