Чему меня научили два месяца с легковесным локальным AI-агентом

от автора

Raspberry Pi на Mac mini - оба гоняют openLight, оба маленькие, оба всегда онлайн.

Raspberry Pi на Mac mini — оба гоняют openLight, оба маленькие, оба всегда онлайн.

Когда я писал первую статью на Хабр про openLight в марте, проект состоял из одного коммита, одной Raspberry Pi и одного Telegram-бота.
У меня был Pi с Tailscale, маленький Matrix-сервер и несколько сервисов, за которыми хотелось приглядывать. Я устал печатать ssh pi@raspberrypi.local && systemctl status ... с телефона, поэтому написал небольшой Go-бинарь: Telegram-бот, SQLite и локальный Ollama как fallback, если обычный роутер не понимал запрос.

Прошло два месяца. Бинарь всё ещё весит около 25 МБ. Всё ещё один YAML-конфиг. Всё ещё SQLite. Но под капотом почти всё переписано хотя бы один раз.

И главное, изменилось понимание того, чем вообще стал проект.

openLight — это легковесный операционный слой для личных серверов, а не очередной AI-ассистент общего назначения.

В марте я бы так не сформулировал. Чтобы прийти к этому, понадобилось несколько месяцев реального использования, переписываний и откатов назад.

Это ретроспектива о том:

  • что сработало

  • что оказалось ошибкой

  • и почему я всё больше верю в маленькие локальные системы вместо “автономных AI-агентов”

Пять моментов, ради которых всё это

Telegram-алерт: synapse status check failed, с кнопками Restart / Logs / Status / Ignore.

Telegram-алерт: synapse status check failed, с кнопками Restart / Logs / Status / Ignore.
  • Поздний вечер, я не дома. Synapse упал на VPS. Я нажимаю Restart прямо в Telegram. Сервис поднимается. Ноутбук даже не открывал.

  • Очередь в магазине. Tailscale начал сыпать warning’ами. Нажимаю Logs, вижу знакомую проблему с peer’ом, ставлю Ignore на 15 минут.

  • Перелёт. Watch-цикл продолжает работать сам. Когда я приземляюсь, в чате уже лежат сообщения resolved — всё восстановилось без меня.

  • Mac mini дома. На нём Ollama и несколько Docker-сервисов. Watch на CPU > 90% срабатывает из-за моего же фонового джоба, о котором я уже забыл.

  • Удалённая машина. /restart matrix с телефона уходит в docker-compose на VPS. Для меня это выглядит так же, как локальная Raspberry Pi.

Вся архитектура ниже существует только потому, что такие сценарии реально происходят.

Что изменилось технически

Роутинг: от плоского к deterministic-first

Изначально роутер был очень простым:

  • попробовать slash-команду

  • попробовать regex

  • если ничего не подошло, то отправить запрос в Ollama

Этого хватает примерно на неделю.

Потом начинаешь замечать:

  • половина задержки — это запуск модели на Pi

  • модель иногда выбирает “почти правильный”, но не тот tool

  • а “не понимаю запрос” и “уверен на 51%, выполняю” — это две очень разные ситуации

Роутер до и после: слева плоский путь slash → regex → Ollama, справа детерминированный каскад и двухстадийный LLM-классификатор.

Роутер до и после: слева плоский путь slash → regex → Ollama, справа детерминированный каскад и двухстадийный LLM-классификатор.

Сейчас перед LLM есть несколько полностью детерминированных слоёв:

  • slash-команды

  • aliases

  • нормализация

  • rule-based parsing

  • semantic mapping

И только если всё это не сработало, подключается модель.

Telegram: "что там с системой" → Hostname, CPU, Memory, Disk, Uptime, Temperature, всё в одном коротком ответе.

Telegram: «что там с системой» → Hostname, CPU, Memory, Disk, Uptime, Temperature, всё в одном коротком ответе.

Скриншот выше, тот же самый skill /status, но без LLM-вызова: русская фраза детерминированно нормализовалась.

CLI бьёт в тот же runtime, что и Telegram-бот — skills, watch list, notes против agent.test.yaml.

CLI бьёт в тот же runtime, что и Telegram-бот — skills, watch list, notes против agent.test.yaml.

