@controllerai/cli 0.2.18 → 0.2.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/api/client.js +32 -0
  2. package/dist/api/client.js.map +1 -1
  3. package/dist/commands/agent.js +101 -8
  4. package/dist/commands/agent.js.map +1 -1
  5. package/dist/commands/expr.js +3 -10
  6. package/dist/commands/expr.js.map +1 -1
  7. package/dist/commands/integration.js +8 -2
  8. package/dist/commands/integration.js.map +1 -1
  9. package/dist/commands/workflow.js +7 -6
  10. package/dist/commands/workflow.js.map +1 -1
  11. package/dist/errors.js +5 -2
  12. package/dist/errors.js.map +1 -1
  13. package/package.json +1 -1
  14. package/skills/controllerai-agents/SKILL.md +25 -192
  15. package/skills/controllerai-agents/references/command-reference.md +205 -0
  16. package/skills/controllerai-ai-nodes/SKILL.md +11 -92
  17. package/skills/controllerai-ai-nodes/references/command-reference.md +98 -0
  18. package/skills/controllerai-data/SKILL.md +34 -159
  19. package/skills/controllerai-data/references/command-reference.md +177 -0
  20. package/skills/controllerai-expressions/SKILL.md +29 -151
  21. package/skills/controllerai-expressions/references/command-reference.md +166 -0
  22. package/skills/controllerai-expressions/references/dsl-and-recipes.md +1 -1
  23. package/skills/controllerai-flow-control/SKILL.md +21 -160
  24. package/skills/controllerai-flow-control/references/command-reference.md +171 -0
  25. package/skills/controllerai-integration-actions/SKILL.md +11 -112
  26. package/skills/controllerai-integration-actions/references/command-reference.md +118 -0
  27. package/skills/controllerai-node-config/SKILL.md +18 -178
  28. package/skills/controllerai-node-config/references/command-reference.md +186 -0
  29. package/skills/controllerai-runs/SKILL.md +25 -117
  30. package/skills/controllerai-runs/references/command-reference.md +113 -0
  31. package/skills/controllerai-start/SKILL.md +36 -67
  32. package/skills/controllerai-start/references/mcp-tool-map.md +6 -0
  33. package/skills/controllerai-triggers/SKILL.md +16 -210
  34. package/skills/controllerai-triggers/references/command-reference.md +215 -0
  35. package/skills/controllerai-workflows/SKILL.md +24 -104
  36. package/skills/controllerai-workflows/references/command-reference.md +118 -0
  37. package/skills/manifest.json +120 -60
