43 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 ✅
├─ v0.28.4 Security tier (red team) ✅
├─ v0.28.5 Frontend SDK + Pipes ✅
│ (switchboard-sdk.js, pipe/filter
│ pipeline, component mounting)
├─ v0.28.6 Infrastructure ✅
│ (virtual scroll, Helm, system tasks,
│ broadcast, git keygen, model prefs)
└─ v0.28.7 Unified Packaging + Task RBAC ✅
(.pkg archive, manifest.type,
task permission gate pre-Starlark)
│
v0.28.8 ICD Green Board
(provider timeout resilience, CORS
lockdown, SDK base path fix, WS
ticket exchange)
│
v0.29.0 Starlark Sandbox
(eval loop, permissions, admin UI
— adds starlark capability to .pkg)
│
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.29.3 Workflow Forms
(form_template → real UI, LLM optional,
.star validation, structured data)
│
v0.30.0 Package Lifecycle
(schema versioning, migrations,
settings extension point,
export/import, marketplace)
│
v0.30.1 SDK Adoption
(sw.notes factory, core surface
migration, fix-once-fix-everywhere)
│
v0.30.2 Workflow Packages
(stage surfaces, custom UI per stage,
team admin workflow builder)
│
v0.31.0 Editor Package
(E2E proof: rebuild editor as
installable .pkg, 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 (72 handlers across 10 templates, 4 unconvertible inline DOM ops, 3 standalone pages excluded) - Phase 4: ES module conversion — IIFE wrappers removed from all 47
files,
<script type="module">across all templates.sb.js+ vendor stay classic. CI test harness fixed (sb.js in VM context). - Phase 5 (future):
import/exportstatements, removewindow[name]dual-write fromsb.register()/sb.ns()
v0.28.4 — Security Tier (ICD Runner Red Team)
New security tier in ICD test runner. 58 tests, 529/531 pass.
5 bugs found and fixed. surfaces/ directory added with build script.
Auth boundary:
- JWT revocation: disable user → existing token rejected immediately.
Real bug found + fixed —
UserStatusCache(30 s TTL) added to auth middleware;is_activechecked on every request. - Token reuse after
POST /auth/logout— revoked refresh token fails - Expired token rejection (tampered JWT, alg=none — both rejected)
- Role escalation: modified JWT claims → 401. Real bug found + fixed — role now resolved from DB, not JWT claims.
- User ID substitution:
user_idin request bodies ignored by handlers
Cross-tenant data access:
- User A's token → GET user B's notes, memories, workspaces, BYOK configs, tasks — all 404/403
- Team member → access other team's providers, personas, tasks — all 403
- Non-participant → read channel messages, files — 403/404
Input validation:
- Path traversal in surface install — rejected (400)
- SQL injection in search endpoints (notes, users, channels) — 7 payloads, all parameterized
- XSS payloads in channel titles, note content, persona names — stored safely, frontend sanitizes
- Oversized request bodies, malformed JSON — handled (400)
- Null byte in path params → 500 fixed —
ValidatePathParams()middleware (400) - Oversized path params → 500 fixed — same middleware (400)
Session security:
- Visitor session → access channels outside bound workflow — 401
- Visitor → escalate to authenticated user via crafted headers — 401
- Cross-visitor isolation: enforced by
session_auth.gochannel binding, but no E2E test (requires public_link workflow setup — defer to visitor E2E milestone)
Infrastructure:
CORS_ALLOWED_ORIGINSenv var — production restricts, dev allows*surfaces/directory withbuild.sh→dist/<n>.surfacehello-dashboard.surfaceunpacked intosurfaces/hello-dashboard/
v0.28.5 — Frontend SDK + Pipes ✅
switchboard-sdk.js — composition layer over existing globals. Surface
authors consume a single coherent API instead of hunting through 15 JS files.
Pipe/filter pipeline formalizes the extension hook model into composable,
priority-ordered transform stages.
SDK core:
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)— stub shipped (warns); notes component needs factory refactor before clean mounting (v0.30.1 SDK Adoption)sw.toast(),sw.confirm()— UI primitivessw.theme.current,sw.theme.on('change', fn)— theme queries- Absorbs cs15 UserMenu band-aid — universal hydration moves into
init()
Pipe/filter pipeline:
Three stages, each a priority-ordered chain of transform functions. Filters receive typed context, return (possibly modified) context. Pipeline wires filters in priority order; any filter can halt the chain.
sw.pipe.pre(priority, fn)— pre-send: transform user message before LLM request. Context:{ message, channel, attachments, metadata }. Runs on bothsendMessage()andregenerateMessage()withregenerateflag.sw.pipe.stream(priority, fn)— post-receive: transform LLM response stream chunks as they arrive (sync-only). Context:{ chunk, accumulated, channel, model }.sw.pipe.render(priority, fn)— post-render: transform rendered HTML/markdown after DOMPurify. Context:{ element, message, channel }. ReplacesrunExtensionPostRender()with compat shim for existingctx.renderers.register()extensions.- Pipeline execution engine: ordered dispatch, error isolation (one filter throws → skip it, continue chain), timing telemetry per filter
sw.pipe.list()— introspection: list registered filters by stage with priority, source, scope, and timing stats- Filter scoping:
{ scope: { channelType: [...] } }restricts execution to specific channel types with zero-overhead skip - Manifest
"pipes"key: declarative filter registration — deferred to v0.28.7 (unified packaging, where manifest keys are extended)
Testing:
- ICD runner gains
sdktest tier: 36 tests — boot, identity, REST, events, theme, pipe registration, execution, scoping, halt, error isolation, compat shim, introspection - Pipe filter tests: priority ordering, halt semantics, error isolation, scoped/unscoped execution
v0.28.6 — Infrastructure ✅
- Virtual scroll for long conversations (prerequisite for heavy task output channels)
- 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: server-side ED25519 key generation (private key vault-encrypted, public key exposed for git host), optional persona binding for commit attribution, settings section with generate/list/copy/delete
system.announcementnotification type: admin broadcast endpoint (POST /admin/notifications/broadcast), fan-out to all active users viaNotifyMany, admin UI for composing announcements- Task webhook trigger UI: schedule selector gains
webhookoption, trigger URL displayed + copy button, outboundwebhook_url+webhook_secretfields in create/edit form, task-to-task chaining documentation in admin UI. Backend fully implemented — this is pure admin UI work. - System task type:
task_type: "system"— built-in Go function registry (retention_sweep,health_prune,session_cleanup,staleness_check). Admin creates task, picks function from dropdown, sets cron schedule. Executor calls registered Go function instead of LLM completion. Replaces the current goroutine-based background jobs with visible, configurable, auditable tasks. Existing goroutines kept as fallback until system tasks are validated. Permanent track — Go registry is not replaced by Starlark (v0.29.0). Core platform ops must not break from bad user code. Admin-only by design (no RBAC needed — hardcoded to admin role). - KB auto-injection moved to v0.29.0 — built as the reference Go implementation of the server-side filter model alongside Starlark. Ensures the filter architecture is right and performant before user code runs in it.
v0.28.7 — Unified Packaging + Task RBAC
Single .pkg archive format for both surfaces and extensions. Manifest
"type" field (surface, extension, full) drives install behavior.
Format first, capabilities later — v0.29.x adds Starlark/DB/API capabilities
into packages that already install cleanly.
Task permission model gates task creation by type and scope. Retroactively
locks down action tasks (webhook relay = data exfiltration risk) and
pre-positions for starlark tasks in v0.29.0.
Depends on: v0.28.5 (SDK — pipe/filter registration is part of manifest contract).
Archive format:
.pkgarchive: zip containingmanifest.json+ assets (JS, CSS, templates, icons). Same structure as.surfacearchives, extended manifest schemamanifest.typefield:surface(routes + templates + data loader),extension(hooks + tools + pipes, no own route),full(both). Absenttypedefaults tosurfacefor backward compatmanifest.pipeskey: declarative pipe filter registration (pre-send, post-receive, post-render) with priority and entry function reference. Wired by SDK on extension loadmanifest.toolskey: LLM-callable tool declarations (existing schema, now part of unified manifest)manifest.hookskey: EventBus subscriptions (existing schema from EXTENSIONS.md, carried forward)
Install infrastructure:
POST /admin/packages/install— unified install endpoint. Readsmanifest.type, validates type-specific requirements, wires subsystems. ReplacesPOST /admin/surfaces/install(old route kept as alias)- Validation branches by type:
surfacerequiresroutes/template,extensionrequires at least one ofhooks/tools/pipes,fullrequires both sets - Admin UI: merge Surfaces + Extensions into single "Packages" section. Type badge on each entry. Filter by type.
packagestable: replaces bothsurface_registryandextensions. Text slug PKs, type/version/tier/scope/is_system columns.
Migration:
- Existing
.surfacearchives install unchanged (type defaults tosurface). Re-install required after DB rebuild. extensionstable removed. Builtin extensions re-seed intopackagestable at startup viaSeedBuiltinPackages().build.shinpackages/produces.pkgfiles. Directory renamed fromsurfaces/.- ICD runner surface install tests updated to use new endpoint (or alias)
Task RBAC:
Permission gate on task creation by task_type. Uses existing group
permission model — no new task_permissions table. task_type is
immutable on PATCH (not in TaskPatch struct).
task.starlarkpermission constant pre-positioned inAllPermissions. Handler rejects with 400 referencing v0.29.0.task.actionpermission gate on create (existing, v0.28.0).systemtype hardcoded admin-only (existing, v0.28.6).- Go tests: starlark rejected, system non-admin denied, action without permission denied
- Admin UI: task permission management — per-user and per-team overrides.
"Allow action tasks for Team X" toggle. Pre-positions
starlarkpermission type (hidden until v0.29.0 enables it)
ICD + docs:
packages.mdICD: install, list, enable/disable, delete, type filteringextensions.md+surfaces.mdredirected topackages.mdtasks.mdICD updated: permission matrix,task_typeimmutability- ICD runner gains
packagingtest tier: install surface-type, install extension-type, install full-type, type validation, backward compat
Tier 2 — Medium value (pull into any v0.28.x):
- User-installable extension packages: RBAC-gated
POST /packages/install(non-admin), team-scoped or personal scope, permission review flow. Depends on v0.28.7 packaging. - 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.28.8 — ICD Green Board
Close out pre-existing ICD test failures. Gate: 543/543 (100%) on the ICD runner before starting v0.29.0 feature work.
Depends on: v0.28.7 (unified packaging).
Provider tier — upstream timeout resilience (6 failures):
The provider test tier calls POST /admin/models/fetch to sync the
model catalog from the upstream provider (Venice). When Venice is slow
(>30s), the Gin handler times out, the reverse proxy returns 502, and
every subsequent provider test fails (bulk enable, model list, team
creation for provider scoping, BYOK creation).
- Request timeout on outbound provider HTTP calls:
http.Clientwith configurable timeout (default 30s). Currently useshttp.DefaultClientwith no timeout inproviders/sync.go. POST /admin/models/fetchreturns 504 (not 502) on upstream timeout, with{"error": "upstream timeout: <provider>"}. Currently the reverse proxy manufactures the 502 from a broken connection.- ICD runner provider tier: retry
models/fetchonce on 502/504 before failing. Slow providers are a fact of life; the test should tolerate one retry.
SDK tier — base path in sw.api calls (4 failures):
sw.api.get('/api/v1/health') calls API._get('/api/v1/health')
which prepends window.__BASE__. In the ICD runner context on a
/dev base path deployment, __BASE__ is /dev, so the request
goes to /dev/api/v1/health — correct. The 502s here are cascading
from the same upstream provider timeout that poisons the proxy
connection pool. These will resolve when the provider timeout fix
lands. No SDK code change needed.
- Confirm SDK 502s are gone after provider timeout fix (no independent fix required)
Security transport — CORS + WebSocket (2 failures):
Both are informational findings from the v0.28.4 security tier. They're real issues but low severity (P2).
CORS_ALLOWED_ORIGINSenforcement: currently*in all environments. Production should restrict to the actual domain. Add validation: ifCORS_ALLOWED_ORIGINSis unset or*, log a warning at startup. Document in ARCHITECTURE.md.- WebSocket ticket exchange: replace
?token=query parameter (JWT visible in server logs, proxy logs, browser history) with a short-lived opaque ticket.POST /api/v1/ws/ticketreturns a single-use token (UUID, 30s TTL, stored in memory/Redis). WebSocket connects with?ticket=instead. Server validates and deletes on use. This is a protocol change — coordinate with frontendevents.jsWebSocket connection logic.
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.8 (ICD green board — clean test baseline before feature work), v0.28.7 (unified packaging), 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 -
task_type: "starlark": task executor gains Starlark code path. Task references a.starscript (inline or from a.pkg), executor runs it in the sandbox with task context (trigger payload, schedule info, previous run data). No LLM, no webhook relay — actual computation. RBAC gate from v0.28.7 enforced:task.starlarkpermission enabled intask_permissions(hidden until this version). Admin-grantable to team admins on a per-team basis.**Two-track execution model (permanent):** - `system` (v0.28.6): Go function registry — core platform ops (`retention_sweep`, `memory_compact`, etc.). Cannot break from bad user code. Admin-only, not editable, not versionable. - `starlark`: custom admin/team admin tasks — nightly reports, data quality checks, integration sync, cleanup scripts. Editable, versionable, sandbox-isolated, permission-gated. -
KB auto-injection: server-side pre-completion filter (Go built-in, not Starlark). Top-K chunk prepend from channel-bound KBs, context budget aware, per-channel toggle. Reference implementation of the server-side filter model that Starlark filters will mirror. Moved from v0.28.6 to validate alongside the Starlark filter architecture.
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.29.3 — Workflow Forms
form_template renders as real UI. The LLM moves to the corner —
present if configured, but not required for data collection. Workflows
become structured processes, not guided conversations.
Depends on: v0.29.2 (DB extensions — structured form data persists in
ext_workflow_* tables, not just stage_data JSON blob), v0.29.0
(Starlark — .star validators run on form submission).
Form rendering:
form_templateschema: typed field definitions (text,email,select,number,date,textarea,checkbox,file). Validation rules per field (required,pattern,min/max). Current freeform JSON → structured schema with backward compat (old JSON treated astextareafield prompts for the LLM)- Stage renders as form when
form_templatehas typed fields. HTML form generation from schema, client-side validation, styled with platform CSS custom properties - Form submission →
stage_datamerge (sameMergeWorkflowStageDatapath as chat-based data collection, but from structured fields instead of LLM extraction) - Stage advance on form submit: if
auto_transition: trueand all required fields pass validation, advance without human operator or LLM involvement
LLM-optional stages:
- Stage without
persona_idrenders form only — no chat pane, no completion endpoint, pure data collection - Stage with
persona_id+form_templaterenders form + chat side-by-side: user fills form, LLM assists (auto-fill suggestions, field explanations, validation help) - Stage with
persona_id+ noform_template— current behavior (pure chat-based collection, no change)
Starlark validation:
validateStarlark hook: manifest declares.starvalidator per stage. Runs on form submit before advance. Returns field errors or approval. Usesdbmodule for cross-reference validation (e.g., "is this email already in the system?")on_submitStarlark hook: post-validation side effects (create records, send notifications, call APIs viahttpmodule)
Visitor form entry:
- Visitor entry point renders form for non-chat stages (branded page with form, no chat widget). Session-scoped same as current visitor auth model
- Progressive: visitor sees form → submits → next stage (may be form or chat or operator review)
Team admin UX:
- Form builder: visual field editor in workflow admin (drag fields,
set types/validation, preview). Replaces raw JSON textarea for
form_template. Admin-only initially, team admin in v0.30.2.
v0.30.0 — Package Lifecycle
Lifecycle sophistication for the .pkg format established in v0.28.7.
Schema management, settings integration, and ecosystem features.
Depends on: v0.29.2 (DB extensions — schema declarations must exist before versioning and migrations make sense).
- Install creates schema + mounts routes + serves assets in one operation (extends v0.28.7 install with v0.29.2 schema support)
- Schema versioning:
"schema_version": Nin manifest - Schema migrations:
"migrations": [{"from": 1, "to": 2, "sql": "..."}] - Settings extension point: packages declare settings sections in manifest, platform settings surface renders them alongside core sections
- Export/import format for sharing packages across instances
- Package marketplace (discovery, not hosting — instances pull from URLs)
v0.30.1 — SDK Adoption
Migrate core surfaces to consume the SDK instead of raw globals.
Prerequisite for the Editor Package — if the platform's own surfaces
don't use sw.*, the SDK isn't validated for real.
The goal: a bug fix in sw.notes() or sw.chat() propagates to every
surface automatically. No more duplicated wiring, no more "fixed on
chat but broken on editor" class of bugs.
Depends on: v0.28.5 (SDK exists), v0.30.0 (package lifecycle — settings extension point needed for surface-specific config).
Component factories:
sw.notes(container, opts)— real factory: creates NoteEditor, note list, graph panel. Extracted fromnotes.jsmonolith into self-contained mountable component.opts: { projectId, channelId, onLink, standalone }. Returns instance withdestroy().sw.chat(container, opts)— extend existing wrapper with pipe integration (stream/render filters auto-wired), model selector binding, file upload, typing indicator. Currently thin wrapper aroundChatPane.create()— needs to become the canonical path.sw.panels(container, opts)— PaneContainer composition via SDK. Resize, tab management, drag handles. Fixes the "scaling bug lives in two places" problem — one implementation, one fix.
Core surface migration:
- Chat surface (
app.js,chat.js): boot viasw = Switchboard.init(), replace directAPI.*/Events.*/UI.*calls withsw.*in new code paths. Existing code keeps working (globals still exist). Progressive — not a rewrite, just new code uses the SDK. - Editor surface (
editor-surface.js): mount chat pane viasw.chat(), mount notes viasw.notes(), consumesw.theme.on('change')for CM6 theme sync. Removes duplicated panel resize/scale wiring. - Notes surface: mount via
sw.notes(), share implementation with chat sidebar notes panel. - Settings surface: consume
sw.api.*andsw.theme.*.
Validation:
- Existing ICD runner tests still pass (surfaces behave identically)
- ICD runner
sdktier extended with component mount/destroy tests - Manual: chat, editor, notes surfaces all function after migration
- Bug fix propagation test: fix applied to
sw.notes()visible on both chat sidebar and notes surface simultaneously
v0.30.2 — Workflow Packages
Workflows become packageable. Each stage can serve its own surface — form, dashboard, chat, or custom UI. Team admins build and manage workflows through a visual builder, not JSON editing.
Depends on: v0.29.3 (workflow forms — form rendering exists),
v0.30.0 (package lifecycle — schema versioning, settings extension point),
v0.30.1 (SDK adoption — stage surfaces consume sw.*).
Stage surfaces:
- A workflow stage can reference a surface (built-in or from a
.pkg). Stage config gainssurface_idfield — platform mounts that surface when the stage is active, with stage data injected as context - Built-in stage surfaces:
form(v0.29.3 form renderer),chat(current behavior),review(operator sees collected data + approve/reject) - Custom stage surfaces: a
.pkgwithtype: "workflow-stage"provides a surface that receives stage data and emits advance/reject signals viasw.workflow.advance(data)/sw.workflow.reject(reason)SDK calls - Stage transitions respect surface completion — surface signals "done" with structured data, workflow engine merges and advances
Workflow-as-package:
.pkgwithtype: "workflow": bundles workflow definition, stage surfaces, Starlark handlers, and assets in one installable archive.POST /admin/packages/installcreates the workflow + stages + surfaces- Manifest
"workflow"key: stages, transitions, form schemas, surface refs, on_complete hooks. Replaces manual workflow creation via admin API - Version pinning: installed workflow package tracks its package version. Upgrade re-publishes with new version number
Team admin workflow builder:
- Visual stage editor: drag-to-reorder stages, per-stage config panel (surface type, persona, form fields, transition rules, assignment)
- Form builder integrated per stage (extends v0.29.3 admin form builder)
- Preview: team admin can walk through the workflow as a visitor would, seeing each stage surface in sequence
- Publish/version from the builder UI — no JSON, no API calls
- Team admin permission:
workflow.manage— team admins can create, edit, publish workflows for their team. Global admin can create global workflows. Regular users can only enter workflows, not build them
SDK extensions:
sw.workflownamespace:advance(data),reject(reason),getData(),getStage(),onTransition(fn)— stage surfaces interact with the workflow engine through the SDK, not raw API calls
v0.31.0 — Editor Package
Rebuild the editor as an installable .pkg package. Zero platform
special-casing — same POST /admin/packages/install, same manifest,
same /s/:slug route as any third-party package. If pages.go or
main.go need changes to support it, the platform abstraction is
wrong, not the editor.
Validates the full v0.28.7–v0.30.2 stack E2E: unified packaging, Starlark handlers, DB extensions, package lifecycle, SDK adoption, and workflow packages all exercised by one real-world package.
Depends on: v0.30.2 (workflow packages — editor validates the full stack including workflow stage surfaces).
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)
Package delivers:
- Editor
.pkgarchive (type:full): manifest, JS, CSS, Starlark handlers - Built separately, installed via admin API — follows exact same patterns as any other package
- 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 package 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.