Routing with Decision Nodes (Early Access)

This feature is in the Early Access stage and under active development. Configuration schemas and behavior are subject to change. We recommend that you do not use this feature in production environments.

This page covers the decision node type, which classifies a workflow's data and selects the next step in a single call to a decision model. It builds on the workflow YAML described in Creating Workflows with the CLI (Early Access).

Understanding Decision Nodes

A decision node sends its input to a decision model and asks it one or more typed questions about that input. A decision model is a structured model from TypeSafe or OpenAI. The model answers every question in one call. The node can then route the workflow on one of the answers: each option of a choice question maps to a downstream node, an answer below a confidence threshold escalates to another node, and a default catches any option that has no route.

Each question has one of three types.

Type What the model returns
boolean Whether a statement about the input is true, with the probability that it is.
choice One option from a list you define, with a probability for each option and an overall confidence.
score A position on a scale of two to 10 ordered levels you define, with a probability for each level and an overall confidence.

Only a choice question can select a branch. Answers to the other questions are available to downstream nodes, so one call can both route a ticket and flag it as urgent.

Choosing Between a Decision Node and an Agent

Without a decision node, the usual way to classify and route is an agent node that classifies the input, followed by a switch node that branches on the agent's answer. The following table compares the two approaches.

Aspect Decision node Classify agent and switch node
Suited to Narrow classification, such as which team owns a ticket, whether a document is privileged, or how severe an incident is Open-ended reasoning, steps that call tools, and steps that need the conversation
Cost per run One call to the decision model for all of the node's questions A full agent turn
Uncertain answers Escalate to another node when confidence is below min_confidence No built-in confidence signal
Where the input goes The decision model's provider (TypeSafe or OpenAI), or a pass-through service such as OpenRouter The large language model (LLM) configured for the agent

The two approaches combine well: the decision node routes the confident answers directly and escalates the uncertain ones to the classify agent you would otherwise have built.

A decision node sends its resolved input to a third party. That third party is the decision model's provider (TypeSafe or OpenAI), or a pass-through service such as OpenRouter that forwards the call. For a workflow that you save in the Agent Mesh UI or apply with sam config apply, Solace Agent Mesh sends nothing until an administrator registers a decision model and a workflow names it. Review the provider's data-processing terms before you register a decision model. Pass the node only the fields its questions are about.

When you describe a workflow in Quick Build (Experimental), Quick Build proposes a decision node only when your deployment has a decision model registered and decision nodes enabled. Otherwise it builds a classify agent and a switch node, and tells you what is missing if you asked for a decision node.

Choosing a Decision Model Provider

A decision model comes from one of two providers (TypeSafe or OpenAI). You write the same boolean, choice, and score questions for either one. Agent Mesh converts them to the provider's format, and the decision node's output has the same fields with both providers. Decision models from both providers are Early Access, and both providers are available wherever decision nodes are enabled. The following table compares them.

Aspect TypeSafe OpenAI
Model name A Jev version, such as jev-1.13.0, or openai/gpt-6-luna-decisions through OpenRouter gpt-6-luna
Images The Jev models and openai/gpt-6-luna-decisions read images Reads images
Refusals Does not return refusals Can refuse to answer a question. For more information, see Handling Provider Failures.

The OpenAI Decisions API accepts no criteria for a yes-or-no question. When a boolean question goes to OpenAI, Agent Mesh appends any descriptions that you set for its "true" and "false" criteria to the question's instructions.

Adding a Decision Node to a Workflow

The following procedure adds a workflow that triages a support ticket with a decision node and applies it with sam config apply.

Before You Begin

  • Register a decision model in your deployment. For the steps in the Agent Mesh UI, see Adding a Decision Model (Early Access). For the YAML, see Define a Decision Model (Early Access). The example on this page uses a decision model with the alias ticket-router.

  • Confirm that decision nodes are enabled. Decision nodes are enabled by default, but an administrator can disable them by setting the SAM_FEATURE_WORKFLOW_DECISION_NODE environment variable to false. While decision nodes are disabled, each decision node fails when a workflow reaches it. The Add Model form then offers decision models only while decision-model evaluators are enabled, which they are by default.

  • Create the agents that the decision node routes to. The example references four agents named billing-desk, tech-desk, general-desk, and ticket-reviewer. For more information, see Creating Agents.

  • Set up a declarative-config repository with a manifest that lists the workflow, as described in Creating Workflows with the CLI (Early Access).

