POM Builder — Prompt Object Model
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.
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.
Install
pip install signalwire-pom
PomBuilder is a separate package — only required if you import it directly.
from signalwire.core.pom_builder import PomBuilder
Building a structured prompt
pom = PomBuilder()
pom.add_section("Role", body="You are a customer service agent for Acme Plumbing.")
pom.add_section("Guidelines", bullets=[
"Be concise.",
"Never promise specific timelines.",
"Always confirm caller identity before discussing accounts.",
])
pom.add_subsection("Guidelines", "Escalation", body="Transfer to a human if unresolved after 2 attempts.")
pom.add_to_section("Guidelines", bullet="Always verify caller identity first.")
prompt_text = pom.render()
render() returns Markdown:
## Role
You are a customer service agent for Acme Plumbing.
## Guidelines
- Be concise.
- Never promise specific timelines.
- Always confirm caller identity before discussing accounts.
- Always verify caller identity first.
### Escalation
Transfer to a human if unresolved after 2 attempts.
XML rendering
For LLMs that respond better to XML (Claude, some open-source models):
prompt_xml = pom.render(format="xml")
<section name="Role">
<body>You are a customer service agent for Acme Plumbing.</body>
</section>
<section name="Guidelines">
<bullets>
<item>Be concise.</item>
...
</bullets>
<subsection name="Escalation">
<body>Transfer to a human if unresolved after 2 attempts.</body>
</subsection>
</section>
Incremental additions
PomBuilder is mutable. Add sections at construction time and append later as conditions change.
pom = PomBuilder()
pom.add_section("Role", body="You are a sales agent.")
# Later, after a CRM lookup
if customer.is_vip:
pom.add_section("VIP", body="Treat this caller as VIP. Skip qualification questions.")
agent.set_prompt_text(pom.render())
Serialization and reconstruction
# Serialize to dict/JSON
data = pom.to_dict()
# Save to file or send over the wire
import json
with open("base_prompt.json", "w") as f:
json.dump(data, f)
# Reconstruct elsewhere
pom2 = PomBuilder.from_dict(data)
Useful for sharing a base prompt across multiple agents, or version-controlling prompts as JSON.
AgentBase shortcuts (no need for PomBuilder directly)
If you're inside an AgentBase subclass, the same methods exist on self:
class SupportAgent(AgentBase):
def __init__(self):
super().__init__(name="support")
self.prompt_add_section("Role", "You are a customer service agent.")
self.prompt_add_section("Guidelines", bullets=[
"Be concise.",
"Never promise timelines.",
])
self.prompt_add_subsection("Guidelines", "Escalation",
"Transfer to a human if unresolved after 2 attempts.")
self.prompt_add_to_section("Guidelines",
bullet="Always verify caller identity first.")
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.
Prompt structure that works
LLMs follow this Markdown shape well for voice agents:
## Role
One-sentence persona.
## Goals
Bulleted list of what success looks like.
## Guidelines
Bulleted list of behavioral rules.
### Tone
Sub-section overrides.
## Tools
Bulleted list of available SWAIG functions and when to use each.
## Examples
Optional short dialogue examples.
Keep total prompt length under ~1500 tokens for voice agents. Longer prompts add latency on every turn.
Anti-patterns
- Dumping unstructured paragraphs into a single section — LLMs do not weight prose evenly. Use bullets.
- Mixing render formats mid-conversation (Markdown one turn, XML the next) — pick one per agent.
- Using PomBuilder inside an AgentBase subclass when the shortcuts exist — adds indirection for no gain.
- Forgetting to call
agent.set_prompt_text(pom.render())after edits — the agent runs with the stale prompt. - Storing prompts as raw strings in code instead of
pom.to_dict()→ JSON — kills git diffability.