Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 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
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
22 changes: 22 additions & 0 deletions .github/scripts/check-live-module-coverage.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/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"
prefix="$(pwd -P)/crates/tinyvoice-module/src/service/live/"
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 6 ]]; then
echo "expected six covered live-runtime source files, 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 'Live runtime files below %s%% line coverage:\n%s\n' "$minimum" "$failures" >&2
exit 1
fi
22 changes: 16 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
# This job executes repository code (cargo build/test); don't persist
# the token in git config.
persist-credentials: false
submodules: true
submodules: recursive

- name: Install X11 development libraries
run: sudo apt-get update && sudo apt-get install --yes libx11-dev libxtst-dev
Expand Down Expand Up @@ -72,15 +72,25 @@ jobs:
- name: Test default features
run: cargo test

- name: Verify compiled live module boundary
run: |
cargo build --locked --manifest-path crates/tinyvoice-module/Cargo.toml --lib
cargo run --locked --manifest-path crates/tinyvoice-module/Cargo.toml --example verify_module -- crates/tinyvoice-module/target/debug/libtinyvoice_module.so

- name: Require 90% line coverage in every source file
run: .github/scripts/check-file-coverage.sh 90 coverage.json

- name: Require 90% live module file coverage
run: .github/scripts/check-live-module-coverage.sh 90 live-module-coverage.json

- name: Upload coverage report
if: ${{ always() }}
uses: actions/upload-artifact@v7
with:
name: coverage-json
path: coverage.json
path: |
coverage.json
live-module-coverage.json
if-no-files-found: ignore

windows-hotkeys:
Expand All @@ -90,7 +100,7 @@ jobs:
- uses: actions/checkout@v7
with:
persist-credentials: false
submodules: true
submodules: recursive

- uses: dtolnay/rust-toolchain@stable

Expand All @@ -112,7 +122,7 @@ jobs:
- uses: actions/checkout@v7
with:
persist-credentials: false
submodules: true
submodules: recursive

- name: Install X11 development libraries
run: sudo apt-get update && sudo apt-get install --yes libx11-dev libxtst-dev
Expand All @@ -136,7 +146,7 @@ jobs:
- uses: actions/checkout@v7
with:
persist-credentials: false
submodules: true
submodules: recursive

- name: Install X11 development libraries
run: sudo apt-get update && sudo apt-get install --yes libx11-dev libxtst-dev
Expand Down Expand Up @@ -169,7 +179,7 @@ jobs:
- uses: actions/checkout@v7
with:
persist-credentials: false
submodules: true
submodules: recursive

- name: Check advisories, licenses, bans, and sources
uses: EmbarkStudios/cargo-deny-action@v2
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
- uses: actions/checkout@v7
with:
fetch-depth: 0
submodules: true
submodules: recursive

- name: Install ALSA development files
run: sudo apt-get update && sudo apt-get install --yes libasound2-dev
Expand Down Expand Up @@ -280,7 +280,7 @@ jobs:
with:
ref: ${{ needs.publish.outputs.tag }}
persist-credentials: false
submodules: true
submodules: recursive

- uses: dtolnay/rust-toolchain@stable

Expand Down Expand Up @@ -397,7 +397,7 @@ jobs:
with:
ref: ${{ needs.publish.outputs.tag }}
persist-credentials: false
submodules: true
submodules: recursive

- name: Download workflow artifacts
uses: actions/download-artifact@v8
Expand Down
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
10 changes: 9 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 42 methods under contract 1.7.

## Methods

Expand All @@ -21,6 +21,14 @@ 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 |
| `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
16 changes: 16 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,21 @@ 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.

### 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 @@ -13,6 +13,7 @@
pub mod capture;
pub mod hotkey;
pub mod intent;
pub mod live;
pub mod names;
pub mod relay;
pub mod transcript;
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