Olenji API and integration contract
This document describes Olenji's local HTTP interface (schema 1.0) and internal adapter contracts, including the 1.2.0 additions. The executable OpenAPI definition from olenji schema --json is authoritative for request validation. The Python 3.11+ package runs a FastAPI service and serves its bundled browser assets; the CLI and MCP adapter use the same HTTP interface.
HTTP contract
All normal /api endpoints return {ok:true,schema_version:'1.0',data:...}. Errors are {ok:false,error:{code,message,details?,retryable?}}. Lists: data {items:[...]}. API host is same origin. Authentication is an HttpOnly cookie after POST /api/pair with {token} from URL fragment #pair=...; JS removes fragment immediately. CLI uses Authorization Bearer. /api/health public basic state. API helper unwraps data. No external frontend dependencies/fonts/CDNs. SSE uses cookie, URL /api/events?after=0&session_id=...; type change, data {seq,type,session_id,payload,created_at}. Replay cursor is global seq. heartbeat may be ignored. Throttle refreshes. Transient event type partial, data {session_id,text,speaker_id?,start,end}. Current state available by snapshot.
The local client authenticates service ownership through its workspace identity and requires the health response's application version to equal its own before normal operations or startup reuse. A mismatch raises SERVICE_VERSION_MISMATCH, including the reported client/service versions. Status and explicit stop remain available so an agent can recover without an automatic recording restart. Application version comes from olenji.__version__; API schema_version and database schema are independent version domains.
Help/version do not need third-party imports or a workspace. Raw export stdout and MCP stdout stay reserved for their document/protocol even when startup fails; such failures use stderr and a nonzero exit. A closed downstream pipe exits quietly. Interrupting a CLI that owns an unready service startup reaps that unreported child, while interrupting a wait on already admitted work leaves the durable job's lifecycle unchanged.
Job waits and events --follow may establish the local service once before beginning their observation budget. Initial service connection/startup is separate from --wait-timeout / --seconds; view-application waits begin after their view mutation, and the interactive terminal timer begins after its initial snapshot. Subsequent observer polls verify the existing service's workspace/version without starting a replacement. Losing that service returns retryable SERVICE_UNAVAILABLE; event-follow errors include the last consumed cursor and selected session_id so an agent can inspect recovery and resume. Job expiry returns retryable WAIT_TIMEOUT with job_id, without cancelling the job. Finite event follow ends cleanly, and an expired view wait returns the last known view with applied:false and wait_timed_out:true rather than claiming a late acknowledgement.
The browser defers incoming view changes while an action is pressed or a dialog is open. A note opened before a requested session change saves to its original session; the pending view applies after the draft closes. Background refresh does not replace the pressed control before activation finishes, and deferred presentation is not acknowledged as rendered. Cancellation, focus loss and hidden-page release resume deferred work; explicit detachment still takes effect.
Observer deadlines use monotonic time, bound sleeps and each network phase's timeout to the remaining budget, and are checked before/after blocking phases and between yielded response chunks. These synchronous deadlines are not hard real-time interruption guarantees: OS scheduling, a currently blocked read and response decoding can finish after the nominal deadline; a result received after expiry is not accepted. Observer JSON bodies are limited to 8 MiB, with a separate 16 KiB health-probe limit. Reads request identity encoding, close their response on success/error/cancellation, and fail with RESPONSE_TOO_LARGE when a normal observer body exceeds its limit. Reduce the event page/window or use export for larger content. These limits do not cap normal one-shot API reads or streamed exports.
CLI JSON document inputs (--body, --params, --metadata, --config, --query, --context, --inference and --file, including their @file/stdin forms) accept at most 8 MiB per document and 256 nested array/object container levels. Files and binary stdin use their encoded byte size, preserving UTF-8/UTF-16/UTF-32 BOM decoding; text-only embedded stdin uses a UTF-8 byte budget. Readers stop at the budget plus the byte/Unicode character needed to detect overflow, close files they opened, and leave caller-owned stdin open. Oversized input returns INPUT_TOO_LARGE with limit_bytes; invalid JSON, excessive nesting and nonfinite numbers (including exponent overflow) return INVALID_JSON. The explicit depth check reports limit_container_depth and uses an iterative ancestor stack, independent of the Python decoder's recursion policy. These argument errors exit with code 2 before contacting the service and do not echo input content. This input-read budget is separate from the HTTP service's 2 MiB non-upload request-body limit; an accepted input document must still satisfy its API schema and encoded request-body limit.
Entities
- Session:
{id,title,state,language,model_id,source_kind,created_at,updated_at,duration,metadata,segment_count,word_count,speaker_count}. state idle/processing/live/paused/ready/failed/interrupted. metadata includes retain_audio, translation_language, source_name, media_available, capture_owner, diarize. - Segment:
{id,session_id,start,end,text,speaker_id,revision,is_final,language,confidence?,words:[]}. API times are seconds; persistence may quantize. Speaker IDs stable, unknown allowed. - Speaker:
{id,session_id,label,color}. - Translation:
{id,session_id,source_refs:[{segment_id,revision}],target_language,text,status,producer,updated_at}status current/stale/draft. Explicit revision preconditions. - Annotation:
{id,session_id,kind,text,segment_ids,created_at}. - Job:
{id,kind,state,progress,phase,message,result,error,created_at,updated_at}state queued/running/completed/failed/cancelled. progress 0..1. - View:
{id,session_id,config,desired_revision,applied_revision,visibility,connected,last_seen}.
Main routes
- GET /api/doctor -> host/capabilities; GET /api/models -> items; GET /api/models/{id}; POST /api/models/install
{model_id?,profile?,compatible_only?}-> job; POST /api/models/{id}/verify; POST /api/models/{id}/remove. GET /api/models/parity (declare before id). - GET /api/jobs; GET /api/jobs/{id}; POST /api/jobs/{id}/cancel.
- GET /api/sessions?q=; POST /api/sessions
{title?,language?,model_id?,source_kind?,metadata?}; GET /api/sessions/{id} -> snapshot{session,segments,speakers,translations,annotations,cursor,views}; PATCH /api/sessions/{id} title/metadata; DELETE /api/sessions/{id}. - POST /api/transcribe multipart
file, model_id, language, diarize bool, title, retain_audio bool, inference JSON object as text ->{session_id,job_id}. POST /api/transcribe/path JSON same with path (CLI local only). - GET /api/sources -> devices/capabilities; POST /api/live
{model_id,source:'microphone'|'system'|'browser'|device id,title?,language?,diarize?,retain_audio?,inference?}->{session_id}; POST /api/sessions/{id}/pause|resume|stop. - WS /api/live/{id}?sample_rate=48000: browser PCM float32 mono binary frames, authorized cookie or bearer token; frame ack not required. Only one source may supply an active browser-owned session. The browser uses getUserMedia and AudioContext+AudioWorklet and stops its tracks on stop. Pause, resume and finish control messages share the same ordered socket and acknowledge their request IDs after preceding audio has been handled.
- PATCH /api/sessions/{id}/segments/{segment_id}
{text?,speaker_id?,expected_revision}-> segment; POST /api/sessions/{id}/speakers{label}; PATCH /api/sessions/{id}/speakers/{speaker_id}{label?,color?}. - POST /api/sessions/{id}/translations
{translations:[{source_refs,target_language,text,producer?,id?}]}; POST /api/sessions/{id}/translate{target_language?,provider?,model?,base_url?}-> job; GET /api/providers -> configured metadata only; POST /api/providers{id,base_url,api_key?,model}stores protected config; no keys returned. - POST /api/sessions/{id}/annotations
{kind,text,segment_ids?}; DELETE /api/sessions/{id}/annotations/{id}. - POST /api/sessions/{id}/refine
{model_id?,diarize?,inference?}-> job; GET /api/sessions/{id}/export?format=json|jsonl|txt|md|srt|vtt|html&language=original|tr|bilingual -> file; GET /api/sessions/{id}/media -> Range-capable local source/preview. - POST /api/sessions/{id}/export/prepare with the same export query parameters ->
{download_url,filename,bytes,cursor,expires_in}. GET the root-relativedownload_urlwith the existing cookie/bearer credential for a one-use native download; the URL does not carry an API credential. - POST /api/views
{session_id,config?}-> View; PATCH /api/views/{id}{config,expected_revision?}-> View; POST /api/views/{id}/ack{applied_revision,visibility}; GET /api/views; POST /api/views/{id}/detach. - GET /api/settings -> nonsensitive settings; PATCH /api/settings.
Omitted or null model, recognition-language and audio-retention options inherit the workspace defaults. Explicit false and "auto" override those defaults. Translation operations inherit default_translation_language when the target is omitted. Bounded read projections are an exception: the SessionWindow.target_language schema and /tail query default to literal tr; callers should supply the selected track explicitly. The TUI resolves its omitted language from settings before requesting its tail. New views inherit layout, theme and target-language preferences; explicit view fields override them. Idempotency fingerprints the original request, so changing preferences does not change a completed retry result.
GET /api/sessions/{id}/metadata returns metadata, counts, speakers, views and cursor without loading all transcript tracks. Use paginated segment/event routes for long-running agent workflows. JSON and JSONL exports retain the complete snapshot's tracks; readable formats apply their language selection.
Inference request ownership
File-path transcription, multipart upload and refinement additionally accept speaker_model_id and speaker_preset. Either field enables speaker processing; omission preserves the existing diarize/WeSpeaker route and default idempotency shape. Presets must be advertised by the selected speaker catalog entry. GET /api/speaker-models returns speaker-only catalog/readiness records. New ONNX/Sherpa timeline choices are offline-only and reject live requests before capture. Session metadata records selected speaker model, verified identity, preset and method independently of the ASR model. Failed refinement restores that metadata with its source-preservation transaction. Timelines retain overlap uncertainty rather than duplicate source words; processing is bounded to one hour.
Global push-to-talk
GET /api/hotkey reports the native binding, requested configuration, pending OS approval, owned session/job and last error. POST /api/hotkey requires a bearer credential even when a browser cookie is paired. Its strict fields are shortcut, optional speech model_id/language/inference, retain_audio (false), local_only (true), max_hold_seconds (strict integer 1–600, default 120), and output (transcript by default, explicit insert). It validates model/language readiness and never silently installs dependencies, starts recording, uses the clipboard or enables login startup. DELETE /api/hotkey unbinds and stops only its owned capture; incomplete cleanup is a retryable failure and retains capture ownership for another disable attempt. No success is reported merely because the listener closed.
Each accepted press creates a service-owned microphone transcript session; release requests stop even during startup and exposes the finalization job. Autorepeat cannot create extra sessions, and another press is busy until prior finalization completes. Disconnect, timeout and disable release the owned source. An enabled saved binding can restore on service restart without resuming a recording. Native scope is the service host's desktop, which is not necessarily the caller's or user's device. Keyboard chords and OS-supported function keys are available; the parser accepts F1–F24, macOS maps F1–F20, and Windows rejects debugger-reserved F12. Bare modifiers and mouse buttons are not supported. Wayland uses the desktop GlobalShortcuts portal and reports its approved trigger description; XWayland is not a fallback for global Wayland ownership. macOS requires Input Monitoring and listen-only detection does not claim shortcut exclusivity. Native OS results require their own evidence.
Insertion captures an opaque supported target before microphone acquisition and keeps native focus details in an owned helper. After the exact owned live.finalize job succeeds, it observes a clean ready transcript, bounds source/event/text counts, rejects gaps or edits and marks one attempt before the external side effect. It rechecks source identity and the native helper rechecks the same focused field. The two processes are not an atomic database/desktop transaction; no exactly-once crash guarantee is claimed. Failed, uncertain and interrupted delivery is never replayed. Disabling cancels a pending delivery and joins an already claimed bounded native write; it cannot retract inserted text. No Enter/Send or clipboard operation is performed. macOS uses only positively writable AXSelectedText on an ordinary field, requires one empty typed selection and working AXStringForRange, and confirms the new UTF-16 span/caret after one write. An explicit disabled state is rejected. Only AXTextArea may omit AXEnabled with the native attribute-unsupported result; this relies on positive selected-text writability rather than declaring the missing state enabled. No-value, malformed, permission and transport failures remain errors. It never reads the surrounding draft. The focused application and field/PIDs must remain the captured identities. Linux uses AT-SPI insertion without an existing selection, and Windows insertion is unsupported. Native permissions and actual field behavior require their own verification.
Recognition inference settings
File-path transcription, multipart upload, live start and refinement accept optional inference settings. Typed fields are device, compute_type, cpu_threads (strict integer 1–64) and native_backend; each model's inference_options capability object identifies accepted fields/defaults. CLI flags and MCP map to that same object. JSON CLI input and individual flags are mutually exclusive. Unsupported backend fields fail before job/session/capture/upload ownership; actual hardware/precision availability is checked by the inference process. Explicit device requests reject unavailable runtime devices/providers; explicit native backends must match the loaded model's reported backend. ONNX CUDA applies to encoder/frontend sessions and does not promise that every operation executes on a GPU. Explicit native auto remains an automatic selection policy.
Only requested settings persist in session metadata and each recognized source's provenance.inference_options; they do not certify actual hardware utilization or effective precision. Refine omission/null inherits saved requests only when the selected backend matches the prior backend. An explicit object replaces the full request; {} restores adapter defaults. Failed refinement restores prior model/language/settings with its existing source-preservation transaction. Default requests retain pre-option idempotency fingerprints; explicit settings are part of the request identity. Cache keys include relevant requested settings and verified installation identity. Full supported values, errors and native-host boundaries are in INFERENCE_OPTIONS.md.
Long sessions and consistency
POST /api/sessions/{id}/windowreads a bounded source/translation projection and atomic cursor. The CLI exposes it throughsessions window SESSION_ID --query JSON; MCP exposes the sameSessionWindowfields througholenji_session_window. Query input supports literal JSON,@fileand-stdin. Its typed body selects source offset/limit, literal full-session search, translation language, fixed/recent speakers, committed turns, seek/source anchor and independently paged notes/speakers. Render onlywindow_ids; additionalsegmentsare connected translation context.expected_cursorrejects intervening content edits with retryableSNAPSHOT_CHANGED;afteris valid only for an identical window/query. Limits are 500 visible turns, 1,000 connected nondeleted/nonsuperseded translation tracks, 5,000 additional source IDs and 8 MiB JSON;WINDOW_TOO_COMPLEXfails explicitly. Full field and ownership semantics are in BROWSER_WINDOWS.md.GET /api/sessions/{id}/segments/{segment_id}/neighborsreturns{segment,previous,next,cursor}from one read transaction. Neighbors follow the full session's(start,id)order, independent of browser filters or translation context. Missing source/session returns 404; merges must still supply current source revisions.GET /api/sessions/{id}?after=CURSORreturns{unchanged:true,cursor}if no selected-session content event occurred after the supplied checkpoint. Otherwise it returns the complete atomic snapshot. View/job state is independent of this content check; use the appropriate endpoints/events for those domains. A cursor ahead of the database produces a complete snapshot.GET /api/sessions/{id}/segments?offset=0&limit=100returns{items,cursor,total,offset,next_offset}from one database read transaction. Pass the first page'scursorasexpected_cursoron later pages. Selected-session changes produce retryableSNAPSHOT_CHANGED(409); restart rather than merging inconsistent pages. Timing filters must be finite, nonnegative and ordered. Unrelated-session events do not invalidate the read.GET /api/sessions/{id}/tail?limit=15&target_language=tr&after=CURSORreturns the snapshot shape plustail_ids. The source list also contains the context referenced by current or human-owned translation groups connected to those turns. Render onlytail_idswhile using the full context for revision/ownership validation. Limits are 1–100 recent turns, 1,000 connected tracks and 5,000 additional source IDs; excessive grouping returnsWINDOW_TOO_COMPLEX(409). Unchanged content uses the same compact response as a full snapshot. Opt-ininclude_partials=trueadditionally returns transientpartials, including when durable content is unchanged. Individual transient records carry their runtimeepochanddraft_revision. Drafts and levels are not committed source rows; transient generation/revision fields do not replace the durable cursor.GET /api/events/pollatomically pairsitems,cursorandhead. Empty filtered pages advance to the current global head. A checkpoint ahead of the database returns{items:[],cursor:head,head,reset:true}. SSE emitsevent: resetwith{cursor,reason:'cursor_ahead'}; a client must close/recreate EventSource and refresh its snapshot so an obsolete Last-Event-ID does not repeat the reset.segments.replacement_startedis a durable event inside the same commit as refinement, turn merge or speaker merge. Consumers that render individual change events should resnapshot at this marker before applying constituents; the snapshot cursor lets them discard events already represented in that atomic state.- Translation language matching is case-insensitive; new tags receive consistent language/script/region casing. Source revisions, entire sentence groups and human corrections keep their existing priority rules.
Database schema version 4 adds derived counters and indexed translation references without changing the public schema version 1.0. Upgrading an existing older database first creates a consistent SQLite backup beside it. doctor reports database_schema_version and sqlite_version; runtime startup requires SQLite 3.24 or newer. Public rows do not expose internal counter columns.
When a source producer commits an earlier speaker ID that has since been merged, the source transaction follows that session's durable speakers.merged chain to its surviving speaker. This applies to file and live recognition, including a merge between acoustic attribution and commit. Never-existing and other-session IDs still return SPEAKER_NOT_FOUND. Existing source revisions and translation staleness change only through the normal merge/edit transaction. A partial merge-event index bounds lookup work to merge history; its additive creation does not change database format version 4.
Snapshot/export list ordering is explicit: segments by (start,id), translations by (updated_at,id), annotations by (created_at,id), views by id, and speakers by stored insertion order. Lists of jobs use creation time then id; view discovery uses last-seen time then id. Equal timestamps therefore do not depend on a SQLite query plan. Translation selection keeps human/current/latest precedence, then uses ascending stable ID for an otherwise tied group in the browser, terminal and export readers. ID comparison uses Unicode code-point order rather than browser locale collation or UTF-16 unit order. This defines previously unspecified ties without a database schema migration.
Admission, live ordering and export lifetime
An explicit session metadata patch with retain_audio:false requests owned-audio cleanup even when the preference is already false. Retention changes and cleanup serialize with job reservation and require the session to have no active capture/transcription/refinement work. A failed OS unlink returns retryable AUDIO_CLEANUP_FAILED (503); retention remains disabled and media access is denied, but owned files may remain on disk. After resolving the file-access problem, repeat the same disable request to retry cleanup. Success is returned only after cleanup completes. This does not delete the external original input, transcript text, prior exports or backups.
doctor.capacity.jobs reports ordinary_pending, ordinary_limit and finalizers_pending; doctor.capacity.live_sources reports active and limit. Ordinary active+queued jobs are bounded at 16 times the startup max_parallel_jobs value (16–128); cleanup finalizers remain admissible. Up to eight live sources can be owned simultaneously, including sources still starting or finalizing. An unadmitted job returns retryable JOB_QUEUE_FULL (503), and an unadmitted source returns retryable CAPTURE_CAPACITY (429). Newly created, unstarted sessions/uploads are cleaned up. These two definite admission failures release their pending idempotency reservation, so the same key and arguments may be retried after capacity becomes available. Completed results and uncertain crash outcomes retain their existing idempotency protection.
The browser PCM socket also accepts {type:'gap',seconds,reason:'browser_processing_overrun',request_id?}. The duration must be a finite positive number no greater than 3,600 seconds, measured from samples the producer actually discarded. The pipeline records the gap in source order, advances timing and retains it even if stop immediately follows. An optional acknowledgment is {type:'gap_recorded',session_id,request_id?}. Gaps are not guesses about network delay. Pause/resume acknowledgments include state, updated_at and an atomic database cursor; clients must not allow an older snapshot to restart a paused producer.
Pause, resume, stop and terminal-state publication share per-session control ordering. A delayed pause cannot publish after a later resume or completed finalization. Ending/cancelled capture returns CAPTURE_STOPPING; a released source returns CAPTURE_NOT_ACTIVE for pause/resume. Stop returns its finalization job after ending capture; inference drains outside the control lock. An earlier bounded control-enqueue wait can delay a concurrent control. See live control ordering for ownership and acceptance evidence.
Measured native or capture-queue loss produces source.gap with exact start, end, duration_known:true and recoverable_from_retained_audio:false, even before the first accepted frame or immediately before stop. One pending contiguous interval transfers with the next accepted frame/control or finalization; PCM queue admission stays nonblocking; measured-loss capture callbacks do not write SQLite. A single cause is device_clock_gap or capture_queue_overflow; mixed contiguous loss is capture_loss with a causes array. Intentional pause time advances the timeline without increasing the lost-audio count. Retained media uses silence at the missing positions and preserves accepted samples. Recognition backlog remains a distinct transcription.gap; see live replay.
Appending agent source serializes its source-writer admission with job reservation. Translation jobs may read revisions while new source is appended; capture, refinement and other source-owning jobs still prevent competing appends. A successful append wakes explicitly enabled automatic translation without requiring a later metadata edit. Appended source remains human/agent-owned through ordinary revision and refinement rules.
Exports read one SQLite snapshot and write a private temporary file before the HTTP transfer begins. Source capture and edits can continue while it is prepared; the snapshot's transaction is closed before a slow client downloads. All seven formats are encoded incrementally, preserving grouped translation ownership and source revisions. JSON/JSONL include original tracks, stale translations, notes, views and provenance.
Two exports may prepare concurrently; at most 16 prepared/sending files are owned at once. EXPORT_BUSY (503) is retryable. Prepared links expire after five minutes and are single-use. Completion, disconnect, preparation failure and shutdown remove the temporary file; expired files are reclaimed periodically. If deletion fails, a non-downloadable cleanup record retains ownership and its capacity slot until retry succeeds. Startup clears abandoned files with Olenji's private spool prefix. Deleting a session revokes its unclaimed exports and prevents an in-progress preparation from publishing. An already transferring download cannot retract bytes the client has received. X-Olenji-Cursor identifies the export snapshot; a one-use download always transfers the complete file and does not support byte ranges. Retained media continues to support ranges.
CLI/MCP destination exports stream into a temporary file beside the destination, flush it and atomically replace the destination only after a complete successful transfer. Inline MCP output is limited to 200,000 bytes without buffering an unlimited response. Export storage is proportional to file size on disk, while Python memory is bounded by a row batch/translation group rather than total transcript length.
Terminal and agent caller semantics
Passive environment advice
This additive interface is included from application version 1.1.0. Earlier 1.0.2 artifacts and their documentation remain immutable and do not expose this command/tool. Registry publication and installed acceptance require their own exact-artifact receipts.
olenji environment --json and olenji_environment accept optional task and context. task is one of inspect (default), live_transcription, file_transcription or text_translation. context is an object with only these optional enum fields: host_relation (unknown, same_device, remote); audio_access and file_access (unknown, available, unavailable); presentation (unknown, browser, artifact, text). Omitted fields remain unknown. Invalid task/context values and unknown fields are rejected before work. These declarations guide recommendations and do not establish access or authorization.
The passive report distinguishes observed process facts from supplied context and unknown facts about the user, audio, file and presentation. It supplies advice, not a governing policy, task replacement, permission grant or executable prompt. A product name, process hint, package installation or MCP client name cannot prove that the process is on the user's computer. A source listed as available is not proof of granted permission or flowing audio; a browser launcher success is not proof of a visible user tab.
The CLI early branch runs before Config and needs no workspace, credentials, service connection, device/model probe, recording, job, download or browser launch. The MCP tool makes no service request or active probe and uses the same report logic; mcp serve retains its existing startup workspace setup. Runtime doctor includes environment context alongside its explicit heavier readiness discovery. MCP capabilities includes environment advice and reuses doctor.capture instead of enumerating sources twice. Process scope must remain explicit: passive CLI/MCP facts describe their answering process; doctor facts describe the service.
MCP initialization/discovery returns concise advisory guidance and points to the environment tool and olenji://agent-guide resource; reading that resource returns the full conditional workflow. Source discovery adds evidence metadata with scope:service_host, availability:adapter_or_prerequisites, permissions:not_checked, audio_flow:not_checked and user_device_relation:unknown. Existing source availability booleans are unchanged and must not be relabelled as successful capture or permission checks.
Task advice is conditional. Accessible files may be processed on a remote host when authorized. Missing live audio calls for a targeted connection check or a concise local-agent handoff; unknown access alone is not a reason to force a platform switch. Text-only translation does not require capture, a speech model or FFmpeg. The selected session's local_only policy controls Olenji provider egress; the doctor's workspace-default value does not establish that session's policy or external-agent data handling.
MCP file imports expand and resolve path in the MCP process working directory before sending it to the persistent service. This matches CLI local-path behavior even when the service was launched elsewhere.
tui SESSION_ID --json and --jsonl return one versioned CLI envelope whose data is a bounded recent-turn snapshot, with complete translation-group context and opt-in ephemeral drafts/levels. Redirected stdout defaults to JSON. These machine modes do not enter the interactive loop; use events --follow --jsonl for durable streaming. live start --tui --json returns normal live-start data plus a terminal snapshot and leaves capture with its declared owner. --seconds bounds an interactive viewer only. Explicit human mode requires a TTY and fails with TERMINAL_REQUIRED before starting capture when one is absent.
The interactive viewport gives newest turns and provisional drafts the available rows, preserves speaker/group attribution and keeps capture/detach status visible. It polls bounded state, applies transient updates even when the durable cursor is unchanged and shows an explicit reconnect state during bounded retryable failures. Refresh/reconnect never starts a replacement service or capture. Authentication, missing-session and nonretryable errors remain errors. See TERMINAL.md for geometry and ownership limits.
Model/engine ABI
Live sessions hold a shared ASR model pin from startup through inference teardown, including silence and pause. Model removal returns model_in_use while that ownership remains. Recognition dispatches read current session local_only metadata; changing policy cannot retract an already dispatched request.
catalog.MODELS list of dicts with id,name,family,description,languages,task,streaming,platforms,download_bytes,license,source,backend,artifact,parity,availability,capabilities. All 17 selectable ASR identities in the pinned 25 October FluidVoice reference, plus functional portable Whisper and extra families. The dated fluidvoice-2026-10-22 profile preserves its original sixteen selections. Preserve exact-vs-alternative identity. Apple and Pico limitations explicit.
ModelManager(config) config has .data_dir:Path, .models_dir:Path, .runtime_dir:Path.
Methods list()->list[dict], get(id)->dict, plan(model_id=None,profile=None,compatible_only=False)->dict, install(model_id,progress=None,cancel=None)->dict, verify(id)->dict, remove(id)->dict, parity()->dict, path(id)->Path|None, doctor()->dict. progress callable (fraction, phase, message); cancel threading.Event. Methods raise OlenjiError(code,message,details=None,retryable=False) from errors.py. Include readiness/platform status in list. Installation must execute real downloads/runtime setup; never claim installed just from catalog. Runtime bootstrap changes only managed runtime env, not global Python.
engines.transcribe(path, model_id, manager, language=None, on_segment=None, cancel=None, collect=True, **kwargs)->list[dict] records {start,end,text,language?,words?,confidence?}. Backend calls actual inference. on_segment(segment) streams committed outputs when supported. collect=False requires that callback and returns an empty list while delivering every record through it, avoiding duplicate long-transcript retention. The default return contract is unchanged. File code and pipeline may call it repeatedly for buffered live chunks. No fake fixtures in production. Arbitrary speech backend capability unsupported must be explicit. engines.available_backends(manager)->dict.
Audio/pipeline ABI
Pipeline(runtime) runtime has .store, .models, .config, .emit_partial(session_id,payload), .submit_job(kind,fn,session_id=None)->job; fn signature (job_id, progress, cancel).
Pipeline methods transcribe_file(session_id,path,model_id,language=None,diarize=False,job_id=None,progress=None,cancel=None,refine=False,inference=None)->dict, start_live(session_id,source,model_id,language=None,diarize=False,retain_audio=False,inference=None)->dict, feed_audio(session_id,pcm:bytes,sample_rate:int)->None, record_browser_gap(session_id,seconds,reason='browser_processing_overrun'), pause(session_id), resume(session_id), stop(session_id)->dict, sources()->dict, shutdown().
Store methods shared: get_session(id), update_session(id,**fields), add_segment(session_id, segment)->dict, list_segments(session_id), ensure_speaker(session_id,label, speaker_id=None)->dict, update_speaker(session_id,id,**fields), add_event(type,session_id=None,payload={}), update_job(id,**fields), snapshot(id), clear_machine_segments(session_id) (must preserve user edits), put_translations(session_id, records)->list, translations(session_id). Session metadata updates merge. Core knows errors. Media saved in config.sessions_dir / session_id.
translations.translate_session(runtime,session_id,target_language,provider=None,model=None,base_url=None,progress=None,cancel=None)->dict. Providers are resolved from config.get_providers(include_secrets=True), a map keyed by provider ID. Translation uses configured OpenAI-compatible endpoints under the session's local-only policy; external agents may instead write revision-bound tracks directly. Acoustic diarization produces anonymous voice clusters and preserves uncertainty when attribution is insufficient.
Provider responses must honor Accept-Encoding: identity. A compressed success response returns TRANSLATION_RESPONSE_INVALID before its body is read, preventing decompression from bypassing the 2 MiB response limit. Configure a provider or proxy that forces compression before retrying.
Automatic translation consumes the selected target and never republishes an older target when its provider call finishes. New committed source or a preference edit during a pass retains a coalesced continuation. A manual translation may select its completed track only if no later explicit target selection has superseded the intent captured at API job admission (or direct-call entry), including a same-language edit or a change away and back. Source-linked tracks and human corrections retain their ordinary ownership rules regardless of which target is selected.
Automatic work persists a service-owned translation_automation marker before admission. One owned controller thread holds at most 128 session entries; stable ID pages recover overflow from persisted metadata without loading transcript text. A full job queue retains pending work and retries admission independently. Retryable provider failures receive delays of 30, 60 and 120 seconds, with at most four consecutive attempts. Success resets the failure chain; permanent rejection or exhaustion blocks further automatic attempts until relevant explicit reconfiguration or provider repair. New speech and unrelated metadata edits do not replenish a blocked budget.
On startup, only previously marked pending/running/retry-wait work is eligible to continue, subject to current enablement and privacy settings. Legacy/unmarked, idle, disabled, cancelled and blocked sessions are not automatically replayed. Capture is never restarted. Disablement is checked before provider resolution and each additional batch. A call already sent may finish and preserve its revision-bound track. Explicit cancellation ends its owned generation; a stale scan or an older job cannot revive it or cancel a newer owner. Read-only status includes state, attempt count, retry deadline and owning job ID. Browser/terminal displays consume that state without submitting work.
An explicit translate command remains independent of automatic scheduling. To resume blocked automatic work, repair its provider or explicitly re-enable/reconfigure automatic translation. Relevant session keys are auto_translate, translation_language, translation_provider, translation_model and local_only; unrelated tags/retention changes do not reset the budget. Provider/default edits reconsider only previously owned unfinished/blocked work, not every saved transcript. Detailed states, cancellation and recovery ownership are in translation recovery.
Offline help
olenji docs returns the installed handbook file URL and section list. --open
requests the default browser; it reports browser_launch as not_requested,
requested, or unavailable, not a claim of visible rendering. --list prints
section IDs; --section ID must match an actual heading in the bundled document.
Unknown IDs return INVALID_ARGUMENT without launching. Missing or malformed
help returns DOCS_UNAVAILABLE. This path creates no Config, Client, workspace,
credentials or service and needs no third-party modules. --data-dir does not
change the installed help location. The browser footer serves the identical HTML
through /assets/handbook.html; it contains documentation, not private session
data. Existing origin/security checks still apply.
Editing and export corrections (28 October source update)
A split expands annotation source links to both resulting turns. A merge replaces
links to removed turns with the surviving ID, preserving order and removing
duplicates. Changed notes emit annotation.updated with the saved record and
previous_segment_ids in the same transaction as the source edit. Unlinked notes
and annotations in other sessions are unaffected. Translations still refer to
exact source revisions and become stale after source changes.
Merging accepts 2–100 distinct, globally adjacent turns assigned to one speaker.
Adjacency follows (start, id), including identical start times, independent of
request-array order or UI filters. Full source text/word arrays are loaded only
for requested turns; the ordered index supplies at most one extra adjacency ID.
A supplied translation id is a creation hint. Existing source-group/language
records keep their identity. If creating a new record would reuse another
record's ID, the write returns HTTP 409 TRANSLATION_ID_CONFLICT; it does not
reassign or reveal the other record. The whole batch rolls back. A repeated
idempotency key returns that same definite failure, not an uncertain pending
state. A corrected payload requires a new idempotency key. Omitting the creation
ID lets Olenji allocate one.
SRT/VTT start and end times are rounded to milliseconds. If rounding collapses a cue, its rendered end is at least one millisecond after its rendered start. Stored source timing, JSON, and JSONL remain unchanged. This enforces positive subtitle duration; it does not improve recognition alignment or guarantee that a very brief cue is comfortably readable.
Coordinated distribution identity
A current stable release has one application version across PyPI, npm and direct downloads. The npm tarball embeds the identical Python wheel; the complete ZIP includes that wheel, the npm archive and canonical source. Direct download is a registry fallback with the same features. Prior immutable artifacts are recovery material, not a separate LTS or feature tier.
The private repository's maintainer coordinator is outside the installed application contract. It plans and resumes publication of a manifest-bound accepted set through existing exact-commit CI/OIDC guards, without rebuilding or overwriting mismatched published files. A verified registry upload does not establish installed acceptance or website delivery. Without an explicit reviewed site plan, website delivery remains pending. The Hetzner adapter must verify both registry identities, clean installed receipts, a fresh storage gate, protected host state and the expected prior release. See RELEASING.md for recovery and authority boundaries.