# Architecture: when a flow, when a Met

[Build an AI agent](build-an-ai-agent.html) sets out the core decision: an agent decides the sequence on every run; a flow has it decided by whoever builds it. This guide goes one step further, because in practice almost nothing good is only one of the two. What works in production is **a flow that calls a Met only at the step that needs judgment**, or **a Met that calls a flow for what cannot be improvised**.

The reason is twofold. Every AI step spends Energy and can get it wrong; every fixed step spends nothing and always does the same thing. The fewer steps depend on the model, the cheaper and more predictable the process.

## The ladder

Ordered from the cheapest, most controllable rung to the most expensive and open-ended:

| | what it is | spends Energy | use it when |
|---|---|---|---|
| **1. Template, rule or code** | fixed text, `flow.condition`, `data.code`, buttons with `channel.ask_options` | no | the answer is known in advance or can be computed |
| **2. One bounded call** | an [AI Field](ai-field.html) or an isolated Run (`tool_policy: "none"`) | one call | you need to read or write language, but the task is a single one and needs no tools |
| **3. Ask a Met** | the `agent.ask` node inside a flow | that step | one step needs a Met's judgment or writing; the rest of the process stays fixed |
| **4. A Met that converses** | the `agent.handoff` node: from there on the Met handles the contact | every turn | the conversation cannot be predicted; the Met calls [Functions](functions-and-flows.html) for what cannot be improvised |

**Start at the bottom and climb only when the rung below falls short.** The signals to climb:

- **From 1 to 2:** the input is free text and the rule has turned into a list of words that never ends.
- **From 2 to 3:** the task needs the tone, the data or the tools of a specific Met.
- **From 3 to 4:** you cannot tell how many turns the conversation will take or in what order.

And the signals to step down, which are the ones that save the most: a Met that always does the same steps in the same order is a flow or a Function; a Met that picks between three fixed options is a button menu; a Met that only writes one sentence does not need its ninety tools.

### Rung 1: template, rule and code

Everything that does not go through a model:

