Workflow Authoring Skill
This skill teaches agents how to author CloseCode ADE workflows — visual flow-based automations built from typed nodes connected by compatible sockets.
1. Overview
A workflow is a directed graph of typed nodes connected by edges. Workflows are persisted as WorkflowDefinition objects with nodes, edges, triggers, and settings.
Key concepts:
- Nodes — Processing units with typed input/output sockets
- Edges — Connections between an output socket on one node and an input socket on another
- Triggers — Binding nodes to external events (manual, cron, webhook, schedule)
- Pipeline Interface — Contract nodes (
pipeline-input / pipeline-output) that define a sub-workflow's external API
Use these closecode MCP tools (activate closecode tools first):
| Tool | Purpose |
|---|
closecode_list_workflow_node_types | Discover all available node kinds with their I/O sockets |
closecode_suggest_compatible_nodes | Find nodes compatible with a given socket |
closecode_create_workflow | Create a new workflow with nodes and edges |
closecode_update_workflow | Update existing workflow (partial) |
closecode_validate_workflow | Validate structure (socket compatibility, cycles, required inputs) |
closecode_run_workflow | Trigger a workflow run (returns runId) |
closecode_get_workflow_run | Check run status and per-node results |
closecode_list_workflows | List all workflows for a project |
closecode_get_workflow | Get full workflow definition |
closecode_delete_workflow | Delete a workflow |
closecode_cancel_workflow_run | Cancel an in-flight run |
closecode_get_workflow_runs | Paginated run history |
3. Node Kind Catalog
All built-in nodes use the kind format builtin.<category>.<name>.
Triggers (category: trigger)
| Kind | Label | Inputs | Outputs |
|---|
builtin.trigger.manual | Manual Trigger | — | fire (trigger) |
builtin.trigger.cron | Cron Trigger | — | fire (trigger) |
builtin.trigger.webhook | Webhook Trigger | — | fire (trigger), body (json), headers (json), method (string) |
builtin.trigger.event | Event Trigger | eventPattern (string) | fire (trigger), payload (json) |
builtin.trigger.schedule | Schedule Trigger | — | fire (trigger), scheduleId (string), scheduleName (string), projectId (string), triggeredAt (string) |
builtin.trigger.dialog-message | Dialog Message Trigger | — | fire (trigger), + dialog/message fields |
builtin.trigger.task-completed | Task Completed Trigger | — | fire (trigger), taskName (string), dialogId (string), status (string), result (task-result) |
builtin.trigger.task-failed | Task Failed Trigger | — | fire (trigger), + task error fields |
builtin.trigger.calendar-upcoming | Calendar Upcoming Trigger | — | fire (trigger), + event fields |
builtin.trigger.file-changed | File Changed Trigger | — | fire (trigger), + file fields |
Agent (category: agent)
| Kind | Label | Inputs | Outputs |
|---|
builtin.agent.invoke | Invoke Agent | fire (trigger), prompt (string, required) | message (message), text (string), done (trigger) |
builtin.task.run | Run Task | fire (trigger) | result (task-result), message (string), done (trigger) |
Control (category: control)
| Kind | Label | Inputs | Outputs |
|---|
builtin.control.condition | Condition | in (any, required), test (boolean) | true (any), false (any) |
builtin.control.switch | Switch | in (any, required) | case1..case4 (any), default (any) |
builtin.control.merge | Merge | in1..in3 (any) | out (any), done (trigger) |
builtin.control.wait | Wait | fire (trigger), in (any) | done (trigger), out (any) |
builtin.control.sub-workflow | Sub-workflow | start (trigger, required) | done (trigger) + dynamic from pipeline |
builtin.control.sub-workflow-fire-and-forget | Sub-workflow (fire & forget) | start (trigger, required) | done (trigger) |
Data (category: data)
| Kind | Label | Inputs | Outputs |
|---|
builtin.data.set | Set Value | fire (trigger) | value (json), done (trigger) |
builtin.data.transform | Transform | in (any, required) | out (any), done (trigger) |
builtin.data.json-parse | JSON Parse | in (string, required) | out (json), done (trigger) |
builtin.data.json-stringify | JSON Stringify | in (json, required) | out (string), done (trigger) |
builtin.data.template | Template | data (json, required) | out (string), done (trigger) |
builtin.data.http-request | HTTP Request | fire (trigger) | body (json), status (number), done (trigger) |
builtin.data.project-search | Project Search | fire (trigger) | results (json), count (number), done (trigger) |
builtin.data.project-analyze | Project Analyze | fire (trigger) | analysis (json), done (trigger) |
I/O (category: io)
| Kind | Label | Inputs | Outputs |
|---|
builtin.io.read-file | Read File | fire (trigger) | content (string), done (trigger) |
builtin.io.write-file | Write File | fire (trigger), content (string, required) | done (trigger) |
builtin.io.cli-execute | CLI Execute | fire (trigger) | stdout (string), exitCode (number), done (trigger) |
builtin.io.service-call | Service Call | body (json) | response (json), ok (boolean), done (trigger) |
Interaction (category: io / interaction)
| Kind | Label | Inputs | Outputs |
|---|
builtin.interaction.human-input | Human Input | trigger (trigger, required), message (string) | response (string), selectedAction (string), done (trigger) |
Notify (category: notify)
| Kind | Label | Inputs | Outputs |
|---|
builtin.notify.log | Log Notify | fire (trigger), text (string) | done (trigger) |
Chat (category: chat)
| Kind | Label | Inputs | Outputs |
|---|
builtin.chat.post | Post Chat Message | fire (trigger), text (string, required), attachments (json) | message (json), id (string), channelId (string), done (trigger) |
builtin.chat.push | Send Push Notification | title (string, required), body (string, required), data (json) | sent (boolean), done (trigger) |
Git (category: git)
| Kind | Label | Inputs | Outputs |
|---|
builtin.git.checkout | Git Checkout | fire (trigger) | done (trigger) |
builtin.git.stage | Git Stage | fire (trigger) | done (trigger) |
builtin.git.commit | Git Commit | fire (trigger) | done (trigger) |
builtin.git.push | Git Push | fire (trigger) | done (trigger) |
builtin.git.create-branch | Create Branch | fire (trigger) | done (trigger) |
Docs (category: docs)
| Kind | Label | Inputs | Outputs |
|---|
builtin.docs.create | Create Document | fire (trigger) | doc (json), id (string), done (trigger) |
builtin.docs.update | Update Document | fire (trigger) | doc (json), done (trigger) |
builtin.docs.todo-create | Create Todo | fire (trigger) | todo (json), id (string), done (trigger) |
Ops (category: ops)
| Kind | Label | Inputs | Outputs |
|---|
builtin.ops.container-exec | Container Exec | fire (trigger) | stdout (string), done (trigger) |
builtin.ops.container-restart | Container Restart | fire (trigger) | done (trigger) |
builtin.ops.env-health-check | Env Health Check | fire (trigger) | status (string), healthy (boolean), checks (json), done (trigger) |
Calendar (category: io / calendar)
| Kind | Label | Inputs | Outputs |
|---|
builtin.calendar.query-events | Query Calendar Events | fire (trigger) | events (json), count (number), done (trigger) |
Pipeline (category: pipeline)
| Kind | Label | Inputs | Outputs |
|---|
builtin.pipeline.input | Pipeline Input | — | Dynamic (from pipeline interface) |
builtin.pipeline.output | Pipeline Output | Dynamic (from pipeline interface) | — |
Entity (category: entity)
| Kind | Label | Inputs | Outputs |
|---|
builtin.entity.user | User | — | ref (entity-ref) |
builtin.entity.role | Role | — | ref (entity-ref) |
4. Socket Types & Compatibility
Socket Types
| Type | Description | Shape |
|---|
trigger | Signal-only, no payload. Fires when upstream completes | Diamond |
any | Untyped passthrough, connects to anything | Circle |
string | Plain text | Circle |
number | Numeric (int or float) | Circle |
json | Structured data (object, array, primitive) | Circle |
message | LLM/dialog message | Hexagon |
task-result | Task run output | Hexagon |
binary | Raw file/blob content | Circle |
boolean | True/false | Square |
file-path | Path relative to project root | Square |
entity-ref | Reference to a project entity (user, role) | Diamond |
Compatibility Rules
isCompatible(from, to) determines if output type from can connect to input type to:
- Same type → always allowed
any on either side → allowed
- Scalar widening to
json → string, number, boolean, entity-ref all widen to json
- Everything else → rejected (use a Transform node for explicit conversion)
// Quick reference:
string → json ✓ json → string ✗ (use transform)
number → json ✓ json → number ✗ (use transform)
boolean → json ✓ json → boolean ✗ (use transform)
entity-ref → json ✓
trigger → trigger ✓ (flow control only)
any → anything ✓
anything → any ✓
5. Workflow Creation Pattern
Step-by-step
-
Discover available nodes:
closecode_list_workflow_node_types()
Returns all kinds with their input/output socket definitions.
-
Plan the flow: Start with a trigger → add processing nodes → add output nodes.
-
Find compatible nodes for a specific socket:
closecode_suggest_compatible_nodes(
sourceNodeKind: "builtin.trigger.manual",
sourceHandleId: "fire",
direction: "downstream"
)
-
Create the workflow with nodes and edges:
json
1{
2 "title": "My Workflow",
3 "nodes": [
4 { "id": "t1", "kind": "builtin.trigger.manual", "position": { "x": 0, "y": 0 }, "config": {} },
5 { "id": "n1", "kind": "builtin.io.read-file", "position": { "x": 300, "y": 0 }, "config": { "path": "input.txt" } },
6 { "id": "n2", "kind": "builtin.notify.log", "position": { "x": 600, "y": 0 }, "config": {} }
7 ],
8 "edges": [
9 { "id": "e1", "source": "t1", "sourceHandle": "fire", "target": "n1", "targetHandle": "fire" },
10 { "id": "e2", "source": "n1", "sourceHandle": "content", "target": "n2", "targetHandle": "text" },
11 { "id": "e3", "source": "n1", "sourceHandle": "done", "target": "n2", "targetHandle": "fire" }
12 ]
13}
-
Validate:
closecode_validate_workflow(workflowId: "my-workflow")
-
Run:
closecode_run_workflow(workflowId: "my-workflow")
-
Check results:
closecode_get_workflow_run(runId: "...")
Each edge connects one output to one input:
json
1{
2 "id": "unique-edge-id",
3 "source": "source-node-id",
4 "sourceHandle": "output-socket-id",
5 "target": "target-node-id",
6 "targetHandle": "input-socket-id"
7}
6. Common Patterns
Approval Workflow (Human-in-the-Loop)
json
1{
2 "title": "Approval Workflow",
3 "nodes": [
4 { "id": "trigger", "kind": "builtin.trigger.webhook", "position": { "x": 0, "y": 0 }, "config": {} },
5 { "id": "ask", "kind": "builtin.interaction.human-input", "position": { "x": 300, "y": 0 }, "config": {
6 "message": "Approve this request?",
7 "nextActions": [
8 { "label": "Approve", "value": "approved" },
9 { "label": "Reject", "value": "rejected" }
10 ]
11 }},
12 { "id": "branch", "kind": "builtin.control.condition", "position": { "x": 600, "y": 0 }, "config": {
13 "expression": "inputs.selectedAction === 'approved'"
14 }},
15 { "id": "approved", "kind": "builtin.notify.log", "position": { "x": 900, "y": -100 }, "config": {} },
16 { "id": "rejected", "kind": "builtin.notify.log", "position": { "x": 900, "y": 100 }, "config": {} }
17 ],
18 "edges": [
19 { "id": "e1", "source": "trigger", "sourceHandle": "fire", "target": "ask", "targetHandle": "trigger" },
20 { "id": "e2", "source": "ask", "sourceHandle": "done", "target": "branch", "targetHandle": "in" },
21 { "id": "e3", "source": "ask", "sourceHandle": "selectedAction", "target": "branch", "targetHandle": "test" },
22 { "id": "e4", "source": "branch", "sourceHandle": "true", "target": "approved", "targetHandle": "fire" },
23 { "id": "e5", "source": "branch", "sourceHandle": "false", "target": "rejected", "targetHandle": "fire" }
24 ]
25}
Agent Pipeline
Trigger → Agent A → Agent B → Output:
json
1{
2 "title": "Agent Pipeline",
3 "nodes": [
4 { "id": "t", "kind": "builtin.trigger.manual", "position": { "x": 0, "y": 0 }, "config": {} },
5 { "id": "a1", "kind": "builtin.agent.invoke", "position": { "x": 300, "y": 0 }, "config": { "agent": "planner", "prompt": "Analyze: {{inputs.prompt}}" } },
6 { "id": "a2", "kind": "builtin.agent.invoke", "position": { "x": 600, "y": 0 }, "config": { "agent": "coder", "prompt": "Implement based on: " } },
7 { "id": "log", "kind": "builtin.notify.log", "position": { "x": 900, "y": 0 }, "config": {} }
8 ],
9 "edges": [
10 { "id": "e1", "source": "t", "sourceHandle": "fire", "target": "a1", "targetHandle": "fire" },
11 { "id": "e2", "source": "a1", "sourceHandle": "done", "target": "a2", "targetHandle": "fire" },
12 { "id": "e3", "source": "a1", "sourceHandle": "text", "target": "a2", "targetHandle": "prompt" },
13 { "id": "e4", "source": "a2", "sourceHandle": "done", "target": "log", "targetHandle": "fire" },
14 { "id": "e5", "source": "a2", "sourceHandle": "text", "target": "log", "targetHandle": "text" }
15 ]
16}
Sub-workflow Delegation
Use builtin.control.sub-workflow to call another workflow. The target workflow should have pipeline-input and pipeline-output nodes to define its contract.
Entity-based Routing
Connect entity.user or entity.role outputs to nodes that accept entity-ref or json inputs to route messages or tasks to specific people/roles.
7. Tips & Best Practices
- Always include a trigger node — A workflow without a trigger cannot run
- Separate data and trigger edges — Most nodes need both a trigger edge (for execution order) and data edges (for values)
- Use
human-input for human-in-the-loop — Pauses execution until a human responds
- Use sub-workflow for reusable flows — Encapsulate common patterns; pipeline interfaces define contracts
- Use
validate before run — Catches socket mismatches, missing required inputs, and cycles
- Position nodes for readability — Space nodes ~300px apart horizontally
- Use descriptive node labels — Set
label on nodes for clarity in the editor
8. Error Prevention
Socket Mismatches
Use closecode_suggest_compatible_nodes before creating edges. Common mistake: connecting a json output to a string input (rejected — use json-stringify first).
Required input sockets (required: true) must be connected. Validation catches this.
Cycle Detection
Workflows must be a DAG (directed acyclic graph). Validation detects and reports cycles.
Node Config
Each node has a config object specific to its kind. Check closecode_list_workflow_node_types for configSchema. Missing required config fields cause runtime errors.