""" Agent private notes — AGENT_NOTES.md. A persistent notepad only the orchestrator can write to. The file itself is never exposed in the Files panel or loaded into user-facing context tiers. Up to 3 rolling backups are kept automatically before each write so past versions can be reviewed. Use for: observations about the user's patterns, working hypotheses, long-running goals, things to remember across sessions that shouldn't be part of the distilled memory visible to the user. """ import asyncio from datetime import datetime, timezone from pathlib import Path from google.genai import types from persona import persona_path _FILENAME = "AGENT_NOTES.md" _N_BACKUPS = 3 def _notes_path() -> Path: return persona_path() / _FILENAME def _now_label() -> str: return datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC") def _rotate(path: Path) -> None: """Rotate up to _N_BACKUPS rolling backups before a write.""" if not path.exists(): return for i in range(_N_BACKUPS, 1, -1): older = path.parent / f"{path.stem}.bak{i}.md" newer = path.parent / f"{path.stem}.bak{i - 1}.md" if newer.exists(): older.write_text(newer.read_text()) bak1 = path.parent / f"{path.stem}.bak1.md" bak1.write_text(path.read_text()) # ── Sync implementations ──────────────────────────────────────────────────── def _agent_notes_read() -> str: p = _notes_path() if not p.exists() or not p.read_text().strip(): return "Agent notes are empty." return p.read_text() def _agent_notes_write(content: str) -> str: p = _notes_path() _rotate(p) p.write_text(content.rstrip() + "\n") return "Agent notes updated." def _agent_notes_append(content: str, heading: str | None = None) -> str: p = _notes_path() _rotate(p) existing = p.read_text() if p.exists() else "" label = heading or _now_label() section = f"\n## {label}\n\n{content.strip()}\n" p.write_text(existing.rstrip() + "\n" + section) return f"Appended to agent notes: {label}" def _agent_notes_clear() -> str: p = _notes_path() _rotate(p) p.write_text("") return "Agent notes cleared." # ── Async wrappers ─────────────────────────────────────────────────────────── async def agent_notes_read() -> str: return await asyncio.to_thread(_agent_notes_read) async def agent_notes_write(content: str) -> str: return await asyncio.to_thread(_agent_notes_write, content) async def agent_notes_append(content: str, heading: str | None = None) -> str: return await asyncio.to_thread(_agent_notes_append, content, heading) async def agent_notes_clear() -> str: return await asyncio.to_thread(_agent_notes_clear) # ── Gemini FunctionDeclarations ────────────────────────────────────────────── DECLARATIONS = [ types.FunctionDeclaration( name="agent_notes_read", description=( "Read your private agent notes — a persistent notepad only you can write to. " "Use this to recall observations, working hypotheses, long-running goals, or " "anything you want to remember across sessions without surfacing it to the user. " "This file is never shown in the user's Files panel." ), parameters=types.Schema(type=types.Type.OBJECT, properties={}), ), types.FunctionDeclaration( name="agent_notes_write", description=( "Replace your private agent notes with new content. " "A backup is saved automatically before writing. " "Use agent_notes_append to add without replacing." ), parameters=types.Schema( type=types.Type.OBJECT, properties={ "content": types.Schema( type=types.Type.STRING, description="The new notes content (markdown supported).", ), }, required=["content"], ), ), types.FunctionDeclaration( name="agent_notes_append", description=( "Add a new section to your private agent notes without replacing existing content. " "A backup is saved automatically before writing. " "Each section gets a UTC timestamp heading unless you supply one." ), parameters=types.Schema( type=types.Type.OBJECT, properties={ "content": types.Schema( type=types.Type.STRING, description="The content to append (markdown supported).", ), "heading": types.Schema( type=types.Type.STRING, description="Optional section heading. Defaults to current UTC timestamp.", ), }, required=["content"], ), ), types.FunctionDeclaration( name="agent_notes_clear", description=( "Erase all private agent notes. A backup is saved automatically before clearing." ), parameters=types.Schema(type=types.Type.OBJECT, properties={}), ), ]