- reminders_remove(index) removes one reminder by 1-based index
- reminders_list now returns numbered output (1. heading / body)
so any model can easily identify which index to pass
- _parse_sections() / _sections_to_text() helpers for clean round-trip
- Not in CONFIRM_REQUIRED — targeted removal is safe without a gate
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixes the broken confirmation gate where users had no way to approve
or deny a blocked tool call in the web UI.
Changes:
- orchestrator_engine.py: add OrchestrateCheckpoint dataclass, extract
loop into _run_from_contents(), add resume() function
- openai_orchestrator.py: same treatment — _run_from_messages(), resume()
- routers/orchestrator.py: POST /{job_id}/confirm and /deny endpoints,
separate _checkpoints store, _resume_job() + _finalize_job() helpers,
"awaiting_confirmation" job status with pending_confirmation payload
- auth_utils.py: get_tool_policy() and save_tool_policy() helpers reading
home/{user}/tool_policy.json (allow/deny lists)
- routers/orchestrator.py: loads tool_policy per user and passes
confirm_allow/confirm_deny to both engines
- app.js: poll loop handles awaiting_confirmation — shows Confirm/Deny
buttons inline, resumes polling after user action
- settings.html + settings.py: Tool Permissions section with allow/deny
textareas, POST /settings/tool-policy route
- style.css: .confirm-gate, .confirm-btn, .deny-btn styles
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
notification.py now handles all three outbound channels. Email defaults
to the user's login address (google_email from auth.json); an optional
override can be set in channels.json. Google Chat uses an incoming
webhook URL. NC Talk was already wired, just needs notification_room set.
Settings page gains a Notifications section: channel dropdown, optional
email override, NC room token, and Google Chat webhook URL. All stored
in per-user channels.json.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Settings page gets an editable textarea (POST /settings/email-allowlist)
so users can view and update their per-user regex allowlist without
touching the raw JSON file.
Files panel gains a "Settings" group containing email_allowlist.json as
a raw JSON editor backup — served from home/{user}/ via files.py USER_FILES.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each entry in email_allowlist.json is treated as a re.fullmatch pattern
(case-insensitive). Allows domain wildcards, plus-addressing, and any
variation expressible as a regex. Invalid patterns are logged and skipped.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reads home/{username}/email_allowlist.json (JSON array of addresses).
Fails safe: if file is missing or address not listed, send is blocked with
an informative message. home/ is gitignored; create the file manually per user.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wraps the existing email_utils.send_email helper as an admin-only tool.
Accepts to, subject, body (plain text); newlines converted to <br> for HTML part.
Registered in _CALLABLES, _ALL_DECLARATIONS, and TOOL_ROLES (admin).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Inline edit form per model row (label, model name/ID, host/account, context, tags)
- Fetch models button in edit form for local models — same live-picker UX as Add Model
- POST /settings/local/models/{id}/edit route in local_llm.py
- Admin role badge (ADMIN/USER pill) in Account Settings page
- HELP.md updated: new tools table with admin/confirm markers, PWA install section
- TODO updated: tool expansions marked done, distill review and Unsloth resolved,
role-based access and admin badge added to completed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- auth_utils: get_user_role() reads role from auth.json (admin|user, default user)
- manage_passwords: new `role` command to promote/demote users (admin-only by convention)
- tools/__init__: TOOL_ROLES map, CONFIRM_REQUIRED set, get_tools_for_role(),
get_openai_tools_for_role() — both orchestrators now filter tools by caller's role
- tools/system: cortex_restart (detached subprocess, 5s delay), cortex_logs (admin-only)
- tools/web: http_fetch — direct URL fetch, distinct from web_search
- tools/files: file_list (directory listing), file_write (restricted paths, admin-only)
- tools/notify: nc_talk_send — proactive outbound via notification.py
- orchestrator_engine + openai_orchestrator: user_role param; CONFIRM_REQUIRED tools
return a confirmation-request result instead of executing — loop breaks after Claude
asks user to confirm in a follow-up message
- home/scott/auth.json: role set to admin
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace plain textarea with CodeMirror 5 + markdown mode loaded from
jsDelivr CDN. Editor fills the modal body via flex layout, theme-aware
via CSS vars (cursor, selection, headings, bold/em/links/code all mapped
to Cortex dark/light palette). Lazy init on first file open; history
cleared per-file so undo doesn't bleed across files.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- cx_last_persona cookie set on serve_ui; root/login/help/settings
redirects use preferred persona from cookie instead of alphabetically first
- /api/personas returns [{name, emoji}] objects; persona switcher dropdown
renders emoji + name with flex layout and .pd-emoji span
- Help, Settings, Model Registry pages apply localStorage theme on load
(no flash); CSS variables for dark/light replacing all hardcoded hex values
- Claude CLI auth status moved from prominent chat banner to Anthropic
provider block in Model Registry — live dot indicator (ok/warn/err)
- Auth banner removed from main chat UI (index.html, app.js, style.css)
- Add Model collapsed into Models section as <details> to shorten page
- Light-mode overrides for provider icons, model badges, ctx-badge, tags
(Anthropic/Google/local colors now readable in both themes)
- Help page gains table, pre/code, hr styles for HELP.md rendered content
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Four new tools for full journal entry lifecycle management:
- ae_journal_entry_update — PATCH any combination of fields (title,
content, summary, tags, enable); only provided fields are changed
- ae_journal_entry_disable — soft-delete via enable=false
- ae_journal_entry_append — fetch entry, append timestamped section
to the bottom (ideal for running logs / data logs)
- ae_journal_entry_prepend — fetch entry, prepend timestamped section
to the top (most-recent-first pattern)
Shared _get_entry / _patch_entry helpers keep the read-modify-write
logic DRY. Also fixed journal_entry_create to prefer the canonical
journal_entry_id field over the legacy id_random alias.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Lists all Aether Journals for the configured account via
POST /v3/crud/journal/search with no filters (account scoped by header).
Returns name + id_random for each journal so the agent can discover
available journals before searching or writing entries.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When height is set to S, mode-select collapses to a row (mode button +
compact tools toggle side by side). M and L keep the vertical column
layout where each control gets its own full-width row. Driven by
data-size attribute set in JS so the switch is instant on click, not
reliant on a viewport media query. Removed the redundant max-height
landscape query.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Stack Chat/Note/OTR button and tools toggle vertically (flex-direction:
column, align-items: stretch) on desktop so they share a tidy left column.
Mobile (≤520px) restores row layout; landscape phone (≤400px height) also
reverts to row to avoid crowding a short viewport.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous approach used a 600ms preview animation + syncHeight() which
collapsed the textarea back to 1 line (empty scrollHeight). Now syncHeight
enforces minHeight = maxHeight/3, so each setting (S/M/L) has a visibly
distinct resting height even when the input is empty.
S (120px): min ~40px ≈ 1-2 lines at rest
M (240px): min ~80px ≈ 3 lines at rest
L (480px): min ~160px ≈ 5-6 lines at rest
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace 3 S/M/L height buttons with one cycling button (like font size)
- Fix closeAllPanels() to include ctx-panel so Context and Settings menus
cannot be open simultaneously
- Fix ctxOpenBtn handler to use the same toggle-via-closeAllPanels pattern
as the settings button
- Align .hdr-dropdown shadow to var(--shadow) instead of hardcoded rgba
- Align #ctx-panel z-index to 200 (match .hdr-dropdown)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace height <select> with S/M/L buttons (data-height); active class shows
current setting; clicking an empty textarea briefly expands it as a preview
so the effect is immediately visible, then auto-shrinks back
- Add --amber/--amber-border/--amber-glow CSS vars to all 4 theme blocks:
dark=#f59e0b (bright), light=#b45309 (deep, 4:1 contrast on light bg)
Fixes local-on/tools-toggle/backend-hint being nearly invisible in light mode
- Rename "Backend" ctx-section to "Role" (matches the role-cycle toggle)
- Update backend-toggle title from stale "primary backend" to "Active role"
- Capitalize distill buttons (Short/Mid/Long/All) to match Memory layer style
- Improve all ctx-panel button titles for clarity
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- #mode-select changed from flex column to flex row (desktop + mobile unified)
- Chat/⚡ buttons now sit side-by-side at the same height as the textarea
- Removed stale mode-agent CSS rules (mode removed in prior commit)
- Mobile: simplified override — flex:1 only, direction/align already desktop default
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
OFF: very dim (nearly invisible) — makes it clear tools are inactive
ON: amber with glow — matches local-on pattern, clearly active
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove 'agent' from mode dropdown; Chat/Note/OTR remain
- Add ⚡ tools toggle button in input bar (persisted in localStorage)
When on: routes to POST /orchestrate (Gemini tool loop); send btn → "Run"
When off: routes to POST /chat (direct to active role); no change
- Role selector and tools toggle are now fully independent:
active chat_role sent in orchestrate payload → used for final response
- orchestrator_engine.run() accepts response_role param; passes it to
complete(role=...) instead of hardcoded model="claude"
- OrchestrateRequest gains chat_role field (default "chat")
- Migrate stored 'agent' mode/MRU entries to 'chat' on load
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add shell_exec to orchestrator tool suite (system.py + __init__.py)
Runs arbitrary shell commands on the Cortex host with timeout (1–120s),
combined stdout/stderr output, optional working_dir, and exit code reporting.
Enables system diagnostics (df, ls, ps, journalctl, etc.) from Agent mode.
- Fix orchestrator_engine.run() to use model_name from resolved registry entry
Previously used settings.orchestrator_model (.env hardcode) regardless of
what model was assigned to the orchestrator role. Now accepts model_name param
and falls back to settings value only when registry has no model_name.
- Update ARCH__FUTURE.md: date, running host, local orchestrator status,
model registry V2 progress, added Cortex Mesh concept (section 9)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The backend toggle now cycles through configured roles (chat, coder,
research, distill, etc.) instead of backup model slots within the chat
role. Each role uses its own primary→backup chain from the registry.
- ChatRequest.slot replaced by chat_role (default "chat")
- GET /backend returns available_roles instead of chat_models
- _available_roles_for_toggle() builds list from defined_roles, excluding
orchestrator (which has its own Agent mode)
- Model label on responses now reflects the actual role's assigned model
- Toggle is inert when only one role is configured (avoids useless cycling)
- Add "Clear browser cache" button to Account Settings (Connected Accounts)
- Add _role_model_label() helper for cleaner response tag labeling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend toggle now cycles through chat role models by label instead of
cycling service type strings (auto/claude/gemini/local).
- model_registry: get_model_for_slot() — resolves a specific priority
slot without walking the fallback chain
- llm_client: complete() gains slot param; explicit slot selection
dispatches directly to that model with no silent fallback
- routers/chat.py: ChatRequest.slot; GET /backend returns chat_models
[{slot, label, type}] for the UI; _stream_chat uses resolved model
label for the response tag when a slot is pinned
- app.js: toggle loads chat_models from /backend, cycles by label,
sends slot in chat payload; legacy model field removed from payload
- app.js: fix Gap B — agent mode placeholder no longer says "Gemini
tool loop"; now says "orchestrator"
- DESIGN doc: updated to reflect phases 1+2 complete, catalog-as-code
decision, Gap A/B documented, Phase 3 implementation details
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add Claude Opus 4.6 and Sonnet 4.5 (previous versions, still available)
- Fix context_k for Opus 4.7 and Sonnet 4.6: both have 1M context (was 200)
- Haiku 4.5 context_k 200 is correct
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove outdated gemini-2.0-flash and gemini-1.5-pro.
Add gemini-2.5-flash-lite (GA) and the three Gemini 3.x preview
models (gemini-3.1-pro-preview, gemini-3-flash-preview,
gemini-3.1-flash-lite-preview). All have 1M token context windows.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Backends section: add local as third backend option, explain model
tag on responses, clarify auto vs explicit toggle behavior
- Agent Mode: remove hard-coded "Gemini" reference — orchestrator model
is now configurable via role assignments
- New Model Registry section: step-by-step for adding Google accounts,
local hosts, cloud/local model entries, and role assignments
- API reference: add local to model field, add /settings/models endpoint
- Remove outdated In Progress section (local backend + multi-user shipped)
- Header controls table: update Backend description
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds cloud provider management to /settings/models:
- Google Accounts section: add/remove Gemini API keys with labels
- Add Model form: provider tabs (Local / Google / Anthropic) with
catalog dropdowns that auto-fill label and context_k
- Provider badges on model rows (Anthropic / Google / Local)
- /settings/local now redirects to /settings/models (canonical URL)
- save_cloud_model() in model_registry for Anthropic/Google entries
- Distill role migration restored in _migrate_from_local_llm
- Test fixes: version assertions updated to V2
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a providers section to the per-user model registry for Anthropic and
Google as first-class providers alongside local hosts. Google accounts
(API keys) are now stored as a list so multiple Google accounts can coexist.
Changes:
- model_registry.py: V2 schema, auto migration V1→V2 (pulls gemini_api_key
from auth.json into providers.google.accounts), _resolve_model() merges
account API key for gemini_api type models
- routers/orchestrator.py: uses model-resolved api_key when orchestrator
role resolves to a gemini_api model with account_id
- ANTHROPIC_CATALOG and GOOGLE_CATALOG constants for model picker (Phase 2)
- New functions: get_google_api_key(), save/remove_google_account(), get_catalog()
- Documentation: ARCH__BACKENDS.md updated to V2 schema, DESIGN doc added
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The AsyncOpenAI client always appends /chat/completions to base_url.
Open WebUI's endpoint is at /api/chat/completions, so for openwebui
host_type the base_url must include the /api prefix — same logic as
_local() in llm_client.py.
Also strip non-standard metadata fields (backend, host, etc.) from
session_messages before passing them to the API.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously any backend error would silently fall back to Claude.
Now if the user has a model configured via the model registry, errors
propagate to the UI so the actual problem is visible rather than hidden
behind a transparent backend switch.
Fallback still applies when using the default/auto backend with no
registry config.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each assistant message in the session JSON now carries:
backend, backend_label, host (platform.node())
These fields are shown as model tags in the UI — on live responses and
when loading session history. Session log entries (sessions/YYYY-MM-DD.md)
include the backend label and host in the turn header.
The local (OpenAI-compat) backend strips non-standard fields before
sending messages to the API so extra fields don't leak upstream.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- openai_orchestrator.py — new ReAct tool loop engine for any
OpenAI-compatible endpoint (OpenRouter, Open WebUI, Ollama, LiteLLM);
model handles both tool loop and final response, no Claude handoff needed
- tools/__init__.py — auto-derive OpenAI JSON Schema from existing Gemini
FunctionDeclarations so tool definitions have a single source of truth
- routers/orchestrator.py — route to openai_orchestrator when model registry
"orchestrator" role resolves to a local_openai type host
- routers/chat.py — pass role to _backend_label(); fix fallback_used logic
(only meaningful for explicit backend overrides, not auto-routing)
- static/app.js — add null/"auto" to backend cycle; fetch local model hint
without overriding the auto default on page load
- model_registry.py — _normalize() back-fills host_type on old registry files
- requirements.txt — add openai>=1.0.0
- ARCH__BACKENDS.md — document OpenAI-compat backend and routing logic
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds host_type ("openwebui" | "openai") to the host schema so Cortex can
talk to both Open WebUI/Ollama and OpenRouter/standard-OpenAI endpoints.
Path differences per type:
openwebui (default): /api/chat/completions, /api/models
openai: /chat/completions, /models
model_registry.py:
- host_type added to host schema (default "openwebui", backward compat)
- save_host() accepts host_type parameter
- _resolve_model() passes host_type through with the merged host fields
llm_client._local():
- Reads host_type from resolved model_cfg
- Selects correct chat completions path accordingly
routers/local_llm.py:
- save_host route accepts host_type form field
- fetch-models uses /models for openai type, /api/models for openwebui
- Existing host rows show type selector pre-filled from stored value
local_llm.html:
- "Add host" form includes type selector
To use OpenRouter:
- Add host: URL = https://openrouter.ai/api/v1, Type = OpenAI-compatible
- API key from openrouter.ai (store in .env or model_registry.json only)
- Fetch models or add manually (e.g. anthropic/claude-sonnet-4-5-20251022)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixes:
- app.js was tracking primaryBackend locally but never included
model: primaryBackend in the /chat POST body, so the server always
used settings.primary_backend regardless of what the user clicked.
Now model: primaryBackend is sent on every chat request.
- Responses were only annotated when fallback occurred. Now every
assistant message shows a small model tag at the bottom right.
chat.py:
- _backend_label() resolves human-readable name:
claude → "Claude", gemini → "Gemini",
local → registry label (e.g. "Gemma 4 E4B") or model_name
- SSE payload now includes backend_label field
app.js:
- model: primaryBackend added to /chat fetch body
- After every response, appends .model-tag div with backend_label
- Fallback shows "⚡ fallback → <label>" in amber; normal is muted
- Removed separate system message for fallback (tag covers it)
style.css:
- .model-tag: small muted text, right-aligned, separated by thin line
- .model-tag.fallback: amber (#f59e0b)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Covers model_registry.py without requiring a running service or LLM:
Empty/fresh state: no files, missing user dir
Save/load: round-trip, corrupt file fallback
Migration: v1 hosts/models, v1 no active, v0 flat, v0 empty url,
distill_backend_mid=local → distill role, saves file after migrate
Built-in resolution: claude_cli, gemini_api, gemini_cli, unknown → None
User model resolution: local_openai merges host, missing host → None
get_model_for_role: registry primary, built-in from registry, skips missing,
walks full backup chain, .env fallback, hardcoded fallback,
custom roles
get_best_local_model: prefers role chain, falls back to first local, None if no local
Host CRUD: create, update, unknown ID creates new, remove + cascades to models
Model CRUD: create, update, remove + clears role refs
set_role: assign model, assign built-in, clear with None, invalid slot,
unknown model ID, creates new role key
get_defined_roles: returns all settings roles, fills gaps with {}
Multi-user isolation: registries don't bleed across users
All tests use tmp_path + patch.object(config.settings, ...) — no real files touched.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the single-host local model settings page with a full model
registry interface at /settings/local.
Hosts section:
- List existing hosts with inline edit + save + remove
- Collapsible "Add host" form
- Per-host "Fetch models" button
Models section:
- List all models with label, model name, host, context_k badge, tags
- Remove button
Add Model section:
- Host dropdown, label, model name, context_k, tags (comma-separated)
- "Fetch models from host" with auto-fill picker
Role Assignments section:
- One row per defined role (chat, orchestrator, distill, coder, research)
- Primary + backup_1 + backup_2 dropdowns per role
- Dropdowns pre-filled from registry on load
- AJAX save on change (POST /api/models/role) with toast confirmation
- Built-in models (claude_cli, gemini_cli, gemini_api) always available in dropdowns
Backend:
- All user_settings references replaced with model_registry
- host/{id}/remove route added
- fetch-models now accepts host_id query param
- POST /api/models/role for AJAX role assignment
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces model_registry.py as the single source of truth for all LLM
backend configuration. Replaces scattered backend settings across user_settings,
config distill_backend_*, and the UI toggle.
model_registry.py:
- Per-user home/{user}/model_registry.json with version, hosts, models, roles
- Models have: type (local_openai|claude_cli|gemini_cli|gemini_api), label,
model_name, host_id, context_k (tokens), tags (capability labels)
- Roles map to priority chains: primary, backup_1..backup_4
- Built-in IDs (claude_cli, gemini_cli, gemini_api) always resolvable
- Auto-migrates existing local_llm.json on first access
- CRUD: save_host, remove_host, save_model, remove_model, set_role
- get_model_for_role(): registry → .env default → hardcoded fallback
config.py:
- role_chat/orchestrator/distill/coder/research .env defaults
- defined_roles: comma-separated standard role list (extensible)
- get_defined_roles() and get_role_default() helper methods
llm_client.complete():
- New role= parameter (default "chat") for registry-based routing
- model= still accepted for explicit UI toggle override
- _claude() and _local() accept model_cfg dict instead of raw string
- _local() uses pre-resolved config from registry
memory_distiller.py:
- distill_mid/long now use role="distill" (no more distill_backend_* .env vars needed)
cron_runner.py:
- brief jobs use role="chat"
routers/chat.py + auth.py:
- Use model_registry instead of user_settings for local model info
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the lone "← Back to Cortex" link with a consistent page-nav
on both pages: ← Chat | Help | Settings | Sign out
Active page is highlighted purple; others are muted gray.
Settings page gets a {{ help_href }} template var from settings.py.
Help page builds nav links from the existing cfg JS object.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Every persona now knows: direct chat has no tools, Agent mode (⚡) has
the full tool suite. If asked to write a reminder/task/etc in chat mode,
tell the user to switch modes rather than silently failing.
Updated: inara, tina, donut, wintermute, developer, cleo PROTOCOLS.md
Updated: persona_template.py so all future personas get this by default
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
NameError: name 'user' is not defined in orchestrator._run_job —
user was resolved in the endpoint but not forwarded to the background task.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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 <noreply@anthropic.com>
- New endpoints: POST /channels/google-chat/{username} and /webhook/nextcloud/{username}
- Channel secrets/config live in home/{username}/channels.json (gitignored)
- auth_utils: get_user_channels() helper reads channels.json
- Both routers load persona, audience/secret, backend, timeout per user;
set_context() wires the correct persona before building the system prompt
- Removed server-level channel settings from config.py and .env —
no user gets a channel until they create their own channels.json
- .gitignore: home/**/channels.json added
To migrate: update Google Chat Add-on webhook URL to /channels/google-chat/{username}
and re-register NC Talk bot at /webhook/nextcloud/{username}
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Removed orphaned root .env and .env.default (values already in cortex/.env,
which is what the systemd service actually loads)
- Replaced outdated cortex/.env.example with the comprehensive .env.default content
- Also tracks: tested/persona/cleo/ (new test persona), Inara memory updates
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- cortex/static/HELP.md: shared Help & Reference base served to all users
- help.html: loads shared base + appends persona-specific HELP.md if present
- inara/HELP.md: cleared (content moved to shared base)
- Google OAuth: registered scott.idem@oneskyit.com; flow now working end-to-end
- .gitignore: exclude home/**/sessions/ (runtime logs)
- New personas tracked: home/holly/persona/donut/, home/scott/persona/developer/
- Removed orphans: holly/, personas/, cortex-holly.service
- CLAUDE.md: updated current state and recently completed list to 2026-03-27
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>