@controllerai/cli 0.2.18 → 0.2.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/package.json +6 -4
  2. package/skills/controllerai-agents/SKILL.md +66 -152
  3. package/skills/controllerai-agents/references/instructions-and-versioning.md +32 -34
  4. package/skills/controllerai-ai-nodes/SKILL.md +57 -65
  5. package/skills/controllerai-ai-nodes/references/selection-and-testing.md +44 -26
  6. package/skills/controllerai-data/SKILL.md +65 -118
  7. package/skills/controllerai-data/references/data-rails.md +24 -26
  8. package/skills/controllerai-expressions/SKILL.md +48 -117
  9. package/skills/controllerai-expressions/references/dsl-and-recipes.md +42 -40
  10. package/skills/controllerai-flow-control/SKILL.md +66 -122
  11. package/skills/controllerai-flow-control/references/patterns.md +23 -60
  12. package/skills/controllerai-integration-actions/SKILL.md +47 -75
  13. package/skills/controllerai-node-config/SKILL.md +60 -130
  14. package/skills/controllerai-node-config/references/prop-and-readiness.md +28 -78
  15. package/skills/controllerai-runs/SKILL.md +50 -85
  16. package/skills/controllerai-runs/references/debugging.md +24 -22
  17. package/skills/controllerai-start/SKILL.md +53 -58
  18. package/skills/controllerai-start/references/mcp-tool-map.md +21 -16
  19. package/skills/controllerai-triggers/SKILL.md +62 -159
  20. package/skills/controllerai-triggers/references/lifecycle.md +36 -48
  21. package/skills/controllerai-workflows/SKILL.md +70 -70
  22. package/skills/controllerai-workflows/references/command-map.md +19 -44
  23. package/skills/manifest.json +80 -77
  24. package/skills-mcp/controllerai-agents/SKILL.md +121 -0
  25. package/skills-mcp/controllerai-agents/references/instructions-and-versioning.md +76 -0
  26. package/skills-mcp/controllerai-ai-nodes/SKILL.md +92 -0
  27. package/skills-mcp/controllerai-ai-nodes/references/selection-and-testing.md +66 -0
  28. package/skills-mcp/controllerai-data/SKILL.md +119 -0
  29. package/skills-mcp/controllerai-data/references/data-rails.md +45 -0
  30. package/skills-mcp/controllerai-expressions/SKILL.md +98 -0
  31. package/skills-mcp/controllerai-expressions/references/dsl-and-recipes.md +136 -0
  32. package/skills-mcp/controllerai-flow-control/SKILL.md +114 -0
  33. package/skills-mcp/controllerai-flow-control/references/patterns.md +83 -0
  34. package/skills-mcp/controllerai-integration-actions/SKILL.md +89 -0
  35. package/skills-mcp/controllerai-node-config/SKILL.md +106 -0
  36. package/skills-mcp/controllerai-node-config/references/prop-and-readiness.md +49 -0
  37. package/skills-mcp/controllerai-runs/SKILL.md +88 -0
  38. package/skills-mcp/controllerai-runs/references/debugging.md +53 -0
  39. package/skills-mcp/controllerai-start/SKILL.md +80 -0
  40. package/skills-mcp/controllerai-start/references/mcp-tool-map.md +175 -0
  41. package/skills-mcp/controllerai-triggers/SKILL.md +112 -0
  42. package/skills-mcp/controllerai-triggers/references/lifecycle.md +59 -0
  43. package/skills-mcp/controllerai-workflows/SKILL.md +109 -0
  44. package/skills-mcp/controllerai-workflows/references/command-map.md +1 -0
  45. package/skills-mcp/manifest.json +189 -0
@@ -1,167 +1,110 @@
1
1
  ---
2
2
  name: controllerai-flow-control
3
- description: Design and configure Controller AI execution paths using only-when rules, Router routes, If/Else branches, for-each loops, and subflows. Use when work must branch, fan out, skip, iterate over a list, call another flow, converge paths, or handle errors without changing the user's intended semantics.
3
+ description: Design and configure Controller AI execution paths with only-when guards, If/Else branches, Router routes, for-each loops, subflows, Join Paths and error handling. Use when work must skip, branch, fan out, iterate over a list, call another flow, converge, return a value, or continue after a failure without changing the user's intended semantics.
4
4
  ---
5
+ <!-- generated from .claude/skills by cli/scripts/render-skills.mjs; edit the source -->
5
6
 
6
- # Controller AI flow control
7
+ # Flow control
7
8
 
8
- The `cai ...` examples are canonical grammar. Use the transport selected in `controllerai-start` and its MCP tool map; never mix transports in one task.
9
+ ## Use this when
9
10
 
10
- Choose by intended runtime semantics.
11
+ - A flow must skip an action, choose one path, run every matching path, repeat work per item, call another flow, converge exclusive paths, return a value, or continue after a failure.
12
+ - A run shows an unexpected skip, a branch that never fires, an empty return, or a loop that stops early.
11
13
 
12
- ## Rules that break tasks when skipped
14
+ Conditions are written with the expression discipline in `controllerai-expressions`; components are found and configured with `controllerai-node-config`; runs are read with `controllerai-runs`.
13
15
 
14
- - Guard the exact action with node `only-when`; see Conditions.
15
- - Use If/Else for one path and Router for all matches; see Pick the correct primitive.
16
- - Keep downstream wiring after node-only skip; see Conditions.
17
- - Never invent a missing condition or Router order; see If/Else and Router.
18
- - Build standard loops only with `flow for-each`; see Loops.
19
- - Converge paths through Join Paths; see Subflows and convergence.
20
- - Fail missing comparisons closed; keep money paths blocking. See Conditions, Loops, and the error pattern.
21
- - Never acknowledge validation errors without explicit approval; see `controllerai-workflows` Lifecycle rules.
16
+ ## How it works
22
17
 
23
- ## Pick the correct primitive
24
-
25
- | Intent | Primitive | Semantics |
18
+ | Intent | Primitive | Runtime semantics |
26
19
  | --- | --- | --- |
