Skip to main content
Back to how-to guides Use this pattern when a specialist must do one bounded job, such as collecting a value, and the calling agent must then continue.

Concept

A returnable handoff sets RETURN: true on a HANDOFF route. The target agent becomes the child. The calling agent becomes the parent and waits. The child talks to the customer until it returns control. RETURN: false is the default. A route with no RETURN line is permanent. Write RETURN: true in lowercase. Values such as True or "true" read as false. EXPECT_RETURN is an alias for RETURN. Use a returnable handoff for a gate or a short job. See Switch the active agent for how HANDOFF changes the active agent. See Permanent handoff when the target keeps the conversation. See Use handoff instead of delegate to choose between HANDOFF and DELEGATE.

How the child returns

The child returns in one of three ways.

What the parent does after the return

ON_RETURN sets the parent behavior. It has these keys. Without ON_RETURN, a return that forwards a customer message replays that message. Otherwise the parent resumes and does not replay. Only mapped fields come back. A child field with no value is skipped. The parent must be allowed to write each MAP and SET target. If one target is not writable, the runtime applies none of the return effects. See Resume after a child or human agent completes for resume detail.

Minimal working example

The supervisor sends every request to Income_Capture_Agent until annual_income is set. The child gathers the value and completes. MAP copies the value to the supervisor. The supervisor then hands off for good. annual_income has one source: the GATHER field in the child. Offer_Agent is a short stand-in for your real specialist.

How it works

  1. annual_income IS NOT SET is true. The runtime checks this condition before the model runs. The supervisor hands off to the child.
  2. The supervisor waits. The child asks for the income and the customer answers.
  3. COMPLETE fires. The child returns. MAP copies annual_income to the supervisor.
  4. resume_intent replays the original customer message. The depth limit is one level.
  5. annual_income IS SET is true. The second route sends the conversation to Offer_Agent, and that route is permanent.
ON_FAILURE runs when the handoff fails before the child accepts it. The options are continue, ESCALATE, and RESPOND "message". The default is continue. It does not run when the child later fails on its own. HISTORY sets how much conversation the child receives. The default is full. Other values are auto, none, summary_only, and a last_n block. auto uses summary_only when SUMMARY has text. Otherwise it passes the last five messages. PASS lists the parent variables the child receives. SUMMARY states the reason for the handoff. Use IS SET and IS NOT SET for empty variables. The check != "" is true when the value is missing. A // in a WHEN outside quotes starts a comment.

Common variations

Continue without a replay

Use action: continue when the parent must not route again. SET adds a parent assignment after MAP. income_note is a parent MEMORY variable. Replace the first HANDOFF entry with this entry.
Declare income_note in the supervisor MEMORY. Without the SET line, the compiler reports W801 for income_note.

Choose the replay message and use the CONTEXT block

CONTEXT groups pass, summary, and history. Keys inside CONTEXT must be lowercase. Uppercase keys inside CONTEXT are ignored. Write a last_n history as an indented block. A one-line { mode: last_n } is ignored and falls back to full.

Name a return handler

A handler reuses return behavior across routes. A handler cannot combine RESPOND with RESUME_INTENT. The compiler reports HANDOFF_ON_RETURN_ACTION_AND_HANDLER. Add MAP on the route, or no value returns.

Let the child hand back to the waiting parent

A child that cannot serve a request hands off to its waiting parent. Add a HANDOFF to the parent in the child. Add it to Income_Capture_Agent in the minimal example.
A route to an agent that is already active is a cycle. The runtime refuses it with the code recursion. A route to a waiting parent is allowed.

Return to the supervisor when the topic changes

A reasoning child can return to the supervisor when the customer changes topic. EXECUTION.pipeline.parentReturn tunes this. minConfidence sets the match threshold, from 0 to 1. ignoreAmbiguous ignores ambiguous matches. The default for ignoreAmbiguous is false. A supervisor setting overrides the project setting. Add this block to the supervisor in the minimal example.

Verification

  1. Validate the project. Every TO target must exist. A MAP source that the child does not declare gives UNKNOWN_HANDOFF_RETURN_FIELD.
  2. Start a new session and send “I want a loan offer”. Expect Income_Capture_Agent to ask for the income. The handoff event shows returnExpected: true and reasonCode: handoff_return_expected.
  3. Send an income. Expect a handoff_return_handler event with mappedFields: ["annual_income"]. Expect a resume_intent event next.
  4. Expect a second handoff event to Offer_Agent with returnExpected: false and reasonCode: handoff_permanent.
  5. Read each handoff_completed event. It has outcome, durationMs, and, on a failure, cause and code. It can also have siblingIntentMs, the time a queued sibling request ran inside the attempt. That time is not in durationMs.

Production readiness checklist

  • Every TO target exists. Replace the stand-in agents with real agents.
  • Every MAP names a child field that the child sets and a parent variable the routes read.
  • The child has a COMPLETE condition or the built-in return action, so it cannot hold the conversation forever.
  • Each route sets ON_FAILURE on purpose.
  • Each PASS list holds only what the child needs.
  • Tests cover a new session, a returning customer with the value set, an unanswered gate, a child that hands back, and a refused route.
  • Trace checks cover handoff, handoff_return_handler, resume_intent, and handoff_completed.

Common mistakes

Troubleshooting