Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
7d12add
MAESTRO: add A Cappella architecture docs (system overview, protocol,…
pedramamini Aug 15, 2026
0116127
MAESTRO: add A Cappella shared protocol types (protocol, state machin…
pedramamini Aug 15, 2026
ef506eb
MAESTRO: register A Cappella as a default-off Encore Feature
pedramamini Aug 15, 2026
565940a
MAESTRO: add A Cappella headless voice session service
pedramamini Aug 15, 2026
418ec39
MAESTRO: add A Cappella mock provider tier and provider registry
pedramamini Aug 15, 2026
4f9ecac
MAESTRO: add A Cappella dispatch route executor and agent roster
pedramamini Aug 15, 2026
433cdcd
MAESTRO: expose A Cappella voice sessions over IPC and preload
pedramamini Aug 15, 2026
2b5506c
MAESTRO: fix isolatedModules type re-export in web-server services ba…
pedramamini Aug 15, 2026
b6c7718
MAESTRO: add A Cappella Voice HUD, dev harness, and renderer session …
pedramamini Aug 15, 2026
3e37bda
MAESTRO: prove every A Cappella voice state transition in tests
pedramamini Aug 15, 2026
2c20c57
MAESTRO: close the A Cappella speech-run wedge and release the floor …
pedramamini Aug 15, 2026
37e605b
MAESTRO: add the hidden A Cappella audio host window
pedramamini Aug 15, 2026
b47c6a8
MAESTRO: build the A Cappella audio host renderer
pedramamini Aug 15, 2026
89a5be5
MAESTRO: add the A Cappella energy-plus-zero-crossing VAD
pedramamini Aug 15, 2026
eac7b2b
MAESTRO: wire the A Cappella duplex audio pipeline
pedramamini Aug 15, 2026
847cfe6
MAESTRO: add A Cappella floor control with tap, hold, and idle timeout
pedramamini Aug 15, 2026
471ff8f
MAESTRO: add A Cappella audio-level and mic-state events with a live …
pedramamini Aug 15, 2026
73ac871
MAESTRO: wire A Cappella capture to a real streaming STT seam
pedramamini Aug 15, 2026
d7bc9f9
MAESTRO: audit the A Cappella audio suites and fix a throwing floor s…
pedramamini Aug 15, 2026
209a64b
MAESTRO: rewire A Cappella audio when the Encore Feature is switched …
pedramamini Aug 15, 2026
54d538b
MAESTRO: add the A Cappella model manager, downloader, and capability…
pedramamini Aug 15, 2026
fbc3a7b
MAESTRO: add the A Cappella native runtime loader, packaging checks, …
pedramamini Aug 15, 2026
b5872bd
MAESTRO: land the A Cappella STT, TTS, Brain, and realtime provider t…
pedramamini Aug 15, 2026
de70cd0
MAESTRO: add the A Cappella wake word, stop word, and named global ho…
pedramamini Aug 15, 2026
65ecfae
MAESTRO: land the A Cappella Conductor router, grammar-constrained de…
pedramamini Aug 15, 2026
ed0d4f1
MAESTRO: measure A Cappella routing quality with a headless evaluatio…
pedramamini Aug 15, 2026
1cf669b
MAESTRO: add the A Cappella conversational translator, sentence-strea…
pedramamini Aug 15, 2026
40d0492
MAESTRO: measure A Cappella time-to-first-spoken-word with a headless…
pedramamini Aug 15, 2026
886d9cc
MAESTRO: add the A Cappella by-hand conversation acceptance checklist
pedramamini Aug 15, 2026
a13d59c
MAESTRO: wire the A Cappella speech layer into the voice session
pedramamini Aug 15, 2026
2a5dbe4
MAESTRO: build the A Cappella voice HUD, live transcript, and per-age…
pedramamini Aug 15, 2026
06a1fc2
MAESTRO: add the A Cappella voice and speed settings panel, live outp…
pedramamini Aug 15, 2026
c922be0
MAESTRO: add the A Cappella WebRTC media leg, device pairing, ICE/TUR…
pedramamini Aug 15, 2026
c90eb53
MAESTRO: pin the platform in the A Cappella microphone-prompt test
pedramamini Aug 15, 2026
e250a6f
MAESTRO: write the A Cappella iOS client specification set
pedramamini Aug 15, 2026
4f6aa50
MAESTRO: specify the A Cappella iOS protocol conformance contract
pedramamini Aug 15, 2026
77e0a00
MAESTRO: build the A Cappella browser reference client
pedramamini Aug 15, 2026
d387c3d
MAESTRO: add the A Cappella protocol conformance suite
pedramamini Aug 15, 2026
9cc53d4
MAESTRO: settle pairing writes before a conformance world is torn down
pedramamini Aug 15, 2026
f479c03
MAESTRO: specify the A Cappella iOS repository layout and signing chain
pedramamini Aug 15, 2026
943c097
MAESTRO: document A Cappella for users and show the hotkey that is ac…
pedramamini Aug 15, 2026
f6215c7
MAESTRO: make switching A Cappella off actually stop everything
pedramamini Aug 15, 2026
c9fbac1
MAESTRO: stop the conformance teardown from racing a pairing write
pedramamini Aug 16, 2026
3ceb585
MAESTRO: stop "hey maestro" from meaning the agent called Maestro
pedramamini Aug 16, 2026
bf00b02
MAESTRO: stop the voice HUD from reporting "1 of 0"
pedramamini Aug 16, 2026
25d6007
MAESTRO: park the minimized voice HUD in the Left Bar, like the media…
pedramamini Aug 16, 2026
5cf047b
MAESTRO: make A Cappella findable in the command palette
pedramamini Aug 16, 2026
d690788
MAESTRO: put the A Cappella microphone under the composer's Send button
pedramamini Aug 16, 2026
ac8bae0
MAESTRO: bottom-justify the composer pills under the A Cappella mic
pedramamini Aug 16, 2026
5b3ff5b
MAESTRO: stop A Cappella promising a voice session it cannot run
pedramamini Aug 16, 2026
56128b7
MAESTRO: stop the voice refusal from arriving as a wall of red
pedramamini Aug 17, 2026
6d2542a
MAESTRO: stop a dead voice error from leaving a HUD nobody opened
pedramamini Aug 17, 2026
9b1135c
MAESTRO: stop the dev harness from opening A Cappella on every dev build
pedramamini Aug 17, 2026
bad9734
MAESTRO: scope a voice session to the window it was opened in
pedramamini Aug 17, 2026
53e1bd8
MAESTRO: drop the voice microphone from the Main Panel header
pedramamini Aug 17, 2026
2dc37b2
MAESTRO: index A Cappella for agents and stop the docs inviting a dea…
pedramamini Aug 17, 2026
e4a450c
MAESTRO: let A Cappella actually hear you, and pick which microphone
pedramamini Aug 17, 2026
6c9ac43
MAESTRO: stop the microphone check talking to your agents, and record…
pedramamini Aug 18, 2026
8e8a35d
MAESTRO: stop a pause mid-sentence becoming two requests to an agent
pedramamini Aug 18, 2026
e938363
MAESTRO: let the Conductor talk it through before handing work to an …
pedramamini Aug 18, 2026
2a95bc9
MAESTRO: give a spoken request an Enter key
pedramamini Aug 18, 2026
9597471
MAESTRO: make letting go of the talk key actually send the request
pedramamini Aug 18, 2026
87febed
MAESTRO: give the browser tab's agent-visibility toggle a real tooltip
pedramamini Aug 19, 2026
01a7a23
MAESTRO: fetch the voice runtimes instead of bundling 300 MB nobody a…
pedramamini Aug 22, 2026
a9603a1
MAESTRO: stop every Cue run losing the session id its token usage han…
pedramamini Aug 25, 2026
3b1ce23
MAESTRO: teach the spoken-recall test that a snooze now says which ki…
pedramamini Aug 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,16 @@ yarn-error.log*

# Bundled-plugin signing artifact (release-time build output; dev signs locally)
examples/plugins/*/signature.json

# A Cappella model weights and partial downloads.
#
# Models live in userData and are downloaded by the user, never bundled into the
# installer: one of them is 1.1 GB, and shipping weights would multiply the
# download for everyone including people who never turn voice on. These patterns
# exist so a stray copy in the working tree (a manual download, a test fixture, a
# .part file from an interrupted transfer) cannot be committed by accident.
*.gguf
*.onnx
*.part
ggml-*.bin
models/acappella/
118 changes: 67 additions & 51 deletions CLAUDE.md

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion build/entitlements.mac.plist
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<!-- A Cappella needs the microphone. Without this entitlement the hardened
runtime denies capture outright and the user never even sees a prompt,
which presents as a session that starts and stays silent forever. -->
<key>com.apple.security.device.audio-input</key>
<false/>
<true/>
<key>com.apple.security.automation.apple-events</key>
<true/>
</dict>
Expand Down
5 changes: 5 additions & 0 deletions docs/.mintignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,8 @@
# `{...}` examples that are valid Markdown but not valid MDX, so exclude the whole
# directory from the Mintlify build to keep the deploy green.
agent-guides/

# Internal architecture notes and ADRs (structured markdown with YAML front
# matter and `[[wiki-links]]` for DocGraph, not part of docs.json navigation).
# Same MDX-hostile `{...}` type examples as above.
architecture/
29 changes: 29 additions & 0 deletions docs/agent-guides/SHARED-UTILS.md
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,35 @@ A checked task is stepped over entirely, marker and all. That keeps a half-finis

---

## A Cappella Encore Flag (`src/shared/acappella/feature-flag.ts` - Both)

The ONE reader of the `encoreFeatures.aCappella` flag. Do NOT hand-roll
`flags.aCappella === true` at a new call site: the surfaces that gate on it are
not one system (IPC handlers, the hotkey installation, the WebSocket signaling
adapter, the transport, the debug-package collector), they each control a real
resource - a microphone, a global shortcut, a Bonjour advert - and a surface that
disagrees with the rest leaves one of those running behind a switch the user
believes is off. Five byte-identical copies had already accumulated.

| Function / Constant | Signature | Purpose |
| -------------------------------- | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `isACappellaEnabled(store)` | `(EncoreFlagStore) => bool` | True only for the literal `true`. A hand-edited `"true"` or `1` reads as OFF, which is the safe direction for a mic feature. |
| `requireACappellaEnabled(store)` | `(EncoreFlagStore) => void` | Throw `ACappellaDisabled` unless the flag is on. What a gated IPC handler calls. |
| `ACAPPELLA_DISABLED_ERROR` | `'ACappellaDisabled'` | The stable error string the renderer maps. Not prose - a sentence here would be a wire contract. |
| `EncoreFlagStore` | `{ get(key, default?) }` | The narrow store slice this needs, so an electron-store or a plain object both satisfy it. |

**Turning the flag off is a teardown, not just a gate.**
`shutdownACappellaForDisable()` in `src/main/ipc/handlers/acappella.ts` is what
`main/index.ts` runs from its `encoreFeatures` watcher: it stops the session,
drops the audio bridge, disposes the inference pipeline (which is also what lets
reclaim-disk delete model files on Windows), and calls
`ACappellaTransport.standDown()` for the advert and the connected phones. It
deliberately does NOT dispose the transport or the hotkey installation - both are
built once per process, so tearing them down would mean switching the feature
back on did nothing until the next restart.

---

## Synopsis Parsing (`src/shared/synopsis.ts` - Both)

| Function / Constant | Signature | Purpose |
Expand Down
207 changes: 207 additions & 0 deletions docs/architecture/acappella/conversation-acceptance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
---
type: reference
title: A Cappella Conversation Acceptance Checklist
created: 2026-08-15
tags:
- acappella
- architecture
- testing
- speech
related:
- '[[latency-baseline]]'
- '[[system-overview]]'
- '[[voice-session-protocol]]'
---

# A Cappella Conversation Acceptance Checklist

The speech layer is the one part of A Cappella that automated tests cannot sign off. A scheduler
can be proven to deliver sentences in order without a human confirming that the result sounds like
a person talking, and a barge-in controller can tear down in the right order while still feeling
laggy to the person doing the interrupting. This document is the by-hand pass: what to say, what to
listen for, and which module to open when a check fails.

Run it on the oldest machine available. Every check below is a latency judgement in disguise, and a
fast machine hides the failures this list exists to find.

## Precondition: the layer has to be reachable

The speech layer is wired as of 2026-08-15. `VoiceSessionService` composes the translator, the
scheduler, the barge-in controller, the detail buffer, and the background announcer directly, and
`src/main/ipc/handlers/acappella.ts` builds the agent-output tap over the process manager and hands
it in as `agentReplyStream`. Confirm both seams before treating any failure below as a bug:

```bash
grep -rn "ConversationalTranslator\|SpeechScheduler\|BargeInController" \
src/main/acappella/voice-session-service.ts
grep -rn "createAgentOutputTap\|agentReplyStream" src/main/ipc/handlers/acappella.ts
```

Two things can still leave the layer inert at runtime, and both are silent:

- **No process manager.** `agentReplyStream` is optional, and without it the session waits for a
whole reply through `submitAgentReply()` - the `buffered` counterfactual the latency harness
measures the shipped path against in [[latency-baseline]]. This is the mock tier and the dev
harness. In the packaged app the manager is passed from `src/main/ipc/bootstrap/index.ts`.
- **A focus-only dispatch.** The tap is only armed when the dispatch actually sent a prompt, so
"switch to the backend agent" is correctly followed by silence rather than by the tab's previous
output being read aloud.

One thing is genuinely not wired yet: `focusTarget`, which is what a "show me" needs to put a tab on
screen. Nothing supplies it, so check 5's `show` case focuses nothing today. That is a renderer
round trip and belongs with the Phase 09 tab affordances.

## What else you need

Four things, none of which live in the repo:

1. The three native runtimes installed. They are declared in
`src/shared/acappella/native-runtimes.ts` but are not yet `package.json` dependencies. See
[[packaging-notes]].
2. The model set downloaded, from **Settings > Plugins > A Cappella > Models**.
3. API keys for OpenAI and ElevenLabs if you are testing anything other than the fully local
configuration.
4. A microphone, and a quiet room. Barge-in checks are meaningless over a fan.

## The checks

Each check is one utterance, one thing to listen for, and one place to look when it fails.

### 1. The first word arrives before the agent has finished writing

**Say:** "ask backend to summarise everything that changed in the router this week"

**Listen for:** speech starting while the agent is visibly still writing in its tab. The point is
not that speech is fast, it is that speech and writing overlap. If the reply finishes rendering in
silence and only then does anything get said, the tap is not cutting at a completed thought.

**When it fails:** `speech/agent-output-tap.ts`. `DEFAULT_MIN_CHUNK_CHARS` is 200, so a reply whose
first paragraph is shorter than that waits for a paragraph break. Raise the agent's verbosity before
concluding the tap is broken.

### 2. Sentences play without gaps

**Say:** anything that produces a four or five sentence answer.

**Listen for:** the seam between sentence one and sentence two. A pause there is a provider round
trip that the lookahead should have hidden.

**When it fails:** `speech/speech-scheduler.ts`. `DEFAULT_LOOKAHEAD` is 1, meaning two sentences are
in flight at once. A slow TTS provider may need 2. Note that the scheduler delivers strictly in
order regardless of lookahead, so raising it cannot reorder speech.

### 3. Interrupting stops audio immediately and captures your first word

**Say:** anything long, then talk over it mid-sentence. Start your interruption with a distinct word
you can check for, such as "stop, actually, what about the tests".

**Listen for:** audio dropping within a beat, not at the end of the current sentence. Then check the
transcript: your first word has to be there. "Actually, what about the tests" with the leading
"stop" missing means the pre-roll is not reaching the reopened floor.

**When it fails:** `speech/barge-in.ts`. Ducking is a 20 ms ramp to `DEFAULT_DUCK_GAIN` 0.15, so what
you hear should be a fast fade rather than a hard cut. The order matters and is deliberate: duck,
flush playback, cancel synthesis, cancel the translator stream, reopen the floor. A missing first
word is the last step; audio that keeps playing is one of the first two.

Also confirm the negative case: the assistant must not interrupt itself. Let a long reply play in
full without speaking. Any self-interrupt means AEC leakage is beating the 250 ms
`DEFAULT_GUARD_MS` window.

### 4. "Tell me more" drills into real detail instantly

**Say:** after any substantial reply, "tell me more".

**Listen for:** detail arriving with no perceptible think time and no new work in the agent's tab.
The whole point of the detail buffer is that the follow-up costs nothing. If the agent tab shows a
new turn, the utterance was routed instead of matched as a follow-up.

**Then check the siblings:** "read that again" repeats what was actually spoken, not what was
queued. "What was the file" speaks a basename, never a path read character by character. "Show me"
focuses the tab and says nothing at all.

**When it fails:** `speech/drill-down.ts`. Intent matching is ordered, with `show` ahead of `file`.

### 5. Nothing markdown-shaped is ever read aloud

**Say:** something that forces a code-heavy answer, such as "ask backend to show me the diff for the
router change".

**Listen for:** silence over the diff. The intro line should be spoken, the fence never. This is the
check most likely to surface something ugly, because it is where the tap's filtering and the
translator's markdown stripping have to agree.

**Known rough edge:** a diff-heavy reply produces a real multi-second silence mid-turn. The intro is
spoken, the fence is correctly skipped, and the hang notice cannot cover the gap because the diff
keeps arriving as `data` and keeps resetting the 20 s timer. Nothing is malfunctioning and it is
still the worst listening experience the harness produces. See [[latency-baseline]].

### 6. A background completion waits for a pause

**Say:** dispatch to a second agent, then start a conversation with the first while the second
works.

**Listen for:** the second agent's completion never landing on top of your conversation, and when it
does land, naming its source ("the backend agent finished the migration").

**When it fails:** `speech/background-announcer.ts`. The setting is `speakBackgroundCompletions`
under the `acappella` settings key, with `on | off | auto`. `auto` is the default and resolves to on
for the Conductor scope, off inside a focused agent session, so a silent announcement inside a
focused session is correct behaviour rather than a bug.

## The visual checks

The six checks above are about what you hear. These are about what you see, and they are equally
outside what an automated test can sign off: jsdom has no layout engine, so every assertion about
clipping, readability, and contrast in the test suite is an assertion about VALUES rather than about
pixels. The exception is colour contrast, which `VoiceAccessibility.test.tsx` verifies against every
shipped theme with `contrastRatio()`, so this pass is looking for layout and legibility rather than
re-checking the numbers.

Run these in at least **three themes, one of which must be a light theme**. Light themes are where a
widget built against a dark default falls apart, and A Cappella's HUD is drawn almost entirely from
theme colours.

### 7. The HUD is readable and nothing clips

**Do:** open a session, drag the HUD to each corner, and let a turn run through listening, thinking,
and speaking in each theme.

**Look for:** the five indicator states distinguishable at a glance and by SHAPE, not only by hue
(outlined ring, filled disc, dashed spinner ring, error ring). The bound scope in the agent's own
colour, legible against the panel. Nothing spilling out of the widget.

### 8. Minimize keeps the audio, close stops it

**Do:** while a reply is being spoken, press the `-` button. Then restore, and press the ESC pill.

**Look for:** minimize collapsing the HUD to a small indicator with the reply STILL AUDIBLE and a
visible way back. Close stopping the speech and ending the session.

A control that hides itself must not silently leave a hot microphone, and a close button that only
hides leaves audio coming from nowhere. This is the one pair in the feature where getting it
backwards is a safety problem rather than a papercut.

### 9. The transcript survives a restart and does not interrupt

**Do:** turn the transcript on from the HUD, quit and reopen Maestro, then turn it off mid-reply.

**Look for:** the transcript still open after the restart, and turning it off leaving the
conversation running - the speech does not stop, the floor is not released, and the next sentence
still arrives.

### 10. Reduced motion actually stops the motion

**Do:** turn on the OS "reduce motion" setting while a session is live (macOS: System Settings ->
Accessibility -> Display -> Reduce motion).

**Look for:** the animations stopping WITHOUT a restart, and each state still distinguishable
without them. This widget is designed to be left on screen all day, which is exactly why a
permanently animating one is a real problem rather than a preference.

## Recording the result

Numbers from the same session go in the **Measured results** table in [[latency-baseline]]: press
**Read last turn** on the Models page and paste the copied JSON. Three turns per configuration,
record the median. The first turn of a local configuration includes the model load and is not
representative of a conversation.
Loading