Files
Cortex-Inara/cortex/tools/scratch.py
Scott Idem eab92d876d refactor: split tool declarations into domain files + role config UI
tools/__init__.py shrinks from 1,137 → 250 lines. Each domain file now
owns both its callables and its FunctionDeclarations (DECLARATIONS list),
so adding a new tool only touches one file.

New TOOL_CATEGORIES dict exported from __init__ — used by the UI for
grouped tool checkboxes.

Role config UI (Settings → Model Registry → Role Assignments):
- ⚙ button per role expands an inline configure panel
- Textarea for system_append (injected into system prompt for this role)
- Grouped checkboxes for tool allow-list (all checked = no restriction)
- POST /api/models/role-config saves both fields; updates ROLE_CONFIG_DATA
  in-page so re-open reflects current state without a page reload

Backend:
- model_registry.set_role_config() writes system_append + tools to registry
- TOOL_CATEGORIES exported from tools/__init__ for UI rendering
- TOOLS.md header updated: 30 → 39 tools (ae_journal_* and cortex_* additions)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 20:40:50 -04:00

129 lines
4.3 KiB
Python

"""
Scratchpad tools for Inara.
A lightweight, persistent notepad stored at inara/SCRATCH.md.
Nothing here is ever distilled or archived — it is intentionally transient.
Good for: working notes mid-task, half-formed ideas, things too long for
a chat response but not worth saving to memory or a journal entry.
Operations:
scratch_read — return the full contents (or a message if empty)
scratch_write — replace the entire scratchpad
scratch_append — add a new timestamped section at the bottom
scratch_clear — erase everything
"""
import asyncio
from datetime import datetime, timezone
from pathlib import Path
from google.genai import types
from persona import persona_path
def _scratch_path() -> Path:
return persona_path() / "SCRATCH.md"
def _now_label() -> str:
return datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
# ---------------------------------------------------------------------------
# Sync implementations
# ---------------------------------------------------------------------------
def _scratch_read() -> str:
p = _scratch_path()
if not p.exists() or not p.read_text().strip():
return "Scratchpad is empty."
return p.read_text()
def _scratch_write(content: str) -> str:
_scratch_path().write_text(content.rstrip() + "\n")
return "Scratchpad updated."
def _scratch_append(content: str, heading: str | None = None) -> str:
p = _scratch_path()
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 section: {label}"
def _scratch_clear() -> str:
p = _scratch_path()
p.write_text("")
return "Scratchpad cleared."
# ---------------------------------------------------------------------------
# Async wrappers
# ---------------------------------------------------------------------------
async def scratch_read() -> str:
return await asyncio.to_thread(_scratch_read)
async def scratch_write(content: str) -> str:
return await asyncio.to_thread(_scratch_write, content)
async def scratch_append(content: str, heading: str | None = None) -> str:
return await asyncio.to_thread(_scratch_append, content, heading)
async def scratch_clear() -> str:
return await asyncio.to_thread(_scratch_clear)
DECLARATIONS = [
types.FunctionDeclaration(
name="scratch_read",
description=(
"Read the full contents of the scratchpad. "
"Use this to recall working notes, mid-task context, or anything previously jotted down. "
"The scratchpad is transient — nothing here is distilled or archived."
),
parameters=types.Schema(type=types.Type.OBJECT, properties={}),
),
types.FunctionDeclaration(
name="scratch_write",
description=(
"Replace the entire scratchpad with new content. "
"Use this to set a clean working note, replacing whatever was there before. "
"For adding without replacing, use scratch_append instead."
),
parameters=types.Schema(
type=types.Type.OBJECT,
properties={
"content": types.Schema(type=types.Type.STRING, description="The new scratchpad content (markdown supported)"),
},
required=["content"],
),
),
types.FunctionDeclaration(
name="scratch_append",
description=(
"Add a new section to the bottom of the scratchpad without replacing existing content. "
"Each section gets a 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="scratch_clear",
description="Erase everything in the scratchpad. Use when the working notes are no longer needed.",
parameters=types.Schema(type=types.Type.OBJECT, properties={}),
),
]