@controllerai/cli 0.2.16 → 0.2.18

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.
@@ -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 the
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 is not "nothing" — it evaluates to the type's empty form: empty text `""` for TEXT, null/undefined for objects. Two platform semantics follow, and both fail silently rather than erroring:
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 comparing against an empty value still filters — and matches NO records.** This is deliberate (fail-closed; matching everything by accident would be worse). If "empty should mean skip this filter", opt that constraint into ignore-empty mode explicitly (`ignoreEmptyConstraints`, see [references/dsl-and-recipes.md](references/dsl-and-recipes.md)).
25
- - **An optional TEXT input left empty arrives as `""`**, so an `equals` constraint against it matches nothing — the classic symptom is "search finds zero results only when the optional filter is blank", which then cascades (e.g. a create-if-missing step makes duplicates).
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 current compiler catalog explicitly supports them.
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 the primitive from the intended execution semantics before adding nodes.
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
- When paths converge, inspect the concrete graph and execution behavior. Do not assume wiring two paths to one action creates a barrier or waits for both. If the requirement is a true join, discover a join-capable component and verify it with representative branch timing.
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 a Return response node — without one it returns nothing, and callers (including agents invoking it as a tool) see silent success with empty output. If several Return response nodes complete in one execution, their result objects merge in execution order and a later node silently overwrites an earlier value for the same field. Design for exactly one Return per path unless merging is the intent.
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 is a normal, undoable edit: it removes the flow's nodes, edges, inputs/outputs, and attached triggers, and unlinks run-subflow callers in other flows — the same way deleting a function breaks its call sites. No confirmation ceremony is needed; run `cai workflow validate` afterwards to see every break, fix or remove the callers it lists, and `cai workflow restore` if the deletion was a mistake. Turning `continueOnError` off is the same class: it deletes the node's Error output and every edge from it — validate after, restore if regretted.
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
- ## Completion gate
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
- - `cai exec tree <workflowExecutionId>` contains no unexplained skipped or missing nodes.
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
 
@@ -64,6 +64,12 @@ Prefer a ready integration action when one existing operation completes the capa
64
64
 
65
65
  If the user's outcome does not reveal whether they want an agent or an automation that runs on an event, ask one short clarifying question. Otherwise proceed with the smallest suitable surface.
66
66
 
67
+ ## Lock targets before mutation
68
+
69
+ Before the first write, resolve every existing target against the effective workflow. Prefer an explicit ID or an object already connected to the pinned workflow. When a list is account-wide, filter it by exact name or ID; never take the first, latest, or similarly named result. Read back the chosen object's ID, name, and owning workflow or flow. Keep those IDs for the task and never rediscover the target by name.
70
+
71
+ Check `ok` and the complete `data` object from every mutation before projecting fields. A missing projected field is not proof that the mutation failed. Read back the returned ID or list the exact target before retrying. Never repeat a create until readback proves the first call created nothing.
72
+
67
73
  ## Platform docs
68
74
 
69
75
  The platform reference lives at https://docs.getcontroller.ai. Fetch https://docs.getcontroller.ai/llms.txt for the machine-readable index of every page; append `.md` to any page path for raw markdown (for example https://docs.getcontroller.ai/start/safety.md), or fetch /llms-full.txt for the whole corpus at once. Skills remain the procedural authority — consult the docs for what they do not carry: the execution and cost models, type-system and version-rail contracts, the full CLI and expression-operation references, and the workflow API. When a docs page and a skill disagree, prefer the skill and its live platform queries (`cai expr ops`, `cai integration actions`).
@@ -76,6 +82,8 @@ The platform reference lives at https://docs.getcontroller.ai. Fetch https://doc
76
82
  - Coding-agent subscriptions cover builder reasoning, not Controller AI hosted-agent runtime usage.
77
83
  - Provider actions can send messages, mutate or delete records, and incur provider charges. Decide the per-action confirmation policy before attaching one.
