This repo is the Go media runtime: WebRTC audio over WHIP, Opus/RTP, VAD,
turn-taking, barge-in, session state, and streaming STT/LLM/TTS orchestration.
Module path is github.com/streamcoreai/streamcore-server.
It is not an agent framework. Prompts, tools, and business logic belong in
the user's application, reached through the plugin system or a custom
llm.Client. That boundary is the main thing reviewers hold the line on — a PR
that moves application logic into the runtime will usually be redirected toward
a plugin.
Client SDKs, the SIP bridge, examples, and the ESP32 firmware live in their own
repos under streamcoreai. Send those
changes there.
Chat: Discord · Docs: https://streamcore.ai
cp config.toml.example config.toml # then add credentials
go run . # listens on :8080, signalling at /whipconfig.toml is gitignored. Keep real keys out of issues, PR descriptions, and
pasted logs — and rotate anything you have leaked.
For the quickest working setup, use the speech-to-speech path: one xAI key covers STT + LLM + TTS.
[realtime]
provider = "grok"
[grok]
api_key = "xai-..."The classic three-provider pipeline needs a minimum of two keys, not three —
Deepgram covers both STT (nova-3) and TTS (aura-2-thalia-en), plus one LLM
key. config.toml.example is the authoritative config schema; don't guess at
keys.
Run exactly what CI runs — these four steps are the whole build job:
gofmt -l . # must print nothing
go build ./...
go vet ./...
go test -race ./...CI deliberately has no paths: filters, so every PR produces a status check and
the README badge always reflects the real state of main. The Go toolchain
version comes from go.mod via go-version-file, so bumping the go directive
is all it takes to move CI with it.
Formatting and vet failures are the single most common reason a PR sits.
| Package | Responsibility |
|---|---|
internal/signaling |
WHIP endpoint, SDP exchange, auth |
internal/peer |
Pion peer connection, ICE, DataChannel |
internal/session |
Session IDs, multi-peer state, lifecycle, teardown |
internal/audio |
RTP packetization, Opus encode/decode |
internal/vad |
Energy-based voice activity detection |
internal/pipeline |
Turn-taking, barge-in, response generation, transcript |
internal/stt, internal/tts, internal/llm |
Provider adapters |
internal/realtime |
Speech-to-speech providers |
internal/plugin, internal/tools |
Plugin runtime, native tools, skills |
internal/rag |
Retrieval and embeddings |
internal/turn |
Built-in STUN/TURN server |
internal/config |
TOML config schema and validation |
internal/procstat |
Per-turn latency accounting |
Most first contributions land in a provider adapter (internal/stt,
internal/tts, internal/llm) or in internal/plugin — both have several
existing implementations to copy the shape from.
The media path is timing-sensitive and hard to test from the outside. For
anything touching internal/pipeline, internal/vad, internal/audio, or
internal/peer:
- Say how you verified it with real audio. Unit tests are necessary and not sufficient. "Ran a call with Deepgram STT and interrupted mid-sentence; ducking fired at ~120ms and the in-flight TTS was cancelled" is the kind of detail that gets a media PR merged.
- Don't regress barge-in. Backchannels ("mm-hm", "yeah okay") must stay filtered; a confirmed interrupt must still cancel in-flight LLM and TTS.
- Watch the latency breakdown. The per-turn log line (endpointing, merge, embedding, vector search, LLM, TTS) is the fastest way to see what your change cost. Include a before/after if you touched anything in that path.
- Add a test where the logic is testable.
internal/pipeline,internal/config,internal/audio, andinternal/llmall have real test suites — extend them rather than adding a new pattern.
Changes to the WHIP wire protocol, the DataChannel event shape, or a config key are breaking for anyone running StreamCore in production. Open an issue first and include the migration story.
- Implement the interface in
internal/stt,internal/tts,internal/llm, orinternal/realtime, following the closest existing adapter. - Add its config block to
config.toml.examplewith every key documented. - Wire it into
internal/configvalidation so a typo fails at startup rather than mid-call. - Streaming is the point — TTS must chunk-stream so audio starts before synthesis finishes, and LLM output must stream sentence by sentence. A request/response adapter that buffers the whole utterance will be sent back.
- Update the capability table in the README, and
README.zh-CN.mdalongside it.
- One concern per PR; keep refactors separate from behaviour changes.
- Explain what breaks today, what your change does, and how you verified it.
- Match the surrounding code — naming, comment density, error handling.
- Keep the README honest: capability tables describe what exists today, and the Roadmap section lists what doesn't. Move items between them as you land work.
- If you use an AI assistant, have it read
AGENTS.mdandconfig.toml.examplefirst. StreamCore is newer than any model's training data, and invented config keys and provider names are a recurring source of bad PRs.
See good first issue
and help wanted.
Comment on an issue before starting anything large.
Don't file vulnerabilities as public issues — see SECURITY.md.
Apache 2.0. See LICENSE. By contributing you agree your contribution is licensed under it.