These libraries are experimental. APIs may change without notice. Generated from source with koruc 0.1.7 on 11/4/2026.
Needle
@korulang/needle@0.0.1On-device speech transcription via libneedle (Cactus-Compute), behind phantom obligations
needle/index.kz · 5 tors
@korulang/needle — on-device speech transcription via libneedle. · 28 more lines
@korulang/needle — on-device speech transcription via libneedle.
Wraps the Cactus-Compute engine (libneedle.dylib, ~1.1MB) behind phantom
obligation types. The C ABI is already an obligation shape:
needle_load(weights, len) → engine ready; <0 → needle_last_error()
needle_transcribe(...) → one-shot PCM → JSON result
needle_stream_transcribe_process/stop → chunked live transcription
needle_reset() → releases loaded model state
Every verb borrows `*Engine<ready>`; transcribing with no engine loaded is
a compile error, not a runtime null. `unload` discharges the minted token.
Results land in the caller's out-buffer as a JSON C string
({"text","language","ttft_ms","decode_tps"}, + "words" when
word_timestamps=1) — the edition hands back that JSON verbatim as
`string<std/io:allocated!>`, discharged by `std/io:free`. Field extraction
belongs to a caller-side parser (koru/yyjson), not to this seam.
PCM is passed as raw little-endian f32 bytes at 16kHz mono (whistle's
contract). A typed sample surface can come later; `string` today carries
the same bytes without inventing a sample type the toolchain hasn't
ruled on.
libs/needle:load(path: "whistle.cact")
| ready eng |> libs/needle:transcribe(eng, pcm: pcm, language: "en")
| ok json |> std/io:print.ln("{{ json:s }}") |> std/io:free(content: json)
|> libs/needle:unload(eng)
| err e |> std/io:print.ln("{{ e:s }}") |> libs/needle:unload(eng)
| err e |> std/io:print.ln("load: {{ e:s }}")
Phantom lifecycles
Derived from the phantom labels in the declarations below — state! issues an
obligation the compiler will chase, !state discharges it, a bare state holds it without moving it. Nothing here is hand-drawn.
Engine 1 state ready!string 2 states allocated!// Lifecycle
//
// Load model weights from a .cact archive — mints the engine token.
~pub tor load { path: string }
| ready *Engine<ready!>
| err string// Discharge the engine token. needle_reset releases the loaded model state
// (the ABI has no narrower unload); the wrapper and weight copy go with it.
~pub tor unload { eng: *Engine<!ready> }// Transcription — one-shot and streaming
//
// Transcribe a whole clip: `pcm` is 16kHz mono f32le bytes, `language` is a
// tag ("en", "de", "fr", "es", "it", "nl", "pl") — empty string detects.
// `pcm` takes <*>: it copies bytes verbatim, so a caller's `allocated!`
// buffer feeds it directly (KORU030 rejected the unqualified spelling).
~pub tor transcribe { eng: *Engine<ready>, pcm: string<*>, language: string }
| ok string<std.io:allocated!>
| err string// Push one chunk of a live stream (~1s of 16kHz mono f32le). Each call's JSON
// carries that chunk's committed text and the unconfirmed tail.
~pub tor stream.process { eng: *Engine<ready>, pcm: string<*>, language: string }
| ok string<std.io:allocated!>
| err string// Drain the stream's tail — call once after the last process chunk.
~pub tor stream.stop { eng: *Engine<ready> }
| ok string<std.io:allocated!>
| err string