docs: document portable workflow import/export YAML format (#1176)

This commit is contained in:
Carlos Florêncio
2026-05-30 22:53:57 +01:00
committed by GitHub
parent 623044a8d4
commit e708a76701
10 changed files with 442 additions and 0 deletions
@@ -1,3 +1,8 @@
# Internal workflow TEMPLATE definition — NOT the portable import/export format.
# Steps here reference each other by string `step_id`; the importable YAML uses a
# `version`/`type`/`workflows` envelope and `step_position`. Don't paste this file
# (or its `step_id` transitions) into the workflow import endpoint.
# Format reference: docs/workflow-import-export.md
id: architecture id: architecture
name: Architecture name: Architecture
description: >- description: >-
@@ -1,3 +1,8 @@
# Internal workflow TEMPLATE definition — NOT the portable import/export format.
# Steps here reference each other by string `step_id`; the importable YAML uses a
# `version`/`type`/`workflows` envelope and `step_position`. Don't paste this file
# (or its `step_id` transitions) into the workflow import endpoint.
# Format reference: docs/workflow-import-export.md
id: feature-dev id: feature-dev
name: Feature Dev name: Feature Dev
description: >- description: >-
@@ -1,3 +1,8 @@
# Internal workflow TEMPLATE definition — NOT the portable import/export format.
# Steps here reference each other by string `step_id`; the importable YAML uses a
# `version`/`type`/`workflows` envelope and `step_position`. Don't paste this file
# (or its `step_id` transitions) into the workflow import endpoint.
# Format reference: docs/workflow-import-export.md
id: improve-kandev id: improve-kandev
name: Improve Kandev name: Improve Kandev
description: >- description: >-
+5
View File
@@ -1,3 +1,8 @@
# Internal workflow TEMPLATE definition — NOT the portable import/export format.
# Steps here reference each other by string `step_id`; the importable YAML uses a
# `version`/`type`/`workflows` envelope and `step_position`. Don't paste this file
# (or its `step_id` transitions) into the workflow import endpoint.
# Format reference: docs/workflow-import-export.md
id: simple id: simple
name: Kanban name: Kanban
description: >- description: >-
@@ -1,3 +1,8 @@
# Internal workflow TEMPLATE definition — NOT the portable import/export format.
# Steps here reference each other by string `step_id`; the importable YAML uses a
# `version`/`type`/`workflows` envelope and `step_position`. Don't paste this file
# (or its `step_id` transitions) into the workflow import endpoint.
# Format reference: docs/workflow-import-export.md
id: office-default id: office-default
name: Office Default name: Office Default
description: >- description: >-
@@ -1,3 +1,8 @@
# Internal workflow TEMPLATE definition — NOT the portable import/export format.
# Steps here reference each other by string `step_id`; the importable YAML uses a
# `version`/`type`/`workflows` envelope and `step_position`. Don't paste this file
# (or its `step_id` transitions) into the workflow import endpoint.
# Format reference: docs/workflow-import-export.md
id: standard id: standard
name: Plan & Build name: Plan & Build
description: >- description: >-
@@ -1,3 +1,8 @@
# Internal workflow TEMPLATE definition — NOT the portable import/export format.
# Steps here reference each other by string `step_id`; the importable YAML uses a
# `version`/`type`/`workflows` envelope and `step_position`. Don't paste this file
# (or its `step_id` transitions) into the workflow import endpoint.
# Format reference: docs/workflow-import-export.md
id: pr-review id: pr-review
name: PR Review name: PR Review
description: >- description: >-
@@ -1,3 +1,8 @@
# Internal workflow TEMPLATE definition — NOT the portable import/export format.
# Steps here reference each other by string `step_id`; the importable YAML uses a
# `version`/`type`/`workflows` envelope and `step_position`. Don't paste this file
# (or its `step_id` transitions) into the workflow import endpoint.
# Format reference: docs/workflow-import-export.md
id: routine id: routine
name: Routine name: Routine
description: System workflow for routine-fired tasks. Single auto-completing step. description: System workflow for routine-fired tasks. Single auto-completing step.
+398
View File
@@ -0,0 +1,398 @@
# Workflow Import / Export — Portable YAML Format
Kandev workflows can be exported to a portable YAML file and imported into
another workspace (or another Kandev install). This page is the reference for
that file format: the envelope, every field, the trigger config shapes, and the
matching rules applied on import.
Everything below is derived from the source of truth:
- `apps/backend/internal/workflow/models/export.go` — the portable structs (`WorkflowExport`, `WorkflowPortable`, `StepPortable`, `AgentProfilePortable`) and `Validate()`.
- `apps/backend/internal/workflow/models/models.go` — the `StepEvents` triggers and their action types.
- `apps/backend/internal/workflow/service/service.go` — `ImportWorkflows` / `importSingleWorkflow` (matching + position→ID mapping).
> **Looking for the built-in workflows instead?** See [Workflows](workflow-tips.md)
> for the default templates and when to use each.
---
## How import/export is exposed
The format is YAML over three HTTP endpoints (all under `/api/v1`):
| Method | Path | Purpose | Response / Body |
|--------|------|---------|-----------------|
| `GET` | `/workflows/:id/export` | Export a single workflow | `application/x-yaml` |
| `GET` | `/workspaces/:id/workflows/export` | Export **all** workflows in a workspace | `application/x-yaml` |
| `POST` | `/workspaces/:id/workflows/import` | Import workflows into a workspace | YAML request body, **max 1 MB** |
Export marshals the structs to YAML; import unmarshals the request body, runs
`Validate()`, then creates each workflow. The same struct shapes also marshal to
JSON (every field carries both `yaml` and `json` tags), but the endpoints speak
YAML.
---
## Envelope
The top-level document is a `WorkflowExport`:
```yaml
version: 1
type: kandev_workflow
workflows:
- # WorkflowPortable …
- # WorkflowPortable …
```
| Field | Type | Required | Notes |
|-------|------|:--------:|-------|
| `version` | int | yes | Must be exactly `1` (`ExportVersion`). Any other value is rejected. |
| `type` | string | yes | Must be exactly `kandev_workflow` (`ExportType`). |
| `workflows` | list | yes | Must contain at least one workflow; an empty list is rejected. |
`Validate()` rejects the document with a descriptive error if `version`,
`type`, or the workflow list fails these checks.
---
## `WorkflowPortable`
Each entry under `workflows:`:
```yaml
- name: My Workflow
description: Optional human description.
agent_profile: # optional, workflow-level default agent
agent_name: Claude Code
model: claude-opus-4-7
mode: default
steps:
- # StepPortable …
```
| Field | Type | Required | Notes |
|-------|------|:--------:|-------|
| `name` | string | yes | Workflow name. Required by `Validate()`. Used for **dedup on import** (see [Import rules](#import-matching-rules)). |
| `description` | string | no | Omitted from export when empty. |
| `agent_profile` | object | no | Workflow-level default agent profile. See [Agent profiles](#agent-profiles). Omitted when the workflow has no profile. |
| `steps` | list | — | The workflow's steps. See [`StepPortable`](#stepportable). |
Instance-specific fields (IDs, timestamps, workspace association) are **not**
part of the portable format — they are regenerated on import.
---
## `StepPortable`
Each entry under `steps:`:
```yaml
- name: In Progress
position: 1
color: bg-blue-500
prompt: |
Optional per-step prompt sent to the agent.
is_start_step: true
show_in_command_panel: true
allow_manual_move: true
auto_archive_after_hours: 24
agent_profile: # optional, step-level agent override
agent_name: Claude Code
model: claude-opus-4-7
mode: default
events:
# triggers — see "Triggers" below
```
| Field | Type | Required | Default in export | Notes |
|-------|------|:--------:|-------------------|-------|
| `name` | string | yes | — | Required by `Validate()`. |
| `position` | int | yes | always emitted | **0-based** index. **Must be unique** within the workflow — duplicates are rejected. Also the anchor for `move_to_step` references (see below). |
| `color` | string | — | always emitted | Tailwind background class, e.g. `bg-blue-500`, `bg-green-500`, `bg-neutral-400`. |
| `prompt` | string | no | omitted when empty | Per-step prompt template. Supports placeholders such as `{{task_prompt}}`. |
| `events` | object | — | always emitted | Triggers and their actions. See [Triggers](#triggers). |
| `is_start_step` | bool | — | always emitted | Marks the step new tasks start in. |
| `show_in_command_panel` | bool | — | always emitted | Whether the step appears in the command panel. |
| `allow_manual_move` | bool | — | always emitted | Whether users can drag the task into this step manually. |
| `auto_archive_after_hours` | int | no | omitted when `0` | Auto-archive a task this many hours after it lands in the step. `0` / omitted = never. |
| `agent_profile` | object | no | omitted when none | Step-level agent profile, overriding the workflow default. See [Agent profiles](#agent-profiles). |
> **Note:** `position`, `color`, `events`, and the three booleans carry no
> `omitempty`, so they always appear in exported files (even when `false` or
> empty). The `prompt`, `auto_archive_after_hours`, and `agent_profile` fields
> are omitted when unset.
> **Not in the portable format:** office/Phase-2 step metadata — `stage_type`,
> step participants (reviewers/approvers), and recorded decisions — is **not**
> exported or imported. Only the fields listed above round-trip.
---
## Agent profiles
`agent_profile` appears at both the workflow level and the step level
(`AgentProfilePortable`):
```yaml
agent_profile:
agent_name: Claude Code # required
model: claude-opus-4-7 # optional, omitted when empty
mode: default # optional, omitted when empty
```
| Field | Type | Notes |
|-------|------|-------|
| `agent_name` | string | The agent's **display name** (`AgentDisplayName`), not its internal ID. |
| `model` | string | Model identifier. Omitted when empty. |
| `mode` | string | Agent mode. Omitted when empty. |
Profiles are stored by **value** (name/model/mode) rather than by ID precisely
so they can be re-matched in a different workspace. See the matching rule below.
---
## Triggers
`events` holds the step's triggers. Each trigger is a list of actions; an
action is `{ type, config }` where `config` is an optional map.
There are two families of triggers.
### Kanban-era triggers (round-trip today)
These four triggers use typed action slices and are fully supported by
import/export:
| Trigger | Allowed action `type`s | `config` |
|---------|------------------------|----------|
| `on_enter` | `enable_plan_mode`, `auto_start_agent`, `reset_agent_context`, `clear_decisions`, `queue_run`, `queue_run_for_each_participant` | first four take no config; `queue_run` / `queue_run_for_each_participant` use the same config keys as the office triggers (see [Office triggers](#office--phase-2-triggers-intended-format--see-caveat)) |
| `on_turn_start` | `move_to_next`, `move_to_previous`, `move_to_step` | `move_to_step` needs `step_position` |
| `on_turn_complete` | `move_to_next`, `move_to_previous`, `move_to_step`, `disable_plan_mode` | `move_to_step` needs `step_position` |
| `on_exit` | `disable_plan_mode` | — |
Example:
```yaml
events:
on_enter:
- type: auto_start_agent
on_turn_start:
- type: move_to_next
on_turn_complete:
- type: move_to_step
config:
step_position: 2
```
#### `move_to_step` uses `step_position`, not `step_id`
This is the one transformation the portable format performs. Internally a step
transition references a target step by its database `step_id`. Because IDs are
not portable, export rewrites every `move_to_step` action's
`config.step_id` → `config.step_position`, and import rewrites it back to a
freshly generated `step_id`.
So in a portable file you **always** write:
```yaml
- type: move_to_step
config:
step_position: 2 # the target step's `position`, NOT a step id
```
`Validate()` enforces that every `move_to_step` `step_position` matches an
existing step's `position` in the same workflow; a dangling reference is
rejected. Any additional keys in the action's `config` are preserved verbatim
through the conversion.
> **Built-in template YAMLs differ.** The embedded templates under
> `apps/backend/config/workflows/*.yml` use string `step_id`s (e.g.
> `step_id: review`) because they are *template definitions*, a different schema
> from this portable export format. Do not copy their `step_id:` form into a
> portable import file — use `step_position:`.
### Office / Phase-2 triggers (intended format — see caveat)
The seven event-driven "office" triggers use the generic action shape
(`GenericAction`):
| Trigger | Fires when |
|---------|-----------|
| `on_comment` | A comment is added to the task. |
| `on_blocker_resolved` | A blocker on the task is resolved. |
| `on_children_completed` | All child tasks complete. |
| `on_approval_resolved` | An approval request is decided. |
| `on_heartbeat` | A periodic heartbeat tick. |
| `on_budget_alert` | A budget threshold is crossed. |
| `on_agent_error` | The agent errors out. |
Each holds a list of generic actions whose `type` is one of `queue_run`,
`clear_decisions`, or `queue_run_for_each_participant`, with a free-form
`config` map interpreted by the engine. Common keys: `target` (e.g. `primary`,
`workspace.ceo_agent`), `task_id` (e.g. `this`), `reason`, and `role`.
Intended shape:
```yaml
events:
on_comment:
- type: queue_run
config:
target: primary
task_id: this
reason: task_comment
on_children_completed:
- type: queue_run
config:
target: primary
task_id: this
reason: children_completed
```
> [!WARNING]
> **These seven triggers do not round-trip today** (tracked by
> [#1109](https://github.com/kdlbs/kandev/issues/1109)). The portable
> conversion (`remapStepEvents` in `export.go`) only copies `on_enter`,
> `on_turn_start`, `on_turn_complete`, and `on_exit`. As a result the office
> triggers are **dropped on export** (they never appear in an exported file) and
> **dropped on import** (if you hand-author them, they are parsed but discarded
> before the step is persisted). The format above documents the *intended*
> shape; until #1109 lands, office-style workflows will not survive a
> round-trip. Coordinate with that fix before relying on it.
---
## Import matching rules
`ImportWorkflows` → `importSingleWorkflow` applies these rules:
1. **Validation first.** The whole document is run through `Validate()`
(envelope + per-workflow name/position/`move_to_step` checks) before
anything is created. A failure aborts the entire import.
2. **Workflow dedup by name.** Existing workflows in the target workspace are
listed; any imported workflow whose `name` already exists is **skipped**
(reported under `skipped`). The rest are **created** (reported under
`created`). The result is `{ created: [...], skipped: [...] }`.
3. **Fresh step IDs + position→ID mapping.** Every step gets a newly generated
UUID. Import builds a `position → new step ID` map, then rewrites each
`move_to_step` action's `step_position` back into the new `step_id`. This is
why step positions must be unique and why `move_to_step` references positions.
4. **Agent profile matched by name/model/mode.** For each `agent_profile`
(workflow-level and step-level), import searches the target workspace's
agents and profiles for one whose **`agent_name` (display name), `model`, and
`mode` all match exactly**. On a match, that profile's ID is assigned. On **no
match**, the field is left empty — the workflow/step is created **without** an
agent profile (silently; no error). Match the names/models/modes in the
target workspace if you need the profile wired up.
---
## Complete worked example
A self-contained, valid import file with two workflows: a four-step kanban loop
(using `move_to_step` with `step_position`) and a two-step planning flow with a
per-step agent profile and prompt.
```yaml
version: 1
type: kandev_workflow
workflows:
- name: Simple Kanban
description: Assign → run → review loop.
steps:
- name: Backlog
position: 0
color: bg-neutral-400
is_start_step: false
show_in_command_panel: false
allow_manual_move: true
events:
on_turn_start:
- type: move_to_next
- name: In Progress
position: 1
color: bg-blue-500
is_start_step: true
show_in_command_panel: true
allow_manual_move: true
events:
on_enter:
- type: auto_start_agent
on_turn_complete:
- type: move_to_step
config:
step_position: 2
- name: Review
position: 2
color: bg-yellow-500
is_start_step: false
show_in_command_panel: true
allow_manual_move: true
events:
on_turn_start:
- type: move_to_previous
- name: Done
position: 3
color: bg-green-500
is_start_step: false
show_in_command_panel: false
allow_manual_move: true
auto_archive_after_hours: 168
events:
on_turn_start:
- type: move_to_step
config:
step_position: 1
- name: Plan & Build
description: Plan first, then implement.
agent_profile:
agent_name: Claude Code
model: claude-opus-4-7
mode: default
steps:
- name: Plan
position: 0
color: bg-purple-500
is_start_step: true
show_in_command_panel: true
allow_manual_move: true
prompt: |
Analyze the task and produce an implementation plan. Do not write code.
Save the plan with create_task_plan_kandev, then stop for review.
agent_profile:
agent_name: Claude Code
model: claude-opus-4-7
mode: plan
events:
on_enter:
- type: enable_plan_mode
- type: auto_start_agent
on_exit:
- type: disable_plan_mode
- name: Implementation
position: 1
color: bg-blue-500
is_start_step: false
show_in_command_panel: true
allow_manual_move: true
prompt: |
Retrieve the plan with get_task_plan_kandev, then implement it.
events:
on_enter:
- type: auto_start_agent
```
Importing this into a fresh workspace creates both workflows
(`created: [Simple Kanban, Plan & Build]`). Re-importing the same file leaves
them untouched (`skipped: [Simple Kanban, Plan & Build]`). The `Claude Code`
agent profiles wire up only if a profile with that exact display name, model,
and mode exists in the target workspace.
+4
View File
@@ -4,6 +4,10 @@ Kandev ships with five workflow templates. Each defines a sequence of steps with
You can use these as-is or **create custom workflows** tailored to you or your team. You can use these as-is or **create custom workflows** tailored to you or your team.
> **Sharing workflows?** Workflows can be exported to and imported from a
> portable YAML file. See [Workflow Import / Export](workflow-import-export.md)
> for the full format reference and a worked example.
## Default Workflows ## Default Workflows
### Kanban ### Kanban