22 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)
- EXTENSION-SURFACES.md — Extension surface authoring guide (manifest format, platform API, CSS properties, install workflow)
- 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–v0.27.5 Foundation → Extensions → Surfaces → Auth ✅
→ Workflows → Tasks → Team Tasks
(see CHANGELOG.md for full history)
│
v0.28.0 Platform Polish
├─ v0.28.1 Surfaces ICD audit ✅
├─ v0.28.2 ICD audit — all domains ✅
├─ v0.28.3 ICD close-out + FE decomp prep
├─ v0.28.4 Security tier (red team)
├─ v0.28.5 Infrastructure
│ (virtual scroll, KB auto-inject,
│ Helm chart, provider model prefs,
│ git credentials UI)
└─ v0.28.6 Frontend SDK
│
v0.29.0 Starlark Sandbox
(eval loop, permissions, admin UI)
│
v0.29.1 API Extensions
(Starlark route handlers,
outbound HTTP, requires_provider)
│
v0.29.2 DB Extensions
(namespaced tables, scoped db module,
declarative schema in manifest)
│
v0.30.0 Surface Packages
(full-stack .surface archives:
UI + API + schema + settings)
│
v0.31.0 Editor Surface Package
(E2E proof: rebuild editor as
installable surface, zero
platform special-casing)
v0.28.0 — Platform Polish
Audit arc, frontend decomposition, security, and infrastructure improvements.
v0.28.1 — Surfaces ICD Audit ✅
- ICD
surfaces.mdcorrected (6 discrepancies: field name, archive format, response shape) - Surface ID slug validation +
extractableRelPathinstall hardening - 19 E2E surface CRUD tests in ICD runner (install, enable/disable, delete, error paths)
- 22 handler-level tests + 14 store-level tests (PG + SQLite)
- CI timeout 8m → 12m for PG integration tests
v0.28.2 — ICD Audit: All Domains ✅
Full audit of every ICD document against code. Goal: ICD becomes the single source of truth. After this version, the workflow is ICD-first — update the contract before changing code.
Methodology: Read ICD → grep all source → trace route → handler → store
(PG + SQLite) → tests → enforce {"data": [...]} envelope convention → fix
ICD → fix code → fix runner → CI green. Final pass picks up straggling
failures across all domains.
Final score: 469/469 (100%)
Completed audits:
Notifications (cs0):
- Notifications ICD audit: object shape, query params, response envelopes, WS events
- Notification type enum sync: remove aspirational types, add implemented types
(
kb.ready,kb.error,grant.changed,task.budget_exceeded) - Implement
memory.extractednotification (hook in memory extractor) - Implement
user.mentionedpersisted notification (was WS-only) - Implement
workflow.claimedpersisted notification (was WS-only) - Remove dead
NotifTypeProjectInviteconstant GET /notifications/preferences— envelope fix (missing key "preferences")
Knowledge (cs1–cs4):
knowledge.mdcorrected: KB object shape (6 field mismatches), search envelope (datanotresults), search result fields, status progression (extractingstep), file type support (text-only, not binary), auth annotations- Test harness: add
RequirePermissiononPOST /knowledge-basesandPOST /:id/documents, add missingGET /:id/documents/:docId/statusandDELETE /:id/documents/:docIdroutes - New tests: document status polling, document delete, update-empty-body 400, permission denial for non-privileged user
- P0 fix:
SetDiscoverableauthorization —loadAndAuthorize+ owner/admin check, audit log, cross-user security tests (cs2) ListDiscoverableKBsresponse normalization — usetoKBResponse(), shape assertion test (cs3)- Dead code removal:
ListGlobal/ListForTeamon KnowledgeBaseStore — interface + PG + SQLite (cs4) - Move
CreateKBteam role check from rawdatabase.DB.QueryRowtostores.Teams.IsTeamAdmin; removedatabaseimport (cs4) - Team-scoped KB creation test: member denied, team admin succeeds (cs4)
Profile (cs5):
profile.mdcorrected: avatar API (wasmultipart/form-data, actually JSON base64), response shapes for all 7 endpoints, auth annotations, field table- P0 fix:
GET /settingsreturns bare object → wrapped in{"settings": {...}} profileResponseshape: addlast_login_atfield + dialect-safe time scan (database.ST()/database.SNT()), COALESCE guard on settings column- Integration test harness: register all 7 profile/settings routes (was only
GET /profile), remove stale/avatarroute aliases - New
profile_test.go: GET profile shape, PUT profile (display_name, email, duplicate email 409), GET/PUT settings envelope, password change (success, wrong current 401, too short 400), avatar delete
Workspaces (cs6):
workspaces.mdcorrected: workspace object shape (addedindexing_enabled,git_*fields,total_bytesnotstorage_bytes,owner_typefull enum), file entry shape (is_directory/size_bytesnotis_dir/size), git status full shape, git log envelope ({"data": [...]}), git commit request (pathsnotfiles), archive format query param, auth annotations, all response shapes- P0 fix:
GET /workspacesreturns bare array →{"data": [...]} - P0 fix:
GET /git-credentialsreturns bare array →{"data": [...]} - Fix:
GET .../git/logreturns bare array →{"data": [...]}+ nil slice guard - New
workspace_test.go: List empty envelope, list with data + shape, root_path not exposed, user isolation, GET by ID shape, not found 404, forbidden 403, git-credentials empty envelope, auth required (11 tests)
Projects (cs7):
projects.mdfull rewrite: 6 categories of drift (ghost fields, missing fields, request shapes, association objects,omitemptyon computed counts)- 14 new Go tests: CRUD shapes, 201 status, envelope, admin list, auth, isolation
- P0 nil guard:
ListByProjectfilesnull→[] - P0:
ListTeamProviderModelsmissingTypefield — Venice 400 fix - ICD runner projects rewritten 9 → 19 tests
- ICD runner tier-providers: SSE parser, model exclusion, ID fallback fixes
v0.28.3 — ICD Close-out + Frontend Decomposition Prep
ICD straggler sweep (rolled in from v0.28.2 tail) and frontend decomposition groundwork.
ICD close-out (cs0):
websocket.mdfull rewrite: event envelope field (eventnottype), payload shapes for 11 event types, routing table from code, room model documented as planned-not-implementedauth.mdcorrected: login response shape (addedtoken_type/expires_in, removed phantom fields), register admin-approval pathenums.mdcorrected: added missingqueuedtask run status- Presence status gap documented (DB allows
away, runtime emits onlyonline/offline) - VERSION, CHANGELOG, ROADMAP updated
WebSocket delivery fix (cs1):
workflow.claimed,workflow.advanced,workflow.completeduse room-scopedBus.Publish()but rooms are never joined — events never reach WebSocket clients. Convert toSendToUser()per channel participant (same pattern asmessage.created,workflow.assigned).
Frontend decomposition:
- JS dependency audit:
docs/JS-DEPENDENCY-AUDIT.md— full map of 47 files, 431 globals, cross-file call graph, script load order per surface - IIFE extraction: all 47 JS files wrapped, ~168 functions privatized, zero
implicit globals remain. Explicit
window.*exports on every file. - Cross-file coupling fix:
events.jsno longer reads_storageKeyfromapi.js— usesAPI.accessTokeninstead - ICD runner gains test tiers:
crud,envelope,security,sdk - Runner coverage target: 100% of ICD-documented endpoints have at least one test
- Phase 2: onclick → addEventListener migration (143 dynamic → 5 unconvertible,
163 data-action delegated via
_uiDispatchon document.body) - Phase 3: Action registry (
sb.js) — 248window.*exports replaced withsb.register()/sb.ns().sb.resolve()centralized dispatch.sb.call()template bridge ready for Go template migration. - Phase 3b: Go template onclick →
sb.call()migration (~50 handlers) - Phase 4: ES module conversion (remove IIFE wrappers, add import/export)
v0.28.4 — Security Tier (ICD Runner Red Team)
New security tier in ICD test runner. Multi-user fixtures already exist.
Auth boundary:
- JWT revocation: disable user via admin API, verify existing token is rejected
immediately. This will likely expose a real bug — JWT middleware validates
signature + expiry only, does not check
is_activein DB. Fix: addis_activecheck in auth middleware (DB lookup, cached with short TTL). - Token reuse after
POST /auth/logout— revoked refresh token must fail - Expired token rejection (craft a token with past expiry)
- Role escalation: modify JWT claims client-side, attempt admin endpoints
- User ID substitution: swap
user_idin request bodies for another user's ID
Cross-tenant data access:
- User A's token → GET user B's notes, memories, workspaces, BYOK configs, task runs
- Team member → access other team's providers, personas, tasks
- Non-participant → read channel messages, files
Input validation:
- Path traversal in surface install (covered by cs12, verify E2E)
- SQL injection in search endpoints (notes, users, channels)
- XSS payloads in channel titles, note content, persona names (round-trip)
- Oversized request bodies, malformed JSON
Session security:
- Visitor session → access channels outside bound workflow
- Visitor → escalate to authenticated user via crafted headers
- Cross-visitor isolation: visitor A's cookie cannot read visitor B's messages
v0.28.5 — Infrastructure
- Virtual scroll for long conversations (prerequisite for heavy task output channels)
- KB auto-injection: top-K chunk prepend, context budget aware, per-channel toggle
- Helm chart (replaces raw k8s manifests,
helm install switchboard ./chart) - Per-provider model preferences — finalize: make
provider_config_idrequired onPUT /models/preferences(fixes NULL-in-UNIQUE dedup bug), migrate/models/enabledand/models/preferencesto{"data": [...]}envelope, add integration test coverage - Git credentials settings UI: vault-encrypted per-user credentials, SSH key
management, per-workspace remote config. Table exists (
git_credentials), vault pattern exists — needs settings section and CRUD handler. system.announcementnotification type: admin broadcast endpoint (POST /admin/notifications/broadcast), fan-out to all active users viaNotifyMany, admin UI for composing announcements
v0.28.6 — Frontend SDK
switchboard-sdk.js — composition layer over existing globals. Surface
authors consume a single coherent API instead of hunting through 15 JS files.
Switchboard.init({ mount })— idempotent boot: tokens, profile, theme, events, user menusw.user,sw.isAdmin— resolved identitysw.api.get()/sw.api.post()— authenticated REST, no token/base-path managementsw.on(event, fn)— WebSocket subscription (noEventsglobal knowledge)sw.chat(container, opts)— drop-in ChatPane (wrapsChatPane.create())sw.notes(container, opts)— drop-in notes componentsw.toast(),sw.confirm()— UI primitivessw.theme.current,sw.theme.on('change', fn)— theme queries- ICD runner gains
sdktest tier: validates init, component mounting, event hooks - Absorbs cs15 UserMenu band-aid — universal hydration moves into
init()
Tier 2 — Medium value (pull into any v0.28.x):
- Memory compaction: summarize old memories, confidence decay, prune low-confidence
capability_matchrouting policy ("cheapest model with tool_calling")- New provider types registrable via config file (OpenAI-compatible + custom schema)
v0.29.0 — Starlark Sandbox + Permission Model
Server-side extension runtime. Prove the eval loop, permission pipeline, and admin UI before adding capabilities.
Depends on: v0.28.0 (platform polish), extension infrastructure (v0.11.0).
go.starlark.netintegration (eval loop, timeout, memory ceiling)- Permission model: manifest declarations, admin grant/revoke, DB schema
(
extension_permissionstable: extension_id, permission, granted, granted_by) - Runtime enforcement: sandbox loader checks granted permissions, injects only approved modules. Denied permissions → clean error, not silent no-op.
- Admin UI: permission review on install, per-extension grant/revoke toggle, audit log of permission changes
- Extension lifecycle states:
install → pending_review → approved → active - Initial modules:
secrets(vault-backed per-extension),notifications(emit to users) - Migration:
extension_permissionstable - ICD: update
extensions.mdwith Starlark-specific endpoints
v0.29.1 — API Extensions
Starlark route handlers. Surfaces can serve custom JSON endpoints.
Depends on: v0.29.0 (sandbox + permissions).
api_routesmanifest key:[{"method": "GET", "path": "/data", "handler": "handlers.list"}]- Routes mounted at
/s/{surface_id}/api/..., auth context injected - Starlark request/response primitives (headers, body, status)
httpoutbound module: allowlist/blocklist per extensionrequires_providermanifest key: extensions declare provider capability needs (e.g."image_generation"). Platform resolves via existing provider chain (BYOK → team → global → routing policy). Starlarkprovidermodule makes calls on behalf of extension — raw API keys never exposed to sandbox.- Server-side tool execution in completion handler (Starlark tools run server-side, browser tools run client-side — unified tool schema)
- Multi-file asset routing:
ServeExtensionAssetpath-based lookup (replaces inline_scriptfor all requests) - Deduplicate tool schema extraction: unify
ListBrowserToolSchemasandListToolsinto shared helper handling both browser and server-side tools
v0.29.2 — DB Extensions
Namespaced tables for extension data. Create-only (no migrations yet).
Depends on: v0.29.1 (API extensions — handlers need somewhere to persist).
- Namespaced tables:
ext_{surface_id}_*prefix enforced by platform - Declarative schema in manifest:
"tables": [{"name": "items", "columns": [...]}]Platform generates dialect-correct DDL (PG + SQLite) dbStarlark module:query(),exec()scoped to extension tables + declared views- Views as read contract: extension declares views over platform tables with explicit column allowlist — never direct access to platform tables
- SQL validation:
dbmodule rejects references to tables outside namespace - Schema creation on install, drop on uninstall
v0.30.0 — Surface Packages
The .surface archive becomes the full-stack packaging unit.
Depends on: v0.29.2 (DB extensions).
- Install creates schema + mounts routes + serves assets in one operation
- Schema versioning:
"schema_version": Nin manifest - Schema migrations:
"migrations": [{"from": 1, "to": 2, "sql": "..."}] - Settings extension point: surfaces declare settings sections in manifest, platform settings surface renders them alongside core sections
- Export/import format for sharing surfaces across instances
- Surface marketplace (discovery, not hosting — instances pull from URLs)
v0.31.0 — Editor Surface Package
Rebuild the editor as an installable surface package. Zero platform
special-casing — same POST /admin/surfaces/install, same manifest,
same /s/:slug route as any third-party surface. If pages.go or
main.go need changes to support it, the platform abstraction is
wrong, not the editor.
Validates the full v0.29.x–v0.30.0 stack E2E.
Depends on: v0.30.0 (surface packages with settings extension point).
Platform primitives consumed (not owned):
- CM6 (core UI primitive — chat input, notes, code blocks all use it)
- Workspace tools (
workspace_ls,workspace_read,workspace_write) - Git tools + credentials (v0.28.0 — core settings, vault-encrypted)
- Provider resolution via
requires_provider(v0.29.1)
Surface package delivers:
- Editor
.surfacearchive: manifest, JS, CSS, Starlark handlers - Built separately, installed via admin API — follows exact same patterns as any other surface
- Editor settings section (via v0.30.0 settings extension point): keybindings (vim/emacs/standard), font size, editor theme
- Editor state persistence via
ext_editor_*tables: open tabs, cursor positions, split layout, per-workspace config - File tree, tab bar, multi-pane layout — all via surface JS, consuming platform CSS custom properties
- Markdown preview (improved renderer, shared with notes surface)
- Remove editor from core: delete
surface-editortemplate,"editor"manifest inpages.go,"editor"data loader,editor-surface.js
TBD (unscheduled — real features, no immediate need)
Items that are real but don't yet have a version assignment. Pull left based on need.
Surfaces + Editor
- Article drag-to-reorder outline sections
- Article-specific AI tools: suggest_outline, expand_section, check_citations
- Mobile: mode selector collapses to hamburger/bottom nav
- Surface IDE: built-in surface for building surfaces (Go template editor, JS/CSS editor, live preview in sandboxed region)
- Project-bound surface/pane defaults: project config specifies which panes are available and default layout
Workspace + Git
- Integration tests: clone, commit, push/pull cycle (requires git binary in CI)
Provider Health + Routing
- Latency-aware routing: track response time percentiles, prefer faster providers
- Cost-aware routing with budget ceiling per request
- Provider profile editor: key-value config per provider type, preview of effective settings
- Fallback chain visualizer: drag-to-reorder provider priority per model family
Extensions
- Image generation tool (browser or sidecar, provider-agnostic)
- STT/TTS (browser extension, Web Speech API)
- Code execution sandbox (server-side, container isolation)
- Sidecar HTTP tool protocol (Tier 2 — container isolation)
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
- Admin settings team/user export (user export blocked by vault-encrypted BYOK keys; team export needs merge-vs-replace semantics)
UX / Multi-Seat
- "Group" scope badge on model selector / KB list: access-source annotation so users see why they have access (global, team, group grant)
- Multi-tenant SaaS mode
- Plugin/extension marketplace
Projects
/p/:id— shared project view- Project-specific files: full project-level upload (own endpoint, storage path)
- Project templates: create from predefined configurations
- Project creation dialog: replace
prompt()with proper modal - Admin-level project management: cross-instance visibility, ownership reassign
- Sub-projects / nested hierarchy
Knowledge Bases
- 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
Memory
- Memory export/import: portable memory format across instances
- Cross-persona memory sharing (opt-in)
- Memory analytics: dashboard showing what Personas are learning, growth trends
Platform
- Rate limiting per user/team/tier (token budgets beyond current group budgets)
Pre-1.0 — Code Quality Sweeps
Codebase-wide refactors required before 1.0. Not tied to a feature version — pull left when touching the affected code, or schedule a dedicated pass.
SELECT * → Explicit Column Lists
All store implementations use SELECT * FROM <table> with positional rows.Scan().
If a migration adds a column, every scan breaks silently at runtime (wrong column
mapped to wrong field). Every scanOne/scanMany helper needs explicit column lists.
Scope: every store file in store/postgres/ and store/sqlite/.
Raw SQL Hunt
Several handlers execute raw SQL via database.DB.Exec / database.TestDB.Exec
instead of going through the store interface. These bypass the dialect abstraction
and are a source of PG/SQLite divergence bugs. Known instances:
handlers/channels.go— inline channel queries withpq.Arrayhandlers/workflows.go— inline unique constraint string matchinghandlers/participants.go—isDuplicateErrstring matching (should usedatabase.IsUniqueViolation)knowledge/handlers — directdatabase.DB.ExecContextfor storage key updates Full sweep: grep fordatabase.DB.Exec,database.TestDB.Exec,DB.QueryRowoutside ofstore/packages. Move to store methods or use existing dialect-safe helpers.