File size: 8,257 Bytes
879acfa | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 | ---
license: apache-2.0
language:
- en
pipeline_tag: voice-activity-detection
library_name: pytorch
tags:
- turn-taking
- end-of-turn
- interruption
- backchannel
- turnbench
- audio
---
# Kuroko
Kuroko is a turn-taking system for two-channel English conversation, built by PatientDesk AI for
[TurnBench](https://turnbench.sesame.com). It decides when a speaker's turn has ended, and whether a sound from the
other speaker is an interruption or an "mhm".
**Write-up:** <https://spero.patientdesk.ai/blog/kuroko>
This repository holds Kuroko's weights, the one file needed to run them, its settings and its predictions for
TurnBench's development split. The training code and the training data are not public, and neither are the
predictions for the test split, at the benchmark's request.
## Run the model
The model is a 14.9M-parameter causal transformer. It hears both speakers and answers 25 times a second for each
of them, from the past only.
```
pip install torch numpy scipy safetensors soundfile huggingface_hub
```
```python
import sys
import soundfile as sf
from huggingface_hub import snapshot_download
path = snapshot_download("cloud0day3/kuroko")
sys.path.insert(0, path)
import kuroko
model = kuroko.load(path)
audio, rate = sf.read("conversation.wav", dtype="int16") # (samples, 2): one speaker on each channel
answers = kuroko.answers(model, kuroko.features(audio, rate)) # (2, 11, n): speaker, answer, 25 a second
claims = answers[:, kuroko.ANSWERS.index("claim")]
```
`answers[s, a, k]` is a probability for speaker `s` that has heard the audio up to `(k + 1) / 25` seconds. The 11
answers are named in `kuroko.ANSWERS`:
| Answer | Meaning |
|---|---|
| `voice` | The speaker is making a sound |
| `claim` | That sound claims the floor. This is the answer the events are read from |
| `hold` | The speaker holds the floor, pauses included |
| 8 more | Whether the speaker, and whether the partner, will talk for half or more of 0 to 0.2 s, 0.2 to 0.6 s, 0.6 to 1.2 s and 1.2 to 2 s ahead |
The model was trained on 16-bit recordings at 48 kHz with one speaker per channel, brought to 16 kHz as
`kuroko.features` does it. Read that way, on the GPU the predictions in this repository were made on, the released
files give the answers behind them on all 38 dev conversations. On a CPU an answer differs by less than 0.001,
which can move a threshold crossing by one answer, 40 ms. Audio of more bits read as floats moves an answer by up
to 0.02.
Only `claim` has been measured against annotations. Offline, a recorded 12.6-minute conversation is answered in 11 s
on four CPU threads, in 40-second stretches. The model has not been run as a live stream.
## From answers to events
The model is one of three parts, and this repository has the code of the model only.
1. **Detector.** [Silero VAD](https://github.com/snakers4/silero-vad) 6.2.3 runs on each channel in 32 ms steps and
marks stretches of sound. It is certain that a stretch has ended 0.26 s after its last sound.
2. **Rules.** The timing rules p99lab published for [turn-1-mini](https://github.com/P99Lab/turn-1-mini), without that
system's learned parts, with the numbers chosen again on the dev split (`policy.json`). Every stop opens a
candidate end of turn, which is committed once the partner has taken over or 2.45 s have passed, unless the
speaker is back.
3. **Model.** `claim` commits an interruption when it reaches 0.9, read from 0.32 s after a sound begins, before
the sound has lasted the 1.3 s the length rule needs. It also tells the end-of-turn rules that the partner has
taken over, when it reaches 0.5 for the partner's sound.
The write-up gives the rules in full.
## Results
TurnBench development split: 38 conversations, 1,904 ends of turn, 347 interruptions, scored by the benchmark's
scorer. The settings were chosen on this split within a false-alarm budget of 8%. "Held out" is the same choice made
on half of the conversations and scored on the other half, over 500 random halves. Kuroko was submitted to TurnBench
on November 13, 2026. The test split is scored by the benchmark's maintainers, and its results are theirs to
publish on the [leaderboard](https://turnbench.sesame.com).
| | Recall | False-alarm rate | Median delay | Held-out recall | Held-out false-alarm rate |
|---|---|---|---|---|---|
| End of turn | 0.987 (1,879 of 1,904) | 0.079 (84 of 1,063) | 630 ms | 0.983 | 0.082 |
| Interruption | 1.000 (347 of 347) | 0.073 (272 of 3,733) | 545 ms | 0.988 | 0.061 |
To check these numbers, drop `predictions-dev.json` on the benchmark's [dev scorer](https://turnbench.sesame.com/dev).
It scores the file in the browser.
What each part adds, on the same split (recall / false-alarm rate / median delay):
| System | End of turn | Interruption |
|---|---|---|
| Rules, turn-1-mini's published settings, on our detector | 0.968 / 0.051 / 879 ms | 0.899 / 0.026 / 1,579 ms |
| Rules, settings chosen on dev | 0.983 / 0.080 / 692 ms | 0.977 / 0.076 / 1,078 ms |
| **Kuroko**: rules and the model | 0.987 / 0.079 / 630 ms | 1.000 / 0.073 / 545 ms |
## Training data
1,025 hours of two-speaker conversation: 786 hours of [Open Yap 1K](https://theagenticdatacompany.com/open-yap-1k)
and about 240 hours of PatientDesk's own recordings, made by contributors who agreed to this use. The labels come
from a timing rule applied in hindsight. None of the data is in this repository.
No audio and no label from TurnBench's dev or test split was trained on, and otoSpeech was not used. The dev split
chose the settings, checked the label rule and compared the systems above.
## Files
| File | Contents |
|---|---|
| `model.safetensors` | The weights, kept at step 12,000 |
| `config.json` | The model's shape |
| `kuroko.py` | The model, its features and how a conversation is answered. Nothing of the training |
| `policy.json` | The settings of the rules, in seconds and answer levels: `eot` for ends of turn, `int` for interruptions |
| `predictions-dev.json` | Kuroko's events for the 38 dev conversations, in TurnBench's submission format |
| `training-log.jsonl` | Training loss and the loss of each answer on held-out conversations, every 600 steps |
Each block of `policy.json` is a full settings object, and each rule reads only part of it. The end-of-turn rules
read `wait_s`, `partner_s`, `deadline_s`, `resumed_s`, `held_s`, `confirm_s`, `partner_claim` and `claim_from_s` from
`eot`. The interruption rule reads `sustained_s`, `claim_fire`, `claim_from_s` and `claim_rise` from `int`.
`known_s` and `begun_s` are the detector's delays.
## Limits
- These are development numbers on 38 conversations, with settings chosen on them. Zero missed interruptions is an
in-sample number; held out, recall is 0.988.
- TurnBench records two people, so the end-of-turn rules can wait for the second person to start talking. A voice
agent is the second speaker, and in an agent these rules would be left with the 2.45 s timer.
- A turn's end can be committed a second time, and an interruption is committed for every sound taken as a claim.
The scorer counts at most one false alarm per pause, and looks for interruptions only near annotated interruptions
and backchannels. Kept at least 1 s apart, the events score 0.975 at 0.074 on ends of turn and 1.000 at 0.070 on
interruptions.
- Studio audio with one clean channel per speaker, English only. Not measured on phone audio, with echo or as a live
stream.
The write-up has the rest: the label rule and its check against the annotators, the ablations, the causality test
and the credits.
## License
`model.safetensors`, `config.json` and `kuroko.py` are released under Apache-2.0.
## Credits
TurnBench is by Sesame AI Labs, with the evaluation corpus from Mundo AI and the otoSpeech training set from Oto
([Jiang et al., 2026](https://arxiv.org/abs/2608.25218)). The timing rules are p99lab's. The detector is Silero VAD.
The model's future-activity targets follow voice activity projection
([Ekstedt and Skantze, 2022](https://arxiv.org/abs/2205.09812)).
Training data: The Agentic Data Company. Open Yap 1K: channel-separated English natural two-speaker conversations.
2026. Version 1.0. <https://theagenticdatacompany.com/open-yap-1k>
|