Inbound voice for a one-person shop. A caller talks; LineOne answers out loud on the AssemblyAI Voice Agent API, runs JSON-schema tools, and Maya watches the ticket board.
This is original work for the AssemblyAI Voice Agent Hackathon on lablab.ai (1–30 Sep 2026). It is not UVAI, ClipToAction, or EventRelay.
Contest AssemblyAI account: connectkk11. LineOne never looks that name up. The only secret it reads is ASSEMBLYAI_API_KEY from the process environment.
Do not paste the API key into git, chat, README, screenshots, or the browser.
| Where | How |
|---|---|
| Local | gitignored .env.local (copy from .env.example) |
| Cursor Cloud | environment secret named ASSEMBLYAI_API_KEY |
| Vercel | Project Settings → Environment Variables → ASSEMBLYAI_API_KEY |
Restart the Node process after the variable is set. GET /api/status reports handshake: "ok" when GET https://agents.assemblyai.com/v1/agents succeeds. That probe does not mint a voice session. Starting a call mints a single-use token via GET /v1/token on the server; the permanent key never goes to the client.
Optional: LINEONE_AGENT_ID to bind a stored agent. If unset, the browser sends an inline session.update (prompt, Mary voice, VAD, tools).
.env* is gitignored except .env.example, which is empty on purpose.
Northline Repair is a solo appliance shop. Maya Chen is usually on a job, so the one shop line is the business. LineOne is that line:
- Browser asks this app for a temporary Voice Agent token. This server calls
GET /v1/tokenwithASSEMBLYAI_API_KEY. - Browser opens
wss://agents.assemblyai.com/v1/ws?token=…and sends onesession.updatewith prompt, Mary voice, VAD, Universal-3 Promax_accuracySTT, and four function tools. - Microphone PCM16 @ 24 kHz streams as
input.audio. Agent speech comes back asreply.audio. - When the model needs shop data it emits
tool.call. LineOne executes the tool against an in-memory board, then sendstool.resultafterreply.doneso turn-taking stays intact.
| Tool | What it does |
|---|---|
lookup_hours |
Weekday hours for Northline Repair (Sun closed, Sat 9–1) |
create_ticket |
Opens a numbered job on the board |
schedule_callback |
Queues a call-back window for Maya |
read_open_tickets |
Reads tickets already on the board |
Node 20+. Chrome or Edge recommended (Safari needs the worklet resample path, which is included).
cp .env.example .env.local
# set ASSEMBLYAI_API_KEY in that file from the AssemblyAI dashboard.
# Do not commit .env.local.
npm install
npm test
npm run devOpen http://127.0.0.1:4317. Click Start call, allow the microphone, wait for the greeting, then talk. Try:
- “Are you open today?”
- “My fridge is warm and the compressor keeps clicking. I’m Jordan Hale, 503-555-0199.”
- “Can Maya call me back tomorrow after 3?”
- “Do you already have a ticket for Elena Vargas?”
Without the env var the board still loads (two seed jobs). Start call returns HTTP 503 from /api/voice-token. That is intentional.
Judges: https://lineone-two.vercel.app
lablab wants a URL judges can open. That production deploy is it.
To recreate the deploy:
- Use this public repo: https://github.com/kk-agent/lineone
- Import it in Vercel.
- Set one secret in the Vercel project:
ASSEMBLYAI_API_KEY. - Deploy. The production URL is the demo.
Anyone with the URL can start sessions billed to that key. Use the contest key with a spend cap.
Render also works (npm run start after npm run build, bind 0.0.0.0:$PORT). Vercel is the path this app is built for.
Server, src/lib/assemblyai.ts:
GET https://agents.assemblyai.com/v1/agents # handshake, not a call
GET https://agents.assemblyai.com/v1/token # one-time browser token
Authorization: Bearer $ASSEMBLYAI_API_KEY
Client, src/lib/voice-session.ts:
session.updatefromsrc/lib/voice-agent-config.ts— inline config, oragent_idwhenLINEONE_AGENT_IDis setinput.audio— base64 PCM16 from/pcm-processor.jstool.resultonly when the latest event isreply.donesession.endon hang-up andpagehideso you are not billed for the 30s resume window
Docs used while building:
- Voice Agent API
- Browser integration
- Client-side tools
- Events reference
- LLM Gateway (the Voice Agent session uses AssemblyAI’s managed LLM routing; you do not pass a second provider key)
- Shop state is in-process memory. Serverless instances do not share it; Reset board restores the two seed tickets.
- No Twilio number in this slice. The Voice Agent API can attach SIP later; the browser call is the judge path.
- No wallet seeds, Stripe, or checkout.
MIT. See LICENSE.