for an api endpoint that gets description(sepcifie...
# ask-ai
l
for an api endpoint that gets description(sepcifies what the agent should do, its role and the company knowledge), name, llm_provider and tts provider. how would you design this api to create/deploy the created agent in livekit cloud using node js ??
t
Thanks for reaching out, Anirban. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
You're asking how to design a Node.js API endpoint that accepts {name, description, llm_provider, tts_provider} and creates + deploys that agent to LiveKit Cloud. Below is a concise, practical design you can implement and extend. 1) API contract • Endpoint: POST /api/agents • Body (JSON): ◦ name: string ◦ description: string (agent instructions / role / company knowledge) ◦ llm_provider: string (e.g., "openai/gpt-4o-mini") ◦ tts_provider: string (e.g., "cartesia/sonic-3") • Response: ◦ 201 { agentId, deployed: true, dashboardUrl } ◦ 4xx validation errors ◦ 5xx deployment errors 2) High-level flow 1. Validate and sanitize input; ensure providers are from an allowlist. 2. Generate agent source (TypeScript/JS) that uses LiveKit Agents APIs and injects the provided fields into AgentSession / defineAgent config. 3. Persist artifacts to a temp directory (agent code, package.json, livekit.toml). 4. Deploy the agent with the LiveKit CLI:
lk agent create
(or use the Cloud Agents API if available in your environment). 5. Return the agent ID and management URL. Clean up temp files or keep for audit. 3) Minimal Node.js/Express example (core pieces) • Validate request • Generate code (template) • Write files • Run deployment command • Return result Code sketch (conceptual — adapt error handling, logging, security):
Copy code
import express from 'express';
import fs from 'fs';
import path from 'path';
import { exec } from 'child_process';

const app = express();
app.use(express.json());

const ALLOWED_LLM = ['openai/gpt-4o-mini','anthropic/claude-2']; // example
const ALLOWED_TTS = ['cartesia/sonic-3','polly/neural-1']; // example

function generateAgentTs({ name, description, llm, tts }) {
  return `
import { defineAgent, voice } from '@livekit/agents';

export default defineAgent({
  entry: async (ctx) => {
    const session = new voice.AgentSession({
      stt: 'assemblyai/universal-streaming:en',
      llm: '${llm}',
      tts: '${tts}',
    });

    const agent = new voice.Agent({
      instructions: \`${description}\`,
      name: '${name}',
      tools: {},
    });

    await session.start({ agent, room: ctx.room });
  }
});
`.trim();
}