Procedure

To add the ticket triage workflow, perform these steps:

  1. Write the workflow file. In the following example, the triage decision node asks which team owns the ticket and whether it is urgent. It routes billing and technical tickets to their desk agents, sends any other ticket to a general desk, and hands any team answer with less than 0.8 confidence to a reviewer agent that answers the ticket itself.

    # ticket-triage.yaml
    kind: workflow
    name: ticket-triage
    description: Routes a support ticket to the desk that owns it, and sends uncertain tickets to a reviewer.
    spec:
      appConfig:
        description: Classifies a ticket with a decision model and routes it.
        decision:
          model_alias: ticket-router  # a model registered with modelType decision
        input_schema:                 # the fields a caller passes; the Orchestrator sends ticket
          type: object
          properties:
            ticket: {type: string}
          required: [ticket]
        output_mapping:
          reply:
            coalesce:                 # only one of these nodes runs, so take whichever answered
              - "{{billing_desk.output}}"
              - "{{tech_desk.output}}"
              - "{{general_desk.output}}"
              - "{{review.output}}"
          department: "{{triage.output.answers.department.choice}}"
        nodes:
          - id: triage
            type: decision
            input:
              ticket: "{{workflow.input.ticket}}"   # what the model reads; every question is about all of it
            questions:
              department:
                type: choice
                instructions: Which team should own this ticket?
                criteria:
                  billing: Payment, invoice, or refund issues
                  technical: Bugs, errors, or outages
                  other: Anything else
              urgent:
                type: boolean
                instructions: The ticket needs an answer today.   # a statement the model scores true or false
            route_on: department
            routes:
              billing: billing_desk
              technical: tech_desk
            default: general_desk     # catches "other", the one option with no route
            min_confidence: 0.8       # a department answer below this confidence escalates
            escalate: review
            escalation_mode: handoff  # the reviewer answers the ticket; no desk runs after it
          - id: billing_desk
            type: agent
            agent_name: billing-desk
            depends_on: [triage]
            input:
              message: "Urgent: {{triage.output.answers.urgent.value}}. Ticket: {{workflow.input.ticket}}"
          - id: tech_desk
            type: agent
            agent_name: tech-desk
            depends_on: [triage]
            input:
              message: "Urgent: {{triage.output.answers.urgent.value}}. Ticket: {{workflow.input.ticket}}"
          - id: general_desk
            type: agent
            agent_name: general-desk
            depends_on: [triage]
            input:
              message: "Urgent: {{triage.output.answers.urgent.value}}. Ticket: {{workflow.input.ticket}}"
          - id: review
            type: agent
            agent_name: ticket-reviewer
            depends_on: [triage]
            input:
              message: "Decide which team owns this ticket and answer it: {{workflow.input.ticket}}"

    To adapt the example, replace the questions and their criteria with your own, point routes at your own nodes, and set model_alias to the alias of your decision model. Every route, default, and escalate node lists the decision node in depends_on. For every field the node accepts, see Decision Node Reference. For how escalation works and when to use handoff instead of the default reroute, see Understanding Escalation.

    Save the file as workflows/ticket-triage.yaml in your declarative-config repository, and list ticket-triage under resources.workflows in your manifest.

  2. Preview the change:

    sam config plan -m manifest.yaml

    The plan lists ticket-triage as a workflow to create.

  3. Apply the manifest to create and deploy the workflow:

    sam config apply -m manifest.yaml

    The output reports the workflow as created and its deployment as completed. Agent Mesh rejects the workflow at this step if decision.model_alias is missing or does not name a configured decision model.

The keys under routes must match the option keys of the route_on question exactly, including case. A route key that is not one of the options fails the workflow when it loads, and so does an option with no route when the node has no default.

Verifying the Workflow

To verify the workflow, perform these steps:

  1. Confirm that ticket-triage appears on the Workflows page.

  2. In chat, ask the Orchestrator to triage a ticket with the ticket-triage workflow, for example: Use the ticket-triage workflow on this ticket: I was charged twice for my October invoice. The Orchestrator calls the workflow with the ticket as its ticket input.

  3. Open the run on the Activities page. The decision node shows the department the model chose and its confidence, and the branches it did not take appear dimmed.

