ctrllife.ru/ctrllife

Настройка OpenCode как Claude Code: LSP, Serena, модели, Docker

nastrojka_opencode_kak_claude_code
Содержание15 разделов

OpenCode — открытый агент для работы с кодом в терминале и прямая альтернатива Claude Code. Он подключается к десяткам провайдеров моделей, редактирует файлы, выполняет команды в оболочке и разбирается в структуре проекта.

При переходе с Claude Code возникает типичное ощущение: агент делает то же самое, но работает заметно «глупее». Причина не в самом OpenCode, а в том, что половина слоёв, формирующих «ум» агента, по умолчанию выключена.

«Ум» Claude Code — это сумма из пяти слоёв. OpenCode умеет всё то же самое; ниже — разбор каждого слоя по убыванию вклада и готовые конфигурации, которые закрывают разрыв.

Из чего складывается «ум» агента

1. Модель

Самый большой вклад в поведение агента даёт сама модель. Claude Code построен вокруг конкретного семейства моделей и настроен под него. На слабой или локальной модели OpenCode не будет вести себя как топовый агент при любой конфигурации — это отправная точка.

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

2. LSP — понимание кода

Это главное, чего не хватает «голому» OpenCode. Language Server Protocol даёт агенту то же, что среда разработки даёт человеку: типы, переходы к определениям, поиск ссылок, диагностику компилятора. Без LSP агент читает код как простой текст и правит его вслепую. С LSP — видит структуру и ошибки заранее.

В OpenCode LSP по умолчанию выключен. Его нужно включить явно и указать серверы под используемые языки. Для типового набора Python + TypeScript это pyright и typescript-language-server.

3. Семантический поиск по коду

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

Решение — подключить внешний MCP-сервер для работы с кодом. Наиболее заметный эффект даёт Serena: она предоставляет LSP-точные операции над символами (найти определение, все использования, переименовать, отредактировать конкретный символ). Фактически она строит для модели «карту» кода, которую та сама построить не может. Это самый ощутимый скачок «интеллекта» на больших кодовых базах.

4. AGENTS.md — правила проекта

Прямой аналог файла инструкций из Claude Code. При старте OpenCode ищет файлы правил, поднимаясь вверх по дереву каталогов: AGENTS.md и CLAUDE.md. Если есть оба, берётся только AGENTS.md. Содержимое подмешивается в контекст модели и настраивает её поведение под конкретный проект.

Создать файл можно командой /init — она сканирует репозиторий, при необходимости задаёт уточняющие вопросы и генерирует заготовку с командами сборки, тестов и линтинга. Файл стоит закоммитить, чтобы правила были общими для команды.

Важный нюанс: чем слабее модель, тем подробнее и жёстче должны быть правила. Флагманская модель домысливает недостающий контекст сама, слабая — нет. Явно прописанные пути, команды и запреты сокращают разрыв между моделями сильнее всего.

5. Агенты, подагенты и режим плана

Claude Code имеет встроенную дисциплину: сначала планирование, потом исполнение, плюс делегирование подзадач. В OpenCode это настраивается.

Есть два основных агента — Build (полный доступ к инструментам) и Plan (ограниченный, преимущественно чтение), между которыми переключаются клавишей Tab. Есть встроенные подагенты — General, Explore, Scout, — которые основной агент вызывает под конкретные задачи.

Рабочий приём, особенно на не-флагманской модели: не давать агенту сразу писать код. Сначала Plan строит план, план проверяется глазами, и только потом идёт переключение в Build. Это отсекает бо́льшую часть глупых ошибок до того, как они попадут в файлы.

Сравнение моделей

Главное преимущество OpenCode перед закрытым агентом — свобода выбора модели. Но выбор не сводится к «взять верхнюю строчку рейтинга». В 2026 году ни одна модель не выигрывает по всем осям сразу, и правильный подход — маршрутизация: сильная модель на сложные задачи, дешёвая на рутину.

Ниже — ориентир по актуальному раскладу (данные на июль 2026, метрика SWE-bench Verified, цены за 1 млн токенов ввода/вывода). Цифры устаревают быстро — сверяйтесь с актуальными таблицами перед выбором.

Модель

SWE-bench Verified

Цена (ввод/вывод)

Открытые веса

Ниша

Claude Opus 4.8

~88.6%

$5 / $25

нет

Длинные автономные прогоны, сложный многофайловый рефакторинг

GPT-5.5

~88.7%

$5 / $30

нет

Терминальные агенты, командная строка

GPT-5.3-Codex

~высокая

ниже GPT-5.5

нет

Codex-подобные потоки, дешевле за задачу

Gemini 3.1 Pro

~80.6%

$2 / $12

нет

Архитектура, большой контекст, экономия

DeepSeek V4 Pro

~75.9–80%

$1.74 / $3.48

да (MIT)

Экономичные агент-циклы, самостоятельный хостинг

Claude Sonnet 4.6

~79.6%

$3 / $15

нет

Повседневная разработка, контекст 1 млн

DeepSeek V4 Flash

ниже

$0.14 / $0.28

да

Дешёвая рутина, «последние 10%» задач

Qwen 3 Coder

ниже

~$0.30 / $1.50

да (Apache 2.0)

Стандарт для локального хостинга

Devstral 2

ниже

локально

да

Агентный код без утечки данных наружу

Практически модели делятся на три яруса. Флагманы (Opus 4.8, GPT-5.5) — для сложных 20% задач, где качество важнее цены. Оптимальные (Sonnet 4.6, Gemini 3.1 Pro, DeepSeek V4 Pro) — для ежедневной работы: почти всё качество за долю стоимости. Дешёвые и быстрые (DeepSeek V4 Flash, Qwen 3 Coder) — для рутины и автодополнения.

Почему обвязка важнее модели

Это ключевой вывод, переворачивающий логику выбора. Независимые замеры 2026 года показали: при прогоне через разную обвязку одна и та же модель набирает от ~52% до ~69% на одном бенчмарке. Разрыв в 17 пунктов создаётся только инструментами и сценарием, а не сменой модели.

Маршрутизация моделей в OpenCode

