SSD Nodes Learn Hosting plans →
Руководства Matt ConnorАвтор: Matt Connor · Обновлено 2026-08-21

Как запустить Deer Workflow на VPS с Ubuntu

Настройте оркестрацию агентов через TypeScript на собственном сервере. Руководство описывает установку Bun, фиксацию версий и запуск графов через systemd без ошибок PATH.

Что вы создаете

Deer Workflow — это runtime с подходом code-first для графов агентов: поток управления находится в файле TypeScript, который можно проверить, а агент-кодер выполняет только те части, где требуется принятие решений. В этом руководстве описывается установка на один VPS с Ubuntu, запуск одного примера графа в фоновом режиме через systemd и запись машиночитаемого потока событий в лог-файл, который можно изучить, если выполнение завершится ошибкой в три часа ночи.

Компоненты системы просты. Bun запускает CLI. Один CLI агента-кодера, например Codex или Claude Code, выполняет работу модели. Один зафиксированный npm-пакет содержит runtime. Один файл TypeScript содержит ваш граф. Юнит и таймер systemd запускают его по расписанию. Большая часть руководства посвящена компонентам, которые чаще всего вызывают сбои: переменная PATH внутри юнита systemd, учетные данные агента в сессии без интерактивной оболочки и фиксация зависимости, впервые опубликованной в июле 2026.

Визуальный конструктор, код или просто промптинг агента

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

Визуальный конструктор предоставляет рабочее поле, библиотеку узлов и пользовательский интерфейс, понятный даже тем, кто не умеет программировать. Это реальное преимущество, и в данной области представлено так много решений, что существует целый обзор альтернатив n8n для self-hosted. Цена этого удобства заключается в том, что логика превращается в JSON-документ, созданный интерфейсом. Разница (diff) в таком документе нечитаема, поэтому для проверки изменений приходится открывать визуальный редактор, а не просматривать патч.

Второй подход — прямой промптинг агента. Вы описываете всю задачу одним абзацем и позволяете модели самой определять порядок действий, количество повторных попыток и условия завершения. Это работает до того момента, пока модель не решит поступить иначе. Здесь нет diff, так как нет артефакта: план существовал только в рамках диалога, а сам диалог исчез.

Третий подход — оркестрация через код. Порядок шагов, параллельное выполнение, повторные попытки и обработка ошибок — это обычный TypeScript в git. Модель вызывается только там, где требуется принятие решений, и нигде больше. Цена этого подхода в том, что кто-то должен писать и поддерживать этот код, а коллега, не владеющий TypeScript, не сможет его отредактировать.

Преимущества и издержки графовой среды выполнения

  • Проверяемый поток управления. Граф представляет собой файл. Изменение политики повторных попыток отображается в pull request как три измененные строки, а не как перемещенный блок.
  • Обработка сбоев в системе контроля версий. Сценарий действий при ошибке на четвертом этапе зафиксирован, протестирован и помечен тегом вместе с остальной инфраструктурой.
  • Взаимозаменяемый агент. Среда выполнения поставляется с адаптерами для Codex, Claude Code и Pi. Смена модели для выполнения этапа требует изменения одного импорта.
  • Наблюдаемое выполнение. Фазы и события выводятся средой выполнения в виде структурированных данных, поэтому после фонового запуска остается запись, которую можно проанализировать.

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

Проект новый, поэтому фиксируйте версию

Deer Workflow распространяется по лицензии MIT и является новым проектом. На 19 августа 2026 года в репозитории насчитывается 47 коммитов в main. В npm опубликованы три версии: 0.0.1 и 0.1.0 от 26 июля 2026 года, а также 0.2.0 от 27 июля 2026 года. Для каждой из них есть git-тег, а в журнале изменений (changelog) можно узнать, что именно изменилось между ними. В разделе Unreleased уже удалена команда deer-workflow agent, поэтому main и новейшая опубликованная версия больше не предоставляют прежний интерфейс командной строки (CLI).