27
- | Skip one action when a condition is false | Node `only-when` | No alternate path |
28
- | Choose exactly one path | If/Else | First matching branch runs; Else is last |
29
- | Run every matching path | Router | Conditions are independent; multiple routes may run |
30
- | Apply work to every list item | `flow for-each` | Atomic loop scaffold with typed item flow |
31
- | Reuse a typed unit of work once | Run flow/subflow node | Explicit input and output contract |
32
- | Continue after an action error | Error output or continue-on-error | Error behavior is explicit and testable |
20
+ | Skip one action when a condition is false | node `only-when` | No alternate path; the node records `only_when_false`, downstream stays eligible |
21
+ | Choose exactly one path | If/Else | The first matching branch runs; Else is the mandatory last fallback |
22
+ | Run every matching path | Router | Conditions are independent; every matching route runs, possibly several |
23
+ | Do work for every list item | `flow for-each` loop | A typed body flow runs once per item, sequentially, under an item cap |
24
+ | Reuse a typed unit of work once | Run flow (subflow) | Explicit input and output mapping to another flow's contract |
25
+ | Continue after a failed action | continue-on-error / Error output | The error is captured, downstream sees null for that node, the run ends `completed_with_error` |
26
+ | One action after whichever exclusive path ran | Join Paths (`registry_flow-merge`) | `chooseActivePath` passes the single active input's value on; `passThrough` returns one field per connected input (blocked ones null) and suits fan-out that can overlap |
27
+ | Give a flow its return value | Return response (`registry_flow-return-response`) | Maps producer values into the declared outputs; without it an otherwise successful flow returns empty outputs |
33
28
 
34
- Do not substitute one primitive merely because its component is easier to find.
29
+ Skip reasons are the evidence: a false guard records `only_when_false`; a node on an untaken branch or an unmatched route records `upstream_skipped`. Choose the primitive by the semantics the caller and the tests need, never because a component was easier to find.
35
30
 
36
- ## Conditions
31
+ A fresh If/Else ships with one blank `If` branch and an Else; the blank condition is a validation blocker until it is set. Router routes cannot be reordered because every match runs. Loops run sequentially with a cap that defaults to 500 items (1 to 10000); a list over the cap fails before any iteration, never truncates. `continueOnIterationError` on a loop node continues past a failed child iteration and cannot help when the list or mapping expression fails before any child starts; node-level continue-on-error on the loop node lets the parent flow continue past that failed loop with a null output, but repairs nothing and resumes no iteration. With `chooseActivePath`, zero active inputs stop the flow at the join unless `continueIfNoActivePath` is enabled, which passes null on. Setting a subflow's target synthesizes mapping props from the target's current inputs; when those inputs change, set the target again and remap. Deleting a flow removes its graph, contract, triggers and caller links; turning continue-on-error off deletes the node's Error output and its edges.
37
32
 
38
- Before writing any condition:
33
+ ## Rules
39
34
 
40
- ```bash
41
- cai expr context <controlNodeId> --json
42
- cai expr validate <controlNodeId> --mode onlyWhen --js '<boolean expression>' --json
43
- ```
35
+ 1. **Guard the action that must skip with `only-when`; do not replace a node skip with If/Else.** A false guard does not stop downstream execution (the skipped node just yields no value), so guard downstream separately or branch when it must skip too.
36
+ 2. **Never invent a missing condition** from names, sample values or one observed run. Leave the branch blank, report the blocker and ask; a missing business rule is not yours to guess.
37
+ 3. **Update the blank default If branch before adding another branch.** Adding beside it leaves a blocker and a duplicate.
38
+ 4. **Order is behavior only for If/Else.** Put the most specific condition first, never write a complement condition for Else, and never simulate Router order by deleting routes or chaining descendants; leave a fan-out unchanged unless the user authorizes a redesign.
39
+ 5. **Build per-item work with `flow for-each`**, never by hand-assembling the injection and mapping pattern. Put the real work in the generated body flow and declare its outputs before consuming aggregate results. Continue past failed iterations only when partial completion is acceptable and the downstream logic handles failed iteration records.
40
+ 6. **Compound guards are supported**: a node guard takes one boolean expression or grouped conditions (inner list AND, outer list OR); a root `&&` or `||` inside one expression is not. Never weaken a rule to fit the grammar, and never probe a private API.
41
+ 7. **Fail missing comparisons closed** with a same-type boundary (`(flow.unitsLeft ?? 5) < 5`), and keep money paths blocking: charges, purchases, transfers and refunds never continue on error, and never enable continue-on-error just to make a run green.
42
+ 8. **Converge exclusive paths through Join Paths**; never wire several branches straight into an ordinary node and never duplicate the shared action.
43
+ 9. **A flow that returns data needs a Return response node** bound to declared outputs. Every Return writes every declared output, unbound ones as null, and a later Return (in execution order) overwrites an earlier one, so a partial Return can erase a value; design one Return per path unless merging is intended.
44
+ 10. **Read the branch list after every edit.** Warnings about missing conditions or unwired paths are blockers even when a run looks green.
44
45
 
45
- For one guarded action:
46
+ ## Recipes
47
+
48
+ ### Guard one action
46
49
 
47
50
  ```bash
51
+ cai expr context <nodeId> --json
52
+ cai expr validate <nodeId> --mode onlyWhen --js '<boolean expression>' --json
48
53
  cai node only-when <nodeId> --js '<boolean expression>' --json
54
+ cai node only-when <nodeId> --groups-json '[["flow.email.isNotEmpty()","flow.units < 5"],["flow.override === true"]]' --json
49
55
  ```
50
56
 
