@alvera-ai/platform-sdk 0.10.0-rc.21 → 0.10.0-rc.23
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/.agent/AGENTS.md +3 -3
- package/.agent/ai_agents.md +64 -29
- package/.agent/cookbook/contact-us-triage-with-llm.md +14 -15
- package/.agent/cookbook/sanctions-screening-with-agent-review.md +13 -13
- package/.agent/cookbook/score-leads-with-llm-categorization.md +14 -14
- package/.agent/interoperability_contracts.md +66 -1
- package/.agent/workflows.md +49 -34
- package/dist/index.d.mts +196 -103
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +5 -56
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/.agent/AGENTS.md
CHANGED
|
@@ -36,9 +36,9 @@ The one-line summaries in the Resources section are
|
|
|
36
36
|
> The index line `workflows.md — Filter + decision + action;
|
|
37
37
|
> standard + agent-driven variants` **cannot** tell you whether
|
|
38
38
|
> `variant` is a body field. It is not. `workflows.md` §1 shows
|
|
39
|
-
> the variant is **structural** —
|
|
40
|
-
>
|
|
41
|
-
> discriminator.
|
|
39
|
+
> the variant is **structural** — an agent-driven workflow simply
|
|
40
|
+
> has one or more AI agents *nested* on its body (the
|
|
41
|
+
> `workflow_ai_agents` array) — **not** a top-level `variant` discriminator.
|
|
42
42
|
|
|
43
43
|
An agent that paraphrases the index into a wire-level claim is
|
|
44
44
|
**hallucinating**. The index summary is too compressed to encode
|
package/.agent/ai_agents.md
CHANGED
|
@@ -184,40 +184,75 @@ distinct names. A naming convention like `"<purpose>-<env>"` or
|
|
|
184
184
|
including the datalake slug in the agent name keeps the
|
|
185
185
|
namespace clean.
|
|
186
186
|
|
|
187
|
-
## 3. Workflow
|
|
187
|
+
## 3. Workflow & contract nesting
|
|
188
188
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
`workflows
|
|
189
|
+
An agent runs as an **enrichment** step on a workflow or an
|
|
190
|
+
interoperability contract. The agent is **nested directly on the
|
|
191
|
+
parent body** — `agentic-workflows` and `interoperability-contracts`
|
|
192
|
+
create/PUT bodies accept a `workflow_ai_agents` /
|
|
193
|
+
`interoperability_contract_ai_agents` array (a `cast_assoc`). There is
|
|
194
|
+
no separate attach/detach endpoint, and **no per-join checksum**: the
|
|
195
|
+
nested agents fold into the parent's own checksum.
|
|
192
196
|
|
|
193
197
|
```typescript
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
//
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
198
|
+
// Nest on create. `position` is REQUIRED.
|
|
199
|
+
const { data: workflow } = await api.workflows.create(
|
|
200
|
+
tenantSlug, datalakeSlug,
|
|
201
|
+
{
|
|
202
|
+
name: 'Contact-Us Triage',
|
|
203
|
+
dataset_type: 'generic_table',
|
|
204
|
+
// …other workflow fields…
|
|
205
|
+
workflow_ai_agents: [
|
|
206
|
+
{
|
|
207
|
+
ai_agent_id: createdAgentId,
|
|
208
|
+
position: 0, // ordering when multiple agents fire; REQUIRED
|
|
209
|
+
context_mapping_config: {
|
|
210
|
+
type: 'custom',
|
|
211
|
+
// Liquid renders to a JSON object whose keys match the agent's
|
|
212
|
+
// input_schema. Values reference the inbound row via `event_dataset.<field>`.
|
|
213
|
+
body: JSON.stringify({
|
|
214
|
+
msg: '{{ event_dataset.message }}',
|
|
215
|
+
submission_id: '{{ event_dataset.submission_id }}',
|
|
216
|
+
}),
|
|
217
|
+
// Workflow joins OMIT output_schema — the server pins it from
|
|
218
|
+
// the agent's input_schema. Interop joins author it (below).
|
|
219
|
+
},
|
|
210
220
|
},
|
|
211
|
-
|
|
212
|
-
],
|
|
213
|
-
decision_config: {
|
|
214
|
-
type: 'custom',
|
|
215
|
-
// The agent's parsed output is exposed at
|
|
216
|
-
// `additional_context.<agent_slug>.<field>`. Property paths
|
|
217
|
-
// match the agent's llm_response_schema.
|
|
218
|
-
body: '["{{ additional_context.contact-us-triage-categorizer.category }}"]',
|
|
219
|
-
output_schema: '{"type":"array","items":{"type":"string"}}',
|
|
221
|
+
],
|
|
220
222
|
},
|
|
223
|
+
)
|
|
224
|
+
workflow.checksum // the PARENT checksum — the nested agents fold into it
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Interoperability contracts nest the same way, via the
|
|
228
|
+
`interoperability_contract_ai_agents` array on the contract create/PUT
|
|
229
|
+
body. The one difference: a contract join's `context_mapping_config`
|
|
230
|
+
**does** carry `output_schema` (the server does not pin it there).
|
|
231
|
+
|
|
232
|
+
Adding, repositioning, and removing agents are all expressed as a
|
|
233
|
+
**full-set PUT** on the parent (`on_replace: :delete`):
|
|
234
|
+
|
|
235
|
+
| Operation | How | Result |
|
|
236
|
+
|------------|------------------------------------------------------|--------|
|
|
237
|
+
| add / nest | include in `*_ai_agents` on create or PUT | parent checksum reflects the agents |
|
|
238
|
+
| reposition | PUT with the same agent at a new `position` | full-set replace |
|
|
239
|
+
| detach | PUT omitting that agent (or `[]` to clear all) | `on_replace: :delete` removes it |
|
|
240
|
+
| drift | parent `checksum()` endpoint over the full body | the would-be parent checksum (no persist) |
|
|
241
|
+
|
|
242
|
+
The parent surfaces its nested agents on GET: a workflow GET returns
|
|
243
|
+
`workflow_ai_agents: [...]` and a contract GET returns
|
|
244
|
+
`interoperability_contract_ai_agents: [...]`. Both arrays are writable
|
|
245
|
+
on the parent body (create + PUT).
|
|
246
|
+
|
|
247
|
+
The workflow's own `decision_config` reads the agent's parsed output
|
|
248
|
+
at `additional_context.<agent_slug>.<field>` (property paths match the
|
|
249
|
+
agent's `llm_response_schema`):
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
decision_config: {
|
|
253
|
+
type: 'custom',
|
|
254
|
+
body: '["{{ additional_context.contact-us-triage-categorizer.category }}"]',
|
|
255
|
+
output_schema: '{"type":"array","items":{"type":"string"}}',
|
|
221
256
|
}
|
|
222
257
|
```
|
|
223
258
|
|
|
@@ -256,10 +256,11 @@ ctx.agentSlug = agentResp.data.slug!
|
|
|
256
256
|
|
|
257
257
|
The workflow has the standard shape — filter, decision, actions —
|
|
258
258
|
but two things distinguish it from a static workflow. First, the
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
259
|
+
Triage agent is **nested** in the workflow create body (the
|
|
260
|
+
`workflow_ai_agents` array — a `cast_assoc`, not a separate attach
|
|
261
|
+
call), binding it into the workflow's enrichment phase with a Liquid
|
|
262
|
+
`context_mapping_config` that projects each submission row's fields
|
|
263
|
+
into the agent's input schema. Second, the `decision_config` body is a Liquid template
|
|
263
264
|
that interpolates the agent's `category` output (read from
|
|
264
265
|
`additional_context["<agent-slug>"].category`) into a
|
|
265
266
|
single-element decision array. The three `actions` are keyed
|
|
@@ -299,17 +300,6 @@ const workflowResp = await api.workflows.create(tenantSlug, datalakeSlug, {
|
|
|
299
300
|
body: DECISION_CONFIG_BODY,
|
|
300
301
|
output_schema: DECISION_OUTPUT_SCHEMA,
|
|
301
302
|
},
|
|
302
|
-
ai_agents: [
|
|
303
|
-
{
|
|
304
|
-
ai_agent_id: aiAgentId,
|
|
305
|
-
position: 0,
|
|
306
|
-
context_mapping_config: {
|
|
307
|
-
type: 'custom',
|
|
308
|
-
body: CONTEXT_MAPPING_BODY,
|
|
309
|
-
output_schema: '{"type":"object"}',
|
|
310
|
-
},
|
|
311
|
-
},
|
|
312
|
-
],
|
|
313
303
|
actions: BUCKETS.map((bucket) => ({
|
|
314
304
|
decision_key: bucket,
|
|
315
305
|
action_type: 'sms',
|
|
@@ -327,6 +317,15 @@ const workflowResp = await api.workflows.create(tenantSlug, datalakeSlug, {
|
|
|
327
317
|
sms_type: 'transactional',
|
|
328
318
|
},
|
|
329
319
|
})),
|
|
320
|
+
// Nest the triage agent inline. Workflow joins omit output_schema —
|
|
321
|
+
// the server pins it from the agent's input_schema.
|
|
322
|
+
workflow_ai_agents: [
|
|
323
|
+
{
|
|
324
|
+
ai_agent_id: aiAgentId,
|
|
325
|
+
position: 0,
|
|
326
|
+
context_mapping_config: { type: 'custom', body: CONTEXT_MAPPING_BODY },
|
|
327
|
+
},
|
|
328
|
+
],
|
|
330
329
|
})
|
|
331
330
|
workflowId = workflowResp.data.id!
|
|
332
331
|
ctx.workflowSlug = workflowResp.data.slug!
|
|
@@ -201,8 +201,10 @@ ctx.agentSlug = agentResp.data.slug!
|
|
|
201
201
|
## 004 — create the Sanctions Review workflow
|
|
202
202
|
|
|
203
203
|
The workflow has the standard shape — filter, decision, actions —
|
|
204
|
-
plus
|
|
205
|
-
|
|
204
|
+
plus the Sanctions Review agent **nested** in the create body (the
|
|
205
|
+
`workflow_ai_agents` array — a `cast_assoc`, not a separate attach
|
|
206
|
+
call) to bind it into the enrichment phase. Three pieces are worth
|
|
207
|
+
noting.
|
|
206
208
|
|
|
207
209
|
The **filter** is the gray-zone band: it passes only screenings
|
|
208
210
|
whose `screening_score` is at least `0.80` and below `0.95`. A
|
|
@@ -256,17 +258,6 @@ const workflowResp = await api.workflows.create(tenantSlug, datalakeSlug, {
|
|
|
256
258
|
body: DECISION_CONFIG_BODY,
|
|
257
259
|
output_schema: DECISION_OUTPUT_SCHEMA,
|
|
258
260
|
},
|
|
259
|
-
ai_agents: [
|
|
260
|
-
{
|
|
261
|
-
ai_agent_id: aiAgentId,
|
|
262
|
-
position: 0,
|
|
263
|
-
context_mapping_config: {
|
|
264
|
-
type: 'custom',
|
|
265
|
-
body: CONTEXT_MAPPING_BODY,
|
|
266
|
-
output_schema: '{"type":"object"}',
|
|
267
|
-
},
|
|
268
|
-
},
|
|
269
|
-
],
|
|
270
261
|
actions: VERDICTS.map((verdict) => ({
|
|
271
262
|
decision_key: verdict,
|
|
272
263
|
action_type: 'sms',
|
|
@@ -284,6 +275,15 @@ const workflowResp = await api.workflows.create(tenantSlug, datalakeSlug, {
|
|
|
284
275
|
sms_type: 'transactional',
|
|
285
276
|
},
|
|
286
277
|
})),
|
|
278
|
+
// Nest the sanctions-review agent inline. Workflow joins omit
|
|
279
|
+
// output_schema — the server pins it from the agent's input_schema.
|
|
280
|
+
workflow_ai_agents: [
|
|
281
|
+
{
|
|
282
|
+
ai_agent_id: aiAgentId,
|
|
283
|
+
position: 0,
|
|
284
|
+
context_mapping_config: { type: 'custom', body: CONTEXT_MAPPING_BODY },
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
287
|
})
|
|
288
288
|
workflowId = workflowResp.data.id!
|
|
289
289
|
ctx.workflowSlug = workflowResp.data.slug!
|
|
@@ -264,9 +264,11 @@ ctx.agentSlug = agentResp.data.slug!
|
|
|
264
264
|
|
|
265
265
|
The workflow has the standard shape — filter, decision, actions — but
|
|
266
266
|
two things distinguish it from the birthday cookbook's static
|
|
267
|
-
workflow. First, the
|
|
268
|
-
the
|
|
269
|
-
|
|
267
|
+
workflow. First, the Score Lead agent is **nested** in the workflow
|
|
268
|
+
create body (the `workflow_ai_agents` array — a `cast_assoc`, not a
|
|
269
|
+
separate attach call), binding it into the workflow's enrichment phase
|
|
270
|
+
with a Liquid `context_mapping_config` that projects each lead row's
|
|
271
|
+
fields into the agent's input schema.
|
|
270
272
|
Second, the `decision_config` body is a Liquid template that
|
|
271
273
|
interpolates the agent's `band` output (read from
|
|
272
274
|
`additional_context["<agent-slug>"].band`) into a single-element
|
|
@@ -307,17 +309,6 @@ const workflowResp = await api.workflows.create(tenantSlug, datalakeSlug, {
|
|
|
307
309
|
body: DECISION_CONFIG_BODY,
|
|
308
310
|
output_schema: DECISION_OUTPUT_SCHEMA,
|
|
309
311
|
},
|
|
310
|
-
ai_agents: [
|
|
311
|
-
{
|
|
312
|
-
ai_agent_id: aiAgentId,
|
|
313
|
-
position: 0,
|
|
314
|
-
context_mapping_config: {
|
|
315
|
-
type: 'custom',
|
|
316
|
-
body: CONTEXT_MAPPING_BODY,
|
|
317
|
-
output_schema: '{"type":"object"}',
|
|
318
|
-
},
|
|
319
|
-
},
|
|
320
|
-
],
|
|
321
312
|
actions: BANDS.map((band) => ({
|
|
322
313
|
decision_key: band,
|
|
323
314
|
action_type: 'sms',
|
|
@@ -335,6 +326,15 @@ const workflowResp = await api.workflows.create(tenantSlug, datalakeSlug, {
|
|
|
335
326
|
sms_type: 'transactional',
|
|
336
327
|
},
|
|
337
328
|
})),
|
|
329
|
+
// Nest the scoring agent inline. Workflow joins omit output_schema —
|
|
330
|
+
// the server pins it from the agent's input_schema.
|
|
331
|
+
workflow_ai_agents: [
|
|
332
|
+
{
|
|
333
|
+
ai_agent_id: aiAgentId,
|
|
334
|
+
position: 0,
|
|
335
|
+
context_mapping_config: { type: 'custom', body: CONTEXT_MAPPING_BODY },
|
|
336
|
+
},
|
|
337
|
+
],
|
|
338
338
|
})
|
|
339
339
|
workflowId = workflowResp.data.id!
|
|
340
340
|
ctx.workflowSlug = workflowResp.data.slug!
|
|
@@ -290,7 +290,72 @@ const { data: list } = await api.interoperabilityContracts.list(
|
|
|
290
290
|
const editable = list.data.filter((c) => !c.system_created)
|
|
291
291
|
```
|
|
292
292
|
|
|
293
|
-
## 6.
|
|
293
|
+
## 6. Nesting AI agents on the contract body
|
|
294
|
+
|
|
295
|
+
A contract can run AI-agent **enrichment** during ingestion. Agents
|
|
296
|
+
are **nested directly on the contract body** — the create/PUT body
|
|
297
|
+
accepts an `interoperability_contract_ai_agents` array (a `cast_assoc`).
|
|
298
|
+
There is no separate attach/detach endpoint, and **no per-join
|
|
299
|
+
checksum**: the nested agents fold into the contract's own checksum.
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
// Nest on create. `position` is REQUIRED.
|
|
303
|
+
const { data: contract } = await api.interoperabilityContracts.create(
|
|
304
|
+
tenantSlug, datalakeSlug,
|
|
305
|
+
{
|
|
306
|
+
name: 'MOA Enrichment Contract',
|
|
307
|
+
resource_type: 'legal_entity',
|
|
308
|
+
template_config: { type: 'custom', body: '{{ name }}' },
|
|
309
|
+
mdm_input_config: { type: 'null' },
|
|
310
|
+
// …other contract fields…
|
|
311
|
+
interoperability_contract_ai_agents: [
|
|
312
|
+
{
|
|
313
|
+
ai_agent_id: createdAgentId,
|
|
314
|
+
position: 0, // ordering when multiple agents fire; REQUIRED
|
|
315
|
+
context_mapping_config: {
|
|
316
|
+
type: 'custom',
|
|
317
|
+
// Liquid renders to a JSON object whose keys match the agent's
|
|
318
|
+
// input_schema; values reference the inbound row via `msg.<field>`.
|
|
319
|
+
body: JSON.stringify({ name: '{{ msg.company_name }}' }),
|
|
320
|
+
// A contract join AUTHORS output_schema — unlike a workflow
|
|
321
|
+
// join, the server does NOT pin it here.
|
|
322
|
+
output_schema: '{"type":"object","properties":{"lead":{"type":"string"}}}',
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
],
|
|
326
|
+
},
|
|
327
|
+
)
|
|
328
|
+
contract.checksum // the PARENT checksum — the nested agents fold into it
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
Adding, repositioning, and removing agents are all expressed as a
|
|
332
|
+
**full-set PUT** on the contract (`on_replace: :delete`):
|
|
333
|
+
|
|
334
|
+
| Operation | How | Result |
|
|
335
|
+
|------------|--------------------------------------------------|--------|
|
|
336
|
+
| add / nest | include in the array on create or PUT | contract checksum reflects the agents |
|
|
337
|
+
| reposition | PUT with the same agent at a new `position` | full-set replace |
|
|
338
|
+
| detach | PUT omitting that agent (or `[]` to clear all) | `on_replace: :delete` removes it |
|
|
339
|
+
| drift | contract `checksum()` endpoint over the full body| the would-be contract checksum (no persist) |
|
|
340
|
+
|
|
341
|
+
A contract GET surfaces its nested agents as
|
|
342
|
+
`interoperability_contract_ai_agents: [...]`. The array is writable on
|
|
343
|
+
the contract body (create + PUT).
|
|
344
|
+
|
|
345
|
+
**Runtime.** Enrichment fires on the ingest path (`execute_contract`),
|
|
346
|
+
**not** on the sandbox `.run`. Attached agents run in `position`
|
|
347
|
+
order: each agent's `context_mapping_config` renders against the row,
|
|
348
|
+
the result is validated against the agent's `input_schema`, sent to
|
|
349
|
+
the bound `llm_enrichment` tool, and the parsed response is merged
|
|
350
|
+
under `msg.enrichment.<agent_slug>` for the contract's
|
|
351
|
+
`template_config` to read. So attaching an agent to a DAC-wired
|
|
352
|
+
contract makes ingestion run enrichment per row — keep agents off
|
|
353
|
+
contracts whose only job is a straight-through upsert.
|
|
354
|
+
|
|
355
|
+
See `ai_agents.md` §3 for the agent create body and the shared attach
|
|
356
|
+
surface, and `workflows.md` for the workflow side of the same join.
|
|
357
|
+
|
|
358
|
+
## 7. Gotchas
|
|
294
359
|
|
|
295
360
|
1. **`filter_template` is empty-passes, non-empty-skips.**
|
|
296
361
|
Already documented in §2 — repeated here because it's the
|
package/.agent/workflows.md
CHANGED
|
@@ -92,9 +92,9 @@ const { data: created } = await api.workflows.create(
|
|
|
92
92
|
)
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
-
`actions`, `context_datasets`, and
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
`actions`, `context_datasets`, and `workflow_ai_agents` are all
|
|
96
|
+
inline arrays — one POST builds the entire workflow body, agents
|
|
97
|
+
included (see §3).
|
|
98
98
|
|
|
99
99
|
## 2. Rules the type cannot encode
|
|
100
100
|
|
|
@@ -115,9 +115,9 @@ to validate the rendered output before invoking the next stage.
|
|
|
115
115
|
|
|
116
116
|
### Replace-on-PUT semantics for inline arrays
|
|
117
117
|
|
|
118
|
-
`actions
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
`actions` and `context_datasets` use **replace semantics on PUT**.
|
|
119
|
+
Submitting an update body that omits either array silently
|
|
120
|
+
disassociates ALL of its
|
|
121
121
|
entries. Always re-supply the full array on every update, even
|
|
122
122
|
when you're only changing `description`.
|
|
123
123
|
|
|
@@ -185,29 +185,43 @@ When `decision_config.type` references an AI agent (instead of
|
|
|
185
185
|
action(s)
|
|
186
186
|
```
|
|
187
187
|
|
|
188
|
-
|
|
189
|
-
body
|
|
190
|
-
|
|
191
|
-
|
|
188
|
+
Agents are **nested directly on the workflow body** — the create and
|
|
189
|
+
PUT body accept a `workflow_ai_agents` array (a `cast_assoc`). There is
|
|
190
|
+
no separate attach/detach endpoint. The agents **fold into the
|
|
191
|
+
workflow's own checksum** — there is no per-join checksum. `position` is
|
|
192
|
+
required, and workflow joins **omit `output_schema`** (the server pins
|
|
193
|
+
it from the agent's `input_schema`). The agent create body (prompt
|
|
194
|
+
config, `input_schema` / `llm_response_schema`, runtime failure codes)
|
|
195
|
+
lives in `ai_agents.md` §3. A workflow GET surfaces the nested agents as
|
|
196
|
+
`workflow_ai_agents: [...]`.
|
|
197
|
+
|
|
198
|
+
PUT is a **full-set replace** (`on_replace: :delete`): the body must
|
|
199
|
+
re-supply every agent you want to keep — agents absent from the array
|
|
200
|
+
are detached, and re-supplied ones are repositioned.
|
|
192
201
|
|
|
193
202
|
```typescript
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
203
|
+
await api.workflows.create(tenantSlug, datalakeSlug, {
|
|
204
|
+
name: 'Contact-Us Triage',
|
|
205
|
+
dataset_type: 'generic_table',
|
|
206
|
+
// …other workflow fields…
|
|
207
|
+
workflow_ai_agents: [
|
|
208
|
+
{
|
|
209
|
+
ai_agent_id: aiAgentId, // UUID from api.aiAgents.create
|
|
210
|
+
position: 0, // ordering when multiple agents fire; REQUIRED
|
|
211
|
+
context_mapping_config: {
|
|
212
|
+
type: 'custom',
|
|
213
|
+
// Liquid renders to a JSON object whose keys match the referenced
|
|
214
|
+
// agent's input_schema. Values reference the inbound row's fields
|
|
215
|
+
// via `event_dataset.<field>`. Workflow joins OMIT output_schema
|
|
216
|
+
// — the server pins it from the agent's input_schema.
|
|
217
|
+
body: JSON.stringify({
|
|
218
|
+
msg: '{{ event_dataset.message }}',
|
|
219
|
+
submission_id: '{{ event_dataset.submission_id }}',
|
|
220
|
+
}),
|
|
221
|
+
},
|
|
208
222
|
},
|
|
209
|
-
|
|
210
|
-
|
|
223
|
+
],
|
|
224
|
+
})
|
|
211
225
|
```
|
|
212
226
|
|
|
213
227
|
The agent's parsed output is exposed in the workflow's runtime
|
|
@@ -219,9 +233,9 @@ action templates reference it via Liquid:
|
|
|
219
233
|
{{ additional_context.contact-us-triage-categorizer.category }}
|
|
220
234
|
```
|
|
221
235
|
|
|
222
|
-
|
|
223
|
-
stage
|
|
224
|
-
|
|
236
|
+
A workflow with no attached agents observably runs the enrichment
|
|
237
|
+
stage but emits `{ status: 'skipped' }` in the per-step artifact
|
|
238
|
+
(see §6).
|
|
225
239
|
|
|
226
240
|
### `event_dataset` is the workflow accessor for the inbound row
|
|
227
241
|
|
|
@@ -273,7 +287,7 @@ filter_config required embed — { type, body, output_schema }
|
|
|
273
287
|
decision_config required embed — same shape
|
|
274
288
|
context_datasets required array — inline; replace-on-PUT
|
|
275
289
|
actions required array — inline; replace-on-PUT
|
|
276
|
-
|
|
290
|
+
workflow_ai_agents optional array — inline; replace-on-PUT (see §3)
|
|
277
291
|
```
|
|
278
292
|
|
|
279
293
|
**Write-only (Request-only).** None.
|
|
@@ -550,10 +564,11 @@ record `opened_at` / `form_submitted_at`.
|
|
|
550
564
|
`interoperability_contracts.md` §2 for the worked example.
|
|
551
565
|
|
|
552
566
|
2. **Replace-on-PUT silently drops omitted arrays.** Workflow
|
|
553
|
-
updates must re-supply `actions
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
567
|
+
updates must re-supply `actions` and `context_datasets`
|
|
568
|
+
verbatim — otherwise the existing rows are deleted. Factor the
|
|
569
|
+
body construction so create and update share one builder.
|
|
570
|
+
(Nested `workflow_ai_agents` follow the same replace-on-PUT
|
|
571
|
+
rule — re-supply the full set on every PUT; see §3.)
|
|
557
572
|
|
|
558
573
|
3. **`sql_where_clause` ids require an alias prefix.** `id` is
|
|
559
574
|
ambiguous across the multi-JOIN base query; use
|