The workflow output carries the desk's reply and the department the decision model chose. A run that escalated carries the reviewer's reply instead, and the decision node's escalation_reason output is low_confidence.

To list every field the decision node type accepts, run the following command:

sam config schema show workflow --type decision

Sending Images to the Decision Model

Three kinds of decision model read images together with the node's input: a Jev model, on the TypeSafe API or through OpenRouter; an OpenAI decision model; and GPT-6 Luna Decisions through OpenRouter. List the images in the node's images field. A model that reads images can, for example, judge how badly a part in a photo is damaged or whether a scanned page is an invoice. Every other TypeSafe decision model reads only text. A decision node that sends images to a model that does not read them fails, even under on_provider_error: escalate.

To use a Jev model, register a TypeSafe decision model with the API base URL https://api.typesafe.ai and a pinned model name, such as jev-1.14.0. Through OpenRouter, use the API base URL https://openrouter.ai/api and a model name, such as typesafe/jev-1.14. To use GPT-6 Luna Decisions, register a TypeSafe decision model with the API base URL https://openrouter.ai/api and the model name openai/gpt-6-luna-decisions. For more information, see Adding a Decision Model (Early Access).

A decision model receives the node's input as text, followed by the images in the order that the node lists them. Write questions that refer to an image in words, such as "the part in the photo". For a Jev model on the TypeSafe API, Agent Mesh also fits each image to the limits that the TypeSafe API sets, in the following ways:

  • Agent Mesh sends PNG and JPEG images unchanged when they are within the limits.

  • Agent Mesh converts WebP images to PNG and sends only the first frame of an animated PNG.

  • Agent Mesh fails the node for an image that it cannot decode to fit the limits, such as an animated WebP image, even under on_provider_error: escalate.

  • Agent Mesh resizes each image, keeping its proportions, so that each side is between 256 and 1,568 pixels.

  • Agent Mesh pads a very narrow image with white to reach 256 pixels on its short side.

  • Agent Mesh fails the node with the input_too_large category for an image larger than 40 megapixels, or escalates the node under on_provider_error: escalate. This limit applies even to an image whose sides are both 8,000 pixels or less.

The node's output lists each image as Agent Mesh loaded it, before Agent Mesh fits it to these limits. The type and size of an image that TypeSafe receives can therefore differ from the ones in the output. The max_image_bytes limit also applies to the images before Agent Mesh fits them.

TypeSafe describes image input as an early feature whose format and limits can change between releases.

A decision node sends its images and its input to a third party in one call. For a Jev model on the TypeSafe API, that third party is TypeSafe. For a Jev model through OpenRouter, it is OpenRouter, which passes the call to TypeSafe. For an OpenAI decision model, it is OpenAI. For GPT-6 Luna Decisions, it is OpenRouter, which passes the call to OpenAI. Review the data-processing terms of each provider that receives them, and send only the images that the node's questions are about.

Each entry in images is one of the following:

  • The filename of an image artifact in the workflow run's session, such as inspection.png. The artifact can come from an earlier node in the run. A filename without a version refers to the artifact's latest version.

  • A filename with a version number, such as inspection.png:2.

  • An artifact:// URI that names one of your own artifacts. A URI that names another user's artifact fails the node.

  • A {{...}} template that resolves to any of these, or to a list of them, such as {{inspect.output.created_artifact}}.

The following example shows a decision node that asks how badly the part in a photo is damaged. An earlier inspect tool node saved the photo, and the decision node reads it through the tool node's created_artifact output:

- id: grade_damage
  type: decision
  depends_on: [inspect]
  input:
    report: "{{workflow.input.report}}"
  images:
    - "{{inspect.output.created_artifact}}"
  questions:
    damage:
      type: choice
      instructions: How badly is the part in the photo damaged?
      criteria:
        none: No visible damage
        minor: Scratches or dents that do not affect how the part works
        major: Cracked, bent, or broken