OpenCode позволяет назначать разные модели разным агентам. Типовая схема: сильная модель на основного агента build, дешёвая — на служебные задачи (small_model: заголовки сессий, сжатие контекста) и на агента-планировщика.

Три яруса на базе DeepSeek

DeepSeek удобен для маршрутизации: весь набор укладывается в две модели плюс переключатель усилия рассуждений (reasoning effort), а цены на порядок ниже флагманов. Раскладка по трём ярусам:

  • Сильная — deepseek-v4-pro с высоким усилием рассуждений. Модель на 1.6 трлн параметров (49 млрд активных), контекст 1 млн токенов, ~$1.74 / $3.48 за 1 млн. Ставится на основного агента build: сложный многофайловый рефакторинг, анализ всей кодовой базы, длинные автономные прогоны. Её поведение при вызове инструментов ближе к Claude Code, чем у прежней линейки V3.
  • Средняя — deepseek-v4-flash с включённым рассуждением. Та же архитектура, но 284 млрд параметров (13 млрд активных), заметно дешевле при приличном качестве рассуждений. Ставится на агента-планировщика plan: построить план, разобраться в задаче — здесь не нужна вся мощь Pro.
  • Дешёвая — deepseek-v4-flash в быстром режиме, ~$0.14 / $0.28 за 1 млн. Идёт на служебные задачи (small_model) и рутину: заголовки сессий, сжатие контекста, простые правки.

Конфигурация с прямым API DeepSeek:

{
  "model": "deepseek/deepseek-v4-pro",
  "small_model": "deepseek/deepseek-v4-flash",
  "agent": {
    "plan": {
      "model": "deepseek/deepseek-v4-flash"
    }
  }
}

При работе через OpenRouter добавляется префикс провайдера:

{
  "model": "openrouter/deepseek/deepseek-v4-pro",
  "small_model": "openrouter/deepseek/deepseek-v4-flash",
  "agent": {
    "plan": {
      "model": "openrouter/deepseek/deepseek-v4-flash"
    }
  }
}

Так более сильная модель оплачивается только там, где она реально нужна — на сложных правках кода, — а планирование и служебные операции идут на дешёвом Flash.

Отдельный пункт, о котором забывают: встроенные подагенты (General, Explore, Scout) наследуют модель основного агента build. Разведка по файлам — самая частая и самая простая операция, держать на ней Pro с рассуждениями расточительно. Их тоже стоит перевести на Flash:

{
  "agent": {
    "plan": {
      "model": "deepseek/deepseek-v4-flash"
    },
    "general": {
      "model": "deepseek/deepseek-v4-flash"
    },
    "explore": {
      "model": "deepseek/deepseek-v4-flash"
    },
    "scout": {
      "model": "deepseek/deepseek-v4-flash"
    }
  }
}

Имена моделей и цены DeepSeek периодически меняются (старые deepseek-chat и deepseek-reasoner уже выведены из обращения) — сверяйтесь с актуальной документацией провайдера.

Установка всех компонентов

Прежде чем конфигурация заработает, в системе должны быть установлены сам OpenCode и все внешние программы, на которые он ссылается: языковые серверы, форматтеры и менеджер пакетов для запуска Serena. Ниже — полный набор команд для Linux (Debian/Ubuntu) и macOS.

1. OpenCode

Официальный установочный скрипт (Linux и macOS):

curl -fsSL https://opencode.ai/install | bash

Альтернативно — через npm (если Node.js уже стоит) или Homebrew на macOS:

npm i -g opencode-ai
# либо
brew install sst/tap/opencode

Проверка:

opencode --version

2. Node.js — основа для языковых серверов

Пакеты pyright, typescript-language-server и prettier ставятся через npm, поэтому Node.js нужен обязательно. Проверка наличия:

node --version

Если команды нет — установить. На Debian/Ubuntu актуальную версию удобнее ставить через официальный репозиторий NodeSource:

curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt-get install -y nodejs

На macOS через Homebrew:

brew install node

3. Языковые серверы и форматтер (npm)

Одной командой устанавливаются сервер Python, сервер TypeScript (вместе с самим пакетом typescript, который ему нужен для работы) и форматтер prettier:

npm i -g pyright typescript typescript-language-server prettier

После этого становятся доступны исполняемые файлы pyright-langserver, typescript-language-server и prettier.

4. Python-инструменты: uv и ruff

uv — быстрый менеджер пакетов Python; через его команду uvx запускается Serena. Он же ставит форматтер ruff.

Установка uv (Linux и macOS):

curl -LsSf https://astral.sh/uv/install.sh | sh

Установщик дописывает PATH в ~/.bashrc, но в уже открытой сессии это не действует. Подхватить uv в текущей сессии без перезапуска терминала:

source $HOME/.local/bin/env
uv --version

Затем устанавливается ruff как отдельный инструмент:

uv tool install ruff

Serena отдельно ставить не нужно — она подтягивается автоматически при первом запуске командой uvx из конфигурации. Требуется лишь установленный uv и наличие в системе Python 3.11 или новее.

5. Проверка PATH

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

which opencode
which pyright-langserver
which typescript-language-server
which prettier
which ruff
which uvx

Если команда ничего не вернула — каталог с программой не входит в PATH. Чаще всего это касается глобальных пакетов npm и инструментов, установленных через uv. Узнать каталог глобальных пакетов npm:

npm config get prefix

Допустим, вернулось /home/user/.npm-global. Тогда в PATH добавляется его каталог bin, а также каталог инструментов uv. Строка вписывается в конец ~/.bashrc (или ~/.zshrc при использовании zsh):

echo 'export PATH="$HOME/.npm-global/bin:$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc

Когда все команды из проверки возвращают пути — система готова, и OpenCode подхватит инструменты из конфигурации автоматически. Запомните вывод which uvx (обычно /root/.local/bin/uvx или $HOME/.local/bin/uvx) — этот абсолютный путь понадобится в конфигурации и в разделе про запуск в фоне.

Готовый глобальный конфиг