78
84
  - Never publish, share with the organization, replay a live trigger, or perform a consequential external test without clear user intent.
85
+ - A missing business rule is a blocker, not permission to infer one from names or sample data. Ask the user and leave the affected state unchanged.
86
+ - Stop when the requested state and its required evidence are reached. Do not add objects, publish, or keep probing beyond that boundary.
79
87
  - When a platform limitation blocks the outcome, STOP and report the partial result with the blocking error; never keep probing until the clock expires.
80
88
 
81
89
  At completion, report what was created, what is still draft or live, the evidence actually observed, and any connection or user decision still required.
@@ -9,6 +9,15 @@ The `cai ...` examples are canonical grammar. Use the transport selected in `con
9
9
 
10
10
  A trigger targets exactly one flow. Its source may capture events before publish, but ordinary event execution uses the live workflow. Creating the source is therefore separate from mapping, development testing, publishing, and live replay.
11
11
 
12
+ ## Rules that break tasks when skipped
13
+
14
+ - Preserve trigger ID and source during repair; see Create and map.
15
+ - Delete a retired start; disable a reversible pause. See Remove or disable an automatic start.
16
+ - Lifecycle uses `--enable|--disable`; test events use `--allow|--disallow`. See Remove or disable an automatic start and Finish trigger setup.
17
+ - Disarm capture when setup ends; see Finish trigger setup.
18
+ - Lifecycle edits are development-only; publish only for Live. See Verify development, then publish.
19
+ - Replay with `--target dev`; Live also requires `--confirm-live`. See Verify development, then publish.
20
+
12
21
  ## Require a real source decision
13
22
 
14
23
  Determine how the trigger choice was established:
@@ -91,9 +100,7 @@ or
91
100
  {"cron":{"intervalSeconds":900}}
92
101
  ```
93
102
 
94
- Do not discard timezone by passing a bare cron string. The timezone is the user's to
95
- choose, not yours to assume: when they say "at 9am" without one, ask — a schedule in the
96
- wrong timezone runs at the wrong time every single day, and nothing ever errors.
103
+ Do not pass a bare cron string or assume a timezone. Ask when the user says "at 9am" without one.
97
104
 
98
105
  ## Create and map
99
106
 
@@ -109,7 +116,9 @@ cai trigger create \
109
116
  --json
