DeepSeek Harness plugin

dsh-plugins-iiaarg

Telegram channel for DeepSeek Harness — one shared module serving several agents on a machine, each with its own bot, token file and allow-list.

Jump to install

Source facts

Repository
iia-arg/dsh-plugins
Latest update
Aug 21, 2026
Category
Tools & Capabilities
GitHub stars
1
Format
plugin
Package path
packages/telegram-multiagent
Catalog evidence
Upstream dsh.bundle evidence
Evidence path
packages/telegram-multiagent/package.json#dsh.bundle
Checked against
0.1.0-rc.8
Upstream check date
2026-08-20

This evidence comes from the upstream catalog. This site has not installed, run, or security-reviewed the plugin.

Install

Start with a prompt that asks an agent to review the GitHub repository and source. Switch to the command if you want to install it yourself.

Copy this prompt into DSH, Codex, or another agent and ask it to review the GitHub repository and source first.

Do not install or run any commands yet. Read this plugin's GitHub repository, README, and relevant source code. Then answer the questions below clearly and directly so I can decide whether it fits my needs:

1. What is this plugin, and what problem does it solve?
2. Who is it for, and what are its typical use cases?
3. How is it used after installation? Include one minimal example.
4. What known limitations or privacy, security, compatibility, or maintenance risks does it have?
5. Give a clear recommendation: recommend, conditionally recommend, or do not recommend, with reasons.

Distinguish statements documented by the repository, inferences from source code, and unknowns. If evidence is insufficient, say so explicitly. Do not guess or simply repeat the README.

GitHub: https://github.com/iia-arg/dsh-plugins/tree/HEAD/packages/telegram-multiagent
Plugin: dsh-plugins-iiaarg
Author: iia-arg

Check the source files

Read the README and other files from this plugin directory before installing.

File explorer2 files
README.mdSource · read only

dsh-telegram-multiagent

A Telegram channel for DeepSeek Harness: message your agent from a phone, get answers back, keep one conversation per chat.

The harness ships no messenger channel — there is no channel abstraction in the product at all. This plugin builds one out of the two ends the harness does give you:

in:   ctx.agents.create/resume(...) → agent.send(message)
out:  ctx.on('session/event', ...)  → send to Telegram

One chat = one session = one agent. Sessions live independently, the way the core intends.

Install

# inside your profile directory ($DSH_HOME/profiles/<name>)
pnpm add dsh-telegram-multiagent

Then add one row to your agent preset (agent.cordis.yml), not to the profile patch layer — see "Where to put the row" below:

- insert:
    - name: dsh-telegram-multiagent
      config:
        agentName: my-agent            # log label only
        tokenFile: /etc/dsh/bot.token  # preferred: a file readable by this agent
        appDir: /opt/my-agent/app      # where THIS agent's harness is installed
        workspace: /opt/my-agent/work
        allowedUsers: [123456789]      # empty = everyone; you do not want that

Configuration

FieldRequiredWhat it does
tokenFileone of thesePath to a file holding the bot token. Preferred: the secret belongs to the machine, the config only carries a path.
token / tokenEnvone of theseLiteral token, or the name of an env var. For debugging.
appDiryesDirectory where this agent's harness is installed. Platform packages are resolved from here, not from the plugin's own location — see "Why appDir" below.
agentNamenoLabel in log lines. Useful when several bots run on one machine.
allowedUsersnoNumeric user ids allowed to talk to the agent. Empty means everyone — an agent usually has real access to the machine, so set it.
workspacenoWorking directory handed to the agent session.
presetnoAgent preset to mount for sessions created by this channel.
provider / modelnoFallback if the deployment has no default model service.
a2aDirnoDirectory for a file-based agent-to-agent channel (in/, out/). Omit and no such channel exists.
a2aSessionnoSession id used for that channel. Default a2a.
transcribeCommandnoExternal command for voice messages: <cmd> <audio-file> auto → transcript on stdout. Omit and voice is politely refused.

Four things that cost us a day

Each of these fails while looking like success. They are commented inline in the source; this is the short version.

1. Polling belongs to the bot, not to the mount. The harness mounts a composition more than once per process and unmounts the extra one. With a single shared running flag you get two pollers fighting over one bot and Telegram cuts both with Conflict. With a polite "new mount asks the old one to step aside" you get no poller at all — because the new mount is the one that gets unmounted. The fix is reference counting: the first mount starts polling, the last unmount stops it.