Файл ~/.config/opencode/opencode.json. Провайдер и модель здесь не зашиты — они подставляются через opencode auth или переменную окружения, поэтому конфигурация подходит под любую модель.

{
  "$schema": "https://opencode.ai/config.json",
  "server": {
    "port": 4096,
    "hostname": "127.0.0.1"
  },
  "default_agent": "build",
  "autoupdate": "notify",
  "lsp": {
    "python": {
      "command": ["pyright-langserver", "--stdio"],
      "extensions": [".py", ".pyi"]
    },
    "typescript": {
      "command": ["typescript-language-server", "--stdio"],
      "extensions": [".ts", ".tsx", ".js", ".jsx"]
    }
  },
  "formatter": {
    "ruff": {
      "command": ["ruff", "format", "$FILE"],
      "extensions": [".py"]
    },
    "prettier": {
      "command": ["prettier", "--write", "$FILE"],
      "extensions": [".ts", ".tsx", ".js", ".jsx", ".json", ".css"]
    }
  },
  "mcp": {
    "context7": {
      "type": "remote",
      "url": "https://mcp.context7.com/mcp",
      "enabled": true
    },
    "serena": {
      "type": "local",
      "command": [
        "ПУТЬ_ИЗ_WHICH_UVX", "--from", "git+https://github.com/oraios/serena",
        "serena", "start-mcp-server",
        "--context", "claude-code",
        "--project", "ПУТЬ_К_ВАШЕМУ_РЕПОЗИТОРИЮ",
        "--transport", "stdio"
      ],
      "enabled": true
    }
  },
  "plugin": [
    "@tarquinen/opencode-dcp@latest"
  ],
  "instructions": ["AGENTS.md", ".cursor/rules/*.md"],
  "permission": {
    "edit": "allow",
    "bash": {
      "rm *": "ask",
      "rm -rf *": "deny",
      "git push *": "ask",
      "git reset --hard *": "ask",
      "git clean *": "ask",
      "docker *": "ask",
      "curl * | *": "deny",
      "wget * | *": "deny",
      "chmod 777 *": "ask",
      "sudo *": "ask",
      "systemctl *": "ask",
      "cat *.env*": "deny",
      "cat */.env*": "deny",
      "env": "deny",
      "printenv*": "deny",
      "*": "allow"
    }
  },
  "compaction": {
    "auto": true
  }
}

Пояснения к конфигурации:

  • server.hostname = 127.0.0.1 означает, что OpenCode Server слушает только локальную петлю. Если сервер стоит на VPS за обратным прокси (nginx, Nginx Proxy Manager) — это правильный выбор: наружу порт не торчит, весь внешний трафик идёт через прокси с TLS. Значение 0.0.0.0 открывает порт на всех интерфейсах и нужно только внутри Docker-контейнера (см. ниже).
  • lsp включает языковые серверы. pyright-langserver и typescript-language-server должны быть доступны в PATH запускающего процесса.
  • formatter прогоняет ruff и prettier после правок, чтобы код оставался в едином стиле.
  • mcp.context7 — публичный удалённый сервер актуальной документации по библиотекам. Это просто URL, OpenCode ему ничего не запускает.
  • mcp.serena — локальный сервер семантического поиска и точных правок символов. Три момента здесь критичны, и все три вынесены в раздел про типичные проблемы: абсолютный путь к uvx, актуальное имя контекста claude-code (старое ide-assistant устарело) и явный --project с путём к вашему репозиторию.
  • plugin — список пакетов npm с плагинами. Подробно про механику установки и про то, чем имя пакета отличается от имени репозитория, — в отдельном разделе ниже.
  • permission.bash переводит потенциально опасные команды в режим подтверждения, а часть запрещает совсем. Обратите внимание на маски curl * | * и wget * | *: без них строка вида curl … | bash проходит под общим разрешением и выполняет произвольный код из сети. Маски с .env и printenv закрывают попытки прочитать секреты через оболочку — но не через чтение файлов, об этом отдельный раздел ниже.
  • compaction.auto включает автоматическое сжатие контекста при приближении к пределу окна модели, чтобы длинные сессии не обрывались.

Serena при нескольких проектах

В конфиге выше путь к репозиторию зашит в глобальный файл. Пока проект один, это работает. Как только на сервере появляется второй репозиторий, Serena продолжает индексировать первый, а модель получает карту чужого кода — при этом никакой ошибки не показывается, поиск просто отвечает не то.

Правильная раскладка: в глобальном ~/.config/opencode/opencode.json остаются только языковые серверы, форматтеры, плагины и права, а Serena переезжает в проектный opencode.json в корне репозитория:

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "serena": {
      "type": "local",
      "command": [
        "/root/.local/bin/uvx", "--from", "git+https://github.com/oraios/serena",
        "serena", "start-mcp-server",
        "--context", "claude-code",
        "--project", "/srv/projects/myrepo",
        "--transport", "stdio"
      ],
      "enabled": true
    }
  }
}

Абсолютный путь к uvx здесь по-прежнему обязателен: проектный конфиг не меняет окружение серверного процесса — Serena запускает всё тот же юнит systemd со своим PATH. Чтобы не гадать, обе переменные лучше задать в юните явно:

[Unit]
Description=OpenCode Server
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=opencode
WorkingDirectory=/srv/projects
Environment="PATH=/home/opencode/.local/bin:/home/opencode/.npm-global/bin:/usr/local/bin:/usr/bin:/bin"
Environment="HOME=/home/opencode"
ExecStart=/usr/local/bin/opencode serve --hostname 127.0.0.1 --port 4096
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

Типичные проблемы: Serena не подключается

Самая частая проблема при первой настройке: в панели MCP сервер serena виден, но переключатель не встаёт (откатывается назад), вверху висит «1 MCP» вместо «2», либо вкладка вовсе показывает «MCP не настроены».

Ключ к пониманию — разница между двумя типами серверов. context7 удалённый: это просто URL, OpenCode ничего не запускает, поэтому его переключатель встаёт всегда. serena локальная: OpenCode должен сам запустить процесс командой uvx. Если этот запуск падает, переключатель откатывается. Значит проблема почти никогда не в самой Serena, а в окружении процесса, который её запускает.