Это не повод отказываться от проекта. Это повод устанавливать конкретную версию и знать, какая именно установлена.

  • Устанавливайте точную версию, никогда не используйте диапазоны.
  • Фиксируйте эту версию в том же репозитории, где хранятся ваши графики.
  • После любого обновления запустите свой график вручную один раз, прежде чем таймер запустит его снова.

Установка Bun и среды выполнения агента

Все действия ниже выполняются от имени обычного пользователя с правами sudo. Не запускайте их от имени root. CLI агентов сохраняют учетные данные в домашнем каталоге пользователя, который выполнил вход, и systemd-юнит впоследствии должен запускаться от того же пользователя, чтобы найти их.

sudo apt update
sudo apt install -y curl unzip jq git nodejs npm
curl -fsSL https://bun.com/install | bash

Установщик Bun распаковывает zip-архив, поэтому сначала должен быть установлен unzip. Установщик добавляет строки PATH в профиль вашей оболочки, и текущая оболочка уже прочитала этот файл, поэтому откройте новую оболочку или добавьте эти две строки в ~/.bashrc самостоятельно и перезагрузите конфигурацию.

export BUN_INSTALL="$HOME/.bun"
export PATH="$BUN_INSTALL/bin:$HOME/.npm-global/bin:$PATH"
bun --version

Эта команда выводит номер версии. Ошибка bun: command not found означает, что строка PATH отсутствует в текущей оболочке, а не то, что установка не удалась. Выполните ls ~/.bun/bin перед тем, как переустанавливать что-либо.

Теперь среда выполнения агента. Codex CLI является стандартной и устанавливается через npm. Установите префикс npm на уровне пользователя, чтобы глобальная установка не требовала прав root.

npm config set prefix "$HOME/.npm-global"
npm install -g @openai/codex
command -v codex
codex

command -v codex должна вывести путь внутри $HOME/.npm-global/bin. Запуск codex без аргументов открывает CLI, где вы входите в свою учетную запись ChatGPT. Сделайте это сейчас, пока у вас есть доступ к экрану.

Claude Code работает как альтернативная среда выполнения и имеет собственный установщик.

curl -fsSL https://claude.ai/install.sh | bash
claude --version

Корректная установка выводит версию, например 2.1.211 (Claude Code). Выполните claude один раз для входа в систему. Это процесс того же класса, с тем же доступом к вашим файлам, что и любой другой агент, который вы размещаете, поэтому примечания об учетной записи и безопасности в запуске агента для программирования на VPS применимы здесь без изменений.

Установка Deer Workflow и фиксация конкретной версии

bun install --global @deerwork-ai/deer-workflow@0.2.0
command -v deer-workflow

command -v выводит абсолютный путь, обычно это /home/<your user>/.bun/bin/deer-workflow. Сохраните его. Systemd-юнит не может использовать простое имя команды.

Указывайте версию в команде установки. Если опустить @0.2.0, будет установлена последняя версия на момент запуска, что в проекте с 47 коммитами может привести к изменению CLI в самый неподходящий момент.

Размещение графов в репозитории git

mkdir -p ~/workflows/logs
cd ~/workflows
git init

Codex проверяет, запущен ли он внутри репозитория git, поэтому CodexAgentConfig содержит опцию skipGitRepositoryCheck для случаев, когда вы не можете его предоставить. На собственном VPS вы можете это сделать, и вам следует это выполнить: граф — это код, а аргументы в пользу написания оркестрации как кода теряют смысл, если код не находится под контролем версий. Создайте каталог logs сейчас, так как systemd не создаст его за вас.

Написание одного графа

Рабочий процесс (workflow) представляет собой обычный модуль TypeScript. Он экспортирует meta — объект, содержащий имя, описание и упорядоченный список фаз, а также экспортирует обработчик как default или как именованный экспорт run. Внутри обработчика вы вызываете вспомогательные функции из пакета. phase() отмечает, на какой стадии находится выполнение, log() записывает строку прогресса, agent() отправляет один запрос (prompt) агенту кодирования, parallel() запускает список задач одновременно, а pipeline() передает список элементов через несколько этапов.

