{"slug":"agents-pom-builder","title":"POM Builder — Prompt Object Model for Agents SDK","tags":["signalwire","agents-sdk","pom","prompt-engineering"],"agent_summary":"PomBuilder constructs structured Markdown or XML prompts for voice AI agents. Sections, subsections, bulleted instructions, incremental additions, serialization, and the relationship between PomBuilder and AgentBase.prompt_add_section.","trigger_phrases":["PomBuilder Python","Prompt Object Model SignalWire","structured AI prompt","prompt_add_section","POM markdown XML render"],"runnable":true,"markdown":"\n# POM Builder — Prompt Object Model\n\n`PomBuilder` is a structured prompt construction tool from the SignalWire Agents SDK. It renders to clean Markdown (or XML) — formats that LLMs follow much more reliably than free-form text. AgentBase exposes shortcuts (`prompt_add_section`, `prompt_add_subsection`), and PomBuilder underlies them.\n\nWhen to use the builder directly: when you're constructing prompts outside an AgentBase (e.g., embedding the same prompt in multiple agents), or when you need to serialize, reconstruct, or merge prompt sections programmatically.\n\n## Install\n\n```bash\npip install signalwire-pom\n```\n\nPomBuilder is a separate package — only required if you import it directly.\n\n```python\nfrom signalwire.core.pom_builder import PomBuilder\n```\n\n## Building a structured prompt\n\n```python\npom = PomBuilder()\n\npom.add_section(\"Role\", body=\"You are a customer service agent for Acme Plumbing.\")\npom.add_section(\"Guidelines\", bullets=[\n    \"Be concise.\",\n    \"Never promise specific timelines.\",\n    \"Always confirm caller identity before discussing accounts.\",\n])\npom.add_subsection(\"Guidelines\", \"Escalation\", body=\"Transfer to a human if unresolved after 2 attempts.\")\npom.add_to_section(\"Guidelines\", bullet=\"Always verify caller identity first.\")\n\nprompt_text = pom.render()\n```\n\n`render()` returns Markdown:\n\n```markdown\n## Role\nYou are a customer service agent for Acme Plumbing.\n\n## Guidelines\n- Be concise.\n- Never promise specific timelines.\n- Always confirm caller identity before discussing accounts.\n- Always verify caller identity first.\n\n### Escalation\nTransfer to a human if unresolved after 2 attempts.\n```\n\n## XML rendering\n\nFor LLMs that respond better to XML (Claude, some open-source models):\n\n```python\nprompt_xml = pom.render(format=\"xml\")\n```\n\n```xml\n<section name=\"Role\">\n  <body>You are a customer service agent for Acme Plumbing.</body>\n</section>\n<section name=\"Guidelines\">\n  <bullets>\n    <item>Be concise.</item>\n    ...\n  </bullets>\n  <subsection name=\"Escalation\">\n    <body>Transfer to a human if unresolved after 2 attempts.</body>\n  </subsection>\n</section>\n```\n\n## Incremental additions\n\nPomBuilder is mutable. Add sections at construction time and append later as conditions change.\n\n```python\npom = PomBuilder()\npom.add_section(\"Role\", body=\"You are a sales agent.\")\n\n# Later, after a CRM lookup\nif customer.is_vip:\n    pom.add_section(\"VIP\", body=\"Treat this caller as VIP. Skip qualification questions.\")\n\nagent.set_prompt_text(pom.render())\n```\n\n## Serialization and reconstruction\n\n```python\n# Serialize to dict/JSON\ndata = pom.to_dict()\n\n# Save to file or send over the wire\nimport json\nwith open(\"base_prompt.json\", \"w\") as f:\n    json.dump(data, f)\n\n# Reconstruct elsewhere\npom2 = PomBuilder.from_dict(data)\n```\n\nUseful for sharing a base prompt across multiple agents, or version-controlling prompts as JSON.\n\n## AgentBase shortcuts (no need for PomBuilder directly)\n\nIf you're inside an AgentBase subclass, the same methods exist on `self`:\n\n```python\nclass SupportAgent(AgentBase):\n    def __init__(self):\n        super().__init__(name=\"support\")\n\n        self.prompt_add_section(\"Role\", \"You are a customer service agent.\")\n        self.prompt_add_section(\"Guidelines\", bullets=[\n            \"Be concise.\",\n            \"Never promise timelines.\",\n        ])\n        self.prompt_add_subsection(\"Guidelines\", \"Escalation\",\n            \"Transfer to a human if unresolved after 2 attempts.\")\n        self.prompt_add_to_section(\"Guidelines\",\n            bullet=\"Always verify caller identity first.\")\n```\n\nInternally these call the underlying PomBuilder. Use them when you have a single agent. Use PomBuilder directly when prompts are shared across agents or built outside the SDK.\n\n## Prompt structure that works\n\nLLMs follow this Markdown shape well for voice agents:\n\n```markdown\n## Role\nOne-sentence persona.\n\n## Goals\nBulleted list of what success looks like.\n\n## Guidelines\nBulleted list of behavioral rules.\n\n### Tone\nSub-section overrides.\n\n## Tools\nBulleted list of available SWAIG functions and when to use each.\n\n## Examples\nOptional short dialogue examples.\n```\n\nKeep total prompt length under ~1500 tokens for voice agents. Longer prompts add latency on every turn.\n\n## Anti-patterns\n\n- Dumping unstructured paragraphs into a single section — LLMs do not weight prose evenly. Use bullets.\n- Mixing render formats mid-conversation (Markdown one turn, XML the next) — pick one per agent.\n- Using PomBuilder inside an AgentBase subclass when the shortcuts exist — adds indirection for no gain.\n- Forgetting to call `agent.set_prompt_text(pom.render())` after edits — the agent runs with the stale prompt.\n- Storing prompts as raw strings in code instead of `pom.to_dict()` → JSON — kills git diffability.\n\n## See also\n\n- [Context Builder](/topic/agents-context-builder)\n- [Python Agents SDK](/topic/signalwire-python-agents-sdk)\n- [SWML AI verb](/topic/swml-ai-verb)\n","html":"<h1>POM Builder — Prompt Object Model</h1>\n<p><code>PomBuilder</code> is a structured prompt construction tool from the SignalWire Agents SDK. It renders to clean Markdown (or XML) — formats that LLMs follow much more reliably than free-form text. AgentBase exposes shortcuts (<code>prompt_add_section</code>, <code>prompt_add_subsection</code>), and PomBuilder underlies them.</p>\n<p>When to use the builder directly: when you're constructing prompts outside an AgentBase (e.g., embedding the same prompt in multiple agents), or when you need to serialize, reconstruct, or merge prompt sections programmatically.</p>\n<h2>Install</h2>\n<pre><code class=\"language-bash\">pip install signalwire-pom\n</code></pre>\n<p>PomBuilder is a separate package — only required if you import it directly.</p>\n<pre><code class=\"language-python\">from signalwire.core.pom_builder import PomBuilder\n</code></pre>\n<h2>Building a structured prompt</h2>\n<pre><code class=\"language-python\">pom = PomBuilder()\n\npom.add_section(\"Role\", body=\"You are a customer service agent for Acme Plumbing.\")\npom.add_section(\"Guidelines\", bullets=[\n    \"Be concise.\",\n    \"Never promise specific timelines.\",\n    \"Always confirm caller identity before discussing accounts.\",\n])\npom.add_subsection(\"Guidelines\", \"Escalation\", body=\"Transfer to a human if unresolved after 2 attempts.\")\npom.add_to_section(\"Guidelines\", bullet=\"Always verify caller identity first.\")\n\nprompt_text = pom.render()\n</code></pre>\n<p><code>render()</code> returns Markdown:</p>\n<pre><code class=\"language-markdown\">## Role\nYou are a customer service agent for Acme Plumbing.\n\n## Guidelines\n- Be concise.\n- Never promise specific timelines.\n- Always confirm caller identity before discussing accounts.\n- Always verify caller identity first.\n\n### Escalation\nTransfer to a human if unresolved after 2 attempts.\n</code></pre>\n<h2>XML rendering</h2>\n<p>For LLMs that respond better to XML (Claude, some open-source models):</p>\n<pre><code class=\"language-python\">prompt_xml = pom.render(format=\"xml\")\n</code></pre>\n<pre><code class=\"language-xml\">&#x3C;section name=\"Role\">\n  &#x3C;body>You are a customer service agent for Acme Plumbing.&#x3C;/body>\n&#x3C;/section>\n&#x3C;section name=\"Guidelines\">\n  &#x3C;bullets>\n    &#x3C;item>Be concise.&#x3C;/item>\n    ...\n  &#x3C;/bullets>\n  &#x3C;subsection name=\"Escalation\">\n    &#x3C;body>Transfer to a human if unresolved after 2 attempts.&#x3C;/body>\n  &#x3C;/subsection>\n&#x3C;/section>\n</code></pre>\n<h2>Incremental additions</h2>\n<p>PomBuilder is mutable. Add sections at construction time and append later as conditions change.</p>\n<pre><code class=\"language-python\">pom = PomBuilder()\npom.add_section(\"Role\", body=\"You are a sales agent.\")\n\n# Later, after a CRM lookup\nif customer.is_vip:\n    pom.add_section(\"VIP\", body=\"Treat this caller as VIP. Skip qualification questions.\")\n\nagent.set_prompt_text(pom.render())\n</code></pre>\n<h2>Serialization and reconstruction</h2>\n<pre><code class=\"language-python\"># Serialize to dict/JSON\ndata = pom.to_dict()\n\n# Save to file or send over the wire\nimport json\nwith open(\"base_prompt.json\", \"w\") as f:\n    json.dump(data, f)\n\n# Reconstruct elsewhere\npom2 = PomBuilder.from_dict(data)\n</code></pre>\n<p>Useful for sharing a base prompt across multiple agents, or version-controlling prompts as JSON.</p>\n<h2>AgentBase shortcuts (no need for PomBuilder directly)</h2>\n<p>If you're inside an AgentBase subclass, the same methods exist on <code>self</code>:</p>\n<pre><code class=\"language-python\">class SupportAgent(AgentBase):\n    def __init__(self):\n        super().__init__(name=\"support\")\n\n        self.prompt_add_section(\"Role\", \"You are a customer service agent.\")\n        self.prompt_add_section(\"Guidelines\", bullets=[\n            \"Be concise.\",\n            \"Never promise timelines.\",\n        ])\n        self.prompt_add_subsection(\"Guidelines\", \"Escalation\",\n            \"Transfer to a human if unresolved after 2 attempts.\")\n        self.prompt_add_to_section(\"Guidelines\",\n            bullet=\"Always verify caller identity first.\")\n</code></pre>\n<p>Internally these call the underlying PomBuilder. Use them when you have a single agent. Use PomBuilder directly when prompts are shared across agents or built outside the SDK.</p>\n<h2>Prompt structure that works</h2>\n<p>LLMs follow this Markdown shape well for voice agents:</p>\n<pre><code class=\"language-markdown\">## Role\nOne-sentence persona.\n\n## Goals\nBulleted list of what success looks like.\n\n## Guidelines\nBulleted list of behavioral rules.\n\n### Tone\nSub-section overrides.\n\n## Tools\nBulleted list of available SWAIG functions and when to use each.\n\n## Examples\nOptional short dialogue examples.\n</code></pre>\n<p>Keep total prompt length under ~1500 tokens for voice agents. Longer prompts add latency on every turn.</p>\n<h2>Anti-patterns</h2>\n<ul>\n<li>Dumping unstructured paragraphs into a single section — LLMs do not weight prose evenly. Use bullets.</li>\n<li>Mixing render formats mid-conversation (Markdown one turn, XML the next) — pick one per agent.</li>\n<li>Using PomBuilder inside an AgentBase subclass when the shortcuts exist — adds indirection for no gain.</li>\n<li>Forgetting to call <code>agent.set_prompt_text(pom.render())</code> after edits — the agent runs with the stale prompt.</li>\n<li>Storing prompts as raw strings in code instead of <code>pom.to_dict()</code> → JSON — kills git diffability.</li>\n</ul>\n<h2>See also</h2>\n<ul>\n<li><a href=\"/topic/agents-context-builder\">Context Builder</a></li>\n<li><a href=\"/topic/signalwire-python-agents-sdk\">Python Agents SDK</a></li>\n<li><a href=\"/topic/swml-ai-verb\">SWML AI verb</a></li>\n</ul>\n"}