AGENT-A · голосовой интерфейс Джарвиса

Gemini Live слышит и говорит, Claude Code думает и делает

Концепция для агента, который будет собирать такой же голос с нуля: из чего состоит, как части разговаривают друг с другом и какие грабли уже пройдены.

Собрано по коду 09.11.2026 · модуль AGENT-A (папка mostik) · статус: работает на стенде, в прод не выкачено
Главная идея
Один разум, два темпа

Пользователь говорит с одной личностью. Внутри два исполнителя: быстрый голос, который отвечает за доли секунды, и медленный разум, который разбирается и действует. Голос никогда не говорит «передам Джарвису» или «другая модель»: ответ разума приходит к нему как его собственная мысль.

Рот и уши
Gemini Live

Слышит речь, сам ловит конец фразы, говорит голосом, отвечает на простое по срезу состояния. Ничего не решает и ничего не меняет.

Мозг и руки
Джарвис в Claude Code

Постоянная сессия Claude Code в tmux со своим MCP. Получает просьбу обычной репликой, думает, вызывает инструменты, меняет мир.

Схема

Браузер
Микрофон и динамик
AudioWorklet снимает звук, режет в 16 кГц и шлёт кусками по 40 мс. Ответ играет через усилитель и ограничитель.
Сервер-прокси (FastAPI)
Держит ключ и связывает голос с разумом
Ключ Vertex живёт только здесь. Три параллельных цикла: звук вверх, звук и события вниз, таймер. Исполняет инструменты Gemini.
Рот и уши
Gemini Live
native-audio, Vertex, ru-RU, голос Charon
Мозг и руки
Джарвис, Claude Code
sonnet, корень ~/ai-system, MCP agent-a

Как голос поручает работу разуму: think_deeper

Пример одной просьбы от начала до конца. Это сердце всей конструкции.

  1. ТимурЗаведи задачу проверить версию 1.8 на очках.
  2. GeminiПересказывает одной фразой, как понял, и спрашивает разрешение. Фразу-заполнитель говорит ДО вызова инструмента, иначе слышна пауза.Так, задача на проверку 1.8 для очков. Делаю?
  3. СерверGemini вызывает think_deeper(request). Сервер сразу отвечает ему «ещё не выполнено, жду подтверждения», чтобы живая сессия не зависла, и уводит работу в фон.
  4. ТимурДа, делай.Сервер ловит «да» регуляркой по транскрипции речи (или кнопкой на экране), ждёт до 90 с. «Нет», «стоп», «отмена» или тишина: ничего не делаем.
  5. СерверОтправляет в tmux-сессию Claude Code обычную реплику, как будто её набрали руками:[голос] Заведи задачу проверить версию 1.8 на очках. (недавний голосовой разговор: …последние 600 символов)
  6. Claude CodeВидит метку [голос] и первым делом вызывает MCP-инструмент say: короткая фраза попадает в очередь сразу, посреди хода. Потом работает как обычно.Принял, запускаю.
  7. СерверКаждые 0,5 с читает очередь say и ленту сессии. Каждые 6–9 с просит Gemini сказать новую короткую фразу, чтобы не было тишины.Ещё пара секунд.
  8. СерверОтвет готов, когда текст есть, сессия свободна и 2 с ничего нового. Возвращает его Gemini как реплику:[Мысль] Задача заведена в реестр… (это твой собственный вывод, скажи от первого лица)
  9. GeminiПроизносит своими словами, как будто сам только что додумал.Готово, задачу на проверку 1.8 завёл.

Компоненты

livetalk.ts

Браузер

  • Микрофон с браузерным эхоподавлением, AudioWorklet, пересэмплирование в 16 кГц int16 моно, куски по 640 сэмплов.
  • Ответ 24 кГц в очередь AudioBufferSource, потом усиление (1×/2×/3×) и ограничитель, чтобы не хрипело.
  • Эхо-гейт: пока звучит ответ, тихий сигнал (RMS ниже 0,05) заменяется нулями. Громкий проходит, это перебивание.
  • Событие interrupted сбрасывает очередь воспроизведения мгновенно.
  • Live не поднялся: сам уходит на запасной локальный TTS (Piper). Интерфейс у движков один, окно не знает, кто говорит.
gemlive.py

Сервер-прокси

  • Первый кадр от клиента: JSON-конфиг (голос, тишина конца фразы, чувствительность). Дальше звук и команды.
  • Ответ только аудио, ru-RU, транскрипция входа и выхода: для субтитров, для ловли «да/нет» и для истории разговора.
  • VAD (определение конца фразы): 1000 мс тишины и низкая чувствительность к концу речи.
  • В системный промпт при старте вшит срез: сессии, задачи, лимиты. Большинство вопросов закрывается без вызовов.
  • Таймер: потолок сессии 10 минут, закрытие после 90 секунд тишины.
