y83 projects / 02 / android + go + llama

Мэри — ассистент, который слышит, действует и выбирает движок

работает · 2026

Мэри живёт на телефоне: оверлей принимает голосовые команды, Go-сервер в Termux маршрутизирует их между локальной моделью, облачным провайдером и WebAI, а собственный мост внутри APK читает дерево экрана, рисует номера поверх кликабельных узлов и выполняет действия. 34 навыка срабатывают по шаблонам без модели, автороутер добирает остальное коротким запросом. Локальный QVikhr работает через llama-server и OpenCL на Adreno 825; облачный путь переключается без перезапуска приложения.

версия
3.31.8 / code 261
сервер
Go · 36 579 строк
клиент
Kotlin · 12 919 строк
API
116 маршрутов · REST + SSE

продукт

Один агент, три движка и два моста

Задача

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

Решение

Основной путь остаётся локальным: QVikhr 1.5B на llama-server, голос Silero и мосты внутри APK. Но модель больше не единственная: ModelHub переключает чат между локальной моделью и любым OpenAI-совместимым API-провайдером, а WebAI/DeepSeek остаётся отдельным слоем для разговоров, поиска и vision-запросов.

Граница

Мэри не обещает, что локальная модель сама разберётся в любом интерфейсе. Быстрые команды уходят навыкам до генерации, а модель получает только очищенный контекст и список интерактивных узлов.

Устройство

Три слоя и один контракт. Оверлей отвечает за голос, окна и рисунок поверх экрана; Go-сервер ведёт навыки, движки, память и браузер; мост внутри APK — за доступность и действия. Что делать на текущем экране, решает сервер: он отдаёт APK состояние, тихий режим, разрешение рисовать номера, длительность окна прослушки, уровень приглушения медиа и доступные команды.

возможности

Что умеет

Основное — не отдельная команда, а соединение голоса, памяти, модели и управления интерфейсом.

Голос и диалог

Плавающий оверлей

Круг-микрофон поверх приложений, режимы «Общие», «Диалог» и «Найти в интернет», панель чата и окна ответов.

  • overlay
  • ui

Распознавание без потери первого слова

Горячее окно SpeechRecognizer и VAD: 70–150 мс до готовности, тишина закрывает прослушивание без повторных попыток.

  • stt
  • vad

Ответ в такт речи

Карточка печатает текст по словам синхронно с фактической длительностью Silero, а не по оценке.

  • tts
  • ui

Тихие сценарии

Поверх видео, Shorts и музыки Мэри уважает контекст: не перебивает звук, а переходит в текст или приглушает только окно ответа.

  • media
  • ui

Три движка

Локальная QVikhr

Внешний llama-server поднимает модель в Termux, определяет реальный GPU-бэкенд по /proc и сам останавливает её после простоя облачной ветки. На Adreno 825 OpenCL ускоряет prefill с 42 до 344 t/s — восьмикратно к CPU.

  • llama
  • opencl

Хаб API-провайдеров

19 пресетов OpenAI-совместимых провайдеров, включая свой адрес: discovery моделей, проверка ключа, флаги vision/reasoning и отдельный лимит контекста.

  • model hub

WebAI/DeepSeek

Отдельный слой аккаунтов с очередью, лимитами, контекстным разговором и локальным OpenAI-совместимым /v1 для внешних клиентов.

  • webai
  • api

Единый выбор

Один resolver собирает клиент для голоса, чата и браузерного агента; при недоступном облаке чат возвращается к локальной модели.

  • engine resolver

Мост и узлы телефона

Мост внутри APK

ControlBridgeService работает как служба доступности и держит свой HTTP-мост: 14 действий — tap и long-press, свайп, ключи, click/tap/long-click/scroll-to по узлу, ввод текста, открытие приложения, URI и браузера, Intent, буфер обмена. На стороне Go — 22 маршрутов /api/control.

  • accessibility
  • bridge

Узлы, а не координаты

Сервер ищет узел по id, тексту, описанию или классу, делает hit_test по координатам, ждёт появления узла и подписывает его; действие идёт по узлу, а не по пикселю.

  • find
  • hit_test
  • wait

Чтение экрана

control/read отдаёт плоский список с ролями, состояниями и позициями, control/semantic — короткое семантическое представление; журнал действий хранит, что и когда нажали.

  • read
  • semantic

Номера поверх экрана

