agents-in-the-loop
Manifest validCross-session call center for DeepSeek Harness (DSH) agents: session_message delivery between sessions + a named contacts directory with name-only self-registration. File-based config, local-only, no
dsh-agents-in-the-loop
Disclaimer: this plugin was fully and automagically coded by GLM 5.3 Flash (Z.ai) running inside the DeepSeek Harness agent federation. Mark Elayan is just the brains — he directs, reviews, and owns every decision; the model does the typing. :D
Cross-session call center for DeepSeek Harness (DSH)
agents. Two model tools — session_message and contacts — plus a
loopback-only HTTP API so agents OUTSIDE dsh (Claude Code, scripts) can
send messages to registered contacts, and so a client panel can read and
manage the contact directory.
Formerly taskboard-flow — the kanban trigger/triage/task engine was removed in v1.0.0 (and the web UI in v1.3.0); the messaging core is preserved verbatim.
Features at a glance
session_message— list live sessions; deliver a message to any registered contact (by NAME — raw session ids are rejected since v1.4.2).contacts— a named directory over session ids: resolve an alias to session id + label + LIVE status in one call, message it, manage entries at runtime (add / update / rename / remove, no config edit, no restart).- External send-only HTTP API (v1.5.0) —
POST /api/agents-in-the-loop/message: loopback curl from any outside agent. Externals never register, never receive, never resurrect sessions. - Contacts HTTP API —
GET/POST/PUT/DELETE /api/agents-in-the-loop/contacts+GET /sessionsfor a client panel. - State-aware delivery engine (shared by all three paths): idle target
gets the full text rendered visibly into its conversation + a
runtime-context note; busy target gets a mid-turn-safe visible notice +
the note; optional
resumeIfDeadresurrects dead sessions.
How delivery works
All sends (tools and HTTP) go through one delivery engine:
| Target state | What happens | Result fields |
|---|---|---|
| idle + wake | full text rendered into the target conversation (steer, fallback followup) and pushed into its runtime context | delivery: context+wake-steer (or -followup), nudgeVia |
| busy + wake | full text injected as a visible plugin-source notice — renders immediately, mid-turn safe, starts no turn — plus the context note | delivery: context+notice, noticeInjected: true |
| wake disabled | runtime-context note only | delivery: context-only |
Harness physics: main GUI sessions start turns on user input, so an idle wake renders the message but does not force a turn — the agent reads it at its next turn via conversation history + the runtime-context note.
Runtime-context notes are delivered through a per-session
systemPrompt.context() registration and appear in the agent's "Current
runtime context" snapshot. Notes expire after 30 minutes, are capped at
5 per session, and any {{ in forwarded text is broken to { { so
messages can never poison the system-prompt template interpolator.
resumeIfDead: true (opt-in) resurrects a dead target via
AgentRegistry.resume before delivering; the resumed agent gets the
deployment-default model selection (a resumed agent must carry a model
route or its wake turn fails on the {{model}} prompt variable). Self-send
is always refused. Status is read exactly once per send (mid-send
idle→busy flips previously produced contradictory results).
Targeting law (v1.4.2): session_message send accepts ONLY a
registered contact name as target — resolved against the live contacts
store at call time, so a re-raised agent re-registered under the same name
is always reached. A raw session-… id is rejected with an error pointing
at session_message/contacts action "list". contacts call is and
always was name-based.
The session_message tool
session_message { action: "list" } → live sessions [{id,status,contact?}]
session_message { action: "send", target, message, → deliver
wake?, resumeIfDead? }
target— registered contact name (required for send).listannotates each live session with its registeredcontactname; successful sends reportresolvedFrom: <contact>.wakedefaults to true;resumeIfDeaddefaults to false.
The contacts tool
contacts { action: "list" } → every contact + live status + store file
contacts { action: "get", name } → one contact + status
contacts { action: "call", name, message, wake?, resumeIfDead? } → message via the delivery engine
contacts { action: "add", name, sessionId?, label?, tags?, note? }
contacts { action: "update", name, sessionId?, label?, tags?, note?, rename? }
contacts { action: "remove", name }
- Self-registration needs the NAME ONLY:
addwith nosessionId(or"self") registers the CALLING session automatically — never research your own session id. An explicit id registers another session. - Names: lowercase
[a-z0-9._-], ≤64 chars. addon a not-currently-live session still saves the contact and returns awarn—resumeIfDeadcan reach it later.- Records carry
label,tags,note,createdAt,updatedAt, and a computed livestatus(idle/running/ … /dead).
External agents (send-only HTTP API)
Agents outside dsh (Claude Code, scripts) can send messages to any
registered contact over loopback HTTP. They never register, never appear
in the contacts store, and never receive messages. resumeIfDead is
hard-wired false: a dead session is refused (409), never resurrected.
curl -s http://127.0.0.1:9001/api/agents-in-the-loop/message \
-H 'Content-Type: application/json' \
-d '{"from":"claude-code","contact":"dev-lead","message":"task done"}'
from— free-form sender label (defaultexternal-agent, truncated to 64 chars), shown asFrom <from>:in the target conversation.- Delivery is identical to the
session_messagetool.
| Status | Meaning |
|---|---|
| 200 | delivered — {ok, contact, from, nudgeVia, noticeInjected} |
| 400 | invalid JSON / invalid contact name / empty message |
| 403 | not loopback (forbidden: loopback-only) |
| 404 | unknown contact |
| 405 | method not allowed (POST only) |
| 409 | delivery refused (e.g. target session dead) |
Contacts panel HTTP API
Same loopback fence, same JSON conventions:
GET /api/agents-in-the-loop/sessions— live sessions[{id,status}].GET /api/agents-in-the-loop/contacts— every contact + live status.POST …/contacts— create; body{name, sessionId, label?, tags?, note?}.PUT …/contacts— update; body{name, sessionId?, label?, tags?, note?, rename?}.DELETE …/contacts?name=<name>— remove.
Errors: 400 invalid input, 404 unknown contact, 405 wrong method, 409 name/rename collision, 500 persist failure.
Install
dsh plugin --profile web add link:/path/to/agents-in-the-loop # or: npm i dsh-agents-in-the-loop
Then add the plugin row to your profile's cordis composition patch (see
cordis.patch.yml in this repo for the shape) and restart dsh web.
Configuration
One row (all keys optional, defaults shown):
- insert:
- id: dsh-agents-in-the-loop
name: dsh-agents-in-the-loop
config:
enabled: true
sessionMessage:
enabled: true # kill-switch for the session_message tool
contacts:
enabled: true # kill-switch for the contacts tool
# file: '~/.dsh/taskboard-flow-contacts.json' # default store
The contacts store defaults to ~/.dsh/taskboard-flow-contacts.json — the
historical taskboard-flow path, so contacts created before the rename keep
working. Atomic tmp+rename writes; personal state, never shipped. A ~/
prefix in a custom path expands. Contact CRUD via the HTTP API and the
contacts tool write the same store — no config edit or restart needed
for directory changes.
Permissions, external services & failure bounds
Disclosed capability surface (this plugin is intentionally privileged):
- Filesystem: reads and writes exactly ONE file — the contacts store above. No other filesystem access.
- Network: registers loopback HTTP routes on the dsh web server
(
127.0.0.1:9001, custom fence rejects non-loopback peers with 403). The external send-only API accepts inbound loopback POSTs; no outbound network calls are ever made. - Process: no subprocess/shell execution, no child processes.
- Credentials: none read, stored, or transmitted. No secrets.
- External services: none. Zero runtime dependencies, zero lifecycle
scripts (
preinstall/install/postinstall/prepare). - Audit trail: action logs go to
console.log(dsh'sctx.loggeroutput never reaches~/.dsh/dsh-web.log— verified 2026-08-28). - Failure bounds: if the dsh core APIs the plugin injects into change shape, the plugin logs the failure and degrades to inert — it never blocks session composition or other plugins. The external message route refuses (409) rather than resurrecting dead sessions.
Compatibility
- DSH
>=0.1.2(hard floor):resumeIfDeadrelies onagents.resume({ resumeSessionId }), introduced in dsh 0.1.2. Declared inpackage.jsonviaengines+ optionalpeerDependencies, and indsh.compatibility.dshReleases(the DSH-Store catalog matrix):0.1.2,0.1.6-alpha.1,0.1.7-rc.2— each verified compatible. - Node
>=20.
No web UI, no database, no background polling — the plugin is inert until an agent calls a tool or an HTTP route is hit.
Data & cleanup notes
~/.dsh/taskboard-flow-contacts.json— the contacts store (kept).~/.dsh/taskboard-flow-state.json— the old dispatch-state file; the v1.0.0+ plugin never reads it and it can be deleted.
Requirements
- A running dsh web deployment (dsh ≥ 0.1.2).
- No other dependencies; dsh-taskboard is NOT required.
License
MIT
Comments
Loading…
Similar plugins
by GengDaPeng
Cross-session agent messaging for DeepSeek Harness with session discovery, offline delivery, delivery receipts, and sender-session navigation.
★ 6
MIT
JavaScript
Sep 15, 2026
dsh plugin --profile web add dsh-agent-messageby happyren
Cross-session verification, claims and a decision ledger for DeepSeek Harness — so two agent sessions don't repeat, contradict or deadlock each other.
★ 7
MIT
TypeScript
Sep 12, 2026
dsh plugin --profile web add dsh-agent-messagingby XBXyftx
Cross-session messaging plugin for DeepSeek Harness: let user-launched sessions in one workspace message each other and coordinate tasks, write scopes, and conflicts.
★ 0
MIT
TypeScript
Oct 18, 2026
dsh plugin --profile web add dsh-session-messagingby HarryHeYu
Cross-agent session continuity for DeepSeek Harness — continue work from Codex, Claude Code, Grok, ZCode and more with sessionFlow.
★ 2
MIT
TypeScript
Oct 21, 2026
dsh plugin --profile web add dsh-sessionflowby PerryLink
Persistent, cross-session multi-agent team rooms for DeepSeek Harness — members, a message bus, a shared task board, and a shared timeline that survive restarts.
★ 3
↓ 470/wk
Apache-2.0
JavaScript
Oct 7, 2026
dsh plugin --profile web add dsh-team-roomsby TobiichiOrigamii
Cross-session messaging, persistent session team, approval gates and a per-session task board for DeepSeek Harness (DSH).
★ 0
MIT
JavaScript
Oct 22, 2026
dsh plugin --profile web add dsh-crosssession