Report an equipment fault by voice. Get a prioritized, dispatched, and tracked work order.
VoiceOps turns a spoken maintenance report into a work order. An operator describes the problem to an ElevenLabs voice agent. The agent gathers the facts and confirms the request. A transactional backend then decides priority, checks for duplicates, and assigns a qualified technician or queues the work.
The language model talks to the operator. Every operational decision comes from deterministic rules in the backend.
▶ Watch the full demo with audio
A human operator reports a significant leak with a person in danger on pump P-101. The agent verifies the asset, asks for the missing facts, and asks whether this is the same fault as an open job. Then it asks for explicit confirmation. The backend rates the report CRITICAL and queues it with escalation because no mechanic is free. When a mechanic is released, the work order is assigned, worked, resolved, and closed.
- Voice intake with guardrails. The agent never guesses an asset, a diagnosis, or a technician. Unreported facts stay
unknownand are never treated as safe. - Deterministic decisions. Pure policy functions own priority, skill matching, duplicate detection, and lifecycle transitions.
- Confirmation before every change. Preparation returns an expiring draft. Nothing is created until the operator explicitly agrees.
- A durable queue in Postgres. Work waits by priority, then age. Resolving a job releases capacity and assigns the next compatible job in the same transaction.
- Safe under concurrency and retries. An advisory lock, database constraints, and idempotency receipts prevent double booking and duplicate work orders.
- Full audit trail. Every decision writes an append-only event with its reason.
flowchart LR
Operator([Operator voice]) --> Agent[ElevenLabs agent]
Agent -->|get_maintenance_context| Context[Verify asset and history]
Agent -->|prepare_work_order| Draft[Expiring draft with priority and duplicates]
Draft --> Consent{Operator confirms?}
Consent -->|confirm_work_order| Backend[Transactional backend]
Backend --> Held[Held for manual review]
Backend --> Assigned[Assigned to a free technician]
Backend --> Queued[Queued, with escalation if CRITICAL]
Tech([Technician CLI]) -->|resolve| Release[Release capacity]
Release --> Assigned
The agent has three tools and no technician permissions. Technicians acknowledge, start, resolve, and close work through a separate CLI with a separate credential.
The highest matching rule wins. Missing facts never lower a priority.
| Priority | Reported conditions |
|---|---|
| CRITICAL | Personnel hazard, fire or smoke, or a significant leak |
| HIGH | Equipment unavailable, severe vibration, or severe temperature |
| MEDIUM | Abnormal vibration or temperature, or degraded performance while operating |
| LOW | A defect with no higher condition |
A report with unknown facts or an unclassified fault is held for manual review instead of being dispatched.
stateDiagram-v2
direction LR
[*] --> NEW
NEW --> VALIDATED
VALIDATED --> PRIORITIZED
PRIORITIZED --> ASSIGNED
ASSIGNED --> ACKNOWLEDGED
ACKNOWLEDGED --> IN_PROGRESS
IN_PROGRESS --> RESOLVED
RESOLVED --> CLOSED
CLOSED --> [*]
Queued work stays PRIORITIZED until a compatible technician frees up.
Capacity is taken at ASSIGNED and released at RESOLVED.
| Layer | Technology |
|---|---|
| Voice | ElevenLabs Agents with webhook tools and a stored secret |
| API | TypeScript 7, Effect 4 HttpApi and Schema |
| Data | Postgres 18 with @effect/sql-pg migrations |
| Demo | Browser page with Play and Live voice modes, plus a local tool relay |
| Testing | Node test runner, real Postgres integration tests, tester-army E2E |
You need Node 24.21.0, npm 12.2.0, and Docker.
-
Create an ignored
.envfile with synthetic credentials.POSTGRES_PASSWORD=replace-with-a-local-password DATABASE_URL=postgres://voiceops:replace-with-a-local-password@127.0.0.1:5432/voiceops TOOL_API_KEY=replace-with-a-random-operator-secret TECHNICIAN_API_KEY=replace-with-a-different-random-technician-secret PORT=3000
-
Start Postgres, seed the demo data, and run the API.
docker compose up -d --wait npm ci --ignore-scripts npm run build npm run seed npm start
-
In a second terminal, start the demo page and open http://127.0.0.1:4173.
npm run demo
Press Play to watch a typed report go through the real backend. To speak to the agent, follow the demo page guide.
-
Work a job from the technician CLI.
npm run technician -- state npm run technician -- resolve demo-busy-mechanical Tech-03 demo-resolve-001
The seed data has three assets (P-101, C-201, V-301) and three technicians. Tech-01 and Tech-03 start busy, so a mechanical report queues until one of them resolves a job. Tech-02 is free for electrical work.
npm run verify # lint, type checks, build, 82 offline tests, policy checks
npm run eval # repository policy evaluation
export TEST_DATABASE_URL=postgres://voiceops:synthetic-test-password@127.0.0.1:5432/voiceops_test
npm run test:integration # 38 tests over real HTTP and Postgres
npm run test:e2e # 38 acceptance cases with the real server and CLIsThe database suites reset a dedicated database whose name ends in _test.
Run them one at a time.
The integration suite covers queue order, concurrent intake and resolution, rollback on failure, retries, and the technician CLI.
See the E2E guide for the full coverage matrix.
To check a release archive, commit first, then run npm run package:source and npm run check:package.
The check installs and verifies the archive in a temporary directory.
The hosted agent is tested with five conversational scenarios in resources/elevenlabs/scenarios.json.
They cover complete intake, a missing asset, an unknown asset, an emergency, and ambiguous facts.
All five pass in ElevenLabs simulation.
The same scenarios also ran as live conversations against the real backend.
See the ElevenLabs guide to provision the agent and run the scenarios.
| Record | What it shows |
|---|---|
| Live voice demo | Tool calls and the persisted CRITICAL work order from the recorded call |
| Simulation results | All five ElevenLabs scenarios passing |
| Earlier live conversation | A queued electrical job assigned after capacity was released |
| Backend verification | Test runs, packaging checks, and limits |
| Path | Contents |
|---|---|
src/ |
HTTP API, pure policy, transactional services, schemas, and database modules |
tests/ |
Offline contracts, real Postgres integration tests, and E2E acceptance tests |
tools/ |
Server, seed, technician CLI, demo server, and ElevenLabs setup |
demo/ |
Browser demo page |
resources/elevenlabs/ |
Agent prompt and conversational scenarios |
docs/ |
Guides, specs, ADRs, and verification evidence |
- Backend guide and API contract
- Demo page guide
- ElevenLabs guide
- Architecture and design decisions
- Product spec
- Contributing, support, and security
- All equipment, technicians, history, and work orders are fictional.
- The backend never diagnoses equipment, dispatches real people, or contacts emergency services.
- Verbal confirmation is handled by the voice agent. The backend cannot independently prove that a person consented.
- Live voice needs the local backend and a public HTTPS tunnel.
