# kcode-audio-ingress 0.5.2 API
`kcode-audio-ingress` durably accepts complete WAV buffers, retains the exact
original, analyzes overlapping audio chunks, parses and validates speaker
features, reconciles a readable transcript, and exposes one durable
transport-neutral speaker-correction packet.
The embedding application supplies configured audio and text callbacks plus
its shared speech classifier. Provider routing, credentials, accounting,
object creation, Kmap storage, and user messaging remain outside this crate.
## Breaking changes from 0.4
- `AudioIngress::open` now requires the caller's shared
`Arc<kcode_speech_classification::SpeechClassifier>` from the exact
`kcode-speech-classification = 0.1.2` dependency.
- `AudioIngress` no longer creates or opens
`<root>/speaker-classification.sqlite3`; all existing scoring, automatic
training, and confirmation operations use the injected classifier.
- The package version is `0.5.2`.
## Ingress API
```rust
pub struct AudioInput {
pub user_id: String,
pub bytes: Vec<u8>,
pub recorded_at: chrono::DateTime<chrono::Utc>,
pub original_filename: Option<String>,
}
pub struct Submission {
pub recording_id: uuid::Uuid,
pub deduplicated: bool,
}
pub struct Status {
pub recordings: Vec<RecordingStatus>,
}
pub struct RecordingStatus {
pub id: uuid::Uuid,
pub user_id: String,
pub sha256: String,
pub original_filename: String,
pub size_bytes: u64,
pub recorded_at: chrono::DateTime<chrono::Utc>,
pub received_at: chrono::DateTime<chrono::Utc>,
pub transcription_model: String,
pub reconciliation_model: String,
pub reconciliation_reasoning: String,
pub state: RecordingState,
pub correction_packet: Option<CorrectionPacket>,
}
pub enum RecordingState {
Queued,
Processing {
attempt: u8,
progress: TranscriptionStatus,
},
Complete {
transcript: String,
},
Failed {
attempts: u8,
error: String,
retryable: bool,
},
}
pub enum ErrorKind {
InvalidInput,
NotFound,
Conflict,
Internal,
}
pub struct Error {
// private fields
}
impl Error {
pub fn kind(&self) -> ErrorKind;
}
pub struct AudioIngress {
// private fields
}
impl AudioIngress {
pub async fn open(
persistence_root: impl AsRef<std::path::Path>,
transcriber: AudioTranscriber,
classifier: std::sync::Arc<
kcode_speech_classification::SpeechClassifier,
>,
) -> Result<AudioIngress, Error>;
pub async fn submit(&self, input: AudioInput) -> Result<Submission, Error>;
pub fn status(&self) -> Result<Status, Error>;
pub fn retry(&self, recording_id: uuid::Uuid) -> Result<(), Error>;
pub fn confirm_speakers(
&self,
confirmation: RecordingConfirmation,
) -> Result<CorrectionPacket, Error>;
}
```
`open` owns these paths:
```text
<root>/
state.sqlite3
originals/
```
It applies compatible ingress migrations, uses the caller-supplied shared
classifier from the exact `kcode-speech-classification = 0.1.2` dependency
in-process, recovers interrupted ingress work, and starts the background
worker. It requires an active Tokio runtime. The caller owns classifier storage
and must keep the shared `Arc<SpeechClassifier>` available for the ingress
lifetime.
Callers must keep at most one active `AudioIngress` opener/worker set per
persistence root. Opening the same root concurrently is unsupported because
startup recovery and worker ownership are not coordinated.
`submit` validates a nonempty user ID and audio buffer, stores the exact bytes
under their SHA-256 identity, and commits recording metadata before returning.
Repeated bytes return the existing recording with `deduplicated: true`.
`status` returns all recordings newest first. A successfully completed
recording contains both its canonical Markdown transcript and correction
packet. Processing snapshots omit transcript and packet bodies.
`retry` gives only a failed recording a fresh automatic five-attempt budget.
Unknown IDs return `NotFound`; nonfailed recordings return `Conflict`.
`confirm_speakers` accepts one completed recording and exactly one confirmed
full name for every observation in its packet. Duplicate, missing, extra, empty,
or wrong-recording confirmations are rejected. The method atomically corrects
each classifier key through the classifier API where possible, rolls back
already-applied changes if a later classifier operation fails, persists the
updated packet, and is idempotent for repeated identical confirmations. It
does not create an object or send a message.
## Transcriber callbacks
```rust
pub struct AudioTranscriber;
impl AudioTranscriber {
pub fn new(
transcribe_chunk: AudioChunkCall,
generate_text: TextGenerationCall,
) -> AudioTranscriber;
pub fn transcribe(
&self,
user_id: impl Into<String>,
audio: Vec<u8>,
) -> TranscriptionJob;
}
pub struct AudioChunkRequest {
pub user_id: String,
pub model: String,
pub prompt: String,
pub audio_ogg: Vec<u8>,
pub schema: Option<serde_json::Value>,
pub max_output_tokens: u32,
}
pub struct TextGenerationRequest {
pub user_id: String,
pub operation: String,
pub model: String,
pub prompt: String,
pub reasoning_effort: String,
pub timeout: std::time::Duration,
}
impl TranscriptionJob {
pub fn status(&self) -> TranscriptionStatus;
}
```
The audio callback receives each complete Ogg Opus chunk, model
`gemini-3.1-pro-preview`, the exact `GEMINI_SPEAKER_PROMPT_V0_1` bytes, and
`schema: None`. Its returned string is retained verbatim.
The text callback receives model `gpt-5.6-sol`, reasoning `xhigh`, and one of
these operations:
- `parse_speaker_analysis`: convert a quoted, explicitly untrusted raw Gemini
response into machine JSON;
- `reconcile_transcript`: faithfully merge chronological overlap using
explicit local-speaker candidate mappings without guessing identity;
- `split_transcript`: add size boundaries without rewriting content.
Provider failures and invalid parser JSON are retried up to three times inside
a full job. The parser rejects missing or extra feature fields, duplicate
speaker labels, unknown utterance-speaker references, invalid ISO 639-3 codes,
empty required content, non-finite values, invalid CEFR values, and
out-of-range features.
The public in-memory `transcribe` form performs audio analysis, parsing, and
reconciliation but has no persistent classifier identity or correction packet.
Use `AudioIngress` for the durable classifier-aware workflow.
## Correction packet
```rust
pub struct CorrectionPacket {
pub recording_id: uuid::Uuid,
pub user_id: String,
pub sha256: String,
pub original_filename: String,
pub size_bytes: u64,
pub recorded_at: chrono::DateTime<chrono::Utc>,
pub clean: bool,
pub chunk_count: usize,
pub chunks: Vec<CorrectionChunk>,
pub confirmation_state: ConfirmationState,
}
pub struct RecordingConfirmation {
pub recording_id: uuid::Uuid,
pub observations: Vec<ObservationConfirmation>,
}
pub struct ObservationConfirmation {
pub observation_key:
kcode_speech_classification::ObservationKey,
pub confirmed_full_name: String,
}
```
Each `CorrectionChunk` retains its chronological chunk index/count, source
start/end milliseconds, complete raw Gemini response, validated
`ParsedChunk`, deterministic observations, candidate evidence, and clean flag.
Each parsed speaker has one lowercase ISO 639-3 primary language and exactly
one typed 24-feature `FeatureRow`. Each observation retains the local label,
sorted ordinal, deterministic key, best available candidate/cost/confidence,
runner-up/background evidence, identified full name, and optional confirmed
full name.
`ConfirmationState` is `Unconfirmed`, `AutomaticallyTrained`, or `Confirmed`.
A chunk is clean only when Gemini marked it valid, it has at least one
observation, every observation has a best candidate with confidence greater
than zero and cost strictly below `background_population_cost`, every
observation has a runner-up whose cost is strictly above
`background_population_cost`, and candidate full names are one-to-one within
the chunk. A recording is clean only when every chunk is clean. Every
observation is automatically trained only for a clean recording; an unclean
recording trains none.
## Stable classifier contract
The exact cohort constants exported by this crate are:
- `CLASSIFIER_PROVIDER = "google"`
- `CLASSIFIER_MODEL = "gemini-3.1-pro-preview"`
- `CLASSIFIER_PROMPT_VERSION = "gemini-speaker-prompt-v0.1"`
- `CLASSIFIER_SCHEMA_VERSION = "gemini-speaker-features-v0.1"`
Primary language is the per-speaker lowercase ISO 639-3 code. Read-only
scoring calls `identify` with finite threshold `1e308` and uses the best
candidate, runner-up, and background-population cost evidence. Clean identity
quality requires confidence greater than zero, best-candidate cost strictly
below background, and runner-up cost strictly above background. Confidence is
raw evidence, not a probability.
Persisted keys use object ID
`kcode-audio-ingress/recording/<recording-uuid>/chunk/<chunk-index>` and the
sorted chunk-local speaker ordinal as `piece_index`. Probe scoring uses the
separate `kcode-audio-ingress/probe/...` namespace. The packet also retains the
actual chunk index/count, source interval, local label, and persisted key.
## Persistence and limits
Audio chunks remain at most four minutes with up to 15 seconds of overlap and
bounded concurrency of four. Each completed chunk is committed before its
progress step completes. Attempt-scoped `audio_transcript_pieces` rows retain
the raw Gemini output and validated parsed JSON. The cache revision excludes
all pre-0.4 structured-transcript rows.
Original retention, deduplication, progress recovery, and fixed retry behavior
remain durable. The crate adds no HTTP, Kmap, Telegram, object-store,
provider-client, credential-vault, or application-framework dependency. It
does not authenticate a speaker, claim calibrated accuracy, create Kmap
objects, or deliver correction messages.