@controllerai/cli 0.2.20 → 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 (67) hide show
  1. package/dist/api/client.js +0 -32
  2. package/dist/api/client.js.map +1 -1
  3. package/dist/commands/agent.js +8 -101
  4. package/dist/commands/agent.js.map +1 -1
  5. package/dist/commands/expr.js +10 -3
  6. package/dist/commands/expr.js.map +1 -1
  7. package/dist/commands/integration.js +2 -8
  8. package/dist/commands/integration.js.map +1 -1
  9. package/dist/commands/workflow.js +6 -7
  10. package/dist/commands/workflow.js.map +1 -1
  11. package/dist/errors.js +2 -5
  12. package/dist/errors.js.map +1 -1
  13. package/package.json +6 -4
  14. package/skills/controllerai-agents/SKILL.md +107 -26
  15. package/skills/controllerai-agents/references/instructions-and-versioning.md +32 -34
  16. package/skills/controllerai-ai-nodes/SKILL.md +83 -10
  17. package/skills/controllerai-ai-nodes/references/selection-and-testing.md +44 -26
  18. package/skills/controllerai-data/SKILL.md +106 -34
  19. package/skills/controllerai-data/references/data-rails.md +24 -26
  20. package/skills/controllerai-expressions/SKILL.md +82 -29
  21. package/skills/controllerai-expressions/references/dsl-and-recipes.md +42 -40
  22. package/skills/controllerai-flow-control/SKILL.md +104 -21
  23. package/skills/controllerai-flow-control/references/patterns.md +23 -60
  24. package/skills/controllerai-integration-actions/SKILL.md +84 -11
  25. package/skills/controllerai-node-config/SKILL.md +108 -18
  26. package/skills/controllerai-node-config/references/prop-and-readiness.md +28 -78
  27. package/skills/controllerai-runs/SKILL.md +82 -25
  28. package/skills/controllerai-runs/references/debugging.md +24 -22
  29. package/skills/controllerai-start/SKILL.md +61 -35
  30. package/skills/controllerai-start/references/mcp-tool-map.md +20 -21
  31. package/skills/controllerai-triggers/SKILL.md +113 -16
  32. package/skills/controllerai-triggers/references/lifecycle.md +36 -48
  33. package/skills/controllerai-workflows/SKILL.md +105 -25
  34. package/skills/controllerai-workflows/references/command-map.md +19 -44
  35. package/skills/manifest.json +80 -137
  36. package/skills-mcp/controllerai-agents/SKILL.md +121 -0
  37. package/skills-mcp/controllerai-agents/references/instructions-and-versioning.md +76 -0
  38. package/skills-mcp/controllerai-ai-nodes/SKILL.md +92 -0
  39. package/skills-mcp/controllerai-ai-nodes/references/selection-and-testing.md +66 -0
  40. package/skills-mcp/controllerai-data/SKILL.md +119 -0
  41. package/skills-mcp/controllerai-data/references/data-rails.md +45 -0
  42. package/skills-mcp/controllerai-expressions/SKILL.md +98 -0
  43. package/skills-mcp/controllerai-expressions/references/dsl-and-recipes.md +136 -0
  44. package/skills-mcp/controllerai-flow-control/SKILL.md +114 -0
  45. package/skills-mcp/controllerai-flow-control/references/patterns.md +83 -0
  46. package/skills-mcp/controllerai-integration-actions/SKILL.md +89 -0
  47. package/skills-mcp/controllerai-node-config/SKILL.md +106 -0
  48. package/skills-mcp/controllerai-node-config/references/prop-and-readiness.md +49 -0
  49. package/skills-mcp/controllerai-runs/SKILL.md +88 -0
  50. package/skills-mcp/controllerai-runs/references/debugging.md +53 -0
  51. package/skills-mcp/controllerai-start/SKILL.md +80 -0
  52. package/skills-mcp/controllerai-start/references/mcp-tool-map.md +175 -0
  53. package/skills-mcp/controllerai-triggers/SKILL.md +112 -0
  54. package/skills-mcp/controllerai-triggers/references/lifecycle.md +59 -0
  55. package/skills-mcp/controllerai-workflows/SKILL.md +109 -0
  56. package/skills-mcp/controllerai-workflows/references/command-map.md +1 -0
  57. package/skills-mcp/manifest.json +189 -0
  58. package/skills/controllerai-agents/references/command-reference.md +0 -205
  59. package/skills/controllerai-ai-nodes/references/command-reference.md +0 -98
  60. package/skills/controllerai-data/references/command-reference.md +0 -177
  61. package/skills/controllerai-expressions/references/command-reference.md +0 -166
  62. package/skills/controllerai-flow-control/references/command-reference.md +0 -171
  63. package/skills/controllerai-integration-actions/references/command-reference.md +0 -118
  64. package/skills/controllerai-node-config/references/command-reference.md +0 -186
  65. package/skills/controllerai-runs/references/command-reference.md +0 -113
  66. package/skills/controllerai-triggers/references/command-reference.md +0 -215
  67. package/skills/controllerai-workflows/references/command-reference.md +0 -118