Agent Mesh handles the images as follows:

  • It accepts PNG, JPEG, and WebP images, and reads the type from the file's contents, not from its name. An artifact that is missing, or that is not one of these types, fails the node, even under on_provider_error: escalate.

  • It fails the node for an image whose header it cannot read, such as a truncated or corrupt PNG, JPEG, or WebP file, even under on_provider_error: escalate.

  • It skips an entry that resolves to nothing, so a node can run without an image when none is supplied. When no image is left, the model answers from the node's input alone.

  • It skips an entry that reads a workflow input that the caller left out or sent as null, an empty string, or an empty list.

  • It skips an entry that reads a node output field that is null or empty, or a node that completed without returning any output, such as an agent that replied with no content.

  • It skips an entry that reads a node that the workflow pruned. A pruned node is one whose when condition was false, one on a branch that a switch node or another decision node decided not to take, or one that was skipped because every node it depends on was pruned.

  • It fails the node for an entry that reads a node that failed, or a node that was skipped because a node upstream of it failed. The node fails even under on_provider_error: escalate.

  • It fails the node for an entry that reads a node that was skipped because its when condition referenced an output field that the referenced node did not return.

  • It fails the node for an entry that reads the target of a switch case that Agent Mesh could not evaluate, even when the switch selected a later case or its default. Agent Mesh does not treat that target as pruned, because no case decided against it.

  • It fails the node for an entry that reads a node that has not run and is not upstream of the decision node. To fix this failure, add that node to the decision node's depends_on.

  • It fails the node for an entry that names an output field that the referenced node did not return.

  • It treats an entry that embeds a template in other text, such as {{workflow.input.photo}}:2, as one unit. When a reference in that entry resolves to nothing, Agent Mesh skips the whole entry or fails the node by the preceding rules.

  • It logs a warning when it loads the workflow if an entry names a node that the workflow does not define. When the decision node runs, that entry fails the node.

  • It sends at most five images, and at most 20 MiB of image data after downscaling, in one call. To change these limits, set max_images and max_image_bytes in the workflow's decision block. If a decision node lists more fixed artifact references (entries without a {{...}} template) than max_images, the workflow fails to load.

  • It checks each artifact's stored size before it reads the artifact. An artifact larger than max_image_bytes fails the node with the input_too_large category, even if downscaling would make it smaller, or escalates under on_provider_error: escalate.

  • It downscales an image whose longer side is more than 8,000 pixels so that its longer side is 8,000 pixels, and sends a downscaled PNG or WebP image as PNG. The PNG that downscaling produces can be larger than the original file, so it uses more of max_image_bytes. It sends an image whose sides are both 8,000 pixels or less unchanged, regardless of its pixel count. The exception is a Jev model on the TypeSafe API, for which Agent Mesh fits each image to the TypeSafe limits that are described earlier in this section.

  • It fails the node for an image that needs downscaling and is larger than 40 megapixels, with the input_too_large category, or escalates under on_provider_error: escalate. For a Jev model on the TypeSafe API, the same applies to any image larger than 40 megapixels. An image that needs downscaling but that Agent Mesh cannot decode or re-encode fails the node, even under on_provider_error: escalate.

  • It checks the number of images before it loads them, and their total size after downscaling. More images than max_images, or images that together are larger than max_image_bytes, fail the node with the input_too_large category before Agent Mesh calls the model, or escalate under on_provider_error: escalate.

  • It loads and downscales the images within the same budget as the call: 90% of the node's timeout, or of the workflow's default_node_timeout when the node sets none. A node that runs out of time fails with the timeout category, or escalates under on_provider_error: escalate.

  • It adds while loading images to the node's error when the node fails with the timeout or input_too_large category while Agent Mesh loads the images. For example, the error then contains timeout failure while loading images.

The images field of the node's output lists each image that Agent Mesh loaded for the call, by name, type, and size. The field never contains the image data. For more information, see Decision Node Output.

Decision Node Reference

This section lists the fields a decision node and its workflow accept. For how to use them, see Adding a Decision Node to a Workflow.

The Workflow Decision Block

The decision block sits under spec.appConfig, next to nodes, and names the model that every decision node in the workflow calls. A workflow that has decision nodes and no decision.model_alias is rejected when you save it.

