STT instance health and passthrough
Back to the transcription overview.
Ambient instance health and text passthrough
caco stt status --instance <id> --json distinguishes the owning node, observation provenance, process liveness, and mode-specific health.
A live PID is not evidence of audio, and an unavailable, old, or non-object control response is not a successful health observation.
An instance configured on another node returns health: unavailable on the viewer rather than probing its local PID files.
Use the supported node selector to read the owner:
caco @sgu24 stt status --instance sgu24 --json
caco @sgu24 stt diff --instance sgu24 --cursor 0 --limit 50 --json
Persist each source's returned cursor separately, inspect its status before interpreting an empty diff, and advance only through the entries actually consumed. Neither an empty diff nor a connected text socket proves that a meeting microphone or provider is working. These are read surfaces, not permission to execute ambient instructions, change a producer, or unmute a stream.
Capture mode
Without passthrough, existing local capture/VAD and the authenticated daemon transcription route remain selected; Cag does not pin or invoke the standalone tools stt executable.
Status includes actual CoreAudio callback counts (or pipe-read batches), frames/samples, last frame/VAD/voice/segment/request-result times, pending request age, and consecutive errors.
silent_valid_frames, receiving_voice, starting, no_frames, processing_stale, telemetry_stale, permission/device failures, and provider degradation are distinct.
Muted capture is intentional inactivity, not missing-audio failure.
Read-only AVFoundation permission preflight runs in the actual capture process's user context; denial or undetermined authorization stops that launch without requesting access or modifying TCC/default devices.
No frames is reported as no frames, not diagnosed as TCC denial.
Health thresholds use CACO_STT_STARTUP_SECS (15), CACO_STT_FRAME_STALE_SECS (15), CACO_STT_PROGRESS_STALE_SECS (45), and CACO_STT_PROVIDER_PENDING_SECS (85).
Values are bounded to 1–300 seconds, with pending age capped at 90 seconds; thresholds do not extend provider deadlines.
Silence and provider failure never restart the microphone.
Only missing frame/processing progress restarts the existing owned child; generation reaping, singleton locks, and exponential backoff up to five minutes prevent overlap or restart storms.
A persisted operator mute survives supervision restarts, including a service configured with unmuted: true; explicit unmute is needed to resume after a persisted mute or permission failure.
Capture drains independently from transcription, with one active request and one queued bounded segment. Queue saturation/expiry is explicit dropped-batch metadata, not unbounded buffering or billed-audio replay. Each segment has one stable request ID across HTTP, result, and health metadata; its queue-plus-request budget is at most 90 seconds and the handler has an 85-second total budget. Busy audit/policy admission fails before dispatch rather than waiting behind an unrelated message-store lock. A result-audit refusal or ambiguous transport timeout does not authorize replay of admitted audio; handler/admission and provider failure categories remain distinguishable.
Optional text-only mode
An instance may instead mirror an external transcript producer through typed service configuration, so the producer is the single listener on the microphone.
The source is exactly one of a WebSocket url or a producer command:
activation:
required_capabilities: [stt_passthrough_v1]
deferred_fields: [services, nodes, dynamic_nodes]
source_commit: "0000000000000000000000000000000000000000" # Replace with the reviewed source commit.
services:
caco-stt-daemon:
node: example-node
name: ambient
scratchpad_id: transcription:ambient:ambient
passthrough:
url: wss://example.invalid/transcripts
token_file: ~/.config/example/stt-token
stale_after_secs: 120
reconnect_max_secs: 60
max_message_bytes: 65536
A producer command, such as lifetail following its own transcript journal, needs the stt_passthrough_v2 capability as well:
activation:
required_capabilities: [stt_passthrough_v1, stt_passthrough_v2]
deferred_fields: [services, nodes, dynamic_nodes]
source_commit: "0000000000000000000000000000000000000000" # Replace with the reviewed source commit.
services:
caco-stt-daemon:
node: example-node
name: phone
passthrough:
command: [lifetail, --json, follow]
resume_args: [--after, "{after}"]
stream: lifetail
initial: head
The same WebSocket producer can be mirrored directly with url: ws://example-host.example-tailnet.ts.net:8402/v1/stream, token_file, and resume_query: after.
The same passthrough object works under node-scoped and dynamic-template STT service entries.
The activation capability is required so an older runtime retains its last compatible configuration instead of ignoring the new field and starting capture.
stt_passthrough_v2 is required for command, resume_args, resume_query, stream, non-default initial, or plain ws:// beyond loopback.
This mode constructs no microphone, VAD, transcription provider, or audio-proxy request; it does not install or supervise the external producer.
Its health is connection/last-message/last-adoption time, reconnects, rejected messages, adoption failures, and configured text staleness, with no capture PID used as health proof.
A connected-but-stale stream describes missing text, not a diagnosed producer microphone/provider failure.
upstream_quiet means no text within stale_after_secs while the upstream is demonstrably alive (any frame or ping, or a running producer); like silent_valid_frames, a quiet room is healthy.
upstream_stale means no text and no liveness evidence, and upstream_unauthorized means the producer or endpoint refused the credentials.
Each assembled UTF-8 websocket text message, or each producer stdout line, is either newline-delimited raw text or one final JSON segment:
{"type":"segment","text":"Synthetic example.","stream_id":"producer-session","sequence":42}
Empty lines are ignored; identified JSON messages remain one segment even with embedded newlines.
type may be omitted or be segment/final; unknown types, malformed identities/JSON, non-text frames, oversized messages, and messages exceeding 128 lines are rejected with metadata-only diagnostics. lifetail frames are also understood: record (seq becomes the sequence of the configured stream), hello (its head is the producer's newest sequence), and partial (counted and ignored).
A CLI error envelope ({"status":"error","error":{"category":"missing_permission"}}) reports upstream_unauthorized.
Sequences are unsigned, contiguous, monotonically increasing per stable stream ID; the first observed sequence establishes that stream's boundary.
Known replay at or below the durable high-water mark causes no second transcript, scratchpad append, or wake; a forward gap first asks a resumable producer to replay from the durable cursor (resyncs); a gap the producer cannot fill is then accepted explicitly, counted in gaps and logged, rather than reordered or allowed to wedge every later segment.
With resume_query or resume_args, every reconnect or respawn resumes after the durable cursor, so neither a dropped connection nor a daemon restart loses or repeats text.
Without a stored cursor, initial: head follows new text only and initial: all replays the producer's history from sequence 0.
When a hello reports a head below the durable cursor (the producer's journal restarted), the receiver continues as a new stream incarnation <stream>~<n> under the initial policy (resets) instead of discarding every new sequence as replay.
At most 16 stream identities are retained in checkpoints; use a stable producer-session identity, not a fresh ID per message.
Identities are also stored with the existing JSONL entries for crash recovery, not in a parallel transcript store.
Without IDs, repeated words/lines are all preserved, and reconnect replay/gap ambiguity is unavoidable: there is no text-hash dedupe or exactly-once claim.
Mute controls adoption only: the connection continues draining, received muted text is discarded, recognized muted IDs are consumed, and a mute epoch invalidates already-queued pre-mute work. Unmute does not ask for producer history or replay a muted backlog; an unidentifiable producer replay cannot be distinguished from genuinely repeated text. Accepted segments use the existing durable JSONL/cursor/diff/tail, scratchpad/connected agent-buffer, and wake paths, without the legacy low-signal utterance aggregator suppressing short final text. The durable transcript is authoritative; scratchpad-mirror failure is explicit and does not erase retained entries or authorize replay.
Connections/auth acquisition have a five-second budget; each instance has an independent 32-segment adoption queue and bounded exponential reconnect backoff.
Queue saturation disconnects with a gap/backpressure diagnostic rather than accumulating unlimited text or blocking another instance's status/control.
WSS is required except on loopback or the tailnet (100.64.0.0/10, fd7a:115c:a1e0::/48, *.ts.net), where WireGuard already authenticates and encrypts the stream.
URL credentials, query parameters, and fragments are refused; the resume cursor is appended only through resume_query.
A producer command is an argv without a shell, started in its own process group with a minimal environment (home, path, locale, XDG, and TLS roots only), so caco credentials never reach it.
The producer reads its own credentials; token_file and resume_query apply only to url.
Its stdout lines are bounded by max_message_bytes, its stderr is discarded because producer diagnostics may contain transcript text, and stop, restart, or a mode change terminates the whole process group.
A producer that exits is restarted with the same bounded backoff; an unauthorized producer is retried at reconnect_max_secs.
Optional bearer tokens are read from the normal readable file target, including managed symlinks, on each reconnect; tokens, endpoint paths, transcript bodies, and raw upstream errors never enter health logs.
STT metadata logs reopen and rotate on append, using CACO_STT_LOG_MAX_BYTES (5 MiB, bounded 4 KiB–100 MiB) and CACO_STT_LOG_KEEP (3 archives, bounded 1–32).
This is prospective bounded retention, not permission to delete existing live evidence.
Config/Collective/platform owners still own package activation, the intended signed macOS launch context, phone-service deployment, and external-producer supervision.
Source tests and PR delivery do not establish deployment or authorized existing-instance/live-meeting acceptance.