Give voice a way to be built

The service was finished and measured — answer latency, chunking, the three
seams — and had no Dockerfile, no gate and no image. So it ran nowhere:
/v1/voice answers 404 in production while the code sits complete in main, and
nothing that wants a spoken conversation can have one.

The gate here runs the tests, not just a compile. Nothing in this tree opens a
database; the seams are interfaces and talk_test.go drives them with stand-ins,
so the suite is hermetic and belongs on every push rather than in a lane of its
own.

Everything it reaches is already ours and needs no argument: speech at
speech.hanzo.svc, the model through api.hanzo.ai, JWKS from hanzo.id. Audio is
billed to whoever spoke, never to a service account.

Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
2026-08-15 09:24:59 -07:00
co-authored by Hanzo Dev
parent 03a9f42e10
commit fdb98ab60f
3 changed files with 76 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
# Canonical caller — every knob lives in /hanzo.yml, none here.
# Runs on the git-runner fleet at git.hanzo.ai, the only pool serving these
# labels. github.com resolves only .github/workflows and has no runner for
# them, so a caller placed there is a gate that cannot be scheduled.
name: CI/CD
on:
push:
branches: [main]
tags: ['v*']
pull_request:
workflow_dispatch:
jobs:
cicd:
uses: hanzoai/ci/.hanzo/workflows/build.yml@v1
secrets: inherit
+38
View File
@@ -0,0 +1,38 @@
# syntax=docker/dockerfile:1
FROM public.ecr.aws/docker/library/golang:1.26.5-alpine AS build
# go.mod pins the toolchain. The golang base sets GOTOOLCHAIN=local, which turns
# a `go` directive newer than the image into a hard failure rather than a fetch.
ENV GOTOOLCHAIN=auto
RUN apk add --no-cache git
WORKDIR /src
# hanzoai/authz is private, so it is fetched over authenticated git rather than
# the public proxy. gh_token is the shared build secret; absent it, this is a
# no-op and the build works for anything already public.
ENV GOPRIVATE=github.com/hanzoai/* \
GONOSUMDB=github.com/hanzoai/*
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=secret,id=gh_token \
if [ -s /run/secrets/gh_token ]; then \
git config --global url."https://x-access-token:$(cat /run/secrets/gh_token)@github.com/".insteadOf "https://github.com/"; \
fi && \
go mod download
COPY . .
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 go build -ldflags="-w -s" -o voice ./cmd/voice
FROM public.ecr.aws/docker/library/alpine:3.21
LABEL maintainer="https://hanzo.ai/"
# Certificates, because every seam this reaches — speech, the model, IAM's JWKS —
# is over TLS.
RUN apk add --no-cache ca-certificates && update-ca-certificates \
&& adduser -D hanzo -u 1000
USER 1000
WORKDIR /
COPY --from=build /src/voice /voice
EXPOSE 8140
ENTRYPOINT ["/voice"]
+23
View File
@@ -0,0 +1,23 @@
# hanzoai/voice — a spoken conversation over one socket.
#
# The service was complete and measured before it had any way to be built, so it
# ran nowhere: /v1/voice answered 404 in production while the code sat finished
# in main. This file is the missing half.
#
# `go test ./...` is the real gate here, unlike the sibling services whose suites
# need a database. Nothing in this tree opens one — the seams (Turn, Mind, Meter)
# are interfaces, and talk_test.go drives them with stand-ins, so the suite is
# hermetic and fast and belongs on every push.
test:
- name: build-vet-test
run: |
set -e
go build ./...
go vet ./...
go test ./...
images:
- name: voice
context: .
dockerfile: Dockerfile
repo: ghcr.io/hanzoai/voice