110
117
  ```
111
118
 
112
- Capture all returned IDs. `triggerId` is the canvas ID used for get/rename/set-enabled/delete. `backendTriggerId` is numeric and used for captured events and replay. They are not interchangeable.
119
+ Capture all returned IDs. `triggerId` is the canvas ID used for get/rename/set-enabled/delete/test-events. `backendTriggerId` is numeric and used for captured events and replay. They are not interchangeable.
120
+
121
+ When repairing an existing trigger, preserve its canvas ID and source. Repair its mapping in place; do not delete and recreate it merely to clear readiness. Replacement changes captured IDs and may change a webhook URL. Replace only when the requested source itself must change, then recapture every returned ID.
113
122
 
114
123
  Read a single trigger back with `cai trigger get <triggerId> --json` — the full record: node, event, connection, props, lifecycle. Read-back is always `get`; the CLI has no `describe` read command anywhere (`cai workflow set-description` is a setter).
115
124
 
@@ -117,11 +126,11 @@ Follow returned `nextSteps` in order. Trigger payload does not automatically pop
117
126
 
118
127
  ## Remove or disable an automatic start
119
128
 
120
- `cai trigger delete <triggerId>` removes the automatic start from the development canvas; use it when the start should no longer exist. `cai trigger set-enabled <triggerId> --enable|--disable` changes the enabled state reversibly and idempotently; pass exactly one state flag.
129
+ `cai trigger delete <triggerId>` removes the automatic start from the development canvas; use it when the start should no longer exist. Never delete its owning flow merely to remove the trigger. `cai trigger set-enabled <triggerId> --enable|--disable` changes the enabled state reversibly and idempotently; pass exactly one state flag.
121
130
 
122
131
  MCP: call `trigger_set_enabled` with `enabled: true` or `enabled: false`.
123
132
 
124
- Both are development edits. Publish after either so production follows; until then, the previous live trigger can still start the published workflow.
133
+ Both are development edits. Publish only for an authorized Live change; otherwise the prior Live trigger remains active.
125
134
 
126
135
  ## Establish a typed sample
127
136
 
@@ -145,9 +154,13 @@ cai trigger test-events --id <triggerId> --disallow
145
154
 
146
155
  MCP: call `trigger_test_events_set` with `allowed: false`, or `allowed: true` to arm it.
147
156
 
148
- The armed state is sticky: nothing disarms it for you, and dev dispatch and live dispatch are independent gates. A trigger that is armed while the workflow is also live runs ONE real event TWICE — both executions real, both billed. The starkest case is a schedule trigger: armed + enabled + published, the cron fires BOTH dev and live at every interval, forever, with no external event involved — until someone edits and republishes.
157
+ Do not mix the flag families. `set-enabled` takes `--enable` or `--disable`; `test-events` takes `--allow` or `--disallow`; replay takes `--target dev` or `--target live`, with `--confirm-live` for Live.
158
+
159
+ The acknowledgements are distinct: `--acknowledge-dual-run` applies only while arming, `--acknowledge-dual-dispatch` only while publishing an armed trigger, and `--confirm-live` only to one Live replay.
149
160
 
150
- Keeping a trigger armed alongside live on purpose (side-by-side comparison on real events) is legitimate but uncommon; it is a user decision, never a default.
161
+ Arming is sticky and independent from enablement. Armed plus live dispatch runs each real event twice; a schedule does so every interval. Disabling does not clear the allow flag, so always disallow after capture.
162
+
163
+ Keeping both rails armed is an uncommon user decision, never a default.
151
164
 
152
165
  ## Verify development, then publish
153
166
 
@@ -160,6 +173,8 @@ cai exec tree <workflowExecutionId> --json
160
173
 
161
174
  A development replay never dispatches live, but nodes still call real providers and can create side effects or spend usage. Publish only after mappings and development behavior pass.
162
175
 
176
+ A draft-only request stops after development proof. Do not publish merely because the trigger is ready.
177
+
163
178
  Before publishing, audit EVERY trigger in the workflow — not just the one you worked on. Users forget triggers left enabled or armed in other flows. Read each row's lifecycle fields from `cai trigger list --json` (`devCanvasEnabled`, `deployment.liveCanvasEnabled`, `deployment.inLiveVersion`, `deployment.devDispatchArmed`, `deployment.dualDispatchLive`) and confirm with the user which triggers should be starting this workflow live:
164
179
 
165
180
  ```bash
@@ -190,3 +205,18 @@ cai exec tree <workflowExecutionId> --json
190
205
  It executes the published workflow, calls live third-party actions, and spends usage. Obtain appropriate user intent before replaying a side-effectful event.
191
206
 
192
207
  Read [references/lifecycle.md](references/lifecycle.md) for trigger state and debugging.
208
+
209
+ ## Before you finish
210
+
211
+ ```bash
212
+ cai trigger get <triggerId> --json
213
+ cai trigger list --json
214
+ cai workflow status --json
215
+ # Only after an authorized create or mapping test:
216
+ cai exec tree <workflowExecutionId> --json
217
+ ```
218
+
219
+ - Read-back must retain the intended trigger, source, target flow, connection, props, and mapping.
220
+ - `devCanvasEnabled`, `liveCanvasEnabled`, and `inLiveVersion` must match the requested rail state.
221
+ - `devDispatchArmed` must be false unless the user explicitly chose dual dispatch; report `dualDispatchLive`.
222
+ - For authorized create/mapping work, the development test must produce the expected tree. Never replay for delete/disable-only work. Live replay needs separate authorization.
@@ -23,7 +23,7 @@ Skipping capture/sample selection commonly leaves downstream output untyped. Ski
23
23
 
