From e5b6d588894179bf44b9b4c3b2a7e3cdd66c33b3 Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Sun, 29 Mar 2026 21:14:22 -0400 Subject: [PATCH] feat: reminders_add and reminders_list tools - New cortex/tools/reminders.py with reminders_add, reminders_list, reminders_clear - reminders_clear moved here from cron.py (cron still imports from same file) - __init__.py: wired up new callables and Gemini declarations - Inara can now add/read reminders in Agent mode via the orchestrator Co-Authored-By: Claude Sonnet 4.6 --- cortex/tools/__init__.py | 42 ++++++++++++++++++++++++ cortex/tools/reminders.py | 69 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 cortex/tools/reminders.py diff --git a/cortex/tools/__init__.py b/cortex/tools/__init__.py index 6e77574..b400811 100644 --- a/cortex/tools/__init__.py +++ b/cortex/tools/__init__.py @@ -28,6 +28,10 @@ from tools.cron import ( cron_add as _cron_add, cron_remove as _cron_remove, cron_toggle as _cron_toggle, +) +from tools.reminders import ( + reminders_add as _reminders_add, + reminders_list as _reminders_list, reminders_clear as _reminders_clear, ) from tools.scratch import ( @@ -196,6 +200,8 @@ _CALLABLES: dict[str, callable] = { "cron_add": _cron_add, "cron_remove": _cron_remove, "cron_toggle": _cron_toggle, + "reminders_add": _reminders_add, + "reminders_list": _reminders_list, "reminders_clear": _reminders_clear, "scratch_read": _scratch_read, "scratch_write": _scratch_write, @@ -409,6 +415,40 @@ _cron_toggle_declaration = types.FunctionDeclaration( ), ) +_reminders_add_declaration = types.FunctionDeclaration( + name="reminders_add", + description=( + "Add a new reminder to REMINDERS.md. Reminders are automatically surfaced " + "in your context at the start of each session (Tier 2+). " + "Use this when the user asks you to remember something, follow up on something, " + "or surface a note at the next session." + ), + parameters=types.Schema( + type=types.Type.OBJECT, + properties={ + "text": types.Schema( + type=types.Type.STRING, + description="The reminder text to add", + ), + "label": types.Schema( + type=types.Type.STRING, + description="Optional heading for this reminder (e.g. 'Follow up on NC Talk'). Defaults to current timestamp.", + ), + }, + required=["text"], + ), +) + +_reminders_list_declaration = types.FunctionDeclaration( + name="reminders_list", + description=( + "Read all current pending reminders from REMINDERS.md. " + "Use this to check what reminders are queued before adding duplicates, " + "or to show the user what's pending." + ), + parameters=types.Schema(type=types.Type.OBJECT, properties={}), +) + _reminders_clear_declaration = types.FunctionDeclaration( name="reminders_clear", description=( @@ -494,6 +534,8 @@ TOOL_DECLARATIONS = [ _cron_add_declaration, _cron_remove_declaration, _cron_toggle_declaration, + _reminders_add_declaration, + _reminders_list_declaration, _reminders_clear_declaration, _scratch_read_declaration, _scratch_write_declaration, diff --git a/cortex/tools/reminders.py b/cortex/tools/reminders.py new file mode 100644 index 0000000..9914b5f --- /dev/null +++ b/cortex/tools/reminders.py @@ -0,0 +1,69 @@ +""" +Reminders tools. + +Reminders are stored in persona/REMINDERS.md and automatically surfaced +in the system prompt at Tier 2+. Use these tools to add, list, and clear +pending reminders. + +Operations: + reminders_add — append a new reminder entry + reminders_list — return all current reminders (or a message if empty) + reminders_clear — erase all reminders (moved here from cron.py for consistency; + cron.py still calls the same underlying file) +""" + +import asyncio +from datetime import datetime, timezone +from pathlib import Path + +from persona import persona_path + + +def _reminders_path() -> Path: + return persona_path() / "REMINDERS.md" + + +def _now_label() -> str: + return datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC") + + +# --------------------------------------------------------------------------- +# Sync implementations +# --------------------------------------------------------------------------- + +def _reminders_list() -> str: + p = _reminders_path() + if not p.exists() or not p.read_text().strip(): + return "No pending reminders." + return p.read_text() + + +def _reminders_add(text: str, label: str | None = None) -> str: + p = _reminders_path() + existing = p.read_text() if p.exists() else "" + heading = label or _now_label() + section = f"\n## {heading}\n\n{text.strip()}\n" + p.write_text(existing.rstrip() + "\n" + section) + return f"Reminder added: {heading}" + + +def _reminders_clear() -> str: + p = _reminders_path() + p.write_text("") + return "All reminders cleared." + + +# --------------------------------------------------------------------------- +# Async wrappers +# --------------------------------------------------------------------------- + +async def reminders_list() -> str: + return await asyncio.to_thread(_reminders_list) + + +async def reminders_add(text: str, label: str | None = None) -> str: + return await asyncio.to_thread(_reminders_add, text, label) + + +async def reminders_clear() -> str: + return await asyncio.to_thread(_reminders_clear)