Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
948071f
chore(vendor): add tinyagents submodule
senamakel Oct 11, 2026
f623303
docs: add live module runtime plan and spec
senamakel Oct 11, 2026
a469077
test(service): cover live first frame metadata defaults
senamakel Oct 11, 2026
80c479f
feat(bus): add live input control frame method
senamakel Oct 11, 2026
99c9f8e
chore: files changed crates/tinyvoice-module/Cargo.lock,crates/tinyvo…
senamakel Oct 11, 2026
bba1439
Merge branch 'shrink-core-live-voice-7414' into shrink-core-live-runt…
senamakel Oct 11, 2026
a5a1002
refactor(bus): move live control vocabulary into a live module
senamakel Oct 11, 2026
94bdc77
refactor(tinyvoice-module): extract live input verification into a he…
senamakel Oct 11, 2026
1a77350
feat(live): add session start, command and handle request types
senamakel Oct 11, 2026
be25628
feat(live): carry host tool markdown preference through declarations
senamakel Oct 11, 2026
e886c03
test(live): assert full ready frame in projection test
senamakel Oct 11, 2026
1ceef0c
Merge branch 'shrink-core-live-voice-7414' into shrink-core-live-runt…
senamakel Oct 11, 2026
37b817f
feat(live): add idempotent context finish and callback size limits
senamakel Oct 11, 2026
70be48e
test(service): cover live reservation before provider startup
senamakel Oct 11, 2026
630a3b9
feat(live): add session table and actor modules
senamakel Oct 11, 2026
a5cc51c
fix(live): confirm host cleanup before releasing a session
senamakel Oct 11, 2026
6f4cca3
feat(service): expose live session lifecycle over the bus
senamakel Oct 11, 2026
13c4723
fix(live): reject start on closed or cancelled sessions
senamakel Oct 11, 2026
71158e4
test(live): add session table tests with tokio test-util
senamakel Oct 11, 2026
b696848
test(live): bind session lease before spawning blocked task
senamakel Oct 11, 2026
3c67f57
test(live): cover release cancellation keeping worker attached
senamakel Oct 11, 2026
44a9537
fix(live): run session cleanup concurrently and abort workers on cancel
senamakel Oct 11, 2026
ddef7a6
Merge branch 'shrink-core-live-voice-7414' into shrink-core-live-runt…
senamakel Oct 11, 2026
ebefcf7
test(live): add test module for callback tool
senamakel Oct 11, 2026
e06c9b7
test(live): add actor tests and simplify callback fixture
senamakel Oct 11, 2026
bf1c6db
fix(live): bound oversized live events and harden context cleanup
senamakel Oct 11, 2026
149c20f
feat(tinyvoice-module): add live runtime verification to module example
senamakel Oct 11, 2026
592076b
chore: files changed crates/tinyvoice-module/src/service/live/callbac…
senamakel Oct 11, 2026
8995188
feat(live): cancel host tool invocations when native dispatch is dropped
senamakel Oct 11, 2026
9aba759
refactor(verify_module): switch live fixture to async mutex
senamakel Oct 11, 2026
72a66f0
fix(tinyvoice-module): remove duplicate imports in live example
senamakel Oct 11, 2026
f414435
fix(live): reject oversized display metadata and type cancel callbacks
senamakel Oct 11, 2026
5c8c01c
feat(bus): expose live transport module
senamakel Oct 11, 2026
955c70a
feat(live): bind module-owned transport to live reservations
senamakel Oct 11, 2026
7e6aac8
refactor(live): reindent transport and table helpers
senamakel Oct 11, 2026
4e480b6
chore(vendor): bump tinybus submodule
senamakel Oct 11, 2026
45a8263
chore(deps): bump tinybus to 0.1.3
senamakel Oct 11, 2026
e8d613e
test(service): cover idempotent live transport binding
senamakel Oct 11, 2026
7c61cab
chore: files changed MODULE.md,crates/tinyvoice-bus/src/names/mod.rs,…
senamakel Oct 11, 2026
fd8f026
test(live): add test module for live transport
senamakel Oct 11, 2026
9afa7aa
test(live): update transport tests for renamed frame types
senamakel Oct 11, 2026
213b266
test(live): cover oversized provider startup failures
senamakel Oct 11, 2026
37f89f1
refactor(live): extract bounded projection helper
senamakel Oct 11, 2026
cbdd735
test(live): cover bound transport and callback cancellation
senamakel Oct 11, 2026
f977ecf
fix(live): capture runtime handle for invocation guard cancellation
senamakel Oct 11, 2026
e2c8ed8
chore(ci): check out submodules recursively
senamakel Oct 11, 2026
63736ed
docs(live-module-runtime): document mechanical socket callbacks
senamakel Oct 11, 2026
e8402b1
test(live): use async mutex in transport fixtures
senamakel Oct 11, 2026
c4b9097
docs: document relay ownership and library surface
senamakel Oct 11, 2026
d0675ff
chore(vendor): bump tinybus submodule
senamakel Oct 11, 2026
d89e9c0
Merge branch 'shrink-core-live-voice-7414' into shrink-core-live-runt…
senamakel Oct 11, 2026
d91ae82
chore(vendor): bump tinybus submodule
senamakel Oct 11, 2026
5834bb5
test(live): cover native input validation and output batch splitting
senamakel Oct 11, 2026
612c919
test(live): cover audio chunking and pre-connect cancellation
senamakel Oct 11, 2026
6361fb9
test(live): cover transport input handling and delivery retries
senamakel Oct 11, 2026
69fe8ee
test(live): cover admission and cleanup rejection paths
senamakel Oct 11, 2026
f6ee23f
test(live): cover idle session cancellation and tidy transport fixtures
senamakel Oct 11, 2026
910f876
test(live): cover dropped output consumer during active provider
senamakel Oct 11, 2026
cff14f0
test(live): wire up session and transport test modules
senamakel Oct 11, 2026
cdb54e9
docs(live): document the live session runtime contract
senamakel Oct 11, 2026
b238e7f
ci: enforce live module coverage and boundary checks
senamakel Oct 11, 2026
6c7d5fa
docs: format TinyAgentsLive as code and note relay coordinator follow-up
senamakel Oct 11, 2026
bc57421
docs: clarify live transport binding method
senamakel Oct 11, 2026
6aa496d
feat(relay): add runtime module and relay configuration test
senamakel Oct 11, 2026
35d57a9
feat(relay_runtime): add relay runtime service module
senamakel Oct 11, 2026
c40fba2
refactor(service): widen live random_id visibility for relay runtime
senamakel Oct 11, 2026
1e21c79
feat(relay): add relay runtime scheduling and lifecycle methods
senamakel Oct 11, 2026
c8b9755
test(relay_runtime): add actor tests and fix turns initialization
senamakel Oct 11, 2026
f445426
fix(relay_runtime): keep lease alive during brain admission
senamakel Oct 11, 2026
c8b2f92
test(relay): bind host authority to the reserved module handle
senamakel Oct 11, 2026
ea65b42
test(relay_runtime): cover native completion fallbacks and host cleanup
senamakel Oct 11, 2026
31484b7
test(relay_runtime): cover cancelled admission and revoked authority …
senamakel Oct 11, 2026
289f885
fix(relay): report coordination failures as relay errors
senamakel Oct 11, 2026
e9b03a2
chore(examples): add relay runtime verification to module check
senamakel Oct 11, 2026
db75361
docs(relay): document the relay module runtime contract
senamakel Oct 11, 2026
d7f60c6
test(relay_runtime): cover overall deadline for queued and active turns
senamakel Oct 11, 2026
84afb3d
ci: extend live module coverage check to relay runtime
senamakel Oct 11, 2026
075c05f
docs: add relay module runtime spec to index
senamakel Oct 11, 2026
d9715d4
test(relay): use digit separator for turn timeout literal
senamakel Oct 11, 2026
21130ad
docs(plans): clarify relay coordinator follow-up
senamakel Oct 11, 2026
d5796ac
test(relay_runtime): cover commit ack loss, finish gating, and deadli…
senamakel Oct 11, 2026
d0ada58
fix(relay): hold the admission permit until host finish is confirmed
senamakel Oct 11, 2026
8536e09
fix(relay_runtime): stop retrying apply after the turn deadline
senamakel Oct 11, 2026
d015999
fix(relay_runtime): defer terminal effects until native cleanup finishes
senamakel Oct 11, 2026
50ef915
fix(relay_runtime): drop unused args from lose call
senamakel Oct 11, 2026
2ecb509
fix(relay): keep context alive for terminal effects after finish
senamakel Oct 11, 2026
28170a0
test(relay_runtime): replace fixture booleans with effect and finish …
senamakel Oct 11, 2026
7ea715c
merge: preserve canonical module CI and live runtime validation
senamakel Oct 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/scripts/check-live-module-coverage.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
# Exercise native fixtures and the real artifact before checking live-runtime files.
set -euo pipefail
minimum="${1:-90}"
report="${2:-live-module-coverage.json}"
manifest="crates/tinyvoice-module/Cargo.toml"
cargo llvm-cov --locked --manifest-path "$manifest" --all-features --lib --no-report
# all-features selects static-link; the loader needs the default dynamic build.
cargo llvm-cov run --locked --manifest-path "$manifest" --no-report --example verify_module -- crates/tinyvoice-module/target/llvm-cov-target/debug/deps/libtinyvoice_module.so
cargo llvm-cov report --manifest-path "$manifest" --json --output-path "$report"
require_runtime_coverage() {
local prefix="$1"
local expected="$2"
count="$(jq --arg prefix "$prefix" '[.data[].files[] | select(.filename | startswith($prefix)) | select(.filename | endswith("_tests.rs") | not) | select(.summary.lines.count > 0)] | length' "$report")"
if [[ "$count" -ne "$expected" ]]; then
echo "expected $expected covered runtime source files under $prefix, found $count" >&2
exit 1
fi
jq -r --arg prefix "$prefix" '.data[].files[] | select(.filename | startswith($prefix)) | select(.filename | endswith("_tests.rs") | not) | select(.summary.lines.count > 0) | "\(.filename): \(.summary.lines.percent)%"' "$report"
failures="$(jq -r --arg prefix "$prefix" --argjson minimum "$minimum" '.data[].files[] | select(.filename | startswith($prefix)) | select(.filename | endswith("_tests.rs") | not) | select(.summary.lines.count > 0) | select(.summary.lines.percent < $minimum) | "\(.filename): \(.summary.lines.percent)%"' "$report")"
if [[ -n "$failures" ]]; then
printf 'Runtime files below %s%% line coverage:\n%s\n' "$minimum" "$failures" >&2
exit 1
fi

}
require_runtime_coverage "$(pwd -P)/crates/tinyvoice-module/src/service/live/" 6
require_runtime_coverage "$(pwd -P)/crates/tinyvoice-module/src/service/relay_runtime/" 2
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@
path = vendor/tinybus
url = https://github.com/tinyhumansai/tinybus
branch = main
[submodule "vendor/tinyagents"]
path = vendor/tinyagents
url = https://github.com/tinyhumansai/tinyagents
17 changes: 16 additions & 1 deletion MODULE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This package contains the native `tinyvoice` module for TinyBus module ABI v1.
Install only the archive matching the host operating system and architecture.