24
24
  | ID | Used by |
25
25
  | --- | --- |
26
- | Canvas `triggerId` | `trigger get`, `rename`, `set-enabled`, `delete` |
26
+ | Canvas `triggerId` | `trigger get`, `rename`, `set-enabled`, `delete`, `test-events` |
27
27
  | Numeric `backendTriggerId` | `trigger events`, `replay` |
28
28
  | `triggerNodeId` | `trigger select-event` |
29
29
  | Captured `eventId` | `trigger event`, `select-event`, `replay --event` |
@@ -44,6 +44,21 @@ Use `--target live --confirm-live` only for an intentional post-publish live ver
44
44
 
45
45
  MCP: call `trigger_set_enabled` with `enabled: true` or `enabled: false`.
46
46
 
47
+ ## Lifecycle flag families
48
+
49
+ ```bash
50
+ cai trigger set-enabled <triggerId> --enable|--disable --json
51
+ cai trigger test-events <triggerId> --allow [--acknowledge-dual-run] --json
52
+ cai trigger test-events <triggerId> --disallow --json
53
+ cai trigger replay --trigger <backendTriggerId> --target dev --json
54
+ cai trigger replay --trigger <backendTriggerId> --target live --confirm-live --json
55
+ cai workflow publish --acknowledge-dual-dispatch --json
56
+ ```
57
+
58
+ Use enable/disable for the trigger definition, allow/disallow for ambient development capture, and target for replay rail. `--acknowledge-dual-run` applies only while arming. `--acknowledge-dual-dispatch` applies only when publishing an armed trigger. `--confirm-live` applies only to one Live replay. These acknowledgements are not interchangeable.
59
+
60
+ The allow flag is sticky even when the trigger is disabled. Always disallow after capture or an abandoned setup so later re-enablement cannot silently rearm development dispatch.
61
+
47
62
  ## Debugging a missing run
48
63
 
49
64
  1. `cai trigger list --json`: enabled, correct flow, recoverable backend ID.
@@ -7,6 +7,15 @@ description: Build, inspect, edit, test, recover, and publish Controller AI work
7
7
 
8
8
  The `cai ...` examples are canonical grammar. Follow the transport rules and MCP map in `controllerai-start`; never mix transports within one task. Do not call private REST endpoints or invoke MCP routes outside the typed tools. When the selected transport lacks a capability, say so and name the UI path instead of bypassing it.
9
9
 
10
+ ## Rules that break tasks when skipped
11
+
12
+ - Lock the effective workflow ID and exact flow ID; see Establish context.
13
+ - Repair the selected workflow and flow instead of creating replacements; see Build protocol.
14
+ - Keep workflow, flow, node, input, and output IDs distinct; see Lifecycle rules.
15
+ - Preserve contract fields in place and refresh their dependents; see Lifecycle rules.
16
+ - Use save, history, and restore for recovery instead of rebuilding; see Lifecycle rules.
17
+ - Treat validation counts as status; prove the requested rail and stop. See Lifecycle rules and Before you finish.
18
+
10
19
  ## Establish context
11
20
 
12
21
  ```bash
@@ -24,11 +33,11 @@ If no workflow exists, create it with its final name:
24
33
  cai workflow create --name "<clear outcome-oriented name>" --json
25
34
  ```
26
35
 