Оверлей рисует номера кликабельных узлов поверх интерфейса — как подсказка выбора в YouTube. Достаточно сказать «нажми 3»: сервер берёт тот же список и тапает нужный узел, а после проверки по фингерпринту говорит, изменился ли экран.

  • overlay
  • numbers

Эффекты действий

SSE-поток /api/control/effect/stream присылает кадр в момент действия: оверлей рисует тап-кольцо со свечением или свайп «пальцем» синхронно с движением, а не с задержкой опроса.

  • sse
  • fx

Контракт экрана

Сервер сам решает поведение: состояние экрана, тихий режим, разрешение рисовать номера, озвучка ответов, длина окна прослушки, уровень приглушения медиа и список доступных команд. APK не угадывает сценарий, а исполняет контракт.

  • behavior

Браузер и поиск

Браузерный агент

Видимый браузер получает очищенное дерево страницы, LLM выбирает web_tap, web_type, web_scroll_to, web_navigate или web_done, а fingerprint сравнивает страницу до и после: два одинаковых состояния подряд останавливают цикл.

  • browser agent

Честный тупик

Детектор отмечает captcha и challenge и не выдаёт их за результат — сценарий останавливается с понятной причиной, а не тыкает в страницу бесконечно.

  • captcha

Поиск через браузер

Скрытый WebView идёт к DuckDuckGo и читает страницы; AgentSearch, SearXNG, Google CSE и Яндекс подключаются как внешние движки, когда заданы ключи.

  • search
  • webview

Скиллы и автороутер

34 быстрых навыка

Погода, солнце, валюты, новости, время, калькулятор, перевод, напоминания, буфер обмена, уведомления, скриншот, карты, открытие приложений, YouTube и управление голосом — каждое срабатывает по шаблону, без обращения к модели.

  • skills

Автороутер

Если шаблоны не совпали, один короткий запрос к активной модели возвращает JSON с навыком, аргументами и уверенностью. Ниже порога 0.6 решение отбрасывается, и фраза идёт обычным диалогом — роутер не угадывает.

  • router
  • llm

Продолжение фразы

Сработавший навык и его аргументы держатся 15 минут: после «погода в Варшаве» фраза «а послезавтра?» досылает недостающий город в тот же навык.

  • follow-up

Умные руки

Команда вида «нажми/открой/введи», которую не поймал ни один шаблон, уходит в UI-фолбэк: очищенный список кликабельных узлов → локальная модель → одно JSON-действие (tap, type, back, home, swipe-up). Результат сверяется по фингерпринту экрана.

  • ui agent

Инструменты вместо строки команд

Облачные модели получают tools API: до трёх раундов вызовов, WebAI — локальный OpenAI-совместимый эндпоинт.

  • tools

Память и сервисы

Память фактов и действий

Пин «запомни, что…», действия, вопросы и ответы попадают в локальную память; релевантное подмешивается через BM25.

  • memory

Дневники недели

Записи дня сжимаются локальной моделью в темы и итог, последние семь дней остаются доступными для вопросов о прошлом.

  • daily memory

Напоминания

Голосовая команда разбирает «через 10 минут» и «завтра в 9», а список и отмена доступны в настройках.

  • reminders

Мосты и watchdog

ControlBridge, WebBridge, NotifBridge и BridgeWatchdog восстанавливают соединение после вылета клиента или агента.

  • bridges

как работает

Путь команды

01

Голос или текст

Оверлей или WebView-чат передают команду Go-серверу в Termux.

02

Маршрутизация

Регулярки и LLM-роутер выбирают навык, режим поиска или диалога.

03

Движок

EngineResolver собирает клиента локальной модели, API-провайдера или WebAI.

04

Инструмент

Модель получает локальный контекст и вызывает навык, телефон, браузер или чтение экрана.

05

Проверка

Действие сверяется по узлу, fingerprint страницы или ответу инструмента.

06

Память и ответ

Результат стримится, пополняет память и озвучивается Silero.

результаты

49,5k

строк Go + Kotlin

36 579 Go (30 439 без тестов) и 12 919 Kotlin
116

маршрутов API

REST, SSE и локальный OpenAI-совместимый /v1
3

движка

локальная QVikhr, API-провайдер и WebAI — переключение без перезапуска
62

тестовых файлов

6 140 строк тестов; 35 файлов Kotlin и 5 682 строк внутренней документации
34

быстрых навыка

срабатывают по шаблонам без модели; выше порога 0.6 подключается автороутер
22

маршрута управления

14 действий моста: узлы, ввод, свайпы, приложения, уведомления и буфер

