Чему меня научили два месяца с легковесным локальным AI-агентом. homelab.. homelab. llm.. homelab. llm. local ai.. homelab. llm. local ai. Open source.. homelab. llm. local ai. Open source. raspberry pi.. homelab. llm. local ai. Open source. raspberry pi. искусственный интеллект.. homelab. llm. local ai. Open source. raspberry pi. искусственный интеллект. Системное администрирование.

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 тебе пригодится.

Автор: IsupovEvgenii

Источник