feat: engine/model in audit log + docs update

- tool_audit: ContextVars (engine, model) set at orchestrator run start; fields added to every entry
- orchestrator_engine: tool_audit.set_context("gemini", model_name) at run() start
- openai_orchestrator: tool_audit.set_context("openai", model label) at run() start
- audit table: Model column between Status and Args
- HELP.md: push notifications section, audit log in Files section, tool count 30→40, new API endpoints
- TODO__Agents.md: web_push and audit log marked complete with full detail

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-05-05 20:42:32 -04:00
parent 02accefe8f
commit 7d221863dc
7 changed files with 55 additions and 6 deletions

View File

@@ -26,6 +26,7 @@ from openai import AsyncOpenAI
from config import settings
from orchestrator_engine import OrchestrateCheckpoint, OrchestratorResult
from tools import OPENAI_TOOL_SCHEMAS, call_tool, get_openai_tools_for_role, get_tools_for_role, CONFIRM_REQUIRED
import tool_audit
logger = logging.getLogger(__name__)
@@ -73,6 +74,7 @@ async def run(
effective_confirm = (CONFIRM_REQUIRED - set(_confirm_allow)) | set(_confirm_deny)
client, model_name, active_tools = _build_client(model_cfg, user_role, tool_list)
tool_audit.set_context("openai", model_cfg.get("label") or model_name)
sys_content = (system_prompt or "") + _TOOL_INSTRUCTION
messages: list[dict] = [{"role": "system", "content": sys_content}]

View File

@@ -27,6 +27,7 @@ from config import settings
from llm_client import complete
from tools import TOOL_DECLARATIONS, call_tool, get_tools_for_role, CONFIRM_REQUIRED
import usage_tracker
import tool_audit
from persona import _user
logger = logging.getLogger(__name__)
@@ -140,6 +141,7 @@ async def run(
)
client = genai.Client(api_key=api_key)
tool_audit.set_context("gemini", model_name or settings.orchestrator_model)
_confirm_allow = frozenset(confirm_allow or ())
_confirm_deny = frozenset(confirm_deny or ())

View File

@@ -6,7 +6,7 @@
and are appended automatically by help.html when present.
-->
*Last updated: 2026-04-30*
*Last updated: 2026-05-05*
---
@@ -16,7 +16,7 @@
|---|---|
| **Sessions** | Open the sessions panel — list, resume, or start sessions |
| **N** (sliders icon) | Open the Context & Memory panel (N = current context tier) |
| **☰** | Settings menu — Files, Account, Sign Out |
| **☰** | Settings menu — Files, push notification toggle, Account, Sign Out |
| **?** | Open this help panel |
The **Context & Memory** panel (sliders icon with tier number) contains all configuration options:
@@ -59,7 +59,7 @@ The orchestrator runs a multi-step tool loop:
The ⚡ toggle is **independent of the Role selector** — you can use any role (chat, coder, research, etc.) with or without tools. The orchestrator model is configured in **Account → Model Registry → Role Assignments → Orchestrator**. By default this is Gemini API.
The full tool reference is in the **Tools** tab. 30 tools across web, files, shell, system, tasks, cron, reminders, scratchpad, notifications, and Aether Journals.
The full tool reference is in the **Tools** tab. 40 tools across web, files, shell, system, tasks, cron, reminders, scratchpad, notifications, and Aether Journals.
Tools mode is best for tasks requiring research, multi-step reasoning, or side effects (e.g. "search for X", "add a task", "what's on my list?", "append this to my journal"). Regular chat is faster for conversational turns.
@@ -222,6 +222,19 @@ The **Files** button opens an editor for your persona's identity and memory file
Toggle **preview** / **edit** to switch between rendered markdown and raw text. **Ctrl+S** saves, **Esc** closes.
The **Audit Log** group at the bottom of the sidebar (collapsed by default) lists tool call logs by date (`YYYY-MM-DD.jsonl`). Click any date to view a read-only table of every orchestrator tool call: time, tool name, status, model, args, and result snippet. Status is colour-coded: green = ok, red = error, amber = denied.
---
## Push Notifications
Cortex can send browser push notifications — even when the tab is closed.
- Open **☰ → Enable notifications** and accept the browser permission prompt.
- Once enabled, the button shows **Notifications on** (in accent colour).
- Click again to disable. Subscriptions are stored per-device.
- The orchestrator's `web_push` tool lets Inara send you a push proactively (e.g. when a long task completes).
---
## Context & Memory ( ⚙ panel )
@@ -305,6 +318,13 @@ For direct access or scripting:
| `GET` | `/orchestrate/{job_id}` | Poll job status and result |
| `GET` | `/settings/models` | Model registry UI |
| `POST` | `/api/models/role` | Set a role assignment (JSON body) |
| `GET` | `/api/push/vapid-key` | VAPID public key (for push subscription) |
| `POST` | `/api/push/subscribe` | Register a push subscription |
| `DELETE` | `/api/push/subscribe` | Remove a push subscription |
| `GET` | `/api/audit/files` | List available audit log dates (own data) |
| `GET` | `/api/audit/day?date=` | Tool call entries for a specific date (own data) |
| `GET` | `/api/audit/recent` | Recent tool calls across days (admin) |
| `GET` | `/api/audit/stats` | Tool call counts by tool/status/user (admin) |
| `GET` | `/health` | Health check — returns `{"status": "ok"}` |
Chat request body (`POST /chat`):

View File

@@ -1547,6 +1547,7 @@
<th class="at-time">Time</th>
<th class="at-tool">Tool</th>
<th class="at-status">Status</th>
<th class="at-model">Model</th>
<th class="at-args">Args</th>
<th class="at-result">Result</th>
</tr></thead>`;
@@ -1554,11 +1555,13 @@
const tbody = document.createElement('tbody');
for (const e of entries) {
const time = (e.ts || '').slice(11, 19); // HH:MM:SS
const model = e.model || e.engine || '';
const tr = document.createElement('tr');
tr.innerHTML = `
<td class="at-time">${time}</td>
<td class="at-tool" title="${e.tool || ''}">${e.tool || '?'}</td>
<td class="${_auditStatusClass(e.status)}">${e.status || '?'}</td>
<td class="at-model" title="${model}">${model}</td>
<td class="at-args" title="${(_fmtArgs(e.args) || '').replace(/"/g, '&quot;')}">${_fmtArgs(e.args)}</td>
<td class="at-result" title="${(e.result_snippet || '').replace(/</g, '&lt;').replace(/"/g, '&quot;')}">${
(e.result_snippet || '').replace(/</g, '&lt;').slice(0, 80)