51
- Use `--clear` only when the user's intent is to remove the guard.
52
-
53
- Guard the action that must skip. Do not substitute If/Else for a node-only skip. Its `only_when_false` does not block ordinary output; downstream remains eligible. Keep wiring. Guard downstream separately or branch if it must also skip.
54
-
55
- Node `only-when` takes one restricted boolean through `--js` or `--js-file`, or grouped conditions through `--groups-json '[["a","b"],["c"]]'` (inner arrays are AND, outer rows are OR). Root scalar `&&` or `||` inside one expression is unsupported; use `--groups-json` for compound guards. Grouped `string[][]` conditions exist only on branch add/update, with outer rows OR and inner entries AND. Use them only when branch semantics fit. If an exact compound node-only guard is required, report it as unsupported; do not weaken the rule or probe private APIs. For a missing number that must fail `< 5`, use a same-type boundary such as `(flow.unitsLeft ?? 5) < 5`.
57
+ Use `--clear` only when removing the guard is the user's intent.
56
58
 
57
- Choose the primitive by the runtime signal that callers and tests need. A false node `only-when` records `skipReason: "only_when_false"`; a node on an untaken If/Else branch or unmatched Router route records `skipReason: "upstream_skipped"`.
58
-
59
- ## If/Else and Router
60
-
61
- Discover and initialize the appropriate control component using `controllerai-node-config`, then inspect its exact branch IDs:
59
+ ### If/Else and Router
62
60
 
63
61
  ```bash
64
62
  cai branch list <controlNodeId> --json
65
- ```
66
-
67
- A fresh If/Else already contains one blank `If` branch and an Else branch. The blank condition is a publish/runtime blocker: copy that default branch ID from `branch list` and update it before adding another condition branch. Do not leave the default blank and create a replacement beside it.
68
-
69
- Never invent a missing condition from node names, current values, or one observed run. If no authoritative platform surface supplies the business rule, leave the branch blank, report the blocker, and ask the user.
70
-
71
- ```bash
72
- cai branch update <ifElseNodeId> \
73
- --route <defaultIfBranchId> \
74
- --name "<meaning>" \
75
- --js '<boolean expression>' \
76
- --json
77
- ```
78
-
79
- Add another condition only when the decision needs another non-Else case. Router routes are independent, so update an existing blank route or add each intended route explicitly:
80
-
81
- ```bash
63
+ cai branch update <controlNodeId> --route <defaultIfBranchId> --name "<meaning>" --js '<boolean expression>' --json
82
64
  cai branch add <controlNodeId> --name "<meaning>" --js '<boolean expression>' --json
83
- cai branch update <controlNodeId> --route <routeOrBranchId> --js '<boolean expression>' --json
84
- cai branch connect <controlNodeId> --route <routeOrBranchId> --to <targetNodeId> --flow <flowId> --json
85
- ```
86
-
87
- For If/Else, order is behavior:
88
-
89
- ```bash
65
+ cai branch update <controlNodeId> --route <routeId> --condition-json '[["flow.email.isNotEmpty()","flow.units < 5"],["flow.override === true"]]' --json
66
+ cai branch connect <controlNodeId> --route <routeId> --to <targetNodeId> --flow <flowId> --json
90
67
  cai branch reorder <ifElseNodeId> --order <conditionId1,conditionId2> --json
91
68
  ```
92
69
 
93
- List every condition branch exactly once and omit Else; the operation keeps Else last. Router routes cannot be reordered because every match runs. Do not simulate order by deleting a route or serializing its downstream nodes. Leave the fan-out unchanged unless the user explicitly authorizes a redesign.
94
-
95
- After edits, run `cai branch list` again. Treat its warnings about missing conditions or unwired paths as blockers: an unwired selected path can end with an apparently green execution.
70
+ Find the control component with `controllerai-node-config`, then read the branch IDs. Grouped conditions are OR of AND rows, each string compiled separately. Add an If branch only for another non-Else case; add a Router route for each intended independent match. Reorder lists every condition branch exactly once and omits Else, which stays last. Wire every returned route ID explicitly.
96
71
 
97
- ## Loops
98
-
99
- Never assemble the standard per-item subflow pattern by hand:
72
+ ### Loop over a list
100
73
 
101
74
  ```bash
102
75
  cai node get <listProducingNodeId> --json
103
- cai flow for-each \
104
- --flow <parentFlowId> \
105
- --from <listProducingNodeId> \
106
- --source-output <exactOutputId> \
107
- --name "<item flow name>" \
108
- --item-type <text|number|date|boolean|custom.type> \
109
- --json
110
- ```
111
-
112
- Use repeatable `--item-field <id:type[:display]>` only when creating a new `custom.*` item type.
113
-
114
- Copy `--source-output` from the selected node's exact `outputs[].id`. It is an opaque ID, not `displayName`, and there is no first-output default. This matters for Router, If/Else, and error outputs as well as ordinary action nodes.
115
-
116
- - If the source's whole output is the list, omit both `--list-field` and `--list-js`.
117
- - If the list is nested in the source payload, use `--list-field` with the dot-delimited path below `.data`; the operation derives the not-yet-created loop input binding deterministically.
118
- - Use `--list-js` only for an advanced exact expression. For multiline or shell-sensitive source, use `--js-file <path|->` instead. Both are mutually exclusive with `--list-field` and with each other; copy the wrapper/binding shape from expression context rather than guessing it.
119
-
120
- MCP: pass expression source inline as `listJs` to `flow_for_each_scaffold`; the server cannot read a local `--js-file` path.
121
-
122
- - The runtime cap defaults to 500 items and accepts 1 through 10000. Set `--max-items` explicitly when the product requirement differs. The scaffold operation creates the core graph first, then materializes and verifies this optional prop. The cap is all-or-nothing at runtime: a list larger than the cap throws and NOTHING iterates — there is no partial truncation.
123
-
124
- Changing an existing loop: update its loop-node props directly:
125
-
126
- ```bash
76
+ cai flow for-each --flow <parentFlowId> --from <listProducingNodeId> --source-output <exactOutputId> --name "<item flow name>" --item-type <text|number|date|boolean|custom.type> --max-items <n> --json
127
77
  cai node set <loopNodeId> --prop maxItems --value <n> --json
128
78
  cai node set <loopNodeId> --prop continueOnIterationError --value true --json
129
79
  ```
