Changeset 0.28.0.1 (#173)
This commit is contained in:
472
docs/ICD/workflows.md
Normal file
472
docs/ICD/workflows.md
Normal file
@@ -0,0 +1,472 @@
|
||||
# Workflows
|
||||
|
||||
Team-owned, stage-based process execution. The channel is the runtime —
|
||||
personas drive each stage, and the existing tool/notes infrastructure
|
||||
handles structured data collection.
|
||||
|
||||
## Definitions
|
||||
|
||||
### List Workflows
|
||||
|
||||
```
|
||||
GET /workflows?team_id=...
|
||||
```
|
||||
|
||||
Returns `{ "data": [...] }`. If `team_id` is provided, scoped to that team.
|
||||
Otherwise returns global (team_id IS NULL) workflows.
|
||||
|
||||
**Auth:** Authenticated.
|
||||
|
||||
### Create Workflow
|
||||
|
||||
```
|
||||
POST /workflows
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Customer Intake",
|
||||
"slug": "customer-intake",
|
||||
"description": "Collect customer info and route to support",
|
||||
"entry_mode": "public_link",
|
||||
"team_id": "uuid|null",
|
||||
"branding": { "accent_color": "#2563eb", "tagline": "Welcome" },
|
||||
"retention": { "mode": "archive", "delete_after_days": 90 }
|
||||
}
|
||||
```
|
||||
|
||||
`slug` auto-generated from `name` if omitted. Must be 2-64 chars,
|
||||
lowercase alphanumeric and hyphens. Unique within scope (team or global).
|
||||
|
||||
`entry_mode`: `public_link` (visitors can start via URL) or `team_only`
|
||||
(only team members can start instances).
|
||||
|
||||
**Auth:** `workflow.create` permission required.
|
||||
|
||||
**Response:** 201 with workflow object.
|
||||
|
||||
### Get Workflow
|
||||
|
||||
```
|
||||
GET /workflows/:id
|
||||
```
|
||||
|
||||
Returns workflow with its stages included in the `stages` array.
|
||||
|
||||
**Auth:** Authenticated.
|
||||
|
||||
### Update Workflow
|
||||
|
||||
```
|
||||
PATCH /workflows/:id
|
||||
```
|
||||
|
||||
Partial update. Accepted fields: `name`, `description`, `branding`,
|
||||
`entry_mode`, `is_active`, `on_complete`, `retention`, `webhook_url`.
|
||||
|
||||
Any edit increments the `version` number.
|
||||
|
||||
**Auth:** `workflow.create` permission required.
|
||||
|
||||
### Delete Workflow
|
||||
|
||||
```
|
||||
DELETE /workflows/:id
|
||||
```
|
||||
|
||||
Cascades: deletes all stages, versions, and active instances.
|
||||
|
||||
**Auth:** `workflow.create` permission required.
|
||||
|
||||
### Workflow Object
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"team_id": "uuid|null",
|
||||
"name": "Customer Intake",
|
||||
"slug": "customer-intake",
|
||||
"description": "...",
|
||||
"branding": { "accent_color": "#2563eb", "logo_url": "...", "tagline": "..." },
|
||||
"entry_mode": "public_link|team_only",
|
||||
"is_active": true,
|
||||
"version": 2,
|
||||
"on_complete": { "action": "start_workflow", "target_slug": "follow-up", "data_map": {"name": "customer_name"} },
|
||||
"retention": { "mode": "archive|delete", "delete_after_days": 90 },
|
||||
"webhook_url": "https://...",
|
||||
"webhook_secret": "...",
|
||||
"created_by": "uuid",
|
||||
"created_at": "...",
|
||||
"updated_at": "...",
|
||||
"stages": [...]
|
||||
}
|
||||
```
|
||||
|
||||
## Stages
|
||||
|
||||
Ordered steps within a workflow. Each stage has a driving persona and
|
||||
optional human assignment team.
|
||||
|
||||
### List Stages
|
||||
|
||||
```
|
||||
GET /workflows/:id/stages
|
||||
```
|
||||
|
||||
Returns `{ "data": [...] }` ordered by `ordinal`.
|
||||
|
||||
**Auth:** Authenticated.
|
||||
|
||||
### Create Stage
|
||||
|
||||
```
|
||||
POST /workflows/:id/stages
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Collect Info",
|
||||
"ordinal": 0,
|
||||
"persona_id": "uuid|null",
|
||||
"assignment_team_id": "uuid|null",
|
||||
"form_template": { "fields": ["name", "email", "issue"] },
|
||||
"history_mode": "full|summary|fresh",
|
||||
"auto_transition": false,
|
||||
"transition_rules": { "auto_assign": "round_robin" }
|
||||
}
|
||||
```
|
||||
|
||||
`history_mode`: what chat history the next stage sees. `full` = complete,
|
||||
`summary` = utility-role summary, `fresh` = clean slate.
|
||||
|
||||
`form_template`: injected into the completion system prompt as guidance
|
||||
for what the persona should collect. Not rendered as UI — the LLM is
|
||||
told what to ask for.
|
||||
|
||||
`transition_rules.auto_assign`: `round_robin` assigns to team members
|
||||
in rotation. Null = unassigned (manual claim).
|
||||
|
||||
**Auth:** `workflow.create` permission required.
|
||||
|
||||
### Update Stage
|
||||
|
||||
```
|
||||
PUT /workflows/:id/stages/:sid
|
||||
```
|
||||
|
||||
Full replacement of stage fields.
|
||||
|
||||
**Auth:** `workflow.create` permission required.
|
||||
|
||||
### Delete Stage
|
||||
|
||||
```
|
||||
DELETE /workflows/:id/stages/:sid
|
||||
```
|
||||
|
||||
**Auth:** `workflow.create` permission required.
|
||||
|
||||
### Reorder Stages
|
||||
|
||||
```
|
||||
PATCH /workflows/:id/stages/reorder
|
||||
```
|
||||
|
||||
```json
|
||||
{ "ordered_ids": ["stage-uuid-2", "stage-uuid-1", "stage-uuid-3"] }
|
||||
```
|
||||
|
||||
Sets ordinals based on array position.
|
||||
|
||||
**Auth:** `workflow.create` permission required.
|
||||
|
||||
### Stage Object
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"workflow_id": "uuid",
|
||||
"ordinal": 0,
|
||||
"name": "Collect Info",
|
||||
"persona_id": "uuid|null",
|
||||
"assignment_team_id": "uuid|null",
|
||||
"form_template": {},
|
||||
"history_mode": "full",
|
||||
"auto_transition": false,
|
||||
"transition_rules": {},
|
||||
"created_at": "..."
|
||||
}
|
||||
```
|
||||
|
||||
## Versions (Immutable Snapshots)
|
||||
|
||||
### Publish
|
||||
|
||||
```
|
||||
POST /workflows/:id/publish
|
||||
```
|
||||
|
||||
Creates an immutable snapshot of the current workflow definition, stages,
|
||||
and persona tool grants. Running instances are pinned to their version.
|
||||
|
||||
Returns 201 with the version object. Returns 409 if the current version
|
||||
number has already been published (edit the workflow to increment first).
|
||||
|
||||
**Auth:** `workflow.create` permission required.
|
||||
|
||||
### Get Version
|
||||
|
||||
```
|
||||
GET /workflows/:id/versions/:version
|
||||
```
|
||||
|
||||
Returns the version snapshot.
|
||||
|
||||
**Auth:** Authenticated.
|
||||
|
||||
### Version Object
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"workflow_id": "uuid",
|
||||
"version_number": 2,
|
||||
"snapshot": { "workflow": {...}, "stages": [...] },
|
||||
"created_at": "..."
|
||||
}
|
||||
```
|
||||
|
||||
## Instances
|
||||
|
||||
A workflow **instance** is a channel with `type=workflow`. The channel's
|
||||
`workflow_id`, `workflow_version`, `current_stage`, `stage_data`, and
|
||||
`workflow_status` columns track instance state.
|
||||
|
||||
### Start Instance
|
||||
|
||||
```
|
||||
POST /workflows/:id/start
|
||||
```
|
||||
|
||||
Creates a new workflow channel, sets `current_stage=0`, pins the latest
|
||||
published version. Returns the channel object.
|
||||
|
||||
**Auth:** Authenticated (for team members). Visitors use the entry API.
|
||||
|
||||
### Get Instance Status
|
||||
|
||||
```
|
||||
GET /channels/:id/workflow/status
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"workflow_id": "uuid",
|
||||
"workflow_version": 2,
|
||||
"current_stage": 1,
|
||||
"stage_data": { "name": "Jane", "email": "jane@co.com" },
|
||||
"status": "active|completed|stale",
|
||||
"last_activity_at": "..."
|
||||
}
|
||||
```
|
||||
|
||||
**Auth:** Authenticated.
|
||||
|
||||
### Advance Stage
|
||||
|
||||
```
|
||||
POST /channels/:id/workflow/advance
|
||||
```
|
||||
|
||||
```json
|
||||
{ "data": { "name": "Jane", "email": "jane@co.com" } }
|
||||
```
|
||||
|
||||
Merges `data` into the channel's accumulated stage data and moves to
|
||||
the next stage. If the current stage is the last, marks the workflow
|
||||
as `completed` and sets `ai_mode=off`.
|
||||
|
||||
On completion, triggers `on_complete` chaining and webhook delivery
|
||||
if configured.
|
||||
|
||||
**Auth:** Authenticated.
|
||||
|
||||
### Reject (Go Back)
|
||||
|
||||
```
|
||||
POST /channels/:id/workflow/reject
|
||||
```
|
||||
|
||||
```json
|
||||
{ "reason": "Missing required field: email" }
|
||||
```
|
||||
|
||||
Moves back one stage. Cannot go below stage 0. `reason` is required —
|
||||
persisted as a system message in the channel history.
|
||||
|
||||
**Auth:** Authenticated.
|
||||
|
||||
### Workflow Advance Tool
|
||||
|
||||
The `workflow_advance` tool is available to personas during workflow
|
||||
completions. When the LLM determines enough data has been collected,
|
||||
it calls this tool to trigger the stage transition programmatically.
|
||||
The tool has a `RequireWorkflow` predicate — it only appears in
|
||||
workflow channels.
|
||||
|
||||
## Assignments
|
||||
|
||||
Human review queue for workflow stages that have an `assignment_team_id`.
|
||||
|
||||
### List My Assignments
|
||||
|
||||
```
|
||||
GET /workflow-assignments/mine
|
||||
```
|
||||
|
||||
Returns assignments where `assigned_to` is the current user or
|
||||
`status=unassigned` for the user's teams.
|
||||
|
||||
**Auth:** Authenticated.
|
||||
|
||||
### Claim Assignment
|
||||
|
||||
```
|
||||
POST /workflow-assignments/:id/claim
|
||||
```
|
||||
|
||||
Sets `assigned_to` to the current user, status to `claimed`.
|
||||
Returns 409 if the assignment is already claimed or not found.
|
||||
|
||||
**Auth:** Authenticated.
|
||||
|
||||
### Complete Assignment
|
||||
|
||||
```
|
||||
POST /workflow-assignments/:id/complete
|
||||
```
|
||||
|
||||
Marks assignment as `completed`. Does NOT auto-advance the stage —
|
||||
the human reviews and manually advances or the persona tool does it.
|
||||
Returns 409 if the assignment is not in `claimed` state.
|
||||
|
||||
**Auth:** Authenticated.
|
||||
|
||||
### List Team Assignments
|
||||
|
||||
```
|
||||
GET /teams/:teamId/assignments?status=unassigned
|
||||
```
|
||||
|
||||
Assignments for the team, filtered by `status` (default: `unassigned`).
|
||||
Valid values: `unassigned`, `claimed`, `completed`.
|
||||
|
||||
**Auth:** Authenticated (team member).
|
||||
|
||||
### Assignment Object
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"channel_id": "uuid",
|
||||
"stage": 1,
|
||||
"team_id": "uuid",
|
||||
"assigned_to": "uuid|null",
|
||||
"status": "unassigned|claimed|completed",
|
||||
"created_at": "...",
|
||||
"claimed_at": "...|null",
|
||||
"completed_at": "...|null"
|
||||
}
|
||||
```
|
||||
|
||||
## Visitor Entry
|
||||
|
||||
Public-facing workflow entry for unauthenticated visitors.
|
||||
|
||||
### Landing Page
|
||||
|
||||
```
|
||||
GET /w/:id
|
||||
GET /w/:id/:slug
|
||||
```
|
||||
|
||||
Server-rendered branded page. Shows workflow name, tagline, branding
|
||||
colors, and a "Start" button. If the visitor has an existing session
|
||||
cookie for this workflow, resumes the conversation.
|
||||
|
||||
Not an API endpoint — returns HTML.
|
||||
|
||||
### Start Visitor Session
|
||||
|
||||
```
|
||||
POST /api/v1/workflow-entry/:scope/:slug
|
||||
```
|
||||
|
||||
No request body. Display name is auto-generated (`Visitor`,
|
||||
`Visitor 2`, etc.).
|
||||
|
||||
`:scope` is a team ID or `global`. Creates a session participant,
|
||||
creates a workflow channel, sets `allow_anonymous=true`. Returns:
|
||||
|
||||
```json
|
||||
{
|
||||
"channel_id": "uuid",
|
||||
"session_id": "uuid",
|
||||
"redirect_to": "/w/channel-uuid"
|
||||
}
|
||||
```
|
||||
|
||||
The session token is set as a cookie (`sb_session`) for subsequent
|
||||
requests. `redirect_to` is the workflow chat surface URL.
|
||||
|
||||
### Visitor Message/Completion
|
||||
|
||||
Visitors interact through the workflow channel using session-scoped routes:
|
||||
|
||||
```
|
||||
POST /api/v1/w/:id/messages
|
||||
GET /api/v1/w/:id/messages
|
||||
POST /api/v1/w/:id/completions
|
||||
```
|
||||
|
||||
These use `AuthOrSession` middleware — the session cookie identifies
|
||||
the visitor without requiring a JWT.
|
||||
|
||||
## Staleness Sweep
|
||||
|
||||
Background goroutine. Checks active workflow instances for staleness
|
||||
(no activity within `WORKFLOW_STALE_HOURS`, default 72). Stale instances
|
||||
are marked `workflow_status=stale`. Retention policy then applies:
|
||||
`archive` keeps the channel, `delete` removes it after `delete_after_days`.
|
||||
|
||||
## On-Complete Chaining
|
||||
|
||||
When a workflow completes and has `on_complete` configured:
|
||||
|
||||
```json
|
||||
{
|
||||
"on_complete": {
|
||||
"action": "start_workflow",
|
||||
"target_slug": "follow-up-survey",
|
||||
"data_map": { "name": "customer_name", "case_id": "case_id" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`action` must be `start_workflow`. `target_slug` identifies the workflow
|
||||
to chain into (looked up in the same team scope). `data_map` is a flat
|
||||
key remapping from source stage data keys to target initial data keys.
|
||||
If `data_map` is empty/omitted, all stage data is passed through as-is.
|
||||
|
||||
The system starts a new instance of the target workflow, passing mapped
|
||||
stage data as initial context. Webhook delivery (if configured) fires
|
||||
before chaining.
|
||||
|
||||
## WebSocket Events
|
||||
|
||||
| Event | When |
|
||||
|-------|------|
|
||||
| `workflow.advanced` | Stage transition (includes new stage info) |
|
||||
| `workflow.completed` | Workflow finished |
|
||||
| `workflow.assigned` | New assignment created |
|
||||
| `workflow.claimed` | Assignment claimed by user |
|
||||
Reference in New Issue
Block a user