Skip to main content
Glama

omp-dsh-workers

test

Запускайте воркеры DeepSeek Harness (DSH) из вашей сессии oh-my-pi. oh-my-pi (OMP) — это терминальный кодинг-агент; DeepSeek Harness (dsh) — это агентный рантайм DeepSeek. Сессия становится директором: она раздаёт брифы через dsh_spawn, каждый воркер работает как постоянная сессия dsh --profile headless, а вопросы и результаты воркеров возвращаются как нативные сообщения, ретранслируемые скриптом.

Экспериментальная v0.1: интерфейсы зафиксированы в docs/contracts/, но ничто здесь ещё не проходило публичный цикл релиза.

Зачем

Харнесс OMP дорог в расчёте на задачу, и нативный субагент платит эту цену за каждое задание. Здесь она платится один раз на уровне директора; работа выполняется в DSH — быстро и экономно по токенам, а между ними только скрипт: ноль токенов модели на задачу. DSH делает запуски постоянными (--resume на настоящем session id). Когда DSH не нужен, нативные субагенты по-прежнему остаются правильным выбором.

Related MCP server: dsh-crew

Как это работает

Два уровня моделей, намеренно разделённых:

Уровень

Кто

Откуда берётся модель

1

Директор — ваша основная сессия OMP в режиме /dvibe

модель вашей сессии OMP

2

DSH-исполнитель — один headless-процесс DSH на запуск

model из dsh_spawn, иначе роль @dsh, иначе модель вашей сессии, иначе значение по умолчанию DSH

Итак, @dsh называет унаследованную модель исполнителя, когда в dsh_spawn нет model — наблюдатель это код, а не модель.

flowchart TD
    D["Director<br/>main OMP session, /dvibe on"]
    B["dsh-bridge<br/>argv spawn · run registry · steer channel"]
    X["DSH headless run<br/>+ resume plugin"]
    L["relay.ts<br/>script representative, in-process"]
    D -->|"dsh_spawn — brief, label, model"| B
    B -->|"dsh --profile headless [--resume]"| X
    X -->|"Envelope v1 (last stdout line)"| B
    B -->|"pollRun, 1s"| L
    L -->|"⟨label⟩ question / result / failure (followUp)"| D
    D -->|"dsh_answer — resumes the session"| B
    D -.->|"steering: dsh_list → dsh_send / dsh_wait by runId"| B
    D -.->|"dsh_kill by runId"| B

Директор порождает запуски через dsh_spawn, отвечает через dsh_answer, управляет через dsh_send, ждёт через dsh_wait и отменяет через dsh_kill; dsh_list сопоставляет метку с runId.

Компоненты

Путь

Что это

extensions/dsh-task/

Расширение OMP: dsh_task, dsh_spawn, dsh_answer, dsh_wait, dsh_kill, dsh_send, dsh_list, скрипт-ретранслятор (relay.ts), /dvibe, watchdog для осиротевших процессов.

tools/dsh-bridge/

bridge-core: запуск в собственной отдельной группе процессов, реестр запусков, Envelope v1, канал управления, аренда владельца и сбор завершённых. Node ≥ 22, чистый ESM JavaScript, без зависимостей и без шага сборки.

plugins/dsh-headless-resume/

Плагин Cordis в headless-профиле DSH: добавляет --resume, печатает Envelope v1, выполняет префлайт модели, читает канал управления.

scripts/

Установка: симлинки в живой каталог OMP, патч профиля DSH, связывание зависимостей плагина.