@@ -0,0 +1,98 @@
1
+ # Controller AI AI nodes
2
+
3
+
4
+
5
+ - Use native Ask AI for a bounded AI step; see Configure Ask AI in dependency order.
6
+ - Keep an existing Ask AI node unless the requested component changes; see Configure Ask AI in dependency order.
7
+ - Set reload selectors before fetching live model options; see Configure Ask AI in dependency order.
8
+ - Never invent or hardcode a model ID; see Configure Ask AI in dependency order.
9
+
10
+ ## Choose the AI surface
11
+
12
+ - Use Ask AI for one bounded transformation with explicit inputs and output contract.
13
+ - Use a Controller AI agent for conversational judgment, multiple tools, durable instructions, and knowledge files. Load `controllerai-agents`.
14
+ - Use `registry_agent` workflow actions only when a workflow must create/message a platform agent conversation or manage its files; discover the exact action and props rather than treating the whole agent as one node.
15
+
16
+ ## Configure Ask AI in dependency order
17
+
18
+ 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.
19
+
20
+ Discover the current component instead of hardcoding a key:
21
+
22
+ ```bash
23
+ cai integration actions "ask AI" --json
24
+ cai integration action --integration <nodeSlug> --key <nodeKey> --json
25
+ cai node init --flow <flowId> --node <nodeSlug> --key <nodeKey> --name "<result produced>" --json
26
+ cai node status <nodeId> --json
27
+ ```
28
+
29
+ Typical selectors are provider, model, prompt/user message, and response data type. Trust current prop metadata over these names.
30
+
31
+ 1. Set the provider or other `reloadProps` selector.
32
+ 2. Re-read props.
33
+ 3. Fetch live model options; never invent a model identifier.
34
+ 4. Set the returned model value.
35
+ 5. Set the response type.
36
+ 6. Author the prompt with expression context.
37
+
38
+ ```bash
39
+ cai node set <nodeId> --prop <providerProp> --value '<provider>' --json
40
+ cai node props <nodeId> --json
41
+ cai node options <nodeId> --prop <modelProp> --query '<model family>' --json
42
+ cai node set <nodeId> --prop <modelProp> --value '<returned value>' --label '<returned label>' --json
43
+ cai expr set <nodeId> --prop <promptProp> --js '<prompt expression>' --json
44
+ ```
45
+
46
+ MCP: `node set` and `expr set` both use `node_prop_set`; pass exactly one of `value` or `js` here.
47
+
48
+ 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.
49
+
50
+ ## Prefer typed output for downstream logic
51
+
52
+ If downstream nodes need fields, create a small `custom.*` shape first:
53
+
54
+ ```bash
55
+ cai schema create custom.classification \
56
+ --field category:text:"Category" \
57
+ --field confidence:number:"Confidence" \
58
+ --field rationale:text:"Rationale" \
59
+ --json
60
+ cai node set <nodeId> --prop <responseTypeProp> --value 'custom.classification' --json
61
+ ```
62
+
63
+ Tell the model exactly what each field means, allowed values, and how to represent uncertainty. Never parse free-form prose downstream when a structured response contract is available.
64
+
65
+ ## Prompt contract
66
+
67
+ A useful workflow prompt contains only behavior-changing material:
68
+
69
+ - Task and audience
70
+ - Actual input data through exact bindings
71
+ - Decision rules and constraints
72
+ - Required output fields/format
73
+ - Explicit uncertainty or no-data behavior
74
+
75
+ Avoid filler personas, repeated instructions, and claims about data not present in expression context. For untrusted input, delimit it clearly and state that embedded instructions are data, not authority.
76
+
77
+ Read [selection-and-testing.md](selection-and-testing.md) for node choice and evaluation.
78
+
79
+ ## Before you finish
80
+
81
+ ```bash
82
+ cai node get <nodeId> --json
83
+ cai node status <nodeId> --json
84
+ cai node props <nodeId> --json
85
+ cai run node <nodeId> --inputs values.json --json
86
+ cai exec node <nodeExecutionId> --full --json
87
+ ```
88
+
89
+ MCP: pass parsed values in `inputs` and set `waitSeconds` on `run_node`.
90
+
91
+ 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.
92
+
93
+ - `node get` must identify the intended native Ask AI component.
94
+ - `node status` must report ready.
95
+ - Props must contain the exact returned provider/model value and label.
96
+ - The execution must show the resolved prompt and typed output fields.
97
+
98
+ 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.
@@ -1,184 +1,59 @@
1
1
  ---
2
2
  name: controllerai-data
3
- description: Define Controller AI custom data types and safely read, write, import, copy, and debug workflow records across development and live stores. Use when a workflow needs durable state, custom structured types, collections, record queries, CSV import, workflowData expressions, or data migration between dev and live.
3
+ description: Build durable application state in Controller AI workflow data stores: schemas, record identity, CRUD flows, search, partial updates, relationships, imports and development/live data. Use for CRM backends, trackers, data APIs and persistence.
4
4
  ---
5
5
 
6
- # Controller AI data
6
+ # Workflow data as an application backend
7
7
 
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.
8
+ Each workflow can own custom schemas and durable collections. Its flows share those collections, so an agent can use a set of flows as a CRM or application API. Creating a schema makes the collection available; creating a flow output only returns a value and does not store it.
9
9
 
10
- Controller AI has two separate concerns:
10
+ `custom.contact` describes a shape. `dataRecord.custom.contact` is a stored record of that shape, including `id`, `createdAt` and `updatedAt`. Lists of fetched records preserve identity. Use real fetched records as inputs to native Get/Update/Delete actions; an object that merely looks like a contact does not prove record identity.
11
11
 
12
- - A `custom.*` schema defines fields and makes a collection available.
13
- - Development and live record stores hold separate rows. Publishing graph/configuration does not copy records.
12
+ ## What is available
14
13
 