The module claims `ai.tinyhumans.tinyvoice.Voice`, serves the object at
`/ai/tinyhumans/tinyvoice/Voice`, and provides 34 methods under contract 1.6.
`/ai/tinyhumans/tinyvoice/Voice`, and provides 47 methods under contract 1.8.

## Methods

Expand All @@ -21,6 +21,19 @@ The module claims `ai.tinyhumans.tinyvoice.Voice`, serves the object at
| `VadReset` | `session` | — |
| `VadClose` | `session` | — |
| `PrepareRelayTurn` | `RelayTurnRequest { messages, readback_prefix }` | `PreparedRelayTurn`: prompt, ordered history pairs, optional readback payload, content-free, speak-back and answerable flags |
| `PrepareLiveInput` | `PrepareLiveInputRequest { awaiting_start, frame }` | parsed control or transport action; host owns policy validation |
| `LiveBindTransport` | reserved handle and mechanical host transport port | idempotent `LiveStatus`; module owns the client/provider coordinator |
| `RelayConfigure` | explicit concurrent/pending turn limits | immutable idempotent `RelayLimits` |
| `RelayReserve` | none | bounded opaque `RelayStatus` before native work |
| `RelayStart` | reserved handle, saved host callback context, explicit timing/fallbacks | asynchronous idempotent `RelayStatus` |
| `RelayRelease` | known relay handle | bounded cleanup and closed status |
| `RelayShutdown` | none | permanent closed admission and bounded concurrent cleanup |
| `LiveReserve` | none | opaque `LiveStatus` reserved before connection startup |
| `LiveStart` | `LiveStartRequest` | idempotent starting/running status for identical inputs |
| `LiveSend` | handle and text, PCM16 audio, interrupt or stop command | status after bounded input admission |
| `LiveRead` | handle and optional acknowledged batch | bounded replayable ordered frames and persistence effects |
| `LiveRelease` | handle | closed status after bounded provider/context cleanup |
| `LiveShutdown` | none | terminal admission shutdown and concurrent bounded cleanup |
| `PrepareFrames` | `samples`, `source_rate`, `channels` | base64 `f32` mono @ 16 kHz |
| `FrameEnergies` | `samples`, `frame_len` | per-frame RMS |
| `EncodeWav` | `samples` (`f32` mono), `sample_rate` | base64 WAV |
Expand Down Expand Up @@ -114,3 +127,5 @@ TinyBus modules are trusted in-process code: a loaded module shares the address
space, the privileges and the crash domain, and TinyBus never unloads one.
Install release artifacts only from a trusted source, and restart the host after
replacing a loaded module.

