# Discord Source: https://docs.poly.ai/adk/community/discord We have a Discord server where ADK users and contributors hang out. Whether you're just getting started or building something advanced, it's the fastest way to get help, share ideas, and stay in the loop on what's new. [Join the ADK Discord](https://discord.gg/nzGcCt6SE) ## What you'll find there * **Help & troubleshooting** — get unstuck with setup, CLI commands, or resource configuration. * **Show & tell** — share agents you've built, interesting patterns, or cool integrations. * **Feature discussion** — chat about what you'd like to see next in the ADK. * **Announcements** — hear about new releases and breaking changes early. Come say hello — we'd love to hear what you're working on. # Common anti-patterns Source: https://docs.poly.ai/adk/concepts/anti-patterns This page collects common implementation mistakes that make agents harder to predict, harder to maintain, or more likely to behave incorrectly at runtime. The general rule is simple: keep prompts focused on conversation, keep Python focused on deterministic logic, and make control flow explicit. ## Flow navigation Flow functions must **always advance** the flow. See the [flows reference](/adk/reference/flows) for navigation methods including `flow.goto_step()`. A flow function should never leave the agent sitting in the same logical place without a clear next step. ### Avoid * returning from a flow function without changing step or flow * leaving navigation implicit * assuming the model will somehow recover the flow state on its own ### Prefer * `flow.goto_step(...)` * returning an explicit transition * making the next state obvious in code **A stuck flow is usually a control-flow bug** If a flow function does not move the agent forward, the conversation can become stuck in an invalid or confusing state. ## Metrics and logging Metrics and logs should capture important events, not generate noise. See the [functions reference](/adk/reference/functions) for `conv.log` and metrics APIs. ### Avoid * writing the same metric repeatedly in a loop * emitting metrics every turn without a clear reason * swallowing external API failures silently ### Prefer * `write_once=True` when an event should only be recorded once * logging meaningful outcomes around API calls and validation failures * using `conv.log.info(...)`, `conv.log.warning(...)`, and `conv.log.error(...)` to make important behavior visible **Good logging explains the shape of the call** Logs and metrics should help you understand what happened in the conversation, not bury you in repetitive trivia. ## Logic in prompts vs code Do not put deterministic branching logic into prompts or YAML instructions. Prompts are for conversational behavior. Python is for comparisons, routing, validation, and state-driven decisions. ### Wrong Encoding branching logic in prompts, for example: ```text theme={"theme":{"light":"github-light","dark":"github-dark"}} If $x == 0 do A, else do B. ``` ### Right Implement the check in Python and transition to the correct step or flow explicitly. ### Why this matters When branching logic is buried in prompts: * behavior becomes harder to test and verify * routing becomes harder to debug * deterministic behavior becomes dependent on how the model interprets the instruction Use prompts for collecting information, presenting information, and guiding conversational style. Use Python for comparisons, routing, validation, retries, and state-based decisions. ## “Anything else?” and exiting flows Do not create a dedicated **“Anything else?”** step just to wrap up a flow. When the flow is finished, exit the flow and return the appropriate closing prompt there. ### Avoid * adding a special cleanup step whose only purpose is to ask whether the user needs anything else * calling `conv.exit_flow()` and then also navigating somewhere else ### Wrong ```python theme={"theme":{"light":"github-light","dark":"github-dark"}} conv.exit_flow() return {"transition": {"goto_flow": "Another Flow"}} ``` or ```python theme={"theme":{"light":"github-light","dark":"github-dark"}} conv.exit_flow() conv.goto_flow("Another Flow") ``` In both cases, the navigation overrides the exit. ### Right Use **one** of these approaches: * exit the flow and return the closing content * navigate to another step or flow Do not do both. **Exit and navigation are mutually exclusive** If you call `conv.exit_flow()` and then also transition elsewhere, the transition wins. ## `end_turn=False` `end_turn=False` is easy to misuse. It should only be used when the agent speaks and then immediately performs another action in the **same turn**, without waiting for user input. ### Wrong Using `end_turn: False` after the agent asks a question and is waiting for a reply. That produces awkward control flow, because the question should simply be part of the normal utterance. ### Right Use `end_turn: False` only when the agent must continue immediately, for example: * the agent says something * then immediately calls a function in the same turn Example pattern: ```text theme={"theme":{"light":"github-light","dark":"github-dark"}} “Your balance is X.” → immediately call `balance_informed` ``` If the user is expected to answer, put the full question in the utterance and let the turn end normally. ## Don't copy project directories between projects Copying an existing ADK project directory and pointing it at a different Agent Studio project will cause push failures. The `.agent_studio_config` file contains resource IDs from the source project, and platform-provisioned resources (voice settings, chat settings, personality, role, ASR settings) cannot be created through the ADK. ### Wrong Copying a project directory, updating `project.yaml` with new IDs, then running `poly push` against a different project. ### Right ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} poly init poly pull ``` Start every new project with `poly init` and `poly pull`. Copy individual resource files if you need to reuse them — never copy `.agent_studio_config` or the whole directory. ## Quick reference | Anti-pattern | Better approach | | ------------------------------------------------ | ---------------------------------------------------------------------------------------------- | | Flow function returns without navigation | Always call `flow.goto_step(...)` or return a transition | | Metric written repeatedly in a loop | Use `write_once=True` where appropriate | | Branching logic in prompts | Put routing logic in Python | | Dedicated “Anything else?” step | Exit the flow and return the closing prompt directly | | `conv.exit_flow()` plus navigation | Choose exit **or** transition, not both | | `end_turn=False` while waiting for a user answer | Only use it when the agent continues immediately in the same turn | | Copying a project directory to a new project | Use `poly init` + `poly pull` for the target project; copy individual resource files as needed | ## Design principle * make control flow explicit * keep prompts conversational * keep code deterministic * prefer simple, testable paths over clever prompt tricks ## Related pages Navigation methods, step transitions, and flow functions. Logging, metrics, conv APIs, and lifecycle hooks. Personality, role, and rules — the global prompt layer. How the ADK maps resources to the local filesystem. # Multi-user workflows and guardrails Source: https://docs.poly.ai/adk/concepts/multi-user-and-guardrails The **PolyAI ADK** was designed with **multi-user collaboration** in mind. It allows multiple developers to work on the same Agent Studio project while preserving the same platform guardrails that prevent incompatible or invalid changes from being pushed. ## Why this matters Local workflows are only useful if teams can collaborate safely. Without guardrails, local editing quickly becomes chaotic: * one developer overwrites another’s work * invalid resources are pushed upstream * branch state becomes unclear * review becomes difficult The ADK is designed to reduce those risks by combining local editing with validation, branching, and synchronization back to Agent Studio. Developers can create and switch branches for isolated work. Local changes can be checked before they are sent back to Agent Studio. Changes can be compared and shared for review before merge. The CLI validates that pushed changes remain valid for the project. ## Branch workflow The collaborative workflow follows the standard [CLI working pattern](/adk/reference/cli#working-pattern), with each developer working on their own branch. Create a branch with [`poly branch create`](/adk/reference/cli#poly-branch-create), edit and push, then merge with [`poly branch merge`](/adk/reference/branch_merge) or through the Agent Studio UI when ready. ## Validation as a guardrail Run `poly validate` before pushing to catch issues locally, before they reach Agent Studio. Examples of what validation protects against include: * invalid resource structures * missing required values * incompatible references between resources * malformed configuration files **Validate before pushing** In collaborative workflows, treat `poly validate` as a standard step in the editing cycle, not an optional one. ## Pulling and merge behavior If work is done to your branch in Agent Studio and you want to bring those changes into your local copy, you can run [`poly pull`](/adk/reference/cli#poly-pull): ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} poly pull ``` If the pulled changes conflict with your own local edits, the ADK will merge them and surface merge markers where conflicts occur. The local workflow is not isolated from Agent Studio UI work - both sides affect branch state. Keep that in mind when collaborating. ## Review workflow When changes are ready for review, generate a review artifact with [`poly review create`](/adk/reference/cli#poly-review): ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} poly review create ``` Use this to compare: * local changes against the remote project * one branch against another * a feature branch against `main` or `sandbox` A GitHub environment token is required. The output lets reviewers inspect changes without access to your local filesystem. ## Guardrails inherited from Agent Studio The ADK is intentionally aligned with the Agent Studio platform. That means it is not just a free-form local editing tool. It is structured so that developers should not be able to push changes that are incompatible with the project as defined by the platform. In practice, this means: * project resources must still conform to Agent Studio expectations * references between resources must remain valid * branch merges happen either through the CLI ([`poly branch merge`](/adk/reference/branch_merge)) or in Agent Studio * deployment still happens through Agent Studio **Local flexibility, platform constraints** The ADK expands where and how developers can work, but it does not remove the constraints that keep projects valid and deployable. ## Best practices for teams When multiple developers are working on the same project, a few habits make the workflow much smoother: * create a branch before making substantial changes * pull the latest changes before starting work * validate locally before pushing * use `poly diff` and `poly status` frequently * review branch output before merging * keep resource names stable and descriptive ## Common failure modes Common collaboration problems usually come from process, not tooling. Watch out for: * editing directly on the wrong branch * forgetting to pull before starting work * pushing without validation * mixing large unrelated changes into one branch * treating Agent Studio UI edits and local edits as if they cannot collide ## Related pages Learn how the local project structure maps to Agent Studio. Review the commands used for branching, validation, diffing, and review. # Resource architecture Source: https://docs.poly.ai/adk/concepts/resource-architecture

