@controllerai/cli 0.2.16 → 0.2.17
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/dist/commands/node.js +93 -39
- package/dist/commands/node.js.map +1 -1
- package/package.json +1 -1
- package/skills/controllerai-agents/SKILL.md +53 -38
- package/skills/controllerai-ai-nodes/SKILL.md +19 -3
- package/skills/controllerai-data/SKILL.md +33 -1
- package/skills/controllerai-expressions/SKILL.md +41 -9
- package/skills/controllerai-expressions/references/dsl-and-recipes.md +10 -0
- package/skills/controllerai-flow-control/SKILL.md +31 -7
- package/skills/controllerai-flow-control/references/patterns.md +65 -0
- package/skills/controllerai-integration-actions/SKILL.md +18 -0
- package/skills/controllerai-node-config/SKILL.md +3 -1
- package/skills/controllerai-start/SKILL.md +8 -0
- package/skills/controllerai-triggers/SKILL.md +38 -8
- package/skills/controllerai-triggers/references/lifecycle.md +16 -1
- package/skills/controllerai-workflows/SKILL.md +31 -15
- package/skills/manifest.json +60 -60
|
@@ -5,26 +5,31 @@ description: Design, assemble, test, publish, and share user-facing Controller A
|
|
|
5
5
|
|
|
6
6
|
# Controller AI agents
|
|
7
7
|
|
|
8
|
-
An agent has one mutable draft and immutable published versions.
|
|
9
|
-
|
|
10
|
-
Build the draft in this order: instructions, tools and descriptions, user-decided permissions, reference files, Test verification, publish, then optional Slack and organization sharing.
|
|
8
|
+
An agent has one mutable draft and immutable published versions. Build instructions, tools, permissions, and files on the draft; verify in Test; publish last. Draft edits never update Live by themselves.
|
|
11
9
|
|
|
12
10
|
The `cai ...` examples are canonical grammar. Use the transport selected in `controllerai-start` and its MCP tool map; never mix transports in one task or call Controller AI REST routes directly.
|
|
13
11
|
|
|
14
|
-
##
|
|
12
|
+
## Rules that break tasks when skipped
|
|
13
|
+
|
|
14
|
+
- Attach one ready action directly instead of wrapping it; see Choose the capability architecture.
|
|
15
|
+
- Choose confirmation before attaching a consequential tool; see Create and assemble the draft.
|
|
16
|
+
- Use one conversation on an explicit rail for each verification task; see Verify the draft as an agent.
|
|
17
|
+
- Resolve pending requests in that same conversation; see Verify the draft as an agent.
|
|
18
|
+
- Enablement is runtime state, not publication; see Create and assemble the draft.
|
|
19
|
+
- Publish authorized workflow dependencies, preflight, then publish the agent; see Publish in dependency order.
|
|
20
|
+
- Draft-only means stop before publish; draft evidence never proves Live. See Publish in dependency order.
|
|
15
21
|
|
|
16
|
-
|
|
22
|
+
## 1. Choose the capability architecture
|
|
17
23
|
|
|
18
24
|
- Use `controllerai-integration-actions` when one existing app operation completes it.
|
|
19
25
|
- Use a workflow tool for multiple steps, transformations, branching, looping, durable state, triggers, a custom typed contract, or reusable business logic.
|
|
20
|
-
- Put
|
|
21
|
-
- Put stable source material in a knowledge file.
|
|
26
|
+
- Put judgment, tool-selection rules, tone, and escalation in instructions; put stable sources in knowledge files.
|
|
22
27
|
|
|
23
28
|
Do not create a one-node workflow merely to expose a ready action, or replace orchestration with several independent direct actions when ordering or data handling must be deterministic.
|
|
24
29
|
|
|
25
30
|
## 2. Build and verify capabilities
|
|
26
31
|
|
|
27
|
-
For a ready action, follow `controllerai-integration-actions`: discover
|
|
32
|
+
For a ready action, follow `controllerai-integration-actions`: discover exact identities, select a connection, decide confirmation, then attach.
|
|
28
33
|
|
|
29
34
|
For every workflow tool, load the relevant workflow skills and test the development state before attaching:
|
|
30
35
|
|
|
@@ -36,12 +41,11 @@ cai run flow <flowId> --inputs values.json --json
|
|
|
36
41
|
|
|
37
42
|
MCP: there is no pin; pass `workflowId` to `flow_get` and `run_flow`, pass parsed `inputs`, and set `waitSeconds`.
|
|
38
43
|
|
|
39
|
-
Capture the successful
|
|
40
|
-
Before attaching, require a nonempty output contract and verify the completed run returns it; otherwise the agent receives an empty result.
|
|
44
|
+
Capture the successful execution ID. Inputs become tool parameters and outputs its result. Require a nonempty output contract and verify the run returns it.
|
|
41
45
|
|
|
42
46
|
## 3. Create and assemble the draft
|
|
43
47
|
|
|
44
|
-
Write short instructions
|
|
48
|
+
Write short instructions for role, tool use, inputs/results, safety, response style, and errors. Require truthful failure reporting and at most one retry.
|
|
45
49
|
|
|
46
50
|
Read [references/instructions-and-versioning.md](references/instructions-and-versioning.md) before publishing or explaining Test versus Live behavior.
|
|
47
51
|
|
|
@@ -75,9 +79,7 @@ cai agent tools <agentId> --json
|
|
|
75
79
|
cai agent get <agentId> --json
|
|
76
80
|
```
|
|
77
81
|
|
|
78
|
-
MCP: use `agent_knowledge_file_add`
|
|
79
|
-
|
|
80
|
-
Adding with `--space shared` updates the live runtime file space immediately without publishing; an active agent sees it on the next turn, a disabled agent after enablement.
|
|
82
|
+
MCP: use `agent_knowledge_file_add` or `agent_workspace_file_add` with inline `content`; `agent_file_list` uses `environment: dev|live`. Shared runtime files update live immediately.
|
|
81
83
|
|
|
82
84
|
The default is no approval: attached actions and workflow tools run without a human approval step unless `requireConfirmation` is `true`, and omitting `--require-confirmation` has the same behavior. When attaching an action that sends, posts, spends, or deletes, choose `true` or explicitly tell the user that it will run unattended. Mention the confirmation choice in the report to the user; `cai agent update-tool` can change it later.
|
|
83
85
|
|
|
@@ -91,11 +93,9 @@ Keep the two modes straight:
|
|
|
91
93
|
| Runtime files | Current live runtime files, overlaid by development/test runtime files at the same path | Current live runtime files only |
|
|
92
94
|
| Access | Owner only | Owner, plus organization members when shared; non-owners never see the draft |
|
|
93
95
|
|
|
94
|
-
Version pinning for workflow tools does not exist
|
|
95
|
-
|
|
96
|
-
Publishing the agent snapshots name, instructions, tool identities, descriptions, approval policies, and knowledge files; it moves the live pointer and restamps every existing live conversation to the new version. It does not snapshot the runtime model.
|
|
96
|
+
Version pinning for workflow tools does not exist. Live tools track the latest release. If asked to freeze one, explain this, attach with policy `latest`, and never abandon the attachment.
|
|
97
97
|
|
|
98
|
-
Publishing
|
|
98
|
+
Publishing snapshots versioned fields, moves the live pointer, and restamps every existing live conversation. It does not snapshot the runtime model. This cutover interrupts in-flight answers and expires pending approvals; time it accordingly.
|
|
99
99
|
|
|
100
100
|
Runtime lifecycle changes do not require publishing:
|
|
101
101
|
|
|
@@ -105,9 +105,7 @@ cai agent update <agentId> --enabled --json
|
|
|
105
105
|
cai agent delete <agentId> --confirm --json
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
-
MCP: draft edits use `agent_update`; runtime
|
|
109
|
-
|
|
110
|
-
Disable pauses runtime immediately; enable resumes it. Delete is soft: it revokes live access, requests best-effort termination of active sessions and pending approvals, keeps history but makes it inaccessible, has no CLI restore, and does not guard dependent workflows.
|
|
108
|
+
MCP: draft edits use `agent_update`; runtime uses `agent_set_enabled`; `agent_delete` has no `confirm`. Disable pauses runtime. Soft delete revokes access and requests session termination; there is no CLI restore.
|
|
111
109
|
|
|
112
110
|
## Runtime usage boundary
|
|
113
111
|
|
|
@@ -124,11 +122,16 @@ cai agent conversation history <conversationId> --json
|
|
|
124
122
|
|
|
125
123
|
MCP: `agent_test` requires `rail: draft` or `rail: live` and takes `waitSeconds`.
|
|
126
124
|
|
|
127
|
-
|
|
125
|
+
`agent test` always creates a conversation. Use it for a one-shot check. When a task may need follow-up or approval, create one conversation on the requested rail, capture its ID, and reuse it:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
cai agent conversation create <agentId> --draft --title "<test case>" --json
|
|
129
|
+
cai agent conversation send <conversationId> --message "<request>" --wait --json
|
|
130
|
+
```
|
|
128
131
|
|
|
129
|
-
|
|
132
|
+
Do not resend the request or create a replacement after an approval pause. A required post-publish Live check is a separate rail task and gets one fresh Live conversation.
|
|
130
133
|
|
|
131
|
-
|
|
134
|
+
Test positive and negative tool selection, exact arguments, results, errors, and knowledge. Offer the returned `appUrl`; on MCP, call `agent_present` and relay the returned card.
|
|
132
135
|
|
|
133
136
|
- For a workflow tool, correlate a side-effectful call with its terminal workflow execution ID.
|
|
134
137
|
- For a direct integration action, inspect persisted conversation action events/run result and the intended external effect. Direct actions do not create workflow execution IDs.
|
|
@@ -140,7 +143,7 @@ cai agent conversation approve <conversationId> --request <requestId> --json
|
|
|
140
143
|
cai agent conversation deny <conversationId> --request <requestId> --message "<reason>" --json
|
|
141
144
|
```
|
|
142
145
|
|
|
143
|
-
The server resolves the tool from the
|
|
146
|
+
The server resolves the tool from the request ID. Do not approve merely to continue. Resolve every pending request before publishing; approval or denial resumes this conversation, while publishing expires it.
|
|
144
147
|
|
|
145
148
|
After `cai agent remove-tool`, test one request that must no longer use the removed capability and one retained capability.
|
|
146
149
|
|
|
@@ -158,6 +161,8 @@ MCP: there is no `use`; pass `workflowId` to `workflow_status` and `workflow_pub
|
|
|
158
161
|
|
|
159
162
|
Publish a workflow only when its verified development state must go live and the user authorized it. A workflow publish already followed by a live agent changes that tool's live behavior immediately; do not republish the agent merely to pick it up.
|
|
160
163
|
|
|
164
|
+
For a draft-only request, stop after Test evidence. Do not publish a workflow or agent merely because it is ready.
|
|
165
|
+
|
|
161
166
|
Then publish the agent:
|
|
162
167
|
|
|
163
168
|
```bash
|
|
@@ -169,31 +174,41 @@ cai agent test <agentId> --live --message "<representative request>" --json
|
|
|
169
174
|
|
|
170
175
|
MCP: `agent_test` requires `rail: draft` or `rail: live` and takes `waitSeconds`.
|
|
171
176
|
|
|
172
|
-
`publish-preflight` is the arbiter of structural readiness
|
|
177
|
+
`publish-preflight` is the arbiter of structural readiness. Report blockers and stop. With none and an authorized release, publish, except an error you actually watched happen; that blocks even a green preflight.
|
|
173
178
|
|
|
174
179
|
Email verification is not a publishing prerequisite. An unclaimed agent-created account may publish; email claiming only raises its monthly usage cap and enables account recovery.
|
|
175
180
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
Published agent versions have no rollback — never promise to "revert" one. Recover forward: restore the wanted DRAFT and publish anew — for agents, reapply an earlier snapshot from `cai agent versions`; for workflow tools, `cai workflow history` + `cai workflow restore --state <stateId>` restores the dev DEFINITION (not record data), then republish.
|
|
179
|
-
|
|
180
|
-
Follow the live-bound exceptions in [references/instructions-and-versioning.md](references/instructions-and-versioning.md): workflow releases, direct-action grants, and runtime files can change Live without an agent publish. Verify them with a fresh Live conversation; after detaching an action, publish to remove the unavailable tool from the published list.
|
|
181
|
+
Use one loop: edit draft, read back, verify Test, publish, verify Live. Published versions have no rollback. Recover forward from `agent versions`; restore workflow definitions with history/restore, then republish. Workflow releases, action grants, and runtime files are live-bound; see [references/instructions-and-versioning.md](references/instructions-and-versioning.md). After detaching an action, publish to remove it from the published list.
|
|
181
182
|
|
|
182
183
|
Draft evidence does not prove the live agent. Test is owner-only; shared users always run the latest published agent version and never see draft instructions, tools, or knowledge.
|
|
183
184
|
|
|
184
185
|
## 6. Offer Slack and organization sharing
|
|
185
186
|
|
|
186
|
-
After
|
|
187
|
-
|
|
188
|
-
- **Slack**: talking to the agent in Slack needs the Slack ↔ Controller AI connection plus per-agent Slack configuration (slug, channel), both set up in the product — point the user there and offer a walkthrough.
|
|
189
|
-
- **Sharing**: agents remain private unless the user explicitly asks for organization access:
|
|
187
|
+
After publish, offer product setup for Slack. Agents remain private unless the user requests organization access:
|
|
190
188
|
|
|
191
189
|
```bash
|
|
192
190
|
cai agent visibility <agentId> --value organization --json
|
|
193
191
|
```
|
|
194
192
|
|
|
195
|
-
|
|
193
|
+
Members use the published version. Direct actions use the owner's external account and organization usage pool. Grant changes affect Live immediately; private visibility revokes access.
|
|
194
|
+
|
|
195
|
+
## Before you finish
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
cai agent tools <agentId> --json
|
|
199
|
+
cai agent conversation list <agentId> --mode dev --json
|
|
200
|
+
cai data query --type custom.<type> --env dev --json
|
|
201
|
+
cai agent conversation history <conversationId> --json
|
|
202
|
+
cai agent publish-preflight <agentId> --json
|
|
203
|
+
cai agent versions <agentId> --json
|
|
204
|
+
cai agent test <agentId> --live --message "<representative request>" --json
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Run the last three commands only for an authorized release. For Live verification, use `--mode live` and `--env live` in the same pair.
|
|
196
208
|
|
|
197
|
-
|
|
209
|
+
- Conversation list must show the captured ID on the requested rail, without retry-created duplicates.
|
|
210
|
+
- History must show the intended tool arguments, terminal result, and no unresolved permission request.
|
|
211
|
+
- For stateful behavior, the same-rail data query must show the expected record; an assistant reply is not proof.
|
|
212
|
+
- A release requires clean preflight, agent version read-back, and one fresh Live conversation. Draft-only work must remain unpublished.
|
|
198
213
|
|
|
199
|
-
|
|
214
|
+
Report the agent ID/version, tools and confirmation policies, execution or direct-action evidence, rail state, connections, access, and anything unverified.
|
|
@@ -7,7 +7,14 @@ description: Configure and verify AI-powered Controller AI workflow nodes, espec
|
|
|
7
7
|
|
|
8
8
|
The `cai ...` examples are canonical grammar. Use the transport selected in `controllerai-start` and its MCP tool map; never mix transports in one task.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
## Rules that break tasks when skipped
|
|
11
|
+
|
|
12
|
+
- Use native Ask AI for a bounded AI step; see Configure Ask AI in dependency order.
|
|
13
|
+
- Keep an existing Ask AI node unless the requested component changes; see Configure Ask AI in dependency order.
|
|
14
|
+
- Set reload selectors before fetching live model options; see Configure Ask AI in dependency order.
|
|
15
|
+
- Never invent or hardcode a model ID; see Configure Ask AI in dependency order.
|
|
16
|
+
|
|
17
|
+
## Choose the AI surface
|
|
11
18
|
|
|
12
19
|
- Use Ask AI for one bounded transformation with explicit inputs and output contract.
|
|
13
20
|
- Use a Controller AI agent for conversational judgment, multiple tools, durable instructions, and knowledge files. Load `controllerai-agents`.
|
|
@@ -15,6 +22,8 @@ First decide whether the capability belongs inside a deterministic workflow or i
|
|
|
15
22
|
|
|
16
23
|
## Configure Ask AI in dependency order
|
|
17
24
|
|
|
25
|
+
For a bounded workflow transformation, use the native Ask AI component. Do not replace it with a provider integration action or delete an existing Ask AI node to bypass configuration.
|
|
26
|
+
|
|
18
27
|
Discover the current component instead of hardcoding a key:
|
|
19
28
|
|
|
20
29
|
```bash
|
|
@@ -43,7 +52,7 @@ cai expr set <nodeId> --prop <promptProp> --js '<prompt expression>' --json
|
|
|
43
52
|
|
|
44
53
|
MCP: `node set` and `expr set` both use `node_prop_set`; pass exactly one of `value` or `js` here.
|
|
45
54
|
|
|
46
|
-
Model catalogs, availability, and prices change. Use current options and
|
|
55
|
+
Model catalogs, availability, and prices change. Use current options and the user's cost/quality preference. If the requested model is absent, leave it unset, report it, and ask; never substitute.
|
|
47
56
|
|
|
48
57
|
## Prefer typed output for downstream logic
|
|
49
58
|
|
|
@@ -74,10 +83,12 @@ Avoid filler personas, repeated instructions, and claims about data not present
|
|
|
74
83
|
|
|
75
84
|
Read [references/selection-and-testing.md](references/selection-and-testing.md) for node choice and evaluation.
|
|
76
85
|
|
|
77
|
-
##
|
|
86
|
+
## Before you finish
|
|
78
87
|
|
|
79
88
|
```bash
|
|
89
|
+
cai node get <nodeId> --json
|
|
80
90
|
cai node status <nodeId> --json
|
|
91
|
+
cai node props <nodeId> --json
|
|
81
92
|
cai run node <nodeId> --inputs values.json --json
|
|
82
93
|
cai exec node <nodeExecutionId> --full --json
|
|
83
94
|
```
|
|
@@ -86,4 +97,9 @@ MCP: pass parsed values in `inputs` and set `waitSeconds` on `run_node`.
|
|
|
86
97
|
|
|
87
98
|
Test at least: normal input, missing/ambiguous input, and an adversarial or boundary case relevant to the task. Check resolved prompt/config, output type, required fields, and values—not just that text was returned.
|
|
88
99
|
|
|
100
|
+
- `node get` must identify the intended native Ask AI component.
|
|
101
|
+
- `node status` must report ready.
|
|
102
|
+
- Props must contain the exact returned provider/model value and label.
|
|
103
|
+
- The execution must show the resolved prompt and typed output fields.
|
|
104
|
+
|
|
89
105
|
AI execution inside the workflow is platform/API usage. Access through either builder transport does not transfer the coding-agent subscription to runtime AI nodes.
|
|
@@ -12,8 +12,19 @@ Controller AI has two separate concerns:
|
|
|
12
12
|
- A `custom.*` schema defines fields and makes a collection available.
|
|
13
13
|
- Development and live record stores hold separate rows. Publishing graph/configuration does not copy records.
|
|
14
14
|
|
|
15
|
+
## Rules that break tasks when skipped
|
|
16
|
+
|
|
17
|
+
- Persisting data requires both a schema and a create-record node; see A type vs a stored record.
|
|
18
|
+
- `schema update --field` replaces the full field list; see Define a schema deliberately.
|
|
19
|
+
- Relabel a stable key instead of recreating it; see How schema changes affect stored records.
|
|
20
|
+
- Query results and writes use different field keys; see Respect field-key asymmetry.
|
|
21
|
+
- Read and write the requested dev or live store; see Work on the correct rail.
|
|
22
|
+
- Remove only scratch schemas created for abandoned attempts; see Before you finish.
|
|
23
|
+
|
|
15
24
|
## A type vs a stored record
|
|
16
25
|
|
|
26
|
+
When a request says store, save, log, or record with no external destination, create the schema and configure a create-record node. A flow output alone is not persistence.
|
|
27
|
+
|
|
17
28
|
`custom.customer` and `dataRecord.custom.customer` are different things, and the difference carries through everything:
|
|
18
29
|
|
|
19
30
|
- `custom.customer` is the TYPE — the shape, like a TypeScript interface. A value of this type is just data in that shape.
|
|
@@ -69,7 +80,13 @@ Schema changes never hard-delete data. There is no delete path for records in sc
|
|
|
69
80
|
|
|
70
81
|
The truly destructive operations are record-level only: `data delete`, `data delete-many`, and overwriting edits. Those have no undo — treat them with explicit user intent; everything schema-shaped is safely reversible.
|
|
71
82
|
|
|
72
|
-
|
|
83
|
+
Relabel a type without changing its stable key:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
cai schema update custom.customer --display "Customers" --json
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Changing `custom.customer` itself is a key migration, not a relabel. It creates a new type and deactivates the old collection, stranding its records under the old key. Migrate records explicitly by export/import or restore the old key.
|
|
73
90
|
|
|
74
91
|
## Respect field-key asymmetry
|
|
75
92
|
|
|
@@ -150,3 +167,18 @@ The live schema must exist through a publish before copying. Treat copy-to-live
|
|
|
150
167
|
`workflowData("custom.customer")` requires an active collection on the executing rail. Read `controllerai-expressions` for filtering. Test stateful behavior against development, then independently verify required live seed data.
|
|
151
168
|
|
|
152
169
|
Read [references/data-rails.md](references/data-rails.md) before schema rewrites, deletion, bulk deletion, import, or copy-to-live.
|
|
170
|
+
|
|
171
|
+
## Before you finish
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
cai schema get custom.<type> --json
|
|
175
|
+
cai data collections --env <dev|live> --json
|
|
176
|
+
cai data query --type custom.<type> --env <dev|live> --json
|
|
177
|
+
cai workflow outline --json
|
|
178
|
+
cai workflow validate --json
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
- Schema read-back must show the intended stable key, display, complete field list, and field types.
|
|
182
|
+
- The same-rail query must prove the expected persisted records; node output alone is insufficient.
|
|
183
|
+
- Compare schema and workflow inventory with the request. Remove only schemas created during abandoned attempts, never pre-existing user objects.
|
|
184
|
+
- For a live request, verify the live collection after publish or copy and report the affected record count.
|
|
@@ -9,20 +9,25 @@ The `cai ...` examples are canonical grammar. Use the transport selected in `con
|
|
|
9
9
|
|
|
10
10
|
Controller AI uses a restricted JavaScript-like authoring language that is compiled into the workflow engine's expression AST. It is not general JavaScript. Discover the real scope and compile before every write.
|
|
11
11
|
|
|
12
|
+
## Rules that break tasks when skipped
|
|
13
|
+
|
|
14
|
+
- Wire first; refresh context after structural changes. See Required write loop.
|
|
15
|
+
- `--value` is static; dynamic values use `--js`. See Typed values and templates.
|
|
16
|
+
- Copy current bindings, not labels or stale names. See Read the destination's exact scope.
|
|
17
|
+
- Never use raw ASTs, general JavaScript, or private APIs. See Use only advertised syntax.
|
|
18
|
+
- Keep ternaries in the destination type. See Typed values and templates.
|
|
19
|
+
- Prove resolution in a containing-flow run. See Verify runtime resolution.
|
|
20
|
+
|
|
12
21
|
Data types are the TypeScript types of workflows: every expression is typed, and an expression can only be written against a known type. Integration types describe what integration actions output (resolved by a successful dev run); `custom.*` types describe shapes for data-store records and AI-node structured outputs. Two wrappers matter: `dataRecord.custom.x` means "an actual stored record of that shape" — the fields plus record identity (`id`, `createdAt`) — while bare `custom.x` is just data in that shape; `list.` prefixes a list of any type. When an expression cannot be authored, the cause is usually a missing type — resolve the upstream node's output type first, don't guess field paths.
|
|
13
22
|
|
|
14
|
-
`.every()` accepts the same multi-condition predicates as `.filter()` and compiles to
|
|
15
|
-
single-pass `:all match` op. A constraint whose field no longer exists in the schema makes
|
|
16
|
-
`.every()` false; `.filter()` drops that constraint. Per-row missing, null, and unparseable values
|
|
17
|
-
follow the comparator's own semantics, and negative comparisons can pass missing or null rows.
|
|
18
|
-
For guard conditions, require field presence explicitly.
|
|
23
|
+
`.every()` accepts the same multi-condition predicates as `.filter()` and compiles to `:all match`. A removed schema field makes `.every()` false but is dropped by `.filter()`. Comparator semantics govern missing, null, and unparseable values; guards must require presence explicitly.
|
|
19
24
|
|
|
20
25
|
## How empty values evaluate
|
|
21
26
|
|
|
22
|
-
An empty input
|
|
27
|
+
An empty input evaluates to its typed empty form: `""` for TEXT, null/undefined for objects. Both cases below fail silently:
|
|
23
28
|
|
|
24
|
-
- **A filter
|
|
25
|
-
- **An optional TEXT input
|
|
29
|
+
- **A filter compared with empty matches no records.** To skip an omitted constraint, opt into `ignoreEmptyConstraints`; see [references/dsl-and-recipes.md](references/dsl-and-recipes.md).
|
|
30
|
+
- **An omitted optional TEXT input arrives as `""`.** Equality then matches nothing and can make a create-if-missing step duplicate data.
|
|
26
31
|
|
|
27
32
|
When a run unexpectedly returns zero results, check the resolved configs for empty comparison values first (`cai exec node <nodeExecutionId> --full`).
|
|
28
33
|
|
|
@@ -66,10 +71,16 @@ cai expr ops --workflow <workflowId> --type <text|number|date|list|boolean|file|
|
|
|
66
71
|
|
|
67
72
|
The catalog is workflow-scoped because it is read from expression context. On CLI, an existing `cai use` or `CAI_WORKFLOW` selection can replace the explicit flag; MCP `expr_operation_list` always requires `workflowId`.
|
|
68
73
|
|
|
69
|
-
Keep the program to optional `const` declarations followed by one final expression. Do not use loops, assignments, `new`, regular expressions, optional chaining, arbitrary library methods, or object construction unless the
|
|
74
|
+
Keep the program to optional `const` declarations followed by one final expression. Do not submit raw AST JSON or probe private APIs. Do not use loops, assignments, `new`, regular expressions, optional chaining, arbitrary library methods, or object construction unless the compiler catalog supports them.
|
|
70
75
|
|
|
71
76
|
Date values are ISO-8601 strings on the wire (`"2026-06-05"` or `"2026-06-05T12:30:00Z"`). A `date`-typed flow input or field is used directly with date operations; only `text` values need `.toDate()` first. For epoch text, pass the unit literal explicitly: `flow.timestamp.toDate("epoch_seconds")` or `flow.timestamp.toDate("epoch_millis")`. The ops table renders that argument generically; never guess the unit from digit count.
|
|
72
77
|
|
|
78
|
+
#### Typed values and templates
|
|
79
|
+
|
|
80
|
+
`--value` stores a literal, so `flow.name`, `{inputs.Name}`, `{{name}}`, and `${name}` stay static. `--js` and `--js-file` compile typed expressions. `${...}` interpolates only inside a backtick template expression, which returns text. Keep non-text values as direct typed expressions.
|
|
81
|
+
|
|
82
|
+
Ternaries preserve compatible scalar branch types: number and boolean branches stay number and boolean. A different inferred destination type means the write is incomplete. Report the compiler error; do not quote or coerce branches to text.
|
|
83
|
+
|
|
73
84
|
Read [references/dsl-and-recipes.md](references/dsl-and-recipes.md) for canonical patterns.
|
|
74
85
|
|
|
75
86
|
### 4. Compile, write, and read back
|
|
@@ -109,6 +120,14 @@ cai expr validate <nodeId> --mode onlyWhen --js '<boolean expression>' --json
|
|
|
109
120
|
cai node only-when <nodeId> --js '<boolean expression>' --json
|
|
110
121
|
```
|
|
111
122
|
|
|
123
|
+
Node `only-when` takes one restricted boolean through `--js` or `--js-file`, or grouped conditions through `--groups-json '[["a","b"],["c"]]'` (inner arrays are AND, outer rows are OR). Root scalar `&&` or `||` inside one expression is unsupported; use `--groups-json` for compound guards. Branch add/update accepts `string[][]`: outer rows are OR, inner entries are AND, and each entry compiles separately.
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
cai branch update <nodeId> --route <routeId> --condition-json '[["flow.email.isNotEmpty()","flow.units < 5"],["flow.override === true"]]' --json
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Use grouping only when branch semantics fit. For a compound node-only guard, use `--groups-json`; do not weaken the rule or probe private APIs. To make missing numeric input fail `< 5`, use `(flow.unitsLeft ?? 5) < 5`.
|
|
130
|
+
|
|
112
131
|
Router and If/Else conditions are written with `cai branch add` or `cai branch update` after the same context/validation discipline.
|
|
113
132
|
|
|
114
133
|
### 5. Verify runtime resolution
|
|
@@ -139,3 +158,16 @@ Inspect `runScope` and `resolvedConfigs` in the run result. A null or missing dy
|
|
|
139
158
|
- Preserve `0` and `false`; they are values, not missing data.
|
|
140
159
|
- Optional filters need an explicit behavior decision. Do not silently change fail-closed filtering to match-all.
|
|
141
160
|
- A compiler success proves syntax and types, not correct source selection. Verify the source node/input IDs when names are ambiguous and inspect runtime values.
|
|
161
|
+
|
|
162
|
+
## Before you finish
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
cai expr context <nodeId> --prop <propName> --json
|
|
166
|
+
cai expr validate <nodeId> --prop <propName> --js '<expression>' --json
|
|
167
|
+
cai expr decompile <nodeId> --prop <propName> --json
|
|
168
|
+
cai run flow <flowId> --inputs <values.json> --json
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
- Context must show current source IDs and bindings.
|
|
172
|
+
- Validation and decompile must agree on source and type.
|
|
173
|
+
- The flow run must show the intended value in `runScope` and `resolvedConfigs`.
|
|
@@ -10,6 +10,16 @@ The live compiler and `cai expr context`/`cai expr ops` are authoritative. These
|
|
|
10
10
|
|
|
11
11
|
Use the exact bindings and fields returned for the destination node.
|
|
12
12
|
|
|
13
|
+
## Text prefix
|
|
14
|
+
|
|
15
|
+
When `.startsWith()` is unavailable, test a prefix with:
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
flow.subject.isNotEmpty() ? flow.subject.split("urgent")[0] === "" : false
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The typed conditional keeps empty text from matching. Replace the binding and prefix literal with exact values from context. Do not split on whitespace unless the requirement is first-word equality. Test punctuation immediately after the prefix, such as `urgent-alpha`, plus a near miss.
|
|
22
|
+
|
|
13
23
|
## HTTP JSON body
|
|
14
24
|
|
|
15
25
|
An HTTP node's JSON/body prop is stored as JSON text. The expression language does not support object literals or `JSON.stringify`. Build the document as a template and insert complete JSON tokens:
|
|
@@ -7,7 +7,18 @@ description: Design and configure Controller AI execution paths using only-when
|
|
|
7
7
|
|
|
8
8
|
The `cai ...` examples are canonical grammar. Use the transport selected in `controllerai-start` and its MCP tool map; never mix transports in one task.
|
|
9
9
|
|
|
10
|
-
Choose
|
|
10
|
+
Choose by intended runtime semantics.
|
|
11
|
+
|
|
12
|
+
## Rules that break tasks when skipped
|
|
13
|
+
|
|
14
|
+
- Guard the exact action with node `only-when`; see Conditions.
|
|
15
|
+
- Use If/Else for one path and Router for all matches; see Pick the correct primitive.
|
|
16
|
+
- Keep downstream wiring after node-only skip; see Conditions.
|
|
17
|
+
- Never invent a missing condition or Router order; see If/Else and Router.
|
|
18
|
+
- Build standard loops only with `flow for-each`; see Loops.
|
|
19
|
+
- Converge paths through Join Paths; see Subflows and convergence.
|
|
20
|
+
- Fail missing comparisons closed; keep money paths blocking. See Conditions, Loops, and the error pattern.
|
|
21
|
+
- Never acknowledge validation errors without explicit approval; see `controllerai-workflows` Lifecycle rules.
|
|
11
22
|
|
|
12
23
|
## Pick the correct primitive
|
|
13
24
|
|
|
@@ -39,6 +50,10 @@ cai node only-when <nodeId> --js '<boolean expression>' --json
|
|
|
39
50
|
|
|
40
51
|
Use `--clear` only when the user's intent is to remove the guard.
|
|
41
52
|
|
|
53
|
+
Guard the action that must skip. Do not substitute If/Else for a node-only skip. Its `only_when_false` does not block ordinary output; downstream remains eligible. Keep wiring. Guard downstream separately or branch if it must also skip.
|
|
54
|
+
|
|
55
|
+
Node `only-when` takes one restricted boolean through `--js` or `--js-file`, or grouped conditions through `--groups-json '[["a","b"],["c"]]'` (inner arrays are AND, outer rows are OR). Root scalar `&&` or `||` inside one expression is unsupported; use `--groups-json` for compound guards. Grouped `string[][]` conditions exist only on branch add/update, with outer rows OR and inner entries AND. Use them only when branch semantics fit. If an exact compound node-only guard is required, report it as unsupported; do not weaken the rule or probe private APIs. For a missing number that must fail `< 5`, use a same-type boundary such as `(flow.unitsLeft ?? 5) < 5`.
|
|
56
|
+
|
|
42
57
|
Choose the primitive by the runtime signal that callers and tests need. A false node `only-when` records `skipReason: "only_when_false"`; a node on an untaken If/Else branch or unmatched Router route records `skipReason: "upstream_skipped"`.
|
|
43
58
|
|
|
44
59
|
## If/Else and Router
|
|
@@ -51,6 +66,8 @@ cai branch list <controlNodeId> --json
|
|
|
51
66
|
|
|
52
67
|
A fresh If/Else already contains one blank `If` branch and an Else branch. The blank condition is a publish/runtime blocker: copy that default branch ID from `branch list` and update it before adding another condition branch. Do not leave the default blank and create a replacement beside it.
|
|
53
68
|
|
|
69
|
+
Never invent a missing condition from node names, current values, or one observed run. If no authoritative platform surface supplies the business rule, leave the branch blank, report the blocker, and ask the user.
|
|
70
|
+
|
|
54
71
|
```bash
|
|
55
72
|
cai branch update <ifElseNodeId> \
|
|
56
73
|
--route <defaultIfBranchId> \
|
|
@@ -73,7 +90,7 @@ For If/Else, order is behavior:
|
|
|
73
90
|
cai branch reorder <ifElseNodeId> --order <conditionId1,conditionId2> --json
|
|
74
91
|
```
|
|
75
92
|
|
|
76
|
-
List every condition branch exactly once and omit Else; the operation keeps Else last. Router routes cannot be reordered because every match runs.
|
|
93
|
+
List every condition branch exactly once and omit Else; the operation keeps Else last. Router routes cannot be reordered because every match runs. Do not simulate order by deleting a route or serializing its downstream nodes. Leave the fan-out unchanged unless the user explicitly authorizes a redesign.
|
|
77
94
|
|
|
78
95
|
After edits, run `cai branch list` again. Treat its warnings about missing conditions or unwired paths as blockers: an unwired selected path can end with an apparently green execution.
|
|
79
96
|
|
|
@@ -136,19 +153,26 @@ MCP: `cai node set-flow` maps to `node_subflow_set`.
|
|
|
136
153
|
|
|
137
154
|
Setting the target flow creates mapping props from the target's current inputs. If those inputs later change, run `set-flow` again and reconfigure mappings.
|
|
138
155
|
|
|
139
|
-
|
|
156
|
+
For one action after whichever exclusive branch runs, use `registry_flow-merge` (Join Paths) in `chooseActivePath` mode, then place the shared action after it. Never wire several branches directly to an ordinary node or duplicate the shared action. Follow the exact recipe in [references/patterns.md](references/patterns.md) and test each branch once.
|
|
140
157
|
|
|
141
|
-
Return semantics: a flow that should return data needs
|
|
158
|
+
Return semantics: a flow that should return data needs `registry_flow-return-response`; without it callers see empty success. Declare outputs first, initialize that exact component after the producer, then bind its generated prop with `cai expr set`. Do not guess component keys. See [references/patterns.md](references/patterns.md). Multiple completed Return nodes merge, with later values overwriting earlier ones; design for one Return per path unless merging is intended.
|
|
142
159
|
|
|
143
|
-
Deleting a flow
|
|
160
|
+
Deleting a flow removes its graph, contract, triggers, and caller links. Validate and restore if mistaken. Turning `continueOnError` off also deletes its Error output and edges; validate and restore likewise.
|
|
144
161
|
|
|
145
162
|
Read [references/patterns.md](references/patterns.md) for concise canonical patterns.
|
|
146
163
|
|
|
147
|
-
##
|
|
164
|
+
## Before you finish
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
cai branch list <controlNodeId> --json
|
|
168
|
+
cai workflow validate --json
|
|
169
|
+
cai run flow <flowId> --inputs <values.json> --json
|
|
170
|
+
cai exec tree <workflowExecutionId> --json
|
|
171
|
+
```
|
|
148
172
|
|
|
149
173
|
- Every branch has the intended condition and a wired target.
|
|
150
174
|
- If/Else ordering matches priority and Else remains last.
|
|
151
175
|
- Router fan-out is intentional when more than one condition can be true.
|
|
152
176
|
- Loop item type and list expression compile against actual source data.
|
|
153
177
|
- Expected true, false/Else, empty-list, one-item, several-item, over-limit, and error paths are tested.
|
|
154
|
-
-
|
|
178
|
+
- The execution tree shows intended `only_when_false` or `upstream_skipped` reasons and no unexplained missing nodes.
|
|
@@ -36,6 +36,19 @@ New order -> Router
|
|
|
36
36
|
|
|
37
37
|
One order may take all three routes. If that is not acceptable, use If/Else.
|
|
38
38
|
|
|
39
|
+
## Grouped branch condition
|
|
40
|
+
|
|
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
|
+
|
|
43
|
+
```bash
|
|
44
|
+
cai branch update <controlNodeId> \
|
|
45
|
+
--route <routeId> \
|
|
46
|
+
--condition-json '[["flow.email.isNotEmpty()","flow.units < 5"],["flow.override === true"]]' \
|
|
47
|
+
--json
|
|
48
|
+
```
|
|
49
|
+
|
|
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
|
+
|
|
39
52
|
## Per-item subflow
|
|
40
53
|
|
|
41
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.
|
|
@@ -46,6 +59,58 @@ The runtime limit defaults to 500 items and supports 1 through 10000. Use `--max
|
|
|
46
59
|
|
|
47
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.
|
|
48
61
|
|
|
62
|
+
## Converge exclusive paths
|
|
63
|
+
|
|
64
|
+
Initialize Join Paths and select its active-path mode:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
cai node init \
|
|
68
|
+
--flow <flowId> \
|
|
69
|
+
--node registry_flow \
|
|
70
|
+
--key registry_flow-merge \
|
|
71
|
+
--name "Join paths" \
|
|
72
|
+
--json
|
|
73
|
+
cai node set <joinNodeId> --prop mode --value chooseActivePath --json
|
|
74
|
+
cai node props <joinNodeId> --json
|
|
75
|
+
# If returned props require it:
|
|
76
|
+
cai node set <joinNodeId> --prop pathDataType --value <returnedTypeValue> --json
|
|
77
|
+
cai node connect --flow <flowId> --from <firstBranchTailNodeId> --to <joinNodeId> --json
|
|
78
|
+
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
|
|
86
|
+
```
|
|
87
|
+
|
|
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.
|
|
89
|
+
|
|
90
|
+
## Return a flow value
|
|
91
|
+
|
|
92
|
+
Declare the output before the Return response node so its generated prop exists:
|
|
93
|
+
|
|
94
|
+
```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
|
|
107
|
+
cai node props <returnNodeId> --json
|
|
108
|
+
cai expr context <returnNodeId> --prop <generatedOutputProp> --json
|
|
109
|
+
cai expr set <returnNodeId> --prop <generatedOutputProp> --js '<producer binding>' --json
|
|
110
|
+
```
|
|
111
|
+
|
|
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.
|
|
113
|
+
|
|
49
114
|
## Error path
|
|
50
115
|
|
|
51
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`:
|
|
@@ -9,6 +9,13 @@ The `cai ...` examples are canonical grammar. Use the transport selected in `con
|
|
|
9
9
|
|
|
10
10
|
Ready integration actions give a hosted agent one app operation without first building a workflow. Use the canonical operations below through the selected transport; do not call Controller AI REST routes directly.
|
|
11
11
|
|
|
12
|
+
## Rules that break tasks when skipped
|
|
13
|
+
|
|
14
|
+
- Attach one existing app operation directly; never wrap it in a one-node workflow. See Choose direct action or workflow.
|
|
15
|
+
- Capture the exact integration, action, connection, and tool IDs. See Discover exact identities.
|
|
16
|
+
- Decide confirmation before attachment, especially for writes or spend. See Decide approval before attaching.
|
|
17
|
+
- Verify persisted direct-action events, not a workflow execution ID. See Verify through the hosted agent.
|
|
18
|
+
|
|
12
19
|
## Choose direct action or workflow
|
|
13
20
|
|
|
14
21
|
Use a direct action for one existing operation such as sending one message, creating one record, or adding one row.
|
|
@@ -105,3 +112,14 @@ If confirmation is required, inspect the pending action and exact arguments befo
|
|
|
105
112
|
Publishing snapshots the action identity, description, and approval policy into the agent version. The connection and authorization grant remain live security bindings: rebinding can redirect the published action, while breakage, revocation, or detachment can stop it immediately without another agent publish. Detachment also removes the draft tool; publish afterward to remove the unavailable tool from the published list.
|
|
106
113
|
|
|
107
114
|
Organization members use the published agent, never its mutable draft. Direct actions run through the agent owner's connected external account. Controller AI usage draws from the organization's shared usage pool, regardless of which member calls the agent. State that credential boundary before organization sharing.
|
|
115
|
+
|
|
116
|
+
## Before you finish
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
cai agent tools <agentId> --json
|
|
120
|
+
cai agent conversation history <conversationId> --json
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
- Tools must show one intended direct attachment with the exact returned identities, connection, description, and confirmation policy.
|
|
124
|
+
- History must contain the persisted action event, exact arguments, and result; confirm any requested external effect separately.
|
|
125
|
+
- No wrapper workflow should exist unless the capability actually required orchestration or reuse.
|
|
@@ -67,7 +67,9 @@ MCP: use `node_add`; `cai node init` is the CLI alias of `cai node add`.
|
|
|
67
67
|
|
|
68
68
|
Omit flags that do not apply. `--after` creates the ordinary main/error wire at creation. Use it when later props need upstream data. Initialization also accepts `--props`, but use it only for static, non-option values already present in the inspected component metadata. Never inline a `remoteOptions` value, a reload selector and its dependents, or a prop that has not materialized yet; initialize first and set those sequentially. Use `--enable-prop <name...>` only for optional props confirmed by component metadata.
|
|
69
69
|
|
|
70
|
-
Capture the returned `nodeId`; do not rediscover it by name.
|
|
70
|
+
Capture the returned `nodeId`; do not rediscover it by name. Inspect the complete create result before projecting fields. A missing projection is not proof that creation failed, so read the captured ID or exact flow inventory before any retry.
|
|
71
|
+
|
|
72
|
+
When repairing an existing node, keep its node ID and configure it in place. Do not delete and recreate it merely to clear bad props, select a component, or refresh generated props; deletion discards wiring, conditions, and identity-based references. Replace it only when the requested component itself must change. Then capture the replacement ID, reconnect every dependent, and revalidate them.
|
|
71
73
|
|
|
72
74
|
### 4. Follow readiness, not guesses
|
|
73
75
|
|