130
80
 
131
- MCP: use `node_prop_set` for both writes.
132
-
133
- This is not `cai node continue-on-error`, which configures the node-level Error output — a different feature.
81
+ `--source-output` is the exact `outputs[].id` of the selected node, never a display name, and there is no first-output default (branch and error outputs included). When the whole output is the list, omit the list selectors; for a nested list use `--list-field` with the dot path below `.data`; reserve `--list-js` for an exact advanced expression whose shape you took from the context; the selection forms are mutually exclusive. `--item-field <id:type[:display]>` (repeatable) is only for creating a new `custom.*` item type; stored records use `dataRecord.custom.*`. Set the cap on purpose when the requirement differs from 500, decide `--continue-on-error` explicitly, then read the returned warnings and follow `nextSteps` in order; a list/item type mismatch means created but unverified. In mappings, `item` exists only where the context exposes it; inside the body flow the item is `flow.<itemInput>`; aggregate results are iteration records with child outputs under `outputs`.
134
82
 
135
- Read every returned warning. A list/item type mismatch means the scaffold is not verified even though it was created. Follow the returned `nextSteps` in order.
83
+ For multiline or shell-sensitive list source, pass `--js-file <path|->` instead of `--list-js`.
136
84
 
137
- Inside loop mappings, `item` exists only where context exposes it. Inside the body flow, use its `flow.<itemInput>` binding. Downstream loop results are iteration records whose declared child outputs are under `.outputs`.
138
-
139
- Loops run sequentially. `--continue-on-error` applies to a failed child iteration; a list expression or mapping/config expression that fails before the child starts still aborts the loop. Test empty, one-item, several-item, over-limit, and intended iteration-error behavior.
140
-
141
- Never enable continue-on-error on a money path; charges, purchases, transfers, refunds, and other spend must fail fast.
142
-
143
- ## Subflows and convergence
144
-
145
- Define the called flow's inputs and outputs first. Then initialize a Run flow component and bind it:
85
+ ### Call another flow
146
86
 
147
87
  ```bash
148
88
  cai node set-flow <runFlowNodeId> --flow <calledFlowId> --json
149
89
  cai node props <runFlowNodeId> --json
150
90
  ```
151
91
 
152
- MCP: `cai node set-flow` maps to `node_subflow_set`.
153
-
154
- Setting the target flow creates mapping props from the target's current inputs. If those inputs later change, run `set-flow` again and reconfigure mappings.
155
-
156
- For one action after whichever exclusive branch runs, use `registry_flow-merge` (Join Paths) in `chooseActivePath` mode, then place the shared action after it. Never wire several branches directly to an ordinary node or duplicate the shared action. Follow the exact recipe in [references/patterns.md](references/patterns.md) and test each branch once.
92
+ Define the called flow's inputs and outputs first, add the Run flow component, set the target, then bind the generated mapping props with expressions.
157
93
 
158
- Return semantics: a flow that should return data needs `registry_flow-return-response`; without it callers see empty success. Declare outputs first, initialize that exact component after the producer, then bind its generated prop with `cai expr set`. Do not guess component keys. See [references/patterns.md](references/patterns.md). Multiple completed Return nodes merge, with later values overwriting earlier ones; design for one Return per path unless merging is intended.
94
+ ### Converge, return, handle errors
159
95
 
160
- Deleting a flow removes its graph, contract, triggers, and caller links. Validate and restore if mistaken. Turning `continueOnError` off also deletes its Error output and edges; validate and restore likewise.
96
+ ```bash
97
+ cai node add --flow <flowId> --node registry_flow --key registry_flow-merge --name "Join paths" --json
98
+ cai node set <joinNodeId> --prop mode --value chooseActivePath --json
99
+ cai flow output add <bodyFlowId> --display "<output label>" --name <output_key> --type <type> --json
100
+ cai node add --flow <bodyFlowId> --node registry_flow --key registry_flow-return-response --name "Return <value>" --after <producerNodeId> --json
101
+ cai node continue-on-error <nodeId> --enable --json
102
+ cai node connect --flow <flowId> --from <nodeId> --to <handlerNodeId> --output error --json
103
+ ```
161
104
 
162
- Read [references/patterns.md](references/patterns.md) for concise canonical patterns.
105
+ Join Paths: connect every type-compatible branch tail to it (all connected input types are checked, inactive paths included, which is why `pathDataType` is asked for once `chooseActivePath` reveals it), set `pathDataType` from the returned options, and place the shared action after it (`--after <joinNodeId>`, or connect the existing action). Return: declare the output first so its generated prop exists, add the exact component after the producer, then bind the prop through the expression setter. Error output wiring is optional; add it only when a failure needs explicit handling.
163
106
 
164
- ## Before you finish
107
+ ## Verify
165
108
 
166
109
  ```bash
167
110
  cai branch list <controlNodeId> --json
@@ -170,9 +113,10 @@ cai run flow <flowId> --inputs <values.json> --json
170
113
  cai exec tree <workflowExecutionId> --json
171
114
  ```
172
115
 
173
- - Every branch has the intended condition and a wired target.
174
- - If/Else ordering matches priority and Else remains last.
175
- - Router fan-out is intentional when more than one condition can be true.
176
- - Loop item type and list expression compile against actual source data.
177
- - Expected true, false/Else, empty-list, one-item, several-item, over-limit, and error paths are tested.
178
- - The execution tree shows intended `only_when_false` or `upstream_skipped` reasons and no unexplained missing nodes.
116
+ Every branch has its intended condition and a wired target; If/Else order matches priority with Else last; Router fan-out is intentional where more than one condition can be true; the loop's item type and list expression compile against real data; the paths the task implies ran: true, false or Else, empty list, one item, several, over the cap, and the intended error path; the execution tree explains every `only_when_false` and `upstream_skipped` and shows no unexplained missing node; a loop's outer success is not proof for every item, so inspect iteration records and surface individual failures.
117
+
118
+ ## Report
119
+
120
+ Which primitive carries each decision and why, the tested paths with their outcomes, any rule you could not set because the business rule was missing, and any error-continuation you enabled and where.
121
+
122
+ Read [references/patterns.md](references/patterns.md) for the canonical shapes: guard, exclusive decision, fan-out, grouped conditions, per-item subflow, convergence, return, error path.
@@ -1,18 +1,19 @@
1
+ <!-- generated from .claude/skills by cli/scripts/render-skills.mjs; edit the source -->
1
2
  # Flow-control patterns