<http://app.post|app.post>('/api/agents', async (req, res) => {
  const { name, description, llm_provider, tts_provider } = req.body;
  if (!name || !description || !llm_provider || !tts_provider) {
    return res.status(400).json({ error: 'Missing required fields' });
  }
  if (!ALLOWED_LLM.includes(llm_provider) || !ALLOWED_TTS.includes(tts_provider)) {
    return res.status(400).json({ error: 'Unsupported LLM or TTS provider' });
  }

  const agentId = `agent_${Date.now()}`;
  const dir = path.join(__dirname, 'generated-agents', agentId);
  fs.mkdirSync(dir, { recursive: true });
  fs.writeFileSync(path.join(dir, 'agent.ts'), generateAgentTs({
    name, description, llm: llm_provider, tts: tts_provider
  }));
  fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({
    name: agentId, version: '1.0.0', main: 'agent.ts',
    scripts: { start: 'lk agent start' },
    dependencies: { '@livekit/agents': '^1.0.0' }
  }, null, 2));

  try {
    await new Promise((resolve, reject) => {
      exec(`cd ${dir} && lk agent create --config livekit.toml`, { env: process.env }, (err, stdout, stderr) => {
        if (err) return reject(err);
        resolve(stdout);
      });
    });
    return res.status(201).json({
      agentId,
      deployed: true,
      dashboardUrl: `<https://cloud.livekit.io/projects/p_/agents/${agentId}>`
    });
  } catch (err) {
    return res.status(500).json({ error: 'Deployment failed', details: err.message });
  }
});
4) Deployment options • Recommended: use
lk agent create
from a server environment where
lk
is installed and authenticated. • Alternate: call the Cloud Agents API programmatically (if you prefer not to run CLI). Use the Cloud Agents / agent create path for programmatic deployment if supported in your environment. 5) Security & best practices • Validate and allowlist provider strings to prevent code injection. • Store LiveKit credentials as environment variables; never embed in generated files. • Rate-limit the endpoint and enforce auth on the API. • Sanitize
description
before injecting into templates (use template-safe insertion or file-based config). • Optionally build an approval workflow before deployment for production agents. 6) Next steps I can help with • Provide a hardened code sample (production-ready validation, logging, cleanup). • Add agent metadata and token-based dispatch example for running the agent in a room. • Show how to use the Cloud Agents API instead of CLI if you prefer non-CLI deployment. Would you like the production-ready code (validation, logging, secrets handling) or a version that uses the Cloud Agents API instead of the CLI? Sources: server-sdk-go | livekit/livekit_composite | DeepWiki | Agent deployment CLI reference | LiveKit docs | Deploying to production | LiveKit Docs | Agent dispatch | LiveKit docs | Agents (Node.js) | livekit/livekit_composite | DeepWiki
l
does any method like the one below exist lk.dispatchAgent({ agentName: "generic-agent", metadata: { agentId: "uuid-from-db" } });
t
You're asking whether a method like
lk.dispatchAgent({...})
exists to dispatch an agent with metadata. Short answer: No —
lk.dispatchAgent(...)
is not a documented method. You can dispatch an agent with metadata using one of these supported options: • Agent Dispatch SDK (recommended for programmatic use) ◦ Node.js example:
Copy code
import { AgentDispatchClient } from 'livekit-server-sdk';

    const client = new AgentDispatchClient(LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET);
    const dispatch = await client.createDispatch('my-room', 'generic-agent', {
      metadata: JSON.stringify({ agentId: 'uuid-from-db' })
    });
• Note:
metadata
must be a string (use JSON for structured data). • LiveKit CLI
Copy code
lk dispatch create --agent-name generic-agent --room my-room --metadata '{"agentId":"uuid-from-db"}'
• Embed dispatch in the access token (room configuration) so an agent is dispatched when joining:
Copy code
token.roomConfig = new RoomConfiguration({
    agents: [
      new RoomAgentDispatch({
        agentName: 'generic-agent',
        metadata: '{"agentId":"uuid-from-db"}'
      })
    ]
  });
If you tell me which SDK or environment you prefer (Node.js server, CLI automation, or token-based dispatch), I can provide a ready-to-use snippet or help adapt this to your code. Sources: Agent dispatch | LiveKit docs | Authentication | LiveKit docs
l
import { AgentDispatchClient } from 'livekit-server-sdk' any method like above ?
t
Yes —
AgentDispatchClient
exists in
livekit-server-sdk
for Node.js and is used to programmatically dispatch agents. Quick usage:
Copy code
import { AgentDispatchClient } from 'livekit-server-sdk';

const client = new AgentDispatchClient(
  process.env.LIVEKIT_URL,
  process.env.LIVEKIT_API_KEY,
  process.env.LIVEKIT_API_SECRET
);

// create a dispatch (metadata must be a string; JSON recommended)
await client.createDispatch('my-room', 'generic-agent', {
  metadata: JSON.stringify({ agentId: 'uuid-from-db' }),
});

