Small scripts that place SIP calls and play synthesized speech. They listen for yes/no or spoken answers and keep audit audio for approvals. https://sip.sad.ovh
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-10-15 10:43:03 +03:00
docs docs: add full technical documentation suite (architecture, pipeline, daemon, security) 2026-10-15 10:43:03 +03:00
gocall msg: split outgoing messages into clean chunks to avoid UDP MTU limits 2026-10-15 10:39:22 +03:00
.gitignore full Go cutover: agentd daemon, gocall ask mode, retire baresip calling 2026-10-14 09:09:08 +03:00
README.md docs: reference technical documentation suite in README 2026-10-15 10:42:19 +03:00
sip-agent.service add sip-agent.service systemd user unit 2026-10-15 09:27:06 +03:00

sip-agent

Speak and chat through SIP as agent@sip.sad.ovh. A pure Go SIP agent (agentd) manages both the voice plane (diago) and chat MESSAGE plane (sipgo) concurrently. Plays neural synthesized speech (KittenTTS via native ONNX Runtime in Go) and transcribes responses (Groq cloud STT). No Python runtime, no baresip.

Extensions on the server: 101 rings sophie, 102 rings the agent, 600 is the echo test. --to takes a name or a number, so --to sophie and --to 101 reach the same phone, and --to echo runs the echo test.

Documentation suite

Detailed system documentation is available in the docs/ directory:

Dial plan config

All of it is handled in gocall/internal/config with env overrides, so no code edits are needed for a new box or a new contact:

SIP_DOMAIN      default sip.sad.ovh
SIP_USER        default agent
SIP_DEFAULT_TO  default 101
SIP_SOPHIE_EXT  default 101
SIP_AGENT_EXT   default 102
SIP_ECHO_EXT    default 600
SIP_EXTRA_EXTS  extra names, e.g. "bob=103,alice=104"

So SIP_EXTRA_EXTS="bob=103" ./gocall/bin/gocall --to bob "hi" dials 103, and the pre-dial presence check follows the name.

Quick start

Voice calls

One-off announcement, no answer needed:

./gocall/bin/gocall --to 101 "Hello Sophie, this is the agent"

Ask something and wait for yes or no:

./gocall/bin/gocall --to 101 --listen-yes "May I restart the box? Answer yes or no."

Ask an open question and print the reply as text:

./gocall/bin/gocall --to 101 --ask "Which host is slow right now?"

Check the audio path without bothering anyone:

./gocall/bin/gocall --to 600 "echo check"

SIP Messages (Chat)

Send a SIP MESSAGE instantly:

./gocall/bin/agentd send --to 101 "Hello from the agent"

Run or test the multi-agent SMS-style reply loop:

# Test simulated message against the active agent session
./gocall/bin/agentd loop --test "What are you working on right now?"

How a call runs

  1. Secret retrieval is native Go via OpenBao AppRole login (gocall/internal/secret). The SIP password lives in memory only.
  2. The message is rendered to an 8 kHz mono wav by the native KittenTTS engine, with 4 s of lead silence and the text repeated twice.
  3. gocall dials via agentd (or registers on demand in one-shot mode). The wav plays as the mic while an RX-only tap records decoded peer audio to mono rec.wav. Our own TX never enters the recording, so the negation veto cannot fire on our question.
  4. For --to 600 the script checks the recording level. RMS above -60 dB means audio returned through Echo, confirming both TX and RX.

Voice & KittenTTS

Default voice is Jasper from KittenTTS Mini 0.8.

The model is evaluated natively via ONNX Runtime inside gocall/bin/tts-daemon behind a unix socket (tts.sock), completely free of Python dependencies. Rendered lines are cached on disk under ~/.cache/sip-agent/tts/ by hash of text plus voice.

Flags:

  • --tts-engine espeak: skips the neural voice and uses espeak-ng.
  • --voice <name>: selects an alternate Kitten voice (e.g. Jasper, Bruno).

Answers and approvals

--listen-yes loops the question for --listen-window seconds (default 50) and polls the tail of the recording through Groq whisper-large-v3-turbo. The API key lives in .groq_api_key, mode 0600.

The rule is fail closed:

  • Silence, timeout, or remote hangup without an explicit yes counts as denied.
  • A yes requires two consecutive positive detections to filter out echo or line blips.
  • Any negative keyword (no, nope, nah, stop, don't, etc.) in the transcript vetoes approval.
  • Every granted approval persists its audio and transcript under approvals/.

gocall --ask is the open-ended form. It plays the question once, holds the call until hangup or --timeout expires, and prints ANSWER: <transcript>.

Unified Agent Daemon (agentd)

gocall/bin/agentd manages both the voice plane and the SIP MESSAGE plane in a single process:

  • agentd start|stop|status|restart: controls the persistent daemon holding agentd.sock.
  • agentd send --to <target> "<message>": sends an out-of-dialog SIP MESSAGE with digest auth and automatic UDP MTU chunking.
  • agentd loop: watches inbox/messages.log, dynamically auto-detects the active agent session (Antigravity or OpenCode) by modification timestamp, prompts the session, and replies over SIP MESSAGE.
  • Background integration: agentd serve automatically runs the reply loop in the background under systemd (sip-agent.service).

Repository layout

Path What it is
docs/ Comprehensive technical documentation suite
gocall/cmd/agentd/ Unified daemon: voice job socket + SIP MESSAGE receive/send/loop
gocall/cmd/gocall/ Dial CLI: --listen-yes confirm, --ask Q&A, --to 600 echo test
gocall/cmd/tts-daemon/ Native Go KittenTTS ONNX inference daemon
gocall/internal/call/ SIP call dialog management and RTP playback/recording
gocall/internal/kitten/ ONNX KittenTTS tokenizer and inference wrapper
gocall/internal/msg/ SIP MESSAGE receive, chunking, and send engine
gocall/internal/secret/ OpenBao AppRole client (memory-only password fetch)
gocall/internal/stt/ Groq Whisper API client
gocall/internal/tts/ TTS orchestration, disk caching, and sox assembly
sip-agent.service Systemd user unit (symlinked to ~/.config/systemd/user/)
approvals/ Stored audio and transcripts for approved voice votes
inbox/ Received chat messages (inbox/messages.log), runtime only
spool/ Temporary call wavs

Requirements

  • Go 1.22+
  • sox, espeak-ng
  • ONNX Runtime library (libonnxruntime.so)
  • Groq API key in .groq_api_key
  • OpenBao AppRole credentials