LiveWire — LiveKit-Compatible API
LiveWire 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.
The point: if you have existing LiveKit agent code, you can run it on SignalWire without rewriting the agent logic.
Drop-in import swap
# Before (LiveKit)
from livekit.agents import Agent, AgentSession, function_tool
# After (LiveWire on SignalWire)
from signalwire.livewire import (
Agent, AgentSession, AgentServer, JobContext,
function_tool, run_app,
)
Existing Agent, AgentSession, and @function_tool code remains intact.
Hello-world LiveWire agent
from signalwire.livewire import (
Agent, AgentSession, AgentServer, JobContext, run_app,
)
server = AgentServer()
@server.rtc_session()
async def entrypoint(ctx: JobContext):
await ctx.connect()
agent = Agent(instructions="You are a helpful assistant.")
session = AgentSession()
await session.start(agent, room=ctx.room)
session.say("Welcome! How can I help you today?")
run_app(server)
server.rtc_session() is the LiveKit-compatible decorator that registers your entrypoint. run_app(server) boots the uvicorn server.
Tools via @function_tool
from signalwire.livewire import (
Agent, AgentSession, AgentServer, JobContext, function_tool, run_app,
)
@function_tool
def get_weather(city: str) -> str:
"""Get the current weather for a city."""
return f"Sunny in {city}"
server = AgentServer()
@server.rtc_session()
async def entrypoint(ctx: JobContext):
await ctx.connect()
agent = Agent(instructions="You help with weather.", tools=[get_weather])
session = AgentSession()
await session.start(agent, room=ctx.room)
run_app(server)
@function_tool returns LiveKit-compatible tool metadata; LiveWire registers it under the hood as a SWAIG function.
What LiveWire maps to under the hood
| LiveKit concept | SignalWire equivalent |
|---|---|
| Agent.instructions | SWML ai.prompt.text |
| Agent(tools=[...]) | SWAIG function definitions |
| AgentSession.start() | SWML document generation + serve |
| session.say(text) | Initial greeting or ai_message |
| session.generate_reply(instructions=...) | Queue text via ai_message |
| session.interrupt() | No-op — SignalWire handles barge-in automatically |
| ctx.connect() | Bind the call to the session |
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.
Lifecycle hooks
Subclass Agent to add on_enter and on_exit hooks.
class GreeterAgent(Agent):
async def on_enter(self):
if self.session:
self.session.say("Welcome! I just started up.")
async def on_exit(self):
# Cleanup, save state, notify CRM, etc.
pass
@server.rtc_session()
async def entrypoint(ctx: JobContext):
await ctx.connect()
agent = GreeterAgent(instructions="You are a friendly greeter.")
session = AgentSession()
await session.start(agent, room=ctx.room)
say semantics
- Text queued before
session.start()is included as the initial greeting in the generated SWML document. - Text queued after
session.start()is spoken as soon as the agent is ready.
session.say("Welcome!") # initial greeting if before start
await session.start(agent, room=ctx.room)
session.say("Did you call about your order?") # spoken when AI is ready
generate_reply
session.generate_reply(
instructions="Please introduce yourself to the caller and ask how you can help."
)
On SignalWire the prompt handles generation automatically; instructions (when provided) is queued as additional text.
When LiveWire is the right call
| Situation | Use | |---|---| | Existing LiveKit agent codebase | LiveWire — minimal migration. | | New build, want full SignalWire feature surface | Native AgentBase — direct SWML, full SWAIG control. | | Need cross-platform agent that targets both | LiveWire — same code runs on both stacks. |
LiveWire trades feature breadth for compatibility. If you don't have existing LiveKit code, native AgentBase is the better path.
Anti-patterns
- Mixing LiveWire and native
AgentBasein the same module — pick one. - Calling
session.interrupt()and expecting it to do something — it's a no-op. - Building a multi-context flow in LiveWire — use ContextBuilder on a native agent instead.
- Skipping
await ctx.connect()— the session never binds to the call.