Skip to content

The Conversation Engine (S2S)

File: configs/s2s.yaml
Command: kenzy-s2s [config_path]

Experimental — off by default

This service powers Kenzy's next-generation conversation mode (the follow-up feature): a realtime speech engine that keeps listening after she answers — and while she thinks — so you can talk back and forth without repeating the wake word. The server routes conversations through it only when s2s.enabled is switched on (see the server's follow-up section) and only on nodes with echo-cancelling speakers; everywhere else, and with the toggle off, the classic pipeline runs exactly as before.

What it is

kenzy-s2s is an orchestration layer, not another model host. It composes the services you already run — speech-to-text from the STT service, replies from a language-model provider, speech from the TTS service — behind one realtime, streaming session protocol. No model weights load here, and your voice identity stays whatever the TTS service is configured to speak.

Service basics

Key Default What it does
host 127.0.0.1 Bind address. Loopback by default, like every service.
port 8771 The engine's WebSocket port.
log_level info Service log verbosity.

The model provider

The engine talks directly to an OpenAI-compatible chat provider — cloud, or a model server on your own hardware (vLLM, llama.cpp, a LiteLLM proxy). This is deliberately a separate choice from the LLM service's model: the conversation engine wants a model tuned for realtime latency, and you may well run a different one there.

Key Default What it does
provider.base_url (empty) OpenAI-compatible endpoint. Empty means OpenAI's API; point it at your own model server to stay local (e.g. http://127.0.0.1:8000/v1).
provider.model gpt-5.1 The model name the provider serves.
provider.auth_env OPENAI_API_KEY Which environment variable holds the key. Use CUSTOM_LLM_API_KEY with a custom base_url — your OpenAI key is never sent to a non-OpenAI endpoint.
provider.temperature (null) Sampling temperature. Null = the provider's default (OpenAI's newer models reject any explicit value); set 0.0 for deterministic local serving.
provider.max_output 512 Reply-length ceiling per turn, in tokens.
provider.timeout 60.0 Per-request timeout, seconds.
# Fully local example — a vLLM server on the same box
provider:
  base_url: "http://127.0.0.1:8000/v1"
  model: "qwen-moe"
  auth_env: "CUSTOM_LLM_API_KEY"

Stage services

The speech stages are the STT and TTS services you already run. When they register with the server, their addresses are wired in automatically — you normally configure nothing here. An explicit url wins over the automatic one (the multi-host escape hatch).

Key Default What it does
stt.url (auto-wired) The STT service's /transcribe endpoint.
stt.timeout 30.0 Transcription timeout, seconds.
tts.url (auto-wired) The TTS service's /speak endpoint.
tts.timeout 30.0 Synthesis timeout, seconds.

Using a cloud realtime engine instead

kenzy-s2s is the default engine, and the default stays local. The server can instead point the whole conversation path at OpenAI's Realtime API — set on the server (not in this file):

# server.yaml (or Settings → Backend services)
s2s:
  enabled: true
  profile: openai-realtime   # default: kenzy (this local service)
  # model: gpt-realtime      # optional; this is the profile's default

The connection authenticates with OPENAI_API_KEY from the server's environment (a custom endpoint set via s2s.url uses CUSTOM_LLM_API_KEY instead — the OpenAI key is never sent to a non-OpenAI host). With the cloud profile active, this local service isn't used and doesn't need to run.

What the cloud engine hears

With profile: openai-realtime, all room audio captured during a conversation streams to OpenAI — the model hears everything said, including anything sensitive spoken aloud. There is no pre-screening: audio leaves before any transcript exists to check. If someone says a password mid-conversation, it reaches the provider as audio. That is the trade of a cloud realtime engine; make it knowingly.

Two things do not change: secrets Kenzy stores for you (the lockbox) never enter any model or any cloud reply — spoken readback still routes through the local lockbox flow — and who-may-do-what stays decided locally: every tool call is checked against the speaker's locally-resolved voice identity before it runs.