Сохраните это как ~/workflows/log-triage.ts.

import { agent, log, parallel, phase } from "@deerwork-ai/deer-workflow";

export const meta = {
  name: "log-triage",
  description: "Groups recent service errors and writes one short report.",
  phases: [{ title: "Collect" }, { title: "Classify" }, { title: "Report" }],
  exampleArgs: { service: "nginx", hours: 24 },
};

export default async function workflow(args: { service: string; hours: number }) {
  if (!args?.service) throw new Error("input needs a service name");

  phase("Collect");
  log(`Reading ${args.hours}h of logs for ${args.service}`);
  const found = await agent<{ patterns: string[] }>(
    `Read the last ${args.hours} hours of journalctl -u ${args.service} and list the distinct error patterns.`,
    {
      sandbox: "read-only",
      schema: {
        type: "object",
        properties: { patterns: { type: "array", items: { type: "string" } } },
        required: ["patterns"],
        additionalProperties: false,
      },
    },
  );

  phase("Classify");
  log(`Classifying ${found.patterns.length} patterns`);
  const notes = await parallel(
    found.patterns.map((pattern) => () =>
      agent(`Explain this error and its most likely cause: ${pattern}`, { sandbox: "read-only" }),
    ),
  );

  phase("Report");
  return agent(`Write a short operations report from these notes: ${JSON.stringify(notes.filter(Boolean))}`);
}

Четыре детали в этом файле имеют важное значение.

  • schema в вызове agent() запрашивает структурированный вывод, и вызов возвращает распарсенный объект. found.patterns — это реальный массив, по которому остальная часть графа может выполнять итерацию. Без схемы agent() возвращает строку, и вам приходится парсить обычный текст.
  • sandbox определяет, к чему этот шаг может обращаться. read-only блокирует запись, workspace-write разрешает защищенную запись, а danger-full-access снимает защиту. Этот параметр устанавливается для каждого вызова, поэтому граф может считывать данные из широкого круга источников, а записывать — только в одном месте.
  • parallel() принимает функции, а не промисы. map((pattern) => () => agent(...)) создает список функций-заглушек (thunks), поэтому среда выполнения сама решает, когда каждая из них начнется. Передача agent(...) напрямую привела бы к запуску каждого вызова в момент создания списка.
  • Невыполненная задача внутри parallel() становится null, и выполнение продолжается, так как частичное завершение предусмотрено архитектурой. Поэтому notes.filter(Boolean) — это не просто украшение: пропустите его, и в случае сбоя ветки в запрос следующего шага будет вставлен текст null.

Обычный вспомогательный метод agent() использует среду выполнения по умолчанию — Codex. Чтобы отправить один шаг в Claude Code, импортируйте класс агента и вызовите его напрямую.

import { ClaudeAgent } from "@deerwork-ai/deer-workflow";

const claude = new ClaudeAgent({ sandbox: "read-only" });
const summary = await claude.run<string>("Summarise ./report.md in five lines.");

Вот как выглядит заменяемый агент на практике: один импорт и один конструктор, при этом сам граф остается неизменным. Флаг --agent codex|claude|pi в CLI относится к deer-workflow create, который генерирует файл рабочего процесса на основе описания. Он не меняет среду выполнения, которую использует deer-workflow run.

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

cd ~/workflows
deer-workflow run ./log-triage.ts --input '{"service":"nginx","hours":24}'

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

Для автоматизации перенесите входные данные в файл. Сохраните ~/workflows/input.json:

{ "service": "nginx", "hours": 24 }
deer-workflow run ./log-triage.ts --input-file ./input.json --print >> logs/run.jsonl

--print, сокращенная форма -p, отключает интерфейс и выводит поток событий в stdout, по одному объекту JSON на строку. В этом режиме в stdout больше ничего не выводится, поэтому перенаправление вывода напрямую в файл .jsonl позволит получить файл, каждая строка которого корректно парсится.

Поток событий и что искать через grep в 3 часа ночи

