@controllerai/cli 0.2.20 → 0.2.22
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/README.md +6 -3
- package/dist/api/client.js +5 -38
- package/dist/api/client.js.map +1 -1
- package/dist/commands/agent.js +169 -146
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/expr.js +10 -3
- package/dist/commands/expr.js.map +1 -1
- package/dist/commands/integration.js +30 -10
- package/dist/commands/integration.js.map +1 -1
- package/dist/commands/skills.js +1 -0
- package/dist/commands/skills.js.map +1 -1
- package/dist/commands/templateSetup.js +42 -0
- package/dist/commands/templateSetup.js.map +1 -0
- package/dist/commands/workflow.js +6 -7
- package/dist/commands/workflow.js.map +1 -1
- package/dist/errors.js +2 -5
- package/dist/errors.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/permissionTool.js +1 -1
- package/dist/permissionTool.js.map +1 -1
- package/package.json +6 -4
- package/skills/controllerai-agents/SKILL.md +109 -26
- package/skills/controllerai-agents/references/instructions-and-versioning.md +32 -34
- package/skills/controllerai-ai-nodes/SKILL.md +83 -10
- package/skills/controllerai-ai-nodes/references/selection-and-testing.md +44 -26
- package/skills/controllerai-data/SKILL.md +106 -34
- package/skills/controllerai-data/references/data-rails.md +24 -26
- package/skills/controllerai-expressions/SKILL.md +82 -29
- package/skills/controllerai-expressions/references/dsl-and-recipes.md +42 -40
- package/skills/controllerai-flow-control/SKILL.md +104 -21
- package/skills/controllerai-flow-control/references/patterns.md +23 -60
- package/skills/controllerai-integration-actions/SKILL.md +89 -11
- package/skills/controllerai-node-config/SKILL.md +108 -18
- package/skills/controllerai-node-config/references/prop-and-readiness.md +28 -78
- package/skills/controllerai-runs/SKILL.md +82 -25
- package/skills/controllerai-runs/references/debugging.md +24 -22
- package/skills/controllerai-start/SKILL.md +63 -35
- package/skills/controllerai-start/references/mcp-tool-map.md +22 -21
- package/skills/controllerai-template-setup/SKILL.md +132 -0
- package/skills/controllerai-template-setup/agents/openai.yaml +4 -0
- package/skills/controllerai-triggers/SKILL.md +113 -16
- package/skills/controllerai-triggers/references/lifecycle.md +36 -48
- package/skills/controllerai-workflows/SKILL.md +105 -25
- package/skills/controllerai-workflows/references/command-map.md +20 -44
- package/skills/manifest.json +96 -136
- 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 +91 -0
- package/skills-mcp/controllerai-ai-nodes/references/selection-and-testing.md +66 -0
- package/skills-mcp/controllerai-data/SKILL.md +116 -0
- package/skills-mcp/controllerai-data/references/data-rails.md +43 -0
- package/skills-mcp/controllerai-expressions/SKILL.md +97 -0
- package/skills-mcp/controllerai-expressions/references/dsl-and-recipes.md +136 -0
- package/skills-mcp/controllerai-flow-control/SKILL.md +112 -0
- package/skills-mcp/controllerai-flow-control/references/patterns.md +82 -0
- package/skills-mcp/controllerai-integration-actions/SKILL.md +90 -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 +87 -0
- package/skills-mcp/controllerai-runs/references/debugging.md +53 -0
- package/skills-mcp/controllerai-start/SKILL.md +82 -0
- package/skills-mcp/controllerai-start/references/mcp-tool-map.md +177 -0
- package/skills-mcp/controllerai-template-setup/SKILL.md +10 -0
- package/skills-mcp/controllerai-triggers/SKILL.md +109 -0
- package/skills-mcp/controllerai-triggers/references/lifecycle.md +59 -0
- package/skills-mcp/controllerai-workflows/SKILL.md +108 -0
- package/skills-mcp/controllerai-workflows/references/command-map.md +1 -0
- package/skills-mcp/manifest.json +200 -0
- package/skills/controllerai-agents/references/command-reference.md +0 -205
- package/skills/controllerai-ai-nodes/references/command-reference.md +0 -98
- package/skills/controllerai-data/references/command-reference.md +0 -177
- package/skills/controllerai-expressions/references/command-reference.md +0 -166
- package/skills/controllerai-flow-control/references/command-reference.md +0 -171
- package/skills/controllerai-integration-actions/references/command-reference.md +0 -118
- package/skills/controllerai-node-config/references/command-reference.md +0 -186
- package/skills/controllerai-runs/references/command-reference.md +0 -113
- package/skills/controllerai-triggers/references/command-reference.md +0 -215
- package/skills/controllerai-workflows/references/command-reference.md +0 -118
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: controllerai-flow-control
|
|
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
|
+
---
|
|
5
|
+
<!-- generated from .claude/skills by cli/scripts/render-skills.mjs; edit the source -->
|
|
6
|
+
|
|
7
|
+
# Flow control
|
|
8
|
+
|
|
9
|
+
## Use this when
|
|
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.
|
|
13
|
+
|
|
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`.
|
|
15
|
+
|
|
16
|
+
## How it works
|
|
17
|
+
|
|
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 |
|
|
28
|
+
|
|
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.
|
|
30
|
+
|
|
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.
|
|
32
|
+
|
|
33
|
+
## Rules
|
|
34
|
+
|
|
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.
|
|
45
|
+
|
|
46
|
+
## Recipes
|
|
47
|
+
|
|
48
|
+
### Guard one action
|
|
49
|
+
|
|
50
|
+
- `expr_context` — `nodeId`, `methods`, `prop`, `workflowId`
|
|
51
|
+
- `expr_validate` — `nodeId`, `js`, `mode`, `prop`, `workflowId`
|
|
52
|
+
- `node_only_when_set` — `nodeId`, `clear`, `groups`, `js`, `workflowId`
|
|
53
|
+
|
|
54
|
+
`expr_validate` takes `mode: "onlyWhen"` and no `prop`. `node_only_when_set` takes `js` for one expression or `groups` as the parsed arrays, for example `groups: [["flow.email.isNotEmpty()", "flow.units < 5"], ["flow.override === true"]]`; pass `clear: true` only when removing the guard is the user's intent.
|
|
55
|
+
|
|
56
|
+
### If/Else and Router
|
|
57
|
+
|
|
58
|
+
- `branch_list` — `nodeId`, `workflowId`
|
|
59
|
+
- `branch_update` — `nodeId`, `conditionJson`, `js`, `name`, `branchId`, `workflowId`
|
|
60
|
+
- `branch_add` — `nodeId`, `allowDuplicate`, `conditionJson`, `js`, `name`, `workflowId`
|
|
61
|
+
- `branch_update` — `nodeId`, `conditionJson`, `js`, `name`, `branchId`, `workflowId`
|
|
62
|
+
- `branch_connect` — `nodeId`, `flow`, `branchId`, `to`, `workflowId`
|
|
63
|
+
- `branch_reorder` — `nodeId`, `order`, `workflowId`
|
|
64
|
+
|
|
65
|
+
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.
|
|
66
|
+
|
|
67
|
+
`branch_update` and `branch_add` take `js` or `conditionJson` as the parsed arrays (`conditionJson: [["flow.email.isNotEmpty()", "flow.units < 5"], ["flow.override === true"]]`) with `branchId` for the route; `branch_connect` takes `branchId`, `to` and `flow`; `branch_reorder` takes `order` as the array of condition IDs.
|
|
68
|
+
|
|
69
|
+
### Loop over a list
|
|
70
|
+
|
|
71
|
+
- `node_get` — `nodeId`, `workflowId`
|
|
72
|
+
- `flow_for_each_scaffold` — `continueOnError`, `flow`, `from`, `itemField`, `itemName`, `itemType`, `listField`, `listJs`, `maxItems`, `name`, `sourceOutput`, `workflowId`
|
|
73
|
+
- `node_prop_set` — `nodeId`, `js`, `label`, `prop`, `rawExpressionJson`, `value`, `workflowId`, `valueJson`
|
|
74
|
+
|
|
75
|
+
`sourceOutput` 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 pass `listField` with the dot path below `.data`; reserve `listJs` for an exact advanced expression whose shape you took from the context; the two are mutually exclusive. `itemField` (an array of `id:type[:display]` strings) is only for creating a new `custom.*` item type; stored records use `dataRecord.custom.*`. Set `maxItems` on purpose when the requirement differs from 500, decide `continueOnError` 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`.
|
|
76
|
+
|
|
77
|
+
`flow_for_each_scaffold` takes `listField` or `listJs` inline; the server reads no local file. Later changes go through `node_prop_set` on the loop node: `prop: "maxItems"` with a number, or `prop: "continueOnIterationError"` with `value: true`.
|
|
78
|
+
|
|
79
|
+
### Call another flow
|
|
80
|
+
|
|
81
|
+
- `node_subflow_set` — `nodeId`, `flow`, `workflowId`
|
|
82
|
+
- `node_prop_list` — `nodeId`, `workflowId`
|
|
83
|
+
|
|
84
|
+
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.
|
|
85
|
+
|
|
86
|
+
### Converge, return, handle errors
|
|
87
|
+
|
|
88
|
+
- `node_add` — `after`, `connection`, `enableProp`, `flow`, `key`, `name`, `node`, `output`, `props`, `workflowId`
|
|
89
|
+
- `node_prop_set` — `nodeId`, `js`, `label`, `prop`, `rawExpressionJson`, `value`, `workflowId`, `valueJson`
|
|
90
|
+
- `flow_output_add` — `flowId`, `description`, `display`, `name`, `type`, `workflowId`
|
|
91
|
+
- `node_add` — `after`, `connection`, `enableProp`, `flow`, `key`, `name`, `node`, `output`, `props`, `workflowId`
|
|
92
|
+
- `node_continue_on_error_set` — `nodeId`, `enabled`, `workflowId`
|
|
93
|
+
- `node_connect` — `flow`, `from`, `output`, `to`, `workflowId`
|
|
94
|
+
|
|
95
|
+
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.
|
|
96
|
+
|
|
97
|
+
`node_add` takes `node: "registry_flow"`, `key: "registry_flow-merge"` (or `"registry_flow-return-response"`), `name` and `after`; `node_prop_set` sets `prop: "mode"`, `value: "chooseActivePath"` and `prop: "pathDataType"`; `node_continue_on_error_set` takes `enabled: true`; `node_connect` takes `output: "error"` for the error path.
|
|
98
|
+
|
|
99
|
+
## Verify
|
|
100
|
+
|
|
101
|
+
- `branch_list` — `nodeId`, `workflowId`
|
|
102
|
+
- `workflow_validate` — `workflowId`
|
|
103
|
+
- `run_flow` — `flowId`, `confirmLive`, `inputs`, `target`, `waitSeconds`, `workflowId`
|
|
104
|
+
- `execution_tree` — `workflowExecutionId`, `cursor`, `flowId`, `limit`, `status`, `workflowId`
|
|
105
|
+
|
|
106
|
+
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.
|
|
107
|
+
|
|
108
|
+
## Report
|
|
109
|
+
|
|
110
|
+
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.
|
|
111
|
+
|
|
112
|
+
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.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<!-- generated from .claude/skills by cli/scripts/render-skills.mjs; edit the source -->
|
|
2
|
+
# Flow-control patterns
|
|
3
|
+
|
|
4
|
+
## Guard a side effect
|
|
5
|
+
|
|
6
|
+
`only-when` means "skip this action, nothing else":
|
|
7
|
+
|
|
8
|
+
```text
|
|
9
|
+
Read record -> Send notification [only when status changed]
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Test true and false; the false run shows `only_when_false` on that node and downstream stays eligible.
|
|
13
|
+
|
|
14
|
+
## Exclusive decision
|
|
15
|
+
|
|
16
|
+
If/Else when exactly one outcome must occur:
|
|
17
|
+
|
|
18
|
+
```text
|
|
19
|
+
Score -> If/Else
|
|
20
|
+
high priority -> Escalate
|
|
21
|
+
normal -> Queue
|
|
22
|
+
Else -> Record unknown
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Most specific condition first; Else is the fallback and never gets a complement condition.
|
|
26
|
+
|
|
27
|
+
## Independent fan-out
|
|
28
|
+
|
|
29
|
+
Router when every applicable action should run:
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
New order -> Router
|
|
33
|
+
international -> Customs review
|
|
34
|
+
high value -> Fraud review
|
|
35
|
+
gift -> Gift note
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
One order may take all three routes. If that is not acceptable, use If/Else.
|
|
39
|
+
|
|
40
|
+
## Grouped conditions
|
|
41
|
+
|
|
42
|
+
Outer rows are OR, entries within a row are AND, and each string compiles on its own:
|
|
43
|
+
|
|
44
|
+
- `node_only_when_set` — `nodeId`, `clear`, `groups`, `js`, `workflowId`
|
|
45
|
+
- `branch_update` — `nodeId`, `conditionJson`, `js`, `name`, `branchId`, `workflowId`
|
|
46
|
+
|
|
47
|
+
## Per-item subflow
|
|
48
|
+
|
|
49
|
+
- `node_get` — `nodeId`, `workflowId`
|
|
50
|
+
- `flow_for_each_scaffold` — `continueOnError`, `flow`, `from`, `itemField`, `itemName`, `itemType`, `listField`, `listJs`, `maxItems`, `name`, `sourceOutput`, `workflowId`
|
|
51
|
+
|
|
52
|
+
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 (`continueOnError`) 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.
|
|
53
|
+
|
|
54
|
+
## Converge exclusive paths
|
|
55
|
+
|
|
56
|
+
- `node_add` — `after`, `connection`, `enableProp`, `flow`, `key`, `name`, `node`, `output`, `props`, `workflowId`
|
|
57
|
+
- `node_prop_set` — `nodeId`, `js`, `label`, `prop`, `rawExpressionJson`, `value`, `workflowId`, `valueJson`
|
|
58
|
+
- `node_prop_list` — `nodeId`, `workflowId`
|
|
59
|
+
- `node_prop_set` — `nodeId`, `js`, `label`, `prop`, `rawExpressionJson`, `value`, `workflowId`, `valueJson`
|
|
60
|
+
- `node_connect` — `flow`, `from`, `output`, `to`, `workflowId`
|
|
61
|
+
- `node_add` — `after`, `connection`, `enableProp`, `flow`, `key`, `name`, `node`, `output`, `props`, `workflowId`
|
|
62
|
+
|
|
63
|
+
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.
|
|
64
|
+
|
|
65
|
+
## Return a flow value
|
|
66
|
+
|
|
67
|
+
- `flow_output_add` — `flowId`, `description`, `display`, `name`, `type`, `workflowId`
|
|
68
|
+
- `node_add` — `after`, `connection`, `enableProp`, `flow`, `key`, `name`, `node`, `output`, `props`, `workflowId`
|
|
69
|
+
- `node_prop_list` — `nodeId`, `workflowId`
|
|
70
|
+
- `expr_context` — `nodeId`, `methods`, `prop`, `workflowId`
|
|
71
|
+
- `node_prop_set` — `nodeId`, `js`, `label`, `prop`, `rawExpressionJson`, `value`, `workflowId`, `valueJson`
|
|
72
|
+
|
|
73
|
+
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.
|
|
74
|
+
|
|
75
|
+
## Error path
|
|
76
|
+
|
|
77
|
+
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`.
|
|
78
|
+
|
|
79
|
+
- `node_continue_on_error_set` — `nodeId`, `enabled`, `workflowId`
|
|
80
|
+
- `node_connect` — `flow`, `from`, `output`, `to`, `workflowId`
|
|
81
|
+
|
|
82
|
+
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.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: controllerai-integration-actions
|
|
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
|
+
---
|
|
5
|
+
<!-- generated from .claude/skills by cli/scripts/render-skills.mjs; edit the source -->
|
|
6
|
+
|
|
7
|
+
# Integration actions
|
|
8
|
+
|
|
9
|
+
## Use this when
|
|
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.
|
|
14
|
+
|
|
15
|
+
Building the agent itself belongs to `controllerai-agents`; a workflow node for the same app belongs to `controllerai-node-config`.
|
|
16
|
+
|
|
17
|
+
## How it works
|
|
18
|
+
|
|
19
|
+
**A ready action is one operation.** The catalog exposes provider integration actions and native registry actions that an agent can call directly, without a workflow; a native action is attachable when the catalog marks it `agentAttachable`. Most native actions need no connection: they run on the platform's paid rails, bill the user's usage balance per call, and describe the charge in `usagePricingDescription`. A native action that does need authorization uses the platform connection flow (`connection_connect_url`), not Pipedream. 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
|
+
|
|
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. When the catalog says the action needs a connection, it binds to one whose `integration` matches that slug exactly.
|
|
22
|
+
|
|
23
|
+
**Runtime.** The agent runtime configures the action's dynamic arguments when it invokes the action; nothing is preconfigured at attachment beyond identity, any required 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
|
+
|
|
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
|
+
|
|
27
|
+
**Slack approvals.** Approval requests can also go to Slack through `slackRoute`. Routes are draft tool settings published with the agent like approval settings. Test conversations use the draft; live ones use the published version. Only the connection owner and configured approvers can answer in Slack; the app's approval card still works.
|
|
28
|
+
|
|
29
|
+
**Live bindings.** Publishing snapshots the action identity, description and approval policy into the agent version. When an action needs a connection, that 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 has the same immediate effect on a connectionless action, always 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; connected actions run through the owner's connected account and the organization's shared usage pool, while connectionless native actions use the platform's paid rails and bill that pool whoever invokes them.
|
|
30
|
+
|
|
31
|
+
## Rules
|
|
32
|
+
|
|
33
|
+
1. **Capture and reuse the exact identities**: integration slug, action key, connection ID when required, tool ID. Narrow the query or raise the result limit when more matches exist; never derive an identifier from a label.
|
|
34
|
+
2. **A required missing connection is a handoff, not a substitution.** Keep the selected integration and action, give the user the platform authorization route, and stop the attachment there; never request provider credentials in chat. Never swap in `registry_http-http-request`: the HTTP node is never agent-attachable, so that substitution is impossible by construction. A raw endpoint the user mentions is context, not an instruction to hand-roll the call.
|
|
35
|
+
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.
|
|
36
|
+
4. **Attach once.** Read the tool list before and after; the final list holds one intended direct attachment.
|
|
37
|
+
5. **A stored active connection is not proof of authorization.** For an action that needs one, only a representative action test proves the provider still accepts it.
|
|
38
|
+
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.
|
|
39
|
+
7. **Say whose account and balance run the action** before organization sharing: connected actions use the owner's account; connectionless native actions use the platform's paid rails; calls use the organization's shared usage balance regardless of caller.
|
|
40
|
+
|
|
41
|
+
## Recipes
|
|
42
|
+
|
|
43
|
+
### Discover
|
|
44
|
+
|
|
45
|
+
- `integration_search` — `query`, `limit`, `kind`
|
|
46
|
+
- `integration_action_list` — `query`, `integration`, `limit`
|
|
47
|
+
- `integration_action_get` — `integration`, `key`
|
|
48
|
+
|
|
49
|
+
`integration_search` ranks integrations (`kind` narrows to actions or events); `integration_action_list` ranks actions across one or all integrations; `integration_action_get` returns the static metadata and props of one exact action. Read `agentAttachable`, the exact props and, for native actions, whether a connection is needed and `usagePricingDescription` before choosing.
|
|
50
|
+
|
|
51
|
+
### Select or request a connection
|
|
52
|
+
|
|
53
|
+
- `connection_list` — `integration`
|
|
54
|
+
|
|
55
|
+
Skip this step when the catalog says the native action needs no connection.
|
|
56
|
+
|
|
57
|
+
Use the active connection whose `integration` matches exactly. If none exists, `connection_connect_url` (it takes the `nodeSlug`) returns a `url`: show it as a markdown link, ask the user to finish, then `connection_list` with `integration` and use the new ID.
|
|
58
|
+
|
|
59
|
+
### Attach
|
|
60
|
+
|
|
61
|
+
- `agent_tool_action_add` — `agentId`, `action`, `connectionId`, `description`, `integration`, `requireConfirmation`, `slackRoute`
|
|
62
|
+
- `agent_tool_list` — `agentId`
|
|
63
|
+
|
|
64
|
+
`agent_tool_action_add` takes `requireConfirmation`, optional and `false` by default; pass `connectionId` exactly when the action needs a connection.
|
|
65
|
+
The description tells the agent when to choose the tool. Read the saved grant back: `toolType`, integration, action key, any required connection, description and confirmation policy.
|
|
66
|
+
|
|
67
|
+
### Update
|
|
68
|
+
|
|
69
|
+
- `agent_tool_update` — `agentId`, `connectionId`, `description`, `requireConfirmation`, `toolId`, `slackRoute`
|
|
70
|
+
|
|
71
|
+
`agent_tool_update` takes `toolId` and at least one of `connectionId`, `description` or `requireConfirmation: true|false`; an omitted field keeps its value, so a connection-only update never changes the approval policy.
|
|
72
|
+
A connection change redirects a connected 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.
|
|
73
|
+
|
|
74
|
+
### Verify in a draft conversation
|
|
75
|
+
|
|
76
|
+
- `agent_test` — `agentId`, `message`, `model`, `waitSeconds`, `title`, `rail`
|
|
77
|
+
- `agent_conversation_history` — `conversationId`, `last`
|
|
78
|
+
- `agent_conversation_approve` — `conversationId`, `requestId`
|
|
79
|
+
- `agent_conversation_deny` — `conversationId`, `message`, `requestId`
|
|
80
|
+
|
|
81
|
+
`agent_test` takes `rail: "draft"` and `waitSeconds`; approve and deny take the `requestId` from the pending request.
|
|
82
|
+
`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.
|
|
83
|
+
|
|
84
|
+
## Verify
|
|
85
|
+
|
|
86
|
+
For an attachment, the tool list shows exactly one intended direct attachment with the exact identities, any required 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.
|
|
87
|
+
|
|
88
|
+
## Report
|
|
89
|
+
|
|
90
|
+
Which action is attached to which agent, any connection and the confirmation choice, what the draft test proved, whose account or paid rail and usage balance the action will use, and what remains: a connection to authorize, a publish, or a live verification.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: controllerai-node-config
|
|
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
|
+
---
|
|
5
|
+
<!-- generated from .claude/skills by cli/scripts/render-skills.mjs; edit the source -->
|
|
6
|
+
|
|
7
|
+
# Node configuration
|
|
8
|
+
|
|
9
|
+
## Use this when
|
|
10
|
+
|
|
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.
|
|
13
|
+
|
|
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`.
|
|
15
|
+
|
|
16
|
+
## How it works
|
|
17
|
+
|
|
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.
|
|
19
|
+
|
|
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.
|
|
21
|
+
|
|
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.
|
|
23
|
+
|
|
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.
|
|
25
|
+
|
|
26
|
+
## Rules
|
|
27
|
+
|
|
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
|
+
- `integration_action_list` — `query`, `integration`, `limit`
|
|
43
|
+
- `integration_action_get` — `integration`, `key`
|
|
44
|
+
|
|
45
|
+
Narrow a broad result with `integration_search`, then `integration_action_list`. Read `required`, `remoteOptions` and `reloadProps` before adding: they decide the configuration order.
|
|
46
|
+
|
|
47
|
+
### Resolve the account
|
|
48
|
+
|
|
49
|
+
- `connection_list` — `integration`
|
|
50
|
+
|
|
51
|
+
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.
|
|
52
|
+
|
|
53
|
+
Call `connection_connect_url` with the `nodeSlug`, show the returned `url` as a markdown link, ask the user to finish, then `connection_list` for that integration and bind the new ID. A URL is a handoff, not a completed authorization.
|
|
54
|
+
|
|
55
|
+
- `node_connection_set` — `nodeId`, `connection`, `workflowId`
|
|
56
|
+
- `node_status` — `nodeId`, `workflowId`
|
|
57
|
+
|
|
58
|
+
### Add the node
|
|
59
|
+
|
|
60
|
+
- `node_add` — `after`, `connection`, `enableProp`, `flow`, `key`, `name`, `node`, `output`, `props`, `workflowId`
|
|
61
|
+
|
|
62
|
+
Name the node after its purpose or the value it produces. `after` wires the upstream node's main output (`output: "error"` for its error path) at creation, which later expressions need; initial `props` may carry only inspected static values, and `enableProp` lists 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.
|
|
63
|
+
|
|
64
|
+
### Follow readiness
|
|
65
|
+
|
|
66
|
+
- `node_status` — `nodeId`, `workflowId`
|
|
67
|
+
- `node_prop_list` — `nodeId`, `workflowId`
|
|
68
|
+
|
|
69
|
+
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. `node_prop_reload` re-resolves dependents without re-setting a selector; when a materialized selector is unset it returns `reconcileSkipped: "unset_selector"` and preserves the dependents.
|
|
70
|
+
|
|
71
|
+
### Set a prop
|
|
72
|
+
|
|
73
|
+
- `node_prop_set` — `nodeId`, `js`, `label`, `prop`, `rawExpressionJson`, `value`, `workflowId`, `valueJson`
|
|
74
|
+
- `node_optional_prop_list` — `nodeId`, `workflowId`
|
|
75
|
+
- `node_prop_options` — `nodeId`, `for`, `page`, `prevContext`, `prop`, `query`, `workflowId`
|
|
76
|
+
- `node_prop_set` — `nodeId`, `js`, `label`, `prop`, `rawExpressionJson`, `value`, `workflowId`, `valueJson`
|
|
77
|
+
- `node_optional_prop_set` — `nodeId`, `prop`, `workflowId`, `enabled`
|
|
78
|
+
|
|
79
|
+
`node_prop_set` takes exactly one of `value`, `valueJson` (JSON text kept verbatim), `js` or `rawExpressionJson`; for an option-backed prop pass `label` as an array of the returned labels aligned with the values (a one-element array for a single selection). `node_prop_options` continues a page with `prevContext` (parsed JSON) and previews under uncommitted earlier values with `for` as an array of `"<prop>=<value>"` strings. `node_optional_prop_set` with `enabled: false` disables; `true` enables. When options are `blocked_upstream`, the error names the selector to set first. For `dynamic_upstream`, no list exists: set a known exact ID with its label, or an expression in `js`.
|
|
80
|
+
|
|
81
|
+
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`.
|
|
82
|
+
|
|
83
|
+
### Wire
|
|
84
|
+
|
|
85
|
+
- `node_connect` — `flow`, `from`, `output`, `to`, `workflowId`
|
|
86
|
+
|
|
87
|
+
`output: "error"` is only for the automatic error path. Router and If/Else outputs use branch connections from `controllerai-flow-control`.
|
|
88
|
+
|
|
89
|
+
### Resolve the output type
|
|
90
|
+
|
|
91
|
+
- `run_node` — `nodeId`, `inputs`, `waitSeconds`, `workflowId`
|
|
92
|
+
- `node_output_type_declare` — `nodeId`, `sample`, `workflowId`
|
|
93
|
+
|
|
94
|
+
`run_node` takes parsed `inputs` and `waitSeconds` (default 60, maximum 120). `node_output_type_declare` takes the parsed provider response in `sample`; the server cannot read a file path.
|
|
95
|
+
|
|
96
|
+
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.
|
|
97
|
+
|
|
98
|
+
## Verify
|
|
99
|
+
|
|
100
|
+
`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 (`expr_context`) lists the upstream binding you expect.
|
|
101
|
+
|
|
102
|
+
## Report
|
|
103
|
+
|
|
104
|
+
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.
|
|
105
|
+
|
|
106
|
+
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.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<!-- generated from .claude/skills by cli/scripts/render-skills.mjs; edit the source -->
|
|
2
|
+
# Prop and readiness reference
|
|
3
|
+
|
|
4
|
+
## What `node_status` reports
|
|
5
|
+
|
|
6
|
+
- `missingConnection`: an authenticated component has no account bound.
|
|
7
|
+
- `missingRequiredProps`: materialized required props without a value.
|
|
8
|
+
- `disabledRequiredProps`: names of required props currently disabled by an activating selector. `blockedBy` and `next` name that selector.
|
|
9
|
+
- `pendingReloadProps`: selectors that must be set before dependent props are trustworthy.
|
|
10
|
+
- `dynamicSelectorNotes`: reload selectors holding dynamic values whose dependents were not re-resolved. Non-blocking; read them even when the node is ready.
|
|
11
|
+
- `outputTypeStatus`: `concrete`, `unresolved` or `none`. Connection-port wrappers never count as a concrete process output.
|
|
12
|
+
- `readyForExpressions`: connection and required/reload props complete. The source still has to be wired before its binding appears in expression context.
|
|
13
|
+
- `readyForRun`: runnable; the run may be what discovers the output type.
|
|
14
|
+
- `readyForDownstreamExpressions`: runnable and the process output type is concrete enough for downstream field bindings. Require this, not `readyForRun`, before binding downstream fields.
|
|
15
|
+
- `next`: diagnostic guidance for the next step, phrased as a CLI command; apply it through the current surface and its policy.
|
|
16
|
+
|
|
17
|
+
## Prop channels
|
|
18
|
+
|
|
19
|
+
`node_prop_set` picks the channel from live metadata: `value` (or `valueJson` for JSON text kept verbatim) is a literal; `js` is an expression, read back as `valueJs` for text, JSON text and custom data, or `expressionValueJs` for a non-text prop in expression mode; `rawExpressionJson` round-trips a compiled tree. Pass exactly one of the four.
|
|
20
|
+
|
|
21
|
+
Never force a channel through internal fields. The expression setters compile before writing (a raw tree is validated by the backend), and every setter verifies the actual channel in the readback.
|
|
22
|
+
|
|
23
|
+
## Option-backed props
|
|
24
|
+
|
|
25
|
+
Static options are declared in the component metadata. Remote options need the current connection and the earlier prop values. When dependency metadata is present, each option-bearing prop reports:
|
|
26
|
+
|
|
27
|
+
- `available`: no earlier selector blocks the lookup (a connection or provider failure can still stop it).
|
|
28
|
+
- `blocked_upstream` with `blockedBy`: an earlier single-choice selector is unset. Follow the error's exact command first.
|
|
29
|
+
- `dynamic_upstream`: an earlier single-choice selector holds an expression, so no concrete option list exists. Write a known exact ID with its display label, or an expression. This is the only case that skips membership validation; it still needs prop metadata and verified readback.
|
|
30
|
+
|
|
31
|
+
Older backends and responses without a dependency catalog omit these fields; absence means unknown.
|
|
32
|
+
|
|
33
|
+
Pass the value and the label from the same live option. Many providers search labels rather than opaque IDs, so a copied ID without its label may be impossible to re-verify. Multi-select takes an ordered array of values and an aligned array of labels; the readback shows `{value, label}` records.
|
|
34
|
+
|
|
35
|
+
`node_prop_options` takes `query`, an opaque `prevContext` from the previous page, and `for` as an array of `"<prop>=<value>"` overrides for a read-only preview under uncommitted earlier values; `node_prop_set` takes the chosen `value` (or an array of values) and `label` as an array of the matching labels.
|
|
36
|
+
|
|
37
|
+
## Reload selectors
|
|
38
|
+
|
|
39
|
+
A prop with `reloadProps: true` changes the schema of later props. Set it, then immediately re-read props and status. Provider, model family, spreadsheet, database and action selectors commonly behave this way; never batch their dependents against stale metadata.
|
|
40
|
+
|
|
41
|
+
A prop reload re-resolves current values without re-setting a selector. If a materialized reload selector is unset, it returns `reconcileSkipped: "unset_selector"` and preserves the materialized dependents instead of reconciling them.
|
|
42
|
+
|
|
43
|
+
For a component whose dependent schema exists only after a concrete selection: set one concrete value, re-read and configure the revealed dependents, then switch the selector to an expression. The dependents persist from the last concrete selection; starting dynamic leaves them absent, and neither the dynamic write nor a reload materializes them.
|
|
44
|
+
|
|
45
|
+
## Output type
|
|
46
|
+
|
|
47
|
+
Resolve an unresolved output by setting the component's supported output or data-type selector, by running the configured node with representative inputs, or by declaring a documented provider sample as unverified. A discovery run (`controllerai-runs`) probes a component that is not in the graph on a scratch node: it deletes that node afterwards, keeps the scratch flow for reuse, and never types a node in the real flow.
|
|
48
|
+
|
|
49
|
+
A completed run passes its exact node-execution ID back for ownership, workflow, node, component-snapshot and terminal-state checks, then the persisted output shape is adopted for that non-static node only. Read `outputTypeAdoption` (or `outputTypeAdoptions`) in the run result: `no_observed_output` means the run produced no reusable output, which is expected for a sink or control node and, for a data-producing action, a reason to rerun with representative data only when a downstream binding needs the type; `error` means stop and surface it. After adoption, re-read `node status` and the expression context. An example response alone never establishes a type.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: controllerai-runs
|
|
3
|
+
description: Run Controller AI nodes and flows and read execution evidence: inputs, resolved configs, outputs, ports, skips, warnings and output-type adoption. Use when proving a build works, investigating a failed or green-but-wrong run, resolving a dynamic output type, or checking what an expression produced at runtime.
|
|
4
|
+
---
|
|
5
|
+
<!-- generated from .claude/skills by cli/scripts/render-skills.mjs; edit the source -->
|
|
6
|
+
|
|
7
|
+
# Runs
|
|
8
|
+
|
|
9
|
+
## Use this when
|
|
10
|
+
|
|
11
|
+
- A node or flow was just built or changed and needs proof with real values.
|
|
12
|
+
- A run failed, or finished green with empty, partial or wrong data.
|
|
13
|
+
- A dynamic node's output type is unknown and downstream expressions cannot be written.
|
|
14
|
+
|
|
15
|
+
Expression authoring belongs to `controllerai-expressions`, branch and loop structure to `controllerai-flow-control`, trigger replay to `controllerai-triggers`.
|
|
16
|
+
|
|
17
|
+
## How it works
|
|
18
|
+
|
|
19
|
+
**A run is real.** Node runs always execute on development; flow runs execute on development by default or, explicitly, on the published live version. Both call the real providers: messages send, records change, usage is spent. Development isolation covers the workflow data store only.
|
|
20
|
+
|
|
21
|
+
**Running resolves types.** A dynamic integration node gets its output type from one completed representative run (`outputTypeAdoption`), and every downstream expression depends on that type. When the user declines to run a consequential action, the fallback is a declared output contract built from the provider's documented response; it is marked `declared (unverified)` and the first real completed run verifies it.
|
|
22
|
+
|
|
23
|
+
**Three scopes.** A node run isolates the node's own configuration and does not execute wired upstream nodes; `runScope.endToEndValidationRequired` is computed from the wiring before the run and says that upstream-derived values need a containing-flow check, while `resolvedConfigs` shows what they actually were. A flow run executes the wiring and proves `inputs.<name>` expressions and the return contract. A discovery run creates a scratch node from a component that is not in the graph inside a reusable scratch flow, executes it and deletes the node (the flow stays), only to learn what it returns; it types nothing in the real flow.
|
|
24
|
+
|
|
25
|
+
**Inputs are keyed by ID.** `run inputs` lists the exact input IDs a node or flow reads. A flow lists every declared input with a `used` flag: supply the used ones (an omitted used input falls back to its saved test value), and values for unused inputs are accepted and ignored. An isolated node run must receive every referenced `flowInputs[].id` or it fails before executing with `ISOLATED_FLOW_CONTEXT_REQUIRED`. Explicit `null`, `""`, `false` and `0` are supplied values; dates are plain ISO-8601 text.
|
|
26
|
+
|
|
27
|
+
**The evidence.** `status` and `errorMessage`; `skipReason` (`only_when_false`, `upstream_skipped`); `runScope`; `resolvedConfigs`, where a null entry on a dynamic prop that the contract did not intend is a silent expression failure on an otherwise green run, and each entry's `resolutionWarnings` (an explicit `[]` means the resolver reported nothing; the value still has to match the contract); the output shape and values; and the adoption result, `outputTypeAdoption` on a node run or `outputTypeAdoptions` on a flow run, where `adopted` or `already_typed` lets the node status converge, `no_observed_output` means the run returned no reusable shape, and each entry's `adoptedNodeIds` must be empty or name only its own node.
|
|
28
|
+
|
|
29
|
+
**Outcomes.** A run ends `completed`, `completed_with_error` (a handled error path, the expected result when testing continue-on-error), `failed` or `cancelled`; it can also stop `paused` waiting on something, or still be running when the wait ends. Polling is bounded by `waitSeconds` (default 60, maximum 120); a timeout, a pause or a failure comes back as execution evidence with its ID. A running or paused status means inspect that execution later, never dispatch the run again.
|
|
30
|
+
|
|
31
|
+
## Rules
|
|
32
|
+
|
|
33
|
+
1. **Mitigate, do not refuse.** Before a send, delete, charge or people-affecting run, say what will really happen and agree a safe target: the user's own address or channel, a record created for the test, a person the user names. Reuse authorization the task already gave; ask only for what it did not cover.
|
|
34
|
+
2. **A declined run is declared, not skipped.** Declare the output from the provider's documented response, say it is unverified, and name the first real run as the verification step.
|
|
35
|
+
3. **Smallest scope first, then the containing flow.** A node run proves configuration; only a flow run proves upstream-derived values and the return value.
|
|
36
|
+
4. **Green is not proof, and red is not always failure.** Read resolved configs, outputs, ports and skips; a `completed` run with empty or null output is a failure until the values are read and explained, a null dynamic config the contract did not intend is a failure even when the status is green, and a run that matched the scenario's expected status, outputs and effects passed even when that status is `completed_with_error`. `0` and `false` are values.
|
|
37
|
+
5. **Find the first divergence, not the last error.** Walk the execution tree, classify each node as completed, failed, skipped on purpose, skipped unexpectedly or absent; later failures are usually consequences.
|
|
38
|
+
6. **Verify the effect where it lives.** Data writes: query the same store. Files: open the artifact. Provider actions: the provider's result, not the execution ID.
|
|
39
|
+
7. **Never let a stalled run become a duplicate.** Inspect the returned execution; rerun only after it reached a terminal state and the cause is understood.
|
|
40
|
+
8. **Adoption stays on the tested node.** Stop if `adoptedNodeIds` names a sibling, and re-read `node status` and downstream expression contexts after a type is adopted.
|
|
41
|
+
|
|
42
|
+
## Recipes
|
|
43
|
+
|
|
44
|
+
### Prepare inputs and run
|
|
45
|
+
|
|
46
|
+
- `run_inputs` — `nodeIdOrFlowId`, `flow`, `workflowId`
|
|
47
|
+
- `run_node` — `nodeId`, `inputs`, `waitSeconds`, `workflowId`
|
|
48
|
+
- `run_flow` — `flowId`, `confirmLive`, `inputs`, `target`, `waitSeconds`, `workflowId`
|
|
49
|
+
|
|
50
|
+
`run_inputs` takes `nodeIdOrFlowId` and `flow: true` for a flow (omit it for a node); `run_node` and `run_flow` take `inputs` as the parsed object keyed by input ID and `waitSeconds`.
|
|
51
|
+
|
|
52
|
+
### Declare an output the user will not run yet
|
|
53
|
+
|
|
54
|
+
- `node_output_type_declare` — `nodeId`, `sample`, `workflowId`
|
|
55
|
+
|
|
56
|
+
`node_output_type_declare` takes the parsed sample in `sample`; the server reads no file.
|
|
57
|
+
|
|
58
|
+
### Discover what a component returns
|
|
59
|
+
|
|
60
|
+
- `run_discover` — `connection`, `enable`, `key`, `node`, `props`, `workflowId`
|
|
61
|
+
|
|
62
|
+
`run_discover` takes `node`, `key`, `connection` (`"none"` for a component without auth), `props` and `enable`; it always deletes the scratch node and has no keep or wait parameter.
|
|
63
|
+
Discovery calls the real action and spends usage. Never probe a destructive action just to learn its output: agree a safe target or declare the shape instead. Prefer testing a configured node: discovery returns `observedOutputType`, `nodeExecution`, `workflowExecutionId`, `scratchFlowId` and `nodeDeleted`, the full execution stays readable by ID, and the real node in the flow still has to be typed by its own run or a declaration.
|
|
64
|
+
|
|
65
|
+
### Verify the published version
|
|
66
|
+
|
|
67
|
+
- `run_flow` — `flowId`, `confirmLive`, `inputs`, `target`, `waitSeconds`, `workflowId`
|
|
68
|
+
|
|
69
|
+
Pass `target: "live"` and `confirmLive: true` to `run_flow`.
|
|
70
|
+
When the user authorized live verification and named a safe target, run the published flow once under supervision instead of waiting for a real trigger event. It fires real actions and spends usage. Where the design calls for it, a safe-audience harness (an If/Else on `isLiveVersion` whose false branch targets the developer and whose true branch targets the real audience) protects development runs; the live run is still a real send, and adding that harness is a design change to agree, not a test step.
|
|
71
|
+
|
|
72
|
+
### Read the execution
|
|
73
|
+
|
|
74
|
+
- `execution_tree` — `workflowExecutionId`, `cursor`, `flowId`, `limit`, `status`, `workflowId`
|
|
75
|
+
- `execution_get` — `workflowExecutionId`, `workflowId`
|
|
76
|
+
- `execution_flow_get` — `flowExecutionId`, `workflowId`
|
|
77
|
+
- `execution_node_get` — `nodeExecutionId`, `full`, `workflowId`
|
|
78
|
+
|
|
79
|
+
Page the tree with the returned `nextCursor` as `cursor` and narrow a large run with `status` (an array) or an exact `flowId`; `execution_node_get` with `full: true` returns the untruncated output. The tree locates the first unexpected status; the full node execution holds the resolved configuration and the untruncated output. Read [references/debugging.md](references/debugging.md) for the fault-isolation order and the symptom table.
|
|
80
|
+
|
|
81
|
+
## Verify
|
|
82
|
+
|
|
83
|
+
The run exercised the changed path with credible values; the first unexpected node, if any, was identified from the tree; every dynamic config resolved to the intended value and `resolutionWarnings` were read; the output shape and values match what downstream expects; skips are intentional and error paths accounted for; a flow run reached the scenario's expected status, outputs and effects after isolated fixes; the stored or external effect was checked where it lives; and any declared contract is still marked unverified in the report.
|
|
84
|
+
|
|
85
|
+
## Report
|
|
86
|
+
|
|
87
|
+
What ran and on which version, the execution IDs, what each run proved, what is declared but unverified, and what remains untested and why.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<!-- generated from .claude/skills by cli/scripts/render-skills.mjs; edit the source -->
|
|
2
|
+
# Execution debugging
|
|
3
|
+
|
|
4
|
+
Use this order; it avoids changing a downstream node when the defect is upstream.
|
|
5
|
+
|
|
6
|
+
## 1. Confirm the version and the graph
|
|
7
|
+
|
|
8
|
+
- `workflow_status` — `workflowId`
|
|
9
|
+
- `flow_get` — `flowId`, `workflowId`
|
|
10
|
+
|
|
11
|
+
Node runs use development. Flow runs use development unless the published version was selected explicitly. Every run can call real providers.
|
|
12
|
+
|
|
13
|
+
## 2. Find the first divergence
|
|
14
|
+
|
|
15
|
+
- `execution_tree` — `workflowExecutionId`, `cursor`, `flowId`, `limit`, `status`, `workflowId`
|
|
16
|
+
|
|
17
|
+
Follow `nextCursor` as `cursor`; use `status` (an array) or an exact `flowId` on a large run. Classify each node as completed, completed with a handled error, failed, cancelled, paused, skipped on purpose (`only_when_false`), skipped unexpectedly (`upstream_skipped` behind a failure or a wrong branch), or absent. The first divergence is usually the cause; later failures are consequences.
|
|
18
|
+
|
|
19
|
+
## 3. Inspect the node boundary
|
|
20
|
+
|
|
21
|
+
- `execution_node_get` — `nodeExecutionId`, `full`, `workflowId`
|
|
22
|
+
|
|
23
|
+
In order:
|
|
24
|
+
|
|
25
|
+
1. Resolved configs: did each expression resolve, and to the right type and value? A null entry on a dynamic prop that the contract did not intend is the failure even when the node is green; read each entry's `resolutionWarnings`.
|
|
26
|
+
2. Port: did the node select the branch or output the test expected?
|
|
27
|
+
3. Output: did the component return the promised shape and values? Compare a flow's return with its declared outputs.
|
|
28
|
+
4. Status and error: did the provider or the engine reject the call?
|
|
29
|
+
5. Upstream rows and wiring: did every expected producer run and connect to this node?
|
|
30
|
+
|
|
31
|
+
## 4. Fix by symptom
|
|
32
|
+
|
|
33
|
+
| Symptom | Inspect next |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| Missing or wrong input | Wiring, branch connection, or the test input ID |
|
|
36
|
+
| Null dynamic config | Expression binding, field path, or a missing source value |
|
|
37
|
+
| Invalid option or account | Node connection, dependency-ordered props, or a stale option |
|
|
38
|
+
| Provider error | Integration configuration, credentials, quota, or payload |
|
|
39
|
+
| Green node, wrong output | Component choice, output contract, or prompt |
|
|
40
|
+
| Unexpected skip | Guard, branch condition or order, or an upstream failure |
|
|
41
|
+
| Empty record query | Store (development versus live), field key, optional-filter semantics |
|
|
42
|
+
| Provider authentication failure | Connection identity and the provider's error; an active status is not a fresh credential test |
|
|
43
|
+
| Return value empty | Return node bindings, skipped branches, or resolved empty values |
|
|
44
|
+
|
|
45
|
+
Re-read metadata and contexts before editing. Rerun the smallest failed scope, then the full flow.
|
|
46
|
+
|
|
47
|
+
## 5. Avoid false confidence
|
|
48
|
+
|
|
49
|
+
- A compile pass does not prove runtime data exists.
|
|
50
|
+
- A node pass does not prove downstream mapping: a node run does not execute wired upstream nodes.
|
|
51
|
+
- A development pass does not prove live data or the live release state.
|
|
52
|
+
- A top-level completed status does not prove every intended branch ran, and a loop's success does not prove every iteration.
|
|
53
|
+
- Keep the evidence of failed runs and separate it clearly from the final verified result; broaden the test when a fix changes shared behavior.
|