This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/docs/ROADMAP.md
2026-02-26 21:19:55 +00:00

1188 lines
56 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Roadmap — Chat Switchboard
**See also:**
- [ARCHITECTURE.md](ARCHITECTURE.md) — Core services design, store layer, scope model
- [EXTENSIONS.md](EXTENSIONS.md) — Extension system spec (Browser/Starlark/Sidecar tiers,
manifests, browser tool bridge, surfaces/modes, model roles)
**Versioning (pre-1.0):** `0.<major>.<minor>` — hotfixes use quad: `0.x.y.z`
No compatibility guarantees before 1.0.
---
## Dependency Graph
Features have real dependencies. This ordering respects them.
```
v0.9.x Stability + Quick UX Wins ✅
v0.9.3 Content Visibility & Block Controls ✅
v0.9.4 API Key Encryption + Vault ✅
v0.10.0 Model Roles (utility + embedding) ✅
+ Usage Tracking
v0.10.2 Summarize & Continue ✅
v0.10.3 Frontend Refactor ✅
v0.10.4 Model Type Pipeline + Role Fixes ✅
v0.10.5 UI Primitives + Extension Surfaces ✅
v0.11.0 Extension Foundation (browser tier) ✅
┌───────┴──────────────┐
│ │
v0.12.0 File Handling v0.13.0 Admin Panel
+ Vision ✅ Refactor (fullscreen)
│ │
│ v0.13.1 Web Search
│ + url_fetch
│ │
└───────┬──────────────┘
v0.14.0 Knowledge Bases (embedding role + file storage + pgvector)
v0.15.0 Compaction (utility role + background job)
v0.15.1 Context Recall Tools (attachment_recall, conversation_search)
┌───────┴──────────────┐
│ │
v0.16.0 User Groups v0.17.0 Persona-KB Binding
+ Resource Grants + Enterprise KB Mode
│ │
└───────┬──────────────┘
v0.18.0 Memory (user + persona scopes, review pipeline)
v0.19.0 Projects / Workspaces (organizational containers)
v0.20.0 @mention Routing + Multi-model
v0.21.0 Extension Surfaces + Modes
v0.22.0 Smart Routing (policy on model roles)
v0.23.0 Multi-Participant Channels + Presence
v0.24.0 Auth Strategy (mTLS/OIDC) + Full RBAC + Group permissions
┌───────┴──────────────┐
│ │
v0.25.0 Workflow v0.26.0 Tasks /
Engine Autonomous Agents
(team-owned, (service channels,
staged processes, scheduler, unattended
human + AI collab) execution)
```
---
## ✅ v0.9.0 — Schema Consolidation + BYOK
The "rewrite" release: 21 migrations collapsed to 1, store layer abstraction,
and the model pipeline made real for end users.
**Schema & Architecture**
- [x] Consolidated schema (21 migrations → single 001_initial.sql)
- [x] Store layer abstraction (all DB via typed interfaces, no raw SQL in handlers)
- [x] Persona-as-trust-boundary model (replaces old presets concept)
- [x] Scope model (global/team/personal) for configs, personas, models
- [x] Backward-compatible API routes (v0.8 → v0.9 field aliases)
**Capabilities System**
- [x] Two-tier resolution: catalog (provider API, per-provider) → heuristic inference
- [x] Venice provider: full capability mapping incl. `optimizedForCode`
- [x] `ResolveIntrinsic` with gap-fill merging (catalog authoritative, heuristic fills gaps)
- [x] Preset capability inheritance via `GetByModelIDAny` fallback
**BYOK (Bring Your Own Key)**
- [x] Auto-fetch models on provider creation
- [x] User-facing refresh endpoint + "Refresh Models" button
- [x] Personal models in selector with `scope=personal` badge
- [x] Composite model IDs (`configId:modelId`) prevent cross-provider collisions
**Bug Fixes (0.9.0.x)**
- [x] API key storage (`json:"-"` tag silently dropped keys)
- [x] NULL model_default scan crash (sql.NullString)
- [x] Nil slice → JSON null (make([]T, 0))
- [x] Frontend error swallowing
**Testing**
- [x] Journey-based integration tests (API-only, 4-actor × 3-provider matrix)
- [x] Live Venice API integration test
- [x] Frontend JS test suite (107 tests, 27 suites)
---
## ✅ v0.9.1 — Capability Architecture + Docs
- [x] Removed static known model table (same model ≠ same capabilities across providers)
- [x] Resolution chain: catalog → heuristic only, no hardcoded table
- [x] Frontend KNOWN_MODELS removed — backend is sole source of truth
- [x] Heuristic patterns expanded (qwen3, gpt-5, grok, kimi, minimax, glm-5, gemma-3)
- [x] All providers updated to use `InferCapabilities()` directly
- [x] EXTENSIONS.md recovered and updated with Appendix A (Custom Renderers)
and Appendix B (Model Roles)
- [x] ROADMAP.md, CHANGELOG.md, README.md comprehensive rewrite
---
## ✅ v0.9.2 — Quick UX Wins + Hardening
Polish that doesn't need new infrastructure. Ship fast, stabilize.
**UX**
- [x] Collapsible code blocks (long outputs get `<details>` wrapper)
- [x] HTML preview for generated content (sandboxed iframe toggle)
- [x] Token count estimate in input area (before send)
- [x] "Conversation is getting long" warning (context % thresholds)
- [x] New switchboard panel favicon (animated SVG + PNG + ICO)
**Hardening**
- [x] Proxy interception detection (Content-Type checks, actionable error messages)
- [x] Environment injection to frontend (`window.__ENV__`)
- [x] Enhanced debug diagnostics (NET:PROXY log type, Content-Type capture, env info)
- [x] Team admin audit scoping (see only their team's audit entries)
---
## ✅ v0.9.3 — Content Visibility & Block Controls
User control over what's expanded/collapsed in the message flow. Thinking
blocks, tool calls, and code blocks all get consistent collapse controls
instead of being hidden or auto-formatted without user agency.
**Code Blocks**
- [x] Collapse/expand toggle button on code block header (alongside Copy/Preview)
- [x] Auto-collapse at threshold (>15 lines), user can always toggle
- [x] Smooth max-height transition instead of `<details>` wrapping
**Thinking Blocks**
- [x] Always render thinking blocks (never hide from DOM)
- [x] `showThinking` setting controls default open/closed (default: false), not visibility
- [x] User can always click to expand regardless of setting
- [x] `reasoning_content` delta field support for models that stream thinking separately (Grok, etc.)
- [x] Reasoning accumulated during streaming, wrapped in `<think>` tags before persistence
**Tool Calls**
- [x] Persist tool call metadata in stored messages (`tool_calls` JSONB column)
- [x] Render tool calls on history load (not just during streaming)
- [x] Collapsed by default: "🔧 tool_name → done" with toggle for full I/O
- [x] Notes tool: "📝 View note" link that navigates to Notes panel
- [x] Tool result rendering fix (operator precedence bug showing "undefined results")
**Notes Panel → Side Panel**
- [x] Copy button on note cards (same pattern as code block copy)
- [x] Unified side panel replaces inline HTML preview and notes modal
- [x] Tabbed interface: Preview and Notes tabs
- [x] Desktop: slides in from right (480px), chat area shrinks via flexbox
- [x] Mobile: full-width overlay at z-index 200
- [x] Escape key integration (generation → command palette → side panel → modals)
**Streaming Refactor**
- [x] Extracted `streamWithToolLoop()` into `stream_loop.go` as single canonical streaming implementation
- [x] Both `streamCompletion` (normal chat) and `Regenerate` call shared function — only persistence differs
- [x] Regenerate handler now has full feature parity: tools, reasoning, tool_calls persistence
- [x] Fixed nil `json.RawMessage` causing PostgreSQL JSONB rejection on regen persistence
**Regenerate Fix**
- [x] Regen streams in-place (truncates display to parent before streaming)
- [x] No more "collapse to original branch" after completion
**Favicon**
- [x] Animated SVG favicon during generation (cascading opacity pulse on 4 dots)
**Admin Scope**
- [x] Admin models endpoint returns only global-scope models (not user BYOK or team models)
- [x] Bulk visibility toggle scoped to global providers only
---
## ✅ v0.9.4 — API Key Encryption + Vault
Two-tier encryption with a trust boundary between organizational keys (admin-
accessible) and personal BYOK keys (user-only). Personal keys are protected
by a per-user encryption key (UEK) that the platform admin cannot recover
without the user's passphrase.
### Threat Model
| Threat | Global/Team keys | Personal BYOK keys |
|--------|-----------------|---------------------|
| DB backup theft | ✅ Protected (env-var AES) | ✅ Protected (UEK AES) |
| SQL injection exfil | ✅ Protected | ✅ Protected |
| Admin reads DB directly | ⚠️ Admin can decrypt (owns env var) | ✅ Admin cannot decrypt (lacks user passphrase) |
| Admin modifies server code | ⚠️ Out of scope (runtime access) | ⚠️ Out of scope (runtime access) |
### Crypto Design
**Two encryption paths, one mechanism (AES-256-GCM):**
1. **Global/Team keys** — encrypted with a key derived from `ENCRYPTION_KEY`
env var. Admin-owned, admin-recoverable. Simple.
2. **Personal keys** — encrypted with a per-user **User Encryption Key (UEK)**.
The UEK is a random 256-bit key generated at account creation, itself
encrypted with a key derived from the user's password via Argon2id.
```
Password → Argon2id(salt) → Password-Derived Key (PDK)
PDK → AES-256-GCM-Decrypt(encrypted_uek) → UEK (plaintext, session-only)
UEK → AES-256-GCM-Decrypt(api_key_enc) → API key (plaintext, per-request)
```
The UEK lives in server memory for the duration of the session (alongside the
JWT). It is never written to disk, logs, or the database in plaintext.
### Schema Changes (migration)
```sql
-- Users: vault support
ALTER TABLE users ADD COLUMN encrypted_uek BYTEA;
ALTER TABLE users ADD COLUMN uek_salt BYTEA; -- Argon2id salt
ALTER TABLE users ADD COLUMN uek_nonce BYTEA; -- GCM nonce for UEK wrap
ALTER TABLE users ADD COLUMN vault_set BOOLEAN NOT NULL DEFAULT false;
-- API configs: encrypted key storage
ALTER TABLE api_configs ADD COLUMN api_key_enc BYTEA;
ALTER TABLE api_configs ADD COLUMN key_nonce BYTEA;
ALTER TABLE api_configs ADD COLUMN key_scope TEXT NOT NULL DEFAULT 'global';
-- key_scope: 'global' | 'team' | 'personal'
-- global/team: decrypt with env-var-derived key
-- personal: decrypt with user's UEK
-- Team providers: same pattern
ALTER TABLE team_providers ADD COLUMN api_key_enc BYTEA;
ALTER TABLE team_providers ADD COLUMN key_nonce BYTEA;
```
Migration backfills: encrypt existing plaintext keys using the env var key
(all existing keys are global/team scope). Drop plaintext `api_key` column
after backfill. If `ENCRYPTION_KEY` is not set, migration refuses to run
(fail-safe).
### Auth Mode Integration
**Builtin auth** — password serves double duty: authentication + UEK
derivation. No additional UX. On login, derive PDK → unwrap UEK → cache
in session. User never knows the vault exists.
**mTLS / OIDC** (v0.24.0) — authentication is external (cert/token).
Vault passphrase is conditionally required:
```
External auth → auto-authenticated
├── Personal providers DISABLED → straight to app, no prompt
└── Personal providers ENABLED
├── First visit → "Set a vault passphrase to protect your API keys"
└── Returning → "Unlock your vault" (passphrase field, pre-filled username)
```
Terminology: **"vault passphrase"** for mTLS/OIDC users (distinct from their
non-existent account password). Builtin auth users never see this term.
### Backend Implementation
**`server/crypto/vault.go`** — pure encryption/decryption, no DB awareness:
- `DeriveKey(password, salt) → key` (Argon2id, 64MB memory, 3 iterations)
- `GenerateUEK() → (uek, error)` (crypto/rand, 32 bytes)
- `WrapUEK(uek, pdk) → (ciphertext, nonce, error)`
- `UnwrapUEK(ciphertext, nonce, pdk) → (uek, error)`
- `Encrypt(plaintext, key) → (ciphertext, nonce, error)`
- `Decrypt(ciphertext, nonce, key) → (plaintext, error)`
- `DeriveEnvKey(envVar) → key` (HKDF-SHA256 with domain separation context)
**Session UEK caching** — UEK stored in a sync.Map keyed by user ID, evicted
on logout / token expiry. Completion handler retrieves UEK from cache to
decrypt personal provider keys at request time.
**Provider resolution**`key_scope` determines decryption path:
```go
func (s *Store) DecryptAPIKey(config ApiConfig, uekCache *sync.Map) (string, error) {
switch config.KeyScope {
case "global", "team":
return crypto.Decrypt(config.APIKeyEnc, config.KeyNonce, envDerivedKey)
case "personal":
uek, ok := uekCache.Load(config.UserID)
if !ok { return "", ErrVaultLocked }
return crypto.Decrypt(config.APIKeyEnc, config.KeyNonce, uek.([]byte))
}
}
```
### Edge Cases
| Scenario | Behavior |
|----------|----------|
| **User changes password** (builtin) | Re-derive PDK, re-encrypt UEK with new PDK. Personal API keys unchanged (keyed to UEK, not password). |
| **Admin resets user password** | Old UEK unrecoverable. Personal keys destroyed. Admin UI warns before confirming. |
| **mTLS user forgets passphrase** | Self-service reset: destroys encrypted personal keys. UX: "This will erase your stored API keys." |
| **Admin disables personal providers** | Keys stay encrypted in DB (inert). Re-enable later → keys accessible if user remembers passphrase. |
| **`ENCRYPTION_KEY` not set** | Startup refuses to run if encrypted keys exist in DB. Fresh install: keys stored plaintext with deprecation warning in logs + admin panel. |
| **`ENCRYPTION_KEY` rotated** | Admin CLI tool: `switchboard vault rekey` — re-encrypts all global/team keys with new env key. Personal keys unaffected (keyed to UEK). |
### Frontend Changes
- Provider management (Settings → Providers): no change to UX for builtin auth
- mTLS/OIDC splash: conditional vault passphrase prompt (new component)
- Admin → Users → Reset Password: warning dialog about personal key destruction
- Admin → Settings: indicator for `ENCRYPTION_KEY` status (set / not set)
### Checklist
**Crypto core**
- [x] `server/crypto/vault.go` — Argon2id derivation, AES-256-GCM wrap/unwrap
- [x] `server/crypto/vault_test.go` — round-trip, wrong-password, corruption (11 tests)
- [x] Env-var key derivation + validation on startup (HKDF-SHA256 with domain separation)
- [x] `server/crypto/cache.go` — UEK cache with copy-on-read/write, memory zeroing
- [x] `server/crypto/resolver.go` — KeyResolver routes decrypt/encrypt by key_scope
- [x] `server/crypto/backfill.go` — startup migration for plaintext → encrypted keys
**Schema + migration**
- [x] Migration 003_vault.sql: encrypted columns, key_scope backfill
- [x] `ENCRYPTION_KEY` enforcement (refuse to start if keys exist but no env var)
- [x] Unencrypted fallback when `ENCRYPTION_KEY` not set (raw bytes, graceful degradation)
**Auth integration**
- [x] UEK generation on user creation (builtin auth)
- [x] UEK unwrap on login, cache in session
- [x] UEK eviction on logout (memory zeroing)
- [ ] UEK re-wrap on password change _(deferred — Settings handler)_
- [ ] UEK destruction on admin password reset with confirmation _(deferred)_
- [ ] Vault passphrase prompt for mTLS/OIDC _(deferred — v0.24.0 dependency)_
**Provider key lifecycle**
- [x] Encrypt on provider create/update (all 6 write paths: admin global, personal, team)
- [x] Decrypt on completion request (all 4 read paths: completion, admin fetch, personal fetch, team fetch)
- [x] `ErrVaultLocked` handling (prompt user to unlock)
**Admin tooling**
- [x] `switchboard vault rekey` CLI command _(v0.12.0)_
- [x] `switchboard vault status` CLI command _(v0.12.0)_
- [x] Admin UI: encryption status indicator _(v0.12.0 — Settings tab)_
- [x] Admin UI: password reset warning (v0.10.0 — vault destruction dialog)
**CI/CD**
- [x] `ENCRYPTION_KEY` Gitea secret → k8s `switchboard-encryption` secret sync
- [x] Backend k8s manifest: optional `secretKeyRef` for `ENCRYPTION_KEY`
- [x] Pipeline version bump to v0.9.4
**Security fix (bonus)**
- [x] DOMPurify: strict `ALLOWED_TAGS` allowlist (was permissive `ADD_TAGS` default)
- [x] Think-block placeholder `\n\n` padding (prevents fence fusion with `</think>`)
- [x] Unclosed code fence auto-close before `marked.parse` (streaming protection)
---
## ✅ v0.10.0 — Model Roles + Usage Tracking + Vault Debt
Three tracks of infrastructure that everything downstream depends on.
**Vault Debt (Security Fixes)**
- [x] UEK re-wrap on user password change (prevents permanent key loss)
- [x] UEK destruction on admin password reset (vault + personal BYOK keys deleted)
- [x] Admin UI: password reset confirmation with vault destruction warning
- [x] Audit logging for vault destruction events
**Model Roles** (see [EXTENSIONS.md Appendix B](EXTENSIONS.md#appendix-b-model-roles))
- [x] `model_roles` in global_settings: named slots (utility, embedding, generation)
- [x] Each role: primary provider+model, optional fallback provider+model
- [x] Team-level role overrides (team.settings JSONB)
- [x] `Provider.Embed()` interface — OpenAI/Venice/OpenRouter implement, Anthropic returns `ErrNotSupported`
- [x] `roles.Resolver` package: `Complete()` + `Embed()` with automatic fallback
- [x] Admin API: `GET/PUT /admin/roles/:role`, `POST /admin/roles/:role/test`
- [x] Team API: `GET/PUT/DELETE /teams/:id/roles/:role`
- [x] Admin UI: role configuration tab (select provider + model per role, test fire)
**Usage Tracking**
- [x] Streaming token capture fix — OpenAI: `stream_options.include_usage` + parse usage chunk;
Anthropic: parse `message_start`/`message_delta` usage events
- [x] Cache token fields across all types (`CompletionResponse`, `StreamEvent`, `streamResult`)
- [x] `usage_log` table: channel_id, user_id, model_id, provider_config_id,
role, token counts (prompt, completion, cache_creation, cache_read), cost, timestamp
- [x] `model_pricing` table: provider+model, input/output/cache per-M, source (catalog/manual)
- [x] Cost calculated at insert time (baked, not query-time)
- [x] BYOK exclusion: admin views filter `provider_scope != 'personal'`, BYOK gets separate endpoint
- [x] Model pricing from catalog sync (won't overwrite manual overrides)
- [x] Admin API: `GET /admin/usage`, `GET /admin/usage/users/:id`, `GET /admin/usage/teams/:id`
- [x] Admin API: `GET/PUT/DELETE /admin/pricing`
- [x] User API: `GET /usage` (personal usage summary)
- [x] Admin UI: usage dashboard (period selector, group-by, totals cards, breakdown table, pricing table)
- [x] Team admin UI: usage dashboard for team-owned providers (`GET /teams/:teamId/usage`)
- [x] Token accumulation across multi-tool-call iterations in `stream_loop.go`
**Schema (migration 004)**
- [x] `usage_log` with indexes on user, created_at, provider, model, scope
- [x] `model_pricing` with unique constraint on (provider_config_id, model_id)
- [x] `model_roles` seed in global_settings
---
## ✅ v0.10.1 — Polish + UX
Spit and polish release: admin system prompt injection, team management modal
extraction, persona tab, side panel enhancements, code download, and critical
OpenAI streaming usage race fix.
- [x] Admin system prompt (global, injected before user/preset prompts)
- [x] Personas tab (moved from Models, policy-gated)
- [x] Team Management modal (extracted from Settings, tabbed, multi-team picker)
- [x] Preview pane: clear button, fullscreen toggle, resizable
- [x] Code block: download button (infers extension from language tag)
- [x] Personal Usage scoped to BYOK only
- [x] OpenAI streaming usage race fix (deferred Done event until usage chunk)
- [x] Usage logging zero-token guard removal
---
## v0.10.2 — Summarize & Continue + Role Cleanup ✅
First consumer of the utility model role. User-triggered conversation
compaction — not automatic (that's v0.15.0).
Depends on: utility model role (v0.10.0).
**Summarize & Continue**
- [x] "Summarize and continue" button in context warning bar (≥75% context)
- [x] `POST /channels/:id/summarize` — calls utility role with conversation history
- [x] Summary inserted as tree node with `metadata.type = "summary"` + boundary metadata
- [x] `loadConversation` recognizes summary boundaries: replaces pre-summary messages with summary system message
- [x] Multiple summaries stack (re-summarize after context fills again)
- [x] Fork-aware: summary is a tree node, branches have independent boundaries
- [x] Original messages preserved: "show full history" toggle reveals collapsed messages
- [x] Usage logged against utility role with cost tracking
**BYOK Role Overrides**
- [x] Personal role resolution chain: personal → team → global
- [x] User Settings → Model Roles tab (BYOK providers only)
- [x] Stored in `user.settings` JSONB (`model_roles` key, same shape as team overrides)
- [x] Only utility and embedding roles (generation removed)
**Generation Role Removal**
- [x] `RoleGeneration` removed from `ValidRoles` (was "generation" for image/media)
- [x] Removed from admin Roles tab UI
- [x] Image gen will be extension-managed (v0.11.0), not a role slot
**Utility Rate Limiting**
- [x] `utility_rate_limit` global setting (default: 20 calls/hour/user, 0 = unlimited)
- [x] Check against `usage_log` before executing org-funded utility calls
- [x] BYOK calls exempt (user's own key, user's own cost)
- [x] 429 response with clear message when exceeded
---
## v0.10.3 — Frontend Refactor ✅
Zero features. Split the two monolith frontend files (app.js 2,940 lines,
ui.js 2,582 lines) into 13 domain-scoped files averaging ~544 lines each.
Vanilla JS, no modules, no build step, no function renaming.
See [REFACTOR-0.10.3.md](REFACTOR-0.10.3.md) for full plan.
- [x] Extract `ui-format.js` — markdown, code blocks, `esc()`, helpers
- [x] Extract `ui-settings.js` — settings tabs, teams, providers, user prefs
- [x] Extract `ui-admin.js` — admin modal tabs, all admin rendering
- [x] Extract `tokens.js` — context tracking, token estimation
- [x] Extract `notes.js` — notes panel, editor, multi-select
- [x] Extract `chat.js` — chat ops, send, regen, edit, branch, summarize
- [x] Extract `settings-handlers.js` — settings save, provider CRUD, cmd palette
- [x] Extract `admin-handlers.js` — admin actions, presets, team management
- [x] Slim `app.js` to orchestrator — state, init, boot, auth, listener dispatch
- [x] Update `sw.js` SHELL_FILES, `index.html` script tags
- [x] All 159+ frontend tests pass, `node --check` on all files
---
## v0.10.4 — Model Type Pipeline + Role Fixes ✅
Provider APIs report model types (chat, embedding, image) — now captured
end-to-end through sync → catalog → resolver → frontend. Role dropdowns
filter models by type instead of showing everything.
- [x] Migration `005_model_type.sql``model_catalog.model_type` column
- [x] `providers.Model.Type` — captured from provider wire response
- [x] Venice: reads `type` field, normalizes (`text``chat`, `embedding``embedding`)
- [x] OpenAI: wire struct extended with optional `type` for compatible APIs
- [x] `CatalogSyncEntry.ModelType``CatalogEntry.ModelType``UserModel.ModelType`
- [x] Frontend `model_type` mapped through `fetchModels()`
- [x] Admin role dropdowns: filter by `model_type` matching role
- [x] User role dropdowns: filter by `model_type` matching role
- [x] `adminSaveRole()` reloads UI after save (was showing "Saved" without refresh)
---
## v0.10.5 — UI Primitives + Extension Surfaces ✅
Consolidate duplicated UI patterns into shared primitives with extension-ready
interfaces. Pre-extension infrastructure: the primitives we build now become
the `ctx.*` extension API surface in v0.11.0 with no rewrite needed.
- [x] `Providers` registry — single source of truth for types, labels, endpoints
- [x] `Roles` registry — names, type filters, hints
- [x] `renderCapBadges()` — consolidates 3 badge builders (compact + detailed modes)
- [x] `renderProviderForm()` — replaces 3 form implementations, dual-mode create/edit
- [x] `renderProviderList()` — replaces 3 list renderers, event delegation
- [x] `renderRoleConfig()` — replaces 2 role UIs + 4 handlers
- [x] `renderUsageDashboard()` — replaces 3 usage renderers (compact + full)
- [x] Admin provider form gets endpoint auto-fill (was missing)
- [x] Team provider form consolidated from 2 separate forms to 1 dual-mode
- [x] Removed ~360 lines of duplicated code + ~40 lines of static HTML
- [x] `showConfirm()` — styled confirm dialog replacing all 16 native `confirm()` calls
- [x] `.popup-menu` shared CSS + `createPopupMenu()` primitive for consistent menus
- [x] Sidebar collapse icon always visible (dimmed), brightens on hover
---
## ✅ v0.11.0 — Extension Foundation (Browser Tier)
The platform play. See [EXTENSIONS.md](EXTENSIONS.md) for full spec.
**Core Loader** (EXTENSIONS.md §4, §7)
- [x] `extensions.js` — loader, registry, scoped context, renderer pipeline (~400 lines)
- [x] `Extensions.register()` API with permission-aware scoped contexts
- [x] Manifest format: `_script` inline or `entry` file, permissions, settings schema
- [x] `extensions` + `extension_user_settings` tables (migration 006)
- [x] Admin endpoints: CRUD, enable/disable, asset serving
- [x] `/api/v1/extensions/:id/assets/*path` — public (no auth, `<script>` tag compatible)
- [x] Auto-seeder: scans `extensions/builtin/*/` on startup, upserts manifests
- [x] Admin Extensions tab with Edit button (view/modify manifest + script inline)
**Browser Tool Bridge** (EXTENSIONS.md §5)
- [x] `ctx.tools.handle()` API for browser tool registration
- [x] Tool schema collection: merge server + extension tools in completions
- [x] `tool.call.*` / `tool.result.*` WebSocket routing via EventBus hub
- [x] 30-second timeout with error recovery
- [x] `Events.waitFor()` pattern for request/response tool calls
**Custom Renderer API** (EXTENSIONS.md Appendix A)
- [x] `ctx.renderers.register()` — block, post, and inline types with pattern/match
- [x] Renderer pipeline: block renderers intercept code fences, post renderers process DOM
- [x] Case-insensitive language matching (LLMs capitalize fence tags)
- [x] Nested ```` ```markdown ```` fence unwrapping (LLMs wrap responses in markdown fences)
- [x] Extension-owned styles via `_injectStyles()` / `destroy()` lifecycle
**Server Tools** (auto-registered via `init()`)
- [x] `datetime` — current date/time/timezone/day/unix/ISO-week (models hallucinate dates)
- [x] `calculator` — recursive-descent evaluator: arithmetic, 17 functions, constants
**Built-in Browser Extensions** (6 total, self-contained with own CSS)
- [x] **Mermaid** — block + post renderer, SVG diagrams, dark mode, local vendor + CDN fallback
- [x] **KaTeX** — block renderer (` ```latex/math/tex ```) + post renderer (inline `$...$` / `$$...$$`)
- [x] **CSV Table** — block renderer, RFC 4180 parser, sortable columns, numeric-aware sorting
- [x] **Diff Viewer** — block renderer, red/green syntax highlighting, stats badge
- [x] **JS Sandbox** — browser tool (`js_eval`), sandboxed iframe, 10s timeout, console capture
- [x] **Regex Tester** — browser tool (`regex_test`), multi-input matching, named groups
**Bugfixes**
- [x] WebSocket token field mismatch (`tokens.access``tokens.accessToken`)
- [x] Extension asset 401 (moved asset route to public group — `<script>` tags can't send auth headers)
- [x] `runExtensionPostRender is not defined` (stale SW cache, added `typeof` guard)
- [x] Extension init order (moved `loadAll()`/`initAll()` before `loadChats()`)
- [x] K8s Ingress WebSocket routing (`pathType: Exact``Prefix` for Traefik longest-prefix-wins)
- [x] Notes missing post-render call (mermaid/KaTeX wouldn't render in note view)
**Diagnostics**
- [x] Test 5: Browser extensions (loaded count, active/inactive, renderers, tool handlers)
- [x] Test 6: Service Worker cache (registration, scope, state, cache names, entry counts)
- [x] Purge Cache button in Debug Log modal (deletes all SW caches, unregisters workers)
---
## v0.12.0 — File Handling + Vision
File input into chat — table stakes for serious use.
**Storage Backend**
- [x] S3-compatible API (MinIO, Ceph RGW, AWS — anything with S3 API)
- [x] Local PVC backend (single-node / dev)
- [x] Admin config: backend health status, orphan cleanup panel
- [x] Reused by KBs (v0.14.0) and compaction snapshots (v0.15.0)
**Chat Integration**
- [x] `attachments` table (metadata in PG, blobs in object store)
- [x] Image/file upload in chat messages (📎 button, drag-and-drop)
- [x] Multimodal message assembly for vision-capable models
- [x] Text extraction pipeline (PDF, DOCX, TXT — inline + sidecar modes)
- [x] Paste-to-upload (clipboard images + large text auto-attach)
- [x] Staged attachment strip with extraction status polling
- [x] Auth-aware rendering (blob URLs with Bearer token)
- [x] Image lightbox viewer
- [x] Vision capability hints on image attachments
- [ ] Document generation output storage (extension → attachment → download)
---
## v0.13.0 — Admin Panel Refactor
The admin modal has grown to 12 tabs in a horizontal overflow strip. Adding
search provider config, KB management, and workflow settings would make it
worse. Replace the modal with a fullscreen admin panel.
Depends on: nothing (frontend-only, no API changes).
**Layout** (modeled after Open WebUI's admin panel):
- Fullscreen overlay (or `/admin` route) replaces the modal
- Top bar: 4 category tabs (horizontal)
- Left sidebar: section tabs within the active category
- Main content: full viewport width for the active section
**Category → Section mapping:**
| Category | Sections | Current tabs absorbed |
|----------|----------|---------------------|
| **People** | Users, Teams | Users, Teams |
| **AI** | Providers, Models, Personas, Roles | Providers, Models, Presets, Roles |
| **System** | Settings, Storage, Extensions | Settings, Storage, Extensions |
| **Monitoring** | Usage, Audit, Stats | Usage, Audit, Stats |
- [x] Fullscreen admin layout: top categories + left sidebar sections
- [x] Migrate all 12 existing tab contents into new layout (no API changes)
- [x] Category → section routing with URL hash or state (e.g. `#admin/ai/models`)
- [x] Responsive: sidebar collapses on narrow viewports
- [x] Keyboard shortcut: `Esc` returns to chat
- [x] Admin button in sidebar opens fullscreen panel (replaces modal trigger)
- [ ] Remove old `adminModal` HTML + CSS
- [x] Preserve all existing `loadAdmin*()` functions — new layout calls same loaders
- [x] Roles moved from People → AI category
- [x] Presets → Personas label rename (UI only, API/data unchanged)
- [x] Banner-aware: admin panel respects `--banner-top/bottom-height`
- [x] CSS design token cleanup: ghost variables, hardcoded colors → tokens
- [x] Global form element theming (bare selects/inputs inherit theme)
- [x] Zoom scaling: admin panel + side panel respect appearance slider
- [x] CI fix: `TruncateAll()` includes `platform_policies` + `global_config`
---
## v0.13.1 — Web Search + URL Fetch
Built-in tools that extend the tool framework shipped in v0.11.0.
Admin configuration lives in the new admin panel (System → Settings or AI → Providers).
Depends on: admin panel refactor (v0.13.0) for config UI, tool framework (v0.11.0).
**Search provider abstraction:**
- [x] `SearchProvider` interface: `Search(ctx, query, maxResults) → []SearchResult`
- [x] DuckDuckGo provider (no API key required — default, works out of the box)
- [x] SearXNG provider (self-hosted, fits airgapped story, configurable endpoint)
- [x] Admin config: select active provider, set endpoint/API key, result count
- [x] Provider config stored in `global_config` as `search_config` key
- [x] Runtime apply on save (no restart required) + load on startup
**Tools:**
- [x] `web_search` tool: calls active search provider, returns title + snippet + URL
- [x] `url_fetch` tool: HTTP GET + content extraction (HTML→text, SSRF protection)
- [x] Results injected into context as tool results (existing tool framework handles this)
- [ ] Rate limiting / concurrency control per-provider
**Tool categories + toggle:**
- [x] `ToolDef.Category` field: search, notes, utilities, browser
- [x] `disabled_tools[]` per-request filtering (completion + regen + edit paths)
- [x] `GET /api/v1/tools` endpoint: returns `[{name, description, category}]`
- [x] Chat-bar tools toggle popup: category-level enable/disable
- [x] localStorage persistence of disabled state
- [x] `ToolDef.DisplayName` field: human-readable name for UI (e.g. "Search" not "web_search")
- [x] Sub-menu per category: expand-in-place to show individual tools
- [x] Tri-state category checkbox: all on / all off / indeterminate (partial)
- [x] Per-tool toggle within categories (child checkboxes)
**Future providers** (abstraction supports but not in v0.13.1):
- Brave Search (API key), Tavily, Kagi, SerpAPI, Google PSE
---
## v0.14.0 — Knowledge Bases
RAG for Switchboard. Upload documents into named knowledge bases, chunk
and embed via the embedding model role, store vectors in pgvector, and
a `kb_search` tool lets the LLM pull relevant context at completion time.
Depends on: embedding model role (v0.10.0), file storage (v0.12.0),
tool framework (v0.11.0), admin panel (v0.13.0).
See [DESIGN-0.14.0.md](DESIGN-0.14.0.md) for full spec.
**Pre-Req: Embedding Dropdown Fix**
- [ ] Tolerant type filter in `renderRoleConfig()` (include untyped models for embedding role)
- [ ] Manual model ID entry fallback (pencil toggle: dropdown ↔ text input)
- [ ] Auto-switch to manual entry when dropdown has zero options
**Phase 1: Schema + Chunker**
- [x] Migration `008_knowledge_bases.sql` (pgvector extension + tables)
- [x] `pgvector-go` dependency in `go.mod`
- [x] `knowledge/chunker.go` — recursive text splitter (configurable size/overlap/separators)
- [x] `knowledge/chunker_test.go`
- [x] `KnowledgeBase`, `KBDocument`, `KBChunk`, `KBSearchResult`, `ChannelKB` model types
- [x] `KnowledgeBaseStore` interface + postgres implementation (CRUD, no vector ops yet)
- [x] Wire `Stores.KnowledgeBases` in `main.go`
**Phase 2: Ingestion Pipeline**
- [x] `knowledge/embedder.go` — batch embed via role resolver (batches of 100)
- [x] `knowledge/ingest.go` — orchestrator (extract → chunk → embed → store)
- [x] Dimension normalization (zero-pad to 3072 column width)
- [x] `handlers/knowledge_bases.go` — KB CRUD + document upload endpoints
- [x] Async ingestion goroutine with semaphore(3) concurrency limit
- [x] Document status polling endpoint
- [x] `SimilaritySearch()` in store (pgvector cosine query)
- [x] Admin panel: AI → Knowledge Bases section
- [x] IVFFlat index creation
**Phase 3: kb_search Tool + Channel Integration**
- [x] `tools/kbsearch.go``kb_search` tool (closure injection, late registration)
- [x] `channel_knowledge_bases` — per-channel KB toggle
- [x] System prompt KB hint injection in completion handler
- [x] KB toggle popup in chat input bar
- [x] "knowledge" category in tools toggle menu
- [x] Team KB endpoints + team management UI tab
- [x] Personal KB endpoints + settings UI
**Phase 4: Notes + Polish**
- [x] Notes `embedding` column (`vector(3072)`)
- [x] Semantic note search (`note_search` tool `semantic: true` param)
- [x] KB rebuild endpoint (re-chunk + re-embed all docs)
- [x] Audit logging for KB operations
- [x] Usage tracking for embedding calls (`role = 'embedding'`)
- [x] Integration tests
---
## v0.15.0 — Compaction
Depends on: utility model role (v0.10.0).
- [ ] Auto-compaction service: background job that calls utility model to summarize
- [ ] Channel-scoped: triggers when context exceeds threshold
- [ ] Summaries stored as system messages
- [ ] Configurable: per-channel opt-in/out, summary model override
- [ ] Admin controls for resource limits
---
## v0.15.1 — Context Recall Tools
Depends on: file handling (v0.12.0), tool framework (v0.11.0).
Attachments are injected into context once (at send time) and are not
replayed on subsequent turns — the model's own response acts as a natural
summary. After compaction (v0.15.0) or in long conversations, the model
may need to re-read source material. These tools bridge that gap.
**`attachment_recall` tool**
- [ ] `attachment_recall(attachment_id)` — re-reads attachment content from storage
- [ ] Images: re-inject as base64 content part (vision gating still applies)
- [ ] Documents: return extracted text
- [ ] Security: scoped to current channel's attachments only
- [ ] Extend `ExecutionContext` with optional storage backend reference
- [ ] Tool definition includes attachment list hint so model knows what's available
**`conversation_search` tool**
- [ ] Keyword search over compacted/archived messages in current channel
- [ ] Returns matching message excerpts with timestamps
- [ ] Useful post-compaction when summary dropped details the user asks about
**Token estimator improvements**
- [ ] Include attachment token estimates in context counter
- [ ] Images: use provider-specific sizing rules (e.g. tile-based for OpenAI)
- [ ] Documents: count extracted text tokens
- [ ] Staged attachments reflected in input token counter before send
**KB auto-injection** (depends on: knowledge bases v0.14.0, compaction v0.15.0)
- [ ] Pre-pend top-K KB results to system prompt (automatic, no tool call needed)
- [ ] Context budget aware: skip injection if context already near limit
- [ ] Per-channel toggle: tool-only vs auto-inject vs both
---
## v0.16.0 — User Groups + Resource Grants
The missing access-control primitive. Teams define organizational
structure (horizontal visibility). Roles define vertical permissions
(admin/user). **Groups** are pure access-control lists that decouple
resource access from team membership.
Depends on: teams (v0.10.0), knowledge bases (v0.14.0).
**Groups Entity**
- [ ] `groups` table: `id`, `name`, `description`, `scope` (global, team), `team_id` (nullable — team-scoped groups), `created_by`, `created_at`, `updated_at`
- [ ] `group_members` table: `group_id`, `user_id`, `added_by`, `added_at`
- [ ] A user can belong to 0 or more groups
- [ ] Groups can span multiple teams (global scope) or be team-internal
- [ ] Admin CRUD: create/delete groups, add/remove members
- [ ] Team admin can manage team-scoped groups
**Resource Grant Model**
- [ ] `resource_grants` table: `resource_type` (persona, knowledge_base, project), `resource_id`, `grant_scope` (team_only, global, groups), `granted_groups` (UUID[]), `created_by`
- [ ] Extends existing `persona_grants` pattern to all resource types
- [ ] Three-way access: `team_only` (existing team members), `global` (all authenticated), `groups` (specific group list)
- [ ] Grant resolution: check user's group memberships against resource grants
- [ ] Backward compatible: existing team/global scoping continues to work, groups are additive
**API Endpoints**
- [ ] CRUD: `/api/v1/admin/groups`, `/api/v1/teams/:teamId/groups`
- [ ] Membership: `POST/DELETE /api/v1/groups/:id/members`
- [ ] Grant management on resources: `PUT /api/v1/knowledge-bases/:id/grants`, `PUT /api/v1/presets/:id/grants`
- [ ] User-facing: `GET /api/v1/groups/mine` (groups I belong to)
**Frontend**
- [ ] Admin panel: Groups section (CRUD, member management)
- [ ] Team admin: group management tab
- [ ] Resource grant picker: when creating/editing a Persona or KB, choose Team Only / Global / Select Groups
---
## v0.17.0 — Persona-KB Binding + Enterprise KB Mode
Personas become **gateways** to knowledge. In enterprise deployments,
users don't interact with KBs directly — they talk to Personas that
have KBs attached. This is the core differentiator for enterprise use.
Depends on: knowledge bases (v0.14.0), user groups (v0.16.0).
**Persona-KB Binding**
- [ ] `persona_knowledge_bases` table: `persona_id`, `kb_id`, `auto_search` (bool — always search vs tool-only)
- [ ] When a channel uses a Persona with bound KBs, `kb_search` is auto-scoped to those KBs
- [ ] No user toggle needed — Persona carries its own KB context
- [ ] Channel KB toggle becomes "additional KBs" on top of Persona-provided ones
- [ ] Persona system prompt auto-includes KB listing hint (reuses `BuildKBHint` pattern)
- [ ] Admin/team admin UI: KB picker on Persona create/edit form
**Enterprise KB Mode**
- [ ] `discoverable` flag on knowledge_bases: `true` (default — appears in user's KB popup) or `false` (hidden, only accessible through Persona binding)
- [ ] Hidden KBs don't appear in `GET /api/v1/knowledge-bases` for non-admins
- [ ] Hidden KBs still searchable by `kb_search` tool when accessed through a Persona
- [ ] Admin panel: toggle discoverability per KB
- [ ] Platform policy: `kb_direct_access` — when `false`, disables channel KB popup entirely (strict enterprise mode)
**Access Control Integration**
- [ ] Persona grants (v0.16.0 groups) control who can *use* a Persona
- [ ] KB grants control who can *manage* a KB (upload/delete docs)
- [ ] Persona-KB binding grants implicit *search* access through the Persona
- [ ] Users who can use a Persona can search its KBs, even if they can't see those KBs directly
**Team Admin Workflow**
- [ ] Team admin creates KB → uploads documents → marks non-discoverable
- [ ] Team admin creates Persona → binds KBs → sets access to Team Only or specific Groups
- [ ] Team members see the Persona in their preset list, use it, get KB-powered answers
- [ ] Team members never see or manage the underlying KBs
---
## v0.18.0 — Memory (User + Persona Scopes)
Long-term memory across conversations, with scope-aware isolation.
Unlike KB (static documents) or compaction (within-conversation), this
captures facts and preferences that persist across channels.
**Key differentiator: Persona-scoped memory.** Each Persona can build
its own memory independently of user memory, preventing cross-context
contamination and enabling specialized knowledge accumulation.
Depends on: compaction (v0.15.0), knowledge bases (v0.14.0), persona-KB binding (v0.17.0).
**Memory Scopes**
- [ ] `user` — personal facts/preferences, persists across all conversations (current industry standard)
- [ ] `persona` — shared across all users of that Persona, accumulated from conversations (differentiator)
- [ ] `persona+user` — per-user within a Persona context (e.g. tutoring progress per student)
- [ ] Configurable per Persona: which scopes are active, whether user memory is passed through
**Data Model**
- [ ] `memories` table: `id`, `scope` (user, persona, persona_user), `owner_id` (user_id or persona_id), `user_id` (nullable — for persona+user scope), `key`, `value`, `source_channel_id`, `confidence` (float), `status` (active, pending_review, archived), `created_at`, `updated_at`
- [ ] Index on `(scope, owner_id, user_id)` for fast lookup
- [ ] Embedding column (`vector(3072)`) for semantic memory search
**Tools**
- [ ] `memory_save` tool: LLM explicitly stores a fact
- Scope-aware: saves to the active memory scope for the current Persona context
- Structured: `key` (short label) + `value` (detail) + `confidence`
- [ ] `memory_recall` tool: LLM queries stored facts relevant to current context
- Merges results from applicable scopes (user + persona + persona+user)
- Semantic search via embeddings + keyword fallback
**Automatic Extraction**
- [ ] Background job: post-conversation analysis identifies memorable facts (opt-in)
- [ ] Configurable extraction prompt per Persona (e.g. "extract FAQ-worthy Q&A pairs")
- [ ] Extracted memories start in `pending_review` status
**Review Pipeline**
- [ ] Admin/team admin review queue: pending memories with approve/reject/edit
- [ ] Persona memory review: team admins see what Personas are "learning"
- [ ] User memory review: users see their own memories in Settings
**Memory Injection**
- [ ] At completion time: inject relevant memories into system prompt
- [ ] Context budget aware: limit injected memory tokens
- [ ] Scope priority: persona+user > persona > user (most specific wins on conflicts)
**Use Cases**
- [ ] Helpdesk Persona: builds FAQ from repeated questions, human-reviewable
- [ ] Roleplay Persona: isolated memory prevents tone/context bleeding from user's other conversations
- [ ] Tutoring Persona: tracks per-student progress, common misconceptions
- [ ] Sales Persona: accumulates objection responses from real conversations
- [ ] Onboarding Persona: learns what new hires commonly struggle with
**User Controls**
- [ ] Settings → Memory: view, edit, delete personal memories
- [ ] Per-Persona memory toggle: "Allow this persona to remember things about me"
- [ ] Admin: enable/disable, retention policies, per-team scoping
- [ ] Privacy: user memories never cross team boundaries; persona memories respect group access
---
## v0.19.0 — Projects / Workspaces
Organizational containers that group related conversations, KBs, and
notes into a single workspace with shared access controls.
Depends on: user groups (v0.16.0), knowledge bases (v0.14.0).
**Data Model**
- [ ] `projects` table: `id`, `name`, `description`, `scope` (personal, team, global), `owner_id`, `team_id`, `settings` (JSONB), `created_at`, `updated_at`
- [ ] `project_resources` table: `project_id`, `resource_type` (channel, knowledge_base, note), `resource_id`, `added_at`
- [ ] Projects use the same grant model (v0.16.0): team_only / global / groups
**Features**
- [ ] Channels can belong to a project (optional `project_id` FK)
- [ ] KBs can belong to a project → auto-linked to all channels in the project
- [ ] Notes can belong to a project → visible to all project members
- [ ] Project-level Persona default: all new channels in the project start with this Persona
- [ ] Project sidebar section: grouped view of conversations with folder-like nesting
**UI**
- [ ] Sidebar: projects as collapsible groups above/integrated with chat history
- [ ] Project creation dialog (name, description, default Persona, default KBs)
- [ ] Drag channels/notes into projects
- [ ] Folders within projects (lightweight — just a `path` column on project_resources)
- [ ] Project settings: manage resources, access, defaults
**Enterprise Use**
- [ ] Team admins create projects for their teams
- [ ] Project templates: pre-configured with specific Personas, KBs, and settings
- [ ] Cross-team projects via group grants
---
## v0.20.0 — @mention Routing + Multi-model
The channel schema already supports multiple models. This adds routing.
_(Shifted from v0.17.0 — no content changes)_
- [ ] @mention parsing in messages (users and AI models)
- [ ] Resolve mentions against `channel_models`
- [ ] Multi-model channels: fire completions per mentioned model
- [ ] "Add a model" UI per channel
- [ ] Enables: second opinions, cross-model conversations
---
## v0.21.0 — Extension Surfaces + Modes
Depends on: extension foundation (v0.11.0).
See [EXTENSIONS.md §6](EXTENSIONS.md#6-surfaces-modes).
_(Shifted from v0.18.0 — no content changes)_
- [ ] Surface registration and activation API
- [ ] Mode selector in sidebar (below brand, above chat history)
- [ ] `ctx.ui.replace()` / `ctx.ui.restore()` for region management
- [ ] `surface.activated` / `surface.deactivated` events
- [ ] Editor mode (extension: file tree + code editor + AI chat split pane)
- [ ] Article mode (extension: outline + rich text + AI assistant panel)
---
## v0.22.0 — Smart Routing
Depends on: model roles (v0.10.0), usage tracking (v0.10.0).
Rules-based routing engine — policy, not ML.
_(Shifted from v0.19.0 — no content changes)_
- [ ] Admin routing policies: "cheapest model with required capabilities",
"prefer private providers, fallback to cloud", "for team X, use Y"
- [ ] Fallback chains: primary provider down → next with same model
- [ ] Cost-aware: factor pricing into routing decisions
- [ ] Latency-aware: track response times per provider, prefer faster
---
## v0.23.0 — Multi-Participant Channels + Presence
The channel foundation for workflows and real-time collaboration. Today
channels are single-user direct chats. This release makes channels a
shared space that multiple actors can inhabit.
Depends on: extension surfaces (v0.21.0), WebSocket infrastructure (already exists).
_(Shifted from v0.20.0 — no content changes)_
**Channel Participants**
- [ ] `channel_participants` table: `channel_id`, `participant_type` (user, session, persona), `participant_id`, `role` (owner, member, observer), `joined_at`
- [ ] Channel access queries rewritten: `WHERE user_id = $1` → participant-based lookup
- [ ] Backward compatible: existing single-user channels auto-have one participant (owner)
- [ ] Channel `type` column: `direct` (current), `group` (multi-user), `workflow` (staged)
**Presence + Real-time**
- [ ] Typing indicators and presence (who's online, who's in this channel)
- [ ] Participant join/leave events via WebSocket
- [ ] Co-editing for extension surfaces (editor mode, article mode)
- [ ] Cursor sharing, selection highlighting
**Channel-Scoped Notes**
- [ ] Optional `channel_id` FK on notes (NULL = personal notebook, set = channel-attached)
- [ ] Notes created by tool calls within a channel auto-attach to that channel
- [ ] Channel notes visible to all participants (not just creator)
---
## v0.24.0 — Auth Strategy (mTLS/OIDC) + Full RBAC
Enterprise auth modes on top of the teams + groups foundation.
Depends on: vault passphrase support from v0.9.3 (conditional unlock for
personal providers under external auth), user groups (v0.16.0).
_(Shifted from v0.21.0 — enhanced with group-based permissions)_
- [ ] `AUTH_MODE` env var: `builtin` | `mtls` | `oidc`
- [ ] All three resolve to the same internal user model
- [ ] `auth_source` + `external_id` columns on users table
- [ ] mTLS: header trust, auto-provision from cert DN
- [ ] OIDC: Keycloak/Okta token validation, claim extraction, role mapping
- [ ] Per-source auto-activate policy: auto_activate, default_team, default_role
- [ ] Vault passphrase prompt for mTLS/OIDC users (v0.9.3 conditional unlock)
- [ ] Fine-grained permissions: model access, KB write, task create, token budgets
- [ ] Group-based permission sets: assign permissions to groups, users inherit from membership
- [ ] OIDC claim → group mapping: external IdP groups auto-sync to internal groups
**Anonymous / Session Participants**
- [ ] Session-based identity for unauthenticated visitors (workflow intake)
- [ ] mTLS: cert CN/fingerprint as stable session identity (no `users` row required)
- [ ] Non-mTLS: ephemeral session token (cookie or URL token)
- [ ] Session participants can send messages and trigger tool calls within workflow channels
- [ ] Session identity visible to assigned team members (cert DN, display name, or "Anonymous #N")
---
## v0.25.0 — Workflow Engine
Team-owned, stage-based process execution. The channel is the runtime,
personas drive each stage, and the existing tool/notes infrastructure
handles structured data collection. See [ARCHITECTURE.md — Workflow
Architecture](ARCHITECTURE.md#workflow-architecture-future--v0210) for
the conceptual model.
Depends on: multi-participant channels (v0.23.0), anonymous identity (v0.24.0).
_(Shifted from v0.22.0 — no content changes, dependency refs updated)_
**Workflow Definitions**
- [ ] `workflows` table: `team_id` (owner), `name`, `description`, `is_active`, `entry_mode` (public_link, team_internal, api)
- [ ] `workflow_stages` table: `workflow_id`, `ordinal`, `persona_id`, `assignment_team_id`, `form_template` (JSONB, structured note schema), `transition_rules` (JSONB)
- [ ] Team-admin CRUD: create/edit/delete workflows and stages
- [ ] Workflow versioning: edits create new version, active instances continue on their version
**Workflow Instances (Channels)**
- [ ] Workflow-typed channels: `type='workflow'`, `workflow_id`, `current_stage`, `stage_version`
- [ ] Entry point: public link generates workflow channel, adds anonymous participant, binds stage 1 persona
- [ ] Stage transitions: triggered by AI (tool call), team member (button), or rule (form complete)
- [ ] On transition: swap active persona, notify assignment team, update channel metadata
**AI Intake**
- [ ] Stage persona drives structured collection via system prompt
- [ ] Form template → persona system prompt injection ("collect these fields: ...")
- [ ] Tool calls create channel-scoped notes as form responses
- [ ] AI determines readiness: "I have everything needed" → trigger transition
**Assignment + Queue**
- [ ] Assignment queue: unassigned workflow channels visible to team members
- [ ] Claim model: team member claims channel (becomes participant with `member` role)
- [ ] Round-robin / rule-based auto-assignment (optional, per-stage config)
- [ ] Assignment notifications via WebSocket + optional webhook
**Team Member Collaboration**
- [ ] Assigned member sees full history (AI intake + artifacts + notes)
- [ ] Persona remains active — assists both visitor and team member
- [ ] Member can trigger stage transitions, add notes, invoke tools
- [ ] Member can reassign to different team member or escalate to different team
**Frontend**
- [ ] Workflow builder UI in team admin panel (stage list, persona picker, form template editor)
- [ ] Queue view: sidebar section showing unassigned workflow channels for user's teams
- [ ] Channel header: workflow stage indicator, transition controls
- [ ] Anonymous visitor view: minimal UI, persona-driven conversation (public link entry)
---
## v0.26.0 — Tasks / Autonomous Agents
Unattended execution — workflows without a human in the loop.
Depends on: workflow engine (v0.25.0).
_(Shifted from v0.23.0 — no content changes, dependency refs updated)_
- [ ] Scheduler + task runner (cron-like triggers for workflow instantiation)
- [ ] `task_create` tool (AI can spawn sub-workflows)
- [ ] `type: 'service'` channels: workflow instances with no human participants
- [ ] Execution budgets: max tokens, max tool calls, max wall-clock per stage
- [ ] Admin controls for resource limits and kill switches
- [ ] Completion webhooks (notify external systems when workflow completes)
---
## TBD (unscheduled — real features, no immediate need)
Items that are real but don't yet have a version assignment. Pull left
based on need.
**Extension System — Additional Extensions**
- Image generation tool (browser or sidecar, provider-agnostic)
- STT/TTS (browser extension, Web Speech API — personal use case)
- Code execution sandbox (server-side, container isolation)
**Extension System — Server Tiers**
- Starlark runtime integration (Tier 1 — server sandbox)
- Sidecar HTTP tool protocol (Tier 2 — container isolation)
- Server-side tool execution in completion handler
**Desktop + Mobile**
- Desktop app (Tauri)
- Full PWA with offline capability
- Mobile-optimized layouts (beyond current responsive)
**Data + Portability**
- Bulk export/import (account data, conversations, settings)
- ChatGPT/other tool import
- GDPR-style "download my data"
- Backup/restore CronJob manifests
**UX / Multi-Seat**
- ~~Per-chat model/preset persistence (server-side)~~ ✅ _(v0.12.0 — `channels.settings.last_selector_id` JSONB merge, localStorage write-through cache)_
**Knowledge Bases — Future**
- Hybrid search: combine vector similarity with full-text `tsvector`, re-rank
- Semantic chunking: embedding-based boundary detection for smarter splits
- HNSW index: better query performance than IVFFlat for large datasets
- Web scraping source: ingest URLs as KB documents (extends url_fetch)
- Scheduled re-indexing: periodic rebuild when source documents update
- ~~KB sharing: cross-team access grants~~ → subsumed by User Groups (v0.16.0) + Resource Grants
- Store cleanup: add `UpdateDocumentStorageKey()` to `KnowledgeBaseStore` interface
(currently uses direct `database.DB.ExecContext` in handler — works but bypasses store layer)
**Memory — Future**
- Memory compaction: summarize old memories to save context tokens
- Memory confidence decay: reduce confidence over time, prune low-confidence entries
- Memory export/import: portable memory format across instances
- Cross-persona memory sharing (opt-in): e.g. "coding assistant" can read facts from "project manager"
- Memory analytics: dashboard showing what Personas are learning, memory growth trends
**Platform**
- Rate limiting per user/team/tier (token budgets)
- Provider health monitoring + key rotation
- Multi-tenant SaaS mode
- Plugin/extension marketplace
- Virtual scroll for long conversations
- SQLite backend option (single-user / dev)