2. Delete the incoming message only after it reached the agent. Reading a file and unlinking it immediately loses the message whenever the handoff fails — and the handoff will fail, see (3). From the outside that is indistinguishable from "the bot ignored me".

3. The agent factory appears later than the channel. The first message can arrive before the harness has registered it, and you get no agent factory registered. Wait for the platform instead of assuming it is ready.

4. A session already on disk must be resumed, not created. Calling create() with an existing session id makes the persistence layer abort every turn with an id-collision error. Externally: "accepted the message and went quiet" — the turn honestly starts and dies in milliseconds. It works until the first restart, which is what makes it nasty. Use resume() when the session exists.

Where to put the row

In the web profile the common-plane tools are disabled on purpose; the toolset comes from the agent preset. A plugin row placed in the profile patch layer composes without a single error, is listed as mounted — and never reaches the agent. Put the row in the agent preset.

The preset is also picked up when a session is created: editing the file does not affect a running session. Restart the platform, or change the default preset in settings (hot-reloaded, takes effect for the next created session).

Why appDir

The plugin resolves @deepseek-ai/dsh-llm and @deepseek-ai/dsh-agent from the directory you pass in appDir, not from its own location. That is deliberate: the module is meant to live once on a machine and serve several agents, each with its own harness installation. Hard-linking it to one agent's node_modules would mean that removing that agent breaks the channel for everybody else.

Security notes

  • allowedUsers empty means anyone who finds the bot talks to an agent that usually has shell access

to the machine. Set it.

  • A rejected stranger is logged and reported to the owner. A silent refusal hides a security event.
  • The token is read from a file at startup; the config carries a path, not a secret.

Status

Written for our own fleet and running in production on several agents. The harness is young (0.1.0-rc) and its plugin API moves; expect to adapt. Inline comments are currently in Russian — they carry the reasoning behind each non-obvious line, and a translation is welcome.

MIT.

1.1.0 — кто спросил, кому отвечено, и кто это видит

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

Пометка отправителя, которую нельзя подделать. Каждое входящее помечается каналом, из которого пришло. 🔴 Ключевое: перед тем как поставить свою пометку, модуль ВЫРЕЗАЕТ из текста всё похожее на неё. Без этого пометка была бы подписью в тексте, а подпись подделывает любой, кто умеет печатать. Проверено нападением: сообщение из личного чата с готовой строкой «служебный канал» приходит агенту помеченным как личный чат.

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

Адресат ответа привязан к ХОДУ, а не к последнему сообщению. Ответ помечается [ответ: кто] с цитатой вопроса. 🔴 Почему не «отвечаем последнему спросившему»: если второй вопрос пришёл, пока первый считается, ответы разъезжаются не тем — причём с правильными на вид пометками.

Режим доставки — в файле ВНЕ кода. Путь берётся из settingsFile (по умолчанию — файл токена с расширением .json). Читается на лету по времени изменения: правка действует со следующего сообщения, перезапуск не нужен. Файл переживает обновление модуля.

{ "deliveryMode": "personal" }
режимкто что видит
personal (умолчание)каждый видит только свои вопросы и ответы
broadcastоба видят весь обмен, чужое помечено «адресовано не вам»
owner-allвладелец видит всё, координатор только своё

🔴 Умолчание выбрано самым тихим намеренно: испорченный или недоступный файл настроек НЕ должен внезапно раскрыть переписку в чужой канал. Ошибка чтения = personal, и о ней говорится в журнал.

Копируются и вопросы, и ответы: половина разговора без второй половины нечитаема.

1.2.0 — отправка переживает сбой сети

🔴 Отправка теперь повторяется. До этой версии одна неудачная попытка теряла сообщение НАВСЕГДА и не оставляла следа — снаружи это неотличимо от «агент промолчал».

Поймано числами: на нашей машине fetch failed случается 6-10 раз в час. Копия вопроса не дошла, а соседняя отправка четырьмя секундами позже прошла — то есть терялось не по логике, а по случайности. Мы к тому моменту полдня искали причину молчания агента в коде, в настройках и в чужих ботах.

Как сделано: три попытки с растущей паузой (0.4 с, 0.8 с), в журнал пишется, с какой попытки удалось. Повторяются только сетевые сбои; отказ Telegram по существу (нет прав, чат не найден, пустой текст) не повторяется — он воспроизведётся. Длинный опрос намеренно оставлен без повторов: у него свой цикл, повтор лишь задержал бы следующий заход.

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