Field Required Default Description
model_alias Yes None The alias of a model registered with modelType: decision. The registered model carries the endpoint, API key, and model version.
timeout_seconds No 10 How long, in seconds, one decision call can take, including up to three attempts when the provider has a transient failure such as a rate limit, overload, or gateway error. The call, together with loading and downscaling its images, also stops at 90% of the node's timeout, if that comes first.
max_state_bytes No 131072 (128 KiB) The largest resolved input, in bytes, that a decision node sends. A larger input fails the node with the input_too_large category, or escalates it under on_provider_error: escalate.
max_images No 5 The maximum number of images that one decision call sends, from 0 to 128. A value of 0 uses the default, and a value outside this range fails the workflow when it loads. More images fail the node with the input_too_large category, or escalate it under on_provider_error: escalate. A decision node that lists more fixed artifact references (entries without a {{...}} template) than this value fails the workflow when it loads.
max_image_bytes No 20971520 (20 MiB) The maximum total size, in bytes after downscaling, of the images that one decision call sends, from 0 to 67108864 (64 MiB). A value of 0 uses the default, and a value outside this range fails the workflow when it loads. An image artifact whose stored size is larger than this value, or images that together are larger than this value after downscaling, fail the node with the input_too_large category, or escalate it under on_provider_error: escalate.

Agent Mesh rejects a workflow whose decision block sets api_base, api_key, or model, because a saved workflow stores its body in plaintext. Register the endpoint and key as a decision model and set model_alias instead.

Decision Node Fields

A decision node takes the id, type, depends_on, when, and timeout fields that every node shares, plus the following fields. It does not take retry_strategy. The workflow fails to load if one is set, because the call already retries transient provider failures within timeout_seconds.

Field Required Description
questions Yes A map of questions keyed by question name, not a list. All questions are answered in one call. For the fields of each question, see Question Fields.
input No The data the model reads, built from {{...}} templates. Every question is asked about all of it, so pass only the fields the questions need.
images No A list of image artifacts that the model reads together with input. Only a decision model that reads images accepts them: a Jev model, an OpenAI decision model, or GPT-6 Luna Decisions through OpenRouter. For more information about the accepted references and limits, see Sending Images to the Decision Model.
route_on When the node routes The name of the choice question whose answer selects the branch. Setting routes, default, escalate, or min_confidence without route_on fails the workflow when it loads.
routes With route_on A map from each option of the route_on question to the ID of the node that runs for it.
default When an option has no route The node that runs when the model chooses an option that has no route. Set it only when some option has no route. If the routes cover every option, the default node never runs.
min_confidence No A number greater than 0 and at most 1. An answer to the route_on question with a lower confidence escalates to the escalate node.
escalate With min_confidence or on_provider_error: escalate The node that runs when the decision node escalates, usually an agent. Naming an escalate node that nothing can reach fails the workflow when it loads.
escalation_mode No Either reroute (the default) or handoff. With reroute, the escalate node answers the route_on question, and its answer picks the route. With handoff, the escalate node's reply is the result and no route runs. For more information, see Understanding Escalation.
on_provider_error No Either fail (the default) or escalate. For more information, see Handling Provider Failures.
model No Overrides the decision model's Model Name for this node only. If you set this field for a TypeSafe model, use a pinned version, such as jev-1.13.0. For an OpenAI model, leave the field unset or set it to gpt-6-luna. A model name that OpenAI does not serve fails the node with a config failure, which does not escalate. On a node that sets images, use a model that reads images, such as a Jev model or openai/gpt-6-luna-decisions through OpenRouter.

A decision node can also be the node that a map or loop node runs, but in that position it cannot set route_on. Branch on its answers after the map or loop node instead. A decision node cannot be an on_exit handler, and its routes, default, and escalate node cannot point at one.

Question Fields

Each question takes the following fields.

Field Required Description
type Yes One of boolean, choice, or score.
instructions Yes The question to ask, written plainly, such as Which team should own this ticket?. For a boolean question, write a statement the model scores true or false, such as The ticket needs an answer today.
criteria For choice and score For a choice question, a map from each option to a description, with two to 255 options. For a score question, a list of two to 10 single-entry level: description items, lowest level first. A map is rejected for a score question because a map does not keep its order. For a boolean question, criteria is optional; when present, it must describe both "true" and "false", and nothing else. Quote those two keys, because YAML otherwise reads them as booleans.

The following example shows a score question and a boolean question with criteria:

