AI Course/week16_memory/memory_agent.py

Course file

memory_agent.py

week16_memory/memory_agent.py

"""
Week 16: Memory Agent
======================
An agent with two types of memory:
1. Conversation history (within a session)
2. Persistent notes (survives restarts via JSON file)

Run it: python memory_agent.py
"""

import os
import json
from dotenv import load_dotenv

load_dotenv()

import anthropic

from memory_store import save_note, get_note, list_notes

client = anthropic.Anthropic()
MODEL = "claude-sonnet-4-20250514"

# ---------------------------------------------------------------------------
# Tool definitions
# ---------------------------------------------------------------------------

TOOLS = [
    {
        "name": "save_note",
        "description": "Save a note to persistent memory. Use this when the user asks you to remember something, save information, or store a note. The note will survive even if the conversation ends.",
        "input_schema": {
            "type": "object",
            "properties": {
                "key": {
                    "type": "string",
                    "description": "A short name for the note, like 'todo', 'favorites', 'user_info'. Use snake_case.",
                },
                "content": {
                    "type": "string",
                    "description": "The content to save.",
                },
            },
            "required": ["key", "content"],
        },
    },
    {
        "name": "get_note",
        "description": "Retrieve a saved note by its key. Use this when the user asks about something that was previously saved.",
        "input_schema": {
            "type": "object",
            "properties": {
                "key": {
                    "type": "string",
                    "description": "The key of the note to retrieve.",
                }
            },
            "required": ["key"],
        },
    },
    {
        "name": "list_notes",
        "description": "List all saved notes with previews. Use this when the user asks what you know or remember, or to see all saved information.",
        "input_schema": {
            "type": "object",
            "properties": {},
            "required": [],
        },
    },
    # TODO: Add delete_note and search_notes tool definitions here
]

TOOL_FUNCTIONS = {
    "save_note": lambda args: save_note(args["key"], args["content"]),
    "get_note": lambda args: get_note(args["key"]),
    "list_notes": lambda args: list_notes(),
    # TODO: Add your new tools here
}

# ---------------------------------------------------------------------------
# System prompt
# ---------------------------------------------------------------------------

SYSTEM_PROMPT = """You are a helpful assistant with persistent memory.

You have tools to save and retrieve notes. When the user tells you something worth remembering (preferences, facts about themselves, tasks, etc.), save it as a note.

When the user asks what you know about them or asks about something previously discussed, check your notes first.

Key behaviors:
- Proactively save important information the user shares
- When asked "what do you know about me?", list your notes
- Use descriptive keys for notes (e.g., "favorite_language", "current_project")
- If a note already exists with the same key, updating it replaces the old content
"""

# ---------------------------------------------------------------------------
# Agent loop with conversation memory
# ---------------------------------------------------------------------------


def run_agent():
    """Run the agent with persistent conversation history."""
    print("Memory Agent — Week 16")
    print("This agent remembers things between messages AND between restarts.")
    print("Try: 'Remember that my favorite color is blue'")
    print("Then quit, restart, and ask: 'What's my favorite color?'")
    print("Type 'quit' to exit.\n")

    # Conversation history — persists within this session
    conversation: list[dict] = []

    while True:
        user_input = input("\nYou: ").strip()
        if user_input.lower() in ("quit", "exit", "q"):
            print("Bye! Your notes are saved in agent_notes.json.")
            break
        if not user_input:
            continue

        # Add user message to conversation
        conversation.append({"role": "user", "content": user_input})

        try:
            # Agent loop — may take multiple iterations if tools are called
            messages = list(conversation)  # Copy so we can add tool results
            max_iterations = 10

            for _ in range(max_iterations):
                response = client.messages.create(
                    model=MODEL,
                    max_tokens=1024,
                    system=SYSTEM_PROMPT,
                    tools=TOOLS,
                    messages=messages,
                )

                if response.stop_reason == "tool_use":
                    tool_results = []

                    for block in response.content:
                        if block.type == "text":
                            pass  # Claude's thinking, don't print yet
                        elif block.type == "tool_use":
                            func = TOOL_FUNCTIONS.get(block.name)
                            if func:
                                result = func(block.input)
                            else:
                                result = f"Unknown tool: {block.name}"

                            print(f"  [{block.name}: {json.dumps(block.input)}]")

                            tool_results.append(
                                {
                                    "type": "tool_result",
                                    "tool_use_id": block.id,
                                    "content": result,
                                }
                            )

                    messages.append({"role": "assistant", "content": response.content})
                    messages.append({"role": "user", "content": tool_results})

                else:
                    # Final text response
                    final_text = ""
                    for block in response.content:
                        if hasattr(block, "text"):
                            final_text += block.text

                    print(f"\nAssistant: {final_text}")

                    # Save assistant response to conversation history
                    conversation.append(
                        {"role": "assistant", "content": final_text}
                    )
                    break

        except anthropic.APIError as e:
            print(f"\nAPI Error: {e}")
            # Remove the failed user message from history
            conversation.pop()
        except Exception as e:
            print(f"\nError: {e}")
            conversation.pop()


if __name__ == "__main__":
    run_agent()