Цифры сверены с кодом проекта 2026-10-14.

экраны

Интерфейс Мэри

Основные состояния и интерфейсы, которые видны пользователю во время работы.

листайте вбок · 8 экранов

Плавающий микрофон

Плавающий микрофон

состояния слушаю, думаю, говорю

Голосовая карточка

Голосовая карточка

ответ печатается в такт Silero

Текстовый чат

Текстовый чат

история, стриминг, Markdown и сноски

Хаб моделей

Хаб моделей

локальная модель или API-провайдер

Браузерный агент

Браузерный агент

дерево страницы и следующий шаг

Панель управления

Панель управления

узлы, уведомления, приложения и журнал

Номера на экране

Номера на экране

нумерация кликабельных узлов поверх интерфейса

Журнал действий

Журнал действий

что нажали, когда и чем закончилось

технологии

Из чего собрано

Клиент

  • Kotlin
  • Jetpack Compose
  • SpeechRecognizer
  • AccessibilityService
  • WebView

Сервер

  • Go
  • HTTP + SSE
  • domain → application → infrastructure
  • modernc.org/sqlite

Движки

  • llama-server
  • OpenCL / Adreno 825
  • OpenAI-compatible API
  • WebAI / DeepSeek

Инфраструктура

  • runit
  • WebView search
  • MCP-клиент
  • Silero TTS

этапы

Что сделано и что дальше

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

этап 1

Голос и оверлей

распознавание, озвучка, режимы и чат

этап 2

Телефон и память

мост доступности, семантическое чтение, дневники

этап 3

Локальный LLM

внешний llama-server и OpenCL на телефоне

этап 4

Хаб движков

провайдеры, discovery моделей, WebAI и автоостановка локальной модели

этап 5

Браузерный агент

семантическое дерево, действия и fingerprint для веб-сценариев

вопросы

Частые вопросы

Мэри работает только локально?

Нет. Локальный QVikhr на llama-server — базовый режим, но ModelHub также выбирает OpenAI-совместимого провайдера, а WebAI/DeepSeek остаётся отдельным слоем для разговоров и поиска. При недоступном облаке чат возвращается к локальной модели.

Зачем нужен собственный мост Android?

ControlBridgeService работает внутри APK через AccessibilityService: отдаёт дерево доступности, принимает действия по узлам и возвращает семантическое состояние экрана. Внешний MCP остаётся альтернативным источником управления.

Как устроен браузерный агент?

Видимый браузер отдаёт очищенное дерево и снимок изменения состояния. LLM выбирает действие из ограниченного набора, после чего fingerprint сравнивает страницу до и после. Одинаковая страница два раза подряд останавливает сценарий.

Что значит «Мэри рисует номера»?

Оверлей нумерует кликабельные узлы текущего экрана и держит номера поверх интерфейса, пока список свежий. Команда «нажми 3» берёт тот же список и нажимает нужный узел через мост; результат проверяется по фингерпринту — если экран не изменился, Мэри об этом скажет.

Кто решает, как вести себя на экране?

Сервер. Он отдаёт APK контракт текущего экрана — состояние, тихий режим, разрешение рисовать номера, озвучку, длину окна прослушки, уровень приглушения медиа и доступные команды. Клиент исполняет контракт, а не угадывает сценарий: правила живут в одном месте.

Зачем свой роутер навыков, если есть модель?

Быстрые команды («погода», «поставь напоминание») дешевле и точнее выполнить по шаблону, чем гонять модель. Автороутер подключается только там, где шаблоны не совпали: один короткий запрос, JSON с навыком и уверенностью, порог 0.6. Ниже порога фраза идёт обычным диалогом.

Почему llama-server иногда не отвечает?

Внешняя llama-server живёт в Termux под runit и может быть остановлена как сервис. Если активна облачная модель и локальную не использовали 10 минут, ModelService гасит её, чтобы не греть GPU; возврат к локальной модели поднимает сервис через Termux или кнопку запуска.

Мэри

Есть вопрос по проекту?

Мэри — голосовой ассистент и браузерный агент для Android. Базовый контур живёт на телефоне: она слышит и отвечает голосом, читает дерево экрана, рисует номера поверх кликабельных узлов, управляет телефоном через собственный мост и выбирает один из трёх движков — локальную QVikhr, API-провайдера или WebAI/DeepSeek.

@xipovski xipovski@gmail.com
следующий проект Y83 llama-adreno