Требования

  • oh-my-pi v18 — проверено на 18.0.3 / 18.0.4; @oh-my-pi/* зафиксирован на ^18.0.4.

  • DSH ≥ 0.1.1-rc.2 в PATH, с наличием профиля headless.

  • bun для тестовых скриптов; Node ≥ 22 для bridge-core.

  • Провайдер модели, настроенный в ваших настройках DSH; расширение нейтрально к провайдеру: оно передаёт строку <provider>/<model>[:<effort>] в DSH.

DSH находится на стадии release-candidate. Плагин resume подключается по entry id, поэтому релиз, переименовывающий эти id, заставит патч молча перестать применяться. После каждого обновления DSH повторно запускайте dsh --profile headless --help: если --resume исчез, плагин не смонтирован; полный чек-лист есть в docs/dsh-update-checklist.md.

Установка

Репозиторий — источник истины; живые каталоги получают только симлинки, ведущие обратно в него.

1. Создайте симлинк расширения в OMP.

scripts/install-omp-links.sh [--dry-run] [--uninstall] [--omp-dir DIR]

Создаёт симлинк extensions/dsh-task в $OMP_DIR (по умолчанию $HOME/.omp/agent). Идемпотентно: ссылка на тот же источник остаётся как есть, ссылка, указывающая в другое место, перенаправляется, а реальный файл в месте назначения прерывает выполнение скрипта. --uninstall удаляет только ссылки, указывающие сюда.

2. Смонтируйте плагин resume в headless-профиль DSH.

scripts/install-resume-plugin.sh              # install
scripts/install-resume-plugin.sh --uninstall  # remove

Делает резервную копию перед каждым изменением; требует dsh в PATH и ${DSH_HOME:-$HOME/.dsh}/profiles/headless. Затем:

  • Создаёт симлинки @deepseek-ai и commander из $DSH_MODULES в node_modules плагина.

  • Добавляет плагин: dsh plugin --profile headless add link:<plugin dir>, после резервного копирования package.json.

  • Добавляет блок cordis.patch.yml, который отключает headless-startup / headless-runner и вставляет headless-resume-startup / headless-resume-runner.

  • Проверяет: успех только когда dsh --profile headless --help упоминает --resume.

3. (только для тестов) scripts/link-plugin-deps.sh сам связывает зависимости; bun run test:resume вызывает его.

Использование

Режим директора

  • /dvibe переключает режим директора; /dvibe on / /dvibe off задают его явно. Модель также может переключать его через инструмент dvibe (action: "on" | "off"), который остаётся в суженном наборе инструментов.

  • Пока он включён, набор инструментов сужается до read, todo, dsh_spawn, dsh_answer, dsh_send, dsh_wait, dsh_list, dsh_kill, dvibe, плюс директива директора, добавляемая к системному промпту. Инструмент dvibe возвращает эту директиву в своём результате: модель вызывает его после того, как отработал before_agent_start, поэтому промпт хода не может содержать правила.

  • Брифы передаются в dsh_spawn дословно. Вопросы воркеров приходят как сообщения ⟨label⟩ от relay.ts, на них отвечают через dsh_answer; результаты приходят тем же образом.

  • Доставка как минимум однократная: событие повторно анонсируется каждые 120 с, пока подходящий message_start не докажет, что последующее сообщение попало в контекст хода; максимум 3 попытки на событие. Доставленный need_input остаётся под наблюдением до dsh_answer.

  • Закончили раздавать работу? Завершите ход: события придут как сообщения сами по себе.

  • dsh_wait — синхронная альтернатива, только когда следующий шаг блокируется на этом конкретном запуске и раздавать больше нечего; конверт, прочитанный таким образом, никогда не приходит дважды.

  • При /dvibe off, завершении работы или переключении сессии в рамках процесса восстанавливается предыдущий набор инструментов.

Брифы, модели, resume

  • Дайте каждой задаче короткий label и, опционально, model — оба как параметры dsh_spawn; по метке запуск находится позже в dsh_list, dsh_answer, dsh_send.

  • Обозначение модели — <provider>/<model>[:<effort>]; уровни effort: off, minimal, low, medium, high, xhigh, max. Суффикс после последнего : считается effort, только если он один из них, иначе двоеточие принадлежит имени модели. Никаких пробелов и управляющих символов; provider/model ≤ 200 символов каждый, spec ≤ 512; некорректный spec приводит к ошибке до запуска: error [invalid_model].

  • Без model запуск наследует роль @dsh из OMP (modelRoles.dsh), затем откатывается к модели вашей сессии, а затем к собственному значению по умолчанию DSH.

  • Resume: передайте resumeFromRunId; bridge ищет его sessionId. Не помещайте runId в resumeSessionId: это разные идентификаторы, вы получите resume_not_found.

  • Resume работает после того, как запуск оставил конверт на диске; без него dsh_spawn выбрасывает has no session to resume — как для всё ещё работающего запуска, так и для исчезнувшего (убитого рано, упавшего при старте, вычищенного). Прежде чем реагировать, проверьте, какой это случай: новый бриф для всё ещё работающего запуска дублирует работу.

  • Переопределение модели не сохраняется: resume без model пересчитывает модель. В dsh_answer вообще нет параметра model; чтобы продолжить на другой модели, используйте dsh_spawn с resumeFromRunId и явным model.

Что видит директор

Карточки инструментов отображаются для людей отдельно от текста, который получает модель: ▶ dsh spawn → <label>, ✓ started <label> (<runId8>) · pid …, затем ⏳ still running с последними строками вывода или ✓ completed · model: … · session: … плюс первые строки результата. Пока отслеживаются запуски, над редактором находится доска dsh runs, а в футере показывается dsh: N running · M done. Текстовый вывод инструментов не меняется: он остаётся контрактом.

Инструменты

Инструмент

Параметры

Текст, который получает вызывающий

dsh_spawn

task, label?, model?, timeoutMs?, resumeFromRunId?, resumeSessionId?

started <runId> (pid <pid>), плюс label=… и model=…, если заданы

dsh_answer

runId / label (хотя бы один; при обоих runId выбирает целевой запуск, label называет новый запуск), плюс answer

answered <oldRunId> -> <newRunId>

dsh_wait

runId, waitMs? (по умолчанию 30000; 0 = одиночный опрос)

результат запуска, или still running: <runId>, или wait cancelled for <runId>; run still active

dsh_send

runId, text

sent to <runId> / pending: … / NOT delivered: run ended before reading; message lost

dsh_kill

runId

kill <runId>: killed (…) или kill <runId>: not killed (…)

dsh_list

no active runs, или по одной строке на запуск

dsh_task

task, model?, timeoutMs?, resumeFromRunId?, resumeSessionId?

результат запуска (блокирующий, одноразовый)

Таймаут dsh_wait — это нормально: запуск остаётся живым, и его можно ждать снова; прерывание ожидания никогда не останавливает запуск. pending от dsh_send означает, что запись достигла канала и запуск был жив при повторной проверке — это не подтверждённая доставка; ждите вместо повторной отправки. dsh_task не оставляет конверта, поэтому его запуск нельзя продолжить; цепочки идут через dsh_spawn.

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

Эти строки попадают в текстовый вывод инструмента, а не только в details, чтобы директор, читающий обычный текст, мог проверить исполнителя, непрерывность и коды ошибок:

model: <provider>/<model>[:<effort>]
session: <sessionId>
error [<code>]: <message>

# and one line per run from dsh_list:
<runId> state=<state> label=<label|-> model=<spec|default> started=<ISO-8601>

Коды ошибок

Каждый сбой возвращается как явный ход с ошибкой и кодом конверта, никогда — как частичный успех.

Код Envelope

Значение

spawn_failed

Бинарник DSH не запустился.

nonzero_exit

Запуск завершился с кодом выхода, означающим ошибку.

timeout

Запуск не завершился в отведённый срок.

killed

Запуск был отменён.

malformed_output

DSH не вернул корректный Envelope v1.

resume_not_found

Нет такой сессии для возобновления.

resume_corrupt

Сохранённая сессия повреждена или не поддерживается.

resume_busy

Сессия уже активна, либо её сохранённая подготовка зарезервирована.

owner_gone

Никто не продлил аренду запуска; сторожевой механизм забрал её.

deadline_exceeded

Запуск пережил свой дедлайн и был принудительно завершён.

model_not_found

Провайдер/модель отсутствует в каталоге DSH.

invalid_model

Модель существует, но effort или метаданные ей не соответствуют.

По умолчанию: дедлайн запуска 30 мин; аренда владельца 5 мин, продлевается каждым окном dsh_wait.

Тесты

bun run test          # unit + integration + bridge = 370 tests, no installed DSH needed
bun run test:resume   # resume plugin — needs an installed DSH

Проверенные количества на этом дереве: 195 unit + 11 integration + 164 bridge = 370 тестов, проходят без установленного DSH. Unit-тесты мокают bridge-core; integration и bridge запускаются против фейкового бинарника dsh, подставляемого через DSH_BINARY. CI запускает те же три набора с чистым HOME, после typecheck, lint, format:check (строгий tsc, Biome). test:resume импортирует @deepseek-ai/* во время выполнения — DSH должен быть установлен.

Ограничения

  • Осиротевшие запуски зачищаются, а не предотвращаются. Запуски DSH переживают OMP-сессию; зачистка происходит при загрузке и каждые 30 с. При чистом session_shutdown расширение убивает свои запуски (SIGTERM синхронно, SIGKILL по возможности), не очищая реестр.

  • Нет восстановления после сбоя во время выполнения: смерть в середине хода не восстанавливается; можно возобновить только сессию DSH.

  • Компактирование при возобновлении читает заголовок предыдущего запуска, пока не будет записан первый заголовок нового запроса.

  • model в Envelope — best-effort: последняя подготовленная конфигурация запроса, а не доказательство отправки.

  • Переопределение модели действует на один запуск, не наследуется через resumeFromRunId.

  • Метрики Hub не видят токены DSH.

Статус, история, лицензия

Экспериментальная v0.1 (0.1.0). Контракты интерфейса находятся в docs/contracts/; docs/dsh-update-checklist.md описывает обновления DSH. Всё, что читает пользователь или модель, — на английском; комментарии в коде и имена тестов — на русском. Лицензия MIT.

Related MCP Connectors

Related MCP Servers