27
- On the CLI, read `selected`, `effectiveWorkflowId`, and `pinSource` from the result; creation does not guarantee that the new workflow won the effective pin. If `selected` is false, rerun without `--workflow` when `pinSource` is `flag`; otherwise run `cai use <id>` for a stored pin or clear an inherited `CAI_WORKFLOW` pin in the parent environment, then re-check context. Use `cai config reset` to clear stale stored pins; it cannot clear an environment pin. `cai doctor` verifies that the effective workflow pin and its development head are readable. On MCP, capture the created `workflowId` and pass it explicitly thereafter. Do not create and then rename: `workflow create --name` submits the name directly.
36
+ Read `selected`, `effectiveWorkflowId`, and `pinSource`; creation may not win the pin. If unselected, remove a command flag, use `cai use <id>` for a stored pin, or clear inherited `CAI_WORKFLOW`, as `pinSource` directs, then re-check. `cai config reset` clears only stored pins. `cai doctor` verifies the effective pin and development head. On MCP, capture and pass the new `workflowId`. Create with the final name.
28
37
 
29
- A new workflow already ships with one empty flow. Rename that flow and build in it — do not add a second flow for the first piece of work; only add flows for genuinely separate canvases. A stray empty "Flow 1" left behind is clutter the user has to clean up.
38
+ After `cai workflow create`, check `flowCount` in the output. If it is 0 (or the field is missing, run `cai flow list --json`), create the first flow with `cai flow create --name "<name>" --json`. If one empty flow already exists, rename it and build in it. Never add a second flow for the first piece of work; add flows only for genuinely separate canvases.
30
39
 
31
- Use IDs returned by the selected transport. Never invent workflow, flow, node, prop, event, field, route, or connection identifiers.
40
+ Use IDs returned by the selected transport. For account-wide lists, filter by exact ID or name and owning workflow; never choose the first or latest similar result. Read the selected object's ID, name, and owner once, reuse those IDs, and never rediscover the target by name. A missing projected create field is not failure: inspect the complete result and exact inventory before retrying. Never invent identifiers.
32
41
 
33
42
  To orient inside a workflow, prefer one call over many:
34
43
 
@@ -36,16 +45,12 @@ To orient inside a workflow, prefer one call over many:
36
45
  cai workflow outline --json
