Caret Docs typewithcaret.com →

caret/v4 · reference implementation

The public reference backend.

Two complete caret/v4 backends and two conformance checkers live in this repository under reference/: one in Go, one in Python. Both are dependency-free, both run in one command, and each language's checker is run against the other language's server, so their agreement about the protocol is demonstrated rather than asserted.

Start here, not from the spec.

Clone the repository, pick Go or Python, run it, and wire its lanes to the recognizer, model and image tool you already use. That is a backend. Go is the recommended default: the one to read first and the one to deploy. Python is the supported alternative; it implements the same protocol, passes the same checks, and composes the same caret-cleanup/1 digest. Neither imports the other, and neither imports anything that is not shipped with its language. Writing your own backend against the protocol page is for runtimes these two cannot run on. If you would rather hand the whole job to a coding agent, give it the agent instructions.

Run it

# the recommended path
git clone https://github.com/kballenegger/caret-docs
cd caret-docs/reference/go
go run ./cmd/caret-v4-backend -addr 127.0.0.1:8080 -keys dev-key

# in another terminal
go run ./cmd/caret-v4-conform -url http://127.0.0.1:8080 -key dev-key -insecure

That is a conforming backend. With no lane flags it serves /dictate with built-in loopback providers: no model, no network, no API keys of anyone's. Enough to point a client at, to run the checker against, and to develop against on a plane. The Python alternative is the same two commands:

cd caret-docs/reference/python
python3 -m caret_v4 serve   --addr 127.0.0.1:8080 --keys dev-key
python3 -m caret_v4 conform --url http://127.0.0.1:8080 --key dev-key --insecure

Python 3.11 or newer, no virtualenv and no pip install, because there is nothing to install. Full setup, the lane grammar for wiring real recognizers and agents, TLS, and an honest list of limitations are in reference/README.md.

Wiring real providers

A lane is a string, and the same grammar works for every provider in both languages: loopback for the built-in deterministic provider, command:<argv> to run a program, an https:// URL to POST JSON, or none to turn the lane off.

go run ./cmd/caret-v4-backend \
  -keys dev-key \
  -stt 'command:whisper-cli --model base.en --file {audio} --output-txt -' \
  -agent 'command:my-agent --prompt-stdin' \
  -cleanup 'command:my-agent --prompt-stdin'

Capabilities follow the wiring, never a config flag: a route with no provider is advertised false on /health and answers not_supported. A backend with no speech lane reports status: not_ready with a no_stt blocker, because /dictate is mandatory and pretending otherwise is the failure mode the protocol exists to prevent.

A command: speech lane receives a WAV file ({audio} in the argv, or CARET_AUDIO_PATH), the client's vocabulary in CARET_VOCABULARY and the language hint in CARET_LANGUAGE_HINT; agent, image and cleanup commands read the prompt on stdin and write the answer on stdout. An https:// lane POSTs one JSON object and reads one back:

LaneRequest bodyResponse body
speechcodec, sample_rate_hz, channels, audio_base64, vocabulary, language_hint{"text": "…"}
agentprompt, visible_text, vocabulary{"text": "…"}
imageprompt, aspect_ratio, quality{"mime_type": "image/png", "data_base64": "…"}
cleanupsystem, transcript, prompt{"text": "…"}

A provider that speaks a different shape gets a small local shim that translates, with the provider's key in the shim's environment and never in the lane string. The adapter contract belongs to the reference, not the protocol; the protocol only sees what /health advertises and what the routes return. A command: or https:// speech lane is buffered, so partials.dictate is advertised false and results say stt_route: "fallback". Live partials need the streaming half of the speech interface in lanes.go or lanes.py.

Goals, in order

  1. Readable before runnable. It is meant to be read: every rule on the protocol page implemented once, in an obvious place, in a codebase small enough to hold in your head.
  2. Dependency-free. Standard library only in both languages, WebSocket framing included. No framework, no build pipeline, no container, no lockfile. go build ./... works offline on a fresh checkout.
  3. Pluggable lanes, honest capabilities. Speech, agent, and image providers plug in behind narrow interfaces; whatever fails to resolve is reported off in /health rather than papered over.
  4. A conformance checker, not just a server. The deliverable is as much the harness that can interrogate any V4 backend as it is the backend itself.

Shape

Every rule the protocol states is implemented once, in the file its name suggests, in both languages.

reference/
  go/                        recommended
    protocol.go              error table, close codes, audio and vocabulary rules
    ws.go                    RFC 6455, standard library only
    server.go                /health, routing, credentials, the result cache
    session.go               the one lifecycle state machine, three finalizers
    lanes.go                 recognizer, agent, image and cleanup providers
    cleanup.go               caret-cleanup/1, consumed from spec/cleanup/v1/
    conformance.go           the checker
    cmd/caret-v4-backend/    the server binary
    cmd/caret-v4-conform/    the checker binary
  python/                    the supported alternative
    caret_v4/                protocol.py ws.py server.py session.py lanes.py
                             cleanup.py conformance.py __main__.py
    tests/                   contract tests over a real socket

Design decisions

DecisionRationale
One lifecycle state machine shared by all three routes The protocol says the routes differ only in finalizer and result type; the code should prove that by construction.
Audio buffer independent of the recognizer §8 of the protocol makes fallback mandatory; the buffer is the server's half of the reliability contract.
Dictate-only is the default build A conforming backend needs speech and nothing else. Agent and image lanes activate only when configured, and /health says so.
Vocabulary reaches both the recognizer and cleanup Advertising "vocabulary": true while dropping the list is forbidden by the protocol; the lane interfaces carry it explicitly so no provider can lose it silently.
Zero retention by default In-memory audio, bounded; cached results expire in minutes; no transcript logging. Matching §10 exactly.

The conformance checker

The checker treats the backend under test as a black box at a base URL, any language, any host, a production backend or a first attempt written this afternoon. It exercises what is easy to get subtly wrong: the shape of ready, a missing and a wrong credential, audio-total verification and audio_incomplete, the audio echo on the result, exactly-one-terminal-event ordering after queued partials, cumulative (never regressing) partial text, replay with a repeated client_request_id served from cache, cancel before and after finalize with a normal 1000 close and no terminal event, polish: false returning the raw transcript, unknown JSON fields ignored, honest capabilities versus served routes, text input only where text_input says so, sha256 and byte_length matching the image bytes actually delivered, buffered STT fallback reporting, and the error table with its close codes and retryable flags, including an oversized frame and a binary frame before ready. Rules it cannot test from outside, such as the 10 second start timeout, the 60 second frame gap and the keep-alive cadence, are printed as SKIP lines so the report says what was not checked. Exit 0 means a V4 client will be happy; 1 means something failed; 2 means the checker could not run. Warnings never fail a run.

go run ./cmd/caret-v4-conform -url https://backend.example.com -key "$CARET_API_KEY"

Point it at your backend, not just at these. It is the harness that makes "conforming" a thing you can check rather than a thing you claim.

What it is not

What the loopback providers are not

Loopback speech counts voiced 20 ms windows and spells the client's vocabulary followed by filler. Loopback cleanup capitalizes a sentence and adds a full stop. Loopback imagine draws a gradient seeded by the prompt. They exist so the lifecycle, vocabulary routing, silence handling and image hashing are exercisable with no model and no network. They are not transcription, not an agent, and not an image model, and they never claim otherwise on /health. Wire a real provider before this faces a keyboard, and read the limitations section of reference/README.md first: in-memory state only, no rate limiting, no clustering.