16 KiB
Repository guidance
Scope and working conventions
This repository is Vocero: a browser voice client, its FastAPI integration server, a speech pipeline, and optional Node-RED flows. Use the code, commands, configuration, and documentation within this checkout. Do not assume other repositories or developer-specific directories are available.
All file and directory references in this guide are relative to the repository root. HTTP routes mentioned below are URLs, not filesystem paths.
- Check the current branch, worktree, and working-tree changes before editing.
Honor the checkout requested by the user, including direct work on
main. Use an isolated worktree when requested and preserve other worktrees. - An analysis-only or planning request authorizes inspection, not implementation. Return findings with source locations. Present plans in the conversation unless the user asks to save a file; follow any explicit approval gate.
- Write clear explanations and ask one conceptual question at a time. Carry out already authorized work without repeatedly asking for permission.
- Use the native patch/edit tool for manual changes. Prefer
rg,fd,jq,git, and standard CLI tools for inspection; check availability before assuming a tool is missing. Do not install system-wide packages unnecessarily. - When commits are requested, make coherent, scoped Conventional Commits as work progresses. Avoid excessive micro-commits and unrelated cleanup.
- Invoke
work-on-featureonly when explicitly requested. For a requested PR, follow the availablepr-descriptionskill exactly and put verification details in the handoff when its format excludes them. Issues are optional; create native Gitea issues or post comments only when requested. - When asked to merge locally, use the requested rebase/fast-forward flow; preserve a recovery ref before rewriting and verify retained patches. A local merge does not imply a push, deployment, or restart. Report those separately.
- Treat old conversations and proposals as context. Verify claims against the current code and distinguish proposed authentication/deployment changes from implemented behavior.
Project map
| Location | Responsibility |
|---|---|
s2s-client/ |
The complete uv project: manifests, environment, Python tooling, tests, and FastAPI package |
s2s-client/s2s_client/main.py |
Application assembly, documentation mount, and retained redirects |
s2s-client/s2s_client/api/ |
HTTP routes, route registration, and response validation helper |
s2s-client/s2s_client/models/ |
Pydantic request and response contracts |
s2s-client/s2s_client/core/ |
Environment parsing, logging, public URLs, and startup diagnostics |
s2s-client/s2s_client/providers/ |
ApiPilot and caller/profile/report HTTP integrations |
s2s-client/s2s_client/services/ |
Prompt/context construction, reasoning, and tool-context rendering |
s2s-client/s2s_client/realtime/ |
WebSocket relay, event handling, marker filtering, and audio forwarding |
s2s-frontend/ |
React, TypeScript, Tailwind, and Vite browser application |
speech_to_speech/ |
Separately run VAD → STT → LLM → TTS pipeline and realtime API |
s2s-reasoning-engine/ |
Optional Node-RED reasoning/logging service |
start_ui.sh |
Dependency sync and supervision of FastAPI plus Vite |
docs/ |
Maintained technical documentation, examples, and historical proposals |
s2s-client/server.py is a compatibility shim for server:app; new backend
implementation belongs in the package. Keep HTTP handlers focused on transport
and reuse providers/services for shared behavior.
The old static browser site, daily talk-time quotas, quota database/accounting, and Hugging Face account UI were deliberately removed. Do not restore their files, routes, dependencies, or compatibility layers as incidental cleanup. Load-balancer allocation/queues and upstream caller authentication remain.
Tooling and checks
The uv project must stay inside s2s-client/, including
s2s-client/pyproject.toml, s2s-client/uv.lock, s2s-client/.python-version,
s2s-client/.venv/, s2s-client/Makefile, and
s2s-client/.pre-commit-config.yaml.
Use Python 3.12 and the uv version required by tool.uv.required-version
(currently 0.12.5). Update dependencies through uv and commit the lockfile;
do not use pip to modify this environment or move it to the repository root.
Keep uv pins in s2s-client/Dockerfile and setup documentation aligned when
changing the required version.
The frontend uses npm and s2s-frontend/package-lock.json. Its Node requirement is
^22.18.0 || >=24.0.0; consult s2s-frontend/package.json for current scripts.
Keep application code and supported configuration files in TypeScript.
Commands below run from the repository root:
| Command | Purpose |
|---|---|
make -C s2s-client install |
Sync the locked Python environment and development tools |
make -C s2s-client format |
Apply Ruff fixes and formatting |
make -C s2s-client check |
Check lock freshness, formatting, Ruff lint, mypy, and pytest |
make -C s2s-client test-launcher |
Test readiness, failures, dependency sync, and process-group shutdown |
make -C s2s-client test-image |
Build and smoke-test the actual backend Docker image |
npm --prefix s2s-frontend ci |
Install locked frontend dependencies |
npm --prefix s2s-frontend run check |
Check formatting, lint, TypeScript, Vitest, and launcher integration |
npm --prefix s2s-frontend run build |
Type-check and build frontend assets |
Python uses Ruff with a 120-character line limit, double quotes, import ordering,
and typed application functions checked by mypy. Frontend formatting and lint
rules live in s2s-frontend/prettier.config.ts and
s2s-frontend/eslint.config.ts; use those tools rather than introducing
competing conventions.
Run checks appropriate to the change: backend checks for Python/API work, frontend checks and build for browser work, launcher integration for supervisor changes, and image tests for packaging/Docker changes. Documentation-only work normally needs link/path and diff checks, not service startup. Wait for commands to finish before reporting success, and state skipped coverage or blockers.
Tests should use mock upstream HTTP and local WebSocket fixtures, isolating environment configuration. Do not rely on deployed services, real credentials, or model downloads for client tests. The client gate does not test the separate speech pipeline, microphone hardware, or real model output. That pipeline has no complete dependency manifest or general test gate in this checkout; inspect the selected backend and explain the verification limits for speech changes.
API and compatibility rules
Read backend development and compatibility before refactoring the server or changing schemas. Existing wire behavior is the baseline, including behavior that looks unsafe, inconsistent, or buggy. Intentional bug fixes or contract changes need their own scope and regression evidence; do not bundle them into tooling or structural refactors.
- Preserve paths, methods, statuses, error shapes, headers, field names, omissions, coercion, authentication precedence, and outgoing provider calls. Trace both frontend consumers and provider parsing; a permissive consumer is not evidence that an upstream schema can safely be tightened.
- Retain existing request-model defaults and extra-field behavior. Do not make new fields required, forbid previously accepted extras, or normalize loosely parsed provider values without evidence and an intentional behavior change.
- Response models validate fields constructed by this service. Upstream-owned
values remain permissive where the old parser was permissive:
Any, optional fields, open objects, and arbitrary profile JSON are deliberate boundaries. validated_json_responseins2s-client/s2s_client/api/responses.pyvalidates with Pydantic, then serializes the original payload. Do not substitutemodel_dump()or automatic response-model serialization: those can insert defaults, filter unknown keys, coerce values, or alter serialization. Keep OpenAPI declarations aligned with the actual validation boundary.- Preserve environment/dotenv parsing, default flags, and import-time behavior during refactors. Do not introduce stricter configuration validation or change authentication behavior as part of a tooling change.
s2s-client/tests/fixtures/legacy-contracts.jsoncaptures the server before the migration at3d42df5.s2s-client/tests/test_compatibility.pycompares HTTP response bytes, statuses, selected headers, outgoing requests, request schemas, and realtime frames. Do not regenerate captures merely to make a refactor pass.- Known reasoning failures, ApiPilot authentication/cache quirks, and relay behavior are documented in the compatibility guide. Preserve narrow lint/type exceptions that retain those behaviors until a deliberate fix is in scope.
- The pytest warning filter is only for Starlette TestClient's exact deprecated
AnyIO
BlockingPortalalias. Do not broaden warning suppression or upgrade runtime frameworks just to silence that warning without a compatibility review.
Browser and realtime behavior
- React renders the UI;
s2s-frontend/src/realtime/controller.tsowns observable application state,s2s-frontend/src/realtime/client.tsowns protocol/session lifecycle, ands2s-frontend/src/audio/owns the audio graph and worklets. Keep high-frequency audio/WebSocket work outside React rendering and preserve cleanup across unmounts and reconnects. - Preserve the existing 16 kHz PCM16 audio path, worklet registration, interruption
behavior, and teardown. Session startup initializes audio, waits for
session.created, and sendssession.updatebefore audio or the greeting. Asession.updatedacknowledgment is optional; requiring it previously broke startup with this speech service. - Preserve query parameters such as
phoneNumberandepisode_id, existings2s.ws.*preference keys, session-only RAG, and TALK/TALKFIRST behavior unless the task explicitly changes them. Context comes from browser instructions and server/provider calls; do not assume a vector database or a completed magic-link authentication flow exists. - Assistant Markdown is parsed with Marked and sanitized with DOMPurify in
s2s-frontend/src/lib/messages.ts. User text stays literal, and exports retain raw message text. Keep sanitization and unsafe-link regression coverage when changing it. - Reuse the existing design tokens, Tailwind integration, and
s2s-frontend/src/styles/legacy-theme.css. Verify both light and dark themes, narrow screens, keyboard focus, dialog dismissal, and voice-state feedback for UI changes. Preserve intentional?debug=truecontrols and browser audio-permission flows. - User-visible times use
America/Santiagowhere no saved zone exists; avoid browser-local timezone assumptions. Keep raw timestamps in their existing API and export formats.
Startup and deployment boundaries
./start_ui.sh sources .env, syncs s2s-client/.venv/ with
uv sync --project s2s-client --locked, and installs frontend packages when the
manifests change.
It starts Vite on 0.0.0.0:8420 and FastAPI on 127.0.0.1:8421, with
UI_HOST, UI_PORT, and S2S_BACKEND_PORT overrides. Use distinct ports for
concurrent worktrees. Preserve readiness checks, failure propagation, and
shutdown of both process groups, including Uvicorn's reload worker.
Staging intentionally retains Vite HMR and Uvicorn reload. The Vite child uses
NODE_ENV=development even under PM2. Do not replace this with static production
serving unless that deployment change is requested. Vite proxies /api,
/v1/realtime, and documentation routes to FastAPI; preserve public Host headers
used to construct WebSocket URLs. Remote staging needs explicit
S2S_DEV_ALLOWED_HOSTS hostnames and reverse-proxy WebSocket forwarding for both
/v1/realtime and /__vite_hmr.
The established PM2 process name is s2s_ui. When it points to this launcher's
deployed checkout, pm2 restart s2s_ui --update-env also performs the locked uv
sync; a separate sync is optional to prepare dependencies before restarting.
Ensure the required uv and Node/npm are on that process's PATH. Verify the live
PM2 script path and environment before making claims about a deployment.
The launcher does not start or restart speech_to_speech/ or Node-RED. A configured
speech URL does not prove which source revision or implementation runs there.
SPEECH_TO_SPEECH_URL pins a direct browser connection; S2S_PROXY_TARGET enables
the local relay. Either disables load-balancer mode. The Node-RED flows in
s2s-reasoning-engine/flows.json are log-only; preserve the meaning of log_only
responses.
The backend Docker image serves APIs, WebSockets, and root docs/, not the React
UI. Build with the repository root as context and s2s-client/Dockerfile; the
image listens on 7860. FastAPI's root remains 404, /docs is the API explorer,
and project documentation is served below /docs/ with retained legacy redirects.
Language, documentation, and secrets
- Use English for code, identifiers, comments, logs, internal instructions, and developer documentation. Spanish is the default for visible UI, spoken output, and user-facing API messages. Preserve literal wake phrases and protocol names; translating an existing response or prompt can change a contract.
- Keep maintained documentation under
docs/client/,docs/speech/,docs/adr/,docs/examples/, ordocs/proposals/. Update existing documents first and keepdocs/README.mdcurrent when adding a document. Use one canonical English file without language suffixes. Historical proposals do not define current behavior. - Reserve root Markdown for established files such as
README.mdandAGENTS.md. Keep task summaries in the conversation unless a saved artifact is requested. Record notable changes underUnreleasedins2s-client/CHANGELOG.md. - Keep credentials and deployment values out of commits and examples. Never copy
the root
.envinto the frontend or expose backend secrets throughVITE_*. The launcher sources.envbefore startup, so it can override shell values; direct Python startup usesload_dotenv(..., override=False). Preserve both behaviors and avoid importing live configuration when a test can isolate it.
Start with usage, architecture, development, and troubleshooting. For speech work, read the realtime protocol and the relevant STT, LLM, or TTS guide before changing pipeline behavior.