# 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_participants` older than N days with no associated messages - Configurable via `SESSION_EXPIRY_DAYS` env 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 from `SURFACE_ASSET_DIR/{id}/` and `SURFACE_TEMPLATE_DIR/{id}/` - `completion.go:164` — remove misleading TODO comment (pricing IS populated in `recordUsage()` 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() Require` method on `Tool` interface - `BaseTool` embed: defaults to `AlwaysAvailable` (backward compat) - Built-in predicates: `RequireWorkspace`, `RequireWorkflow`, `RequireTeam`, `DenyVisitor`, `All()` - `tools.AvailableFor(tctx, disabled)` replaces `AllDefinitionsFiltered()` **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`, `TeamID` to existing `ExecutionContext` - 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)** - `:scope` is `team-slug` or `global` — 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), `description` - `branding` (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`, `name` - `persona_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` — default `full`) - `auto_transition` (bool — advance automatically when form complete) - `transition_rules` (JSONB — conditions, round-robin config) **`workflow_versions`** - `id`, `workflow_id`, `version_number` - `snapshot` (JSONB — full serialized definition + stages + tool grants) - `created_at` ### API - Team-admin CRUD: create/edit/delete workflows and stages - Reorder stages (PATCH ordinal) - `workflow.create` permission 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-slug` or `/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_version` columns on `channels` (populated on workflow channel creation) - `current_stage` (ordinal), `stage_data` (JSONB) - `workflow_status` enum: `active`, `completed`, `stale`, `cancelled` - `last_activity_at` tracking ### Entry Point - Public link: `/w/:scope/:slug` → creates workflow channel, adds anonymous session participant (v0.24.3), binds stage 1 persona, auto-creates workspace. `:scope` is team slug or `global`. - Team-only: same flow but requires authenticated user ### Stage Transitions - AI-triggered: `workflow_advance` tool (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 `stale` after configurable threshold - Stale UX: visitor sees "Continue or Start Over" (start over = new instance on latest version) ### Migration - Adds columns to `channels`, or separate `workflow_instances` table (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.branding` JSONB 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 `AuthOrSession` middleware (v0.24.3) ### Tool Scoping - `DenyVisitor` predicates 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 - `RequireWorkflow` availability 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_template` JSONB injected into persona system prompt: "Collect the following information from the visitor: [field list]" - Persona conducts conversational intake - When all fields gathered → calls `workflow_advance` with data - Rejection: persona sees rejection reason in conversation, re-collects ### Assignment Queue **`workflow_assignments` table** - `id`, `channel_id`, `stage` (ordinal), `team_id` - `assigned_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_assignments` table --- ## 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 1. **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 as `retention` JSONB on `workflows` table. 2. **Multi-stage persona conversations.** Three modes, configurable per-stage via `history_mode` enum on `workflow_stages`: - `full` — complete history + boundary message (default, simplest) - `summary` — utility-role summarization injected as system message - `fresh` — clean slate, no prior context First and third are simplest to implement; ship those first, `summary` can land as a follow-up. 3. **Visitor re-entry.** Resume if session cookie matches and instance is `active`. Otherwise start new on latest version. 4. **Workflow-to-workflow chaining.** Deferred to v0.27.0 (tasks/agents). `on_complete` JSONB column on `workflows` table 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. 5. **Route namespaces.** `/s/:slug` for extension surfaces (single-page sub-apps). `/w/:scope/:slug` for 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.