2
3
 
3
4
  ## Guard a side effect
4
5
 
5
- Use `only-when` when false should mean "skip this action" and nothing else:
6
+ `only-when` means "skip this action, nothing else":
6
7
 
7
8
  ```text
8
9
  Read record -> Send notification [only when status changed]
9
10
  ```
10
11
 
11
- Test both true and false. A false run should show an intentional skip reason.
12
+ Test true and false; the false run shows `only_when_false` on that node and downstream stays eligible.
12
13
 
13
14
  ## Exclusive decision
14
15
 
15
- Use If/Else when only one outcome must occur:
16
+ If/Else when exactly one outcome must occur:
16
17
 
17
18
  ```text
18
19
  Score -> If/Else
@@ -21,11 +22,11 @@ Score -> If/Else
21
22
  Else -> Record unknown
22
23
  ```
23
24
 
24
- Put the most specific/highest-priority conditions first. Never write a complement condition for Else.
25
+ Most specific condition first; Else is the fallback and never gets a complement condition.
25
26
 
26
27
  ## Independent fan-out
27
28
 
28
- Use Router when every applicable action should run:
29
+ Router when every applicable action should run:
29
30
 
30
31
  ```text
31
32
  New order -> Router
@@ -36,95 +37,57 @@ New order -> Router
36
37
 
37
38
  One order may take all three routes. If that is not acceptable, use If/Else.
38
39
 
39
- ## Grouped branch condition
40
+ ## Grouped conditions
40
41
 
41
- Only Router and If/Else branch commands accept grouped conditions. The outer rows are OR and the expressions within one row are AND:
42
+ Outer rows are OR, entries within a row are AND, and each string compiles on its own:
42
43
 
43
44
  ```bash
44
- cai branch update <controlNodeId> \
45
- --route <routeId> \
46
- --condition-json '[["flow.email.isNotEmpty()","flow.units < 5"],["flow.override === true"]]' \
47
- --json
45
+ cai node only-when <nodeId> --groups-json '[["flow.email.isNotEmpty()","flow.units < 5"],["flow.override === true"]]' --json
46
+ cai branch update <controlNodeId> --route <routeId> --condition-json '[["flow.email.isNotEmpty()","flow.units < 5"],["flow.override === true"]]' --json
48
47
  ```
49
48
 
50
- Each string must compile independently. Node `only-when` has no `--condition-json`; if exact compound node-only semantics are required, report the limitation instead of using a private API or weakening the guard.
51
-
52
49
  ## Per-item subflow
53
50
 
54
- Use `cai flow for-each` to create the item flow, typed item input, loop node, and list wiring atomically. First run `cai node get <sourceNodeId> --json`, choose the intended `outputs[]` entry, and pass its exact opaque `id` as `--source-output`; never pass its label or assume the first output. Omit list-selection flags when the whole selected output payload is a list; use `--list-field <nested.path>` for a list below `.data`; reserve `--list-js` for an exact advanced expression or `--js-file <path|->` for that expression in a UTF-8 file/stdin. The three selection forms are mutually exclusive.
55
-
56
- MCP: call `flow_for_each_scaffold` and pass expression source inline as `listJs`.
57
-
58
- The runtime limit defaults to 500 items and supports 1 through 10000. Use `--max-items` when the intended bound differs, then verify the returned read-back. Put meaningful work inside the generated body flow and declare body outputs before consuming aggregate results.
51
+ ```bash
52
+ cai node get <sourceNodeId> --json
53
+ cai flow for-each --flow <parentFlowId> --from <sourceNodeId> --source-output <exactOutputId> --name "<item flow name>" --item-type <type> --json
54
+ ```
59
55
 
60
- Decide explicitly whether one child-flow failure should stop the sequential loop. Use `--continue-on-error` only when partial completion is acceptable and downstream logic handles failed iteration records. It does not recover a list or mapping expression that fails before an iteration starts.
56
+ The scaffold creates the item flow, its typed item input, the loop node and the list wiring in one operation, then materializes the optional cap; a failure setting the cap does not roll the scaffold back. The body receives one item, not the whole list; a custom-shaped item and a stored record (`dataRecord.custom.*`) are different types. Continue past a failed iteration (`--continue-on-error`) only when partial completion is acceptable and the downstream logic handles failed iteration records (null outputs with a status and an error); it cannot rescue a list or mapping expression that fails before the child starts.
61
57
 
62
58
  ## Converge exclusive paths
63
59
 
64
- Initialize Join Paths and select its active-path mode:
65
-
66
60
  ```bash
67
- cai node init \
68
- --flow <flowId> \
69
- --node registry_flow \
70
- --key registry_flow-merge \
71
- --name "Join paths" \
72
- --json
61
+ cai node add --flow <flowId> --node registry_flow --key registry_flow-merge --name "Join paths" --json
73
62
  cai node set <joinNodeId> --prop mode --value chooseActivePath --json
74
63
  cai node props <joinNodeId> --json
75
- # If returned props require it:
76
64
  cai node set <joinNodeId> --prop pathDataType --value <returnedTypeValue> --json
77
65
  cai node connect --flow <flowId> --from <firstBranchTailNodeId> --to <joinNodeId> --json
78
66
  cai node connect --flow <flowId> --from <secondBranchTailNodeId> --to <joinNodeId> --json