15
- ## Rules that break tasks when skipped
14
+ - Native Create, Get, Update, Update Multiple and Delete data-record actions.
15
+ - `workflowData("custom.contact")` expressions for query/filter/sort/count and selecting records.
16
+ - Typed record relationships, separate dev/live records, schema changes, CSV import validation/commit, and explicit copy-to-live.
17
+ - MCP data tools for inspecting, creating, querying, patching and deleting records during setup or debugging. Native workflow nodes provide the reusable capability an agent calls later.
16
18
 
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.
19
+ Discover the current `registry_data` action keys and schemas. Do not infer unsupported database guarantees: unique business keys, permissions, relationship deletion policy and concurrent business rules need deliberate application logic or verified platform support.
23
20
 
24
- ## A type vs a stored record
21
+ ## Recipe: CRM flows for an agent
25
22
 
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.
23
+ 1. Create schemas with stable field IDs: companies, contacts, deals and activities. Use fields for business data, record references for relationships where appropriate, and a clear status/archive policy.
24
+ 2. Define create/get/update/delete/search flow contracts per entity. Include useful descriptions and explicit results for invalid inputs, missing records and duplicate business keys. Do not make an empty record look like success.
25
+ 3. Create validates input and referenced records before writing. Get selects by record ID. Update fetches the record and patches only requested fields. Delete applies the chosen relationship policy. Search returns matching records and count/page information.
26
+ 4. Add useful composite reads such as “contact plus company, deals and activity history.” Prefer explicit projections over large unbounded record graphs.
27
+ 5. Test create → get → search → update → get → delete → missing get. Also test duplicate keys, invalid references, omitted filters, empty matches, zero/false values, list membership, clearing a field and page boundaries.
28
+ 6. Attach each tested flow as an agent tool when a conversational CRM agent is requested.
27
29
 
28
- `custom.customer` and `dataRecord.custom.customer` are different things, and the difference carries through everything:
30
+ ## Search expressions
29
31
 
30
- - `custom.customer` is the TYPE — the shape, like a TypeScript interface. A value of this type is just data in that shape.
31
- - `dataRecord.custom.customer` is an ACTUAL STORED RECORD of that shape — the same fields plus record identity (`id`, `createdAt`, `updatedAt`) proving it lives in the collection.
32
+ Use current `expr_context` bindings. Typical sources and operations:
32
33
 