- **`flow.condition`** branches the flow by groups of conditions (all or any). It compares with `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `contains`, `starts_with`, `in`, `not_in`, `has_any`, `has_none`, `is_set` and `is_empty`, and with `matches_term`, which looks for whole words ignoring case, accents and punctuation. If no group matches, it exits through "Si no" (else).
- **`data.code`** transforms data with JavaScript: it receives `entrada` and whatever you return lands in `@{node:<id>.result}`. It runs isolated, with no internet, files or modules, with 1 second, 64 MB of memory and a result of up to 256 KB. The `@{...}` references go in its input data, not inside the code.
- **`channel.ask_options`** asks the contact a question and waits for their choice with buttons or quick replies, without spending the Met's Energy. It is the way to classify when the options are few and known: letting the person choose costs less and gets it wrong less often than guessing what they meant.
- **Templates and fixed messages**: the messaging nodes send text you wrote, with references to the trigger's data.

### Rung 2: one bounded call

When you need language but not an agent:

- An **[AI Field](ai-field.html)** fills a collection cell with the field's prompt: classify a lead, summarize a note, pull the city out of an address.
- An **isolated Run** runs a Met with `tool_policy: "none"`: it works only with the `input`, its attachments (PDFs and images) and the Met's instructions. It loads no skills, integrations, history, variables or workspace context, and if the model tries to use a tool the run stops without dispatching it. Detail in [Run Mets](run-mets.html#isolated-run-no-tools-no-context).

```ts
const run = await met.runs.create({
  met: 'Extractor',
  tool_policy: 'none',
  input: 'Return only the tax ID and the total from this text, as JSON: ...',
});
```

### Rung 3: Ask a Met (`agent.ask`)

The `agent.ask` node ("Pedirle a Met") hands a task to a Met in the workspace with two fields: the Met (`met_id`) and the instruction (`prompt`), which can carry references to earlier nodes. The answer lands in `@{node:<id>.text}`.

Two things worth knowing before you use it:

- **The Met runs in full**, with its instructions, skills and tools. The node has no level of its own: it uses the Met's. So the Met you pick for a bounded step sets the cost of that step. For a short sentence, use a specialized Met on the agile level, with brief instructions and its tools switched off (below, [bounded context](#bounded-context)).
- **The answer is free text**, with no schema. If it is going out to someone, validate it first (below, [control the output](#control-the-output)).

### Rung 4: a Met that converses

The `agent.handoff` node binds a Met to the conversation: from there on that Met answers the contact. It is the right rung for handling people, and the worst one for running procedures: anything with fixed steps —charging, booking, logging an order— goes in a [Function](functions-and-flows.html) that the Met calls, with parameters the Function validates.

## Both directions of the combination

| direction | how | when |
|---|---|---|
| **flow → Met** | `agent.ask` for one step; `agent.handoff` to hand over the conversation | the process is fixed and one of its steps needs judgment or writing |
| **Met → flow** | an [AI Function](functions-and-flows.html) with an HTTP handler or with `flow_id`; the flow returns its result with the `function.result` node | the Met converses and, at some point, has to run something it cannot improvise |

In the second direction, the Function's parameters are the contract: with `enum_values` the Met can only send one of the values you allowed, and what happens next is decided by your flow or your backend, not by the model.

## What each thing costs

**Logic, data and messaging nodes spend no Energy.** Conditions, code, buttons, templates, waits, loops and CRM reads or writes run without a model. The nodes that spend are the ones that invoke AI: `agent.ask`, `agent.handoff`, `agent.nudge`, `image.generate`, `image.edit`, `voice.generate`, `video.generate`, and a `flow.action` node when one of its actions puts a Met to work. An `mcp.call` node does not go through a model, but the tool it calls may have its own cost, for example one that generates images.

**What makes a Met expensive is not just its level.** Every call to the model carries its instructions and the definition of all its active tools, even when the turn is trivial: a Met with a long prompt and dozens of tools pays for that context to write one sentence. The part of the context that repeats is charged less when it comes from cache, but it is not free. And the level multiplies all of the above.

**Routing costs too.** A Run without `met` is handled by the workspace's main Met, which can delegate to another Met: two executions where one would do. With `met`, the Run goes straight to that Met, without going through the orchestrator.

**How to measure it:** `GET /billing/executions` filters by `met_id` and by `run_id`, and `totals` adds up the whole filter ([What each call costs](cost-per-call.html)). There is no per-flow cost in the API today, so use a trick: give your flow's AI step **its own Met**. Filtering by that `met_id` tells you exactly what that step costs, run by run.

## Levels

Each Met has a level, chosen with `model` when you create or edit it ([Mets and their tools](mets-and-tools.html)):

| `model` | for what |
|---|---|
| `agil` | fast and the one that spends the least Energy per answer: classifying, extracting, writing a short sentence, answering what is already in its instructions |
| `brillante` | the everyday one: handling people, combining data, writing with judgment |
| `genio` | reasons more and spends more: decisions with many variables, long and delicate texts |

```ts
await met.agents.update(writerId, { model: 'agil' });
```

The practical rule: **agile for bounded tasks**. If the task fits in a two-line instruction and a one-sentence answer, raising its level does not make it better; it only makes it more expensive. The image and video nodes in a flow pick their own level; `agent.ask` uses the Met's.

## Bounded context

The less context a Met loads, the less each turn costs and the fewer ways it has to drift:

- **Switch off what it does not use** with `disabled_tools`. The valid names come from `met.agents.tools(id)` ([Mets and their tools](mets-and-tools.html#switch-tools-off-per-met)).
- **Hide the collections** that are none of its business with `expose_to_agent: false`.
- **Prefer several specialized Mets** to one that knows everything. A writer Met with five lines of instructions and its tools switched off is the right Met for an `agent.ask` that writes one sentence.
- **To extract or classify a single thing, use an isolated Run** (`tool_policy: "none"`): it does not even load the history.

## Control the output

A model can return something reasonable but wrong. Before its output reaches a customer, a flow checks it without AI:

1. **Validate with code.** `flow.condition` compares values but does not measure lengths or look for patterns, so the validation goes in a `data.code` that returns a yes or a no, and the condition reads it:

   ```js
   // Input data: {"texto": "@{node:redactar.text}"}
   const t = String(entrada.texto || '').trim();
   const ok = t.length > 0 && t.length <= 120 && !/https?:\/\/|www\./i.test(t);
   return { ok, texto: t };
   ```

   Then, a `flow.condition` with `@{node:validar.result.ok}` equal to `true`.
2. **Keep a fixed fallback.** The "Si no" (else) branch sends text you wrote. If the model fails, the contact gets something correct instead of nothing or something odd.
3. **Whatever publishes, charges or sends goes through a person.** When a Met asks to post on social media, charge, invoice, spend ad budget or send a template that Meta bills, the action can wait for someone in the business to approve it ([Human approvals](human-approvals.html)). A flow has no approval node: what it sends on its own goes out unreviewed, which is why it has to be fixed text or validated output.
4. **Narrow what the Met can ask for.** In a Function, `enum_values` and required parameters make what reaches your backend have the shape you expect.

## End-to-end example: replying to comments

This is the pattern Meteor uses to reply to comments on its own posts: when someone comments the keyword, they get the information by private message, a short public reply and a "Like". Only one of the six steps uses AI.

| step | node | what it does | spends Energy |
|---|---|---|---|
| 1 | `trigger.start` with the `social.comment_received` trigger | starts on a new comment on the chosen account that contains the keyword (`contains`, case-insensitive) | no |
| 2 | `mcp.call` → `meta_reply_to_comment_private` | sends a fixed private text with the information and the link | no |
| 3 | `mcp.call` → `meta_like_comment` | likes the comment (Facebook only; skipped on Instagram) | no |
| 4 | `agent.ask` | asks the writer Met for one sentence to reply in public | **yes** |
| 5 | `data.code` + `flow.condition` | validates the sentence: that it exists, that it is at most 120 characters and that it has no links | no |
| 6 | `mcp.call` → `meta_reply_to_comment` | posts the validated sentence or, through the "Si no" (else) branch, a fixed fallback text | no |

The comment's data arrives in the trigger: `@{trigger.payload.comment_id}`, `@{trigger.payload.comment_text}`, `@{trigger.payload.platform}` and `@{trigger.payload.account_id}`. Each comment is claimed before the flow runs, so the same comment never triggers it twice.

The instruction for step 4 fits in three lines:

```text
Write a public reply of a single warm sentence, at most 120 characters,
with no links or contact details, for this comment: @{trigger.payload.comment_text}
```

The writer Met runs on `agil`, with brief instructions about the brand's tone and its tools switched off with `disabled_tools`. What is at stake with AI is small —one sentence— and even so it does not go out without passing validation.

## Routines are this same pattern

Met's [Routines](routines-and-groups.html) —create and post on social media, advertise what you sell, follow up with your customers— are built this way inside:

- **Choosing is a button.** At each step the person picks a path, and picking it calls no model.
- **The assignment is a template.** What the path's Met is asked to do is put together from fixed text and the business's data.
- **One delegation per step.** Each AI step is a single assignment to a single Met. Steps that need no AI —working out whom to follow up with from the orders, for example— are done by the server.
- **The output is validated.** The Met closes with a block of structured data that is read, bounded and normalized; if it does not arrive or cannot be parsed, the step keeps its text and no figures: they are never made up.
- **Whatever publishes goes through approval.** During a routine, actions that publish or spend ad budget always wait for a person.
- **Cost is measured per run.** Each step is a turn with its Energy, and when the routine closes you see what the whole run spent.

## FAQ

### Does a flow with no AI steps spend Energy?

No. Logic, data and messaging nodes do not invoke a model. Watch out for what does cost outside of Energy: a WhatsApp template outside the 24-hour window is billed by Meta.

### Can I choose the level of an `agent.ask` node?

Not per node: the step uses the level of the Met you pick. If you need one step on agile and another on brilliant, use two Mets.

### When is an isolated Run better than `agent.ask`?

When you call from your backend and the task is a single extraction or classification over a text or a document. The isolated Run loads no tools or context; `agent.ask` runs inside a flow and the Met does it with everything it has.
