feat: session_search tool + tool expansion docs update

session_search (tools/files.py):
- Full-text search across past session logs, exposed to the orchestrator
- Params: query (required), limit (default 5, max 20)
- Returns dated excerpts, newest first; own sessions only via ContextVars
- User-level — no TOOL_ROLES gating needed
- Registered in __init__.py callables + TOOL_CATEGORIES["Files"]

ARCH__FUTURE.md §2: updated tool count to 44, marked prior tools complete,
added Round 2 planned tools table (session_search now done, reminders due dates,
http_post, nc_talk_history, task_list priority filter, http_fetch max_chars),
noted datetime_now is not needed (already in system prompt via context_loader)

TODO__Agents.md: session_search checked off, Round 2 task list added

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-05-08 21:41:26 -04:00
parent f8f7cd75da
commit 750cde489d
4 changed files with 125 additions and 24 deletions

View File

@@ -30,7 +30,7 @@ from tools.ae_knowledge import (
journal_entry_prepend as _ae_journal_entry_prepend,
)
from tools.ae_tasks import task_list as _ae_task_list
from tools.files import file_read as _file_read, file_list as _file_list, file_write as _file_write
from tools.files import file_read as _file_read, file_list as _file_list, file_write as _file_write, session_search as _session_search
from tools.system import (
shell_exec as _shell_exec,
claude_allow_dir as _claude_allow_dir,
@@ -89,7 +89,7 @@ import tools.agent_notes as _mod_agent_notes
TOOL_CATEGORIES: dict[str, list[str]] = {
"Web": ["web_search", "http_fetch"],
"Files": ["file_read", "file_list", "file_write"],
"Files": ["file_read", "file_list", "file_write", "session_search"],
"Shell": ["shell_exec", "claude_allow_dir"],
"System": ["cortex_restart", "cortex_logs", "cortex_status", "cortex_update"],
"Tasks": ["task_list", "task_create", "task_update", "task_complete"],
@@ -126,6 +126,7 @@ _CALLABLES: dict[str, callable] = {
"file_read": _file_read,
"file_list": _file_list,
"file_write": _file_write,
"session_search": _session_search,
"shell_exec": _shell_exec,
"claude_allow_dir": _claude_allow_dir,
"cortex_restart": _cortex_restart,

View File

@@ -1,13 +1,15 @@
"""
File read tool — restricted to known-safe directory roots.
File read/write/search tools — restricted to known-safe directory roots.
Lets the orchestrator read local files (documentation, notes, config references)
without exposing arbitrary filesystem access. All paths are resolved and checked
against an allowlist of roots before any read is performed.
and search past session logs without exposing arbitrary filesystem access.
All paths are resolved and checked against an allowlist of roots before any
read or write is performed.
"""
import asyncio
import logging
import re
from pathlib import Path
from google.genai import types
@@ -225,6 +227,55 @@ def _sync_file_write(path: str, content: str, mode: str) -> str:
return f"Write error: {e}"
_SEARCH_EXCERPT_CHARS = 150
async def session_search(query: str, limit: int = 5) -> str:
"""Search past session logs for a keyword or phrase.
Returns up to `limit` matching excerpts with session dates, newest first.
Only searches the current user's own sessions (per-persona isolation via ContextVars).
"""
return await asyncio.to_thread(_sync_session_search, query, limit)
def _sync_session_search(query: str, limit: int) -> str:
from persona import persona_path
sessions_dir = persona_path() / "sessions"
if not sessions_dir.exists():
return "No session logs found."
limit = max(1, min(limit, 20))
pattern = re.compile(re.escape(query), re.IGNORECASE)
session_files = sorted(sessions_dir.glob("*.md"), reverse=True)
matches = []
for sf in session_files:
if len(matches) >= limit:
break
try:
text = sf.read_text()
except OSError:
continue
for m in pattern.finditer(text):
if len(matches) >= limit:
break
start = max(0, m.start() - _SEARCH_EXCERPT_CHARS)
end = min(len(text), m.end() + _SEARCH_EXCERPT_CHARS)
excerpt = text[start:end].strip()
if start > 0:
excerpt = "" + excerpt
if end < len(text):
excerpt = excerpt + ""
matches.append(f"[{sf.stem}] {excerpt}")
if not matches:
return f"No matches for '{query}' across {len(session_files)} session logs."
header = f"Session search: '{query}'{len(matches)} match(es) across {len(session_files)} logs\n"
return header + "\n\n".join(matches)
DECLARATIONS = [
types.FunctionDeclaration(
name="file_read",
@@ -278,4 +329,22 @@ DECLARATIONS = [
required=["path", "content"],
),
),
types.FunctionDeclaration(
name="session_search",
description=(
"Search past conversation session logs for a keyword or phrase. "
"Use this to recall what was discussed in previous sessions — "
"e.g. 'what did we decide about X?', 'when did we set up Y?'. "
"Returns matching excerpts with session dates, newest first. "
"Only searches this user's own sessions."
),
parameters=types.Schema(
type=types.Type.OBJECT,
properties={
"query": types.Schema(type=types.Type.STRING, description="Keyword or phrase to search for"),
"limit": types.Schema(type=types.Type.INTEGER, description="Max results to return (default 5, max 20)"),
},
required=["query"],
),
),
]

View File

@@ -46,27 +46,32 @@ Full API reference: [`docs/OPEN_WEBUI_API.md`](../docs/OPEN_WEBUI_API.md)
## 2. Orchestrator Tool Expansions
**Status:** Planned. Current tool count: 27. These fill obvious gaps.
**Status:** Ongoing. Current tool count: 44. Previously planned tools are all complete.
New tools for `cortex/tools/` — each follows the existing async pattern (implement function,
add `FunctionDeclaration`, register in `__init__.py`).
### Completed
All originally planned tools are live: `cortex_restart`, `cortex_logs`, `http_fetch`,
`file_list`, `file_write`, `nc_talk_send`, `email_send`, `web_push`, `agent_notes_*`.
| Tool | Module | Description |
|---|---|---|
| `cortex_restart` | `system.py` | `systemctl --user restart cortex` — Inara can apply her own config changes; returns last 10 log lines after restart |
| `cortex_logs` | `system.py` | `journalctl --user -u cortex -n N` — tail service logs for debugging |
| `http_fetch` | `web.py` | Fetch a specific URL and return content; for health checks, API probing, webhook testing — not a search, a direct GET/POST |
| `file_list` | `scratch.py` or new `files.py` | List files and directories at a path; currently only `file_read` exists |
| `file_write` | `files.py` | Write content to a file with a path allow-list (persona dir + scratch by default) |
| `nc_talk_send` | new `notify.py` | Proactively send a message to the user via Nextcloud Talk outbound API |
| `email_send` | `notify.py` | Send email via existing `email_utils.py` SMTP helper |
| `web_push` | `notify.py` | Browser push notification via Web Push API (requires push subscription stored per-user in `home/{user}/push_sub.json`; pairs with the PWA service worker) |
### Next additions
**Safety note for `cortex_restart`:** The service will drop in-flight SSE connections on restart.
Only call if no streaming response is active. Add a check or a short delay before restarting.
**Datetime note:** The current date and time is already injected into every system prompt
via `context_loader.py` (`--- System --- Current date and time: ...`). A dedicated
`datetime_now` tool is not needed — the timestamp is always in context.
**Safety note for `file_write`:** Enforce an allow-list at the tool level, not just in the prompt.
Default allow: `home/{user}/persona/{name}/` and `/tmp/cortex-scratch/`. Reject any path outside.
| Tool | Module | Priority | Description |
|---|---|---|---|
| `session_search` | new `search.py` or `files.py` | High | Full-text search across past session logs. The UI search already exists (`GET /sessions/search`) — this exposes it to the orchestrator so the agent can answer "what did we discuss about X last month?" |
| `reminders due dates` | `reminders.py` | Medium | Add optional `due` field to `reminders_add`. Surface only due/overdue reminders in context rather than the full flat list. Makes reminders time-aware rather than always-on noise. |
| `http_post` | `web.py` | Medium | POST to an external URL — for webhooks, REST APIs, form submissions. Requires a per-user host allowlist (same pattern as `email_send`) to prevent misuse. |
| `nc_talk_history` | `notify.py` | Medium | Read recent messages from a Nextcloud Talk conversation. The bot can send but cannot read — adding read capability gives it full context before replying. |
| `task_list` priority filter | `tasks.py` | Low | `task_list` accepts `status` but not `priority`. Add `priority` param so the agent can ask "what are my high-priority tasks?" without returning everything. |
| `http_fetch` max_chars | `web.py` | Low | Currently hardcapped at 8,192 chars. Accept optional `max_chars` param so callers can request more or less content. |
### Not needed / deferred
- **`datetime_now`** — already in system prompt (see note above)
- **`memory_read`** — memory files are already loaded into system prompt at Tier 2+; a tool adds no value except at Tier 1, which is a rare edge case
- **Calculator** — modern models handle arithmetic well; `shell_exec` covers edge cases for admins
- **Google Calendar** — useful but requires Google API OAuth scope expansion; defer until auth layer supports it
---

View File

@@ -55,8 +55,7 @@ automatically. Remaining work is quality/reliability parity, not ground-up desig
- `/manifest.json` and `/sw.js` served at root; added to `_PUBLIC` in auth_middleware
- Tested: install prompt confirmed working in Chromium
### [Tools] Orchestrator tool expansions
New tools for `cortex/tools/` — higher-value additions that fill obvious gaps.
### [Tools] Orchestrator tool expansions — Round 1 ✅
- [x] **`cortex_restart`** — detached subprocess, 5s delay, admin-only, confirm-required — 2026-04-29
- [x] **`cortex_logs`** — `journalctl --user -u cortex -n N`, admin-only — 2026-04-29
- [x] **`http_fetch`** — direct URL fetch via httpx, 8192 char cap — 2026-04-29
@@ -66,6 +65,33 @@ New tools for `cortex/tools/` — higher-value additions that fill obvious gaps.
- [x] **`email_send`** — SMTP via email_utils, per-user regex allowlist in `home/{user}/email_allowlist.json`, managed via Settings UI textarea + Files panel raw editor — 2026-04-29
- [x] **`web_push`** — VAPID push via pywebpush; subscriptions in `home/{user}/push_subscriptions.json`; "Enable notifications" toggle in ☰ menu; sw.js push+notificationclick handlers — 2026-05-05
### [Tools] Orchestrator tool expansions — Round 2
Next additions identified 2026-05-08. See `ARCH__FUTURE.md` §2 for design notes.
**Note:** `datetime_now` is NOT needed — current date/time is already injected into every
system prompt by `context_loader.py` at all tiers.
- [x] **`session_search`** — expose existing session search to the orchestrator — 2026-05-08
- Wraps session log grep as a tool callable in `tools/files.py`
- Params: `query: str`, `limit: int = 5` (max 20)
- Returns: excerpts with session date, newest first; own sessions only via ContextVars
- User-level (no TOOL_ROLES entry needed)
- [ ] **`reminders` due-date support** — make reminders time-aware
- Add optional `due: str` (ISO date or natural language) to `reminders_add`
- Filter context surfacing: only show reminders where `due` is today or past (or no due date)
- `reminders_list` should show due date and overdue status
- [ ] **`http_post`** — POST to external URLs
- Params: `url: str`, `body: dict | str`, `headers: dict | None`
- Per-user host allowlist in `home/{user}/http_allowlist.json` (same pattern as email)
- Default: blocked unless URL host matches an allowlist entry
- Confirm-required for safety
- [ ] **`nc_talk_history`** — read recent Talk messages before replying
- Params: `conversation_token: str`, `limit: int = 20`
- Returns last N messages with sender + timestamp
- Admin-only (requires NC Talk API credentials from channels.json)
- [ ] **`task_list` priority filter** — add `priority` param alongside existing `status`
- [ ] **`http_fetch` max_chars** — optional param, default 8192, cap at 32768
### [Channel] Proactive notifications
Inara reaches out on her own initiative via NC Talk or Google Chat when a reminder
fires, a cron job completes, or something else warrants attention. The cron/reminder