37
46
  ```
38
47
 
39
- `cai workflow state --json` is the bounded state projection; add `--flow <flowId>` for that
40
- flow's node and edge identities. Use `cai workflow document --json` only when the complete opaque
41
- workflow JSON is actually needed.
42
-
43
- It returns the whole map compactly — every flow with typed inputs/outputs, its nodes with upstream `deps` (the graph shape), triggers with source/enabled/armed state, and data types with per-rail record counts. Drill into a specific flow with `cai flow get <flowId>` only when you need props and expressions.
48
+ `cai workflow state --json` is bounded; add `--flow <flowId>` for node and edge identities. Use `workflow document` only for complete opaque JSON. `workflow outline` shows flows, contracts, graph `deps`, triggers, and data inventory. Use `flow get` for props and expressions.
44
49
 
45
50
  ## Build protocol
46
51
 
47
52
  1. Translate the request into inputs, steps, outputs, trigger, integrations, failure behavior, and publish intent. When the request says to store, save, log, or record something and names no external destination, that means a data type plus a create-record node (`controllerai-data`) — a flow output alone does not persist anything. When the user names the destination (a spreadsheet, a CRM, another app, a file), write there with that integration's node instead; add a platform record only when the user also wants a copy kept here.
48
- 2. Inspect the current workflow before editing: `cai workflow outline`, then `cai flow get <flowId>` for the flows you will touch.
53
+ 2. Inspect the current workflow before editing: `cai workflow outline`, then `cai flow get <flowId>`. Repair those IDs; do not create replacements for a repair.
49
54
  3. Create or select a flow. Define flow inputs and outputs before nodes that derive props from them.
50
55
  4. Build in execution order. After each material change, read it back with the corresponding `get`, `props`, `status`, or `list` command.
51
56
  5. Run `cai flow layout <flowId>` once per changed flow as the LAST step after a headless build or any batch of structural edits (adds, deletes, rewires) — add-time placement avoids overlap, but only layout produces clean left-to-right columns. An open builder adopts your layout automatically.
@@ -73,23 +78,32 @@ Load the narrow skill when the task reaches that surface:
73
78
  - 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.
74
79
  - On the CLI, prefer `--json` for decisions and IDs; human tables are for presentation. MCP tools return typed JSON. If human output looks odd, re-run the same read with `--json` before acting.
75
80
  - A workflow contains flows; a flow is one canvas. Do not confuse their IDs.
76
- - For flow inputs, pass a human `--display` label and use the compiler-current binding from the latest `cai flow get` (`jsBinding`) or `cai workflow outline` (`binding`). An input rename can change it: re-read bindings after every rename and never cache their spellings across renames. `legacyBinding` is diagnostic-only; never use it in expressions.
81
+ - For flow inputs, pass a human `--display` label and use the current binding from `flow get` (`jsBinding`) or `workflow outline` (`binding`). Rename an existing contract input or output in place; do not delete and recreate it. After any rename or deletion, re-read the flow and repair every expression and subflow mapping before validation or execution. `legacyBinding` is diagnostic-only.
77
82
  - Name nodes after the data they produce. Downstream input bindings are seeded from that name when wired.
78
83
  - Use `cai workflow save --name "<checkpoint>"` before a risky rewrite. Named saves and state renames work on every plan.
79
- - **Edit the graph with a plan-aware safety net.** Each save is a version state. `cai workflow history` has no age cutoff on Free, Max, or custom plans and a 90-day window on Pro/Starter; expand a group with `cai workflow history --group <groupId>` to get its state IDs. Restore with `cai workflow restore --state <stateId>` within 7 days on Free, 90 days on Pro/Starter, or without an age limit on Max/custom. Duplicate, delete, and refactor with that window in mind. Restore rolls back the DEFINITION (graph and schemas) only — record data is unversioned and stays as it is. External side effects (sent messages, provider calls) are equally outside the undo.
80
- - **Validate after structural edits, don't ask permission before them.** After deleting a flow, deleting a type, or a large refactor — and before every publish — run `cai workflow validate` and fix what it lists. It is deep: required props, connections, wiring, expressions, output types, subflows, branches, trigger issues, grouped per red node. Publish refuses when validation reports errors; `--acknowledge-validation-errors` may only be used after the user explicitly approved publishing with the listed issues. Deleting a flow deletes its nodes, edges, inputs/outputs, and attached triggers, and breaks callers in other flows — exactly like deleting a function breaks its call sites; validate shows every break.
84
+
85
+ ```bash
86
+ cai workflow history --json
87
+ cai workflow history --group <groupId> --json
88
+ cai workflow restore --state <stateId> --json
89
+ ```
90
+
91
+ - **Use version history for recovery.** Each save is a state. History retention is unlimited on Free, Max, and custom plans and 90 days on Pro/Starter; restore eligibility is 7 days on Free, 90 on Pro/Starter, and unlimited on Max/custom. Expand groups to capture state IDs. Restore rolls back the definition only; records and external effects remain.
92
+ - **Validate after structural edits and before publish.** Fix every listed prop, wire, expression, type, subflow, branch, and trigger error. Publish refuses errors; use `--acknowledge-validation-errors` only after explicit approval of the listed issues. Flow deletion also removes its contract, graph, and triggers and breaks callers; validation shows each break.
93
+ - Treat validation warning and error counts as status only. Never write them or their messages into unrelated workflow properties.
81
94
  - On a conflict, re-read current state and reapply intent. Do not replay stale IDs or whole documents.
82
95
  - Never delete a workflow without the user's explicit intent; the CLI requires `--yes` but that is not a substitute for authorization. MCP: `workflow_delete` has no `yes` parameter, but still requires the same explicit intent. `cai workflow delete` tears down deployed triggers and soft-deletes the workflow row. Historical workflow/version data remains stored, but there is currently no restore command for the deleted row.
83
- - **Know the dependency graph before deleting or refactoring shared resources.** `cai deps` shows which agents attach this workflow as a tool (LIVE or draft), which workflows call it, and which connections it uses; `cai deps --account` maps the whole account. Deleting a workflow with agent dependents is refused server-side, and `--force` is a deprecated, inert compatibility flag. For a draft attachment, run `cai agent remove-tool <agentId> --tool <toolId>`. If it is also LIVE, publish that detached draft with `cai agent publish <agentId>`; if it is LIVE-only, publish the already-detached current draft or delete the agent. Publishing or deleting a dependent agent is its own live/destructive decision — get the user's explicit approval for that step first. Then retry the workflow delete.
84
- - **Check the account feed when recent activity matters.** `cai events` lists what happened in the account, newest first: connections created, workflow edits/publishes/runs, agents created/published, members added. Filter with `--type` (comma-separated), `--since 30m|2h|7d|<ISO>`, `--for-workflow <id>`; page with `--cursor`. Typical uses: `cai events --type connection.created --since 10m` to see whether the user finished connecting, `cai events --since 24h` to catch up at session start, `cai events --for-workflow <id> --type workflow.run` for recent run activity. The feed cannot show connection deletions, settings changes, member removals, or label activity — absence there proves nothing. When the user asks you to reconstruct a past change the platform does not record (e.g. which workflow a label was removed from), say it is not recorded and change nothing; names or current state that merely suggest an answer are not evidence, and acting on a guess is worse than reporting the limitation.
85
- - **Share the platform, not just text.** At the right moments, hand the user the real product page — command outputs include `appUrl` when the app origin is known: after `workflow publish` (the live builder view), after development `run flow`/`run node` (the run page; live flow runs omit it), and after `agent publish`/`test` (agent and conversation pages). On the CLI, `connection connect` prints the link; share it with `--no-open` before optionally adding `--wait`. MCP: `connection_connect_url` returns the link immediately and has no `open` or `wait` parameter. When the user asks for a link at any other time, don't guess URLs: `cai workflow get`, `cai workflow status`, and `cai agent get` include `appUrl`. When you cannot perform something (settings, product-only surfaces), give the link instead of apologizing.
96
+ - **Check dependencies before shared-resource changes.** `cai deps` shows attached agents, workflow callers, and connections; `cai deps --account` maps the account. Deleting a workflow with agent dependents is refused server-side, and `--force` is a deprecated, inert compatibility flag. Detach draft tools with `cai agent remove-tool <agentId> --tool <toolId>`. If still LIVE, use `cai agent publish <agentId>` after explicit approval. A dependent agent publish or delete is its own live/destructive decision. Then retry deletion.
97
+ - **Use the account feed only for recorded history.** Filter `cai events` by type, time, workflow, and cursor. It omits deletions, settings, member removals, and labels. For an unrecorded past change, say it is not recorded and change nothing; names and current state are not proof.
98
+ - **Share real product links.** Relevant publish, development run, agent, connection, workflow get/status, and agent get outputs include `appUrl` when known. Share returned links; never guess one. MCP `connection_connect_url` returns its link immediately.
86
99
  - Never silently weaken the request to make a test pass. Report an unsupported capability or ask about a real product decision.
87
100
 
88
- ## Completion gate
101
+ ## Before you finish
89
102
 
90
103
  Before claiming success, verify all of the following:
91
104
 
92
105
  ```bash
106
+ cai workflow outline --json
93
107
  cai workflow validate --json
94
108
  cai workflow status --json
95
109
  cai flow get <flowId> --json
@@ -106,5 +120,7 @@ Then confirm:
106
120
  - Expected branch or error paths were exercised where practical.
107
121
  - Flow outputs contain the promised values.
108
122
  - The reported live/dev state matches what the user asked for.
123
+ - Final outline inventory matches the request. Remove only scratch schemas, flows, nodes, and triggers created during abandoned attempts.
124
+ - After fixing requested blockers and testing each changed path once, stop. Do not redesign for non-blocking warnings.
109
125
 
110
126
  State what was built, what was tested, whether it was published, and any user-owned connection or product choice still pending.