87 KiB
Roadmap — Chat Switchboard
See also:
- ARCHITECTURE.md — Core services design, store layer, scope model
- EXTENSIONS.md — Extension system spec (Browser/Starlark/Sidecar tiers, manifests, browser tool bridge, surfaces/modes, model roles)
- CHANGELOG.md — Detailed release notes for all completed versions
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
│ + QOL / UX Wins
└───────┬──────────────┘
│
┌───────┴──────────────┐
│ │
v0.17.1 SQLite Backend ✅ v0.17.2 CodeMirror 6 ✅
v0.17.3 Notes Graph + Wikilinks ✅
(dual DB) (editor bundle, chat
│ input, ext editor)
└───────┬──────────────┘
│
v0.18.0 Memory ✅ (user + persona scopes, review pipeline)
│
v0.18.1 Side Panel Architecture ✅ (single-slot, ctx.ui primitives, mermaid refactor)
│
v0.19.0 Projects / Workspaces ✅ (project groups, KB resolution, drag-drop sidebar)
│
v0.19.1 Active Project + System Prompt + Detail Panel ✅
│
v0.19.2 Project Persona Default + Archive + Reorder ✅
│
v0.20.0 Notifications + @mention Routing + Multi-model ✅
│
v0.21.0 Workspace Storage Primitive ✅
│
┌───────┴──────────────┐
│ │
v0.21.1 Workspace ✅ v0.21.3 Surface Infra ✅
Tools + Bindings + REPL (parallel)
│ │
v0.21.2 Workspace ✅ v0.21.5 Editor Surface ✅
Indexing + Search (Development Mode)
│ │
v0.21.4 Git ✅ v0.21.6 Editor Surface ✅
Integration + Document Output
└───────┬──────────────┘
│
v0.21.7 Bugfix: scanJSON driver buffer aliasing ✅
│
v0.22.0 Provider Health + Capability Overrides ✅
+ Workspace Pane Refactor (FE)
│
v0.22.1 Provider Extensions (declarative config) ✅
│
v0.22.2 Routing Policies + Fallback Chains ✅
│
v0.22.3 Provider Admin UI + Deferred Polish ✅
│
v0.22.4 Provider Health UX + Project Files + Export ✅
│
v0.22.5 Surfaces + Go Templates + Admin Bug Fixes ✅
│
v0.22.6 Split Deployment Fix + CI Timeout + Code Pruning ✅
│
v0.22.7 Surface Prototypes (Theme + ChatPane + Splash) ✅
│
v0.22.8 Surface Integration (Wire into existing JS) ✅
│
v0.23.0 @mention Routing + Persona Handles + Proxy ✅
│
v0.23.1 Multi-User Navigation + Conversation Taxonomy ✅
│
v0.23.2 Multi-User Polish + Channel Lifecycle ✅
│
v0.24.0 Auth Abstraction + User Identity ← active
│
├── v0.24.1 mTLS + OIDC Providers (parallel)
│ │
│ └── v0.24.3 Anonymous Sessions
│
└── v0.24.2 Fine-Grained Permissions (parallel)
│
┌───────┴──────────────┐
│ │
v0.25.0 Workflow v0.26.0 Tasks /
Engine Autonomous Agents
(team-owned, (service channels,
staged processes, scheduler, unattended
human + AI collab) execution)
Completed Releases
Full details for all completed versions are in CHANGELOG.md.
✅ v0.9.0 — Schema Consolidation + BYOK
21 migrations → single schema, store layer abstraction, persona-as-trust-boundary, BYOK with auto-fetch, composite model IDs, journey integration tests.
✅ v0.9.1 — Capability Architecture + Docs
Removed static known model table. Resolution chain: catalog → heuristic only. Frontend relies solely on backend for capabilities. Docs rewrite.
✅ v0.9.2 — Quick UX Wins + Hardening
Collapsible code blocks, HTML preview, token counter, context warning, proxy interception detection, environment injection, team admin audit scoping.
✅ v0.9.3 — Content Visibility & Block Controls
Button-driven code collapse, always-rendered thinking blocks, tool call
persistence in history, Notes panel → unified side panel, streaming refactor
(streamWithToolLoop), regenerate fix.
✅ v0.9.4 — API Key Encryption + Vault
Two-tier AES-256-GCM: env-var key for global/team, per-user UEK (Argon2id)
for personal BYOK. server/crypto/ package. Migration 003. Startup backfill.
DOMPurify strict allowlist (XSS fix).
✅ v0.10.0 — Model Roles + Usage Tracking + Vault Debt
Named role slots (utility, embedding) with fallback. Provider.Embed() interface.
usage_log + model_pricing tables. Streaming token capture. Vault debt:
UEK re-wrap on password change, destruction on admin reset.
✅ v0.10.1 — Polish + UX
Admin system prompt injection, Personas tab, Team Management modal extraction, preview pane enhancements, code download, personal usage scoped to BYOK.
✅ v0.10.2 — Summarize & Continue + Role Cleanup
User-triggered conversation compaction via utility role. Summary as tree node with boundary metadata. BYOK role overrides (personal → team → global). Utility rate limiting. Generation role removed.
✅ v0.10.3 — Frontend Refactor
2 monolith files → 13 domain-scoped files (~544 lines avg). Zero features, no function renames. Vanilla JS, no build step. See REFACTOR-0.10.3.md.
✅ v0.10.4 — Model Type Pipeline + Role Fixes
model_type (chat/embedding/image) captured end-to-end from provider API
through catalog to frontend. Role dropdowns filter by type.
✅ v0.10.5 — UI Primitives + Extension Surfaces
Shared Providers/Roles registries, 6 consolidated render primitives,
showConfirm() replacing native dialogs, .popup-menu CSS primitive.
Removed ~400 lines of duplication.
✅ v0.11.0 — Extension Foundation (Browser Tier)
Full extension lifecycle: manifest, loader, scoped ctx.* API, browser tool
bridge via WebSocket. Custom renderer pipeline. 2 server tools (calculator,
datetime), 6 built-in browser extensions (Mermaid, KaTeX, CSV, Diff, JS
Sandbox, Regex). See EXTENSIONS.md.
✅ v0.12.0 — File Handling + Vision
S3/PVC storage backend, image/file upload (📎, drag-drop, paste), multimodal
message assembly, text extraction pipeline (PDF/DOCX/XLSX/PPTX/ODT/RTF),
vault CLI (rekey/status), per-chat model persistence.
✅ v0.13.0 — Admin Panel Refactor
12-tab modal → fullscreen admin panel. 4 categories (People, AI, System, Monitoring) × section sidebar. URL-based routing, responsive, banner-aware. CSS design token cleanup.
✅ v0.13.1 — Web Search + URL Fetch
web_search + url_fetch tools. Search provider abstraction (DuckDuckGo,
SearXNG). Tool categories with per-tool toggle UI in chat bar.
✅ v0.14.0 — Knowledge Bases
RAG: upload → chunk → embed (pgvector) → kb_search tool. Channel KB toggle.
Team/personal KB scopes. Notes semantic search. Admin panel KB management.
See DESIGN-0.14.0.md.
✅ v0.15.0 — Compaction
Background scanner with configurable thresholds. Automatic summarization via utility role. Per-channel opt-in/out. Context budget guard rail (80% ceiling).
✅ v0.15.1 — Context Recall Tools
attachment_recall (list + read, channel-scoped), conversation_search
(full-text via plainto_tsquery). Token estimator attachment awareness.
✅ v0.16.0 — User Groups + Resource Grants
Groups (global/team-scoped ACLs) decoupled from team membership. Three-way
resource grants (team_only/global/groups) for Personas and KBs. Grant picker
UI. Schema consolidation: 9 migrations → single 001_v016_schema.sql.
v0.17.0 — Persona-KB Binding + Enterprise KB Mode + QOL ✅
Personas become gateways to knowledge. In enterprise deployments, users don't interact with KBs directly — they talk to Personas that have KBs attached. Plus quality-of-life improvements: chat rename, token display, state persistence, utility model auto-naming.
Depends on: knowledge bases (v0.14.0), user groups (v0.16.0).
Persona-KB Binding
persona_knowledge_basestable:persona_id,kb_id,auto_search(bool — always search vs tool-only)- When a channel uses a Persona with bound KBs,
kb_searchis 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
BuildKBHintpattern) - Admin/team admin UI: KB picker on Persona create/edit form
Enterprise KB Mode
discoverableflag on knowledge_bases:true(default — appears in user's KB popup) orfalse(hidden, only accessible through Persona binding)- Hidden KBs don't appear in
GET /api/v1/knowledge-bases-discoverablefor non-admins - Hidden KBs still searchable by
kb_searchtool when accessed through a Persona - Admin panel: toggle discoverability per KB
- Platform policy:
kb_direct_access— whenfalse, 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
Technical Debt (carried forward)
- Security: KB create handler authorization — team/global scope verified
- Test hygiene: stripped DIAG diagnostics from
TestGroupBasedPersonaAccess - Architecture:
KnowledgeBaseStore.UpdateDocumentStorageKey()— moved to store layer - UX: Embedding dropdown fix — tolerant type filter, manual model ID fallback, auto-switch on empty (already implemented in
ui-primitives.js) - Minor: Paste-to-file character threshold — synced from backend
PublicSettings(storage.paste_to_file_chars), admin-configurable, default 2000
Role Fallback Alerts (#69)
- Audit log entry on fallback fire:
action = "role.fallback"with primary/fallback model IDs and error string role.fallbackWebSocket event via EventBus (PublishAsync, zero latency impact on completion path)- In-memory cooldown on
Resolver(per-role, 5-minute TTL) to deduplicate repeated fallback alerts - Admin UI: persistent alert banner on
role.fallbackevent (persists until dismissed) - Wire
*events.Busintoroles.NewResolvervia.WithBus()builder
QOL / UX Wins
- Chat rename: inline edit on sidebar item — dblclick title → input → blur/Enter saves via
API.updateChannel(id, { title }) - Chat token count: display
Tokens.estimateConversation()in model bar, update onselectChatand after each message - State restore on refresh: persist
App.currentChatIdtosessionStorageon select, restore on load - Utility model auto-naming: after first assistant response, background utility role request →
POST /channels/:id/generate-title→ re-render sidebar. Fallback to truncation when no utility model configured. Debounced via_autoNamePendingset - SW extension cache fix: exclude
/extensions/from service worker fetch handler - Mermaid renderer: promoted enhanced version (v2.0 — pan/zoom, SVG/PNG export, source copy) to
extensions/builtin/mermaid-renderer/
v0.17.1 — SQLite Backend ✅
Dual-database support. Every subsequent schema change is developed against both Postgres and SQLite from day one — no retrofit. SQLite enables single-binary deployment for dev, demo, edge, and single-user.
Depends on: v0.17.0 DB debt cleanup (store layer is sole DB interface).
Store Layer
- 19 SQLite store implementations covering all domain interfaces
DB_DRIVERenv var:postgres(default) |sqlite- Separate migration files per driver (
migrations/sqlite/) - WAL mode + single-writer connection pooling for SQLite concurrency
- CI matrix: full handler integration tests against both Postgres and SQLite
Vector Search
- App-level cosine similarity in Go (pure Go, no CGO/sqlite-vec required)
- Embeddings stored as JSON text in
kb_chunks.embeddingcolumn - Full feature parity with pgvector:
SimilaritySearch,InsertChunks - Graceful: adequate performance for SQLite-scale deployments (single-user, not millions of chunks)
Schema Compatibility
- UUID generation: application-side
store.NewID()(Gouuid.New()) - JSONB → JSON text columns with
ToJSON()/ScanJSON()helpers UUID[]arrays → JSON arrays withToJSONArray()/ScanJSONArray()TIMESTAMPTZ→ SQLiteTEXTwithdatetime('now')ON CONFLICT excluded.*pattern (works in both dialects)INSERT RETURNING id→ separate query pattern for SQLite
Test Infrastructure
dialectSQL()converts$N→?and strips::jsonbat runtimedatabase.PH(n)returns dialect-appropriate placeholderdatabase.TruncateAll()dialect-aware (DELETE + PRAGMA vs TRUNCATE CASCADE)- Generic provider config:
PROVIDER/PROVIDER_KEY/PROVIDER_URL(replacesVENICE_API_KEY) - Model selection prefers non-reasoning models in live tests
What SQLite mode skips (feature-gated, not broken):
LISTEN/NOTIFY— WebSocket event fan-out uses in-process EventBus (already works)- Full-text search
tsvector— conversation_search uses LIKE fallback
v0.17.2 — CodeMirror 6 Integration ✅
Rich editor infrastructure for the frontend. CM6 is ESM-native; the build
step runs in Docker (esbuild → single IIFE bundle). No change to dev
experience for non-editor code — the rest of src/js/ stays vanilla.
Depends on: nothing (frontend-only). Prerequisite for: extension surfaces (v0.21.5 editor surface). See DESIGN-CM6.md for full spec.
Build Pipeline
src/editor/directory:package.json,build.mjs,index.mjs, language/theme modulesscripts/build-editor.sh: shared build script for both Dockerfiles and local dev- New Docker stage (
cm6-build):npm ci+node build.mjs→vendor/codemirror/ - Both
Dockerfile.frontendandDockerfile(unified) use the shared build script - Bundle output:
codemirror.bundle.js(~295KB min, ~90KB gzip) - Graceful degradation: all integration points check
window.CM— falls back to<textarea>if bundle unavailable
Languages (bundled)
- Markdown, JavaScript, JSON, SQL, HTML, CSS, YAML, Go, Python, Rust
- Additional modes: one-line import + rebuild
Phase 1: Extension Editor (admin panel)
CM.codeEditor()factory: line numbers, bracket matching, auto-indent, search/replace- Replace manifest
<textarea>→ CM6 JSON mode - Replace script
<textarea>→ CM6 JavaScript mode - Remove manual Tab-key handler in
admin-handlers.js(CM6 handles it) - Update
saveAdminExtension()to use.getValue()
Phase 2: Chat Input (markdown mode)
CM.chatInput()factory: markdown highlighting, minimal chrome (no line numbers, no gutter)- Enter=send, Shift+Enter=newline keybindings
- Auto-growing height, placeholder text
- Wire
onChange→updateInputTokens() - Update
chat.js,attachments.js,tokens.jsto use CM API - WYSIWYG fenced code block decorations (visual container matching claude.ai UX)
- Inline code shortcut (Ctrl/Cmd+E)
Phase 3: Polish
- Theme integration: CSS variable overrides (
--bg,--border,--accent, etc.) - Dark/light/system mode toggle in appearance settings
- Vim/Emacs keybinding preference in appearance settings (
@replit/codemirror-vim,@replit/codemirror-emacs) - Vim/Emacs applies to code editor + extension editor only (never chat input)
- SW cache: exclude
vendor/codemirror/with version bust - Update
debug.jssnapshot to include CM6 version - Documentation in ARCHITECTURE.md
CI/CD
- Path-based change detection gating (FE-only → skip BE tests, docs-only → skip all)
v0.17.3 — Notes Graph + Wikilinks ✅
Knowledge graph and bi-directional linking for the notes system. Transforms
notes from flat documents into an interconnected knowledge base with
Obsidian-style [[wikilinks]], backlinks, graph visualization, and
transclusion.
Depends on: CodeMirror 6 (v0.17.2), Notes CRUD (v0.13.x). See DESIGN-0.17.3.md for full spec.
Backend: Note Links
note_linkstable (Postgres + SQLite migrations)NoteLinkStoreinterface + both implementationsExtractWikilinks()regex parser + tests- Link extraction on note create/update
- Dangling link resolution on note create
notes.source_message_idcolumn for chat-to-note provenance
API Endpoints
GET /notes/:id/backlinks— notes linking to this noteGET /notes/search-titles?q=— lightweight fuzzy search for autocompleteGET /notes/graph— full graph topology (nodes + edges + unresolved)
CM6 Note Editor
note-editor.mjsfactory: markdown live preview (heading sizes, blockquotes, code blocks)wikilink.mjsCM6 extension: parse, decorate as clickable chips, autocomplete on[[- Textarea → CM6 swap in
notes.jswith graceful fallback noteEditorThemeintheme.mjs
Graph Visualization
- Canvas-based force-directed graph (~200 lines, no d3 dependency)
- O(n²) Coulomb repulsion + Hooke spring + center gravity
- Pan, zoom (0.15–4.0x), drag nodes, hover highlight, click to open
- Node sizing by link_count, folder-based coloring
- Ghost nodes for unresolved
[[links]]with toggle - Energy-based pause (stop rAF below KE threshold)
- ResizeObserver for responsive canvas
Note-from-Selection + Daily Notes
- "Note" button in message action bar
- Selection-aware capture (
window.getSelection()within message) - Provenance tracking (
source_channel_id+source_message_id) - "Today" button → find-or-create daily note in
/daily/
Read Mode
[[link]]rendered as clickable wikilink chips![[embed]]transclusion with async content fetch, recursion guard- Backlinks panel (collapsible, with count badge)
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), SQLite backend (v0.17.1).
See DESIGN-0.18.0.md for full spec.
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
memoriestable: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_savetool: 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_recalltool: 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_reviewstatus
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.18.1 — Side Panel Architecture ✅
Refactored the side panel from a shared-tab layout to independent single-slot panels. Any action fills the slot, replacing whatever was there — no tabs, no association between panel types. Extension UI primitives give browser extensions safe access to host app features.
Depends on: Notes graph + wikilinks (v0.17.3). Prerequisite for: live collaboration (v0.23.0) where multiple panels need simultaneous visibility.
Panel System
- Panel registry: named panels with independent open/close state
- Single-slot model: action-driven, each open replaces the previous
- Dual-view mode: two panels side-by-side (configurable split ratio via drag divider)
- Panel-persistent state: each panel remembers scroll position, open note, etc.
- Header label shows active panel name (replaces tab bar)
- Keyboard shortcut: Ctrl/Cmd+\ cycles panels, Ctrl/Cmd+Shift+\ toggles dual
Preview Panel
- Dedicated preview panel (independent of Notes)
- HTML/document preview with iframe sandbox
- Code output preview (extension-rendered blocks via pop-out)
- Live-updating during streaming responses (500ms debounce)
Extension UI Primitives
ctx.ui.toast()— toast notificationsctx.ui.openPreview()— load HTML into side panel previewctx.ui.isDark()— theme detectionctx.ui.isMobile()— viewport width checkctx.ui.isPanelOpen()— side panel visibilityctx.ui.confirm()— modal confirm dialog- Mermaid extension refactored to use primitives exclusively (zero direct globals)
- Context-aware expand: single button, fullscreen or side panel pop-out
Mobile
- Swipe navigation between panels
- Tap-to-close overlay
- Graceful collapse to single-panel on narrow viewports
- Enlarged touch targets, fullscreen close button (40px desktop, 48px mobile)
✅ 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
projectstable:id,name,description,color,icon,scope(personal/team/global),owner_id,team_id,is_archived,settings(JSONB)project_channelsjunction: ordered membership with UNIQUEchannel_id, denormalized FK onchannels.project_idproject_knowledge_basesjunction: withauto_searchflagproject_notesjunctionresource_grantsCHECK extended to include'project'
Features
- Channels can belong to a project (optional
project_idFK) - KBs can belong to a project → auto-linked to all channels in the project (BuildKBHint + kbsearch)
- Notes can belong to a project → auto-associated when created from project channel
- KB resolution chain: Persona → Project → Channel → Personal
- Project sidebar section: collapsible groups above time-based Recent section
UI
- Sidebar: projects as collapsible groups with color dots, counts, options menu
- Right-click context menu: move chat to project / remove / create-and-assign
- Drag-and-drop channels between project groups and Recent
- New Project button in split dropdown
- Channel list
project_idfilter (?project_id=uuidor?project_id=none) - Collapse state persisted in localStorage
Deferred to later versions:
- Project creation dialog (name, description, default Persona, default KBs) — using prompt() for now
Project detail panel (KB + note management within a project)→ v0.19.1Project-level Persona default→ v0.19.2- Folders within projects
- Project templates
- Admin-level team/global project management
- Project-specific files (full project-level upload — deferred for proper architecture)
- Notifications (moved to v0.20.0)
✅ v0.19.1 — Active Project + Project System Prompt + Detail Panel
Quality-of-life improvements making projects usable for daily workflow. No migrations.
Depends on: v0.19.0 Projects/Workspaces.
Active Project
- Pin a project as active — new chats auto-assign via
addChannelToProjectafter channel creation App.activeProjectIdpersisted inlocalStorage('cs-active-project')- Visual indicator:
.project-group.activeaccent border + 📌 icon - Toggle from project ⋯ menu ("Pin as active" / "Unpin project")
- Cleared on project delete
Project System Prompt
- Per-project instructions in
projects.settingsJSONB (system_promptkey) - Injected in
loadConversationbetween persona/channel prompt and KB hint - Merge semantics on update: reads existing JSONB, overlays patch keys, writes back
- Both Postgres and SQLite stores updated
Project Detail Panel
- Registered with
PanelRegistry(same pattern as Notes, Preview) - System prompt textarea with Save button
- KB management: list with names (LEFT JOIN), [+ Add] picker dropdown, [✕] remove
- Notes list with titles (LEFT JOIN), [✕] unbind
- Accessible from project ⋯ menu → "Project settings"
Bug Fixes
renderChatListearly return prevented project groups from rendering when zero chats- Recent section drop target: "Drop chats here to unassign" hint with
min-height: 40px
✅ v0.19.2 — Project Persona Default + Archive + Channel Reorder
Completes the project management feature set. No migrations.
Depends on: v0.19.1.
Project Persona Default
- Bind a persona to a project via detail panel dropdown
- Stored in
projects.settings.persona_id - Resolution chain: explicit
req.PresetID→ project persona → none - Inherits model, provider config, temperature, max tokens, system prompt as defaults
Project Archive Toggle
- Checkbox in project detail panel
- Archived projects hidden from sidebar by default
- "▸ Show archived (N)" toggle button below project list
- Chats in hidden-archived projects spill into Recent section (never vanish)
- Dimmed + italic visual treatment for archived groups
Channel Reorder
loadProjectChannelPositions()fetches server-persisted positions on startup_getReorderedProjectChats()sorts project chats by stored position- Right-click context menu: ↑ Move up / ↓ Move down (optimistic swap + API call)
- Order maintained across add/remove/move operations
v0.20.0 — Notifications + @mention Routing + Multi-model ✅
Notification infrastructure that makes collaboration real-time, plus multi-model routing for channels. Three-phase delivery: notifications core (in-app), @mention parsing + fan-out completions, email transport
- per-user notification preferences.
(Notifications shifted from v0.19.0; @mention routing shifted from v0.17.0)
Depends on: v0.19.2 (Projects complete), EventBus + WebSocket hub (v0.9.x),
channel_models table (v0.16.0 schema). See
DESIGN-0.20.0.md for full spec.
Phase 1 — Notifications Core (in-app)
notificationstable:id,user_id,type,title,body,resource_type,resource_id,is_read,created_at(Postgres + SQLite migrations)NotificationStoreinterface + both implementations: Create, ListByUser (paginated), MarkRead, MarkAllRead, Delete, UnreadCountnotifications.ServicewithNotify()/NotifyMany()— centralized creation + WebSocket dispatch- EventBus routes:
notification.new,notification.read(badge sync across tabs) - API endpoints: list, unread-count, mark-read, mark-all-read, delete (5 endpoints, all user-scoped)
- Initial sources:
role.fallback(EventBus subscription),kb.ready/kb.error,grant.changed - Frontend: bell icon + unread badge (capped 9+), notification dropdown (latest 10, click-to-navigate), full panel via PanelRegistry
- WebSocket handler: real-time push + toast for high-priority types
- Background cleanup goroutine (configurable retention, default 90 days)
Phase 2 — @mention Parsing + Multi-model Routing
mentions.Parse(): extract @mentions, resolve against channel model roster (case-insensitive, longest-match-first, trailing punctuation tolerant)- Parser tests: no mentions, unresolved, multiple, overlapping names, punctuation edge cases
- Channel model CRUD handlers (4 endpoints: list, add, remove, update)
- Completion handler: mention extraction → target resolution → sequential fan-out → N assistant messages
- SSE streaming: model attribution in delta events (
model_displayfield) - Frontend: model pills in chat header with add/remove UI
- Frontend: @mention autocomplete via CM6
mentionCompletionextension (roster-backed) - Frontend: model attribution labels on multi-model assistant messages
- Backward compat: channels with no extra
channel_modelsrows work exactly as before
Phase 3 — Email Transport + Notification Preferences
notification_preferencestable (Postgres + SQLite): per-user, per-type in_app/email toggles- Preference resolution chain: specific type → user wildcard
*→ system default (in_app=true, email=false) EmailTransport: SMTP with implicit TLS (port 465) and STARTTLS (port 587), multipart MIME (HTML + plaintext)- Email templates: branded HTML + plaintext, domain-prefixed subjects, Go
html/template - Preference CRUD endpoints (3: list, set with partial patch, delete)
- Admin SMTP configuration: enable toggle, host/port/user/password/from/TLS fields, test email button
- User notification preferences UI (Settings → Notifications tab, per-type checkboxes)
- Notification service: preference check before dispatch, async email delivery (goroutine, 30s timeout)
- Tests: preference resolution chain (7 tests), email templates (4 tests)
- Deferred: digest mode (batched email), SMTP password vault encryption
Hotfix (carried forward from v0.19.2 investigation)
user_model_settingsNULL scan fix: COALESCE onhidden/sort_orderin SELECT + INSERT for both dialects- Gin release mode: auto-set
gin.ReleaseModewhenENVIRONMENT=production, suppress health check log spam
v0.21.x — Workspace Platform + Extension Surfaces
Seven-release series decomposing workspace storage, tools, indexing, git, surface infrastructure, and editor/article modes. Full specification in DESIGN-0.21.0.md.
Depends on: file storage (v0.12.0), extension foundation (v0.11.0), CodeMirror 6 (v0.17.2), knowledge base embedding pipeline (v0.14.0).
v0.21.0 — Workspace Storage Primitive ✅
Pure backend foundation. Workspaces as a polymorphic platform primitive owned by users, projects, channels, or teams. Dual-layer storage: PVC filesystem (source of truth) + DB metadata index (queryable cache).
workspacestable: polymorphic owner (user/project/channel/team), root_path, max_bytes quota, statusworkspace_filestable: metadata index (path, MIME type, size, sha256, is_directory)WorkspaceStoreinterface: CRUD, file index (upsert/delete/list/prefix), ownership lookup, stats- Postgres + SQLite store implementations with upsert-on-conflict for file index
workspace.FSpackage: read, write (atomic via temp+rename), delete, mkdir, stat, list, reconcile (FS→DB sync)workspace.Archive: zip/tar.gz extract + create with archive bomb protection (10K file limit, 100MB single file, quota enforcement), common-prefix stripping- Content type detection: extension-based (40+ source code types) with http.DetectContentType fallback
- SHA256 computed during write via tee reader (enables content-addressed skip in v0.21.2)
- Path traversal guards: cleanPath + absPath validation, symlink rejection
- 15 API endpoints: workspace CRUD, file CRUD, archive upload/download, reconcile, stats
- Owner-based authorization: user=self, channel=owner, project=member, team=member
- Unit tests: path cleaning, traversal detection, MIME detection, unsafe path filtering, write/read round-trip, delete, mkdir
v0.21.1 — Workspace Tools + Channel/Project Binding ✅
Make workspaces useful in chat mode via tool calls.
- Workspace tools (category: workspace):
workspace_ls,workspace_read(50KB soft limit),workspace_write(auto-mkdir),workspace_rm(recursive guard),workspace_mv,workspace_patch(find/replace) channels.workspace_idFK,projects.workspace_idFK- Workspace resolution: channel workspace → project workspace (override pattern)
- Tool injection in completion handler when workspace bound
- Frontend: deferred (channel settings workspace toggle, project Files tab, chat bar workspace indicator)
v0.21.2 — Workspace Indexing + Semantic Search ✅
Embed text files for semantic search. Reuses knowledge.SplitText + knowledge.Embedder.
workspace_chunkstable (Postgres + SQLite migrations)workspace_filesadditions:index_status,chunk_countcolumnsworkspaces.indexing_enabledboolean (default true)- Store methods:
InsertChunks,DeleteChunksByFile,SimilaritySearch,UpdateFileIndexStatus - Indexable types: text MIME types + 40+ code extensions + special filenames (Makefile, Dockerfile, etc.)
- Code-aware chunking: 1500-char chunks with function/class/type/sub boundary separators
- Content-addressed skip: sha256 comparison before re-chunking/re-embedding
workspace.Indexer: background goroutine with configurable concurrency semaphore- Single-file indexing via
FS.WriteFilehook (SetIndexer) - Batch indexing via
ExtractArchive(sequential within one semaphore slot) workspace_searchtool: semantic search with optional glob filtering, top-K capped at 20GET /api/v1/workspaces/:id/index-statusendpoint (per-status file counts, total chunks)- Configuration:
WORKSPACE_INDEXING_ENABLED,WORKSPACE_INDEX_CONCURRENCYenv vars - Graceful degradation when no embedding role configured
- Mock store updated for FS unit tests
v0.21.3 — Surface Infrastructure + REPL ✅
Pure UI architecture. No workspace dependency (parallel development).
data-surface-regionattributes on index.html containers (surface-header, surface-main, surface-footer, sidebar-content)surfaces.js— SurfaceRegistry: register, activate, deactivate, getCurrent, listctx.ui.replace()/ctx.ui.restore()with DOM preservation (DocumentFragment-based)ctx.surfaces.register()API for extensions- Mode selector component in sidebar (
#modeSelectorWrap, auto-shown when ≥2 surfaces) - Chat as default surface (implicit, always registered)
surface.activated/surface.deactivated/surface.registered/surface.unregisteredEventBus events- REPL tab: AsyncFunction wrapper, global injection (API, Events, Extensions, Surfaces, DebugLog, Panels, UI,
,$, sleep) - REPL tab: pretty-print results (collapsible JSON, type-colored primitives, DOM element summaries)
- REPL tab: command history (sessionStorage, ↑/↓ navigation)
- REPL tab: tab-completion on object graphs, event labels, globals
- REPL tab: event label hints (Events.on/emit completion)
- REPL tab: admin gate (admin role OR ?debug=1 URL param)
- REPL tab: toolbar (clear, copy, help)
- CSS: mode selector + REPL styles
- Documentation in EXTENSIONS.md §6 updated with implementation details
- Mobile: mode selector collapses to hamburger/bottom nav — moved to v0.22+
- Integration with extension loader (surfaces from manifest — moved to v0.22+)
v0.21.4 — Git Integration ✅
Layer git operations onto workspaces.
workspacesadditions: git_remote_url, git_branch, git_credential_id, git_last_syncgit_credentialstable: auth_type (https_pat/https_basic/ssh_key), encrypted_data (AES-256-GCM)- Postgres migration (012_v0214_git.sql) + SQLite migration (011_v0214_git.sql)
models.GitCredential,GitCredentialSummary,GitStatus,GitFileStatus,GitLogEntryGitCredentialStoreinterface + Postgres/SQLite implementations (Create, GetByID, ListByUser, Delete)workspace.GitOpswith exec-based git operations (9 operations)- Credential injection: temp
GIT_ASKPASS(PAT),.git-credentials(basic),GIT_SSH_COMMAND(SSH) - Security: reject file:// URLs, sanitize error output, GIT_TERMINAL_PROMPT=0
- Clone, Pull, Push, Status, Diff, Commit, Log, BranchList, Checkout
SetGitLastSyncon WorkspaceStore interface + both implementations- Post-operation reconcile + re-index hook (clone, pull, checkout)
- 5 git tools: git_status, git_diff, git_commit, git_log, git_branch
- Git tools filtered out when no workspace bound (completion handler)
- 9 git API endpoints on /api/v1/workspaces/:id/git/*
- 3 credential CRUD endpoints on /api/v1/git-credentials
- Wired in main.go: GitOps creation, tool registration, route registration
- Workspace settings UI: git config section — moved to v0.22+
- User Settings: git credentials management UI — moved to v0.22+
- Integration tests: clone, commit, push/pull cycle (requires git binary in CI)
.gitignorerespect in workspace indexing — moved to v0.22+
v0.21.5 — Editor Surface (Development Mode) ✅
IDE-like experience. Browser-tier surface consuming workspace primitives.
- Layout: file tree (sidebar), code editor (CM6, main), AI chat panel (resizable split), status bar
- File tree: workspace_ls backed, context menu (open, delete), nested directory expansion, file icons
- Code editor: CM6 codeEditor() factory with textarea fallback, language auto-detection (20+ languages), tab bar, Ctrl+S save
- AI chat panel: same channel context via Surfaces DOM borrowing, live update via workspace.file.changed event
- Keyboard shortcuts: Ctrl+P quick open, Ctrl+S save, Ctrl+W close tab
- Split pane layout with draggable resize handle
- Status bar: file path, language mode, git branch
- Workspace API methods on frontend (8 methods)
- Surfaces.getSavedFragment/putSavedFragment for cross-surface DOM sharing
- workspace.file.changed event emission from stream_loop.go after tool calls
- Event route table: workspace.file. → DirToClient
- editor-mode.css: dark theme, mobile responsive (stacked at ≤768px)
- Auto-registration: surface registers when channel/project has workspace binding
- Workspace management UI: create, list, bind from project settings + chat context menu
- GET /workspaces list endpoint, workspace_id in channel API (update, get, list)
- Git status indicators in file tree (M/A/U badges + colors) — closed in v0.21.6
- Drag-drop file reorder — moved to v0.22+
- Ctrl+Shift+F workspace search (maps to quick open) — closed in v0.21.6
- Auto-save on tab/mode switch — closed in v0.21.6
v0.21.6 — Article Surface + Document Output Pipeline ✅
Writing-focused surface. Document is artifact, conversation is secondary.
- Layout: outline (sidebar top), AI assistant (sidebar bottom), rich text editor (CM6 markdown, main)
- Outline: auto-generated from headings, click-to-scroll (navigates CM6 cursor)
- Rich text editor: CM6 codeEditor() markdown mode, line wrapping, centered content (720px max), textarea fallback
- AI assistant: chat panel embedded in sidebar via Surfaces DOM borrowing
- Export dropdown: Download Markdown, Download HTML (via marked.js + DOMPurify), Copy to clipboard
- Document picker: lists .md/.mdx/.txt files from workspace, create new document inline
- Focus mode: dims non-active lines in editor (CM6 opacity-based, toggle button)
- Word count + reading time in status bar (~230 wpm)
- Auto-save: debounced (2s) on content change, save on tab switch / surface deactivate
- Live update on workspace.file.changed event (reloads if file unmodified)
- Surface registration: registers as "Article" with file-text icon when workspace bound
- article-mode.css: outline styling, export menu, focus mode, mobile responsive
- (from v0.21.5 deferred) Git status indicators in editor file tree (M/A/U badges + colors)
- (from v0.21.5 deferred) Auto-save on tab/mode switch in editor surface
- (from v0.21.5 deferred) Ctrl+Shift+F workspace search (maps to quick open)
- Article-specific AI tools (suggest_outline, expand_section, check_citations) — moved to v0.22+
- Drag-to-reorder outline sections — moved to v0.22+
- PDF export via pandoc — moved to v0.22+
Bugfixes & DX (added post-initial)
- Hash Router (
router.js): URL-driven direct-to-surface navigation (#/chat, #/editor, #/article). Bookmarkable, back/forward works. Precursor to Gohtml/templateserver-rendered pages (v0.25.0) for public/workflow entry points. openDirect(wsId)on EditorMode + ArticleMode: bypass check(), register directly with workspace ID- Workspace picker overlay: shown when navigating to surface without workspace, auto-selects if only one
SEED_PROVIDERSenv var: auto-creates global providers on startup (dev/test only, idempotent)- Mode selector labels: icon + text, labels hide when sidebar collapsed
chat.switched/chat.createdevents: wired into selectChat(), newChat(), sendMessage()workspace_idin channel API responses: added to list/get/create for both SQLite and Postgres- Frontend chat + project objects: now carry
workspace_idfor Router and surface check() optimization
v0.21.7 — Bugfix: scanJSON Driver Buffer Aliasing ✅
Critical fix for intermittent 500 errors on paginated list endpoints.
scanJSON[]byte path: copy driver buffer instead of aliasing (json.RawMessage(v)→make+copy)CreateChannelPostgres RETURNING path: usescanJSON(&ch.Settings)instead of bare&ch.SettingsUpdateChannelsettings write: validatejson.Valid()before JSONB merge, reject with 400- New tests:
TestScanJSON_ByteSliceNotAliased,TestScanJSON_ByteSliceIsolation_MultiRow
Root cause: json.RawMessage(v) is a type conversion, not a copy — it aliases
the []byte buffer owned by database/sql. Per the Scanner interface docs, []byte
values are only valid until the next rows.Scan() call. In paginated list endpoints,
row N's Settings slice header pointed into memory that row N+1's scan overwrote,
producing corrupt JSON that passed json.Valid() at scan time but failed at marshal
time when SafeJSON serialized the full response.
v0.22.0 — Provider Health + Capability Overrides ✅
Smallest shippable unit that unblocks routing. Track provider error rates and latency, add admin override layer for model capabilities.
Depends on: usage tracking (v0.10.0), capabilities resolver (v0.9.1).
Provider Health Tracking
provider_healthtable:provider_config_idFK,window_start(hourly buckets),request_count,error_count,timeout_count,total_latency_ms,max_latency_ms,last_error,last_error_at- In-memory health accumulator: goroutine-safe counters per provider, flushed to DB on interval (60s)
RecordSuccess/RecordError/RecordTimeoutcalled from completion handler after every provider call (sync + streaming)- Provider status derivation:
healthy/degraded/downbased on error rate thresholds (5% = degraded, 25% = down) GET /api/v1/admin/providers/:id/healthendpoint: current status + hourly windows (configurable hours param)GET /api/v1/admin/providers/healthendpoint: summary status for all providers- (shipped v0.22.3) Health status included in
GET /api/v1/models/enabledresponse (newprovider_statusfield) - (deferred → v0.22.2) Auto-disable: optional policy to mark provider inactive when
downfor N consecutive windows - Background cleanup: prune health rows older than 7 days (6-hour cycle)
Capability Admin Overrides
capability_overridestable:provider_config_id(nullable for global),model_id,field,value,set_by,created_at- Three-tier resolution in
ResolveIntrinsic(): catalog → heuristic → admin override (highest priority) PUT /api/v1/admin/models/:id/capabilitiesendpoint: set individual capability overridesDELETE /api/v1/admin/models/:id/capabilities/:overrideIdendpoint: remove overrideGET /api/v1/admin/models/:id/capabilitiesendpoint: show resolved caps with source annotation (catalog/heuristic/override)GET /api/v1/admin/capability-overridesendpoint: list all overrides- Postgres + SQLite migrations (013_v0220_health.sql / sqlite 012_v0220_health.sql)
Search Provider Health (deferred → v0.22.1)
RecordOutcomefor web_search and url_fetch tool calls (search provider tracking)- Rate limit tracking per provider config (requests/minute counter in health accumulator)
Workspace Pane Refactor (frontend)
.workspaceflex container replaces.chat-area+.side-panellayout.workspace-primary/.workspace-secondaryindependent pane model.workspace-handledrag-resize between panes (replaces old side-panel-resize)- Surface layout declarations:
primary,secondary,secondaryOptsper surface - Dual-view mode removed (was
_dualMode,_splitRatio,_secondaryin PanelRegistry) - Zoom selector updated for new class names
Bugfix (v0.21.7)
scanJSONdriver buffer aliasing — copy[]bytefrom database driver instead of aliasing slice headerCreateChannelRETURNING path usesscanJSON(&ch.Settings)instead of bare scanUpdateChannelvalidatesjson.Valid()on settings before JSONB merge
v0.22.1 — Provider Extensions (Declarative Config) ✅
Replace hardcoded provider-specific behavior with data-driven configuration. Same model, different provider → different parameters.
Depends on: provider health (v0.22.0).
Provider Profiles
- Profile schema per provider type: defines available settings, types, defaults, validation, dependency rules (
providers/profile.go) - Built-in profile schemas for: openai, anthropic, venice, openrouter (unknown types fall back to openai-compatible)
- System prompt injection:
system_prompt_prefixsetting prepended to first system message (all providers) - Thinking mode toggle: Anthropic
extended_thinking+thinking_budget→ injectsthinking.type=enabled; Veniceenable_thinking→ disables Venice system prompt - (not needed)
provider_profilesJSONB column — existingprovider_configs.settingsJSONB already serves this purpose
Request/Response Transforms
Hooks.PreRequest(cfg, req)— decorate request before dispatch (system prompt prefix, ExtraBody injection)Hooks.PostStreamEvent(cfg, event)— normalize provider-specific response fields (Venice thinking extraction)- Hook implementations: Venice web search + thinking, Anthropic extended thinking + beta header, OpenRouter routing preferences + require_parameters, OpenAI frequency/presence penalty
- Preset-level overrides:
MergePresetSettings()merges persona overrides onto provider settings, respectingProviderOnlyfields ProviderConfig.Settingsfully utilized: hooks read from config, not hardcoded switch statementsCompletionRequest.ExtraBodyfield +mergeExtraBody()for provider-specific wire fields- Anthropic extended thinking stream support:
thinking_deltacontent blocks →Reasoningfield
Provider Type Registry
providers.Init()usesRegisterType()with full metadata (name, description, default endpoint, profile schema)ProviderTypeMetastruct: ID, name, description, default_endpoint, profile_schemaGET /api/v1/admin/provider-typesendpoint: list available provider types with their profile schemas- (deferred → v0.23) New provider types registrable via config file (openai-compatible endpoint + custom profile schema)
Search Provider Health (carried from v0.22.0)
- (deferred → v0.22.2)
RecordOutcomefor web_search and url_fetch tool calls - (deferred → v0.22.2) Rate limit tracking per provider config
v0.22.2 — Routing Policies + Fallback Chains ✅
Rules-based routing engine — policy, not ML. Evaluated after resolveConfig()
between "what model was requested" and "which provider serves it."
Depends on: provider health (v0.22.0), provider extensions (v0.22.1).
Routing Policies
routing_policiestable:id,name,scope(global/team),team_id,priority(lower wins),policy_type,config(JSONB),is_active- Policy types:
provider_prefer(ordered fallback list),team_route(restrict team to providers),cost_limit(heuristic cost cap),model_alias(alias → provider+model rewrite) - Policy evaluator: takes requested model + user context → returns ranked list of
(providerConfigID, modelID)candidates - Integration point:
evaluateRouting()called after resolveConfig, before provider dispatch — reloads winning config credentials on switch - (deferred → v0.23)
capability_matchpolicy type ("cheapest model with tool_calling")
Fallback Chains
- Fallback runner:
RunWithFallback()tries candidates in order with configurable max retries - Health-aware: skip providers with status
down, preferhealthyoverdegraded - All-down graceful degradation: keeps candidates rather than failing
- (deferred → v0.23) Latency-aware preference: track response time percentiles, prefer faster providers
- (deferred → v0.23) Cost-aware preference with budget ceiling per request
Routing Metadata
X-Switchboard-Providerresponse header:providerID/configIDon every completionX-Switchboard-Routeheader: policy name when routing is activerouting_decisionJSONB column onusage_logPOST /api/v1/admin/routing/testendpoint: dry-run policy evaluation with live health status- Admin CRUD: GET/POST/PUT/DELETE
/api/v1/admin/routing/policies
v0.22.3 — Provider Admin UI + Deferred Polish ✅
Admin interfaces for v0.22.0–v0.22.2 features, plus deferred items from v0.21.x.
Depends on: routing policies (v0.22.2).
Admin UI
- Provider health dashboard: status badges, error rate/latency/timeout metrics per provider, refresh button (
loadAdminHealth) - Capability override editor: table listing all overrides with model, config, field, value; delete button per row (
loadAdminCapabilities) - Routing policy builder: CRUD with name/priority/type/scope/team/config(JSON)/active toggle, dry-run test panel with candidate ranking and health status (
loadAdminRouting,_showRoutingForm,_runRoutingTest) - New "Routing" admin category with Health, Routing, and Capabilities sections
- Health status included in
GET /api/v1/models/enabledresponse (provider_statusfield on UserModel) - (deferred → v0.23) Provider profile editor: key-value config per provider type, preview of effective settings
- (deferred → v0.23) Fallback chain visualizer: drag-to-reorder provider priority per model family
Deferred from v0.22.0–v0.22.2
- (deferred → v0.23)
capability_matchpolicy type ("cheapest model with tool_calling") - (deferred → v0.23) Latency-aware preference: track response time percentiles, prefer faster providers
- (deferred → v0.23) Cost-aware preference with budget ceiling per request
- (deferred → v0.23) New provider types registrable via config file (openai-compatible endpoint + custom profile schema)
Deferred from v0.21.x
- (deferred → v0.23) Mobile: mode selector collapses to hamburger/bottom nav
- (deferred → v0.23) Integration with extension loader (surfaces from manifest.json)
- (deferred → v0.23) Workspace settings UI: git config section in channel/project settings
- (deferred → v0.23) User Settings: git credentials management UI
- (deferred → v0.23)
.gitignorerespect in workspace indexing - (deferred → v0.23) Drag-drop file reorder in editor file tree
- (deferred → v0.23) Article tools: suggest_outline, expand_section, check_citations (AI-powered)
- (deferred → v0.23) Drag-to-reorder outline sections in article mode
- (shipped v0.22.4) PDF/DOCX export via pandoc in article mode
- (shipped v0.22.4) Project-specific file uploads (full project-level upload architecture)
v0.22.4 — Provider Health UX + Project Files + Export ✅
Closes remaining provider infrastructure gaps and delivers UX features deferred from earlier releases.
Provider Infrastructure:
- Rate limit tracking per provider config (HTTP 429 classified separately)
- Tool health tracking (web_search, url_fetch →
tool_healthtable) - Auto-disable policy (N consecutive "down" windows → deactivate provider)
- Search provider health tracking via tool health pipeline
- SQLite health store parity (rate_limit_count, tool health, auto-disable)
- Config:
PROVIDER_AUTO_DISABLE_THRESHOLDenv var (default: 3)
UX:
- Workspace settings UI: git config section in project panel (remote URL, branch, credential)
- Project-specific file uploads (POST/GET endpoints, project panel UI)
- PDF/DOCX export via pandoc (POST /api/v1/export, editor menu integration)
- Admin health dashboard: rate limit count display with warning color
Frontend:
- 5 new API client methods (projectUploadFile, projectListFiles, exportDocument, updateWorkspace, listGitCredentials)
- Health cards show rate limit counts
- Git settings section appears when workspace is bound
v0.22.5 — Surfaces + Go Templates + Admin Bug Fixes ✅
Replaces monolithic client-side rendering with server-rendered Go
templates organized as composable surfaces. Each page route renders a
dedicated surface that owns its full layout below the classification
banner. Reusable template components (model-select, team-select,
file-upload) eliminate duplicated form-building logic and fix data
population bugs by construction.
Template Engine:
html/templatewith//go:embedfor compiled-in templates- Custom
FuncMap(roleFilterType, toJSON, hasPrefix, dict) - CSP nonce generation per request
- Data loaders per surface (pre-fetch exactly what templates need)
- Base template with banner system (top + bottom, CSS custom properties)
Surfaces:
- Chat surface (
/,/chat/:chatID) — bridge to existing JS - Editor surface (
/editor/:wsId) — server-rendered layout shell, fixes bugs #4 + #5 - Notes surface (
/notes/:noteId) — standalone with assist pane - Settings surface (
/settings/:section) — full-page replaces modal - Admin surface (
/admin/:section) — 5 categories, 10+ sections - Login page (
/login) — standalone, cookie-based auth
Reusable Components:
model-select— type-filtered model dropdown (fixes bug #1)team-select— pre-populated team picker (fixes bug #2)file-upload— drop zone + file picker
Auth:
AuthOrRedirectmiddleware — cookie JWT, redirect to/loginRequireAdminPage()— role gate for admin pagessb_tokencookie sync on every token save/refresh
Editor File Upload (bug #3):
- Upload button in toolbar + drag-and-drop on file tree
uploadWorkspaceFile()API method (binary-safe, respects quota)- Auto-open single uploaded text files
Cleanup:
- Removed
/legacySPA fallback route - nginx.conf: page route proxy blocks before SPA fallback
v0.22.6 — Split Deployment Fix + CI Timeout + Code Pruning ✅
Hotfix for FE/BE split k8s deployment broken by v0.22.5 surfaces architecture, plus aggressive pruning of OBE client-side code now replaced by server-rendered surfaces.
BACKEND_URLenv var on FE container for page route proxying- Entrypoint generates
proxy_passblocks for all surface routes - Works with both
BASE_PATH=""andBASE_PATH="/prefix" - CI:
-timeout 8mon allgo testinvocations - Removed
router.js(322 lines) — hash routing replaced by server routes - Removed
surfaces.js(368 lines) — client registry replaced by Go templates - Replaced
index.htmlSPA shell (1,296 → 16 lines) with redirect stub - Stripped old Surfaces paths from
editor-mode.js(~260 lines) - Simplified
docker-entrypoint-fe.sh— requiresBACKEND_URL, removed SPA-only path - Consolidated
nginx.confproxy blocks for unified container - Net: ~2,430 lines removed
v0.22.7 — Surface Prototypes (Theme + ChatPane + Splash) ✅
Foundation CSS/JS for the v0.22.5 surface architecture: theme system, reusable chat pane component, splash/login redesign, and UI primitive extensions. All files target the correct tree locations established in v0.22.5. Settings surface gains BYOK and User Personas feature gates.
Depends on: surfaces + Go templates (v0.22.5), code pruning (v0.22.6).
Theme System:
theme.css: CSS custom properties for light/dark themes via[data-theme]attribute- System preference auto-detection (
prefers-color-schememedia query) - Variable bridge: new vars (
--bg,--text) alias old vars (--bg-primary,--text-primary) - Shared component styles: toast stack, badges, buttons, icon buttons, form fields, confirm dialog, theme toggle, avatar upload, section headers
- Surface layout styles: splash, chat-pane, admin, settings, editor
- Google Fonts: DM Sans (UI) + JetBrains Mono (code)
base.html:data-themeon<html>,theme.cssbeforestyles.css
ChatPane Component:
chat-pane.js: factory pattern —ChatPane.create(opts)returns self-contained instance- Instance API: renderMessages, appendChunk, finalizeStream, appendTyping, removeTyping, scrollToBottom, showWelcome, clear, getInputValue, setInputValue, focusInput, destroy
- Lookup:
ChatPane.get(id),ChatPane.forChannel(channelId),ChatPane.active() components/chat-pane.html: Go template partial for server-rendered mount points- Editor surface:
chat-panetemplate in assist pane withChatPane.create()boot
Splash / Login:
login.htmlrewritten: split hero + auth card layout- Animated grid canvas (40px grid, scrolling offset,
--bordercolor) - Auth tabs (Login / Register) with server-gated registration
- Register form: username, display name, email, password, avatar upload
pages-splash.js:Pages.initSplash(), grid animation, POST login, POST register + PUT avatarPageDatagainsInstanceName,LogoURL,Tagline,RegistrationOpenloadBranding()+isRegistrationOpen()from GlobalConfig
UI Primitives Additions:
Toast: show/success/error/warning/info with auto-dismiss, stackingrenderBadge(): 6 color variants (accent, success, danger, warning, purple, muted)renderIcon(): 20+ SVG icon setrenderIconBtn(): icon button factory with active stateTheme: init/set/get/resolved/renderToggle with localStorageshowConfirmDialog(): enhanced with variant, onConfirm/onCancelmountAvatarUpload(): file input with preview, onUpload callback
Settings Surface Gates:
- BYOK gate: My Providers, Model Roles, Usage tabs shown when
user_providers.enabled - User Personas gate: My Personas tab shown when
user_presets.enabled - Models tab added to base navigation
SettingsPageDatagainsBYOKEnabled,UserPersonasEnabledfrom GlobalConfig- BYOK status indicator in nav footer
Go Changes:
PageData: Theme, SurfaceSettings, InstanceName, LogoURL, Tagline, RegistrationOpenRender()defaults Theme to "dark"RenderLogin()populates splash fields from GlobalConfigsettingsLoader()reads feature gates
✅ v0.22.8 — Surface Integration (Wire into Existing JS)
Connect v0.22.7 prototypes to the existing frontend JS modules. ChatPane.primary replaces singleton UI.* calls. Theme system wired into appearance settings. Admin surface hybrid loader completed. Naming cleanup: preset → persona, APIConfigID → ProviderConfigID.
Depends on: surface prototypes (v0.22.7).
ChatPane Bridge:
ChatPane.primarycreation instartApp()from server-rendered mount pointsui-core.jsdelegation:UI.renderMessages→ChatPane.primary.renderMessages, etc.- Branding init from
window.__SETTINGS__(instanceName, logoURL)
Theme Integration:
- Wire
Themeinto appearance settings save/load cycle - CM6 theme sync on theme change
- Persist theme preference to user settings API
Admin Surface:
- Complete hybrid section loaders for all JS-loaded sections
Settings Surface:
- Models visibility toggles (API.saveModelPreferences)
- User Personas CRUD (add/edit/delete with confirm dialogs)
- BYOK provider CRUD in settings context
- Teams tab content
Naming Cleanup:
- preset → persona: routes, JSON keys, Go structs, JS, CSS, DOM IDs, templates
- APIConfigID → ProviderConfigID: all Go structs and JSON fields
- Shared
app-state.jsextracted fromapp.js— available on all surfaces - Auth tokens loaded for all surfaces via
base.html
✅ v0.23.0 — @mention Routing + Persona Handles + Proxy
@mention routing works in any chat against the full model catalog and persona
registry. No roster, groups, or participant setup required — just type @handle
or @model-id and send.
@mention Resolution
resolveMention(): direct DB lookup — persona handle (exact/prefix) → model catalog (exact/prefix)- Autocomplete popup on
@in any chat — all enabled models + personas - Handle field on personas: auto-generated from name, unique, editable
- @mention pill rendering in message content (accent-colored, code-aware)
- Context boundaries: persona→plain and persona→persona style isolation
- Participant list injection: system prompt tells LLM who it can @mention
AI-to-AI Chaining
chainIfMentioned(): usesresolveMention()on assistant response content- Self-mention blocked, depth capped at 5
- WebSocket delivery (
message.created+ typing indicators) - Same resolution path for user→LLM and LLM→LLM routing
Provider Proxy
proxy_mode(system/direct/custom) +proxy_urlonprovider_configs- All four providers use
cfg.Client()with mode-aware HTTP transport - Bad custom URL falls back to system (not direct) — safe for mandatory-proxy environments
Channel Infrastructure
channel_participantstable with polymorphic types and roleschannel_modelspartial indexes: persona entries keyed per-persona, raw models per-provider- Per-provider model preferences (composite keys in
user_model_settings) persona_groups+persona_group_memberstables (schema ready, CRUD not yet wired)
✅ v0.23.1 — Multi-User Navigation + Conversation Taxonomy
Conversation type taxonomy (direct/dm/group/channel/workflow), three-section
sidebar (Projects → Channels → Chats), folder system for chats, DM plumbing,
channel configuration with ai_mode, presence heartbeat, and the unified
active conversation model.
Depends on: @mention routing (v0.23.0), WebSocket infrastructure (exists). See DESIGN-0_23_1.md for the full spec.
Conversation Types:
- Five-type taxonomy: direct (1:1 AI), dm (human-to-human), group (multi-participant), channel (named persistent), workflow (staged, deferred)
- Channel type constraint extended:
dm,channeladded totypeCHECK ai_modecolumn on channels:auto|mention_only|offai_modeguard in completion handler —offreturns 403,mention_onlywithout @mention delivers without AItopiccolumn on channels for header display
Sidebar Architecture:
- Three collapsible sections: Projects → Channels → Chats
renderChannelsSection()with # / person icons, unread badges, online dots- Channel items: context menu (rename, set topic, delete), ⋯ hover button
- Section collapse/expand state in localStorage
Folder System:
chat_folderstable, CRUD handler (handlers/folders.go)- Drag chats into/out of folders; folder context menu (rename, delete)
- Folder delete modal: "Keep chats" / "Delete chats too" / Cancel
- Unfiled drop zone when folders exist
DM Plumbing:
resolveMention()extended to resolve users (exact + prefix on username)- User @mention skips AI, delivers
user.mentionedWebSocket notification - DM creation flow with user search modal
- DM dedup guard — one channel per participant pair
Presence:
user_presencetable with heartbeat upsertPOST /presence/heartbeat(30s interval),GET /presence?users=...(90s threshold)- Presence WebSocket events:
presence.changed
Channel Participants:
channel_participantsCRUD handler, auto-created on channel creation- Persona group CRUD endpoints wired (
persona_groups,persona_group_members) - DM participant auto-creation from
req.Participants
Migration:
- 016_v023_multiuser:
ai_mode,topic,user_presence, channel type extension
✅ v0.23.2 — Multi-User Polish + Channel Lifecycle
Bug fixes, unified active conversation refactor, channel lifecycle (archive/delete),
group chat leader routing, @all fan-out, message attribution, and deferred
surface integration from v0.22.8.
Depends on: v0.23.1 sidebar and conversation taxonomy.
Unified Active Conversation:
App.activeConversation = { id, type }replacescurrentChatId+currentChannelIdsetActive(id, type),activeIdgetter,getActiveChat()helper- Send path, model bar, streaming, session restore all keyed off
activeConversation.id - Sidebar highlight works across both chat and channel sections
Group Chat + Persona Groups:
- Group leader default response: no @mention in group → leader persona responds
@allfan-out: routes to every persona participant, depth-1 only- Participant mutation guards: DMs keep ≥2 humans, groups keep ≥1 persona
- "New Group Chat" flow with ad-hoc persona picker
Message Attribution:
- Human sender display: other participants show avatar + display name (not "You")
- Persona sender display verified for loaded history in channel context
Channel Lifecycle:
- Archive action via context menu (
is_archived,archived_at) - Delete gated by
channel_retention.mode(flexible/retain) - Admin purge with
purge_after_daysfloor - Retention config keys in
global_config
Bug Fixes:
- Channel persistence through refresh (resp.channels → resp.data)
- Folder drag-and-drop (folderId mapping, drop handlers, unfiled zone)
- DM creation 404 → added
GET /users/searchendpoint - Channel ⋯ menu consistency (replaced ✕ with hover ⋯ pattern)
- Folder ⋯ button reflow (visibility:hidden instead of display:none)
- Unread subquery using
last_read_at(not migration-017-dependent column) - Settings Models section template wiring
u.avatar→u.avatar_urlin ListMessages JOIN and treepath
Surface Integration (deferred v0.22.8):
ChatPane.primarybridge from server-rendered mount points- Theme save/load cycle wired to settings appearance
- Admin hybrid section loaders completed
- Settings surface: models toggles, persona CRUD, BYOK, teams
@mention UX:
- Autocomplete includes users alongside personas and models
- User mention pill styling (distinct color from persona pills)
user.mentionedWebSocket notification with toast
Migration:
- 017_unread:
last_read_message_idonchannel_participants
v0.24.0 — Auth Abstraction + User Identity ✅
Auth provider interface decoupling builtin auth from the handler/middleware
layer. User model extended with auth_source, external_id, handle.
@mention resolution upgraded to use handles. Zero behavior change for
existing users — builtin mode goes through the new abstraction identically.
See DESIGN-0.24.0.md for the full spec including v0.24.1–v0.24.3 subversions.
Depends on: v0.23.0 (channel_participants), v0.16.0 (groups), v0.9.4 (vault).
Auth Provider Interface (server/auth/):
Providerinterface:Mode(),Authenticate(),SupportsRegistration(),Register()BuiltinProvider: extracts login/register fromhandlers/auth.goUniqueHandle(): collision-safe handle generation with-2,-3suffixesErrorToHTTPStatus(): maps auth errors to HTTP codesParseMode(): validatesAUTH_MODEstring with error return
Auth Handler Refactor:
AuthHandlergainsprovider auth.ProviderfieldLogin()delegates toprovider.Authenticate(), then vault unlock + JWTRegister()delegates toprovider.Register(), then vault init + JWTgenerateTokens()response includeshandle,auth_sourceBootstrapAdmin()/SeedUsers()backfill handles for existing users
User Model Extension:
AuthSource(builtin/mtls/oidc),ExternalID(nullable),Handle(unique)- All user store queries (PG + SQLite) include three new columns
GetByHandle(),GetByExternalID()on both store implementations- Unified
scanOneUser/scanOnehelpers replace per-method scan blocks
Config + Middleware:
AUTH_MODEenv var (defaultbuiltin)main.godispatch: builtin wired, mTLS/OIDC fail-fast at startup- Middleware unchanged — JWT validation is auth-mode-agnostic
@mention Resolution:
resolveMention()usesLOWER(handle)instead ofLOWER(username)SearchUsersreturnshandle, filters on handle- Frontend autocomplete matches on handle, uses handle as @mention token
Migration:
- 018_auth_abstraction:
auth_source,external_id,handlecolumns + unique indexes + backfill
v0.24.1 — mTLS + OIDC Providers ✅
Two external auth implementations plugging into v0.24.0's provider interface. Keycloak integration test environment via docker-compose overlay.
Depends on: auth abstraction (v0.24.0). See DESIGN-0.24.0.md §v0.24.1 for full spec.
- mTLS provider: trusted header extraction, cert DN parsing, auto-provision
- OIDC provider: well-known discovery, JWKS caching, token validation, claim extraction
- OIDC authorization code flow: login redirect, code exchange, token handoff via fragment
- Split-horizon issuer support (
OIDC_EXTERNAL_ISSUER_URLfor Docker/K8s) - OIDC claim → group mapping (external groups sync to internal groups with
source=oidc) - Login page adapts by
AUTH_MODE(form / cert status / SSO button) - Keycloak docker-compose overlay with pre-configured realm (
ci/keycloak-realm.json) QArgs()dialect adapter: handles Postgres$Nreuse for SQLite arg expansiondatabase.QueryRow/Query/Execwrappers with automatic arg expansion- Migration 019:
oidc_auth_statetable,groups.sourcecolumn - Unit tests: mTLS (ParseDN, config), OIDC (discovery, auth URL, mode), QArgs (5 tests)
- Regression test:
TestIntegration_ChannelListWithTypeFilter - SQLite local dev fixes:
_time_formatDSN, store wiring, nginx resolver, extension assets
v0.24.2 — Fine-Grained Permissions
Groups become capability carriers. Permission model on top of existing group infrastructure.
Depends on: auth abstraction (v0.24.0). Parallel to v0.24.1. See DESIGN-0.24.0.md §v0.24.2 for full spec.
- Permission constants (
domain.actionconvention, code-level enum) permissionsJSONB column on groupsResolvePermissions(): union of group permissions +DefaultUserPermsRequirePermission()middleware- Token budgets: per-group daily/monthly ceilings, enforced in completion handler
- Model access control: per-group model allowlists
- Admin UI: permission checklist, budget fields, model picker on group edit
DefaultUserPermsoverride viaglobal_configkeydefault_permissions- Migration 020: permissions, budgets, allowed_models on groups
v0.24.3 — Anonymous / Session Participants
Unauthenticated visitors for workflow intake channels. Direct prerequisite for v0.25.0 (Workflow Engine).
Depends on: mTLS provider (v0.24.1) for cert-based anonymous identity. See DESIGN-0.24.0.md §v0.24.3 for full spec.
session_participantstable (channel-scoped, ephemeral token)- Session JWT:
SessionClaims{SessionID, ChannelID}— distinct from user JWT SessionOrAuth()middleware: accepts either JWT type- Capability scoping: session participants limited to their bound channel
- mTLS anonymous path: cert fingerprint as stable session identity
channels.allow_anonymousflag- Session display in participant list (team members see visitor identity)
- Migration 021:
session_participantstable,allow_anonymouscolumn
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 for the conceptual model.
Depends on: multi-participant channels (v0.23.0), anonymous identity (v0.24.3).
(Shifted from v0.22.0 — no content changes, dependency refs updated)
Workflow Definitions
workflowstable:team_id(owner),name,description,is_active,entry_mode(public_link, team_internal, api)workflow_stagestable: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
Go Template Pages (server-rendered entry points)
Server-rendered HTML via Go html/template for pages that need SEO, OG tags,
pre-auth rendering, or standalone access outside the SPA shell. Same binary,
same auth middleware, same deployment — no second language runtime.
- Template engine: Go
html/templatewith shared base layout, partials for nav/footer - Route layer:
/w/:slugfor public workflow pages,/p/:idfor shared project views - Workflow landing page: branded intake form, persona avatar, description — rendered server-side
- Shared/embedded surface pages:
/s/editor/:wsId,/s/article/:wsId/:path— server renders minimal shell, JS hydrates surface directly (replaces hash routing for public links) - Template data: workflow metadata, branding, environment banner — injected server-side
- Auth gating: public pages skip auth, team pages require session, admin pages require role
- Precursor: hash router (v0.21.6) handles in-app navigation; Go templates handle external/public entry points
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
memberrole) - 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_createtool (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
- "Group" scope badge on model selector / KB list: show access-source annotation so users see why they have access (global, team, group grant). Requires backend to include
access_sourcein list responses. - Per-provider model preferences:
user_model_settingsunique key is(user_id, model_id)— same model from different providers shares one visibility toggle. Needsprovider_config_iddimension in DB constraint, store, API, and frontendhiddenModelskeying. Frontend composite key (configId:modelId) already exists inApp.models[].id.
Projects — Future
- Project-specific files: full project-level upload (own endpoint, storage path, UI surface — not just channel attachment re-linking)
- Project templates: create new projects from predefined configurations (persona, KBs, system prompt)
- Project creation dialog: replace
prompt()with proper modal (name, description, persona, KB picker) - Admin-level project management: cross-instance visibility, reassign ownership, enforce team policies (scope: enterprise only, BYOK personal projects stay private)
- Sub-projects / nested hierarchy: child inherits parent KBs, system prompt, persona (deferred until usage patterns clarify need vs tags/labels)
Knowledge Bases — Future
- KB auto-injection: top-K chunk prepend to system prompt, context budget aware, per-channel toggle (latency budgeting required)
- 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
Store cleanup: add(done in v0.17.0) (currently uses directUpdateDocumentStorageKey()toKnowledgeBaseStoreinterfacedatabase.DB.ExecContextin 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→ v0.22.0 + key rotation- Multi-tenant SaaS mode
- Plugin/extension marketplace
- Virtual scroll for long conversations
SQLite backend option (single-user / dev)→ v0.17.1- Helm chart. The k8s/ raw manifests with
${VAR}substitution have served well but are friction for external adopters and make values management manual. A Helm chart wraps the same backend + frontend deployments with avalues.yaml(replicas, image tags, ingress host, storage class, secret refs, resource limits, feature flags). Target:helm install switchboard ./chartfor a fresh cluster,helm upgradefor rolling deploys. Subcharts for optional Postgres (for dev/test — prod uses external). Candidate for v0.27+ or a parallel track once the core feature set stabilizes.
Pane Architecture (Workspace Container)
The main area to the right of the sidebar is a workspace container that holds 1+ panes. Each pane is a self-contained unit (chat, editor, notes, preview, etc.) with its own lifecycle. Panes compose side-by-side rather than replacing each other (current surface model).
- Default layout: Left nav + single Chat pane (what exists today, zero cognitive overhead)
- Power user: Left nav + Editor pane + Chat pane side by side. Or Editor + Notes. Or all three.
- Workflow mode: Left nav hidden. Team member gets Chat + History. Customer gets just Chat.
- User-resizable splits between panes, layout persists per-project or per-user preference
- The current surface system was a stepping stone — proved out the region/activate/deactivate lifecycle. Panes reuse that contract but drop the "only one active at a time" constraint.
- Sidebar tabs (Chats / Files) already decouple navigation from center content (v0.21.6)
Surfaces as Extensions
- Surfaces (future: panes) become first-class extension types alongside block renderers and tool bridges
Surfaces.register()already defines the contract: name, regions, activate/deactivate callbacks — swap hardcoded JS modules for manifest + dynamically loaded bundles- Admin-installable surfaces: upload or enable from extension registry, scoped to team or global
- Surface manifest in
manifest.json: declares required regions, capabilities, dependencies - Surface IDE: built-in surface for building surfaces — Go template editor for server-rendered shells, JS/CSS editor for client behavior, live preview in sandboxed region. Eats its own dog food.
- Surface marketplace: share custom surfaces across instances (presentation mode, kanban, form builder, dashboard, etc.)
- Project-bound surface/pane defaults: project config specifies which panes are available and default layout