CLI теперь работает через тот же runtime, что и Telegram-бот:

  • тот же registry

  • тот же router

  • тот же auth

  • те же skill’ы

Это позволило использовать его и для smoke-тестов, и для локальной отладки.

От localhost к SSH-нодам

Первая версия openLight умела работать только с той машиной, на которой была запущена.

Но быстро стало понятно, если у тебя есть VPS, Mac mini, Raspberry Pi и ещё пара коробок, то нужен единый интерфейс. Так появилась идея:

  • один openLight

  • много SSH-нод

Нода — это просто именованный SSH-target в конфиге.

Сервис может выглядеть так:

node:vps:compose:/opt/matrix/docker-compose.yml:synapse

Для пользователя это всё ещё просто:

/restart matrix

Одна команда у пользователя, одна проверка allowlist'а, один резолвер, шесть бэкендов: local systemd / docker / compose плюс их удалённые версии через SSH.

Одна команда у пользователя, одна проверка allowlist’а, один резолвер, шесть бэкендов: local systemd / docker / compose плюс их удалённые версии через SSH.

Но внутри это может быть:

  • systemd

  • docker

  • docker compose

  • локально

  • удалённо через SSH

Самое важное, пользователь этого не видит. Есть:

  • один skill

  • один allowlist

  • один audit path

Именно так и должно быть.

От request-response к monitoring loop

Самое большое изменение — система watch’ей. v0.0.1 был полностью реактивным: я что-то спрашивал, агент отвечал. v0.1.0 добавил monitoring loop:

  • правила

  • polling

  • incidents

  • cooldown’ы

  • и Telegram-алерты с кнопками

Restart, Logs, Status, Ignore.

Но самая важная часть не в polling’е. А в том, что кнопки используют тот же самый skill path, что и ручные команды. Когда я нажимаю Restart в alert’е, вызывается тот же service_restart, что и при обычной команде.

  • тот же allowlist

  • тот же audit

  • тот же logging path

Нет отдельного “режима автоматизации”.

End-to-end инцидент: alert → кнопка Restart → "Restarting synapse..." → подтверждение статуса сервиса → несколько resolved-сообщений.

End-to-end инцидент: alert → кнопка Restart → «Restarting synapse…» → подтверждение статуса сервиса → несколько resolved-сообщений.

Наверное, это решение нравится мне больше всего. И оно почти противоположно тому, куда движется большая часть AI-agent tooling. Сейчас популярная идея:

  • дать модели shell

  • дать sandbox

  • дать autonomy

  • и надеяться на лучшее

Но для инфраструктуры это выглядит опасно. Инфраструктуре нужен не “автономный агент”. Ей нужен понятный, проверяемый execution path. Автоматизация — это кнопка поверх уже существующего безопасного механизма, а не отдельный уровень привилегий.

Что изменилось стратегически

Skill’ы — единственная настоящая граница безопасности

В ранних версиях у меня был mutating_execute_threshold — порог уверенности для действий, которые меняют состояние. Потом я понял, что это неправильная модель. Правильная модель гораздо проще:

LLM может выбирать только из уже существующих skill’ов, а сами skill’ы обеспечивают безопасность на уровне Go-кода.

Либо функция существует и проходит allowlist-проверки. Либо нет. Модель — это классификатор намерения, а не носитель привилегий.

Core vs Optional

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

  • vision,

  • browser,

  • voice,

  • OCR,

мне хотелось включить это по умолчанию. Потому что демо выглядит круто. А потом через пару недель я дебажил:

  • память,

  • Playwright,

  • лишние зависимости,

  • latency,

  • и весь тот “AI assistant creep”, которого изначально хотел избежать.

Поэтому сейчас в проекте есть чёткое разделение:

  • core modules,

  • optional modules.

Если убрать optional-модули, то openLight всё ещё останется openLight. Если убрать core, то проект потеряет идентичность.

/skills с группами: Chat, Notes, Memory, Files, Browser, Services, Watch, System, Core, Vision, OCR, Visual watch.

/skills с группами: Chat, Notes, Memory, Files, Browser, Services, Watch, System, Core, Vision, OCR, Visual watch.

Один и тот же реестр виден LLM-классификатору и в ответе на пользовательский /skills. Никакой параллельной поверхности нет.

От Raspberry Pi к personal infrastructure

