@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.
- package/package.json +6 -4
- package/skills/controllerai-agents/SKILL.md +66 -152
- package/skills/controllerai-agents/references/instructions-and-versioning.md +32 -34
- package/skills/controllerai-ai-nodes/SKILL.md +57 -65
- package/skills/controllerai-ai-nodes/references/selection-and-testing.md +44 -26
- package/skills/controllerai-data/SKILL.md +65 -118
- package/skills/controllerai-data/references/data-rails.md +24 -26
- package/skills/controllerai-expressions/SKILL.md +48 -117
- package/skills/controllerai-expressions/references/dsl-and-recipes.md +42 -40
- package/skills/controllerai-flow-control/SKILL.md +66 -122
- package/skills/controllerai-flow-control/references/patterns.md +23 -60
- package/skills/controllerai-integration-actions/SKILL.md +47 -75
- package/skills/controllerai-node-config/SKILL.md +60 -130
- package/skills/controllerai-node-config/references/prop-and-readiness.md +28 -78
- package/skills/controllerai-runs/SKILL.md +50 -85
- package/skills/controllerai-runs/references/debugging.md +24 -22
- package/skills/controllerai-start/SKILL.md +53 -58
- package/skills/controllerai-start/references/mcp-tool-map.md +21 -16
- package/skills/controllerai-triggers/SKILL.md +62 -159
- package/skills/controllerai-triggers/references/lifecycle.md +36 -48
- package/skills/controllerai-workflows/SKILL.md +70 -70
- package/skills/controllerai-workflows/references/command-map.md +19 -44
- package/skills/manifest.json +80 -77
- package/skills-mcp/controllerai-agents/SKILL.md +121 -0
- package/skills-mcp/controllerai-agents/references/instructions-and-versioning.md +76 -0
- package/skills-mcp/controllerai-ai-nodes/SKILL.md +92 -0
- package/skills-mcp/controllerai-ai-nodes/references/selection-and-testing.md +66 -0
- package/skills-mcp/controllerai-data/SKILL.md +119 -0
- package/skills-mcp/controllerai-data/references/data-rails.md +45 -0
- package/skills-mcp/controllerai-expressions/SKILL.md +98 -0
- package/skills-mcp/controllerai-expressions/references/dsl-and-recipes.md +136 -0
- package/skills-mcp/controllerai-flow-control/SKILL.md +114 -0
- package/skills-mcp/controllerai-flow-control/references/patterns.md +83 -0
- package/skills-mcp/controllerai-integration-actions/SKILL.md +89 -0
- package/skills-mcp/controllerai-node-config/SKILL.md +106 -0
- package/skills-mcp/controllerai-node-config/references/prop-and-readiness.md +49 -0
- package/skills-mcp/controllerai-runs/SKILL.md +88 -0
- package/skills-mcp/controllerai-runs/references/debugging.md +53 -0
- package/skills-mcp/controllerai-start/SKILL.md +80 -0
- package/skills-mcp/controllerai-start/references/mcp-tool-map.md +175 -0
- package/skills-mcp/controllerai-triggers/SKILL.md +112 -0
- package/skills-mcp/controllerai-triggers/references/lifecycle.md +59 -0
- package/skills-mcp/controllerai-workflows/SKILL.md +109 -0
- package/skills-mcp/controllerai-workflows/references/command-map.md +1 -0
- 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
|
|
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
|
-
#
|
|
7
|
+
# Flow control
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
## Use this when
|
|
9
10
|
|
|
10
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
| Intent | Primitive | Semantics |
|
|
18
|
+
| Intent | Primitive | Runtime semantics |
|
|
26
19
|
| --- | --- | --- |
|
|
27
|
-
| Skip one action when a condition is false |
|
|
28
|
-
| Choose exactly one path | If/Else |
|
|
29
|
-
| Run every matching path | Router | Conditions are independent;
|
|
30
|
-
|
|
|
31
|
-
| Reuse a typed unit of work once | Run flow
|
|
32
|
-
| Continue after
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
33
|
+
## Rules
|
|
39
34
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 <
|
|
84
|
-
cai branch connect <controlNodeId> --route <
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
83
|
+
For multiline or shell-sensitive list source, pass `--js-file <path|->` instead of `--list-js`.
|
|
136
84
|
|
|
137
|
-
|
|
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
|
-
|
|
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
|
-
|
|
94
|
+
### Converge, return, handle errors
|
|
159
95
|
|
|
160
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
40
|
+
## Grouped conditions
|
|
40
41
|
|
|
41
|
-
|
|
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
|
|
45
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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`
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
#
|
|
7
|
+
# Integration actions
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
## Use this when
|
|
9
10
|
|
|
10
|
-
|
|
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
|
-
|
|
15
|
+
Building the agent itself belongs to `controllerai-agents`; a workflow node for the same app belongs to `controllerai-node-config`.
|
|
13
16
|
|
|
14
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
29
|
+
## Rules
|
|
34
30
|
|
|
35
|
-
|
|
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
|
-
|
|
38
|
-
cai connection list --integration <integrationSlug> --json
|
|
39
|
-
```
|
|
39
|
+
## Recipes
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
### Discover
|
|
42
42
|
|
|
43
43
|
```bash
|
|
44
|
-
cai
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
91
|
+
## Verify
|
|
111
92
|
|
|
112
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|