79
- cai node init \
80
- --flow <flowId> \
81
- --node <sharedNodeSlug> \
82
- --key <sharedNodeKey> \
83
- --name "<shared action>" \
84
- --after <joinNodeId> \
85
- --json
67
+ cai node add --flow <flowId> --node <sharedNodeSlug> --key <sharedNodeKey> --name "<shared action>" --after <joinNodeId> --json
86
68
  ```
87
69
 
88
- Set `pathDataType` only when returned props require it, using an exact returned option. Repeat the branch-tail connection for every type-compatible exclusive path. If the shared action already exists, connect it after Join Paths instead of initializing a replacement. Do not connect several branches directly to an ordinary node or duplicate the action. `chooseActivePath` requires at most one active input, so do not use it after a Router whose routes can overlap. Run one test per branch and stop after all pass.
70
+ Set `pathDataType` from the returned options once `chooseActivePath` reveals it. Repeat the tail connection for every type-compatible exclusive path; if the shared action already exists, connect it after Join Paths instead of adding a replacement. `chooseActivePath` allows at most one active input and stops the flow when none is active unless `continueIfNoActivePath` is enabled (then it passes null); after a Router whose routes can overlap use `passThrough`, which returns one field per connected input with the blocked ones null. Run one test per branch and stop after all pass.
89
71
 
90
72
  ## Return a flow value
91
73
 
92
- Declare the output before the Return response node so its generated prop exists:
93
-
94
74
  ```bash
95
- cai flow output add <bodyFlowId> \
96
- --display "<output label>" \
97
- --name <output_key> \
98
- --type <type> \
99
- --json
100
- cai node init \
101
- --flow <bodyFlowId> \
102
- --node registry_flow \
103
- --key registry_flow-return-response \
104
- --name "Return <value>" \
105
- --after <producerNodeId> \
106
- --json
75
+ cai flow output add <bodyFlowId> --display "<output label>" --name <output_key> --type <type> --json
76
+ cai node add --flow <bodyFlowId> --node registry_flow --key registry_flow-return-response --name "Return <value>" --after <producerNodeId> --json
107
77
  cai node props <returnNodeId> --json
108
78
  cai expr context <returnNodeId> --prop <generatedOutputProp> --json
109
79
  cai expr set <returnNodeId> --prop <generatedOutputProp> --js '<producer binding>' --json
110
80
  ```
111
81
 
112
- Do not guess `registry_flow-return` or `registry_flow-response`. Run the body flow and verify its top-level output contains the promised value.
82
+ Declare the output before adding the Return node so its generated prop exists. Do not guess `registry_flow-return` or `registry_flow-response`. Run the body flow and check the promised top-level output. Populate the outputs on every intended return path, including the "nothing found" path, which should return a clear result without performing writes.
113
83
 
114
84
  ## Error path
115
85
 
116
- Continue-on-error is for steps whose failure must not block the main path — notifications, logging, optional enrichment. Enabling it captures the node's error, downstream nodes still execute (receiving null for the failed node's output), and the run ends `completed_with_error`:
86
+ Continue-on-error suits steps whose failure must not block the main path: notifications, logging, optional enrichment. The node's error is captured, downstream nodes run with null for its output, and the run ends `completed_with_error`.
117
87
 
118
88
  ```bash
119
89
  cai node continue-on-error <nodeId> --enable --json
120
- ```
121
-
122
- MCP: call `node_continue_on_error_set` with `enabled: true` or `enabled: false`.
123
-
124
- Wiring the Error output is OPTIONAL — add it when a failure needs explicit handling, not as a precondition:
125
-
126
- ```bash
127
90
  cai node connect --flow <flowId> --from <nodeId> --to <handlerNodeId> --output error --json
