Changeset 0.26.0 (#165)
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# Architecture — Chat Switchboard v0.20
|
||||
# Architecture — Chat Switchboard v0.26
|
||||
|
||||
## Deployment Modes
|
||||
|
||||
@@ -47,34 +47,42 @@ Three Docker images support different deployment scenarios:
|
||||
|
||||
6. **Channels as Execution Context**: Channels are the universal container for conversation state — messages, tool activity, notes, and artifacts all hang off a channel. Today channels are single-user direct chats. The architecture anticipates multi-participant channels (team members, anonymous visitors, AI personas) for workflow execution, without requiring changes to the message tree, tool framework, or streaming infrastructure.
|
||||
|
||||
## Workflow Architecture (Future — v0.25.0+)
|
||||
## Workflow Architecture (v0.26.0 — Shipped)
|
||||
|
||||
The platform's existing primitives (teams, personas, channels, tools, notes)
|
||||
compose into a workflow engine where the channel is the execution context
|
||||
that moves through defined stages.
|
||||
|
||||
### Conceptual Model
|
||||
### Data Model
|
||||
|
||||
```
|
||||
Team
|
||||
└─ Workflow (team-admin defined)
|
||||
├─ name, description
|
||||
├─ entry conditions (public link, team-internal, API trigger)
|
||||
└─ Stages[]
|
||||
└─ Workflow (admin-defined, team-scoped or global)
|
||||
├─ name, slug, description, branding (JSONB)
|
||||
├─ entry_mode (authenticated | public_link)
|
||||
├─ is_active, version (auto-incremented on edit)
|
||||
└─ Stages[] (ordered)
|
||||
├─ persona_id (which AI drives this stage)
|
||||
├─ assignment_team_id (who can be assigned)
|
||||
├─ form_template (structured note schema, optional)
|
||||
└─ transitions[] (conditions → next stage)
|
||||
├─ system_prompt (stage-specific override)
|
||||
├─ form_template (JSONB — fields to collect)
|
||||
├─ history_mode (full | summary | fresh)
|
||||
└─ assignment_team_id (who can be assigned)
|
||||
|
||||
Channel (workflow instance)
|
||||
├─ workflow_id + current_stage
|
||||
├─ workflow_id + workflow_version + current_stage
|
||||
├─ stage_data (JSONB — accumulated form data across stages)
|
||||
├─ workflow_status (active | completed | stale | cancelled)
|
||||
├─ participants[]
|
||||
│ ├─ anonymous visitor (mTLS fingerprint / session token)
|
||||
│ ├─ anonymous visitor (session token)
|
||||
│ ├─ AI persona (per-stage, from workflow definition)
|
||||
│ └─ assigned team member (claimed or auto-routed)
|
||||
│ └─ assigned team member (claimed from assignment queue)
|
||||
├─ messages (existing tree structure)
|
||||
├─ notes (channel-scoped artifacts, intake forms)
|
||||
└─ tool activity (existing execution framework)
|
||||
└─ notes (stage data persisted as channel-scoped notes)
|
||||
|
||||
WorkflowAssignment (queue entry)
|
||||
├─ channel_id + stage + team_id
|
||||
├─ assigned_to (nullable — claimed by team member)
|
||||
└─ status (unassigned | claimed | completed)
|
||||
```
|
||||
|
||||
### How Existing Primitives Map
|
||||
@@ -84,33 +92,46 @@ Channel (workflow instance)
|
||||
| **Persona** (system prompt + model) | Drives a workflow stage — the AI knows what to collect, when to route |
|
||||
| **Team** (members + roles) | Owns the workflow definition; members are assignable to channels |
|
||||
| **Channel** (messages + tree) | Execution instance of a workflow; full conversation history |
|
||||
| **Notes + Tools** | Structured data collection; the persona's system prompt *is* the form definition, the note *is* the filled form |
|
||||
| **Notes + Tools** | Structured data collection; `workflow_advance` tool triggers transitions, notes persist form data |
|
||||
| **EventBus + WebSocket** | Real-time notifications for assignment, stage transitions, new messages |
|
||||
| **Anonymous Sessions** (v0.24.3) | Visitors participate without user accounts |
|
||||
| **DenyVisitor** predicate | Scopes tool availability per participant type |
|
||||
|
||||
### Design Constraints for Current Development
|
||||
### Stage Transition Flow
|
||||
|
||||
These invariants keep the workflow path open without building it prematurely:
|
||||
```
|
||||
Visitor starts → Channel created (stage 0, persona bound)
|
||||
│
|
||||
▼
|
||||
Persona collects form data via conversation
|
||||
│
|
||||
▼
|
||||
Persona calls workflow_advance(data) → Stage notes created
|
||||
│ │
|
||||
▼ ▼
|
||||
Next stage: new persona bound If assignment_team_id set:
|
||||
(or workflow completes) → workflow_assignment created
|
||||
→ team member claims
|
||||
→ member + persona collaborate
|
||||
```
|
||||
|
||||
- **Channels**: Don't assume single-owner. If touching channel queries, keep
|
||||
room for `team_id`, `type` (beyond `direct`), and multi-participant access
|
||||
patterns alongside `user_id`.
|
||||
- **Notes**: User-scoped with `[[wikilink]]` bi-directional linking (v0.17.3).
|
||||
The `note_links` junction table tracks directed edges between notes, with
|
||||
`target_note_id` nullable for dangling links (unresolved references). Links
|
||||
are extracted from content on save via regex, resolved by title match, and
|
||||
re-resolved when new notes are created. The graph endpoint returns all nodes,
|
||||
edges, and unresolved links for Canvas-based force-directed visualization.
|
||||
Future channel-scoped notes (attached to a conversation) need a `channel_id`
|
||||
FK option. `source_message_id` enables jump-to-source provenance from notes
|
||||
back to the originating chat message.
|
||||
- **Personas**: Already team-scoped. Don't couple to "user picks from dropdown"
|
||||
— a workflow stage references a persona programmatically.
|
||||
- **Tool ExecutionContext**: Already carries `UserID` + `ChannelID`. Will need
|
||||
`TeamID` and `WorkflowID` — the struct is easily extended.
|
||||
- **Auth**: mTLS anonymous users need identity to participate in channels.
|
||||
Lightest version: a `participants` table that can reference a `user_id` or
|
||||
an opaque session identifier (cert fingerprint). Don't assume every channel
|
||||
participant has a row in `users`.
|
||||
### Key Implementation Details
|
||||
|
||||
- **Form template injection.** When `channelType == "workflow"`, the completion
|
||||
handler loads the current stage's `form_template` JSONB and injects a system
|
||||
prompt telling the persona what fields to collect and when to call
|
||||
`workflow_advance`.
|
||||
|
||||
- **Dialect-safe stage data merge.** `MergeWorkflowStageData()` reads existing
|
||||
JSON from the `stage_data` column, merges in Go, writes back. No
|
||||
Postgres-specific JSONB operators — works on both Postgres and SQLite.
|
||||
|
||||
- **Optimistic claim lock.** `UPDATE workflow_assignments SET assigned_to = $1
|
||||
WHERE id = $2 AND status = 'unassigned'` — if rows_affected == 0, another
|
||||
team member already claimed it.
|
||||
|
||||
- **Staleness sweep.** Background goroutine (1h tick) marks workflow instances
|
||||
as `stale` when `last_activity_at` exceeds `WORKFLOW_STALE_HOURS`.
|
||||
|
||||
## Package Structure
|
||||
|
||||
|
||||
Reference in New Issue
Block a user