14 KiB
DESIGN — v0.26.0: Workflow Engine
Status: Shipped (v0.26.0–v0.26.5, 15 changesets)
Branch: 0.26.0
Scope: Team-owned staged processes, AI intake, human assignment, visitor experience
Depends on: Dynamic surfaces (v0.25.0), multi-participant channels (v0.23.x),
anonymous identity (v0.24.3), permissions (v0.24.2)
Phasing
Six sub-releases. Each is shippable and testable independently. Deferred tech debt from v0.21–v0.25 absorbed into v0.26.0 (phase 0).
v0.26.0 Foundation: cleanup + context-aware tool system
│
v0.26.1 Workflow definitions + versioning (schema + CRUD)
│
v0.26.2 Workflow instances + stage transitions (runtime)
│
v0.26.3 Visitor experience (surfaces + branded chat)
│
v0.26.4 AI intake + assignment queue
│
v0.26.5 Team collaboration + workflow builder UI
v0.26.0 — Foundation
Cleanup debt, then build the tool infrastructure that workflows need.
Deferred Cleanup (absorb from v0.21–v0.25)
Session cleanup job
- Background goroutine: delete
session_participantsolder than N days with no associated messages - Configurable via
SESSION_EXPIRY_DAYSenv var (default: 30) - Runs on startup + every 6h
- Closes v0.24.3 deferred item
Stale code TODOs
surfaces.go:108-109— surface delete: clean up static assets + templates fromSURFACE_ASSET_DIR/{id}/andSURFACE_TEMPLATE_DIR/{id}/completion.go:164— remove misleading TODO comment (pricing IS populated inrecordUsage()at line ~1996)intrinsic.go:19— remove stale "TODO: 0.9.2" comment (shipped in v0.22.0)
Roadmap housekeeping
- Mark shipped items in "Technical Debt + Deferred Items" section:
- Rate limit tracking (v0.22.4)
- Auto-disable policy (v0.22.4)
- Tool health recording (v0.22.4)
- PDF export via pandoc (v0.22.4)
- Persona tool grant enforcement (v0.25.0)
- Clean up stale deferred-into version markers
Context-Aware Tool System
Absorbed from v0.25.0 roadmap. Prerequisite for workflow tool scoping.
ToolContext struct (tools/types.go)
- Fields:
ChannelType,WorkspaceID,WorkflowID,TeamID,PersonaID,IsVisitor - Populated from channel record in completion handler before tool execution
Require predicates
Availability() Requiremethod onToolinterfaceBaseToolembed: defaults toAlwaysAvailable(backward compat)- Built-in predicates:
RequireWorkspace,RequireWorkflow,RequireTeam,DenyVisitor,All() tools.AvailableFor(tctx, disabled)replacesAllDefinitionsFiltered()
Self-declaring tools
- Workspace tools:
RequireWorkspace + DenyVisitor - Git tools:
RequireWorkspace + DenyVisitor workspace_create: available only when no workspace bound- Memory/notes tools:
DenyVisitor - Eliminates
WorkspaceToolNames()/GitToolNames()manual suppression
ExecutionContext extension
- Add
WorkflowID,TeamIDto existingExecutionContext - Populated from channel record in completion handler
Persona tool grant enforcement hardening
- v0.25.0 shipped the second-pass allowlist in completion.go:917
- For workflows: version snapshot must include persona tool grants at snapshot time (frozen for running instances)
Route Namespaces
Two distinct namespaces. No collision.
/s/:slug — Extension Surfaces (application extensions)
- Single-page, self-contained sub-applications
- Examples: custom dashboard, kanban board, form builder
- One template, one JS entry point, scoped CSS
- Registered via surface manifest (v0.25.0
surface_registry)
/w/:scope/:slug — Workflows (business logic automation)
:scopeisteam-slugorglobal— workflows belong to a team or are org-wide. Prevents slug collisions across teams.- Multi-page: landing, visitor chat, team review, admin tracking — different views depending on who is accessing (visitor vs. team member vs. admin)
- Involves customers and team members, possibly spanning multiple teams
- Purpose-built Go templates per view, not a single-page pattern
Both use Go template rendering. They share base.html but have
independent template trees and JS entry points.
Migration
- 023_v0260_foundation.sql: session cleanup additions (if schema changes needed), no new tables in this phase
v0.26.1 — Workflow Definitions + Versioning
Schema and CRUD for defining workflows. No runtime yet.
Tables
workflows
id,team_id(nullable — NULL = global),name,slug(unique within scope:UNIQUE(team_id, slug)with partial index for global),descriptionbranding(JSONB: accent color, logo URL, tagline)entry_mode(enum:public_link,team_only)is_active,version(auto-increment on edit)on_complete(JSONB, nullable — v0.27.0 chaining hook, NULL for now. Future schema:{"action": "start_workflow", "target_slug": "...", "data_map": {...}}. Column exists early so the table doesn't need migration when chaining lands.)retention(JSONB —{"mode": "archive"|"delete", "delete_after_days": N}. Default:{"mode": "archive"})created_by,created_at,updated_at
workflow_stages
id,workflow_id,ordinal,namepersona_id(FK — persona drives this stage)assignment_team_id(FK — which team handles human review)form_template(JSONB — fields the persona should collect)history_mode(enum:full,summary,fresh— defaultfull)auto_transition(bool — advance automatically when form complete)transition_rules(JSONB — conditions, round-robin config)
workflow_versions
id,workflow_id,version_numbersnapshot(JSONB — full serialized definition + stages + tool grants)created_at
API
- Team-admin CRUD: create/edit/delete workflows and stages
- Reorder stages (PATCH ordinal)
workflow.createpermission required- Publish action: snapshot current definition →
workflow_versions - Slug validation: lowercase, alphanumeric + hyphens, unique within scope
(same slug can exist under different teams). URL resolves as
/w/team-slug/workflow-slugor/w/global/workflow-slug.
Design Decisions (to flesh out)
- Branding schema: minimal (accent + logo + tagline) or extensible JSONB?
- Stage persona: must be team-scoped persona? Or any accessible persona?
Migration
- 023_v0261_workflows.sql:
workflows,workflow_stages,workflow_versions
v0.26.2 — Workflow Instances + Stage Transitions
Channels become workflow runtime containers.
Channel Extensions
workflow_id,workflow_versioncolumns onchannels(populated on workflow channel creation)current_stage(ordinal),stage_data(JSONB)workflow_statusenum:active,completed,stale,cancelledlast_activity_attracking
Entry Point
- Public link:
/w/:scope/:slug→ creates workflow channel, adds anonymous session participant (v0.24.3), binds stage 1 persona, auto-creates workspace.:scopeis team slug orglobal. - Team-only: same flow but requires authenticated user
Stage Transitions
- AI-triggered:
workflow_advancetool (see v0.26.4) - Human-triggered: button in channel header
- Rejection: return to previous stage with reason text
- On transition: swap active persona, notify assignment team, update channel metadata, create channel-scoped note with collected data
History mode (per-stage, configurable in workflow_stages):
full— new persona sees complete conversation history with a system boundary message (same pattern as @mention context boundaries, v0.23.0). Simplest to implement, best for continuity-sensitive workflows.summary— utility-role summarization of prior stages injected as a system message. New persona starts with context but not raw history.fresh— new persona sees nothing from prior stages. Clean slate. Simplest for independent review stages.
Default: full. Stored as history_mode enum on workflow_stages.
Staleness Sweep
- Background goroutine (like health accumulator pattern)
- Marks idle instances as
staleafter configurable threshold - Stale UX: visitor sees "Continue or Start Over" (start over = new instance on latest version)
Migration
- Adds columns to
channels, or separateworkflow_instancestable (TBD — inline columns simpler, separate table cleaner)
v0.26.3 — Visitor Experience
Purpose-built surfaces for anonymous workflow participants.
Landing Page
GET /w/:scope/:slug— branded page with persona avatar, description, "Start"- Go template:
workflow-landing.html - Reads
workflow.brandingJSONB for accent color, logo, tagline - System dark/light mode (no theme toggle — visitors don't have prefs)
Visitor Chat
GET /w/:scope/:slug/c/:channelId— bubble chat (NOT full ChatPane)workflow-chat.js: lightweight WebSocket client- Message send/receive
- Markdown rendering (marked.js + DOMPurify, already available)
- Typing indicator
- Stage transition animation (visual feedback on advance)
- Scoped: no sidebar, no settings, no navigation
- Session auth via
AuthOrSessionmiddleware (v0.24.3)
Tool Scoping
DenyVisitorpredicates prevent visitors from accessing workspace, git, memory, notes tools- Persona tool grants further restrict per-stage
Design Decisions (to flesh out)
- Visitor can upload files? (attachments to workflow channel)
- Visitor can see previous stage history? (probably not — each stage is a clean persona conversation)
- Mobile-first layout for visitor surfaces
v0.26.4 — AI Intake + Assignment Queue
The AI does the data collection. Humans review and act.
workflow_advance Tool
RequireWorkflowavailability predicate- Input: collected form data (JSONB matching
form_template) - Validates all required fields present
- Triggers stage transition (same path as human-triggered)
- Creates channel-scoped note with structured form response
Form Template → System Prompt
- Stage
form_templateJSONB injected into persona system prompt: "Collect the following information from the visitor: [field list]" - Persona conducts conversational intake
- When all fields gathered → calls
workflow_advancewith data - Rejection: persona sees rejection reason in conversation, re-collects
Assignment Queue
workflow_assignments table
id,channel_id,stage(ordinal),team_idassigned_to(nullable user_id),status(unassigned/claimed/completed)created_at,claimed_at,completed_at
Claim model
- Unassigned workflow channels visible to all team members
- Optimistic lock:
UPDATE ... WHERE status = 'unassigned' - Round-robin auto-assignment (optional, per-stage
transition_rules)
Notifications
- New workflow assignment → notification to team (existing infra)
- WebSocket push to online team members
- Claim confirmation notification to claimer
Migration
workflow_assignmentstable
v0.26.5 — Team Collaboration + Workflow Builder UI
Frontend surfaces for building and managing workflows.
Team Member View
- Assigned member sees full history (AI intake + artifacts + notes)
- Persona remains active — assists both visitor and team member
- Member can: advance stage, reject (with reason), add notes, invoke tools, reassign, escalate
Workflow Builder
- Team admin panel section
- Stage list editor: add/remove/reorder stages
- Per-stage config: persona picker, assignment team, form template editor
- Branding editor: accent color, logo upload, tagline
- Publish button (creates version snapshot)
- Read-only tool grants display per stage persona
Channel Header (Workflow Mode)
- Workflow stage indicator (step N of M, stage name)
- Advance / Reject buttons (permission-gated)
- Assignment info (who claimed, when)
- History mode indicator (full/summary/fresh)
Queue UI
Prototype both approaches in v0.26.5, decide based on usage:
Option A: Sidebar section for team members — shows unassigned workflow channels for user's teams, count badge, click opens in main pane. Low friction, always visible.
Option B: Dedicated surface (/workflows or team-admin view) for
tracking and management — full table with filters, assignment history,
metrics. Better for high-volume operations.
Likely outcome: both. Sidebar for team members (day-to-day claims), dedicated surface for team admins (tracking, reporting, bulk ops).
Resolved Decisions
-
Workflow channel lifecycle. Configurable per-workflow retention policy. Default: archive (read-only,
ai_mode='off', no new messages). Optional: auto-delete after N days. Stored asretentionJSONB onworkflowstable. -
Multi-stage persona conversations. Three modes, configurable per-stage via
history_modeenum onworkflow_stages:full— complete history + boundary message (default, simplest)summary— utility-role summarization injected as system messagefresh— clean slate, no prior context First and third are simplest to implement; ship those first,summarycan land as a follow-up.
-
Visitor re-entry. Resume if session cookie matches and instance is
active. Otherwise start new on latest version. -
Workflow-to-workflow chaining. Deferred to v0.27.0 (tasks/agents).
on_completeJSONB column onworkflowstable ships as nullable in v0.26.1 migration so the schema is pre-wired. v0.27.0 populates it and adds the trigger logic. No migration needed when chaining lands. -
Route namespaces.
/s/:slugfor extension surfaces (single-page sub-apps)./w/:scope/:slugfor workflows (multi-page, role-dependent views). No collision. See "Route Namespaces" in v0.26.0.
Cross-Cutting Concerns
Testing strategy: Integration tests per phase. v0.26.0 tool context tests. v0.26.1 workflow CRUD + versioning tests. v0.26.2 stage transition tests. v0.26.4 assignment claim concurrency test.
Migration numbering: Single migration per phase (023, 024, ...) or consolidated? Leaning toward: one per phase, squash at tag time if needed.
Backward compat: Non-workflow channels unaffected. ToolContext with
BaseTool defaults means existing tools work without changes. Workflow
columns on channels are nullable.