Workflows

The logic layer: how a workflow turns your data into a page, a response, or an automation, step by step.

A workflow is the logic layer, the recipe that turns your data into a finished thing: a web page, an API response, an image, or an automation. It's the piece your agent spends the most time on, and the one most worth understanding when you want to follow what it built or adjust it by hand in Workflow Studio.

You rarely author a workflow yourself. But being able to read one, to see the shape of what your agent assembled, is what makes the whole system legible instead of a black box.

#The shape of a workflow

A workflow is an ordered list of steps. It starts at the first step and runs each one, once, top to bottom. There are no jumps and no loops back: a step can repeat itself, but the workflow never returns to an earlier one. That straight-line shape is deliberate: you can read a workflow top to bottom and know exactly what runs, and when.

Each step has a name and does a bit of work. Wherever a value has to be worked out on the fly (a price, a filename, a filter), it's a small expression (more on those below).

#What carries between steps

This is the one rule worth holding onto. Inside a step, the results of its actions are available (the reply from an API call, the rows from a content lookup), but only for the rest of that step. To use a value in a later step, you save it into a variable.

Variables are the workflow's memory; everything else is scratch. It's why your agent adds "variables" here and there: it's stashing the pieces it will need further down.

#Steps and blocks

A step is built from blocks: you tick the ones it needs. A step holds at most one of each, and when it runs, its blocks fire in a fixed order regardless of how they were added:

  1. Assert: a precondition. If it's false, the whole run fails. ("The customer must exist.")
  2. Conditional: a skip gate. If it's false, the rest of this step is skipped. ("Only if they opted in.")
  3. Loop: repeat the rest of this step, either while a condition holds or for each item in a list. Iterations can run in parallel.
  4. API call: call one of your integrations and read the response.
  5. Sub-workflow: run another workflow and use its output. This is how a page fans out: header, body, and footer built by separate workflows, then stitched into one document.
  6. Data: read, write, or list your content records.
  7. LLM: call a language model (the key comes from a secret, never the workflow itself). Give it tools and it becomes an agent; see Agents below.
  8. Asset: read, upload, or generate a media file, including turning an SVG into an image, handy for social-share cards.
  9. Variables: set the named values that carry to later steps.

A step with no blocks is a valid do-nothing. Most steps are one or two blocks: fetch something, then stash it in a variable.

#Each action leaves a result

When a block does something, it leaves its result under a name you can read for the rest of that step:

BlockLeaves behindWhich is…
API call$response{ status, headers, body } (the reply, already parsed)
Sub-workflow$sub_outputwhatever that workflow returned
Data$data_resultthe record(s) read, or the outcome of a write
LLM$llmthe model's reply ($llm.text is the message)
Asset$assetthe file's details ($asset.url is its public address)

Because these vanish when the step ends, anything a later step needs gets carried forward into a variable first.

#Agents: the LLM block with tools

With no tools, the LLM block is one exchange: a prompt in, a reply out as $llm.text. Give it tools and it becomes an agent. The model can ask for a tool to be run, read the result, and go again, up to a max turns you set (counted in model calls, not tool calls). A tool is one of:

  • an integration, with its arguments inferred from the template's own variables;
  • another workflow, whose arguments are that workflow's input;
  • a set of Tessryx operations, so the agent can create, edit, publish, run and preview workflows, endpoints, schemas and the rest of your workspace. You tick the operations it may use, and each becomes a tool. It acts with editor permission, only inside a scope it must be given, and can never change members or billing. A run or preview an agent starts counts toward the same recursion limit as the run it is already inside, so an agent cannot loop through the front door. Firing a schedule is not on the list.

Two settings shape the work. Effort is how hard the model thinks per turn: lower is faster and cheaper, higher is better for long, many-step builds. Thinking display decides whether the run trace shows a summary of that reasoning. Both are display and budget choices; the model reasons either way.

Max tokens is per turn, not per block, and covers the model's thinking as well as its reply. Set it generously. A turn cut off at the limit is treated as a failure, not a short answer, so an agent never continues from half a tool call.

An agent stops when it answers without asking for a tool. If it runs out of turns while still asking, the run fails, and that last turn's tool calls are not made. An agent that ran out of turns did not finish and must not look as if it had.

#Scope and secrets

A workflow can declare a scope: the folders it may touch and what it may do in each. The three permissions are read (get, list, and call an integration or another workflow), write (save and delete) and publish (serve at a public path). A folder covers everything under it, so shop covers shop/orders/42, but never shop-old.

Leave the scope off and the workflow reaches the whole workspace, with the same authority as whoever wrote it. For most workflows that's allowed. It's worth setting a scope whenever a slug comes from something you don't control (a path parameter, a form field, text an agent read), so a surprising value can only reach that app's own files.