// list dispatches for a room
const dispatches = await client.listDispatch('my-room');
If you want, I can provide a full example with error handling, retries, or integrate this into your earlier API endpoint. Which would you prefer? Sources: Agent dispatch | LiveKit docs
l
cli.runApp( new WorkerOptions({ agent: fileURLToPath(import.meta.url), agentName: 'anirban', // NOTE --> Make sure agent has a name to avoid problems during dispatch }), ); what does this line do ? Does it spawn workers or agents
t
You're asking what that cli.runApp(...) line does and whether it spawns workers or agents. • It starts one agent worker process that loads your agent code (the file at
fileURLToPath(import.meta.url)
) and registers that worker with LiveKit under the name
anirban
. • It does NOT spawn multiple workers or agents by itself. It runs a single worker instance that will accept dispatch jobs for the named agent. •
agentName
enables explicit dispatch: the worker will join rooms only when dispatched (via API/CLI/token), and LiveKit will route jobs to workers that advertise the same
agentName
. • To scale or get concurrency, run multiple processes (containers/VMs) with the same `agentName`; LiveKit will load-balance dispatches across them. If you want, I can show a short example of running multiple workers (process manager / Docker compose) or how dispatch calls target that named worker. Which would help more? Sources: Agent server options | LiveKit docs | Agent dispatch | LiveKit docs | Silero VAD plugin | LiveKit docs | Agents Framework | livekit/livekit_composite | DeepWiki
l
so everytime a user dispathes a new agent name, description what happens . explain the workflow
t
You're asking what happens end-to-end when a user dispatches an agent with a given name and description. Below is a concise, step-by-step workflow of what LiveKit does and how your agent code and workers participate. 1) Agent code exists and is deployed • You have an agent implementation (exports via
defineAgent
or similar) and a worker runtime started with a name (e.g.,
agentName: 'anirban'
) using
cli.runApp(...)
. • Workers register with LiveKit and advertise the
agentName
they can run. 2) User issues a dispatch • Dispatch can be triggered via: ◦ Agent Dispatch API (programmatic):
AgentDispatchClient.createDispatch(room, agentName, { metadata: JSON.stringify(...) })
◦ CLI:
lk dispatch create ...
◦ Participant token with
RoomAgentDispatch
embedded (dispatch on join) ◦ SIP inbound call using a SIP dispatch rule • The dispatch payload includes
agentName
, target
room
, and optional `metadata`/description (must be a string; JSON recommended). 3) LiveKit routes the job • LiveKit receives the dispatch and finds registered workers advertising the same
agentName
. • A JobRequest is sent to one of those available workers (load‑balanced). If no matching worker is available, the job will wait or fail per server policy. 4) Worker accepts and spawns job • The selected worker validates/accepts the JobRequest (you can implement pre-validation). • The worker then spawns the job instance (runs your agent entrypoint), creating a JobContext that contains
ctx.room
,
ctx.job
(metadata), and
ctx.agent
. 5) Session initialization inside the agent • Agent connects to the specified room as a participant (
ctx.agent
). • AgentSession is configured (STT, LLM, TTS) using the dispatched metadata/description if you pass it into the session configuration. • The agent begins interacting with participants (voice/chat), using tools or model calls as coded. 6) Agent runtime / lifecycle • The job runs until the session ends (room closes, job completes, or is cancelled). • When the last user leaves, agent disconnects and the job terminates. • Workers can handle subsequent jobs; to increase concurrency you run multiple worker processes with the same
agentName
so LiveKit can load‑balance. 7) Key implementation notes •
agentName
is the dispatch key — it determines routing. •
metadata
must be a string; include structured info as JSON if needed. • Description/instructions can be passed via metadata and read from
ctx.job
to adapt agent behavior at runtime. •
cli.runApp(...)
starts a single worker process; scale by running multiple processes (containers/VMs). • For safety, validate and sanitize metadata before injecting into prompts or code. If you want, I can: • Show a short Node.js example that reads
ctx.job.metadata
and injects it into an
AgentSession
, or • Provide a diagram or minimal code for dispatching an agent with metadata and the worker accept handler. Which would you prefer? Sources: Agent dispatch | LiveKit docs | Agent server | LiveKit docs | Agent server options | LiveKit docs | SIP dispatch rule | LiveKit docs | Agents (Node.js) | livekit/livekit_composite | DeepWiki
l
for being able to create agents only when user wants to how should i craft my api below router.post('/deploy', async(req, res) => { try { const { agentId } = req.body if(!agentId){ res.status(400).json({ message: "Agent ID does not exist" }) return } const agent = await prisma.agents.findUnique({ where: { id: agentId } }) if(!agent){ res.status(400).json({ message: "Agent does not exist" }) return } const room =
agent-room-${agentId}
const dispatch = await dispatchClient.createDispatch( room, 'anirban', { metadata: JSON.stringify({ agentId: agent.id }) } ) res.status(200).json({ message: "Agent has been dispatched", dispatchId: dispatch.id }) return } catch (error) { console.error(
Failed to dispatch agent ${error}
) res.status(500).json({ message: "Server error" }) return } })
t
You're on the right track — your /deploy endpoint triggers an explicit dispatch only when called. Below are concise confirmations and suggestions to make it safer, more flexible, and production-ready. What your endpoint does (quick summary) • Validates request and looks up agent config in DB. • Builds a room name and calls
dispatchClient.createDispatch(room, 'anirban', { metadata })
. • LiveKit routes that dispatch to a worker advertising agentName "anirban"; that worker reads
ctx.job.metadata
to get the agentId and any description. Recommended improvements (minimal, actionable) 1) Validate inputs & agent ownership • Confirm caller is authorized to deploy that agent (check userId / ownership). • Validate agent fields you need (e.g., ensure agent has a valid name/entrypoint). 2) Include runtime config in metadata • Send description/LLM/TTS or other runtime options in
metadata
(JSON string) so the worker can configure the session: ◦ metadata: JSON.stringify({ agentId, description, llm_provider, tts_provider }) 3) Ensure agentName matches running workers • Make sure workers are started with the same
agentName
(or use agentName from DB). Prefer using the DB agent record’s name rather than hard-coding 'anirban'. 4) Improve room naming and idempotency • Use a deterministic or scope-limited room name pattern and consider checking for existing active dispatch to avoid duplicates. ◦ e.g., room =
agent:${agentId}:${Date.now()}
or reuse
agent-room-${agentId}
with a check for active dispatches 5) Return useful info & track status • Return dispatch id, room name, and optionally job/dispatch status. • Persist dispatchId in DB for later status checks and auditing. 6) Robust error handling / retries • Distinguish transient errors from fatal ones; consider retry with backoff for transient dispatch failures. • Bubble up helpful error messages to the caller. 7) Sanitization & security • Sanitize metadata before insertion into prompts or model calls on the worker to avoid injection risks. • Rate-limit this endpoint to prevent abuse. 8) Example adjustments (sketch) • Use agent.agentName from DB: ◦ agentName = agent.name • Metadata payload: ◦ metadata = JSON.stringify({ agentId: agent.id, description: agent.description, llm: agent.llm, tts: agent.tts }) • Save dispatchId to DB after successful createDispatch. If you'd like, I can: • Provide a compact, production-ready version of your endpoint (with auth check, improved metadata, storing dispatchId, and error handling), or • Show the worker code that reads
ctx.job.metadata
and configures the AgentSession from it. Which would you prefer?
l
i have an express js server and an agents.ts file from the agent server code of nodejs. how do i connect the dits after teh above api is called
t
You're close — the Express API dispatches a JobRequest to LiveKit; your agent worker (agents.ts) must be running and registered under the same agentName so it receives that job and reads the dispatch metadata to configure the session. Below are the minimal, concrete steps and code snippets to connect them. 1) Start the agent worker (agents.ts) with the same agentName the API dispatches • Run the worker separately (same machine or different host). Example start in agents.ts:
Copy code
import { cli } from '@livekit/agents';
import { WorkerOptions } from '@livekit/agents/dist/worker'; // adjust import if needed
import { fileURLToPath } from 'url';