Диагностика по шагам

Сначала проверьте, что конфиг валиден — битый JSON OpenCode игнорирует молча, без ошибок в интерфейсе:

python3 -m json.tool ~/.config/opencode/opencode.json > /dev/null && echo OK || echo BROKEN

Затем запустите команду Serena вручную — ровно ту, что стоит в конфиге:

/root/.local/bin/uvx --from git+https://github.com/oraios/serena \
  serena start-mcp-server --context claude-code \
  --project /srv/projects/myrepo --transport stdio

Если в выводе появляется MCP server lifetime setup complete и список из ~23 инструментов (find_symbol, find_referencing_symbols и т.д.) — сама Serena полностью рабочая, выйдите по Ctrl+C. Значит дело исключительно в том, как её запускает сервер. Если же вылезает command not found — uvx не виден процессу.

Три причины и их устранение

Причина №2 — устаревшее имя контекста. В свежих версиях Serena контекст ide-assistant переименован в claude-code. Пока старое имя даёт лишь предупреждение в логе, но в новых сборках может стать ошибкой запуска. Используйте --context claude-code.

Причина №3 — зависание на активации проекта. При первом запуске Serena проводит onboarding и активирует проект. Без явного пути она иногда зависает на этом шаге, и OpenCode считает запуск неудачным. Добавьте --project с абсолютным путём к репозиторию.

Обязательный перезапуск сервера

MCP-серверы, плагины и PATH подхватываются только при старте, поэтому после любой правки конфига нужно перезапустить именно сам серверный процесс (не сессию в приложении). Как — зависит от способа запуска.

Если сервер работает через systemd:

systemctl restart opencode
systemctl status opencode --no-pager
journalctl -u opencode -n 40 --no-pager | grep -iE "serena|mcp|plugin|error"

Если запущен вручную в tmux или screen — убить процесс и запустить заново из оболочки, где uvx виден в PATH:

pkill -f "opencode serve"
opencode serve --hostname 127.0.0.1 --port 4096

После перезапуска закройте и снова откройте проект в приложении OpenCode, чтобы оно перечитало список MCP с сервера. Переключатель serena должен встать и остаться — вверху появится «2 MCP». При первом запуске Serena несколько секунд тянется через uvx и индексирует проект, так что появляется не мгновенно.

Не запускайте демоны из сессии агента

Отдельная ловушка, которая проявляется не сразу. Если попросить агента поднять Docker, обратный прокси или туннель, он выполнит команду в оболочке — и процесс окажется в дереве процессов службы opencode. Внешне всё работает, но systemd считает эти демоны частью своей службы.

Следующий же systemctl restart opencode убьёт весь набор: контейнеры остановятся, прокси отвалится, сайт отдаст 502. Диагностика при этом уводит в сторону — сервер-то запустился нормально.

systemctl status opencode --no-pager

В блоке CGroup должны быть только сам opencode и процессы Serena. Всё остальное — dockerd, containerd, nginx, cloudflared — выносится в собственные службы и запускается через systemctl, а не из чата с агентом.

Плагины: как они устроены

Плагин — это модуль на JavaScript или TypeScript, который подписывается на события OpenCode и меняет его поведение. Загружаются они из двух источников, и оба работают одновременно.

Пакеты npm перечисляются в поле plugin конфига. Ставить их вручную не нужно: при старте OpenCode сам подтягивает пакеты через Bun и складывает в кеш ~/.cache/opencode/node_modules/. Поддерживаются и обычные имена, и с областью видимости:

{
  "$schema": "https://opencode.ai/config.json",
  "plugin": [
    "@tarquinen/opencode-dcp@latest",
    "opencode-snip@latest"
  ]
}

Локальные файлы кладутся в каталог ~/.config/opencode/plugin/ (или .opencode/plugin/ в корне проекта). Любой файл оттуда подхватывается при запуске автоматически, никаких записей в конфиге не требуется. Это удобный способ написать что-то своё на полсотни строк, не публикуя пакет.

Вместо ручной правки конфига есть команда — она сама допишет нужную строку:

opencode plugin @tarquinen/opencode-dcp@latest --global

Проверка: плагин загрузился или нет

В панели справа есть вкладка «Плагины». Если она показывает только подсказку «Плагины настроены в opencode.json» — значит не загружен ни один. Это пустое состояние, а не список. Загруженный плагин отображается там по имени.

Быстрая проверка из оболочки:

grep -n '"plugin"' ~/.config/opencode/opencode.json
ls -l ~/.config/opencode/plugin/ 2>/dev/null
journalctl -u opencode -n 40 --no-pager | grep -iE "plugin|error"

Частая причина «прописал, но не работает» — забытый перезапуск серверного процесса: плагины читаются только при старте.

Экономия контекста: шестой слой

Параметр compaction.auto из конфига выше — это реакция на переполнение окна: контекст сжимается, когда предел уже близко, и всегда с потерей деталей. Профилактика дешевле, и работает она на четырёх независимых уровнях.

1. Не пускать мусор в индекс

Самый дешёвый выигрыш и единственный полностью бесплатный. Каталоги зависимостей и сборки (node_modules, dist, .venv, дампы) должны быть исключены из работы агента — иначе на первом же поиске в контекст утягиваются сотни килобайт минифицированного кода. Базово это закрывается правилами в AGENTS.md; для более жёсткой фильтрации по маскам существуют плагины вроде opencode-ignore.

2. Читать символами, а не файлами

Serena — это не только «умный поиск», это в первую очередь экономия: find_symbol возвращает одну функцию вместо файла на две тысячи строк. Но по умолчанию модель к этому не тянется — прочитать файл целиком ей проще. Приём нужно закрепить правилом, иначе Serena стоит подключённой и простаивает.

3. Резать вывод команд

Плагин opencode-snip подставляет ко всем командам оболочки обёртку, которая фильтрует вывод до попадания в контекст — заявлено сокращение на 60–90%.

Половину того же эффекта даёт бесплатное правило: гонять длинные команды с | tail -80, --quiet, -q. С него и стоит начать.

