Restructures persona storage from a flat personas/{name}/ layout to
home/{username}/persona/{name}/, mirroring Linux home directories.
Changes:
- persona.py: two ContextVars (user + persona), Linux-style name validation,
set_context(), get_user(), get_persona(), validate(), list_users(),
list_user_personas(); persona_path() takes (username, name)
- config.py: replaces personas_dir with home_dir + home_root()
- git mv personas/inara → home/scott/persona/inara (history preserved)
- home/holly/persona/tina/: Holly's persona stub added
- cron_runner.py: all storage functions take (username, persona) params
- tools/cron.py: stamps user + persona on jobs; APScheduler IDs are
{user}:{persona}:{job_id} to prevent collisions across users
- memory_distiller.py: distill_short/mid/long take (username, persona);
added missing Path + settings imports
- scheduler.py: _load_user_crons() iterates home/*/persona/* (two-level)
- routers/chat.py, orchestrator.py: user field added; set_context() called
- tests/conftest.py: home_root fixture with two-level structure;
patches home_dir instead of personas_dir
- tests/test_persona.py: fully rewritten for two-level API
- tests/test_api_files.py: updated fixture name and path
- .env.default: documents HOME_DIR setting; scrubs stale API key
- CLAUDE.md, README.md: directory maps updated for new layout
All 80 tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add cortex/persona.py: ContextVar-based per-request routing with
path traversal protection and persona validation
- Migrate inara/ → personas/inara/ (git history preserved via git mv)
- config.py: add personas_root(), inara_path() delegates to personas/inara
- All 14 settings.inara_path() call sites replaced with persona_path()
- ChatRequest + OrchestrateRequest: add persona field (default: "inara")
with validation at request entry before any processing
- memory_distiller: add optional persona param for future per-persona distill
- cron_runner/tools/cron: stamp persona on jobs, prefix APScheduler IDs
(persona:job_id) to prevent collisions across personas
- scheduler: _load_user_crons() iterates all personas at startup
Adding a new persona: create personas/<name>/ with IDENTITY.md + SOUL.md.
Auth: handled at nginx level (inject X-Cortex-Persona header per subdomain).
Future: persona maps to Aether account_id_random for full integration.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add cortex/tools/scratch.py with scratch_read/write/append/clear tools
- Register all four scratch tools in the orchestrator tool registry
- Create inara/SCRATCH.md as the backing file (never distilled/archived)
- Fix auth.py: expiresAt reflects short-lived access token (~8h) not the
1-year refresh token — suppress expiry warning when refreshToken is present
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Orchestrator now saves turns to session store so history survives page refresh
- UI session_id updated from job result; history controls attached to agent turns
- Cortex migrated from system service to systemd user service (no more sudo)
- Update README.md and CLAUDE.md with correct service commands
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add Agent mode toggle to web UI input row — routes through POST /orchestrate
instead of /chat; polls for result with live tool-call count in thinking bubble
- Add cortex/tools/system.py with claude_allow_dir tool; registers in tool registry
- Fix web search: duckduckgo_search renamed to ddgs, update import + requirements.txt
- Allow WebSearch and WebFetch in ~/.claude/settings.json for Claude CLI fallback
- Add claude-allow-dir script docs and security note to CLAUDE.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All hardcoded "Inara"/"Scott" strings replaced with settings.agent_name
and settings.user_name, read from .env at startup:
- config.py: AGENT_NAME and USER_NAME settings (defaults: Inara / Scott)
- llm_client.py: conversation labels in prompt builder
- session_logger.py: **Name:** labels in session log markdown
- memory_distiller.py: distillation system prompts (mid + long)
- routers/nextcloud_talk.py: @mention prefix strip
- routers/google_chat.py: greeting message
Second instance scaffolding:
- holly/: identity directory with placeholder files (USER_NAME=Holly,
AGENT_NAME to be chosen by Holly)
- cortex/.env.holly: config for Holly's instance on port 8001
- cortex-holly.service: systemd unit for the second instance
No behavioural change to the Inara/Scott instance — defaults unchanged.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Session delete:
- DELETE /sessions/{session_id} endpoint (chat.py + session_store.py)
- × button on each session item in the panel (hover-reveal on desktop)
- Clears UI if the active session is deleted
Touch accessibility:
- @media (hover: none) rule makes msg-actions always visible on touch devices
- msg-act-btn tap targets enlarged to 36px min-height, readable font size
- session-delete-btn also always visible and finger-sized on touch
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Stack textarea above button row on mobile (flex-direction: column)
- font-size: 16px on textarea prevents iOS Safari auto-zoom on focus
- body height: 100dvh adjusts dynamically as soft keyboard opens/closes
- Right col goes horizontal (row) with full width on mobile
- Hide height-row and enter-toggle (desktop-only concepts)
- Larger touch targets for Send/Stop/Note
- Hide session-id to reclaim vertical space
Desktop layout unchanged.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
/auth/status now returns per-backend status: Claude warns on <24h expiry,
Gemini warns only when oauth_creds.json is missing or has no refresh_token
(access token rotates automatically so expiry_date is not a useful signal).
Banner shows warnings for both backends when needed, and the hint text
names the specific CLI commands to run.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fetches /distill/status when the ⚙ panel opens and renders next run
times below the distill buttons (monospace, muted). Shows "today",
"tomorrow", or "Mar 18" format depending on how far away.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Banner now shows a second line explaining how to fix it: SSH to the
Cortex host, run `claude`, follow the login prompt, restart Cortex.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New GET /auth/status endpoint reads ~/.claude/.credentials.json and
returns hours remaining + warning flag. UI shows a dismissible amber
banner when < 24h remain, turning red if expired. Checked on page load
and every 30 minutes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Header trimmed to 4 buttons (Sessions, Files, ⚙, ?). Backend toggle,
font size, and theme moved into the ⚙ settings panel under new Backend
and Display sections. Panels use responsive widths to avoid overflow on
small screens. Mobile breakpoints tighten padding and hide subtitle.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- scheduler.py: AsyncIOScheduler with three cron jobs
short daily 03:00 (no LLM, always fast)
mid weekly Sun 03:30 (LLM)
long monthly 1st 04:00 (LLM — off by default)
- config.py: AUTO_DISTILL, AUTO_DISTILL_SHORT/MID/LONG .env flags
- main.py: start/stop scheduler in FastAPI lifespan
- routers/distill.py: GET /distill/status — next run times + config
- requirements.txt: apscheduler>=3.10
- HELP.md: updated planned items, added /distill/status to API table
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- static/index.html: reduced to 127-line HTML shell
- static/style.css: all styles extracted (~900 lines) + help modal styles
+ shared markdown rendering for file-preview and help-modal-body
including tables (previously missing)
- static/app.js: all JS extracted (~900 lines) + help modal fetch/render
- index.html: adds ? help button + help modal HTML
- inara/HELP.md: comprehensive reference doc covering all features,
keyboard shortcuts, API endpoints, memory system, planned items
- routers/files.py: HELP.md added to ALLOWED set
- context_loader.py: HELP.md loaded at tier 2+ (after PROTOCOLS.md)
so Inara can reference it when helping Scott with the interface
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Cycles normal (16px) → large (18px) → small (14px) on the root element
so all rem-based text scales together. Persisted in localStorage, applied
before first paint to avoid flash. Also include today's session log.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Edit/delete individual messages from session context with inline editing
(Ctrl+Enter saves, Escape cancels); changes sync to backend via PUT /history
- PUT /history/{session_id} endpoint to replace full message list
- Named sessions: readable slugs (e.g. quiet-spring) instead of UUID fragments
- Scroll no longer snaps to bottom when user has scrolled up to read history
- cortex.service: systemd unit for auto-start and restart-on-failure
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Cortex: FastAPI backend serving Inara via Claude/Gemini CLI backends.
Includes SSE streaming chat, session persistence, Google Chat webhook
handler, and Docker support.
Inara: Identity files (persona, soul, protocols, memory, context tiers)
mounted read-only into the container at runtime.
Features in initial cut:
- /chat endpoint with SSE keepalive + LLM fallback
- Session store with rolling history window
- Markdown rendering, copy-to-clipboard, links open in new tab
- Stacked right-column input controls (height selector, enter toggle,
note mode with public/private) — semi-hidden until textarea grows
- /note endpoint for injecting public context into session history
- Docker Compose config (local dev runs natively; Docker for server)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>