olenji.

Published application 1.4.0 · Versioned documentation · Markdown source

Olenji API and integration contract

This document describes Olenji's local HTTP interface (schema 1.0) and internal adapter contracts, including the 1.4.0 configurable dictation and live textbox 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

Main routes

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 requested configuration, native binding/approval, listening, effective_state, connected capture, owned session/job, delivery, feedback and recovery. Top-level error includes a nested native binding fault; operation_error separately retains manager/cleanup failures. A failed stop can leave capture.connected:true and owned audio despite suspension, blocks recovery and requires another disable attempt. enabled means saved requested intent and is not readiness. States distinguish disabled, ready, starting, recording, stopping, processing, inserting, inserted, saved_only, no_speech, suspended, permission_needed, failed, delivery_uncertain and unavailable. Recording requires the owned session's capture_connected=true; an accepted press or created session is insufficient. Processing follows the exact owned queued/running finalization job.

POST /api/hotkey requires a bearer credential even when a browser cookie is paired. Strict fields include primary shortcut, activation (hold by default or toggle), up to eight additional bindings of {shortcut, activation}, delivery_timing (final by default or explicit live), and optional speech model_id/language/inference, retain_audio (false), local_only (true), max_hold_seconds (strict integer 1–600, default 120), output (transcript by default, explicit insert), feedback (true), optional strict nonnegative input_device and optional input_device_name guard. Explicit inputs resolve to an observed name at configuration and are rechecked before every activation; missing/changed identity fails with DICTATION_INPUT_CHANGED rather than substituting another device. Omission retains the OS default. GET /api/hotkey/devices only enumerates service-host inputs/default; it opens no stream and reports permission/audio flow untested. The paired browser may read status/devices and prepare a CLI command but cannot change the binding through its cookie.

Model/language validation never silently installs dependencies, records, accesses the clipboard or enables login startup. DELETE /api/hotkey unbinds, cancels recovery/delivery and stops only its owned capture; incomplete cleanup is retryable and retains ownership. An enabled saved binding can restore at service startup without resuming audio. Native scope is the service host's desktop. Legacy canonical chords remain unchanged. Typed shortcut status includes kind, physical_key, side_policy, display_label, modifiers, canonical and the physical edge identity; configured activation is reported per binding and as active_activation during capture. Duplicate or overlapping physical bindings are refused before replacing listeners: alt overlaps right_alt, alt+a overlaps right_alt+a, and modifier order alone does not distinguish ctrl+alt from alt+ctrl. Hold ends on its owning release; toggle starts on a fresh press and finishes on a second fresh press after release. A logical recording deadline applies even when the key is up. Interruption/fault of any owned listener cancels logical capture and pending delivery, including toggle captures with no physical key held. Both gestures share one capture owner; busy presses cannot queue surprise recordings. right_alt is Right Option/Alt alone, distinct from left_alt and alt+right. Either-side modifiers permit both sides; a sided trigger rejects the opposite side and extra modifier families. Modifier-only chords use the final stated modifier as primary. Repeats, preheld primaries and mid-hold modifier correction cannot start capture; all primary sides must release before a new deliberate press. Disconnect/timeout/disable finalize only owned audio.

macOS uses listen-only key/flags edges with Input Monitoring, including physical modifier/Fn keys and F1–F20. X11 uses passive grabs and consumes the trigger key, including family-grab conflicts before side validation. Windows physical modifier/sided bindings are listen-only; legacy registered chords consume the shortcut and debugger F12 is refused. Windows numpad Enter/equal/clear are unrepresentable here. Ordinary ASCII letter/digit chords resolve through the current layout; the browser recorder uses KeyboardEvent.key rather than US letter/digit positions and refuses non-ASCII output. Mac modifier callbacks snapshot HID state instead of toggling a cache that asynchronous polls can advance; only an event with its primary still held can press, while polls may release/rearm. Forbidden opposite-side/family events end and block a physical hold even when a brief tap already ended in the hardware snapshot; either-side bindings retain their permitted opposite-side behavior. Portal numpad triggers use XKB names and Clear never aliases keypad5. The parser accepts F1–F24 and modified navigation/numpad/ordinary keys; capabilities describe platform prerequisites rather than successful registration or hardware tests. Media and mouse keys are unsupported. Wayland uses GlobalShortcuts consent/approved descriptions and refuses physical sides/modifier-only keys before registration; it never falls back to XWayland.

