{"slug":"agents-livewire-compat","title":"LiveWire — LiveKit-Compatible API on SignalWire","tags":["signalwire","livewire","livekit","compatibility","agents-sdk"],"agent_summary":"LiveWire is a LiveKit-compatible API surface from SignalWire — drop-in for code written against `livekit.agents`. Covers AgentServer/Agent/AgentSession, the rtc_session decorator, function_tool, JobContext.connect, no-op interrupt semantics, and the lift-and-shift migration from LiveKit code.","trigger_phrases":["LiveWire SignalWire","LiveKit compatibility SignalWire","livekit.agents drop in","function_tool SignalWire","rtc_session entrypoint","migrate LiveKit to SignalWire"],"runnable":true,"markdown":"\n# LiveWire — LiveKit-Compatible API\n\nLiveWire is a thin compatibility layer that exposes a LiveKit-compatible API surface on top of SignalWire infrastructure. Code written against `livekit.agents` lifts and shifts with a single import change. Under the hood it generates a SignalWire AI agent and runs the standard SWML/SWAIG stack.\n\nThe point: if you have existing LiveKit agent code, you can run it on SignalWire without rewriting the agent logic.\n\n## Drop-in import swap\n\n```python\n# Before (LiveKit)\nfrom livekit.agents import Agent, AgentSession, function_tool\n\n# After (LiveWire on SignalWire)\nfrom signalwire.livewire import (\n    Agent, AgentSession, AgentServer, JobContext,\n    function_tool, run_app,\n)\n```\n\nExisting `Agent`, `AgentSession`, and `@function_tool` code remains intact.\n\n## Hello-world LiveWire agent\n\n```python\nfrom signalwire.livewire import (\n    Agent, AgentSession, AgentServer, JobContext, run_app,\n)\n\nserver = AgentServer()\n\n@server.rtc_session()\nasync def entrypoint(ctx: JobContext):\n    await ctx.connect()\n    agent = Agent(instructions=\"You are a helpful assistant.\")\n    session = AgentSession()\n    await session.start(agent, room=ctx.room)\n    session.say(\"Welcome! How can I help you today?\")\n\nrun_app(server)\n```\n\n`server.rtc_session()` is the LiveKit-compatible decorator that registers your entrypoint. `run_app(server)` boots the uvicorn server.\n\n## Tools via `@function_tool`\n\n```python\nfrom signalwire.livewire import (\n    Agent, AgentSession, AgentServer, JobContext, function_tool, run_app,\n)\n\n@function_tool\ndef get_weather(city: str) -> str:\n    \"\"\"Get the current weather for a city.\"\"\"\n    return f\"Sunny in {city}\"\n\nserver = AgentServer()\n\n@server.rtc_session()\nasync def entrypoint(ctx: JobContext):\n    await ctx.connect()\n    agent = Agent(instructions=\"You help with weather.\", tools=[get_weather])\n    session = AgentSession()\n    await session.start(agent, room=ctx.room)\n\nrun_app(server)\n```\n\n`@function_tool` returns LiveKit-compatible tool metadata; LiveWire registers it under the hood as a SWAIG function.\n\n## What LiveWire maps to under the hood\n\n| LiveKit concept | SignalWire equivalent |\n|---|---|\n| `Agent.instructions` | SWML `ai.prompt.text` |\n| `Agent(tools=[...])` | SWAIG function definitions |\n| `AgentSession.start()` | SWML document generation + serve |\n| `session.say(text)` | Initial greeting or `ai_message` |\n| `session.generate_reply(instructions=...)` | Queue text via `ai_message` |\n| `session.interrupt()` | **No-op** — SignalWire handles barge-in automatically |\n| `ctx.connect()` | Bind the call to the session |\n\n`session.interrupt()` is a no-op for API compatibility. SignalWire's control plane manages barge-in based on the agent's `allow_interruptions` setting. You don't need to call it explicitly.\n\n## Lifecycle hooks\n\nSubclass `Agent` to add `on_enter` and `on_exit` hooks.\n\n```python\nclass GreeterAgent(Agent):\n    async def on_enter(self):\n        if self.session:\n            self.session.say(\"Welcome! I just started up.\")\n\n    async def on_exit(self):\n        # Cleanup, save state, notify CRM, etc.\n        pass\n\n@server.rtc_session()\nasync def entrypoint(ctx: JobContext):\n    await ctx.connect()\n    agent = GreeterAgent(instructions=\"You are a friendly greeter.\")\n    session = AgentSession()\n    await session.start(agent, room=ctx.room)\n```\n\n## `say` semantics\n\n- Text queued *before* `session.start()` is included as the initial greeting in the generated SWML document.\n- Text queued *after* `session.start()` is spoken as soon as the agent is ready.\n\n```python\nsession.say(\"Welcome!\")             # initial greeting if before start\nawait session.start(agent, room=ctx.room)\nsession.say(\"Did you call about your order?\")   # spoken when AI is ready\n```\n\n## `generate_reply`\n\n```python\nsession.generate_reply(\n    instructions=\"Please introduce yourself to the caller and ask how you can help.\"\n)\n```\n\nOn SignalWire the prompt handles generation automatically; `instructions` (when provided) is queued as additional text.\n\n## When LiveWire is the right call\n\n| Situation | Use |\n|---|---|\n| Existing LiveKit agent codebase | **LiveWire** — minimal migration. |\n| New build, want full SignalWire feature surface | **Native [AgentBase](/topic/signalwire-python-agents-sdk)** — direct SWML, full SWAIG control. |\n| Need cross-platform agent that targets both | **LiveWire** — same code runs on both stacks. |\n\nLiveWire trades feature breadth for compatibility. If you don't have existing LiveKit code, native `AgentBase` is the better path.\n\n## Anti-patterns\n\n- Mixing LiveWire and native `AgentBase` in the same module — pick one.\n- Calling `session.interrupt()` and expecting it to do something — it's a no-op.\n- Building a multi-context flow in LiveWire — use [ContextBuilder](/topic/agents-context-builder) on a native agent instead.\n- Skipping `await ctx.connect()` — the session never binds to the call.\n\n## See also\n\n- [Python Agents SDK](/topic/signalwire-python-agents-sdk)\n- [SWML AI verb](/topic/swml-ai-verb)\n- [Context Builder](/topic/agents-context-builder)\n- [LiveKit BYO SIP trunk](/topic/byo-sip-trunk-livekit)\n","html":"<h1>LiveWire — LiveKit-Compatible API</h1>\n<p>LiveWire is a thin compatibility layer that exposes a LiveKit-compatible API surface on top of SignalWire infrastructure. Code written against <code>livekit.agents</code> lifts and shifts with a single import change. Under the hood it generates a SignalWire AI agent and runs the standard SWML/SWAIG stack.</p>\n<p>The point: if you have existing LiveKit agent code, you can run it on SignalWire without rewriting the agent logic.</p>\n<h2>Drop-in import swap</h2>\n<pre><code class=\"language-python\"># Before (LiveKit)\nfrom livekit.agents import Agent, AgentSession, function_tool\n\n# After (LiveWire on SignalWire)\nfrom signalwire.livewire import (\n    Agent, AgentSession, AgentServer, JobContext,\n    function_tool, run_app,\n)\n</code></pre>\n<p>Existing <code>Agent</code>, <code>AgentSession</code>, and <code>@function_tool</code> code remains intact.</p>\n<h2>Hello-world LiveWire agent</h2>\n<pre><code class=\"language-python\">from signalwire.livewire import (\n    Agent, AgentSession, AgentServer, JobContext, run_app,\n)\n\nserver = AgentServer()\n\n@server.rtc_session()\nasync def entrypoint(ctx: JobContext):\n    await ctx.connect()\n    agent = Agent(instructions=\"You are a helpful assistant.\")\n    session = AgentSession()\n    await session.start(agent, room=ctx.room)\n    session.say(\"Welcome! How can I help you today?\")\n\nrun_app(server)\n</code></pre>\n<p><code>server.rtc_session()</code> is the LiveKit-compatible decorator that registers your entrypoint. <code>run_app(server)</code> boots the uvicorn server.</p>\n<h2>Tools via <code>@function_tool</code></h2>\n<pre><code class=\"language-python\">from signalwire.livewire import (\n    Agent, AgentSession, AgentServer, JobContext, function_tool, run_app,\n)\n\n@function_tool\ndef get_weather(city: str) -> str:\n    \"\"\"Get the current weather for a city.\"\"\"\n    return f\"Sunny in {city}\"\n\nserver = AgentServer()\n\n@server.rtc_session()\nasync def entrypoint(ctx: JobContext):\n    await ctx.connect()\n    agent = Agent(instructions=\"You help with weather.\", tools=[get_weather])\n    session = AgentSession()\n    await session.start(agent, room=ctx.room)\n\nrun_app(server)\n</code></pre>\n<p><code>@function_tool</code> returns LiveKit-compatible tool metadata; LiveWire registers it under the hood as a SWAIG function.</p>\n<h2>What LiveWire maps to under the hood</h2>\n<p>| LiveKit concept | SignalWire equivalent |\n|---|---|\n| <code>Agent.instructions</code> | SWML <code>ai.prompt.text</code> |\n| <code>Agent(tools=[...])</code> | SWAIG function definitions |\n| <code>AgentSession.start()</code> | SWML document generation + serve |\n| <code>session.say(text)</code> | Initial greeting or <code>ai_message</code> |\n| <code>session.generate_reply(instructions=...)</code> | Queue text via <code>ai_message</code> |\n| <code>session.interrupt()</code> | <strong>No-op</strong> — SignalWire handles barge-in automatically |\n| <code>ctx.connect()</code> | Bind the call to the session |</p>\n<p><code>session.interrupt()</code> is a no-op for API compatibility. SignalWire's control plane manages barge-in based on the agent's <code>allow_interruptions</code> setting. You don't need to call it explicitly.</p>\n<h2>Lifecycle hooks</h2>\n<p>Subclass <code>Agent</code> to add <code>on_enter</code> and <code>on_exit</code> hooks.</p>\n<pre><code class=\"language-python\">class GreeterAgent(Agent):\n    async def on_enter(self):\n        if self.session:\n            self.session.say(\"Welcome! I just started up.\")\n\n    async def on_exit(self):\n        # Cleanup, save state, notify CRM, etc.\n        pass\n\n@server.rtc_session()\nasync def entrypoint(ctx: JobContext):\n    await ctx.connect()\n    agent = GreeterAgent(instructions=\"You are a friendly greeter.\")\n    session = AgentSession()\n    await session.start(agent, room=ctx.room)\n</code></pre>\n<h2><code>say</code> semantics</h2>\n<ul>\n<li>Text queued <em>before</em> <code>session.start()</code> is included as the initial greeting in the generated SWML document.</li>\n<li>Text queued <em>after</em> <code>session.start()</code> is spoken as soon as the agent is ready.</li>\n</ul>\n<pre><code class=\"language-python\">session.say(\"Welcome!\")             # initial greeting if before start\nawait session.start(agent, room=ctx.room)\nsession.say(\"Did you call about your order?\")   # spoken when AI is ready\n</code></pre>\n<h2><code>generate_reply</code></h2>\n<pre><code class=\"language-python\">session.generate_reply(\n    instructions=\"Please introduce yourself to the caller and ask how you can help.\"\n)\n</code></pre>\n<p>On SignalWire the prompt handles generation automatically; <code>instructions</code> (when provided) is queued as additional text.</p>\n<h2>When LiveWire is the right call</h2>\n<p>| Situation | Use |\n|---|---|\n| Existing LiveKit agent codebase | <strong>LiveWire</strong> — minimal migration. |\n| New build, want full SignalWire feature surface | <strong>Native <a href=\"/topic/signalwire-python-agents-sdk\">AgentBase</a></strong> — direct SWML, full SWAIG control. |\n| Need cross-platform agent that targets both | <strong>LiveWire</strong> — same code runs on both stacks. |</p>\n<p>LiveWire trades feature breadth for compatibility. If you don't have existing LiveKit code, native <code>AgentBase</code> is the better path.</p>\n<h2>Anti-patterns</h2>\n<ul>\n<li>Mixing LiveWire and native <code>AgentBase</code> in the same module — pick one.</li>\n<li>Calling <code>session.interrupt()</code> and expecting it to do something — it's a no-op.</li>\n<li>Building a multi-context flow in LiveWire — use <a href=\"/topic/agents-context-builder\">ContextBuilder</a> on a native agent instead.</li>\n<li>Skipping <code>await ctx.connect()</code> — the session never binds to the call.</li>\n</ul>\n<h2>See also</h2>\n<ul>\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<li><a href=\"/topic/agents-context-builder\">Context Builder</a></li>\n<li><a href=\"/topic/byo-sip-trunk-livekit\">LiveKit BYO SIP trunk</a></li>\n</ul>\n"}