4. Сжимать историю на ходу

Плагин DCP (пакет @tarquinen/opencode-dcp) работает двумя способами. Дедупликация находит повторные вызовы одного инструмента — тот же файл прочитан пять раз, та же сборка прогнана трижды — и оставляет только свежий вывод; выполняется на каждом запросе и ничего не стоит. Второй механизм — инструмент сжатия, который модель вызывает сама, когда задача закрыта: отработанные куски переписки заменяются техническими сводками. История сессии при этом не меняется, подстановка происходит только в отправляемом запросе.

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

Пятым пунктом идёт измерение: без него непонятно, окупились ли предыдущие четыре шага. Панель контекста в интерфейсе показывает разбивку по долям — сколько занимают ваши сообщения, ответы модели, вызовы инструментов и «другое» (системная часть, правила из AGENTS.md, описания инструментов от серверов MCP). Если «другое» переваливает за половину окна, разбирать нужно именно его, а не резать вывод команд.

Блок в AGENTS.md, который закрывает первые два пункта без единого плагина:

## Работа с контекстом
- Для навигации по коду использовать Serena: `find_symbol`,
  `find_referencing_symbols`, `get_symbols_overview`.
- Не читать файл целиком, если нужна одна функция или класс —
  запрашивать конкретный символ.
- Правки вносить через `replace_symbol_body` и `insert_after_symbol`,
  а не переписыванием файла.
- Перед поиском по тексту пробовать поиск по символам; `grep` — только
  для строк, комментариев и конфигов.
- Не заходить в `node_modules`, `dist`, `.venv`, `build`.
- Не выводить содержимое `package-lock.json`, `uv.lock` и дампов.
- Длинные команды запускать с ограничением вывода: `| tail -80`,
  `--quiet`, `-q`.
- Диагностику брать из LSP, а не из полного прогона сборки.

Отдельно про тесты

Самая частая причина, по которой агент выглядит «тупящим», — не модель, а ожидание. Полный прогон набора тестов в контейнере занимает полторы-две минуты, и агент повторяет его снова и снова, если из вывода непонятно, что упало.

Классический промах — фильтровать вывод по слову FAIL. Ошибки на этапе сбора тестов (сломанный импорт, синтаксис, падение в beforeAll) печатаются отдельным блоком и этого слова не содержат. Агент получает бесполезные пятьдесят строк и запускает прогон заново. Правила, которые обрывают цикл:

## Тесты
- Не запускать полный набор тестов для диагностики одной ошибки.
- Первый прогон — с `--bail=1` и подробным отчётом, вывод брать `| tail -80`.
- Фильтр `grep 'FAIL'` не использовать: ошибки сбора и хуков в него не попадают.
- После определения упавшего файла запускать только его, по пути.
- Для повторных прогонов использовать `docker compose exec` в уже запущенном
  контейнере, а не `docker run --rm`.

Что ещё стоит доставить

Экосистема дополнений собрана в репозитории awesome-opencode. Ставить всё подряд смысла нет, но несколько направлений закрывают заметные дыры базовой настройки. Точные строки для поля plugin смотрите в README каждого проекта — они, как уже говорилось, расходятся с именами репозиториев.

Защита секретов

Маски permission.bash из конфига выше контролируют только команды оболочки. Но чтение файлов идёт не через оболочку: агент читает .env штатным инструментом чтения, и никакая маска cat *.env* его не останавливает. При работе через стороннего провайдера моделей это означает, что ключи от базы и платёжных систем уезжают в контекст наружу.

Закрывается плагином envsitter-guard: он запрещает читать и править файлы .env*, оставляя агенту имена ключей и их отпечатки — так тот понимает, какие переменные существуют, но не видит значений. Вторым рубежом ставится claude-code-safety-net — перехват разрушительных команд git и файловой системы до выполнения.

Ревью плана глазами

Приём «сначала Plan, потом Tab в Build» упирается в то, что план проверяется чтением простыни текста в терминале, а правки диктуются словами. Плагины plannotator и open-plan-annotator перехватывают режим планирования и открывают правку плана в браузере: выделяешь кусок, зачёркиваешь, заменяешь, комментируешь, потом утверждаешь. Работает локально. На не-флагманской модели это даёт больше всего — ошибка ловится до того, как разошлась по файлам.

Память между сессиями

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

Параллельные задачи

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

Как установить OpenCode в Docker

Если OpenCode Server работает в контейнере, действует важное ограничение среды.

Минимальный Dockerfile, собирающий полный набор:

FROM node:22-slim

