How a recipe is composed
A recipe is a feature written as configuration: a named, versioned bundle of the platform's own primitives -- collections, tools, flows, automations, a tool placed in an agent's slot and a flow placed as a connection's thread flow -- and the parameters it asks the organization for. Each page in this cookbook is made from one recipe, and this page explains every word they use, read from the code that validates them. Read How it fits together first for the model they are made of.
The recipes
- Mail triage: Decide what each mail on one connection calls for -- answer, draft, internal only, escalate or ignore -- with a small model, and wake your agent only for the mail that needs it.
- Memory: Give one agent a memory: notes about each person and about the business, taken after every conversation and handed back at the start of the next one.
- Triage: Sort each new thread of one connection under the organization's labels, sending some to people and the rest to an agent.
- Weekly summary: Tell one person, once a week, what one of the organization's collections gained, in a few short paragraphs a model writes.
A recipe
A recipe declares key, version, name, description, intent, adapt, headline (optional), setup, setup_blank (optional), mark (optional), es (optional), requires (optional), parameters (optional), resources, proposed_labels (optional). Its intent is one sentence on one line and its adapt a note on how to make it your own; neither, nor any part's says, may hold a ${...} placeholder, because they are read as written.
A recipe also carries its setup: one sentence, at most 200 characters, in the client's words, that says what the recipe will do with what it is given. Each parameter it asks is a hole in the sentence, written {name} after the parameter; every parameter without a default is a hole, once, and one with a default may be a hole or may be left out. A recipe's page publishes the sentence with each hole shown as what its parameter is called. Every language a recipe speaks holds the same holes, in the order its own grammar wants (rule 45).
A recipe may propose labels for the organization's vocabulary, each a name, the line saying what belongs under it and whether it joins the one parameter that chooses labels they are for. The install creates the ones the client keeps as the organization's own labels: never the recipe's, never locked, and left in place by an update and by uninstalling.
At most 20 parameters and 50 parts.
Parts and their keys
Every key -- a parameter's name, a part's, a flow step's -- is spelled ^[a-z][a-z0-9_]*$ and is stable: parts point at one another by key, never by id, so the same recipe installs the same way in every organization. A part names only parts declared before it. The kinds of part:
- collection:
key,name,says,description(optional),fields,identity(optional),contact(optional),editable(optional),records(optional). - tool:
key,name,says,description(optional),builtin(optional),flow(optional),config(optional),fixed(optional),input_schema(optional),deadline_seconds(optional),when(optional). - flow:
key,name,says,steps,edges(optional),entry(optional). - automation:
key,name,says,flow,cron(optional),trigger(optional),fan_out(optional),enabled(optional). - placement:
key,name,says,agent,slot,tool. - thread_flow:
key,name,says,connection,flow.
A flow's step takes key, tool, params (optional), when (optional), unless (optional); an edge from, to, condition (optional), when (optional), unless (optional). A step's edges are tried in the order the flow declares them, and the first whose condition holds is taken.
A tool, a step and an edge may be made only while a parameter has a value: when: ${param.<name>} leaves it out when that value is blank -- none, an empty text, an empty list, a no -- and, on a step or an edge, unless: ${param.<name>} leaves it out when it is not. An edge to or from a step left out goes with it, and the entry step is never left out. A parameter that may be left blank is read only by what such a when makes, and so is a tool made only when one has a value; a key of a tool's config that is one whole ${param.<name>} token is left out of the tool while that value is blank.
A collection may mark one personal field as the contact its records are about, offer its records for editing (editable), and say what retention does to its records past the window (records): personal_fields, whole -- whole deletes a record with a personal field whole, and the recipe's page says so.
Each kind is made by hand with these public operations; each recipe's page names them part by part:
- collection:
POST /api/v1/collectionscreates the collection with its fields;PATCH /api/v1/collections/{collection_id}sets the identity field once the fields exist. - tool:
POST /api/v1/toolscreates the instance: a built-in's, or a flow offered as one tool by itsconfig.flow_id. - flow:
POST /api/v1/flowscreates the flow;POST /api/v1/flows/{flow_id}/stepsadds each step, naming the tool it runs;POST /api/v1/flows/{flow_id}/edgesadds each edge;PATCH /api/v1/flows/{flow_id}/entry-stepsets the step a run starts at. - automation:
POST /api/v1/automationscreates the automation on its trigger, running the flow;PUT /api/v1/automations/{automation_id}/enabledswitches it on: an automation is created switched off. - placement: no public operation of its own yet; the nearest, tied to a version:
PATCH /api/v1/agents/versions/{version_id}adds the tool to the draft'spre_hook_tool_idsorpost_hook_tool_ids;POST /api/v1/agents/versions/{version_id}/publishpublishes the draft. - thread_flow: no public operation of its own yet; the nearest, tied to a version:
POST /api/v1/channelsmakes the connection's channel, answered by the flow it names.
From a recipe to the API
A recipe's YAML is its declaration, not a request body. Building a part by hand, send each field as below: a value written ${...} is replaced with your own, and a part, step or agent named by key is named by the id its own operation answered.
collection
| Recipe field | Operation | Request field | Note |
|---|---|---|---|
key | never sent | — | the recipe's own |
name | POST /api/v1/collections | name | |
says | never sent | — | the recipe's own |
description | POST /api/v1/collections | description | |
fields | POST /api/v1/collections | fields | the same shape, one entry per field |
identity | PATCH /api/v1/collections/{collection_id} | identity_field_key | once the fields exist |
contact | never sent | — | no request field yet: a mark the recipe keeps |
editable | never sent | — | what the recipe's own page offers |
records | never sent | — | no request field yet: a mark the recipe keeps for retention |
tool
| Recipe field | Operation | Request field | Note |
|---|---|---|---|
key | never sent | — | the recipe's own |
name | POST /api/v1/tools | name | |
says | never sent | — | the recipe's own |
description | POST /api/v1/tools | description | |
builtin | POST /api/v1/tools | source, config.builtin | source is "builtin", and the built-in's name goes in config.builtin |
flow | POST /api/v1/tools | source, config.flow_id | source is "builtin", and the flow's id goes in config.flow_id |
config | POST /api/v1/tools | config | beside the key above |
fixed | POST /api/v1/tools | fixed_parameters | |
input_schema | POST /api/v1/tools | input_schema | |
deadline_seconds | POST /api/v1/tools | deadline_seconds | |
when | never sent | — | never sent: made only while the parameter it names has a value |
flow
| Recipe field | Operation | Request field | Note |
|---|---|---|---|
key | never sent | — | the recipe's own |
name | POST /api/v1/flows | name | |
says | never sent | — | the recipe's own |
steps | POST /api/v1/flows/{flow_id}/steps | — | one call per step, in order; the first added is the entry |
steps[].key | never sent | — | never sent: the call answers the step's id, which every later $ref: and edge names instead |
steps[].tool | POST /api/v1/flows/{flow_id}/steps | tool_id | the tool's id |
steps[].params | POST /api/v1/flows/{flow_id}/steps | param_overrides | each $ref:<step key> written $ref:<that step's id> |
edges | POST /api/v1/flows/{flow_id}/edges | — | one call per edge |
edges[].from | POST /api/v1/flows/{flow_id}/edges | source_step_id | the step's id |
edges[].to | POST /api/v1/flows/{flow_id}/edges | target_step_id | the step's id |
edges[].condition | POST /api/v1/flows/{flow_id}/edges | condition | field, op, value; field reads <step id>.<path> |
steps[].when | never sent | — | never sent: made only while the parameter it names has a value |
steps[].unless | never sent | — | never sent: made only while the parameter it names is left blank |
edges[].when | never sent | — | never sent: made only while the parameter it names has a value |
edges[].unless | never sent | — | never sent: made only while the parameter it names is left blank |
entry | PATCH /api/v1/flows/{flow_id}/entry-step | step_id | only when it is not the first step |
automation
| Recipe field | Operation | Request field | Note |
|---|---|---|---|
key | never sent | — | the recipe's own |
name | POST /api/v1/automations | name | |
says | never sent | — | the recipe's own |
flow | POST /api/v1/automations | flow_id | the flow's id |
cron | POST /api/v1/automations | trigger_kind, trigger_config.cron | trigger_kind is "schedule" |
trigger | POST /api/v1/automations | trigger_kind | its one key |
trigger.schedule.cron | POST /api/v1/automations | trigger_config.cron | |
trigger.interaction_ended.agent | POST /api/v1/automations | trigger_config.agent_id | the agent's id |
trigger.interaction_ended.end_reasons | POST /api/v1/automations | trigger_config.end_reasons | |
trigger.interaction_ended.leave_out_tests | POST /api/v1/automations | trigger_config.leave_out_tests | |
trigger.record_written.collection | POST /api/v1/automations | trigger_config.collection_id | the collection's id |
fan_out | POST /api/v1/automations | fan_out | |
fan_out.collection | POST /api/v1/automations | fan_out.collection_id | the collection's id |
fan_out.filters | POST /api/v1/automations | fan_out.filters | |
fan_out.limit | POST /api/v1/automations | fan_out.limit | |
enabled | PUT /api/v1/automations/{automation_id}/enabled | enabled |
placement
| Recipe field | Operation | Request field | Note |
|---|---|---|---|
key | never sent | — | the recipe's own |
name | never sent | — | the recipe's own |
says | never sent | — | the recipe's own |
agent | PATCH /api/v1/agents/versions/{version_id} | — | the agent whose draft version is version_id |
slot | PATCH /api/v1/agents/versions/{version_id} | pre_hook_tool_ids, post_hook_tool_ids | pre is the first list, post the second |
tool | PATCH /api/v1/agents/versions/{version_id} | — | the tool's id, added to that list |
thread_flow
| Recipe field | Operation | Request field | Note |
|---|---|---|---|
key | never sent | — | the recipe's own |
name | never sent | — | the recipe's own |
says | never sent | — | the recipe's own |
connection | POST /api/v1/channels | connection_id | the connection the channel is made on; the parameter names its channel |
flow | POST /api/v1/channels | flow_id | the flow's id |
References
A string anywhere in a part may hold ${<head>.<name>[.<attribute>]}:
${param.<name>}-- a parameter's value. Attributes: a collection parameter'sget_tool,name,query_tool,write_tool; a weekly_moment parameter'scron,hour,weekday; an agent parameter'sname; a connection parameter'sname.${resource.<key>}-- the id of a part declared earlier. Attributes: a collection'sget_tool,name,query_tool,write_tool.${platform.<name>}-- a value of the deployment's:mail_sender.
A placeholder that is the whole string keeps the value's type; one inside a longer string is written into it. A field that names one thing -- an automation's flow, a placement's agent and tool -- is one whole placeholder of the right kind.
A flow step's parameters also speak the flow's own grammar: $ref:<step>.<path> is what an earlier step answered, and $ref:trigger.<path> is what set the run off.
A tool's fixed values may name a fact of the conversation the tool runs in, and only one every slot keeps, after the conversation included: $ref:conversation.contact, $ref:conversation.id, $ref:conversation.number. It is read only there: a flow placed in a slot reads it as $ref:trigger.<key>, handed in by the tool that runs it.
Parameters
A parameter takes name, kind, label, description (optional), required (optional), default (optional), minimum (optional), maximum (optional), and is either required or has a default. Its kind is one of:
textnumberbooleandateemailcollectionweekly_momentagentconnectionlabelsperson
Only a number takes a range, minimum and maximum, either end optional; a value outside it is refused, saying the range. A recipe asks for one agent at most, always required and fixed once installed: it says which install this is, one per agent. It asks for one connection at most -- a connection that takes threads, by its channel's id -- always required and fixed once installed, one install per connection; beside it, the agent does not say which install this is, so it may be left blank, and is fixed once installed too. A labels or person parameter may be left blank in any recipe, and then is none. A recipe never asks for a secret: a credential lives in a connection a parameter points at.
A connection's thread flow
A thread_flow part places one of the recipe's flows as the thread flow of the connection its connection parameter names, in place of the one the connection answered with: a flow the connection wrote goes, and one built by hand stays yours. The connection runs it on every message, behind its own who reaches agents and reply mode, and what set the run off is the burst, read as $ref:trigger.<path>. While the recipe is off, a message that would run the flow waits for a person, saying why; uninstalling leaves the connection answered by the agent the recipe named, or by a person.
Slots
A placement puts one of the recipe's tools -- a built-in's instance, or a flow offered as a tool -- in an agent's slot, outside its versions: it runs from the agent's next conversation without a publish and stays through every publish. The slots:
prepost
Triggers
An automation runs one of the recipe's flows. A bare cron is the schedule written short; otherwise its trigger is exactly one of:
schedule: takescroninteraction_ended: takesagent,end_reasons,leave_out_testsrecord_written: takescollectionwebhook: takes nothing
With fan_out (collection, filters (optional), limit (optional)) it opens one run per matching record, at most 200. An automation also takes enabled (optional).
Rows written by one step
A collection's write tool takes rows, a list of rows written as one record each, in order, at most 200 from one step; a longer list fails the step before any row is written. Its query tool takes newest_first to read the newest records first, and a limit.
The drafting built-in draft_text returns rows instead of a text when its instance's config names a row's fields under rows: its answer is then rows, ready for a write step.
The step that replaces sensitive numbers
The built-in redact_sensitive_numbers takes a text, or any value whose strings it walks, and returns it with card numbers, bank accounts and identity numbers replaced, keeping nothing of what it replaced. Put it between a drafting step and the write that keeps the result.
What it costs
Nothing is charged for a recipe itself: what its parts consume -- a tool execution, a model's tokens, a message, storage -- is charged as those parts are.