промпт Gemini

Одна личность

  • Только от первого лица: «проверю», «сейчас подумаю». Запрещено «передам», «спрошу у мозга», «система ответила».
  • Коротко, 1–2 фразы, на «ты», живые реакции («так», «ага, смотри»), без списков.
  • Перед действием: пересказ и «делаю?». «[Мысль]» произносить как свой вывод.
  • Перебили: замолчать на полуслове и отвечать на новое.
role.py, mcp_server.py

Сторона Claude Code

  • Метка [голос] означает, что фразу сказали вслух и ответ будет озвучен.
  • Нужен хоть один инструмент: первым вызовом say с фразой из 2–3 слов.
  • Итог: 1–3 коротких предложения без markdown, списков, ссылок и кода. Длинное: суть вслух и «подробности в чате».
  • Распознавание путает названия: неясное слово переспросить, а не угадывать.

Инструменты Gemini

ИнструментЧто делаетКто исполняет
get_statusСессии, задачи или лимиты неделисервер, до 8 с
session_statusПодробно про одну сессиюсервер, до 8 с
think_deeperРазобраться и сделать: задача, запуск работы, ответ сессии, решениеClaude Code, до 170 с
end_sessionПопрощаться и закрыть разговор («пока», «отбой»)сервер

Быстрые инструменты только читают. Всё, что меняет мир, идёт через Claude Code и только после «да».

Замеры на стенде, 05.11

ЧтоВремя
Первый звук после конца фразы, VAD 600 мс (как в приложении очков)1,63 с
То же, VAD 1000 мс и низкая чувствительность (выбрано)2,9–3,2 с
Полный цикл: просьба, «делаю?», «да», ответ Claude Code озвучен6,3 с
Старый путь через Piper, только озвучка5–11 с

При 600 мс длинные фразы обрываются на паузе. При 1000 мс фраза с паузой 0,8 с посередине дошла целиком, поэтому выбран медленный, но надёжный вариант.

Почему голос здесь именно рот и уши

Gemini не получает права действовать. Он слышит, говорит и отвечает по тому, что уже знает. Всё остальное делает Claude Code, у которого вся система, память и инструменты.

Порядок сборки с нуля

  1. Прокси WebSocket ↔ Live API с ключом на сервере: только звук туда и обратно, VAD, транскрипции.
  2. Браузерный клиент: worklet, пересэмплирование, очередь воспроизведения, усиление и ограничитель, эхо-гейт, сброс по interrupted.
  3. Промпт «одна личность, два темпа» и срез состояния при старте разговора.
  4. Быстрые инструменты только на чтение.
  5. think_deeper: мгновенный ответ инструмента, ожидание подтверждения, реплика [голос] в сессию мозга, сбор ответа из say и ленты, заполнители, возврат через [Мысль].
  6. Голосовые правила в промпте мозга и инструмент say в его MCP.
  7. Предохранители: потолок времени, закрытие по тишине, mute, end_session, запасной TTS.

С чего начать безопасно

Прототип с заглушкой вместо AGENT-A лежит в папке projects/lab/live-voice/: server.py, jarvis_test/stub.py (вымышленные сессии и задачи), brain.py (настоящий Claude Code смотрит на заглушку). Боевую сессию Джарвиса на нём не задеть.

Грабли и честные оговорки

  • Журнал Claude Code ленивый. Реплика и вызовы попадают в файл сессии в конце хода. Без инструмента say первая фраза прозвучит только после всей работы.
  • think_deeper нельзя ждать синхронно. Ответ инструмента сразу, работа в фоне, иначе Live-сессия замирает.
  • Сессия мозга занята или ждёт решения в окне. Реплика не дойдёт: сказать об этом голосом честно, а не молчать.
  • Без потолков сессия висит и тратит деньги. Потолок 600 с и закрытие по 90 с тишины обязательны. При выключенном микрофоне тишина простоем не считается.
  • Эхо. Без гейта Gemini слышит сам себя и перебивает свою же речь.
  • Оговорка по цифрам: замеры сняты на одном стенде 05.11, Chromium с фейковым микрофоном. В прод эта версия ещё не выкачена, живой статистики нет.
Код: projects/mostik/server/assistant/gemlive.py · web/src/panels/assistant/livetalk.ts · server/jarvis/ (runtime, say, role, mcp_server)
Текстовая версия: projects/mostik/docs/VOICE-GEMINI-LIVE.md