# Системные зависимости и Python
RUN apt-get update && apt-get install -y --no-install-recommends \
    curl ca-certificates git python3 python3-venv \
    && rm -rf /var/lib/apt/lists/*

# Языковые серверы и форматтер
RUN npm i -g opencode-ai pyright typescript typescript-language-server prettier

# uv и ruff
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
ENV PATH="/root/.local/bin:${PATH}"
RUN uv tool install ruff

WORKDIR /workspace
EXPOSE 4096
CMD ["opencode", "serve", "--hostname", "0.0.0.0", "--port", "4096"]

Внутри контейнера --hostname 0.0.0.0 обязателен — иначе сервер будет недоступен через проброс порта. Но наружу порт 4096 публиковать не нужно: пробрасывайте его только во внутреннюю сеть Docker, а внешний доступ давайте через обратный прокси с TLS. При таком образе Serena так же подтянется через uvx при первом запуске, а все проверки which внутри контейнера пройдут успешно.

Управление с телефона: OpenCode Telegram Bot

Конфигурация из статьи — это opencode serve под systemd на VPS. Длинный автономный прогон идёт без вас: либо сидеть и смотреть в терминал, либо возвращаться через час и выяснять, что агент двадцать минут назад упёрся в подтверждение git push и ждёт.

OpenCode Telegram Bot закрывает этот разрыв: полноценный клиент OpenCode в Телеграме. Задачи ставятся с телефона, ответы приходят туда же, код — файлами. Бот работает на вашей машине и связывается только с локальным сервером OpenCode и Bot API — наружу ничего не открывается. Проект открытый, лицензия MIT.

Что доступно из чата: постановка задач и продолжение сессий, переключение моделей и режимов Plan/Build, ответы на вопросы агента и подтверждение прав кнопками, живой статус с текущим проектом, моделью и заполненностью контекста, просмотр и скачивание файлов, переключение рабочих деревьев git, запуск пользовательских команд и навыков, голосовые сообщения с распознаванием и задачи по расписанию.

Что нужно заранее

  • Node.js 22.14+ — если ставили по разделу выше, уже есть.
  • Запущенный сервер OpenCode на той же машине, где будет работать бот. По умолчанию бот стучится на http://localhost:4096 — порт из нашего конфига совпадает.
  • Бот в Телеграме — команда /newbot у @BotFather, оттуда берётся токен.
  • Свой числовой идентификатор — любое сообщение боту @userinfobot, он ответит числом.

Установка

Попробовать без установки можно так:

npx @grinev/opencode-telegram-bot@latest

Если ничего не настроено заранее, поднимется мастер: спросит язык интерфейса, токен бота, ваш идентификатор, адрес API OpenCode и, при необходимости, логин с паролем от сервера.

npm install -g @grinev/opencode-telegram-bot
opencode-telegram start

Команда start по умолчанию держит процесс на переднем плане — это то, что нужно для systemd, Docker и pm2. Встроенный фоновый режим (start --daemon, status, stop) предназначен для установок без внешнего супервизора. Перенастроить в любой момент можно командой opencode-telegram config.

Файл настроек

Мастер складывает .env в каталог настроек: на Linux это ~/.config/opencode-telegram-bot/.env. Значения из окружения процесса имеют приоритет над файлом. Рабочий набор для VPS:

TELEGRAM_BOT_TOKEN=123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
TELEGRAM_ALLOWED_USER_ID=123456789
BOT_LOCALE=ru

OPENCODE_API_URL=http://127.0.0.1:4096
OPENCODE_SERVER_USERNAME=opencode
OPENCODE_SERVER_PASSWORD=your-server-password
OPENCODE_MODEL_PROVIDER=deepseek
OPENCODE_MODEL_ID=deepseek-v4-pro

OPENCODE_AUTO_RESTART_ENABLED=true
OPENCODE_MONITOR_INTERVAL_SEC=300
TRACK_BACKGROUND_SESSIONS=true
LOG_LEVEL=info

Разбор ключевых переменных:

  • TELEGRAM_ALLOWED_USER_ID — белый список из одного человека. Сообщения от всех остальных молча игнорируются и пишутся в журнал как попытка неавторизованного доступа. Это не теория: чужие обращения приходят в первые же минуты после запуска, имя бота находится перебором.
  • OPENCODE_API_URL — тот самый порт 4096 из конфига статьи. Сервер продолжает слушать только локальную петлю, наружу порт не выставляется.
  • OPENCODE_AUTO_RESTART_ENABLED — бот сам поднимает упавший локальный сервер OpenCode по неудачной проверке здоровья. На VPS с задачами по расписанию это обязательный пункт. Но если сервером уже управляет systemd с Restart=on-failure, второй супервизор только мешает — оставьте что-то одно.
  • TRACK_BACKGROUND_SESSIONS — короткие уведомления, когда отсоединённые сессии текущего проекта отвечают, задают вопрос или просят подтверждение. Это и есть решение проблемы «агент час ждал подтверждения».

Юнит systemd для бота

Юнит не наследует окружение вашей оболочки, поэтому пути в нём абсолютные. Сначала выясните фактические — глобальные пакеты npm попадают в разные каталоги в зависимости от того, как ставился Node.js:

command -v opencode-telegram
command -v node

Если первая команда ничего не вернула — пакет не установлен глобально, вернитесь на шаг назад.

Второй обязательный шаг — настроить бота от имени того пользователя, под которым будет работать служба. Мастер интерактивный, а под systemd интерактива нет: без готового .env сервис уйдёт в цикл перезапусков.

opencode-telegram config
ls -l ~/.config/opencode-telegram-bot/.env

Теперь сам юнит. Значения подставляются на месте, поэтому EOF здесь без кавычек:

BIN="$(command -v opencode-telegram)"
NODEDIR="$(dirname "$(command -v node)")"

tee /etc/systemd/system/opencode-telegram.service > /dev/null <<EOF
[Unit]
Description=OpenCode Telegram Bot
After=network-online.target opencode.service
Wants=network-online.target

[Service]
Type=simple
User=$USER
Environment="HOME=$HOME"
Environment="PATH=$NODEDIR:/usr/local/bin:/usr/bin:/bin:$HOME/.local/bin"
ExecStart=$BIN start
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target
EOF

cat /etc/systemd/system/opencode-telegram.service

Последняя команда показывает результат — убедитесь, что в ExecStart стоит реальный путь, а не пустота. Запуск:

systemctl daemon-reload
systemctl enable --now opencode-telegram
systemctl status opencode-telegram --no-pager
journalctl -u opencode-telegram -n 20 --no-pager

Признак успеха — строка Bot @ваш_бот started! в журнале.

Слежение за живой сессией

Бот умеет подключаться к сессии, запущенной в терминале, — видеть её события и продолжать её же из чата. Условие: консольный экземпляр OpenCode должен работать на том же порту, что и бот, а по умолчанию OpenCode занимает случайный порт. Два рабочих варианта:

  • Один терминал. Запускать с явным портом: opencode --port 4096, бот направлен на http://127.0.0.1:4096.
  • Несколько клиентов на общем сервере. Один opencode serve --port 4096, а каждый терминал подключается командой opencode attach http://127.0.0.1:4096.

Задачи по расписанию

Команда /task создаёт отложенный или повторяющийся запуск, /tasklist показывает и удаляет существующие. Задача берёт текущий выбранный проект и модель, выполняется всегда агентом build и идёт вне активной сессии, не мешая текущей работе. Минимальный интервал повтора — 5 минут; если предыдущий запуск ещё идёт, параллельная копия не стартует и пропущенные интервалы не отыгрываются задним числом. По умолчанию задач может быть 10 (TASK_LIMIT), а на один запуск отводится 120 минут (SCHEDULED_TASK_EXECUTION_TIMEOUT_MINUTES).

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

Если Телеграм недоступен из сети сервера

Есть два взаимоисключающих режима, и включать нужно только один — при обоих сразу бот откажется стартовать.

Прямой проход через промежуточный сервер (SOCKS5 или HTTP):

TELEGRAM_PROXY_URL=socks5://user:password@127.0.0.1:1080

Либо через свой обратный прокси с TLS, если api.telegram.org закрыт, а собственный домен доступен:

TELEGRAM_API_ROOT=https://tg-proxy.yourdomain.com
TELEGRAM_PROXY_SECRET=some-long-random-string

Секрет уходит заголовком X-Proxy-Secret при каждом обращении, и прокси может по нему отсеивать чужих. Отдельный случай — машина, где DNS отдаёт IPv6, а исходящий IPv6 не работает: тогда запуск падает на сетевых ошибках, и лечится это принудительным IPv4:

TELEGRAM_FORCE_IPV4=true

Основные команды бота

Команда

Что делает

/status

Состояние сервера, текущий проект, сессия и модель

/new

Новая сессия

/abort

Прервать текущую задачу

/sessions

Переключение между недавними сессиями

/messages

История запросов с откатом и ответвлением от нужной точки

/projects

Переключение между проектами

/worktree

Переключение между рабочими деревьями git

/ls

Просмотр каталогов и скачивание файлов проекта

/commands, /skills

Запуск пользовательских команд и навыков

/mcps

Включение и отключение серверов MCP

/task, /tasklist

Задачи по расписанию

/settings

Настройки бота во время работы

/opencode_start, /opencode_stop

Запуск и остановка локального сервера OpenCode

Обычный текст в чате уходит агенту как запрос, если в этот момент не идёт интерактивный сценарий вроде вопроса или подтверждения прав.

Когда сайт отдаёт 502

Отдельный сюжет для тех, кто выставляет веб-интерфейс OpenCode наружу через обратный прокси. Ошибка 502 означает, что прокси до сервера не достучался, но причин у этого несколько, и половина из них не в самом OpenCode.

Порядок проверки — от быстрого к долгому:

systemctl status opencode --no-pager
ss -tlnp | grep 4096
curl -i -m 10 -u ЛОГИН:ПАРОЛЬ http://127.0.0.1:4096/

Если сервер жив и отвечает локально, дело во входе. Дальше смотрите, что стоит на 80 и 443:

ss -tlnp | grep -E ':80 |:443 '
docker ps -a
ps aux | grep -E 'cloudflared|nginx|caddy|traefik' | grep -v grep

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

ufw status numbered

Ищите адрес прокси в списке дважды: один раз с пометкой REJECT IN … by Fail2Ban и один раз с разрешением на нужный порт. Правила применяются по порядку, первое совпадение выигрывает — запрет из начала списка перекрывает разрешение из конца. Разбан и внесение в исключения:

fail2ban-client set имя-джейла unbanip АДРЕС_ПРОКСИ

Затем в /etc/fail2ban/jail.local дописать адрес в существующую строку ignoreip — не добавлять второй блок [DEFAULT] в конец файла, иначе перезагрузка настроек упадёт с ошибкой о дубликате параметра:

grep -n "ignoreip" /etc/fail2ban/jail.local
# дописать адрес в конец найденной строки, затем:
fail2ban-client reload
fail2ban-client get имя-джейла ignoreip

Почему прокси вообще попадает под бан: если ему разрешён только один порт, любое его обращение на другой (проверка доступности, попытка на 80 или 443) пишется в журнал ufw как заблокированный пакет. Несколько таких записей — и правило срабатывает.

Заготовка AGENTS.md

Файл кладётся в корень проекта и дорабатывается под конкретный репозиторий. Пример для проекта на FastAPI + React — блоки «Работа с контекстом» и «Тесты» из раздела про экономию контекста добавляются сюда же:

# Правила проекта

## Стек
- Бэкенд: FastAPI + PostgreSQL, асинхронный (asyncpg / SQLAlchemy 2.0).
- Фронтенд: React + Vite + TypeScript + TailwindCSS.
- Всегда полный рабочий код, без заглушек «добавьте сюда».

## Команды
- Запуск бэкенда: `uvicorn app.main:app --reload`
- Тесты: `pytest -q`
- Линт и форматирование: `ruff check . && ruff format .`
- Сборка фронтенда: `npm run build`
- Перед завершением задачи прогонять `ruff` и `pytest`.

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

## Инфраструктура
- Не запускать системные демоны (dockerd, nginx, прокси) из сессии:
  они попадут в дерево процессов агента и умрут при его перезапуске.
- Порты служб в docker-compose публиковать только на 127.0.0.1.

Чем конкретнее этот файл, тем меньше разрыв в качестве между сильной и слабой моделью.

Порядок внедрения

Включать всё сразу не стоит — эффект лучше проверять послойно:

  1. Модель. Подключение лучшей доступной модели даёт основную часть разницы.
  2. LSP. Агент перестаёт слепо редактировать и начинает видеть типы и ошибки.
  3. Serena. Скачок качества на больших репозиториях, где раньше терялся контекст.
  4. AGENTS.md и агенты. Тонкая настройка правил, прав и режима планирования.
  5. Права и защита секретов. Это не улучшение, а закрытие дыры: делать сразу, как только агент получил доступ к боевому репозиторию.
  6. Экономия контекста. Сначала бесплатные правила в AGENTS.md, затем плагины — по мере роста счёта.
  7. Телеграм. Нужен, когда прогоны стали длинными и за терминалом больше не сидят.

Если после первых двух-трёх шагов ощущение «как в топовом агенте» уже появилось — остальное подключается по мере того, как начнёт мешать его отсутствие.

Заключение

По «ощущению из коробки» настроенный таким образом OpenCode дотягивается до эталонного агента примерно на 85–90%. Оставшийся разрыв — это годами вылизанный системный промпт и внутренняя обвязка (harness) закрытого продукта, которые в OpenCode воспроизводятся через AGENTS.md, подагентов и режим плана, но не байт-в-байт.

Для практической ежедневной работы эта разница почти незаметна, а бесплатные слои — LSP, семантический поиск через Serena и правила в AGENTS.md — дают больший прирост качества, чем переход на модель классом выше. Взамен закрытого продукта пользователь получает свободу выбора модели и провайдера, отсутствие привязки к вендору и полный контроль над инструментами на собственной инфраструктуре. А вместе с экосистемой плагинов и клиентом в Телеграме — ещё и сценарии, которых у закрытого агента нет вовсе: свои правила экономии контекста, задачи по расписанию и управление с телефона при полностью локальном исполнении.

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

Чем OpenCode отличается от Claude Code?

OpenCode — открытый агент с поддержкой десятков провайдеров моделей, который можно запускать на собственной инфраструктуре. Claude Code — закрытый продукт, настроенный под одно семейство моделей. По базовой функциональности (правки файлов, оболочка, понимание проекта) они сопоставимы, но OpenCode требует ручной настройки слоёв, которые у Claude Code включены по умолчанию.

Нужен ли LSP для OpenCode?

Да, если требуется, чтобы агент понимал типы, ссылки и ошибки, а не правил код вслепую. По умолчанию LSP в OpenCode выключен — его нужно включить в opencode.json и указать языковые серверы под используемые языки, например pyright для Python и typescript-language-server для TypeScript.

Какая модель лучше для OpenCode?

Зависит от задачи и бюджета. Для максимального качества — флагманы уровня Claude Opus или GPT-5.5. Для баланса цены и качества — Gemini 3.1 Pro, Claude Sonnet или DeepSeek V4 Pro. Для рутины — DeepSeek V4 Flash. Оптимальная схема — маршрутизация: сильная модель на сложные правки, дешёвая на планирование и служебные задачи.

Что важнее — модель или её настройка?

Настройка. Замеры показывают, что одна и та же модель через разную обвязку набирает от ~52% до ~69% на одном бенчмарке. Разрыв в 17 пунктов создаётся инструментами и сценарием, а не сменой модели. Сначала стоит выжать максимум из LSP, семантического поиска и правил проекта, и только потом доплачивать за более сильную модель.

Как установить плагин в OpenCode?

Пакеты npm перечисляются в поле plugin файла opencode.json — OpenCode ставит их сам при запуске, вручную ничего скачивать не нужно. Либо командой opencode plugin имя-пакета@latest --global, которая допишет конфиг за вас. Локальные плагины кладутся файлами в каталог ~/.config/opencode/plugin/ и подхватываются без записей в конфиге. Важно: имя пакета на npm обычно не совпадает с именем репозитория на GitHub — берите точную строку из README.

Почему вкладка «Плагины» пустая?

Если там видна только надпись о том, что плагины настраиваются в opencode.json, — не загружен ни один. Это пустое состояние, а не список. Проверьте, что поле plugin в конфиге действительно есть (grep -n '"plugin"' ~/.config/opencode/opencode.json), что JSON валиден, и что серверный процесс перезапущен: плагины читаются только при старте.

Почему Serena не подключается в OpenCode?

Чаще всего потому, что процесс OpenCode Server (под systemd или в Docker) не видит uvx в своём PATH, хотя из обычной оболочки команда работает. Решение — указать в конфиге абсолютный путь к uvx из which uvx, задать актуальное имя контекста claude-code вместо устаревшего ide-assistant, добавить --project и перезапустить сервер. Отдельно проверьте, что в конфиге не остался пример пути из инструкции: с чужим путём Serena работает без ошибок, но индексирует не тот каталог.

Что такое AGENTS.md в OpenCode?

Это файл с правилами проекта, который подмешивается в контекст модели и настраивает её поведение под конкретный репозиторий — прямой аналог инструкций из Claude Code. Создаётся командой /init или вручную; его стоит закоммитить в репозиторий.

Можно ли запустить OpenCode в Docker?

Да. Важно, что PATH внутри контейнера отдельный от хостового, поэтому все инструменты — языковые серверы, форматтеры, uv — должны быть установлены внутри образа. В статье приведён готовый Dockerfile с полным набором.

Как сократить расход токенов в OpenCode?

Четыре шага по убыванию отдачи: исключить из работы каталоги сборки и зависимостей, заставить агента читать код символами через Serena, а не файлами целиком, обрезать вывод команд оболочки и сжимать отработанные куски переписки. Первые два делаются бесплатно правилами в AGENTS.md. Автоматическое сжатие контекста — последний рубеж, а не первый: оно срабатывает у предела окна и всегда теряет детали.

Как управлять OpenCode с телефона?

Через OpenCode Telegram Bot: он ставится на ту же машину, где работает сервер OpenCode, и даёт полноценный клиент в Телеграме — постановка задач, переключение моделей и режимов, подтверждение прав кнопками, задачи по расписанию. Наружу порты не открываются, доступ ограничен белым списком по числовому идентификатору пользователя.

Почему служба systemd падает с ошибкой 217/USER или 203/EXEC?

Код 217/USER означает, что пользователя из директивы User= нет в системе, 203/EXEC — что файла по пути из ExecStart не существует или на нём нет права на запуск. Оба случая возникают при копировании юнита из инструкции без подстановки своих значений: пути к глобальным пакетам npm различаются в зависимости от способа установки Node.js. Выясняйте их командой command -v и подставляйте в юнит.

Безопасно ли давать агенту доступ к репозиторию?

При настройках по умолчанию — нет. Маски в permission.bash контролируют только команды оболочки, а файлы агент читает штатным инструментом, минуя их: .env с ключами уходит в контекст к провайдеру модели. Нужен отдельный плагин, запрещающий чтение файлов окружения, и ужесточённые маски, закрывающие загрузку скриптов из сети и правку служебных каталогов git. Отдельно следите за тем, что агент запускает: поднятые им демоны попадают в дерево процессов службы и умирают при её перезапуске.

Оцените запись