33
- In the product UI this appears as the "data record" checkbox when selecting a type for a flow input or run-subflow-on-list: unchecked means "data shaped like a customer", checked means "a real stored customer record". Users find this checkbox confusing — be ready to explain it in these terms.
34
-
35
- Consequences:
36
-
37
- - Only real records satisfy a `dataRecord.*` input. An expression that CONSTRUCTS record-shaped objects (e.g. a `.map()` that builds new objects) produces `custom.*`-shaped values with no identity — pass fetched records through instead.
38
- - Record metadata (`id`, `createdAt`) exists only on `dataRecord.*` values; on plain `custom.*` values those paths resolve empty.
39
-
40
- ## Data nodes: update semantics
41
-
42
- - "Update multiple data records" takes an expression typed `list.dataRecord.custom.<type>` — real stored records passed through from a query or `workflowData(...)`. The batch is ONE transaction: if any resolved record is stale (deleted since fetched) or foreign to the collection, the node errors and NOTHING updates. A failed run therefore means zero changes — fix and rerun safely.
43
- - On the single-record "Update data" node, every ENABLED field is written. Enable only the fields you intend to change; disabled fields keep their stored values.
44
-
45
- ## Define a schema deliberately
46
-
47
- Inspect existing types first:
48
-
49
- ```bash
50
- cai schema list --json
51
- cai schema get custom.<name> --json
52
- ```
53
-
54
- Create a type with stable field IDs:
55
-
56
- ```bash
57
- cai schema create custom.customer \
58
- --display "Customer" \
59
- --field email:text:"Email" \
60
- --field score:number:"Score" \
61
- --json
62
- ```
63
-
64
- Creating the schema creates/synchronizes its collection. `cai data collection create` is only an alias for schema creation.
65
-
66
- MCP: `cai data collection create` is merged into `schema_type_create`.
67
-
68
- `schema update --field` replaces the complete field list; it does not append — this is deliberate, so a subset CAN intentionally replace the whole schema. Read the current schema, preserve intended fields, and disclose any removals. The transport response warns about every field being dropped and echoes back the COMPLETE stored shape after the write — always read that echo and confirm it matches intent. Deleting a schema hides its collection and records and breaks dependent expressions — tell the user, and know it is recoverable (see below).
69
-
70
- ## How schema changes affect stored records
71
-
72
- Schema changes never hard-delete data. There is no delete path for records in schema sync — everything is deactivation and hiding:
73
-
74
- - Delete a type → its collection goes INACTIVE on the next saved state; every record row survives. Re-add the same `custom.*` key and the same collection reactivates with all records intact.
75
- - Delete a field → its values stay inside each record, unaddressable. Re-add the SAME field id and the old values reappear.
76
- - Add a field → purely additive; existing records read the new field as null.
77
- - Change a field's type → values are never converted or erased; incompatible values read as null and return if the type is changed back. The schema hash changes, which blocks copy-to-live until dev and live match again.
78
- - Publish applies the same deactivate/reactivate logic to live. Live records are hidden, never deleted.
79
- - Restoring an older workflow state rolls back the DEFINITION (graph and schemas), never the data. The record store is not versioned and only moves forward: a deleted type reactivates with whatever the store holds NOW — records created, edited, or soft-deleted after the restored state stay exactly as they are. Restore is not a data snapshot.
80
-
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.
82
-
83
- Relabel a type without changing its stable key:
84
-
85
- ```bash
86
- cai schema update custom.customer --display "Customers" --json
34
+ ```javascript
35
+ workflowData("custom.contact").filter(r => r.id === flow.recordId).first()
36
+ workflowData("custom.contact").filter(r => r.owner === flow.owner, { ignoreEmptyConstraints: true })
37
+ workflowData("custom.contact").filter(r => r.tags.includes(flow.tag))
38
+ workflowData("custom.contact").skip(flow.offset).take(flow.limit)
87
39
  ```
88
40
 
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.
41
+ `ignoreEmptyConstraints` skips absent values, empty text and empty lists. `false` and `0` remain actual filter values. Without the option, an empty value remains a real comparison. List `.includes(value)` takes an **element**, not a list of elements. A text `.includes` is case-sensitive; for case-insensitive search, store normalized search text and normalize the query before comparing. Test the exact filter against representative rows.
90
42
 
91
- ## Respect field-key asymmetry
43
+ Use `.take(n)` for a count and `.skip(n)` for an offset. Legacy `.itemsFrom(n)`/`.itemsUntil(n)` use one-based boundaries: `.itemsUntil(51)` returns the first 50. Keep page counts and limits explicit; do not label a truncated list as a complete export.
92
44
 
93
- ```bash
94
- cai data fields --type custom.customer --json
95
- ```
96
-
97
- - `data create` and `data update` take field IDs.
98
- - Query/get results are keyed by field display names.
99
- - CSV columns are also matched by display names.
100
-
101
- Never feed a queried record directly into a write. Build a new fields object keyed by the `writeKey` mapping and omit reserved `id`, `createdAt`, and `updatedAt`.
45
+ ## Partial updates
102
46
 
103
- Fields may be typed `date`. Their values are strict ISO-8601 strings (`"2026-06-05"` or `"2026-06-05T12:30:00Z"`) on both write and read; anything else is rejected. Reserved `createdAt`/`updatedAt` are currently `text` — use `.toDate()` before date operations on them.
47
+ Single Update writes every enabled field by default. Enable only intended fields for a fixed patch. For a dynamic API patch, enable the optional `$fieldMask` prop, bind it to a `list.text` of schema field IDs, and enable/configure the candidate fields. Only selected fields are written in one update. An empty mask selects none; an unknown or disabled selected field is an error. Omit the mask to keep the existing enabled-field behavior.
104
48
 
105
- ## Work on the correct rail
106
-
107
- Development is the default:
108
-
109
- ```bash
110
- cai data collections --env dev --json
111
- cai data query --type custom.customer --env dev --json
112
- cai data create --type custom.customer --fields '{"email":"a@example.com","score":5}' --env dev --json
113
- ```
49
+ This supports list/date/scalar updates without one node per field. Selected `false`, `0`, empty text and empty lists are real updates; omitted mask entries preserve the stored values. Distinguish clearing from omission in the flow contract. Update Multiple applies a patch transactionally to the selected real records; verify its failure behavior for stale or foreign IDs.
114
50
 
115
- MCP: every data write requires explicit `env`; reads default to `dev` when `env` is omitted.
51
+ ## Keys, schemas and environments
116
52
 
117
- Use `--env live` only when the user intends to inspect or mutate production state. Before a live write, say that published/triggered runs read this store.
53
+ Inspect `data_field_list` for write keys. Data-tool writes use stable field IDs; query results can use display labels. Construct writes from the mapping instead of feeding a returned display-keyed object back into a write. Renaming a label is different from changing the stable schema/field key.
118
54
 
119
- `data query --query` is plain-text search over serialized records and metadata, not a field predicate. Perform exact field filtering inside workflow expressions or inspect results explicitly.
55
+ Schema field updates replace the full declared field list. Read the existing schema and include every field intended to remain. Removing a schema or field hides its stored data; it does not snapshot or erase the underlying records. Restoring workflow history restores the definition only.
120
56
 
121
- ## Delete records explicitly
122
-
123
- Record deletion is available only through the control surface; no workflow data node exposes it. Delete one known record or an explicit set of IDs:
124
-
125
- ```bash
126
- cai data delete <recordId> --type custom.customer --env dev --json
127
- cai data delete-many --type custom.customer --records <id1,id2> --env dev --json
128
- ```
129
-
130
- Delete every record in a collection only with explicit user intent; `--all` also requires `--yes`:
131
-
132
- ```bash
133
- cai data delete-many --type custom.customer --all --yes --env dev --json
134
- ```
135
-
136
- MCP: `data_record_delete_many` requires explicit `env: dev|live` and exactly one of `recordIds` or `all: true`; it has no `yes` parameter.
137
-
138
- The CLI default rail is development. Targeted and `--all --yes` bulk deletions also support `--env live`; a live deletion immediately changes the data read by the published workflow, so use it only with explicit production intent and query that rail afterward.
139
-
140
- ## Import and promote
141
-
142
- Validate CSV before writing:
143
-
144
- ```bash
145
- cai data import --type custom.customer --file customers.csv --mode dryRun --env dev --json
146
- cai data import --type custom.customer --file customers.csv --mode commit --env dev --json
147
- ```
148
-
149
- MCP: pass CSV text inline as `content` with explicit `env: dev|live` to `data_record_import_validate`, then use `data_record_import_commit` to write the validated content.
150
-
151
- Both CLI modes support `--env live`: dry-run remains non-writing, while a live commit appends rows immediately visible to the published workflow. Verify result counts and query the imported rows on the same rail.
152
-
153
- To seed live from development:
154
-
155
- ```bash
156
- cai workflow publish --json
157
- cai data copy-to-live --type custom.customer --json
158
- cai data collections --env live --json
159
- ```
160
-
161
- MCP: call `data_copy_to_live` with the explicit acknowledgement `env: live`.
162
-
163
- The live schema must exist through a publish before copying. Treat copy-to-live as a production mutation and report counts/results.
164
-
165
- ## Workflow expressions
166
-
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.
168
-
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
- ```
57
+ Development is the default test store; MCP writes require explicit `env`. Publishing the workflow does not copy records to Live. Use explicit validated copy/import operations when data promotion is actually intended. Verify state in the same environment as the flow execution.
180
58
 
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.
59
+ Detailed schema, import and data commands: [command reference](references/command-reference.md). Environment and reference semantics: [data rails](references/data-rails.md).
@@ -0,0 +1,177 @@
1
+ # Controller AI data
2
+
3
+
4
+ Controller AI has two separate concerns:
5
+
6
+ - A `custom.*` schema defines fields and makes a collection available.
7
+ - Development and live record stores hold separate rows. Publishing graph/configuration does not copy records.
8
+
9
+
10
+ - Persisting data requires both a schema and a create-record node; see A type vs a stored record.
11
+ - `schema update --field` replaces the full field list; see Define a schema deliberately.
12
+ - Relabel a stable key instead of recreating it; see How schema changes affect stored records.
13
+ - Query results and writes use different field keys; see Respect field-key asymmetry.
14
+ - Read and write the requested dev or live store; see Work on the correct rail.
15
+ - Remove only scratch schemas created for abandoned attempts; see Before you finish.
16
+
17
+ ## A type vs a stored record
18
+
19
+ 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.
20
+
21
+ `custom.customer` and `dataRecord.custom.customer` are different things, and the difference carries through everything:
22
+
23
+ - `custom.customer` is the TYPE — the shape, like a TypeScript interface. A value of this type is just data in that shape.
24
+ - `dataRecord.custom.customer` is an ACTUAL STORED RECORD of that shape — the same fields plus record identity (`id`, `createdAt`, `updatedAt`) proving it lives in the collection.
25
+
26
+ In the product UI this appears as the "data record" checkbox when selecting a type for a flow input or run-subflow-on-list: unchecked means "data shaped like a customer", checked means "a real stored customer record". Users find this checkbox confusing — be ready to explain it in these terms.
27
+
28
+ Consequences:
29
+
30
+ - Only real records satisfy a `dataRecord.*` input. An expression that CONSTRUCTS record-shaped objects (e.g. a `.map()` that builds new objects) produces `custom.*`-shaped values with no identity — pass fetched records through instead.
31
+ - Record metadata (`id`, `createdAt`) exists only on `dataRecord.*` values; on plain `custom.*` values those paths resolve empty.
32
+
33
+ ## Data nodes: update semantics
34
+
35
+ - "Update multiple data records" takes an expression typed `list.dataRecord.custom.<type>` — real stored records passed through from a query or `workflowData(...)`. The batch is ONE transaction: if any resolved record is stale (deleted since fetched) or foreign to the collection, the node errors and NOTHING updates. A failed run therefore means zero changes — fix and rerun safely.
36
+ - On the single-record "Update data" node, every ENABLED field is written. Enable only the fields you intend to change; disabled fields keep their stored values.
37
+
38
+ ## Define a schema deliberately
39
+
40
+ Inspect existing types first:
41
+
42
+ ```bash
43
+ cai schema list --json
44
+ cai schema get custom.<name> --json
45
+ ```
46
+
47
+ Create a type with stable field IDs:
48
+
49
+ ```bash
50
+ cai schema create custom.customer \
51
+ --display "Customer" \
52
+ --field email:text:"Email" \
53
+ --field score:number:"Score" \
54
+ --json
55
+ ```
56
+
57
+ Creating the schema creates/synchronizes its collection. `cai data collection create` is only an alias for schema creation.
58
+
59
+ MCP: `cai data collection create` is merged into `schema_type_create`.
60
+
61
+ `schema update --field` replaces the complete field list; it does not append — this is deliberate, so a subset CAN intentionally replace the whole schema. Read the current schema, preserve intended fields, and disclose any removals. The transport response warns about every field being dropped and echoes back the COMPLETE stored shape after the write — always read that echo and confirm it matches intent. Deleting a schema hides its collection and records and breaks dependent expressions — tell the user, and know it is recoverable (see below).
62
+
63
+ ## How schema changes affect stored records
64
+
65
+ Schema changes never hard-delete data. There is no delete path for records in schema sync — everything is deactivation and hiding:
66
+
67
+ - Delete a type → its collection goes INACTIVE on the next saved state; every record row survives. Re-add the same `custom.*` key and the same collection reactivates with all records intact.
68
+ - Delete a field → its values stay inside each record, unaddressable. Re-add the SAME field id and the old values reappear.
69
+ - Add a field → purely additive; existing records read the new field as null.
70
+ - Change a field's type → values are never converted or erased; incompatible values read as null and return if the type is changed back. The schema hash changes, which blocks copy-to-live until dev and live match again.
71
+ - Publish applies the same deactivate/reactivate logic to live. Live records are hidden, never deleted.
72
+ - Restoring an older workflow state rolls back the DEFINITION (graph and schemas), never the data. The record store is not versioned and only moves forward: a deleted type reactivates with whatever the store holds NOW — records created, edited, or soft-deleted after the restored state stay exactly as they are. Restore is not a data snapshot.
73
+
74
+ 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.
75
+
76
+ Relabel a type without changing its stable key:
77
+
78
+ ```bash
79
+ cai schema update custom.customer --display "Customers" --json
80
+ ```
81
+
82
+ 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.
83
+
84
+ ## Respect field-key asymmetry
85
+
86
+ ```bash
87
+ cai data fields --type custom.customer --json
88
+ ```
89
+
90
+ - `data create` and `data update` take field IDs.
91
+ - Query/get results are keyed by field display names.
92
+ - CSV columns are also matched by display names.
93
+
94
+ Never feed a queried record directly into a write. Build a new fields object keyed by the `writeKey` mapping and omit reserved `id`, `createdAt`, and `updatedAt`.
95
+
96
+ Fields may be typed `date`. Their values are strict ISO-8601 strings (`"2026-06-05"` or `"2026-06-05T12:30:00Z"`) on both write and read; anything else is rejected. Reserved `createdAt`/`updatedAt` are currently `text` — use `.toDate()` before date operations on them.
97
+
98
+ ## Work on the correct rail
99
+
100
+ Development is the default:
101
+
102
+ ```bash
103
+ cai data collections --env dev --json
104
+ cai data query --type custom.customer --env dev --json
105
+ cai data create --type custom.customer --fields '{"email":"a@example.com","score":5}' --env dev --json
106
+ ```
107
+
108
+ MCP: every data write requires explicit `env`; reads default to `dev` when `env` is omitted.
109
+
110
+ Use `--env live` only when the user intends to inspect or mutate production state. Before a live write, say that published/triggered runs read this store.
111
+
112
+ `data query --query` is plain-text search over serialized records and metadata, not a field predicate. Perform exact field filtering inside workflow expressions or inspect results explicitly.
113
+
114
+ ## Delete records explicitly
115
+
116
+ Native workflow Delete Record actions provide reusable deletion inside a flow. Discover their exact keys under registry_data. The data tools below also delete one known record or an explicit set of IDs during setup and debugging:
117
+
118
+ ```bash
119
+ cai data delete <recordId> --type custom.customer --env dev --json
120
+ cai data delete-many --type custom.customer --records <id1,id2> --env dev --json
121
+ ```
122
+
123
+ Delete every record in a collection only with explicit user intent; `--all` also requires `--yes`:
124
+
125
+ ```bash
126
+ cai data delete-many --type custom.customer --all --yes --env dev --json
127
+ ```
128
+
129
+ MCP: `data_record_delete_many` requires explicit `env: dev|live` and exactly one of `recordIds` or `all: true`; it has no `yes` parameter.
130
+
131
+ The CLI default rail is development. Targeted and `--all --yes` bulk deletions also support `--env live`; a live deletion immediately changes the data read by the published workflow, so use it only with explicit production intent and query that rail afterward.
132
+
133
+ ## Import and promote
134
+
135
+ Validate CSV before writing:
136
+
137
+ ```bash
138
+ cai data import --type custom.customer --file customers.csv --mode dryRun --env dev --json
139
+ cai data import --type custom.customer --file customers.csv --mode commit --env dev --json
140
+ ```
141
+
142
+ MCP: pass CSV text inline as `content` with explicit `env: dev|live` to `data_record_import_validate`, then use `data_record_import_commit` to write the validated content.
143
+
144
+ Both CLI modes support `--env live`: dry-run remains non-writing, while a live commit appends rows immediately visible to the published workflow. Verify result counts and query the imported rows on the same rail.
145
+
146
+ To seed live from development:
147
+
148
+ ```bash
149
+ cai workflow publish --json
150
+ cai data copy-to-live --type custom.customer --json
151
+ cai data collections --env live --json
152
+ ```
153
+
154
+ MCP: call `data_copy_to_live` with the explicit acknowledgement `env: live`.
155
+
156
+ The live schema must exist through a publish before copying. Treat copy-to-live as a production mutation and report counts/results.
157
+
158
+ ## Workflow expressions
159
+
160
+ `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.
161
+
162
+ Read [data-rails.md](data-rails.md) before schema rewrites, deletion, bulk deletion, import, or copy-to-live.
163
+
164
+ ## Before you finish
165
+
166
+ ```bash
167
+ cai schema get custom.<type> --json
168
+ cai data collections --env <dev|live> --json
169
+ cai data query --type custom.<type> --env <dev|live> --json
170
+ cai workflow outline --json
171
+ cai workflow validate --json
172
+ ```
173
+
174
+ - Schema read-back must show the intended stable key, display, complete field list, and field types.
175
+ - The same-rail query must prove the expected persisted records; node output alone is insufficient.
176
+ - Compare schema and workflow inventory with the request. Remove only schemas created during abandoned attempts, never pre-existing user objects.
177
+ - For a live request, verify the live collection after publish or copy and report the affected record count.