Voxtral-Mini-4B-Realtime (ASR) → EuroLLM-1.7B-Instruct (MT), both on vLLM on one L40S, delivered as per-locale BBB closed captions.
The spec is docs/live-translation-plan-v2.md.
Read it before starting a phase. CLAUDE.md holds the conventions and the invariants that
are easy to violate by accident.
| Phase | State |
|---|---|
| 1a — hardware validation | ⚠ harness written, NOT RUN. Needs the L40S. See scripts/phase1a/. |
| 0 — scaffolding, contracts, security baseline | ✅ done |
| 1b — translation-service | ✅ done, against the mock backend and the draft contract |
| 2 — bridge integration | ⛔ blocked on two external dependencies (below) |
- Re-agree the contract with the bridge owner — see
docs/contract-change-request.md. It adds a caller-owned offset and per-locale accumulation on their side, so it is a larger change than a budget tweak. - Run the delta-stability probe —
scripts/probe_delta_stability.py. If Voxtral's deltas revise rather than append, clause-incremental is unsound and the design reopens. Finals-only (mode A) ships either way.
uv sync # Python 3.14
uv run pytest # unit + contract; no GPU, no network
uv run ruff check . && uv run ruff format --check .
./scripts/check_no_text_logging.sh # payload-logging gate
# Run the service against the mock backend -- no GPU needed.
LAB_MODE=true PYTHONPATH=translation-service:. \
uv run uvicorn app:app --factory --host 127.0.0.1 --port 8080
# Watch translation grow clause by clause, as the bridge would drive it.
uv run python debug-client/replay.pyTRANSLATION_BACKEND=mock is the default everywhere except the GPU suites. If a unit test
starts needing a GPU, the seam is in the wrong place.
Local dev is rootless podman on SELinux; production is docker compose. See CLAUDE.md
for the podman rules that bite (CDI for GPUs, :z labels, named volumes for model caches).
podman-compose up # not `docker compose`translation-service/ the Step 1 deliverable -- a stateless façade over vllm-mt
app.py FastAPI: /translate_incremental, /translate_batch, /translate,
/detect, /languages, /healthz
clauses.py clause-boundary detection past the caller's cursor (§6.5)
prompt.py system → glossary → context → segment, most-stable-first (§6)
fanout.py asyncio.gather over (item × target). NOT a micro-batching queue
sanitize.py the §6 output contract: preamble, refusal, langid, ratio, copy
logging_policy.py the only place allowed to emit a log line
backends/ vllm_openai (primary) · mock (default) · ct2_nllb (Phase 6 only)
translation-relay/ Step 2 stub. Read its README before starting Phase 3
debug-client/ dev viewer, used without BBB or LiveKit
shared/schemas.py the Step 1 contract, as pydantic models
scripts/phase1a/ the measurement harness and its blank report
tests/{unit,contract,integration,load}
docs/ the spec, the contract, the system prompt, the language table
- Originals are never blocked, delayed or altered by translation. MT down ⇒ original captions keep flowing.
- Original and all its translations share the segment's
start_time. Contract-tested. - A failed (segment, target) pair is skipped — never faked, never fatal.
- Never log, label or persist transcript or translation text. Including prompts, which
is why
vllm-mtruns with--disable-log-requests. - Translation is incremental and append-only. Already-shown translated text is never
rewritten, except the optional whole-utterance revision at FINAL behind
revise_final.
The full list, and the reasoning, is in CLAUDE.md.