Relay coordination uses context-bound host callbacks; see the [relay runtime contract](docs/specs/relay-module-runtime.md).
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ permission check.
| Wake-word gate, intent routing | What to *do* with an intent |
| Hallucination detection | The STT transport, credentials, and retry policy |
| Relay user-text extraction, readback classification and turn preparation | Local agent execution, product prompts, acknowledgment timing and delivery |
| Portable live controls and module-owned provider/socket coordinator | Prepared credentials, provider selection, current tool policy and transcript persistence |

A visible consequence: `VadConfig` has no constructor that reads a config file.
A host builds one from whatever it persists. A crate that guessed at that shape
Expand Down Expand Up @@ -98,6 +99,29 @@ on a realtime thread where blocking causes dropouts, so callbacks forward raw
interleaved samples into a bounded queue. Hosts poll batches and call
`PrepareFrames` from a worker.

### Relay turn coordination

The compiled module also coordinates admitted local-brain turns through
explicit RelayHost callbacks: scheduling, selected progress, an acknowledgment
clock that starts before queueing, and deferred completion. Hosts supply the
native brain and retain correlation, phrase and delivery policy. See the
[relay runtime contract](docs/specs/relay-module-runtime.md).

### Live sessions

The compiled module owns the live provider loop through TinyAgentsLive and a
bounded client/provider coordinator. Hosts reserve a lease, bind mechanical
socket callbacks with `LiveBindTransport`, and supply prepared connection
material and explicit declarations through `LiveStart`. The module handles
polling, ordered effects, replay and cleanup; callbacks retain product policy
and persistence. A declaration never grants execution permission.