Secure Input preserves enabled intent with effective_state:suspended. Recovery waits 1, 2, 4, 8, 16 and 30 seconds for at most six fresh handshake attempts per episode. recovery exposes state, attempts, budget and retry delay. A fault is cleared only after a healthy replacement handshake; exhaustion needs explicit re-enable. Disable/rebind/shutdown cancel waits. Recovery never replays edges, resumes a microphone session or retries insertion, and requires release/new press. Permission revocation, layout change, disconnect and resource faults are distinct and require explicit repair/re-enable.

On macOS, optional feedback owns an isolated nonactivating click-through AppKit panel. The bounded acknowledged pipe sends only allowlisted state, numeric level/elapsed and a selected-input flag at most five times per second, never transcript/draft text. A stale level becomes unknown after 0.8 seconds; 2.5 seconds without acknowledged updates fails/ends the companion. Feedback failure is reported independently and does not interrupt capture. Other desktops report unsupported native feedback. Native focus, display, key, microphone and field behavior require their own physical verification.

With output:insert and delivery_timing:final, 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.

With output:insert and delivery_timing:live, macOS captures a progressive target before opening the microphone. delivery.capabilities.live_supported is explicit; unsupported desktops refuse live configuration before recording. Cumulative source plus the latest non-overlapping hypothesis replaces only Olenji's previously inserted span. Hypotheses can change or shrink; they are not appended repeatedly. Finalization replaces the provisional text with the clean owned final result, without duplicating identical text. Buffered recognition latency depends on the selected model; this is not instantaneous per-word streaming.

Live mode privately reads a bounded full AXValue (at most 200,000 UTF-16 units), original caret and expected field value in the native helper. It checks foreground app/instance, field, secure state, full expected value and selection before each update, selects only its own span, rechecks and verifies the write. Human edits, caret/focus changes, failed/uncertain replies or capture gaps stop further delivery. AX does not provide atomic compare-and-replace against a human edit racing the setter; no atomic desktop transaction is claimed. Target contents never enter status, logs or durable delivery metadata. On interruption/cancellation, the last displayed draft may remain for review; there is no automatic rollback, retarget, Send or retry. Successful no-speech finalization can clear only Olenji's owned provisional span. Final-only mode keeps its existing draft-free, single-write contract.

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.

Hardware-aware model recommendations

POST /api/models/recommend, olenji models recommend and MCP olenji_recommend_models share ModelRecommendationRequest: workload is dictation (default), live or file; priority is latency, balanced (default) or quality; language defaults to auto; word_timestamps defaults to false; optional memory_budget_mb is a strict integer from 128 through 1048576, in MiB; probe_runtimes defaults to true. CLI --no-runtime-probe sets the last field to false. Invalid typed values and unknown fields fail before probing.

The report ranks at most five exact compatible ASR identities and returns best_fit, ready_now, excluded, hardware, runtime_probes, memory_budget and limitations. Candidates expose inference, execution_language, estimated_memory_bytes, installed, runtime_observed, ready, accelerator, accelerator_memory_fit, cpu_fallback, install_command, sample_required and reasons. Language and word-timing requirements are filters, not permission to substitute a model or fabricate timestamps. A model using automatic language detection can require execution_language: "auto" even when its language support satisfied a specific-language request.

ready_now is independently reranked among ready candidates and usable CPU fallbacks, so it can differ from best_fit. CUDA/Vulkan recommendations with unknown accelerator-memory fit remain unready even when the device is observed; helper executable presence alone never makes a Core ML model ready. CPU/Metal fit uses the host-budget estimate rather than certifying actual model execution.

The recommendation observes the service host and makes bounded capability probes in already installed inference runtimes when enabled. It does not download models/runtimes, select a default, start recording or benchmark speech recognition. Missing or skipped probe evidence stays unknown; a successful probe is not successful inference. Memory figures are advisory estimates and a budget is not a reservation. Explicit models and saved defaults retain their existing semantics. Applying a candidate is a separate explicit operation using its proposed inference settings and execution language.

Unspecified CPU thread defaults respect detected process affinity and Linux cgroup CPU quotas, capped at eight; explicit supported cpu_threads values remain overrides. These limits reduce oversubscription rather than promise maximal utilization or an optimal thread count.

Long sessions and consistency

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.