questions:
  severity:
    type: score
    instructions: How severe is the incident this ticket reports?
    criteria:
      - low: Cosmetic, or affects one user
      - medium: Degrades a feature for some users
      - high: An outage or data loss
  needs_human:
    type: boolean
    instructions: The ticket asks to speak to a person.
    criteria:
      "true": The customer asks for a call, a manager, or a named employee
      "false": The customer asks only for a fix or an answer

Decision Node Output

Downstream nodes and the workflow's output_mapping read a decision node's output with {{<node-id>.output.<field>}} templates.

Field Description
answers.<question>.type The question's type: boolean, choice, or score.
answers.<question>.choice For a choice question, the option the model chose.
answers.<question>.value For a boolean question, true when the probability that the statement is true is 0.5 or more, otherwise false.
answers.<question>.probability For a boolean question, the probability that the statement is true.
answers.<question>.score For a score question, a number from 0 to one less than the number of levels. The score can land between two levels.
answers.<question>.legend For a score question, a map from each level number ("0", "1", and so on) to its level key.
answers.<question>.probabilities For a choice or score question, the probability of each option or level, keyed by your option or level keys.
answers.<question>.confidence For a choice or score question, the model's confidence in its answer. The min_confidence threshold is compared with this value.
selected_branch The node the decision selected. After an escalation, this value is the escalate node.
routed_branch The route or default node that actually ran. The value is empty when no route ran, for example after an escalation with escalation_mode: handoff.
escalated Set to true when the node escalated.
escalation_reason Either low_confidence or provider_error. The value is empty when the node did not escalate.
provider_error After a provider-failure escalation, the failure category: rate_limited, timeout, unreachable, invalid_response, input_too_large, or refused. The category never contains the provider's error text.
model The model version the provider served.
usage.input_tokens, usage.output_tokens The tokens the call used.
duration_ms How long the call took, in milliseconds.
images One entry for each image that Agent Mesh loaded for the call, with its artifact name, version (only when the reference named one), mime_type (the type after any downscaling), and size in bytes after downscaling. For a Jev model on the TypeSafe API, mime_type and bytes describe the image before Agent Mesh fits it to the TypeSafe limits. The field is present only when the node sent images.

Understanding Escalation

The escalation_mode field sets what an escalation does:

  • reroute (the default) — The escalate node answers the same route_on question, and its answer picks the route as if the model had answered confidently.

  • handoff — The escalate node's reply is the result. Agent Mesh skips every route and the default node. Use handoff when the escalate node finishes the work itself, for example when it answers the request or is a human-review step, and whenever the escalate node is also a route or the default node.

A low-confidence escalation keeps the model's answers in the decision node's output, so the escalate node can read them in either mode.

Routing on the Escalation Answer

With escalation_mode: reroute, the default, Agent Mesh reads the escalate node's answer from its output field named after the route_on question. When the escalate node is a decision node, Agent Mesh reads that node's own answer to a choice question of the same name. Every route and default node waits for the escalate node, so you don't have to list the escalate node in their depends_on:

nodes:
  - id: triage
    type: decision
    input:
      ticket: "{{workflow.input.ticket}}"
    questions:
      department:
        type: choice
        instructions: Which team should own this ticket?
        criteria:
          billing: Payment, invoice, or refund issues
          technical: Bugs, errors, or outages
          other: Anything else
    route_on: department
    routes:
      billing: billing_desk
      technical: tech_desk
    default: general_desk
    min_confidence: 0.8
    escalate: reclassify               # escalation_mode: reroute is the default
  - id: reclassify
    type: agent
    agent_name: ticket-reviewer
    depends_on: [triage]
    input:
      message: "Classify this ticket: {{workflow.input.ticket}}"
    instruction: "Return department: exactly one of billing, technical, or other."
    output_schema_override:             # declares the field named after route_on
      type: object
      properties:
        department:
          type: string
          enum: [billing, technical, other]
      required: [department]
  - id: billing_desk
    type: agent
    agent_name: billing-desk
    depends_on: [triage]
    input:
      message: "{{workflow.input.ticket}}"
  - id: tech_desk
    type: agent
    agent_name: tech-desk
    depends_on: [triage]
    input:
      message: "{{workflow.input.ticket}}"
  - id: general_desk
    type: agent
    agent_name: general-desk
    depends_on: [triage]
    input:
      message: "{{workflow.input.ticket}}"

