BotHarness is a plugin layer on top of DSH (DeepSeek Harness) that gives agents a persistent identity: PersonaBot — a persona with memory that spans sessions and can work concurrently. DeepSeekBot is its first app (sidebar roster + delegation + IM integration). The DSH core is not forked; IM channels come from the dsh-im base.
Status: M1 implemented (PR #13) · M2 memory MVP · M3 roster & delegation · M5 IM adapter · updated 2026-09-17
1 · System context
Two entry points (DSH Web roster/delegation, Feishu group IM), one PersonaBot brain.
2 · Modules & packages
| Module | Responsibility | Status |
|---|---|---|
plugin.ts |
Plugin entry: settings namespace + provide('botharness'); assembled by createCore() |
M1 ✅ |
bots/registry.ts |
PersonaBot lifecycle + atomic persistence; remove keeps memory by default, only purge clears it |
M1 ✅ |
state/bot-state.ts |
Five session states reported → PersonaBot aggregation; aggregate-changed / session-changed / session-removed |
M1 ✅ |
im/* |
Read-only dsh-im store (v1/v2/v3 compatible) + workspace→BotIdentity (IM binding helper) | M1 ✅ (M5 wiring) |
| Memory (M2) | front-matter, directory-tree injection, memory_* tools, visibility, git versioning |
M2 |
| roster client | main panel + sidebar.panellist; roster tree / detail / create; @delegation |
M3 |
3 · Boot & service exposure
Everything goes through the Cordis service bus — no file polling.
4 · Creating a PersonaBot (data flow)
Validate → duplicate check (against valid records, not stuck on tombstone directories) → atomic write → memory directory.
5 · IM binding resolution (helper today, wired in M5)
6 · State machine & events
| Event | Trigger | Consumer |
|---|---|---|
aggregate-changed |
aggregate state changed | roster / avatars (M3+) |
session-changed |
any session state change (even if aggregate holds) | session detail |
session-removed |
session ended / cleaned up | tree refresh |
7 · On-disk data
Ours (written by the registry):
$DSH_HOME/botharness/bots/<slug>/
├── bot.json # machine metadata (atomic write)
└── memory/ # default memory dir; absolute path configurable
# M2: PERSONA.md / MEMORY.md / topic filesdsh-im’s (read-only):
$DSH_HOME/integrations/dsh-feishu/
├── config.json # bots[]
├── workspaces.json # v3: workspaces/aliases/overrides
└── bots/<botId>/state.json # conversation binding (M5)8 · Communication & boundaries
| Channel | Direction | Notes |
|---|---|---|
Cordis service provide/inject |
core → client/im/third-party | the botharness service; no global singleton |
Tracker subscription states.on() |
core → client | in-process events, not polling |
DSH event bus ctx.on |
DSH/dsh-im → core | M3 subscribes to agent/* to drive state |
| Feishu / Lark | dsh-im ↔ open platform | outbound long connection; no public ingress (webhook exception, see PRD) |
| dsh-im disk | read-only | only through the single im/ module; no fork / no patch |
| Secrets | — | only in the DSH credentials service; zero plaintext in the repo |
9 · How to maintain
- This is a living architecture document: when modules, data flows, or boundaries change structurally, update this file (mermaid sources are inlined).
- This page is synced to the docs site (
apps/docs) byscripts/sync-docs.mjs; site addresshttps://botharness.ai/dev/architecture. - Companions: platform spec docs/botharness.md · app PRD PRD.md · glossary CONTEXT.md · decisions docs/adr/.