Files
voice_outbound/AGENTS.md

6.5 KiB

Outbound Voice (demo)

Build spec for Paperclip. Status: last project. Do not start until the Hub, the phone app, and Artemis work. Follow the phases in section 11 in order.


1. Purpose and outcome

A demonstration of technology. Show that the system can hold an artificial voice conversation: MiniMax supplies the voice, an LLM supplies the words.

Sam sets a phone number, a topic, and a tone, then presses Call now.

Outcome (the single success statement):

Sam enters a number, a topic, and a tone, presses Call now, and listens to a short, natural conversation driven by the LLM in a MiniMax voice.

2. Scope

In scope

  • Set the target number, the topic, the tone, and the goal.
  • Start a call on demand ("Call now").
  • Speech to text the other person; the LLM decides the reply; MiniMax text to speech speaks.
  • Keep the conversation on topic and in tone.
  • End the call gracefully.
  • Store and show the transcript.

Out of scope

  • Inbound calls.
  • Production or unattended use.
  • Anything safety-critical.
  • WhatsApp, unless it turns out to be simple. Decide in Phase 2.

3. Users and roles

  • Sam only. This is an operator tool.

4. Functional requirements

ID Requirement
FR-1 Set a target: phone number, topic, tone, and goal. Choose the channel: a phone call, WhatsApp, or Sam's Aussie Broadband service.
FR-2 Start a call on demand ("Call now").
FR-3 Loop: STT the callee → LLM plans the reply → MiniMax TTS speaks it.
FR-4 Keep the reply on topic and in the chosen tone.
FR-5 Detect the end and close the call gracefully.
FR-6 Store the transcript: each turn, who spoke, and the time.
FR-7 Show a live transcript during the call.
FR-8 A hard time limit per call. The demo never runs away.
FR-9 Log the call: number, topic, tone, duration, result.

5. Screens and UX flow

Screen: control page

  1. Fields: target number, topic, tone, goal.
  2. A Call now button.
  3. A live transcript panel (speaker, text, time).
  4. A Hang up button.
  5. Call history with transcripts.

Main flow

Fill the form → Call now → watch the transcript → Hang up (or auto-end) → read the log

6. Look and feel

Reuse the Family Home Lab design system.

  • Source: /home/sam/Docker/Containers/family-home-lab/DESIGN.md
  • Tokens: /home/sam/Docker/Containers/family-home-lab/portal/static/tokens.css
  • Copy tokens.css into this project.

Canvas #f6f5f4, one action blue #0075de, Inter, 8px grid, pill CTAs. Dark mode.

7. Data model

SQLite inside the service. Suggested:

Table Holds
calls number, topic, tone, goal, started-at, ended-at, duration, result
turns call id, speaker (ai or human), text, created-at
recordings optional audio paths

Retention: keep transcripts for a bounded period. Recordings only if Sam asks.

8. Integrations

System Use
MiniMax Text to speech, and speech to text. Keys already in environment.d.
OmniRoute The LLM that plans each reply.
Telephony To be decided — see section 13.
Hub Optional. /ask is not required. A /notify on call end is optional.

9. Technology (locked)

Item Value
Backend Go
UI HTMX + server-rendered HTML
Database SQLite
Voice MiniMax T2A and STT
LLM OmniRoute
Telephony Choose per call: phone call, WhatsApp, or Aussie Broadband. Providers decided in Phase 2.
Host .13, Docker
Container This project's own.
Port Allocate one (suggest 8794).
URL voice.lab.audasmedia.com.au via Caddy on .35
Auth Separate session login.

10. Rules and constraints

  1. It is a demo. Keep calls short, and set a hard time limit.
  2. Never call a real person without Sam's explicit action. No bulk, no cold lists.
  3. Consider whether to disclose that the caller is an AI. Decide in section 13.
  4. Models go through OmniRoute, except MiniMax voice.
  5. Secrets come from environment.d.
  6. Own folder, own container, own port.
  7. This project writes its own docs.

11. Build order

Phase 1 — Skeleton and control page Go service, SQLite, session login, the form, Docker, Caddy route. DoD: the page runs on .13 and the form saves.

Phase 2 — Telephony (the risk phase) Prove that a call can be placed and that audio flows both ways. Decide the provider here. DoD: a real call connects and the app receives the other person's audio. If this fails, stop and report.

Phase 3 — The conversation loop STT → LLM → MiniMax TTS, turn by turn. DoD: a short real conversation happens, with the LLM replying in a MiniMax voice.

Phase 4 — Topic and tone Prompt the LLM from the form's topic, tone, and goal. DoD: two different tones produce clearly different conversations.

Phase 5 — Transcript Store and display each turn live. DoD: the transcript is complete after a call.

Phase 6 — Graceful end Detect the end, hang up, enforce the time limit. DoD: the call ends cleanly at the limit.

Phase 7 — Documentation DoD: project-docs run; docs updated.

12. Acceptance criteria

  • Sam enters a number, topic, and tone, and presses Call now.
  • A real call connects, and the LLM holds a short on-topic conversation.
  • The tone setting measurably changes the conversation.
  • The transcript is stored and readable.
  • The call ends cleanly at the time limit.
  • It runs in its own container on its own port.

13. Decisions and remaining questions

Resolved:

  1. It is a demo, not production.
  2. Last project. Do not start early.
  3. MiniMax voice, OmniRoute for the LLM.

Resolved:

  1. Channel is chosen per call: a phone call, WhatsApp, or Sam's Aussie Broadband service.

Remaining:

  1. Telephony providers. Three channels to prove in Phase 2:
    • Phone call — a SIP/cloud provider (for example Twilio), or the Kotlin app places the call and routes audio.
    • WhatsApp — no public API. Options: WhatsApp Business Cloud API, or screen control through artemis_control.
    • Aussie Broadband — mobile. Confirmed as a mobile service. Confirm whether it exposes an API, SIP, or only a SIM. Phase 2 proves at least one channel end to end.
  2. Which channel is the default?
  3. One-way (speak a message) or two-way (hold a conversation)? The demo implies two-way.
  4. Do we disclose that the caller is an AI? Recommend yes.
  5. Which number does it call from, and who pays for it?