A confident answer still routes directly and skips reclassify. An escalation waits for reclassify, then runs the route for its answer, or the default node for an option with no route. If the escalate node is skipped, no route runs. The routed_branch output field records the route that ran.

The answer of the escalate node must exactly match one of the route_on options. Matching is case-sensitive and ignores only surrounding whitespace, so Billing does not match billing. An answer that is missing, is not a string, or is not an option fails the escalate node. Declare the field in the agent's output schema as a required enum of exactly the route_on options, because Agent Mesh does not check the enum before a run.

With escalation_mode: reroute, the workflow fails to load unless all of the following are true:

  • The escalate node is an agent, workflow, tool, or decision node that runs in the main flow, not as the node that a loop or map node runs.

  • If the escalate node is a decision node, it asks a choice question named after route_on, offers only options from the route_on question, and does not escalate itself.

  • No other node routes to the escalate node, and no route or default is the escalate node itself.

  • The escalate node does not depend, directly or through other nodes, on a route or default node, because those nodes wait for it.

Each of these errors names escalation_mode: handoff as the alternative.

Earlier versions of Agent Mesh used an escalation_choice template to route on the escalation answer. A workflow that still sets escalation_choice fails to load. The sam config plan command reports the field as an error when the plan creates or changes the workflow, and as a warning when the workflow is unchanged. Remove the field. With escalation_mode: reroute, the default, Agent Mesh reads the answer from the escalate node's output field named after the route_on question. To have the escalate node end the routing instead, set escalation_mode: handoff.

Handling Provider Failures

Agent Mesh stops a decision call, including its retries of transient failures, when it reaches the workflow's decision.timeout_seconds or 90% of the node's timeout, whichever comes first. Loading and downscaling the node's images count against the same 90% of the node's timeout. A stopped call is a timeout failure.

With on_provider_error: fail, the default, a failed decision call fails the node. With on_provider_error: escalate, the node escalates instead in the following cases:

  • The failure is transient. A transient failure is a timeout, a rate limit, an unreachable provider, a provider server error, or an invalid response.

  • The input or the images are too large for the decision model or for the decision block's limits. An LLM agent can usually still handle a large input.

  • An OpenAI decision model refuses to answer a question.

If an OpenAI decision model refuses any one question, the whole call fails with the refused category, and the node gets no answers. Agent Mesh never logs or returns the refusal text, because the refusal text can repeat the node's input. The error names only the question.

The following failures always fail the node:

  • Rejected credentials

  • Other requests that the provider rejects

  • An OpenAI account with no credits or quota left

  • An API endpoint whose host does not resolve or whose certificate is not trusted

  • A model_alias that does not name a decision model

After a provider-failure escalation, the decision node's answers output is empty. A node that reads {{<node-id>.output.answers...}} after such an escalation fails, and under the default fail_fast: true the whole run fails. With on_provider_error: escalate, read answers only in the following nodes, which run only when the model answered:

  • a route or default node of a decision node that sets escalation_mode: handoff, if it is not also the escalate node

  • a node reached only through one of those route or default nodes

In the workflow's output_mapping, such a reference does not fail the run. Agent Mesh leaves it unresolved as literal text.

Troubleshooting

The following sections describe common problems with decision nodes and how to resolve them.

Decision Node Fails with a Feature-Disabled Error

The run fails when it reaches the decision node, and the error says that the workflow_decision_node feature is disabled. An administrator disabled decision nodes, so the workflow loads but sends nothing to the provider.

  • Ask your administrator to remove the SAM_FEATURE_WORKFLOW_DECISION_NODE=false setting, or set it to true, and restart Agent Mesh.

  • Otherwise, replace the decision node with a classify agent and a switch node.

Decision Node Fails with an OpenAI Quota Error

The run fails at the decision node with a config failure, and the error says that the OpenAI account has no credits or quota left. Agent Mesh does not retry or escalate this failure, even with on_provider_error: escalate. Every later call fails the same way until the account has credits again.

  1. Add credits to the OpenAI account or raise its quota.

  2. On the Models page, edit the decision model and click Test Connection to confirm the fix.

  3. Run the workflow again.