@@ -1,39 +1,122 @@
1
1
  ---
2
2
  name: controllerai-flow-control
3
- description: Build Controller AI execution paths with conditions, routers, if/else branches, loops, joins and subflow calls. Use when a flow must validate, branch, iterate or reuse another flow.
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
- # Routing, loops and reusable flows
7
+ # Flow control
7
8
 
8
- Control-flow nodes decide which steps execute and how data moves between paths. Use conditions to validate requests, a router for named alternatives, a loop for each item in a list, and subflows for reusable behavior.
9
+ ## Use this when
9
10
 
10
- ## Choose the construct
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
- - **Only when:** skip one node unless a condition holds.
13
- - **If/Else:** two paths, such as record found versus not found.
14
- - **Router:** several named routes, such as search modes or operation types.
15
- - **For each / Run Flow on List:** execute a body flow for each typed item and collect iteration results.
16
- - **Subflow:** call a reusable typed flow and map its inputs/outputs.
17
- - **Join paths:** deliberately reconcile active paths before a shared continuation.
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`.
18
15
 
19
- Choose whether multiple matching routes should run or one alternative should win. Make invalid/missing-input paths return a clear result without performing the write. A skipped node is expected only if its condition and port explain why.
16
+ ## How it works
20
17
 
21
- ## Build branches
18
+ | Intent | Primitive | Runtime semantics |
19
+ | --- | --- | --- |
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 |
22
28
 
23
- Inspect the router's current expression context, define a route condition, and connect the route output to its path. Conditions can use the flow's inputs and wired upstream data. Use the supported structured OR-of-AND condition groups for compound logic; do not assume ordinary JavaScript syntax is valid in every condition surface.
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.
24
30
 
25
- A condition accepting a record requires a fetched record, not a fabricated object. Test both branches, including false/zero values and a missing record. Declare and populate the flow output on each intended return path.
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.
26
32
 
27
- ## Build loops
33
+ ## Rules
28
34
 
29
- Use the available for-each scaffolding operation to create the loop, body flow, typed item input and mappings together. It reduces the mistakes involved in assembling loop injection manually. Supply the list expression and exact source output when wiring an upstream list.
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.
30
45
 
31
- The body receives one item; it does not receive the whole original list implicitly. Custom-shaped items and real data-record items have different types. Loop output represents iteration results, so inspect its actual fields before mapping a final list.
46
+ ## Recipes
32
47
 
33
- For an uploaded CSV, a conversational agent can read rows and call a per-row flow. Use a native loop when the workflow already receives a typed list and deterministic iteration is desired.
48
+ ### Guard one action
34
49
 
35
- ## Verify paths
50
+ ```bash
51
+ cai expr context <nodeId> --json
52
+ cai expr validate <nodeId> --mode onlyWhen --js '<boolean expression>' --json
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
55
+ ```
36
56
 
37
- Run one representative case per route and inspect node status, selected output ports, skipped reasons and final outputs. For writes, verify the same-environment record effects. A successful outer loop does not prove every iteration succeeded; surface row failures in the result.
57
+ Use `--clear` only when removing the guard is the user's intent.
38
58
 
39
- Examples and commands: [command reference](references/command-reference.md). Control-flow details: [patterns](references/patterns.md).
59
+ ### If/Else and Router
60
+
61
+ ```bash
62
+ cai branch list <controlNodeId> --json
63
+ cai branch update <controlNodeId> --route <defaultIfBranchId> --name "<meaning>" --js '<boolean expression>' --json
64
+ cai branch add <controlNodeId> --name "<meaning>" --js '<boolean expression>' --json
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
67
+ cai branch reorder <ifElseNodeId> --order <conditionId1,conditionId2> --json
68
+ ```
69
+
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.
71
+
72
+ ### Loop over a list
73
+
74
+ ```bash
75
+ cai node get <listProducingNodeId> --json
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
77
+ cai node set <loopNodeId> --prop maxItems --value <n> --json
78
+ cai node set <loopNodeId> --prop continueOnIterationError --value true --json
79
+ ```
80
+
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`.
82
+
83
+ For multiline or shell-sensitive list source, pass `--js-file <path|->` instead of `--list-js`.
84
+
85
+ ### Call another flow
86
+
87
+ ```bash
88
+ cai node set-flow <runFlowNodeId> --flow <calledFlowId> --json
89
+ cai node props <runFlowNodeId> --json
90
+ ```
91
+
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.
93
+
94
+ ### Converge, return, handle errors
95
+
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
+ ```
104
+
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.
106
+
107
+ ## Verify
108
+
109
+ ```bash
110
+ cai branch list <controlNodeId> --json
111
+ cai workflow validate --json
112
+ cai run flow <flowId> --inputs <values.json> --json
113
+ cai exec tree <workflowExecutionId> --json
114
+ ```
115
+
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,24 +1,97 @@
1
1
  ---
2
2
  name: controllerai-integration-actions
3
- description: Discover and attach ready integration actions directly to Controller AI agents, configure connections and dynamic properties, and verify external app operations. Use when one existing action satisfies an agent capability.
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
- # Ready actions as agent tools
7
+ # Integration actions
7
8
 
8
- A ready integration action is one existing operation: search a service, read an object, create a record or send a message. Attach it directly to an agent when that operation is the capability. Use a workflow tool when you also need deterministic validation, multiple ordered steps, transformation, persistence or a reusable typed contract.
9
+ ## Use this when
9
10
 
10
- ## Discover and configure
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
- Search by the intended operation, inspect the matching integration, then read its exact action key and properties. Prefer capability evidence over a similar integration name. Native actions and external provider actions can have different authorization requirements.
15
+ Building the agent itself belongs to `controllerai-agents`; a workflow node for the same app belongs to `controllerai-node-config`.
13
16
 
14
- Select an existing matching connection and verify its identity. If a new connection is needed, return the connection URL and let the user complete provider authorization. Never request provider credentials in chat. An “active” stored connection still needs a representative action test to prove current authorization.
17
+ ## How it works
15
18
 
16
- Attach the action with its exact integration/action/connection and a description that tells the agent when to use it. Inspect configurable inputs and reload dependent options after selector changes. Choose the tool's runtime confirmation setting for the requested experience.
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.
17
20
 
18
- ## Verify the attached capability
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.
19
22
 
20
- Read the saved tool list and test in a draft conversation. Inspect the chosen action, arguments, terminal result and intended external effect. A direct action does not create a workflow execution. For a failed or uncertain mutation, inspect external state before repeating it.
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.
21
24
 
22
- Connection rebinding, detachment or revocation can affect Live independently of publishing the agent. If the user requested release, publish the updated agent configuration after testing and verify a Live conversation. Do not publish merely because the draft is ready.
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.
23
26
 
24
- Discovery, attachment, update and removal commands: [reference](references/command-reference.md).
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.
28
+
29
+ ## Rules
30
+
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.
38
+
39
+ ## Recipes
40
+
41
+ ### Discover
42
+
43
+ ```bash
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
47
+ ```
48
+
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.
50
+
51
+ ### Select or request a connection
52
+
53
+ ```bash
54
+ cai connection list --integration <integrationSlug> --json
55
+ ```
56
+
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.
58
+
59
+ ### Attach
60
+
61
+ ```bash
62
+ cai agent add-action <agentId> --integration <integrationSlug> --action <actionKey> --connection <connectionId> --description "Use when ..." --require-confirmation --json
63
+ cai agent tools <agentId> --json
64
+ ```
65
+
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.
68
+
69
+ ### Update
70
+
71
+ ```bash
72
+ cai agent update-tool <agentId> --tool <toolId> --connection <connectionId> --json
73
+ cai agent update-tool <agentId> --tool <toolId> --require-confirmation --json
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
76
+ ```
77
+
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.
79
+
80
+ ### Verify in a draft conversation
81
+
82
+ ```bash
83
+ cai agent test <agentId> --draft --message "<request that needs the action>" --json
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
87
+ ```
88
+
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.
90
+
91
+ ## Verify
92
+
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.
94
+
95
+ ## Report
96
+
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.
@@ -1,32 +1,122 @@
1
1
  ---
2
2
  name: controllerai-node-config
3
- description: Discover and configure Controller AI workflow action nodes, dynamic properties, connections, optional fields, output types and graph wiring. Use when assembling or repairing a concrete step in a flow.
3
+ description: Discover, add, connect, configure, wire, and verify Controller AI workflow nodes. Use when adding an action, choosing a component, binding an account, setting props (static, optional, option-backed or dynamic), wiring nodes, resolving an output type, or fixing a node that is not ready.
4
4
  ---
5
+ <!-- generated from .claude/skills by cli/scripts/render-skills.mjs; edit the source -->
5
6
 
6
- # Workflow nodes
7
+ # Node configuration
7
8
 
8
- A node is a step in a flow: a ready integration action, native data/file/AI action, or a control-flow construct. Its component defines properties, required authorization and output shape. The saved node is an instance configured for this workflow.
9
+ ## Use this when
9
10
 
10
- ## Discover by capability
11
+ - A flow needs an action from an app or a native service, and the node has to be found, added, bound to an account, configured and wired.
12
+ - A node reports missing connection, missing or disabled required props, pending selectors, or an unresolved output type.
11
13
 
12
- Use `integration_search` and `integration_action_list` to search for the intended action, then inspect the exact integration slug and action key. Integration display names are not component identities. For native storage use `registry_data`; for generated files inspect native file actions; for web research inspect available search actions and connections.
14
+ Computed values and conditions belong to `controllerai-expressions`; Router, If/Else, loops and subflows to `controllerai-flow-control`; Ask AI provider, model and typed output to `controllerai-ai-nodes`; tests and execution evidence to `controllerai-runs`.
13
15
 
14
- `integration_action_get` describes static properties. Some actions add dependent properties after a type, object, sheet or other selector is chosen. Saved connection status indicates an available binding; a successful representative provider request is the evidence it still works.
16
+ ## How it works
15
17
 
16
- ## Configure in dependency order
18
+ A **node** is a configured instance of a **component**: an integration action, a native data, file or AI action, or a control node. The component defines the props, whether an account is required, and the output shape. A node becomes usable in stages: component, connection, props in dependency order, then wiring and a concrete output type for whatever reads it downstream; each stage has its own readiness signal, so configure it as a sequence, not as one batch of property writes.
17
19
 
18
- 1. Add the action to the intended flow using its returned exact key. Name the node after its purpose or produced value.
19
- 2. Bind a required connection. Use `connection_connect_url` only when the user needs to connect an account; never request provider credentials in chat.
20
- 3. Set selectors first, reload properties with `node_prop_list`, then read the resulting schema.
21
- 4. Enable optional fields before setting them. Omission and an enabled empty value can have different effects, especially for updates.
22
- 5. Set literal properties with `value`; use `js` for computed values. Read `expr_context` for expected types and available inputs.
23
- 6. Wire the source output channel to the destination. Use returned output/port IDs; “main” and “error” are distinct channels.
24
- 7. Inspect `node_status` and `node_get`, test the step or complete flow, then verify the saved output type and downstream mapping.
20
+ **Connections** are the authorized accounts of an integration. A saved active connection proves a binding exists, not that the provider still accepts it; a representative request proves that. Changing a node's connection invalidates every connection-scoped option: re-fetch and re-verify them.
25
21
 
26
- Use the supported initial configuration batch when creating a node: `node_add` accepts its name, connection, upstream wiring, optional fields and a `props` map together. Direct values are literals; an advanced entry such as `"name": {"valueJs": "flow.name"}` compiles a typed expression. Put selectors such as `dataType` first so dependent fields exist. Inspect the returned `propsReadback` and readiness, then fix only rejected or missing fields. This avoids one call per property when repeating a proven CRUD pattern. Use flow scaffolds for loops and return mappings where they fit.
22
+ **Props** come in kinds: static values; optional props that exist in the catalog and appear once enabled; option-backed props whose values come from static lists or a provider lookup; reload selectors (`reloadProps: true`) that change the schema of later props; and expression-capable props that take a dynamic value. Options are resolved in prop order: an earlier single-choice selector gates later lookups. `optionsAvailability` reports `available`, `blocked_upstream` with `blockedBy`, or `dynamic_upstream`; when the field is absent, availability is unknown, not available.
27
23
 
28
- A static output contract should remain stable after a run. A dynamic action may learn its output shape from a representative test; re-read its output/context before authoring downstream fields. Do not overwrite a known contract with an unrelated inferred schema just to remove a warning.
24
+ **Readiness** (the node status read) reports distinct blockers: `missingConnection`, `missingRequiredProps`, `disabledRequiredProps` (the selector that activates them is named in `blockedBy` and `next`), `pendingReloadProps`, an unset subflow target or an invalid static option, and `dynamicSelectorNotes` (non-blocking: dependents still reflect the last concrete selection). `readyForExpressions` means the connection and the required and reload props are complete; `readyForRun` means it can execute; `readyForDownstreamExpressions` additionally means the output type is concrete. Readiness does not check wiring. `outputTypeStatus` is `concrete`, `unresolved` or `none` (a sink or control node has no output); an unresolved integration output is learned from a representative run, a supported output selector or a declared sample, and adoption changes only that node, so two instances of a generic component can learn different shapes. `next` is diagnostic guidance phrased as a CLI step: apply it through the current surface and its policy rather than executing it blindly.
29
25
 
30
- Use `controllerai-flow-control` for branches/loops and `controllerai-ai-nodes` for AI output configuration. Layout once after a group of structural changes.
26
+ ## Rules
31
27
 
32
- Detailed property-setting examples: [command reference](references/command-reference.md). Readiness and dynamic-property behavior: [prop reference](references/prop-and-readiness.md).
28
+ 1. **Use the exact component the discovery returned** (`nodeSlug` and full `key`); never infer a key from a display name, and never swap in a similar integration or a raw HTTP request (`registry_http-http-request`) because a connection is missing. A user's mention of an endpoint is context, not a request to hand-roll the call.
29
+ 2. **Add a node without a connection when no account exists.** `missingConnection: true` is a valid handoff state; get the user's authorization and bind the exact new connection ID afterwards. Never ask for a provider credential in chat.
30
+ 3. **Configure in dependency order and read back after every write.** Set reload selectors before their dependents, re-read props after each selector or connection change, and treat a write as done only after readback plus `node status`.
31
+ 4. **Write only props that exist.** Materialized props and catalog optionals are writable; a name absent from both is an error. Setting a catalog optional enables it automatically; do not enable first. If that write then fails, the prop stays enabled: disable it and re-read.
32
+ 5. **Repair in place.** Keep the node ID, wiring, conditions and references, because downstream bindings and subflow mappings point at that ID. Replace a node only when the requested component itself changes; then reconnect and revalidate every dependent.
33
+ 6. **Keep value and label together for option-backed props.** Labels are how providers that do not index opaque IDs are searched and how values are verified. Outside `dynamic_upstream`, a value that is not in the advertised options fails closed.
34
+ 7. **Wire before writing an expression that reads upstream data**, and use branch connections for Router and If/Else outputs.
35
+ 8. **Never fabricate an output schema.** Resolve the type with a representative run, a supported output selector, or a documented provider sample declared as unverified; preserve a static contract instead of overwriting it with an inferred one.
36
+ 9. **Update Data writes every enabled field that resolves to a value.** A field that resolves to nothing is left out, but an enabled field that resolves to null or empty text overwrites the stored value, so disable fields you enabled by accident and inspect the resolved values before running.
37
+
38
+ ## Recipes
39
+
40
+ ### Find the component
41
+
42
+ ```bash
43
+ cai integration actions "<capability>" --json
44
+ cai integration action --integration <nodeSlug> --key <key> --json
45
+ ```
46
+
47
+ Narrow a broad result with `cai integration search "<integration>" --actions --json`, then `cai integration actions "<action>" --integration <nodeSlug> --json`. Read `required`, `remoteOptions` and `reloadProps` before adding: they decide the configuration order.
48
+
49
+ ### Resolve the account
50
+
51
+ ```bash
52
+ cai connection list --integration <nodeSlug> --json
53
+ ```
54
+
55
+ One active connection: use its ID. Several: follow the user's stated preference, otherwise ask which account. None: add the node without a connection and hand off.
56
+
57
+ Get the authorization link with `cai connection connect <nodeSlug> --no-open` and share it right away rather than waiting on `--wait`. After the user confirms, list connections again and bind the new ID.
58
+
59
+ ```bash
60
+ cai node set-connection <nodeId> --connection <connectionId> --json
61
+ cai node status <nodeId> --json
62
+ ```
63
+
64
+ ### Add the node
65
+
66
+ ```bash
67
+ cai node add --flow <flowId> --node <nodeSlug> --key <key> --name "<data this node produces>" --connection <connectionId> --after <upstreamNodeId> --json
68
+ ```
69
+
70
+ Name the node after its purpose or the value it produces. Omit flags that do not apply. `--after` wires the upstream node's main output (or `--output error`) at creation, which later expressions need. Initial `--props` may carry only inspected static values, and `--enable-prop <name...>` is for optionals confirmed by the metadata. Never pass a remote option, a reload selector with its dependents, or a prop that has not materialized at creation. Capture the returned `nodeId`, then inspect `propsReadback` and `readiness` in the result and fix only rejected or missing fields.
71
+
72
+ ### Follow readiness
73
+
74
+ ```bash
75
+ cai node status <nodeId> --json
76
+ cai node props <nodeId> --json
77
+ ```
78
+
79
+ Clear blockers in this order: connection; required reload selectors; newly revealed required props; static and option-backed props; wiring an expression needs; expressions; output type. Use `next` as the guide for the next step. After a concrete selector or connection change, re-read props: fields appear, disappear or reset. `cai node reload <nodeId> --json` re-resolves dependents without re-setting a selector; when a materialized selector is unset it returns `reconcileSkipped: "unset_selector"` and preserves the dependents.
80
+
81
+ ### Set a prop
82
+
83
+ ```bash
84
+ cai node set <nodeId> --prop <exactName> --value '<literal-or-json>' --json
85
+ cai node optional-props <nodeId> --json
86
+ cai node options <nodeId> --prop <exactName> --query "<specific label>" --json
87
+ cai node set <nodeId> --prop <exactName> --value '<returned value>' --label '<returned label>' --json
88
+ cai node disable-prop <nodeId> --prop <exactName>
89
+ ```
90
+
91
+ Multi-select takes a JSON array of returned values and one `--label` per value in the same order. `node options` continues a provider page with `--prev-context '<context JSON>'` and previews under uncommitted earlier values with repeatable `--for '<earlierProp>=<value>'` (valid JSON is typed, bare text stays a string). When options are `blocked_upstream`, the error names the selector and prints the exact command to run first. For `dynamic_upstream`, no list exists: write a known exact ID with `--value` plus `--label`, or an expression with `--js`.
92
+
93
+ For a component whose dependent props exist only after a concrete selection, materialize a template first: set one concrete value, re-read props and configure the revealed dependents, then switch the selector to an expression. The dependents persist from that last concrete selection; starting dynamic leaves them absent. For dynamic values, load `controllerai-expressions`.
94
+
95
+ ### Wire
96
+
97
+ ```bash
98
+ cai node connect --flow <flowId> --from <sourceNodeId> --to <targetNodeId> --output main --json
99
+ ```
100
+
101
+ `--output error` is only for the automatic error path. Router and If/Else outputs use branch connections from `controllerai-flow-control`.
102
+
103
+ ### Resolve the output type
104
+
105
+ ```bash
106
+ cai run node <nodeId> --inputs '<json>' --json
107
+ cai node declare-output <nodeId> --sample-file - --json
108
+ ```
109
+
110
+ `--inputs` and `--sample-file` also take a file path; `--sample-file -` reads the documented response from stdin.
111
+
112
+ Prefer the representative run: the completed execution is checked for ownership and terminal state, then its output shape is adopted for that node. Inspect `outputTypeAdoption` in the run result: `no_observed_output` on a data-producing action means the run returned nothing reusable, so rerun it with inputs that return data only when a downstream binding needs the type and the additional real action is authorized; on a sink or control node it is the expected result. `error` means stop and surface it. Declare a documented sample (an object or a non-empty array of items) when the action is unavailable, unsafe or declined; it stays unverified until the first real run. A node test with a wired input is an isolated check; upstream expressions are verified only by running the containing flow. Adoption is node-local: a discovery run on a scratch node never types the node in the flow. Read `controllerai-runs` for discovery.
113
+
114
+ ## Verify
115
+
116
+ `node status` shows no blockers for the requested readiness level, every written prop reads back with the intended value and label, the wiring matches the flow, and the output type is concrete before downstream fields are bound. Expression context (`cai expr context <nodeId> --prop <prop> --json`) lists the upstream binding you expect.
117
+
118
+ ## Report
119
+
120
+ The component and connection used, which props were set and which remain for the user (a missing authorization above all), and whether the output type was verified by a run or only declared.
121
+
122
+ Read [references/prop-and-readiness.md](references/prop-and-readiness.md) when a prop is dynamic, option-backed, expression-capable, or unexpectedly absent, or when a status field needs its exact meaning.