atteq API
  • Guides
  • Recipes
  • Cookbook
  • API Reference
Mail triageMemoryTriageWeekly summary
powered by Zudoku

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/collections creates the collection with its fields; PATCH /api/v1/collections/{collection_id} sets the identity field once the fields exist.
  • tool: POST /api/v1/tools creates the instance: a built-in's, or a flow offered as one tool by its config.flow_id.
  • flow: POST /api/v1/flows creates the flow; POST /api/v1/flows/{flow_id}/steps adds each step, naming the tool it runs; POST /api/v1/flows/{flow_id}/edges adds each edge; PATCH /api/v1/flows/{flow_id}/entry-step sets the step a run starts at.
  • automation: POST /api/v1/automations creates the automation on its trigger, running the flow; PUT /api/v1/automations/{automation_id}/enabled switches 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's pre_hook_tool_ids or post_hook_tool_ids; POST /api/v1/agents/versions/{version_id}/publish publishes the draft.
  • thread_flow: no public operation of its own yet; the nearest, tied to a version: POST /api/v1/channels makes 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 fieldOperationRequest fieldNote
keynever sent—the recipe's own
namePOST /api/v1/collectionsname
saysnever sent—the recipe's own
descriptionPOST /api/v1/collectionsdescription
fieldsPOST /api/v1/collectionsfieldsthe same shape, one entry per field
identityPATCH /api/v1/collections/{collection_id}identity_field_keyonce the fields exist
contactnever sent—no request field yet: a mark the recipe keeps
editablenever sent—what the recipe's own page offers
recordsnever sent—no request field yet: a mark the recipe keeps for retention

tool

Recipe fieldOperationRequest fieldNote
keynever sent—the recipe's own
namePOST /api/v1/toolsname
saysnever sent—the recipe's own
descriptionPOST /api/v1/toolsdescription
builtinPOST /api/v1/toolssource, config.builtinsource is "builtin", and the built-in's name goes in config.builtin
flowPOST /api/v1/toolssource, config.flow_idsource is "builtin", and the flow's id goes in config.flow_id
configPOST /api/v1/toolsconfigbeside the key above
fixedPOST /api/v1/toolsfixed_parameters
input_schemaPOST /api/v1/toolsinput_schema
deadline_secondsPOST /api/v1/toolsdeadline_seconds
whennever sent—never sent: made only while the parameter it names has a value

flow

Recipe fieldOperationRequest fieldNote
keynever sent—the recipe's own
namePOST /api/v1/flowsname
saysnever sent—the recipe's own
stepsPOST /api/v1/flows/{flow_id}/steps—one call per step, in order; the first added is the entry
steps[].keynever sent—never sent: the call answers the step's id, which every later $ref: and edge names instead
steps[].toolPOST /api/v1/flows/{flow_id}/stepstool_idthe tool's id
steps[].paramsPOST /api/v1/flows/{flow_id}/stepsparam_overrideseach $ref:<step key> written $ref:<that step's id>
edgesPOST /api/v1/flows/{flow_id}/edges—one call per edge
edges[].fromPOST /api/v1/flows/{flow_id}/edgessource_step_idthe step's id
edges[].toPOST /api/v1/flows/{flow_id}/edgestarget_step_idthe step's id
edges[].conditionPOST /api/v1/flows/{flow_id}/edgesconditionfield, op, value; field reads <step id>.<path>
steps[].whennever sent—never sent: made only while the parameter it names has a value
steps[].unlessnever sent—never sent: made only while the parameter it names is left blank
edges[].whennever sent—never sent: made only while the parameter it names has a value
edges[].unlessnever sent—never sent: made only while the parameter it names is left blank
entryPATCH /api/v1/flows/{flow_id}/entry-stepstep_idonly when it is not the first step

automation

Recipe fieldOperationRequest fieldNote
keynever sent—the recipe's own
namePOST /api/v1/automationsname
saysnever sent—the recipe's own
flowPOST /api/v1/automationsflow_idthe flow's id
cronPOST /api/v1/automationstrigger_kind, trigger_config.crontrigger_kind is "schedule"
triggerPOST /api/v1/automationstrigger_kindits one key
trigger.schedule.cronPOST /api/v1/automationstrigger_config.cron
trigger.interaction_ended.agentPOST /api/v1/automationstrigger_config.agent_idthe agent's id
trigger.interaction_ended.end_reasonsPOST /api/v1/automationstrigger_config.end_reasons
trigger.interaction_ended.leave_out_testsPOST /api/v1/automationstrigger_config.leave_out_tests
trigger.record_written.collectionPOST /api/v1/automationstrigger_config.collection_idthe collection's id
fan_outPOST /api/v1/automationsfan_out
fan_out.collectionPOST /api/v1/automationsfan_out.collection_idthe collection's id
fan_out.filtersPOST /api/v1/automationsfan_out.filters
fan_out.limitPOST /api/v1/automationsfan_out.limit
enabledPUT /api/v1/automations/{automation_id}/enabledenabled

placement

Recipe fieldOperationRequest fieldNote
keynever sent—the recipe's own
namenever sent—the recipe's own
saysnever sent—the recipe's own
agentPATCH /api/v1/agents/versions/{version_id}—the agent whose draft version is version_id
slotPATCH /api/v1/agents/versions/{version_id}pre_hook_tool_ids, post_hook_tool_idspre is the first list, post the second
toolPATCH /api/v1/agents/versions/{version_id}—the tool's id, added to that list

thread_flow

Recipe fieldOperationRequest fieldNote
keynever sent—the recipe's own
namenever sent—the recipe's own
saysnever sent—the recipe's own
connectionPOST /api/v1/channelsconnection_idthe connection the channel is made on; the parameter names its channel
flowPOST /api/v1/channelsflow_idthe 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's get_tool, name, query_tool, write_tool; a weekly_moment parameter's cron, hour, weekday; an agent parameter's name; a connection parameter's name.
  • ${resource.<key>} -- the id of a part declared earlier. Attributes: a collection's get_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:

  • text
  • number
  • boolean
  • date
  • email
  • collection
  • weekly_moment
  • agent
  • connection
  • labels
  • person

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:

  • pre
  • post

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: takes cron
  • interaction_ended: takes agent, end_reasons, leave_out_tests
  • record_written: takes collection
  • webhook: 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.

Last modified on November 4, 2026
Weekly summary
On this page
  • The recipes
  • A recipe
  • Parts and their keys
  • From a recipe to the API
    • collection
    • tool
    • flow
    • automation
    • placement
    • thread_flow
  • References
  • Parameters
  • A connection's thread flow
  • Slots
  • Triggers
  • Rows written by one step
  • The step that replaces sensitive numbers
  • What it costs