cli.runApp(
  new WorkerOptions({
    agent: fileURLToPath(import.meta.url),   // loads this file's agent definition
    agentName: 'anirban'                     // must match dispatch agentName
  })
);
• Ensure env vars are set: LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET. 2) In agents.ts: read dispatch metadata from JobContext and configure AgentSession • Your agent entrypoint receives
ctx
containing
ctx.job.metadata
. Metadata is a string (JSON). • Parse metadata and build the session (STT/LLM/TTS) from it. Example agent handler (concept):
Copy code
import { defineAgent, voice } from '@livekit/agents';

export default defineAgent({
  entry: async (ctx) => {
    const metaStr = ctx.job?.metadata ?? '{}';
    const meta = JSON.parse(metaStr);

    // meta may contain: agentId, description, llm_provider, tts_provider
    const instructions = meta.description ?? 'Default instructions';

    const session = new voice.AgentSession({
      stt: meta.stt || 'assemblyai/universal-streaming:en',
      llm: meta.llm_provider || 'openai/gpt-4o-mini',
      tts: meta.tts_provider || 'cartesia/sonic-3',
    });

    const agent = new voice.Agent({
      instructions,
      // other config...
    });

    await session.start({ agent, room: ctx.room });
    // agent handles interactions until session ends
  }
});
3) Dispatch from Express (you already have this) • Your /deploy endpoint should: ◦ lookup agent config from DB ◦ call dispatchClient.createDispatch(room, agentName, { metadata: JSON.stringify({ agentId, description, llm_provider, tts_provider }) }) • Make agentName come from DB or config; avoid hardcoding if agents differ. 4) End-to-end flow • Developer starts agent worker(s): agents.ts registers with LiveKit under
agentName
. • Client calls your Express
/deploy
→ Express calls createDispatch with metadata JSON. • LiveKit routes job to one of the registered workers with that agentName. • Worker receives JobRequest,
ctx.job.metadata
contains your JSON, worker parses it and starts the session accordingly. 5) Practical tips • Use agentName from DB so multiple agent types can exist. • Validate/sanitize metadata before using in prompts. • Persist dispatchId and room to DB for monitoring. • Run multiple worker processes with same agentName for concurrency/load-balancing. • Log ctx.job and agent lifecycle events to debug dispatch routing. If you want, I can: • Provide a production-ready Express endpoint that includes agentName from DB, richer metadata, and storing dispatchId; or • Provide a full agents.ts example wired to read and apply metadata fields. Which do you want next?
l
so basically the express server runs separately and the cli command spins up event lsiteners ??
t
Yes — they run separately and communicate via LiveKit. • Express server: your API process. When /deploy is called it calls LiveKit (e.g.,
AgentDispatchClient.createDispatch
) to request an agent be assigned to a room. It does not directly start or host the agent logic. • Agent worker (cli.runApp / agent runtime): a separate process you start (locally, container, or VM). Running the CLI/worker registers that process with LiveKit and opens event listeners for incoming JobRequests/dispatches. When LiveKit receives the dispatch from your API, it routes a JobRequest to a worker that advertises the matching
agentName
. The worker receives the job and runs your agent entrypoint (reading
ctx.job.metadata
) to join the room and start the session. Best practices / notes • Use the same
agentName
in DB/dispatch and in worker startup so routing works. • Workers must already be running (or scaled) to accept dispatches; if none are available, the job waits or fails per server policy. • Run workers separately (process manager, Docker, Kubernetes). For local dev you can run Express and the worker side-by-side. • Store dispatchId/room in DB for tracking, and sanitize metadata before use. • To scale, run multiple worker processes with the same agentName — LiveKit load-balances dispatches across them. Would you like a Docker Compose example showing both services, or a minimal worker + Express run script?
l
but everytime i start the agent.ts file containinf teh cli method , it registers an agent in livekit cloud . why is that so ?
t
Starting your agents.ts that calls
cli.runApp(...)
registers the worker with LiveKit Cloud by design. Here's why and what that means: • Worker registration is part of startup: when the process runs it connects to LiveKit, advertises the
agentName
and capabilities, and becomes available to receive JobRequests. This registration is required so LiveKit can route dispatches to running workers. • It registers an instance, not a permanent agent definition: each run creates a live worker instance (a runnable agent). The server can route jobs to any matching running instances. Stopping the process removes that instance from availability. • Why you see it in the Cloud dashboard: the Cloud shows active worker instances and their advertised agent names so you can monitor which agents are online and ready to accept dispatches. • Implications: ◦ You must run at least one worker process for dispatches to succeed. ◦ To scale, run multiple worker processes (same
agentName
) — LiveKit will load‑balance dispatches. ◦ To avoid unintended registrations, don’t run agents.ts in environments where you don’t want the agent to be available (e.g., run only in dev or dedicated worker hosts). ◦ Use distinct `agentName`s per agent type or use DB-backed naming to control which workers register which agents. If you want, I can show how to: • Start/stop workers safely (process manager / Docker compose), or • Make agent registration conditional so the worker only registers in certain environments. Which would help you most?
l
i have a frontend with create agent button . i need to be able to create agents everytim user clicks it. what are the difrerent was to c=achieve it
t
Good question — there are multiple ways to create/run an agent on each front-end "Create agent" click. Below are the practical options, short steps to implement each, and pros/cons so you can pick one that fits your scale, latency, cost, and operational needs. 1) Pre‑deployed generic worker (recommended for low latency) • Idea: Run one or more long‑lived workers that advertise a generic agentName. When user clicks, your frontend calls your Express API which calls LiveKit createDispatch with metadata (name, description, llm, tts, agentId). The running worker receives the job, reads metadata from
ctx.job.metadata
, and configures the session dynamically. • Flow: Frontend -> Express (/deploy) -> AgentDispatch.createDispatch(room, agentName, { metadata }) -> LiveKit -> running worker -> worker parses metadata -> starts AgentSession. • Pros: Low latency, simple, cheaper than spinning containers per click, easy scaling by running multiple workers. • Cons: Worker must be always running; all agent logic must be parameterizable by metadata. 2) Use distinct agentName + pool of workers per agent type • Idea: If agents differ fundamentally, run named worker pools (e.g.,
agent-type-A
,
agent-type-B
). API chooses agentName based on DB and dispatches accordingly. • Pros: Clear separation, easier to enforce different capabilities. • Cons: More ops to run/manage multiple worker pools. 3) Spawn a dedicated worker per request (container / k8s job / process) • Idea: On button click, Express generates agent code/config or points to an agent image and spawns a container/job (Kubernetes Job, Docker run, or server process) that starts
cli.runApp(...)
with a unique agentName or same name. Then call createDispatch (or the worker can self‑dispatch once running). • Flow variants: ◦ A: Express starts container then immediately calls createDispatch (must wait until worker registers). ◦ B: Container self-registers and then calls back to your API to trigger dispatch. • Pros: Isolation per agent, custom agent code per user, easy to tear down. • Cons: Higher latency (cold start), more cost and complexity, need readiness checks and cleanup. 4) Deploy agents via LiveKit CLI / Cloud Agents API programmatically • Idea: Have your server generate agent package (TS/JS), call
lk agent create
or Cloud Agents API to register/deploy the agent in LiveKit Cloud, then dispatch it. • Pros: Full agent lifecycle in Cloud, persistent agents per user. • Cons: Longer deploy time, needs CLI or Cloud API credentials and build pipeline, more complex. 5) Token-based dispatch (dispatch on join) • Idea: Instead of API createDispatch, issue a participant access token that includes a
RoomAgentDispatch
config so when the user (or a connector) joins the room, LiveKit dispatches the agent automatically. • Pros: Useful for client-driven flows, avoids separate dispatch call. • Cons: Requires the worker to be running or available to accept the job. 6) Hybrid — prewarm pool + on‑demand workers • Idea: Keep a small pool of warm workers to guarantee low latency, spawn extra dedicated workers when demand spikes or when a user requires a fully isolated agent. • Pros: Best latency-cost compromise. • Cons: More complex autoscaling logic. Implementation notes & best practices • Metadata must be a string (use JSON). Send description, llm_provider, tts_provider, agentId in metadata and parse in the worker via
ctx.job.metadata
. • Use agentName from DB (avoid hardcoding) so routing aligns with workers. • Ensure workers have env vars: LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET. • Handle readiness: if you spawn a worker, wait for it to register before dispatch (poll dispatchClient.listDispatch or implement worker callback). • Security: authenticate API callers, validate/allowlist llm/tts providers, sanitize metadata before injecting into prompts. • Idempotency & tracking: persist dispatchId, room, status in DB so you can monitor and clean up. • Cleanup: if you create per-request rooms/containers, schedule teardown when session ends. • Scaling: run multiple worker processes (same agentName) for load‑balancing; use k8s/autoscaler for many concurrent jobs. Which approach to pick? • If you need fast response and many clicks → pre‑deployed generic workers or hybrid prewarm pool. • If you need strict isolation and custom code per user → spawn containers or use CLI/Cloud API deploys per request. • If you want simplest implementation first → pre‑deployed generic worker reading metadata from dispatch. Would you like: • A ready Express + frontend flow example that uses pre‑deployed generic workers, or • A Kubernetes job example that spawns a worker per click (with readiness checks and dispatch logic)? Which one should I produce for you?