128
91
  ```
129
92
 
130
- Never enable continue-on-error on critical-path steps (validate → charge → confirm must fail fast), and never merely to make the parent execution green. Read back the setting after changing it.
93
+ Wiring the Error output is optional. Never enable continue-on-error on a critical path (validate, charge, confirm must fail fast) and never to make a parent run green. Read the setting back after changing it.
@@ -1,125 +1,97 @@
1
1
  ---
2
2
  name: controllerai-integration-actions
3
- description: Discover, connect, attach, update, and verify ready integration actions used directly by Controller AI agents. Use when an agent needs one existing app operation without a workflow, when choosing between a direct action and a workflow tool, or when changing an action's connection or per-run approval policy.
3
+ description: Discover, connect, attach, update and verify ready integration actions that a Controller AI agent calls directly. Use when an agent needs one existing app operation without a workflow, when choosing between a direct action and a workflow tool, or when changing an action's connection or per-run approval policy.
4
4
  ---
5
+ <!-- generated from .claude/skills by cli/scripts/render-skills.mjs; edit the source -->
5
6
 
6
- # Controller AI integration actions
7
+ # Integration actions
7
8
 
8
- The `cai ...` examples are canonical grammar. Use the transport selected in `controllerai-start` and its MCP tool map; never mix transports in one task.
9
+ ## Use this when
9
10
 
10
- Ready integration actions give a hosted agent one app operation without first building a workflow. Use the canonical operations below through the selected transport; do not call Controller AI REST routes directly.
11
+ - An agent needs one existing app operation: send one message, create one record, add one row, look something up.
12
+ - You must decide between attaching a ready action and building a workflow tool.
13
+ - An attached action needs another connection, a different approval policy, or proof that it runs.
11
14
 
12
- ## Rules that break tasks when skipped
15
+ Building the agent itself belongs to `controllerai-agents`; a workflow node for the same app belongs to `controllerai-node-config`.
13
16
 
14
- - Attach one existing app operation directly; never wrap it in a one-node workflow. See Choose direct action or workflow.
15
- - Capture the exact integration, action, connection, and tool IDs. See Discover exact identities.
16
- - Decide confirmation before attachment, especially for writes or spend. See Decide approval before attaching.
17
- - Verify persisted direct-action events, not a workflow execution ID. See Verify through the hosted agent.
17
+ ## How it works
18
18
 
19
- ## Choose direct action or workflow
19
+ **A ready action is one operation.** The catalog exposes provider integration actions that an agent can call directly, without a workflow; native registry actions appear in the same discovery but are not attachable as direct actions, and every direct attachment needs a connection, so native and provider operations carry different authorization requirements. Use a workflow tool instead when the capability needs several operations, transformation, branching, loops, durable state, a trigger, a custom typed contract, or reuse outside the agent. Never wrap a ready action whose contract already fits in a one-node workflow, and never replace deterministic orchestration with several independent direct actions.
20
20
 
21
- Use a direct action for one existing operation such as sending one message, creating one record, or adding one row.
21
+ **Identity is a pair.** An action is the exact integration slug plus its action key, as returned by discovery; a display label is not an identity, and a similar integration with a familiar name is not evidence of the capability. The action binds to a connection whose `integration` matches that slug exactly.
22
22
 
23
- Use a workflow tool instead when the capability needs multiple operations, transformations, branches, loops, durable state, triggers, a custom typed contract, or reuse outside the agent. Do not build a one-node workflow when the same ready action already fits.
23
+ **Runtime.** The agent runtime configures the action's dynamic arguments when it invokes the action; nothing is preconfigured at attachment beyond identity, connection, description and approval policy. A direct action runs inline in the conversation and creates no workflow execution: the evidence is the persisted conversation, its action events with arguments and result, and the external effect.
24
24
 
25
- ## Discover exact identities
25
+ **Approval policy.** An attached action runs without a human approval step unless `requireConfirmation` is `true`; the default is no approval. When confirmation is on, the runtime pauses with a pending request that the user approves or denies with the exact arguments visible.
26
26
 
27
- ```bash
28
- cai integration search "<app or capability>" --json
29
- cai integration actions "<operation>" --integration <integrationSlug> --json
30
- cai integration action --integration <integrationSlug> --key <actionKey> --json
31
- ```
27
+ **Live bindings.** Publishing snapshots the action identity, description and approval policy into the agent version. The connection and its authorization grant are live security bindings: rebinding redirects the published action immediately, and revocation, breakage or detachment stops it immediately without an agent publish; detachment also removes the draft tool, and a later publish removes it from the published list. Confirmation is strictest-wins: tightening the grant to require confirmation blocks the published action at once until a publish carries the new policy, while relaxing it waits for the publish. Organization members use the published agent, never the draft; direct actions run through the owner's connected account and the organization's shared usage pool whoever calls them.
32
28
 
33
- Preserve each returned `nodeSlug` and `key` pair exactly, then inspect that action's static props before using it. Never derive either identifier from a display label. Narrow the query or raise `--limit` when more matches exist.
29
+ ## Rules
34
30
 
35
- ## Select a connection
31
+ 1. **Capture and reuse the exact identities**: integration slug, action key, connection ID, tool ID. Narrow the query or raise the result limit when more matches exist; never derive an identifier from a label.
32
+ 2. **A missing connection is a handoff, not a substitution.** Keep the selected integration and action, give the user the authorization route, and stop the attachment there; never swap in `registry_http-http-request` or request provider credentials in chat. A raw endpoint the user mentions is context, not an instruction to hand-roll the call.
33
+ 3. **Decide the approval policy before attaching.** For an action that sends, posts, spends or deletes, choose `true` or explicitly tell the user it will run unattended; prefer confirmation for permission changes, irreversible writes or an ambiguous destination, and omit it for reads and low-risk reversible operations the user asked to run autonomously. Mention the confirmation choice in the report to the user.
34
+ 4. **Attach once.** Read the tool list before and after; the final list holds one intended direct attachment.
35
+ 5. **A stored active connection is not proof of authorization.** Only a representative action test proves the provider still accepts it.
36
+ 6. **Verify what the request asked for, where it lives.** Discovery proves metadata; a blocked attachment proves the honest handoff and the independent work; an attachment proves the saved grant; only an authorized runtime test proves the external effect, and for that the action event, its arguments and result, and the external state are the evidence, never a reply that says it ran. Inspect the external state before repeating a failed or uncertain mutation.
37
+ 7. **Say whose account runs the action** before organization sharing: the owner's connection and the organization's usage pool, regardless of caller.
36
38
 
37
- ```bash
38
- cai connection list --integration <integrationSlug> --json
39
- ```
39
+ ## Recipes
40
40
 
41
- Use an active connection whose `integration` exactly matches the integration slug. If none exists:
41
+ ### Discover
42
42
 
43
43
  ```bash
44
- cai connection connect <integrationSlug>
44
+ cai integration search "<app or capability>" --json
45
+ cai integration actions "<operation>" --integration <integrationSlug> --json
46
+ cai integration action --integration <integrationSlug> --key <actionKey> --json
45
47
  ```
46
48
 
47
- MCP: call `connection_connect_url`, show its `url` as a markdown link, ask the user to finish, then confirm with `connection_list` using its `integration`; it has no `open` or `wait` parameter.
48
-
49
- The connection handoff keeps provider credentials out of the agent context. The CLI may open the URL; MCP returns it. Re-list connections after the user finishes, then attach the action.
50
-
51
- Rename or change the default with `cai connection update <id> [--name "<displayName>"] [--default|--no-default]` (MCP: `connection_update`); delete only intentionally with `cai connection delete <id> --yes` (MCP: `connection_delete`).
52
- Deleting a connection immediately removes authorization from bound nodes and agent tools.
53
-
54
- A missing connection is never a reason to substitute `registry_http-http-request` for a catalog component. For a workflow node, add the real component and leave it unconnected; `missingConnection: true` is a valid handoff state. For a direct action, preserve the selected integration and action and stop at the connection handoff. Give the user the returned connection URL; on CLI, `cai connection connect <slug>` obtains it. A raw endpoint mentioned by the user is background context, not an instruction to hand-roll the call.
49
+ `search` ranks integrations (`--actions` or `--events` narrows the kind); `actions` ranks actions across one or all integrations; `action` returns the static metadata and props of one exact action. Read that metadata before choosing.
55
50
 
56
- ## Decide approval before attaching
57
-
58
- Attached actions run without a human approval step unless `requireConfirmation` is `true`. When attaching an action that sends, posts, spends, or deletes, choose `true` or explicitly tell the user that it will run unattended. Mention the confirmation choice in the report to the user.
59
-
60
- - Prefer confirmation for permission changes, irreversible writes, or an ambiguous destination.
61
- - Usually omit confirmation for read-only retrieval and low-risk, reversible operations the user clearly asked the agent to perform autonomously.
51
+ ### Select or request a connection
62
52
 
63
53
  ```bash