Each resource type in the ADK has a specific purpose. This page helps you decide where to put new content or logic before you write it.

Choosing the wrong resource type is one of the most common sources of hard-to-debug agent behavior. The right question is not "how do I add this?" but "what kind of thing is this?" ## The core split The ADK separates two concerns: Information the agent should retrieve and communicate. Lives in topics. What the agent should do, when, and how. Lives in rules, flows, and functions. Keep these separate. Mixing factual content and behavioral instructions in the same resource makes both harder to maintain and harder to reason about. ## Decision table | You are adding... | Use | | --------------------------------------------------------------------- | ----------------------------------------------------- | | A new FAQ, policy, or factual answer | Topic (`topics/`) | | A global behavioral rule (always do X, never do Y) | `agent_settings/rules.txt` | | Structured data collection from the caller | Entity + flow | | Deterministic branching or routing logic | Function (`functions/`) | | Call initialization — routing, variant selection, reading SIP headers | `functions/start_function.py` | | A multi-step guided conversation | Flow (`flows/`) | | Reusable SMS message content | SMS template (`config/sms_templates.yaml`) | | Per-site or per-location configuration | Variant attributes (`config/variant_attributes.yaml`) | | Agent identity and tone | `agent_settings/personality.yaml` and `role.yaml` | ## Rules vs topics vs functions These three resources overlap in ways that create confusion. **`rules.txt`** is for durable, global behavioral instructions that apply on every turn — for example, "always confirm the booking reference before making changes" or "do not discuss competitor products." Rules are not retrieved via RAG; they are always present in the prompt. **Topics** are for subject-specific knowledge that should only appear when relevant. The agent retrieves the right topic when the caller asks about that subject. Put factual content and the behavioral instructions for that specific subject area in the topic, not in rules. **Functions** are for anything that requires a deterministic outcome — checking a value, calling an API, routing to a different flow, or making a decision that must not be left to the model. **A useful test** If the instruction is always true, it belongs in rules. If it is only relevant when someone asks about a specific subject, it belongs in a topic. If it requires a comparison, calculation, or API call, it belongs in a function. ## Common mistakes ### Putting behavioral logic in topic content The `content` field of a topic is retrieved by RAG and made available as context. It should contain facts, not instructions. ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} # Wrong — behavioral logic in content content: |- If the caller asks to cancel, transfer them to the cancellations queue. # Right — behavioral logic in actions actions: |- If the caller asks to cancel, use {{ho:cancellations}} to transfer them. ``` ### Putting facts in rules `rules.txt` is not a good place for factual content because it is always present in the prompt, consuming context space even when the information is not relevant to the current turn. Keep facts in topics where they are only retrieved when needed. ### Writing prose conditionals in rules or topics Logic like "if `{{vrbl:caller_number}}` is available, do X; otherwise do Y" is unreliable when the variable is empty. The model cannot reliably detect an empty variable from prompt text alone. Write the branch in Python instead. ## Where `start_function` fits `start_function.py` runs once at call start, before the first user input. It is the right place for: * reading SIP headers and setting variant routing * initializing state variables the rest of the conversation depends on * making a fast API call to preload caller context It is **not** the right place for logic that only applies mid-conversation, or for slow API calls that would delay the greeting. ## Related pages Common mistakes to avoid when building flows, writing prompts, and handling control flow. Full reference for topic structure, content, and actions. Personality, role, and rules — the global prompt layer. Python functions for deterministic logic and lifecycle hooks. How topics are retrieved, ranked, and used by the platform — including RAG mechanics and topic types. Lifecycle hook reference — when it runs, what it can read, and common initialization patterns. Alternative to Managed Topics for large, unstructured content sets — help articles, PDFs, FAQs. Per-site configuration using variant attributes — how routing and attribute lookup work. # Working locally Source: https://docs.poly.ai/adk/concepts/working-locally With the ADK, you work on Agent Studio projects from your local machine instead of exclusively through the browser. Your local filesystem becomes your primary editing surface. You can: * edit agent resources directly * review changes with Git-style workflows * validate changes before pushing * work in **VS Code** or **Cursor** with the [PolyAI ADK extension](/adk/reference/tooling#polyai-adk-extension-for-vs-code-and-cursor), or pair the ADK with [AI coding agents](/adk/reference/tooling#claude-code) such as **Claude Code** * test and iterate before merging in Agent Studio Agent configuration lives on disk in a structured project directory. Pull, edit, validate, push, and review changes using the `poly` CLI. Agent Studio remains the source of deployment and preview. Branches can be merged with [`poly branch merge`](/adk/reference/branch_merge) from the CLI or through the Agent Studio UI. The local workflow works naturally with editors, terminals, and AI-assisted coding tools. ## What a local project contains Each local ADK project represents an Agent Studio project. A project can define a voice or webchat agent, and its runtime behavior is controlled by resources such as flows, functions, topics, settings, and configuration files. A typical project structure looks like this: ```text theme={"theme":{"light":"github-light","dark":"github-dark"}} // ├── _gen/ # Generated stubs - do not edit ├── agent_settings/ # Agent identity and behavior │ ├── languages.yaml # Optional │ ├── personality.yaml │ ├── role.yaml │ ├── rules.txt │ ├── safety_filters.yaml # Optional │ └── experimental_config.json # Optional ├── config/ # Configuration │ ├── entities.yaml # Optional │ ├── handoffs.yaml # Optional │ ├── sms_templates.yaml # Optional │ ├── translations.yaml # Optional │ └── variant_attributes.yaml # Optional ├── context/ # Optional - document context files │ └── {document_name}.md ├── voice/ # Voice channel settings │ ├── configuration.yaml │ ├── safety_filters.yaml # Optional │ ├── speech_recognition/ │ └── response_control/ ├── chat/ # Chat channel settings │ ├── configuration.yaml │ └── safety_filters.yaml # Optional ├── flows/ # Optional - flow definitions ├── functions/ # Global functions ├── topics/ # Knowledge base topics ├── test_suite/ # Optional - simulated conversation tests └── project.yaml # Project metadata ``` **Generated files** Files under `_gen/` are generated stubs and should not be edited directly. ## How local work maps to Agent Studio The ADK does not replace Agent Studio. It acts as the local development layer around it. A typical flow looks like this: 1. initialize or pull a project locally 2. create or switch to a branch 3. edit resources on disk 4. validate and inspect changes 5. push changes back to Agent Studio 6. test and review the branch in Agent Studio 7. merge when ready This means the local filesystem becomes your main editing surface, while Agent Studio remains the place where work is previewed, reviewed, and deployed. ## Standard CLI workflow See the [CLI working pattern](/adk/reference/cli#working-pattern) for the full step-by-step. The short version: init → pull → branch → edit → validate → push → review → merge → chat. **Run commands from the project folder** ADK commands are expected to be run from within the local project directory. If needed, use the `--path` flag to point to a project explicitly. ## Resource reference syntax Many ADK resources support references to other resources or values. These placeholders are used in prompts, rules, topic actions, and related text fields: | Syntax | Resolves to | Common use | | ------------------------------ | ------------------------------------------------- | -------------------------------------------- | | `{{fn:function_name}}` | [Global function](/adk/reference/functions) | Rules, topic actions, advanced step prompts | | `{{ft:function_name}}` | [Flow transition function](/adk/reference/flows) | Advanced step prompts within the same flow | | `{{entity:entity_name}}` | [Collected entity value](/adk/reference/entities) | Flow prompts | | `{{attr:attribute_name}}` | [Variant attribute](/adk/reference/variants) | Rules, prompts, greetings, personality, role | | `{{twilio_sms:template_name}}` | [SMS template](/adk/reference/sms) | Rules, topic actions | | `{{ho:handoff_name}}` | [Handoff destination](/adk/reference/handoffs) | Rules | | `{{vrbl:variable_name}}` | [State variable](/adk/reference/variables) | Prompts, topic actions, SMS templates | These references let settings, prompts, and behaviors point to resources by name rather than repeating hard-coded values. **A Git-like workflow for Agent Studio** Think of the ADK as a synchronization layer between your local files and the Agent Studio platform. ## Related pages Review the main ADK commands and their purpose. Write simulated conversation test cases under `test_suite/`. Learn how branching, validation, and review fit into collaborative work. # Confirm caller ID before sending SMS Source: https://docs.poly.ai/adk/examples/confirm-caller-id-before-sms This pattern covers a very common voice + SMS flow: the agent has the caller's number from the inbound call, confirms the last four digits with the caller, and sends an SMS to that number. If the caller's number is not available (for example, in a chat session), the agent asks for it instead. ## Files involved ```text theme={"theme":{"light":"github-light","dark":"github-dark"}} functions/start_function.py ← stash caller_number at call start functions/caller_number_confirmed.py ← check presence, compare last four config/sms_templates.yaml ← the SMS template (alongside other templates) topics/Send Booking Link.yaml ← topic that triggers the flow ``` ## start\_function.py Stash the raw `caller_number` at call start so it is available throughout the conversation. **`start_function.py` may already exist** If your project was set up via Quick Agent Setup, `start_function.py` likely already contains initialization logic. Add the `conv.state.caller_number` line to the existing function rather than replacing the file. ```python theme={"theme":{"light":"github-light","dark":"github-dark"}} from _gen import * # def start_function(conv: Conversation): conv.state.caller_number = conv.caller_number return str() ``` ## caller\_number\_confirmed.py Called when the caller has confirmed (or declined) sending the SMS. Branches on whether `caller_number` is present, then validates the last-four match before sending. ```python theme={"theme":{"light":"github-light","dark":"github-dark"}} from _gen import * # @func_description( "Called after the caller confirms or provides a number for us to send the SMS to. " "confirmed: True if they confirmed the number on file, False if they want to use a different one. " "provided_number: the number they provided if confirmed is False." ) @func_parameter("confirmed", "Whether the caller confirmed the number on file") @func_parameter("provided_number", "The number the caller provided if confirmed is False; pass an empty string if confirmed is True") def caller_number_confirmed(conv: Conversation, confirmed: bool, provided_number: str): if confirmed and conv.state.caller_number: to_number = conv.state.caller_number elif provided_number: to_number = provided_number else: return "Ask the caller to provide the number where we should send the link." conv.send_sms_template(to_number=to_number, template="booking_link") return "Tell the caller the link has been sent and ask if there is anything else you can help with." ``` ## Send Booking Link.yaml ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} enabled: true example_queries: - Can you send me the booking link? - Can you text me a link? - Send me the details by text - I'd like a link to book online content: |- The booking link is available by SMS. actions: |- ## If the caller's number is available Tell the caller you can send the link to the number ending in the last four digits of $caller_number. Ask them to confirm or provide a different number. Use {{fn:caller_number_confirmed}} once they respond. ## If the caller's number is not available Ask the caller for the number where we should send the link. Use {{fn:caller_number_confirmed}} with confirmed=False and the number they provide. ``` **Prose conditionals and empty variables** The topic actions above use a natural-language conditional on `$caller_number`. This works when the variable is populated, but can behave unreliably if the variable is always empty (for example, in chat). If you need strict branching, move the presence check into `caller_number_confirmed` and call it unconditionally from the topic action. ## SMS template (config/sms\_templates.yaml) All SMS templates are defined in a single `config/sms_templates.yaml` file under the `sms_templates` key. ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} sms_templates: - name: booking_link text: "Here's your booking link: https://book.example.com" env_phone_numbers: sandbox: "" pre_release: "" live: "" ``` ## Related pages Reference for SMS template structure and variable substitution. How `conv.state` variables are discovered and referenced. Why prose conditionals on variable presence are unreliable. Full reference for `conv.caller_number`, `conv.send_sms_template`, and all other `conv` attributes. When start function runs, what it can access, and how to use it for initialization. Configuring SMS channels, sender numbers, and template structure. # Examples Source: https://docs.poly.ai/adk/examples/index Small, focused examples that can be copied and adapted when building with the **PolyAI ADK**. Validate the caller's identity before sending an SMS confirmation. Customize the agent's closing message based on variant attributes. Send an SMS with a link and fall back to a live transfer if it fails. # SMS link with transfer fallback Source: https://docs.poly.ai/adk/examples/sms-or-transfer-fallback A common pattern in voice agents: offer to send the caller a link by SMS, but transfer to a live agent if SMS isn't an option or the caller asks for it. The decision lives in a function so it is deterministic — not subject to LLM interpretation. ## Files involved ```text theme={"theme":{"light":"github-light","dark":"github-dark"}} functions/start_function.py ← stash caller_number functions/send_link_or_transfer.py ← SMS or handoff decision config/sms_templates.yaml ← SMS template (alongside other templates) config/handoffs.yaml ← transfer destination (alongside other handoffs) topics/Get Booking Link.yaml ← triggers the pattern ``` ## send\_link\_or\_transfer.py ```python theme={"theme":{"light":"github-light","dark":"github-dark"}} from _gen import * # @func_description( "Called when the caller has been offered the booking link by SMS or by speaking to an agent. " "wants_sms: True if they want the link by text, False if they want to speak to someone." ) @func_parameter("wants_sms", "True if the caller wants the SMS link, False if they want to speak to an agent") def send_link_or_transfer(conv: Conversation, wants_sms: bool): if wants_sms: to_number = conv.state.caller_number if not to_number: return ( "Tell the caller we were unable to send the SMS because we do not have their number. " "Offer to transfer them to an agent instead or ask for a number to send to." ) conv.send_sms_template(to_number=to_number, template="booking_link") return "Tell the caller the link has been sent and ask if there is anything else you can help with." else: conv.call_handoff( destination="agent_queue", reason="caller_requested_agent", utterance="Let me connect you with a member of the team now.", ) ``` ## Get Booking Link.yaml ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} enabled: true example_queries: - Can you send me a link to book? - How do I book online? - I want to make a reservation online - Send me a booking link - Can I speak to someone about booking? content: |- Bookings can be made online at book.example.com, or by speaking with a team member. actions: |- Ask the caller if they would like the link sent by text message, or if they would prefer to speak to someone. Use {{fn:send_link_or_transfer}} once they respond. ``` **Testing via `poly chat`: `caller_number` will be empty** `conv.state.caller_number` is populated from inbound caller ID, which is only available on a real voice call. When testing with `poly chat`, `caller_number` is always empty regardless of `--channel` — `wants_sms: True` will always hit the "unable to send" branch. To exercise the SMS path locally, mock the value in `start_function.py`: ```python theme={"theme":{"light":"github-light","dark":"github-dark"}} conv.state.caller_number = "+15551234567" # remove before deploying ``` ## config/handoffs.yaml The `destination` value passed to `conv.call_handoff` must match the `name` of a handoff defined in `config/handoffs.yaml`. All handoffs are defined in a single file under the `handoffs` key. ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} handoffs: - name: agent_queue description: Transfer to live agent queue is_default: true sip_config: method: refer phone_number: "+441234567890" ``` See the [handoffs reference](/adk/reference/handoffs) for all SIP method options and field details. ## Per-environment sender number If the sender number differs between environments, the simplest approach is to configure `env_phone_numbers` directly in `config/sms_templates.yaml` — no code required: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} sms_templates: - name: booking_link text: "Here's your booking link: https://book.example.com" env_phone_numbers: sandbox: "+441111111111" pre_release: "+442222222222" live: "+443333333333" ``` The platform selects the right number automatically when `conv.send_sms_template()` is called. See the [SMS setup reference](https://docs.poly.ai/sms/introduction) for full template configuration options. If you need more control — for example, when using `conv.send_sms()` to send free-form content rather than a template — you can read `conv.env` directly: ```python theme={"theme":{"light":"github-light","dark":"github-dark"}} ENV_SENDER_NUMBERS = { "sandbox": "+441111111111", "pre-release": "+442222222222", "live": "+443333333333", } def send_link_or_transfer(conv: Conversation, wants_sms: bool): if wants_sms: from_number = ENV_SENDER_NUMBERS.get(conv.env, ENV_SENDER_NUMBERS["sandbox"]) conv.send_sms( to_number=conv.state.caller_number, from_number=from_number, content="Here's your booking link: https://book.example.com", ) return "Tell the caller the link has been sent." ... ``` **Use secrets for sender numbers in production** Avoid hardcoding phone numbers in function code. Store them as [secrets](https://docs.poly.ai/secrets/introduction) and retrieve with `conv.utils.get_secret("sms_sender_live")`. ## Related pages Structure and variable substitution for SMS templates. Configure transfer destinations used by `conv.call_handoff`. Return values, conv API, and function structure. Full reference for `conv.send_sms_template`, `conv.call_handoff`, `conv.env`, and all other `conv` attributes. Store sensitive values like sender numbers and retrieve them at runtime with `conv.utils.get_secret`. Configuring SMS channels, sender numbers, and template structure. # Venue-specific goodbye with clean hangup Source: https://docs.poly.ai/adk/examples/venue-specific-goodbye When the agent needs to say a specific goodbye and then hang up, a common mistake is relying on the LLM to produce the closing utterance. The LLM may add its own filler ("Thank you for calling, goodbye!") before the function executes, resulting in two closing statements — or the wrong one playing. The fix is to return the closing utterance directly from a function, combined with `hangup: True`. The function controls exactly what is said and when the call ends. ## The problem ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} # In a topic action — don't do this for goodbyes actions: |- Thank the caller and say goodbye. Use {{fn:hang_up_call}} to end the call. ``` The LLM speaks its own goodbye first, then the function executes. You end up with two closing statements. ## The solution Return the utterance and hangup from the function itself. The function speaks the closing message and ends the call atomically — no LLM turn in between. ## Files involved ```text theme={"theme":{"light":"github-light","dark":"github-dark"}} functions/goodbye_and_hang_up.py ← closing utterance + hangup config/variant_attributes.yaml ← per-site closing messages (optional) topics/Goodbye.yaml ← triggers the function ``` ## goodbye\_and\_hang\_up.py ```python theme={"theme":{"light":"github-light","dark":"github-dark"}} from _gen import * # @func_description( "Called when the caller is ready to end the call. " "Speaks the closing message and hangs up." ) def goodbye_and_hang_up(conv: Conversation): # Use a variant attribute for site-specific closings, or a plain string for a single site. # getattr guards against AttributeError if the attribute is missing for the current variant. default_closing = "Thanks for calling. Goodbye!" closing = getattr(conv.variant, "closing_message", default_closing) if conv.variant else default_closing return { "utterance": closing, "hangup": True, } ``` ## Goodbye.yaml ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} enabled: true example_queries: - Goodbye - That's all I need - Thanks, bye - I'm done - No, nothing else content: "" actions: |- Use {{fn:goodbye_and_hang_up}} to close the call. Do not say anything before calling the function. ``` **The 'do not say anything' instruction** Including "Do not say anything before calling the function" in the topic action suppresses the LLM's tendency to add its own closing line before the function fires. The utterance in the function return value is what the caller hears. ## Variant attribute for site-specific closing messages If different locations need different goodbyes, add a `closing_message` attribute to `config/variant_attributes.yaml`. All variants and attributes are defined in this single file. ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} variants: - name: london - name: new_york is_default: true attributes: - name: closing_message values: london: "Thanks for calling the London store. Goodbye!" new_york: "Thanks for calling the New York store. Have a great day!" ``` Then access it in the function via `conv.variant.closing_message` as shown above. Every variant must have a value for every attribute — leave it as an empty string if a variant has no custom closing. **`is_default` may be reassigned on push** The platform controls which variant is the default. After a push and pull, the `is_default` assignment in your local file may differ from what you wrote — the server picks the canonical default. This does not affect runtime behavior, but expect the value to change on round-trip. ## Testing variants Use the `--variant` flag with `poly chat` to verify that each variant produces the correct closing message: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} poly chat --variant london poly chat --variant new_york ``` The agent should end each session with the closing message configured for that variant. If `conv.variant` is `None` (for example, if no variants are pushed yet), the function falls back to the default string. **`--variant` resolves against the deployed environment** `--variant` looks up the variant name in the environment you are chatting against (default: `main` in sandbox). If you pushed `variant_attributes.yaml` on a feature branch but have not merged it yet, the variant names will not exist in sandbox and the flag will have no effect. Merge the branch first — with [`poly branch merge`](/adk/reference/branch_merge) or through Agent Studio — then run `poly chat --variant `. ## Related pages Return values, utterance, hangup, and control flow. Per-site configuration using variant attributes. All supported return shapes — `utterance`, `hangup`, combined dicts, and transition objects. How variant attributes are defined, routed, and accessed via `conv.variant`. # First commands Source: https://docs.poly.ai/adk/get-started/first-commands Once the ADK is installed and your API key is set, the very first thing to do is create a local project with `poly init`. After that, the fastest way to get oriented is to inspect the CLI directly from inside that project folder. ## Create your local project with `poly init` ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} poly init ``` Run `poly init` with no arguments. The CLI walks you through interactive dropdowns: 1. **Region** — auto-selected if your API key only has access to one. 2. **Account** — auto-selected if there's only one in the region; otherwise pick from a searchable list. 3. **Project** — pick from a searchable list of every project the API key can see. `poly init` then creates a subdirectory at `{account_id}/{project_id}` in your current directory and pulls the project configuration down from Agent Studio. When it completes, `cd` into that folder — every other `poly` command runs from inside the project directory. **Skip the prompts if you already know the IDs** For scripting or repeat runs, pass any combination of flags to skip the matching prompts: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} poly init --account_id --project_id poly init --region --account_id --project_id ``` The IDs appear in the Agent Studio URL when your project is open: ``` https://studio.poly.ai///... ``` `poly init --json` requires all three flags (no interactive prompts in JSON mode). If the project has already been initialized locally at a previous point, use `poly pull` to refresh it in place instead of running `poly init` again. ## View top-level help Run `poly --help` to see every available command: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} poly --help ``` Each command also accepts `--help` for its own flags and options: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} poly push --help ``` ## Core commands The ADK provides the following core commands: Initialize a new Agent Studio project locally. Pull the latest project configuration from Agent Studio. Push local changes back to Agent Studio. View changed, new, and deleted files in your project. Show differences between the local project and the remote version. Manage project branches. Format project resources. Validate project configuration locally. Create a GitHub gist for reviewing changes. Start an interactive chat session with your agent. Revert local changes. ## Explore any command To learn what a command does and what flags it accepts, run it with `--help`: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} poly init --help poly pull --help poly push --help ``` ## Common first-run behavior ### `poly status` shows variables you didn't create After `poly init` or `poly pull`, `poly status` may report `variables/` entries as new files — for example, `variables/caller_number` or `variables/verified_record`. These are **virtual**: the ADK scans function code for `conv.state.*` assignments and tracks each one as a variable resource. No corresponding files exist on disk. This is expected and does not mean you have changes to push. ### `poly status` shows platform-generated functions as modified After a fresh `poly init` or `poly pull`, `poly status` may report functions such as `functions/get_api_keys.py` or `functions/check_otp.py` as modified, even though you have not touched them. The diff is typically a single stripped blank line introduced by the platform. These are harmless — the ADK and the platform have slightly different whitespace conventions for generated code. You can push through them or ignore them. ### `poly branch switch` reports uncommitted changes If `poly status` shows phantom `variables/` entries or modified platform functions and you try to switch branches, the ADK may block the switch: ```text theme={"theme":{"light":"github-light","dark":"github-dark"}} Cannot switch branches with uncommitted changes. Use --force to switch and discard changes. ``` Use `--force` to override: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} poly branch switch --force ``` This does not lose any real work — the `variables/` entries are virtual and will reappear after the next pull. ### `poly chat` returns a 404 on a feature branch `poly chat` defaults to chatting against your current branch's last pushed state. On most projects this works fine; on some projects the branch deployment endpoint returns a 404: ```text theme={"theme":{"light":"github-light","dark":"github-dark"}} Error: 404 ... /branches//sequence ``` If you hit this, push your changes with [`poly push`](/adk/reference/cli#poly-push), merge the branch with [`poly branch merge`](/adk/reference/branch_merge) (or in the Agent Studio UI), then chat against sandbox instead: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} poly chat --environment sandbox ``` ### `poly branch delete` fails with a 404 or requires a TTY `poly branch delete` triggers the same platform endpoint as branch chat. If the endpoint is unavailable, the delete will fail with a 404 after the confirmation prompt. Additionally, running `poly branch delete` in a non-interactive environment (for example, from a script) throws `[Errno 22] Invalid argument` because the command requires a terminal for its confirmation prompt. If you are stuck with a branch you cannot delete from the CLI, delete it through the Agent Studio UI instead. ## Next step Continue to the command reference for a complete listing, or go straight to the tutorial to see a real workflow. See a more detailed overview of the available commands. Follow the step-by-step workflow for using the ADK in practice. # Getting started with PolyAI Source: https://docs.poly.ai/adk/get-started/get-started The fastest way to get up and running is entirely from the command line. Two steps — install the ADK, then sign in — take you from an empty machine to a local project you can edit, push, and deploy. Sign-in uses `poly start` (self-serve) or `poly login` (enterprise), described below. *** ## Step 1 — Install the ADK You need **uv** to manage the Python environment. If you already have it, skip the first line. ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -LsSf https://astral.sh/uv/install.sh | sh # or: brew install uv ``` Then create a virtual environment and install the ADK: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} uv venv --python=3.14 --seed source .venv/bin/activate pip install polyai-adk ``` Confirm it worked: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} poly --help ``` **Suppress SyntaxWarnings from platform-generated code** Platform-generated code uses regex patterns (such as `\d`) that trigger `SyntaxWarning` in Python 3.14's stricter string handling. This produces 40+ warning lines on every `poly` command and obscures normal output. To suppress them, set this before running any `poly` command: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} export PYTHONWARNINGS=ignore ``` **Optional — install the VS Code / Cursor extension** If you plan to work in **VS Code** or **Cursor**, you can also install the [PolyAI ADK extension](/adk/reference/tooling#polyai-adk-extension-for-vs-code-and-cursor) for resource-aware editing on top of the CLI. The extension is additive — the `poly` command remains the source of truth for every workflow. ## Step 2 — Sign in and set up your API key The right setup path depends on the type of Agent Studio account you have: * **Self-serve accounts** (signed up at [studio.poly.ai](https://studio.poly.ai)) — use [`poly start`](#self-serve-accounts-poly-start) for an end-to-end setup that creates an account, an API key, and an optional first project. * **Enterprise accounts** (a workspace provisioned by PolyAI on a regional cluster such as `us-1`, `euw-1`, or `uk-1`) — use [`poly login --region `](#enterprise-accounts-poly-login-or-manual-api-key) to sign in through your browser, or create an API key in the Agent Studio UI and [export it manually](#manual-api-key-export). `poly start` is self-serve only and does not work against enterprise clusters. If you're not sure which you have, your PolyAI contact can confirm. ### Self-serve accounts — `poly start` ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} poly start ``` `poly start` handles everything you need to authenticate: 1. **Sign up or sign in** — opens a browser window for authentication. This can be on any device, not just the machine running the CLI. 2. **API key** — generates a key and saves it to `~/.poly/credentials.json`. Future `poly` commands pick it up automatically — no environment variables to manage. 3. **Create a project** — optionally creates a new Agent Studio project and pulls it down locally so you can start editing immediately. **Already have a self-serve account?** If `poly start` detects an existing API key (from the credential file or an environment variable), it asks whether to keep using it — accept and you skip straight to the project creation step. Decline and it runs the full sign-in flow again. ### Enterprise accounts — `poly login` or manual API key Enterprise workspaces have two options. `poly login` is the quickest path for most users; the manual export is the fallback if you can't authenticate through the browser (for example, on a CI runner). #### Option 1 — `poly login` (recommended) ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} poly login --region us-1 # or euw-1, uk-1 ``` `poly login`: 1. Opens a browser window so you can sign in to your enterprise workspace. 2. Fetches (or creates) an API key for your user. 3. Saves it to `~/.poly/credentials.json` under the region you specified, so future `poly` commands pick it up automatically. If you omit `--region`, the CLI prompts you to pick one. To sign in to more than one region from the same machine, re-run `poly login` with each region — the credential file stores them side by side. #### Option 2 — Manual API key export If you'd rather create the key yourself in the Agent Studio UI: 1. Log in to Agent Studio for your region and open your workspace. 2. In the **API Keys** tab (next to the **Users** tab), click **+ API key**. Generating an API key in Agent Studio — API Keys tab selected, showing the + API key button in the top-right Then export the key: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} export POLY_ADK_KEY= ``` To make it permanent, add the export line to your shell profile (`~/.zshrc` or `~/.bashrc`). If you work across more than one region, use the region-scoped variables in [per-region API keys](#per-region-api-keys) below. **How the ADK resolves API keys** The ADK checks for credentials in the following order: 1. **Credential file** — `~/.poly/credentials.json` (written by `poly start` or `poly login`) 2. **Region-specific env var** — e.g. `POLY_ADK_KEY_US` 3. **General env var** — `POLY_ADK_KEY` The first match wins. If nothing is found, the CLI raises an error. ## Step 3 — Start building If `poly start` created a project for you, `cd` into the project directory. Otherwise, connect to an existing project: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} poly init ``` [`poly init`](/adk/reference/cli#poly-init) walks you through interactive dropdowns to pick a region, account, and project, then pulls the configuration locally. From inside your project directory, the core workflow is: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} poly status # see what's changed poly diff # inspect changes in detail poly branch create dev # work on a branch poly push # push changes to Agent Studio poly chat # talk to your agent ``` Edit flows, functions, topics, and other resources in your editor of choice — they're just YAML and Python files. Push when you're ready to test in Agent Studio. Follow the full step-by-step tutorial for local development. Explore the full set of CLI commands available to you. *** ## Seed an agent from your website If you're starting from scratch and want a working baseline, you can generate an agent from your company website inside Agent Studio. This gives you topics and agent settings pre-populated from your site's public content — a useful starting point before building locally. 1. Open [Agent Studio](https://studio.poly.ai) for self-serve, or your region's URL for enterprise, and sign in with the same account you authenticated with above. 2. Click **+ Agent** → **Quick Agent Setup**. 3. Enter your website URL and click **Create agent**. Quick setup button Agent Studio Agent Studio crawls your site and generates a configuration — usually within a few minutes. Once it's ready, pull it into your local project: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} poly pull ``` **What gets generated** Agent Studio populates **topics** (knowledge base entries) and basic **agent settings** (personality, role, rules) from your website's public content. It does not generate flows, variants, entities, handoffs, or integrations — those are for you to build locally with the ADK. *** ## Already have an agent in Agent Studio? If you have an existing project — built in the browser, by a PolyAI team, or by any other method — connect it to the ADK with `poly init` once your API key is set up: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} # Self-serve accounts: poly start # sign in and save your API key (skip if already done) poly init # interactive prompts to pick region, account, and project # Enterprise accounts (poly login — recommended): poly login --region us-1 # or euw-1, uk-1 poly init # Enterprise accounts (manual key export, fallback): export POLY_ADK_KEY= # see "Manual API key export" above poly init ``` `poly init` creates a local directory and pulls the full project configuration. From there the standard `poly status` / `poly push` / `poly pull` workflow applies. *** ## Per-region API keys If you work across multiple regions, you can set region-scoped environment variables. The ADK checks the credential file first, then region-scoped env vars, then `POLY_ADK_KEY`. | Region | Environment variable | | --------- | ---------------------- | | `us-1` | `POLY_ADK_KEY_US` | | `euw-1` | `POLY_ADK_KEY_EUW` | | `uk-1` | `POLY_ADK_KEY_UK` | | `studio` | `POLY_ADK_KEY_STUDIO` | | `staging` | `POLY_ADK_KEY_STAGING` | | `dev` | `POLY_ADK_KEY_DEV` | ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} export POLY_ADK_KEY_US= export POLY_ADK_KEY= # used for any other region ``` *** ## Next step Understand what the ADK does and how it fits into Agent Studio. # Prerequisites Source: https://docs.poly.ai/adk/get-started/prerequisites Before using the **PolyAI ADK**, you need a couple of local tools. If you run into any issues, contact [developers@poly-ai.com](mailto:developers@poly-ai.com). ## Local requirements | Tool | Version | Notes | | ------- | ------- | ---------------------------------------------------------------------- | | **uv** | latest | Manages Python and virtual environments | | **Git** | any | Optional — recommended for version control of your local project files | ### Install uv `uv` manages Python versions for you, including the version required by the ADK. Install it with: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -LsSf https://astral.sh/uv/install.sh | sh ``` Alternatively, with Homebrew on macOS: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} brew install uv ``` See the [uv installation guide](https://docs.astral.sh/uv/getting-started/installation/) for more options. ## API key The ADK needs an API key to communicate with Agent Studio. How you obtain it depends on your account type: * **Self-serve accounts** ([studio.poly.ai](https://studio.poly.ai)) — `poly start` signs you in and saves the key automatically. * **Enterprise accounts** (regional clusters such as `us-1`, `euw-1`, `uk-1`) — `poly login --region ` signs you in through the browser and saves the key. Alternatively, create the key in the Agent Studio UI and export it manually. `poly start` is self-serve only. See [Getting started](/adk/get-started/get-started#step-2-sign-in-and-set-up-your-api-key) for the full walkthrough. **API keys are workspace-scoped** An API key grants access to one specific Agent Studio workspace. When you run `poly init`, it lists all projects visible to that key. If you see projects that don't look like yours, you may be using a key scoped to the wrong workspace. Contact your PolyAI contact to confirm. ## Checklist Before continuing, confirm: * `uv` is installed * You have an API key — saved by `poly start` (self-serve), saved by `poly login` (enterprise), or exported manually as `POLY_ADK_KEY` ## Next step Initialize a project, pull configuration, and push your first change. # Walkthrough Video Source: https://docs.poly.ai/adk/get-started/walkthrough-video This walkthrough shows how to build a production-ready voice agent with the **PolyAI ADK**. It demonstrates the end-to-end developer workflow and shows how the ADK fits alongside the [PolyAI ADK extension for VS Code and Cursor](/adk/reference/tooling#polyai-adk-extension-for-vs-code-and-cursor). ## Watch the video