The minimal contract also supports manual `LiveSend`/`LiveRead` for hosts that
need a custom transport, with replay until acknowledgment. Those operations
are excluded once a module transport is bound. See the
[live runtime contract](docs/specs/live-module-runtime.md) for bounds and
callback cancellation requirements.

## Layout

```text
Expand Down
1 change: 1 addition & 0 deletions crates/tinyvoice-bus/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ mod error;
pub mod capture;
pub mod hotkey;
pub mod intent;
pub mod live;
pub mod names;
pub mod provider;
pub mod relay;
Expand Down
168 changes: 168 additions & 0 deletions crates/tinyvoice-bus/src/live/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
//! Portable live websocket control vocabulary.

pub mod session;
/// Mechanical client socket and persistence callbacks for module-owned pumping.
pub mod transport;

use serde::{Deserialize, Serialize};

/// A client control frame; audio travels separately as PCM16 bytes.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ClientFrame {
/// First-frame connection request; identities remain opaque host metadata.
Start {
/// Optional host provider selection.
#[serde(default)]
provider: Option<String>,
/// Optional host transcript identity.
#[serde(default)]
thread_id: Option<String>,
/// Optional host approval-routing identity.
#[serde(default)]
client_id: Option<String>,
/// Mono PCM16 input rate, defaulting to sixteen kHz.
#[serde(default = "default_input_rate")]
input_sample_rate: u32,
},
/// Typed user input.
Text {
/// Original user text.
text: String,
},
/// Interrupt the current provider reply.
Interrupt,
/// Ask the provider to close.
Stop,
}

fn default_input_rate() -> u32 {
16_000
}

/// Transport facts supplied to the module's control parser.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum TransportFrame {
/// UTF-8 JSON control input.
Text {
/// The original websocket text.
text: String,
},
/// Mono PCM16 little-endian input.
Audio {
/// PCM16 bytes.
pcm16: Vec<u8>,
},
/// The client disconnected.
Closed,
/// Ping, pong and other non-command frames.
Other,
}

/// Parse one control frame under the current session phase.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PrepareLiveInputRequest {
/// Whether the connection still requires its first start frame.
pub awaiting_start: bool,
/// Original transport frame.
pub frame: TransportFrame,
}

/// A parsed command; malformed later frames are deliberately ignored.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum PreparedLiveInput {
/// An accepted control command.
Control(ClientFrame),
/// A non-control transport action.
Transport(LiveTransportAction),
}

/// A transport action requiring no JSON control parsing.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum LiveTransportAction {
/// Forward input audio.
Audio {
/// Original PCM16 bytes.
pcm16: Vec<u8>,
},
/// Close the provider sender.
Closed,
/// No provider action.
Ignore,
}

/// Who a final transcript belongs to.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TranscriptRole {
/// Spoken or typed user input.
User,
/// Spoken assistant output.
Agent,
}

/// A live server JSON frame; audio remains a separate binary transport action.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ServerFrame {
/// The provider connected.
Ready {
/// Provider session identity or the host fallback.
session_id: Option<String>,
/// Host-selected provider identifier.
provider: String,
/// Mono PCM16 output sample rate.
output_sample_rate: u32,
/// Opaque host transcript identity.
thread_id: Option<String>,
},
/// Partial or final speech transcript.
Transcript {
/// User or agent speech.
role: TranscriptRole,
/// Original transcript text.
text: String,
/// Whether this completes the utterance.
#[serde(rename = "final")]
is_final: bool,
},
/// A provider tool call entered the harness.
ToolStarted {
/// Provider call identity.
call_id: String,
/// Declared tool name.
name: String,
},
/// A tool call completed or was cancelled.
ToolFinished {
/// Provider call identity.
call_id: String,
/// Declared tool name.
name: String,
/// Whether the result was successful.
ok: bool,
/// Whether the provider cancelled the result.
cancelled: bool,
},
/// Provider playback was interrupted.
Interrupted,
/// The provider completed its turn.
TurnComplete,
/// A provider or connection error.
Error {
/// Stable portable error classification.
code: String,
/// Provider diagnostic.
message: String,
/// Whether the provider is terminating.
fatal: bool,
},
/// The provider connection ended.
Closed {
/// Established close diagnostic.
reason: String,
},
}
Loading
Loading