Voice plugin

June 15, 2026 · View on GitHub

Draftcat's voice build tag adds an HTTP receiver for voice-AI session events and three pipeline harvest actions that consume them. The orchestrator (Dograh, Pipecat-direct, or any other) calls draftcat over webhooks. Draftcat owns governance: per-day call budgets, schema-validated outcomes, operator approval before any downstream write, and a 7-step Learning-Item review flow.

The plugin is intentionally orchestrator-agnostic: the 5 receivers accept any JSON-posting caller. The reference integration target is Dograh (github.com/dograh-hq/dograh), which wires up via its built-in webhook nodes.

Building

go build -tags voice -o draftcat
go test  -tags voice ./...

Without the tag, the lean binary is unchanged.

Configuration

Add a voice block to config.yaml. The bearer token must match what Dograh sends in the Authorization: Bearer <token> header (configured in Dograh as a BEARER_TOKEN webhook credential).

voice:
  enabled: true
  listen_addr: 127.0.0.1:8080
  public_base_url: https://draftcat.client.eu
  auth:
    method: bearer
    token_env: DRAFTCAT_VOICE_TOKEN
  dograh:
    base_url: https://dograh.client.internal
    api_key_env: DOGRAH_API_KEY
  pre_call:
    enabled: true
    timeout_ms: 300
    routing_rules:
      - {if: support_tier == 'enterprise', workflow: dach-enterprise}
      - {if: lang == 'de-CH',              workflow: schweizerdeutsch-screener}
      - {default: standard-de}

budgets:
  per_day_calls: 200
  per_day_call_minutes: 1500

Run behind Caddy or nginx for TLS termination. Dograh requires HTTPS webhooks.

Endpoints

All endpoints require Authorization: Bearer <token> when auth.method=bearer. All accept Content-Type: application/json and respond with {"ok": true} on success.

EndpointDirectionPurpose
POST /voice/session_startDograh → draftcatCall connected. Persists to voice_sessions.
POST /voice/eventDograh → draftcatMid-call event (utterance, intent, tool call). voice_events.
POST /voice/session_endDograh → draftcatCall ended with outcome, transcript, cost. voice_outcomes.
POST /voice/handoffDograh → draftcatTransfer-to-human request. voice_handoffs. Response carries routing target.
POST /voice/learningDograh → draftcatAgent-flagged Learning-Item. voice_learnings.
POST /voice/pre_call_contextDograh → draftcatEnrich greeting with account context. Sub-300ms p95 budget.

Wiring Dograh

Dograh's webhook nodes execute asynchronously after a workflow run completes. Place separate webhook nodes on different paths in your workflow so they reach the right draftcat endpoint with the right payload.

Minimal workflow wiring (one node per draftcat endpoint that path needs):

Workflow positionWebhook targetPayload template includes
First node (always reached)/voice/session_startid, caller_phone, workflow
Terminal node (always)/voice/session_endsession_id, outcome, transcript, recording_url
On handoff branch only/voice/handoffsession_id, reason
On learning-flag branch/voice/learningsession_id, description, category, severity
First node (HTTP custom)calls /voice/pre_call_context synchronously and stores result in initial_context

Example payload for /voice/session_end (Dograh template):

{
  "session_id": "{{workflow_run_id}}",
  "ended_at_ms": "{{call_time_ms}}",
  "outcome": {{gathered_context.outcome | tojson}},
  "transcript": {{transcript_json | tojson}},
  "recording_url": "{{recording_url}}",
  "cost_cents": {{cost_info.cost_cents}}
}

Pipeline actions

Seven new actions are available once draftcat is built with -tags voice.

Harvest (deduped by record ID per (pipeline, scope))

ActionYields
voice_calls_completeddata.voice_calls, data.voice_call_count
voice_handoffs_pendingdata.voice_handoffs, data.voice_handoff_count
voice_learnings_newdata.voice_learnings, data.voice_learning_count

Resolve

ActionBehavior
voice_handoffs_resolveReads data.ai_output.resolutions[].{handoff_id, target} and writes resolutions back to the voice store. Sets data.voice_handoffs_resolved_count.

Admin (used by the 7-step guardrail pipeline)

ActionBehavior
git_commit_workflow_updateOptionally writes data[content_var] to vars.path, then git add + git commit in vars.repo_dir. Sets data.voice_admin_commit_sha.
dograh_staging_smokePOST /api/v1/public/agent/workflow/{workflow_uuid} to voice.dograh.staging_url. Sets data.voice_admin_smoke_run_id.
dograh_prod_publishPUT /api/v1/workflow/{workflow_id} to voice.dograh.base_url with the JSON read from vars.definition_path. Dograh auto-versions on PUT, preserving prior versions. Sets data.voice_admin_publish_status.

Example pipelines

State

The plugin reuses draftcat's existing SQLite state database (state.db by default). On first start with -tags voice, five new tables are created: voice_sessions, voice_events, voice_outcomes, voice_handoffs, voice_learnings.

Smoke test

With draftcat running and a bearer token set:

export TOKEN="$DRAFTCAT_VOICE_TOKEN"
SID="smoke-$(date +%s)"

curl -sS -X POST http://127.0.0.1:8080/voice/session_start \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d "{\"id\":\"$SID\",\"caller_phone\":\"+491234567890\",\"workflow\":\"smoke\"}"

curl -sS -X POST http://127.0.0.1:8080/voice/session_end \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d "{\"session_id\":\"$SID\",\"outcome\":{\"decision\":\"qualified\",\"score\":85},\"cost_cents\":1200}"

# Next run of voice-dach-screener pipeline harvests the session and triggers
# operator approval on Telegram.