66 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.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)
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- (deferred → 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.22.3) 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 in 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:
capability_match("cheapest model with tool_calling"),provider_prefer("prefer provider X, fallback Y"),team_route("team X uses provider Y"),cost_limit("max $X/request") - Policy evaluator: takes requested model + user context → returns ranked list of
(providerConfigID, modelID)candidates - Integration point:
resolveConfig()calls evaluator after preset unwrap, before provider dispatch
Fallback Chains
- Fallback chain configuration: ordered list of providers for a given model or model family
- Health-aware: skip providers with status
down, preferhealthyoverdegraded - Automatic retry on provider error: next provider in chain, transparent to client (configurable max retries)
- Latency-aware preference: track response time percentiles, prefer faster providers (weight configurable)
- Cost-aware preference: factor pricing into routing decisions (cheapest-first, budget ceiling per request)
Routing Metadata
X-Switchboard-Providerresponse header: which provider actually served the request (debug mode)routing_decisionfield in usage_log: selected provider, policy that matched, fallback depthGET /api/v1/admin/routing/testendpoint: dry-run policy evaluation for a given model + user
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 sparklines, latency charts (last 24h)
- Capability override editor: per-model toggle grid with source indicators
- Provider profile editor: key-value config per provider type, preview of effective settings
- Routing policy builder: CRUD with priority ordering, dry-run test panel
- Fallback chain visualizer: drag-to-reorder provider priority per model family
Deferred from v0.21.x
- Mobile: mode selector collapses to hamburger/bottom nav
- Integration with extension loader (surfaces from manifest.json)
- Workspace settings UI: git config section in channel/project settings
- User Settings: git credentials management UI
.gitignorerespect in workspace indexing- Drag-drop file reorder in editor file tree
- Article tools: suggest_outline, expand_section, check_citations (AI-powered)
- Drag-to-reorder outline sections in article mode
- PDF/DOCX export via pandoc in article mode
- Project-specific file uploads (full project-level upload architecture)
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.3), WebSocket infrastructure (already exists).
(Shifted from v0.20.0 — no content changes)
Channel Participants
channel_participantstable: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
typecolumn: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_idFK 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_MODEenv var:builtin|mtls|oidc- All three resolve to the same internal user model
auth_source+external_idcolumns 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
usersrow 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 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
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
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