# Workflow Templates

> Applies to: ChengOS v0.1.0+ ｜ Last verified: 2026-08-12 ｜ Source: `crates/cheng-core/src/domain/workflow_template.rs`, `crates/cheng-api/src/rest/handlers/workflow_template/mod.rs`, `deploy/workflow-templates/`, `chengflow-ui/src/api/workflowTemplateSync.ts`

A workflow template is a built-in workflow that ChengOS installs into your workspace as an
**ordinary, editable workflow**. Once installed the copy is yours: edit it freely. What the
template system adds is the ability to tell you, later, that upstream changed — and to let you
decide what to do about it.

Installed copies are private workspace workflows, never public templates.

## The catalog

Templates ship under `deploy/workflow-templates/`, one directory per template, each containing
`template.json` (the manifest) and `workflow.json` (the graph).

Nine templates ship today:

| Key | Sort | What it demonstrates |
|---|---:|---|
| `create-workflow` | 10 | Building a workflow from a description |
| `http-tools` | 20 | Calling external HTTP APIs |
| `main-chat` | 30 | Intent routing to three sub-workflows |
| `memory-chat` | 40 | Conversation with history |
| `no-memory-chat` | 50 | Single-turn exchange |
| `scheduled-task` | 60 | Time-triggered execution |
| `skills-run` | 70 | Running a skill |
| `skills-importer` | 80 | Importing a skill package |
| `tools-user` | 90 | A ReAct agent doing file operations |

`main-chat` is the one to read first: a `utils/condition_router` evaluates user intent and
dispatches to `memory-chat`, `no-memory-chat`, or `tools-user` as [subflows](/docs/editor-subflows).
It is a working example of the routing pattern most real deployments end up needing.

### The manifest

```json
{
  "schemaVersion": 1,
  "key": "main-chat",
  "version": "1.0.0",
  "name": "main_chat",
  "description": "…",
  "sortOrder": 30,
  "enabledByDefault": true
}
```

`key` **must equal the directory name**, and must be stable, unique, and lowercase kebab-case.

`version` is display and audit metadata **only**. Change detection uses an identity-independent
**content hash** (`sha256:…`) of the canonical workflow file — with `workflowId` deliberately
omitted, so the hash describes the graph rather than the installation. A template whose version
string never changes still gets a correct update signal.

## Update states

Installation is tracked by a durable record linking `(workspace_id, template_key)` to one
`workflow_id`, and storing `applied_hash` — the upstream revision last installed or synced.
Comparing three things — upstream hash, `applied_hash`, and the current installed file's hash —
gives a **three-way** state per template:

| State | Meaning |
|---|---|
| `new` | No installation record in this workspace |
| `up_to_date` | Neither upstream nor your copy changed |
| `locally_modified` | You edited your copy; upstream is unchanged |
| `update_available` | Upstream changed; your copy is still clean |
| `conflict` | **Both** changed |
| `destination_conflict` | Not installed, but the canonical destination is occupied |
| `missing_file` | Installation record exists but the file is gone |
| `invalid_copy` | The destination exists but cannot be decoded or validated |
| `inconsistent` | Installation record, workflow row, path, and workflow id disagree |
| `orphaned` | Installed from a template key that no longer exists in the catalog |

The distinction between `locally_modified` and `conflict` is the whole point of storing
`applied_hash` separately from the current file hash. Without it, any edited workflow would look
like a conflict and the system would nag you forever.

## Syncing

Two modes:

| Mode | Behaviour |
|---|---|
| `add_missing` | Install `new` and explicitly selected templates; **never overwrite** |
| `overwrite_all` | Install missing defaults **and overwrite** existing installations |

The flow is preview-then-confirm: `GET /updates` returns the per-template states, you choose,
and `POST /sync` applies. The sync is guarded optimistically — if the state changed between the
preview and the confirmation, that item comes back as `state_changed` rather than being applied
against a stale view.

Per-template outcomes:

| Outcome | Meaning |
|---|---|
| `installed` | A new installation was created |
| `overwritten` | An existing installation took upstream content |
| `already_installed` | Left untouched (`add_missing` on an existing install) |
| `skipped` | Not eligible for the requested mode |
| `failed` | See `error_code` / `error_message` |
| `state_changed` | State moved between preview and confirmation |
| `not_found` | The key is not in the current catalog |
| `blocked` | A destination conflict or unresolved inconsistency |

Error codes are stable machine-readable strings — `TEMPLATE_NOT_FOUND`,
`INVALID_TEMPLATE_SELECTION`, `DESTINATION_CONFLICT`, `STATE_CHANGED`, … — shared by the sync
service and the HTTP layer, so a client can branch on them rather than parsing messages.

**`overwrite_all` discards local edits** on installed templates. Where you have customised a
template and want to keep the changes, detach it first.

## Detaching

```text
DELETE /installations/:template_key
```

Detaching removes the installation record while leaving the workflow itself intact. The workflow
becomes an ordinary workspace workflow with no template link, so it stops appearing in update
checks and can never be overwritten by a sync. This is the right move once a template copy has
diverged into something of your own.

Detach is also optimistically guarded: if the installation changed since the confirmation was
captured, it returns a conflict and asks you to refresh.

## REST surface

```text
GET    /workflows/templates          list catalog templates
GET    /workflows/templates/:id      one template
PUT    /workflows/:id/template       mark a workflow as a template
DELETE /workflows/:id/template       unmark it

GET    /updates                      per-template update states (workspace-scoped)
POST   /sync                         apply a sync batch
DELETE /installations/:template_key  detach
```

The catalog projection exposed by the API never includes the workflow graph or any filesystem
path — only key, name, description, category, version, sort order, and `enabledByDefault`.

## Next steps

- [Your First Workflow](/docs/quickstart-first-workflow) — building on a template.
- [Workflow Versioning](/docs/editor-versioning) — versioning your own edits.
- [Subflows & Batch Subflows](/docs/editor-subflows) — the pattern `main-chat` uses.