Где-то на третьем или четвёртом крупном рефакторе я понял, что openLight уже не про Raspberry Pi. Pi был просто самой маленькой машиной, которая оказалась под рукой. На самом деле меня начала интересовать другая категория:

  • маленькие always-on компьютеры

  • локальные серверы

  • Mac mini

  • старые ThinkPad

  • NUC

  • Raspberry Pi

  • домашние ARM-машины

Все они живут в странном промежутке:

  • это уже не ноутбук

  • но ещё и не “облако”

Для них плохо подходит mainstream tooling. Kubernetes здесь почти всегда избыточен. Datadog — тоже. Большие AI-agent frameworks — тем более. Этим машинам нужен:

  • маленький

  • понятный

  • дешёвый

  • repairable слой управления

Mac mini особенно сильно изменил моё восприятие проекта. M1 спокойно тянет:

  • Ollama

  • несколько Docker-сервисов

  • monitoring

  • Telegram-agent

  • и при этом потребляет смешное количество энергии

В какой-то момент openLight перестал быть “ботом для Raspberry Pi”. Он стал агентом для personal infrastructure, который просто использует Telegram как интерфейс. И мне кажется, что в ближайшие годы вокруг этой категории появится очень много интересного софта.

Что оказалось ошибкой

Фрейминг “альтернатива OpenClaw”

В первых README я слишком сильно определял проект через: “мы не такие как X” Это плохая идея. Во-первых, если человек не знает OpenClaw, ему всё равно. Во-вторых, решения начинают приниматься “от противного”: “они делают так, значит мы должны наоборот”. Это не архитектурный принцип.

“Structured tool calling” в раннем roadmap

Тогда мне казалось, что проблема решается более сложным tool-calling. Сейчас я думаю наоборот: проблема решается более сильным deterministic routing. Большая часть запросов вообще не должна доходить до LLM.

Полный registry в prompt

Ранний classifier видел:

  • все skill’ы

  • все описания

  • весь registry

Это:

  • раздувало prompt

  • замедляло routing

  • и ухудшало качество

Двухстадийная классификация решила проблему:

  • сначала только группы

  • потом только skill’ы внутри группы

LLM input budget — это тоже архитектурное ограничение.

Что подтвердилось практикой

Telegram — отличный интерфейс для homelab

Я пробовал:

  • Slack

  • web UI

  • dashboard’ы

Но всё проигрывало сценарию:

“я не дома, сервис упал, достал телефон, нажал Restart”.

SQLite хватает

  • Watch’и

  • Incidents

  • History

  • Settings

  • Skill calls

Всё живёт в одном SQLite-файле. Backup — это literally cp. И мне это нравится.

Один Go-бинарь — правильная форма

Без:

  • Redis,

  • Postgres,

  • service mesh,

  • Helm,

  • runtime dependency zoo.

scp + systemd/launchd — и всё работает.

Локальные LLM уже достаточно хороши

Qwen 0.5B на Raspberry Pi хватает для routing/classification. Мне не нужен GPT-4, чтобы понять:

“что там сломалось?”

Модель здесь не “думает”. Она помогает выбрать intent. И маленькие модели surprisingly хороши в этом.

Почему мне кажется, что это направление важно

Сейчас вся AI-индустрия движется в сторону:

  • огромных cloud-agent systems

  • autonomous workflows

  • giant tool ecosystems

  • generalized assistants

Но чем дольше я работаю над openLight, тем сильнее мне кажется:

  • самая полезная AI-инфраструктура ближайших лет будет не гигантской, а маленькой.

  • Не cloud-first. А local-first.

  • Не “autonomous”. А deterministic by default.

  • Не opaque. А observable.

  • Не “умной”. А repairable.

openLight — это очень маленький проект. Но для меня он стал способом исследовать именно эту идею. Он не пытается заменить инженера. Он пытается уменьшить трение между инженером и теми маленькими системами, которыми этот инженер уже управляет.

Код лежит на github.com/evgenii-engineer/openLight.

Если у тебя дома есть:

  • Raspberry Pi

  • Mac mini

  • VPS

  • homelab

  • или просто несколько always-on машин

возможно, openLight тебе пригодится.

ссылка на оригинал статьи https://habr.com/ru/articles/1033220/