{"slug":"agents-context-builder","title":"Context Builder — Multi-Step Guided Flows in Python Agents SDK","tags":["signalwire","agents-sdk","context-builder","multi-step","guided-flow"],"agent_summary":"Use ContextBuilder when an AI agent needs structured, step-by-step conversations. Up to 50 contexts per builder, 100 steps per context. Covers single-context steps, multi-context with cross-context transitions, system_prompt overrides, and create_simple_context.","trigger_phrases":["ContextBuilder Python","multi-step AI agent flow","guided conversation SignalWire","add_step set_step_criteria","set_valid_contexts","create_simple_context"],"runnable":true,"markdown":"\n# Context Builder — Multi-Step Guided Flows\n\nUse `ContextBuilder` when an AI agent needs structured, step-by-step conversations rather than free-form prompting. Each context has its own system prompt and a chain of steps. Steps gate progression with criteria, allowed tools, and valid next-steps.\n\nLimits: **50 contexts per builder, 100 steps per context.**\n\n## Single context, sequential steps\n\n```python\nfrom signalwire import AgentBase, FunctionResult\n\nclass OrderAgent(AgentBase):\n    def __init__(self):\n        super().__init__(name=\"order-agent\")\n        self.add_language(\"English\", \"en-US\", \"rime.spore\")\n        self.prompt_add_section(\"Role\", \"You help customers place orders.\")\n\n        contexts = self.define_contexts()\n        order = contexts.add_context(\"default\")\n\n        order.add_step(\"get_item\") \\\n            .set_text(\"Ask what item they want to order.\") \\\n            .set_step_criteria(\"Customer has specified an item\") \\\n            .set_valid_steps([\"get_quantity\"])\n\n        order.add_step(\"get_quantity\") \\\n            .set_text(\"Ask how many they want.\") \\\n            .set_step_criteria(\"Customer has specified a quantity\") \\\n            .set_valid_steps([\"confirm\"])\n\n        order.add_step(\"confirm\") \\\n            .set_text(\"Confirm order details and thank them.\") \\\n            .set_step_criteria(\"Order confirmed\") \\\n            .set_end(True)\n```\n\nThe agent advances when `step_criteria` is satisfied. `set_valid_steps([...])` limits which steps are reachable next; out-of-list jumps are blocked.\n\n## Multi-context flow with cross-context transitions\n\n```python\ncontexts = self.define_contexts()\n\n# Triage / menu context\nmain = contexts.add_context(\"default\")\nmain.add_step(\"menu\") \\\n    .set_text(\"Ask whether they need sales, support, or billing.\") \\\n    .set_functions(\"none\") \\\n    .set_valid_contexts([\"sales\", \"support\", \"billing\"])\n\n# Sales context\nsales = contexts.add_context(\"sales\")\nsales.set_system_prompt(\"You are a friendly sales representative.\")\nsales.add_step(\"qualify\") \\\n    .set_text(\"Understand what product the caller is interested in.\") \\\n    .set_functions([\"check_inventory\", \"get_pricing\"]) \\\n    .set_valid_steps([\"close\"])\nsales.add_step(\"close\") \\\n    .set_text(\"Close the sale or schedule a follow-up.\") \\\n    .set_valid_contexts([\"default\"])\n\n# Support context\nsupport = contexts.add_context(\"support\")\nsupport.set_system_prompt(\"You are a tier-2 support engineer.\")\nsupport.add_step(\"triage\").set_text(\"Get problem details, system info, and urgency.\")\n\n# Billing context\nbilling = contexts.add_context(\"billing\")\nbilling.set_system_prompt(\"You are a billing specialist. Verify account before answering.\")\nbilling.add_step(\"verify\").set_text(\"Ask for the last four digits of the account.\")\n```\n\nCrossing contexts requires `set_valid_contexts([...])` on the source step. The SDK blocks the transition otherwise.\n\n## Step configuration table\n\n| Setter | Purpose |\n|---|---|\n| `.set_text(\"...\")` | The step's prompt instruction (what the AI should do in this step). |\n| `.set_step_criteria(\"...\")` | The exit condition. The AI evaluates whether this is met. |\n| `.set_valid_steps([...])` | Whitelist of step names reachable from here. |\n| `.set_valid_contexts([...])` | Whitelist of contexts reachable from here. |\n| `.set_functions([...])` | SWAIG functions usable inside this step. `\"none\"` disables tools entirely. |\n| `.set_end(True)` | Mark this as the terminal step of the context. |\n\n## Context configuration\n\n| Setter | Purpose |\n|---|---|\n| `.set_system_prompt(\"...\")` | Override the agent's base prompt for this context. |\n| `.add_step(\"name\")` | Add a new step. Returns the step builder for fluent chaining. |\n\n## `create_simple_context` — standalone, no builder\n\nFor one-off contexts without the full builder pattern:\n\n```python\nfrom signalwire import create_simple_context\n\nctx = create_simple_context()  # name defaults to \"default\"\nctx.add_step(\"greet\").set_text(\"Say hello to the caller.\")\nctx.add_step(\"help\").set_text(\"Ask how you can help today.\")\n```\n\nUse this when you have a single context and don't need cross-context transitions.\n\n## Triggering transitions from SWAIG handlers\n\nA SWAIG function can advance the flow programmatically:\n\n```python\nfrom signalwire import FunctionResult\n\n@agent.tool(description=\"Caller wants sales\")\ndef route_to_sales(args, raw_data=None):\n    return FunctionResult(\"Connecting you to sales.\").swml_change_context(\"sales\")\n\n@agent.tool(description=\"Move to confirmation step\")\ndef proceed_to_confirm(args, raw_data=None):\n    return FunctionResult(\"Let me confirm your order.\").swml_change_step(\"confirm\")\n```\n\n`.swml_change_step(name)` jumps within the active context. `.swml_change_context(name)` jumps to a different context (must be allowed via `set_valid_contexts`).\n\n## Context vs free-form prompting — when to use which\n\n| Pattern | Use this |\n|---|---|\n| Open-ended chat with optional tools | Free-form prompt (no ContextBuilder). |\n| 2-4 step ordered process (intake, quote, schedule) | Single context with sequential steps. |\n| Multi-persona handoff (triage → specialist) | Multi-context with `set_valid_contexts`. |\n| Stateful flow with strict gating | ContextBuilder with `step_criteria` and `set_valid_steps`. |\n\n## Anti-patterns\n\n- Defining 30+ steps in one context — split into multiple contexts.\n- Skipping `set_valid_steps` or `set_valid_contexts` — the AI may jump anywhere, defeating the structure.\n- Setting `set_functions(\"none\")` in a step that needs tools — the AI will improvise instead of calling SWAIG.\n- Re-using step names across contexts — fine, but confuses logs. Prefix with context name (`sales_qualify`, `support_qualify`).\n- Manually editing `prompt.text` and a context's `system_prompt` to overlap — the context prompt wins inside that context.\n\n## See also\n\n- [SWML context switch](/topic/swml-context-switch)\n- [POM Builder for structured prompts](/topic/agents-pom-builder)\n- [Python Agents SDK](/topic/signalwire-python-agents-sdk)\n- [SWAIG functions](/topic/swaig-functions)\n","html":"<h1>Context Builder — Multi-Step Guided Flows</h1>\n<p>Use <code>ContextBuilder</code> when an AI agent needs structured, step-by-step conversations rather than free-form prompting. Each context has its own system prompt and a chain of steps. Steps gate progression with criteria, allowed tools, and valid next-steps.</p>\n<p>Limits: <strong>50 contexts per builder, 100 steps per context.</strong></p>\n<h2>Single context, sequential steps</h2>\n<pre><code class=\"language-python\">from signalwire import AgentBase, FunctionResult\n\nclass OrderAgent(AgentBase):\n    def __init__(self):\n        super().__init__(name=\"order-agent\")\n        self.add_language(\"English\", \"en-US\", \"rime.spore\")\n        self.prompt_add_section(\"Role\", \"You help customers place orders.\")\n\n        contexts = self.define_contexts()\n        order = contexts.add_context(\"default\")\n\n        order.add_step(\"get_item\") \\\n            .set_text(\"Ask what item they want to order.\") \\\n            .set_step_criteria(\"Customer has specified an item\") \\\n            .set_valid_steps([\"get_quantity\"])\n\n        order.add_step(\"get_quantity\") \\\n            .set_text(\"Ask how many they want.\") \\\n            .set_step_criteria(\"Customer has specified a quantity\") \\\n            .set_valid_steps([\"confirm\"])\n\n        order.add_step(\"confirm\") \\\n            .set_text(\"Confirm order details and thank them.\") \\\n            .set_step_criteria(\"Order confirmed\") \\\n            .set_end(True)\n</code></pre>\n<p>The agent advances when <code>step_criteria</code> is satisfied. <code>set_valid_steps([...])</code> limits which steps are reachable next; out-of-list jumps are blocked.</p>\n<h2>Multi-context flow with cross-context transitions</h2>\n<pre><code class=\"language-python\">contexts = self.define_contexts()\n\n# Triage / menu context\nmain = contexts.add_context(\"default\")\nmain.add_step(\"menu\") \\\n    .set_text(\"Ask whether they need sales, support, or billing.\") \\\n    .set_functions(\"none\") \\\n    .set_valid_contexts([\"sales\", \"support\", \"billing\"])\n\n# Sales context\nsales = contexts.add_context(\"sales\")\nsales.set_system_prompt(\"You are a friendly sales representative.\")\nsales.add_step(\"qualify\") \\\n    .set_text(\"Understand what product the caller is interested in.\") \\\n    .set_functions([\"check_inventory\", \"get_pricing\"]) \\\n    .set_valid_steps([\"close\"])\nsales.add_step(\"close\") \\\n    .set_text(\"Close the sale or schedule a follow-up.\") \\\n    .set_valid_contexts([\"default\"])\n\n# Support context\nsupport = contexts.add_context(\"support\")\nsupport.set_system_prompt(\"You are a tier-2 support engineer.\")\nsupport.add_step(\"triage\").set_text(\"Get problem details, system info, and urgency.\")\n\n# Billing context\nbilling = contexts.add_context(\"billing\")\nbilling.set_system_prompt(\"You are a billing specialist. Verify account before answering.\")\nbilling.add_step(\"verify\").set_text(\"Ask for the last four digits of the account.\")\n</code></pre>\n<p>Crossing contexts requires <code>set_valid_contexts([...])</code> on the source step. The SDK blocks the transition otherwise.</p>\n<h2>Step configuration table</h2>\n<p>| Setter | Purpose |\n|---|---|\n| <code>.set_text(\"...\")</code> | The step's prompt instruction (what the AI should do in this step). |\n| <code>.set_step_criteria(\"...\")</code> | The exit condition. The AI evaluates whether this is met. |\n| <code>.set_valid_steps([...])</code> | Whitelist of step names reachable from here. |\n| <code>.set_valid_contexts([...])</code> | Whitelist of contexts reachable from here. |\n| <code>.set_functions([...])</code> | SWAIG functions usable inside this step. <code>\"none\"</code> disables tools entirely. |\n| <code>.set_end(True)</code> | Mark this as the terminal step of the context. |</p>\n<h2>Context configuration</h2>\n<p>| Setter | Purpose |\n|---|---|\n| <code>.set_system_prompt(\"...\")</code> | Override the agent's base prompt for this context. |\n| <code>.add_step(\"name\")</code> | Add a new step. Returns the step builder for fluent chaining. |</p>\n<h2><code>create_simple_context</code> — standalone, no builder</h2>\n<p>For one-off contexts without the full builder pattern:</p>\n<pre><code class=\"language-python\">from signalwire import create_simple_context\n\nctx = create_simple_context()  # name defaults to \"default\"\nctx.add_step(\"greet\").set_text(\"Say hello to the caller.\")\nctx.add_step(\"help\").set_text(\"Ask how you can help today.\")\n</code></pre>\n<p>Use this when you have a single context and don't need cross-context transitions.</p>\n<h2>Triggering transitions from SWAIG handlers</h2>\n<p>A SWAIG function can advance the flow programmatically:</p>\n<pre><code class=\"language-python\">from signalwire import FunctionResult\n\n@agent.tool(description=\"Caller wants sales\")\ndef route_to_sales(args, raw_data=None):\n    return FunctionResult(\"Connecting you to sales.\").swml_change_context(\"sales\")\n\n@agent.tool(description=\"Move to confirmation step\")\ndef proceed_to_confirm(args, raw_data=None):\n    return FunctionResult(\"Let me confirm your order.\").swml_change_step(\"confirm\")\n</code></pre>\n<p><code>.swml_change_step(name)</code> jumps within the active context. <code>.swml_change_context(name)</code> jumps to a different context (must be allowed via <code>set_valid_contexts</code>).</p>\n<h2>Context vs free-form prompting — when to use which</h2>\n<p>| Pattern | Use this |\n|---|---|\n| Open-ended chat with optional tools | Free-form prompt (no ContextBuilder). |\n| 2-4 step ordered process (intake, quote, schedule) | Single context with sequential steps. |\n| Multi-persona handoff (triage → specialist) | Multi-context with <code>set_valid_contexts</code>. |\n| Stateful flow with strict gating | ContextBuilder with <code>step_criteria</code> and <code>set_valid_steps</code>. |</p>\n<h2>Anti-patterns</h2>\n<ul>\n<li>Defining 30+ steps in one context — split into multiple contexts.</li>\n<li>Skipping <code>set_valid_steps</code> or <code>set_valid_contexts</code> — the AI may jump anywhere, defeating the structure.</li>\n<li>Setting <code>set_functions(\"none\")</code> in a step that needs tools — the AI will improvise instead of calling SWAIG.</li>\n<li>Re-using step names across contexts — fine, but confuses logs. Prefix with context name (<code>sales_qualify</code>, <code>support_qualify</code>).</li>\n<li>Manually editing <code>prompt.text</code> and a context's <code>system_prompt</code> to overlap — the context prompt wins inside that context.</li>\n</ul>\n<h2>See also</h2>\n<ul>\n<li><a href=\"/topic/swml-context-switch\">SWML context switch</a></li>\n<li><a href=\"/topic/agents-pom-builder\">POM Builder for structured prompts</a></li>\n<li><a href=\"/topic/signalwire-python-agents-sdk\">Python Agents SDK</a></li>\n<li><a href=\"/topic/swaig-functions\">SWAIG functions</a></li>\n</ul>\n"}