View File

@@ -1261,7 +1261,8 @@
.at-time { width: 7em; color: var(--muted); white-space: nowrap; }
.at-tool { width: 11em; color: var(--accent); font-weight: 500; }
.at-status { width: 4.5em; font-weight: 600; }
.at-args { width: 30%; color: var(--muted); }
.at-model { width: 10em; color: var(--muted); }
.at-args { width: 25%; color: var(--muted); }
.at-result { color: var(--muted); }
.at-status.ok { color: #4ade80; }
.at-status.error { color: #f87171; }

View File

@@ -16,6 +16,7 @@ Each line is a JSON object:
import asyncio
import json
import logging
from contextvars import ContextVar
from datetime import datetime, date
from pathlib import Path
@@ -29,6 +30,16 @@ _SNIPPET_MAX = 300 # chars of result to keep as snippet
# Per-file write locks — prevents interleaved lines under concurrent tool calls
_locks: dict[str, asyncio.Lock] = {}
# ContextVars set by orchestrators before their tool loop runs
_audit_engine: ContextVar[str] = ContextVar("_audit_engine", default="")
_audit_model: ContextVar[str] = ContextVar("_audit_model", default="")
def set_context(engine: str, model: str) -> None:
"""Call at the start of each orchestrator run to tag subsequent tool calls."""
_audit_engine.set(engine)
_audit_model.set(model)
def _truncate_args(args: dict) -> dict:
out = {}
@@ -63,6 +74,8 @@ async def record(
entry = {
"ts": datetime.now().isoformat(timespec="seconds"),
"user": user,
"engine": _audit_engine.get(),
"model": _audit_model.get(),
"tool": tool,
"args": _truncate_args(args),
"status": status,

View File

@@ -39,8 +39,7 @@ New tools for `cortex/tools/` — higher-value additions that fill obvious gaps.
- [x] **`file_write`** — overwrite/append to home_root paths, admin-only, confirm-required — 2026-04-29
- [x] **`nc_talk_send`** — outbound NC Talk message via notification.py, admin-only — 2026-04-29
- [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
- [ ] **`web_push`** — send a browser push notification (requires push subscription stored
per-user; pairs well with the PWA service worker already in place)
- [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
### [Channel] Proactive notifications
Inara reaches out on her own initiative via NC Talk or Google Chat when a reminder
@@ -121,6 +120,15 @@ so Scott can see who's spending what.
- [ ] Expose via `/api/usage` endpoint; add a summary row to the Settings page
- [ ] Optional: soft spending limit with a warning toast when exceeded
### [Security] Tool call audit log — 2026-05-05
Every orchestrator tool invocation logged to `home/{user}/tool_audit/YYYY-MM-DD.jsonl`.
- [x] `tool_audit.py` — JSONL writer with asyncio locks; ContextVars for engine/model set by each orchestrator at run start
- [x] Hook in `call_tool()` — fire-and-forget `asyncio.create_task`; captures status ok/error/denied, 300-char result snippet, args (truncated at 500 chars)
- [x] `GET /api/audit/files` — lists available dates for current user (self-service)
- [x] `GET /api/audit/day?date=` — returns entries for one date (self-service)
- [x] `GET /api/audit/recent` + `/stats` — cross-user aggregation (admin only)
- [x] "Audit Log" group in Files panel sidebar (collapsed by default) — read-only table with time/tool/status/model/args/result columns; colour-coded status
### [Intelligence] Dev agent pipeline
See `ARCH__Intelligence_Layer.md`. Full design not yet started.
- [ ] Specialist agent: frontend (SvelteKit) code changes