64
- cai agent add-action <agentId> \
65
- --integration <integrationSlug> \
66
- --action <actionKey> \
67
- --connection <connectionId> \
68
- --description "Use when ..." \
69
- --require-confirmation \
70
- --json
54
+ cai connection list --integration <integrationSlug> --json
71
55
  ```
72
56
 
73
- MCP: call `agent_tool_action_add`; `requireConfirmation` is optional and defaults to `false`. On CLI, `--require-confirmation` selects `true` and omission preserves the `false` default. Do not attach the same action twice.
57
+ Use the active connection whose `integration` matches exactly. If none exists, `cai connection connect <integrationSlug> --no-open` returns the authorization URL: share it right away, and after the user finishes, list again and use the new ID.
74
58
 
75
- Read the saved grant back:
59
+ ### Attach
76
60
 
77
61
  ```bash
62
+ cai agent add-action <agentId> --integration <integrationSlug> --action <actionKey> --connection <connectionId> --description "Use when ..." --require-confirmation --json
78
63
  cai agent tools <agentId> --json
79
64
  ```
80
65
 
81
- Verify `toolType`, integration, action key, connection, schema tier, description, and confirmation policy. The hosted runtime configures dynamic action properties when it invokes the action; the builder does not preconfigure runtime-only action arguments here.
66
+ `--require-confirmation` sets the policy to `true`; omitting it keeps the `false` default.
67
+ The description tells the agent when to choose the tool. Read the saved grant back: `toolType`, integration, action key, connection, description and confirmation policy.
82
68
 
83
- To rebind the live authorization grant or change a versioned policy:
69
+ ### Update
84
70
 
85
71
  ```bash
86
72
  cai agent update-tool <agentId> --tool <toolId> --connection <connectionId> --json
87
73
  cai agent update-tool <agentId> --tool <toolId> --require-confirmation --json
88
74
  cai agent update-tool <agentId> --tool <toolId> --no-confirmation --json
75
+ cai agent update-tool <agentId> --tool <toolId> --description "<when to use it>" --json
89
76
  ```
90
77
 
91
- MCP: call `agent_tool_update`; use `connectionId` and `requireConfirmation: true` or `requireConfirmation: false`.
92
-
93
- `--connection` can redirect the published action immediately because authorization is live-bound. Description and confirmation-policy edits remain draft changes until the agent is published.
78
+ A connection change redirects the published action at once. A description change waits for the agent publish. A policy change is strictest-wins: turning confirmation on blocks the published action immediately until the next publish carries it, turning it off takes effect only at that publish.
94
79
 
95
- ## Verify through the hosted agent
96
-
97
- Use a representative draft conversation:
80
+ ### Verify in a draft conversation
98
81
 
99
82
  ```bash
100
- cai agent test <agentId> --draft --message "<request>" --json
83
+ cai agent test <agentId> --draft --message "<request that needs the action>" --json
101
84
  cai agent conversation history <conversationId> --json
85
+ cai agent conversation approve <conversationId> --request <requestId> --json
86
+ cai agent conversation deny <conversationId> --request <requestId> --message "<reason>" --json
102
87
  ```
103
88
 
104
- MCP: call `agent_test` with `rail: draft` and `waitSeconds`.
105
-
106
- A direct action runs inline and does not create a workflow execution ID. Evidence is the persisted conversation, its direct-action tool/run events, the returned result, and any observed external side effect. Never invent a workflow execution ID for it.
107
-
108
- If confirmation is required, inspect the pending action and exact arguments before approving or denying its request. A successful assistant reply alone does not prove the intended action ran.
89
+ `agent test` is a one-shot that opens a new conversation; when a follow-up or an approval is expected, create one draft conversation and send into it as in `controllerai-agents`. When confirmation is on, inspect the pending action and its exact arguments, then approve or deny in that same conversation and read the history for the terminal result. Direct actions do not create workflow execution IDs: never invent one.
109
90
 
110
- ## Publishing and sharing semantics
91
+ ## Verify
111
92
 
112
- Publishing snapshots the action identity, description, and approval policy into the agent version. The connection and authorization grant remain live security bindings: rebinding can redirect the published action, while breakage, revocation, or detachment can stop it immediately without another agent publish. Detachment also removes the draft tool; publish afterward to remove the unavailable tool from the published list.
93
+ For an attachment, the tool list shows exactly one intended direct attachment with the exact identities, connection, description and confirmation policy; for an authorized runtime test, the draft conversation history holds the action event, its arguments and its result, and the external effect was checked separately; for discovery alone, the exact metadata was read and nothing was changed. No wrapper workflow exists unless orchestration or reuse required it.
113
94
 
114
- Organization members use the published agent, never its mutable draft. Direct actions run through the agent owner's connected external account. Controller AI usage draws from the organization's shared usage pool, regardless of which member calls the agent. State that credential boundary before organization sharing.
115
-
116
- ## Before you finish
117
-
118
- ```bash
119
- cai agent tools <agentId> --json
120
- cai agent conversation history <conversationId> --json
121
- ```
95
+ ## Report
122
96
 
123
- - Tools must show one intended direct attachment with the exact returned identities, connection, description, and confirmation policy.
124
- - History must contain the persisted action event, exact arguments, and result; confirm any requested external effect separately.
125
- - No wrapper workflow should exist unless the capability actually required orchestration or reuse.
97
+ Which action is attached to which agent, its connection and confirmation choice, what the draft test proved, whose account and usage pool the action will use, and what remains: a connection to authorize, a publish, or a live verification.