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.
It is a working example of the routing pattern most real deployments end up needing.
The manifest
{
"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
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
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 — building on a template.
- Workflow Versioning — versioning your own edits.
- Subflows & Batch Subflows — the pattern
main-chatuses.