Каждая строка содержит type, sequence, timestamp, workflowId, depth и scriptPath. Типы событий: workflow:start, workflow:meta, workflow:end, workflow:error, workflow:phase:start, workflow:phase:end и log. События фаз содержат phase, события завершения — durationMs, событие log содержит message, а событие workflow:error содержит error вместе с name, message и, как правило, stack.

Этой структуры достаточно, чтобы ответить на два вопроса, возникающих в три часа ночи: завершилось ли выполнение и на каком этапе оно остановилось.

grep workflow:error logs/run.jsonl
jq -r 'select(.type == "workflow:error") | .error.message' logs/run.jsonl
jq -r 'select(.type == "workflow:phase:end") | [.phase, .durationMs] | @tsv' logs/run.jsonl
jq -r 'select(.type == "log") | .message' logs/run.jsonl

Чтобы отслеживать текущий процесс выполнения, используйте команду для чтения файла в реальном времени: tail -f logs/run.jsonl | jq -c 'select(.type == "log")'. Один запуск записывает небольшое количество строк, но файл постоянно растёт, поэтому добавьте правило logrotate для ~/workflows/logs/*.jsonl после того, как таймер проработает несколько недель.

Запуск через systemd

Используйте сервис oneshot вместе с таймером, а не постоянно работающий демон. Граф запускается, выполняет задачу и завершается. Создайте /etc/systemd/system/log-triage.service, заменив deploy на имя вашего пользователя.

[Unit]
Description=Log triage workflow
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
User=deploy
WorkingDirectory=/home/deploy/workflows
Environment=HOME=/home/deploy
Environment=PATH=/home/deploy/.bun/bin:/home/deploy/.npm-global/bin:/usr/local/bin:/usr/bin:/bin
ExecStart=/home/deploy/.bun/bin/deer-workflow run ./log-triage.ts --input-file ./input.json --print
StandardOutput=append:/home/deploy/workflows/logs/run.jsonl
StandardError=journal
TimeoutStartSec=3600

Затем выполните /etc/systemd/system/log-triage.timer:

[Unit]
Description=Run the log triage workflow every night

[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true

[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl start log-triage.service
systemctl status log-triage.service
sudo systemctl enable --now log-triage.timer
systemctl list-timers log-triage.timer

Сначала запустите сервис вручную. Успешное выполнение завершается переходом юнита в неактивное состояние, а logs/run.jsonl получает блок событий, заканчивающийся на workflow:end. Только после этого активируйте таймер. list-timers выводит время следующего запланированного запуска, а Persistent=true означает, что пропущенный во время простоя сервера запуск произойдёт один раз при следующей загрузке. StandardOutput=append: направляет поток событий в файл, оставляя в журнале всё остальное, поэтому journalctl -u log-triage.service остаётся читаемым.

Почему граф работает в моей оболочке, но не запускается под управлением systemd?

Проверьте эти четыре пункта в указанном порядке.

Юнит не может найти исполняемые файлы. systemd никогда не считывает ~/.bashrc, а его стандартный PATH не содержит ни ~/.bun/bin, ни ~/.npm-global/bin. Юнит завершается менее чем за секунду, а journalctl -u log-triage.service показывает ошибку выполнения команды. Именно поэтому в ExecStart используется абсолютный путь, и именно поэтому в Environment=PATH= по-прежнему указаны оба каталога: сама среда выполнения должна найти codex или claude при запуске шага агента.

Агент не может найти свои учетные данные. CLI агента считывает данные для входа из домашнего каталога, поэтому явно укажите User= и Environment=HOME=, задав тот домашний каталог, под которым вы авторизовались. Если выполнение доходит до workflow:start, а затем выдает workflow:error, сообщение которого исходит от CLI агента, а не от вашего кода, — почти всегда причина именно в этом.

Процесс завершается по истечении 90 секунд. Для Type=oneshot systemd применяет таймаут запуска ко всей команде, и по умолчанию он составляет 90 секунд. Граф агента может выполняться несколько минут. В журнале фиксируется Start operation timed out. Terminating., юнит переходит в состояние сбоя, а файл лога содержит лишь часть выполнения без workflow:end. TimeoutStartSec=3600 увеличивает время до часа. Используйте infinity, если хотите, чтобы процесс никогда не завершался по таймауту.

Относительные пути указывают не туда. ./log-triage.ts и ./input.json отсчитываются относительно WorkingDirectory. Если убрать эту строку, systemd запустит процесс в /, где этих файлов нет.

Что разрешено делать оркестратору

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

Первый элемент контроля — это песочница для каждого вызова agent(). read-only является правильным значением по умолчанию для любого шага, который только читает данные: логи, метрики или репозиторий, который вы анализируете. Переходите к workspace-write только тогда, когда шаг действительно должен выполнять запись, и ограничивайте область записи с помощью additionalWritableDirectories вместо использования danger-full-access.

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

Бюджет — это деньги. Каждый вызов agent() — это полноценная сессия агента, а parallel() запускает несколько таких сессий одновременно. Таким образом, граф, который разветвляется на двенадцать направлений, будет выполнять двенадцать сессий каждую ночь, независимо от того, читает ли кто-то отчет. Измерения и лимиты, описанные в Контроль расходов на AI-агентов на VPS, напрямую применимы к графам, работающим по расписанию.

Перед обновлением среды выполнения прочитайте список изменений, установите новую точную версию и запустите граф вручную с помощью --print. В столь молодом проекте интерфейс CLI все еще меняется: в разделе Unreleased уже исключена команда, которая существовала в версии 0.2.0. Граф, работающий по таймеру, надежен ровно настолько, насколько надежна зафиксированная вами версия и последний запуск, за которым вы действительно наблюдали.

FAQ

Нужен ли мне Bun или Node.js запустит Deer Workflow?

Установите Bun. Опубликованный пакет указывает свой бинарный файл deer-workflow на src/cli.ts, исходный файл TypeScript, а документация называет Bun обязательным требованием. Bun исполняет TypeScript напрямую, поэтому этап сборки не требуется. Установите его с помощью sudo apt install -y unzip, затем curl -fsSL https://bun.com/install | bash, и подтвердите установку командой bun --version. Вам по-прежнему потребуются Node.js и npm отдельно, если вы устанавливаете Codex CLI из npm.

Почему мой рабочий процесс работает в терминале, но завершается ошибкой в systemd?

Почти всегда дело в PATH, HOME или тайм-ауте запуска. systemd не считывает ваш профиль оболочки, поэтому ExecStart требует абсолютного пути к deer-workflow, а Environment=PATH= требует указания директории, содержащей codex или claude. Агент CLI считывает учетные данные из $HOME, поэтому установите User= и Environment=HOME= для той учетной записи, под которой вы вошли в систему. Кроме того, Type=oneshot наследует тайм-аут запуска в 90 секунд, который прерывает работу агента и оставляет Start operation timed out. Terminating. в журнале, поэтому установите TimeoutStartSec=3600.

Как использовать Claude Code вместо Codex для отдельного шага?

Обычный вспомогательный инструмент agent() использует среду выполнения по умолчанию — Codex. Импортируйте ClaudeAgent из пакета, создайте его экземпляр и вызовите .run() для тех шагов, которые должен обрабатывать Claude Code. Флаг --agent codex|claude|pi относится к deer-workflow create, команде, которая генерирует файл рабочего процесса на основе описания, и он не влияет на deer-workflow run. Для любого используемого агента требуется установленный CLI, авторизованный под тем же пользователем, от имени которого работает служба.

Какую версию Deer Workflow мне следует установить?

Ту самую, которую вы тестировали. По состоянию на 19 августа 2026 года новейшая опубликованная версия — 0.2.0 от 27 июля 2026 года, а репозиторий содержит 47 коммитов. Укажите @0.2.0 (или актуальную на момент прочтения версию) в команде установки, сохраните этот номер в git рядом с вашими графами и запускайте один граф вручную после каждого обновления, прежде чем таймер снова его активирует.