Decision Node Fails with a Refused Error

The run fails at the decision node with a refused failure, and the error names the question that an OpenAI decision model refused to answer.

  • Reword or narrow the question, or pass the node fewer input fields.

  • To route refused runs elsewhere instead of failing, set on_provider_error: escalate and name an escalate node.

Saving or Deploying the Workflow Fails with a Decision Model Error

Agent Mesh rejects the workflow, and the error names the decision block. The block is missing model_alias, names a model that does not exist or has no API key, names a chat model, or carries an inline api_base, api_key, or model.

  • Run sam config pull --only model --url <platform-url> -o ./pulled and confirm that the alias exists with modelType: decision.

  • Register a decision model if none exists, and set decision.model_alias to its alias. Remove any inline endpoint or key from the block.

Workflow Fails to Load

The workflow deploys but does not start, and the error names a decision node. The following table lists the most common causes and their fixes.

Cause Fix
A route_on option has no route, and the node has no default Add a route for that option, or set default.
The decision node sets retry_strategy Remove it. The decision call already retries transient provider failures within timeout_seconds.
A score question's criteria are written as a map Rewrite them as a list of single-entry level: description items, lowest level first.
The route_on field names a question that is not a choice question Point route_on at a choice question.
An escalate node is set, but neither min_confidence nor on_provider_error: escalate can reach it Set one of them, or remove escalate.
The node still sets escalation_choice Remove it. The escalation_mode field, reroute by default, replaces it.
The escalate node cannot answer a decision node that reroutes Meet the rules in Routing on the Escalation Answer, or set escalation_mode: handoff.

To find and fix the cause:

  • Read the error, which names the node and the field, and correct that field.

  • For an error in a single field, such as an option with no route or score criteria written as a map, the sam config plan command prints the same error as a warning before you apply. Errors in how nodes connect appear only when the workflow deploys. These errors include violations of the rules in Routing on the Escalation Answer. A leftover escalation_choice field fails the plan when the plan creates or changes the workflow.

  • Run sam config schema show workflow --type decision to check the field names.

Escalated Runs Fail

Runs that the model answers confidently succeed, but runs that escalate fail. Either a node reads answers after a provider-failure escalation, or, with escalation_mode: reroute, the escalate node returns an answer that is not one of the route_on options.

  • Search the workflow for {{<node-id>.output.answers and move each reference into a node that runs only when the model answered, or set on_provider_error: fail.

  • Check that the escalate agent declares a field named after the route_on question as a required enum of exactly the route_on options.

  • If the escalate node answers the request itself rather than choosing a route, set escalation_mode: handoff.

Routing Changes Without a Workflow Change

Runs start choosing different routes for similar input although nobody changed the workflow. The Model Name of a TypeSafe decision model is most likely an alias such as jev-latest, which the provider can move to a newer model version.

  • Edit the decision model and set Model Name to a pinned version, such as jev-1.13.0.

  • Check that the decision node does not set its own model field to an alias, because that field overrides the decision model's Model Name.

  • Read the model field of the decision node's output to see which version served a run.

Cannot Rename a Decision Model

Agent Mesh refuses to rename a decision model and names the workflows that use it. A workflow's decision.model_alias names the model in its saved version, in the version still deployed, or in both.

  • Point each named workflow at another decision model and redeploy it, then rename the model.

Workflow Stops After Its Decision Model Is Deleted

A workflow that was deployed is no longer deployed, and the decision model that its decision.model_alias names no longer exists. When you delete a decision model, Agent Mesh undeploys every deployed workflow that names it. The workflow's saved version still names the deleted alias, so if you redeploy the workflow without changing it, its decision node fails when the workflow runs.

  • Point the workflow at another decision model, then redeploy it.

Pruned Decision Model Is Not Deleted

The sam config apply --prune command reports that the deletion of a decision model failed, and the error names a workflow. The command does not undeploy a workflow to delete a model, so it keeps any decision model that a workflow still names. A workflow that you point at a new decision model and apply with --no-deploy still runs on the old model, and a later apply skips that workflow while its file is unchanged.

  • Redeploy each named workflow on another decision model, then run sam config apply --prune again.

Next Steps

You have a workflow that classifies its input with a decision model and routes on the answer. To manage the decision model it calls, see Configuring Models.