The one place a scope is required is an agent that can build: an LLM block given Tessryx operations as tools needs a scope, on the block or on the workflow. Without one the workflow is refused when it's saved and again when it runs, so every agent that can change your workspace works inside a limit someone declared.

In a scoped workflow:

  • Data, asset, API call and sub-workflow blocks can only name slugs inside its folders. Anything else fails the step. A sub-workflow runs only if its own scope fits inside the caller's, so an unscoped workflow can't be called from a scoped one.
  • An LLM block with tools can narrow things further with its own tool scope, worked out while the run happens, such as the one app the agent is building. It can't be wider than the workflow's scope. A tool scope that comes out empty or malformed fails the step before the model is called, rather than falling back to no limit.

#An agent can't widen its scope

An agent inherits the scope of whatever called it and can never widen it. Every workflow it creates, edits, publishes, runs or connects to an endpoint or schedule has to fit inside its own scope. If it saves a workflow without writing a scope, its own scope is written in for it, so an agent can't produce a workspace-wide workflow by leaving the field out. If it writes a wider scope, the save is refused.

#Secrets are handed over, never shared

A scoped workflow also lists the secrets it may use: the key of an LLM block and the credentials of any integration it calls. Using a secret that isn't on the list fails the step.

The list belongs to the workflow that's running and is never passed down from whoever called it. That's what makes delegation safe. An agent can call a workflow that holds a key (one that generates images, say) and the key does its job, but the agent never sees it and can't reuse it. A scoped agent can't save or edit a workflow that lists a secret, and can't build an integration that uses one. Unscoped workflows can use every secret in the workspace, as before.

#Chains: a job longer than one run

Every run has an execution budget (see Guardrails and cost). Some jobs are bigger than that: an agent building a whole app, a crawl of ten thousand pages. A workflow can run those as a chain. It finishes one run, saves a bookmark, and another run of the same workflow picks up from it.

Two settings on the workflow turn this on:

  • Continuation, with a max links: the most runs one chain may use, counting the first. A chain that reaches this number and still asks to continue fails rather than stopping quietly, so set it comfortably above what the job should need.
  • Continue with: an expression evaluated after the final transform. Return nothing and the chain ends, which is the normal way for one to end. Return a value and another run starts with that value as its entire input. Keep it a bookmark: a page token, the id you reached, what is left to do. Anything bigger belongs in a content record the next run reads. If the workflow declares an input schema, the bookmark must carry everything that schema requires, or the next run fails validation.

Inside a run, $link says where it is: $link.index (0 on the first run), $link.chain_id and $link.max. A chain keeps the workflow version it started on, so publishing mid-chain never changes the code under a saved bookmark. Every run after the first happens in the background, which makes chains a fit for a schedule, not for a page someone is waiting on.

Agents are the main reason chains exist. A chained agent that runs out of time stops at a turn boundary, sets $llm.stop_reason to out_of_time, and hands back its conversation as $llm.transcript. Return that from continue with, and give the LLM block a resume expression (usually $input.transcript) so the next run continues the same conversation instead of starting over. Resume evaluates to nothing on the first run, so one block serves every link in the chain.

#Producing the output

After the last step, a few optional expressions shape what the run returns:

  • Final transform: the expression whose value is the run's output (the HTML of a page, the JSON of an API route). Leave it off and the run returns nothing (fine for an automation that just writes data).
  • Output status & headers: an optional HTTP status and headers, for a page that needs to answer 404 for a missing record or 301 for a redirect. The endpoint serving the workflow applies them.
  • Output handling: for a workflow run from an editor, whether its result simply displays, replaces the record, or patches part of it.
  • Final message: a short confirmation shown in the editor after it runs.

#Expressions: JSONata and JavaScript

Anywhere a value is computed, it's a small expression in one of two languages:

  • JSONata: a compact language for querying and reshaping data, ideal for pulling fields out and restructuring them. One quirk worth recognizing: values are referenced with a leading $ ($input, $response, and your own variables); a bare word means "a field of the input" and usually comes back empty.
  • JavaScript: for logic that reads more clearly as code; it returns the value.

A variable can also hold a literal: text returned exactly as written, with no evaluation. That's the right home for a big blob of embedded CSS, HTML, or JavaScript a page needs; the dynamic bits get assembled separately and slotted in.

You mostly won't write these (your agent does), but recognizing the shapes is most of what it takes to read a workflow.

#Guardrails and cost

Every run has an execution budget: a wall-clock cap (up to five minutes) so a workflow that hangs or loops forever is stopped rather than running up cost. A run that exceeds it is ended. A job that legitimately needs more than that runs as a chain, which is bounded by its own max links.

Where to watch runs and read a failure is covered in Runs and monitoring; what a run costs is in Credits and caching.


Put simply: a workflow is a straight line of steps, each assembled from a few blocks that run in a fixed order, passing values forward through variables, and ending in an output your endpoint serves or your schedule runs on a clock.