# dsh-flat-teams **English** | [中文](README.md) A flat-teams plugin for DeepSeek Harness (DSH): multiple independent agent windows (top-level sessions) form a **leaderless flat team**, dispatch structured tasks to each other across windows, while a built-in read-only "recorder" service keeps the books — so the user can ask for real progress from any member window. ## Features - **Flat teams**: `team_create` (optionally with member specs — new member windows are created programmatically, inheriting the current workspace), existing windows self-register via `team_join`, plus `team_leave` and `team_roster` (members / areas / online status). No captain: any member can dispatch tasks to any other member. - **Structured task dispatch**: `task_dispatch` targets a member by name or by area. Task state machine: `pending → delivered → claimed → in_progress → completed | failed | cancelled` (plus `timeout`), every transition mints a fresh attemptId and appends to the event stream. - **Three-stage delivery, no task loss**: tasks are persisted to disk and to the recipient's mailbox (`/flat-teams//inbox/`) before a best-effort wake (followup when online; cold-resume via `agents.resume` then followup when offline). If the wake fails, the task stays `delivered` in the mailbox. - **Recorder**: a pure service — no window, no admin rights. It subscribes to the event stream and maintains `progress.json` (per-task status and result summaries, per-member in-flight tasks and completion counts, team timeline). It is only a cached projection and can be fully rebuilt from `events.jsonl` at any time. - **User queries**: `team_status` returns the recorder's structured progress. A bundled prompt protocol teaches the model: dispatches must be self-contained, receivers claim before working, results must be reported back, and progress questions are answered from recorder data — never fabricated. - **Web dashboard**: after installing into a web profile, a "team dashboard" badge appears at the top-right of the browser — a team list (member count / online / in-flight / dormant marker) drilling into a detail view (roster / task board / progress timeline). The dashboard exposes **user-only admin actions**: disband team and kick member (both with two-step confirmation, via `POST /plugins/flat-teams/teams//disband|kick`; member agents do not have these capabilities). Data comes from `GET /plugins/flat-teams/state` (5s polling). ## Install ```sh # npm (recommended, prebuilt artifacts, no build approval needed) dsh plugin --profile add dsh-flat-teams # or from git (lib/ is committed, also no build approval) dsh plugin --profile add github:whateverboy2333/dsh-flat-teams # local path (development) dsh plugin --profile add /absolute/path/to/dsh-flat-teams ``` **Restart the profile** after installing. Verify: ```sh dsh --profile --dump-config # the composition tree should show a flat-teams row (# == dsh-flat-teams layer) ``` Uninstall: `dsh plugin --profile remove dsh-flat-teams`. ## Compatibility - DSH `0.1.0-rc.6` (`@deepseek-ai/*` on the same release train; `@deepseek-ai/cordis ^4.0.1`) - Node `^22.19` or `>=24` - The plugin ships a runtime version guard: on an incompatible DSH version it **refuses to load** with a loud error instead of failing silently ## Tool Reference | Tool | Purpose | |---|---| | `team_create` | Create a team; optional member specs spawn member windows (name/area/persona); the current window becomes the first member | | `team_join` / `team_leave` / `team_roster` | Self-register / leave / list roster (with live online status) | | `task_dispatch` | Dispatch a task: `to` is a member name or area (multiple area candidates → error asking for an explicit name); persist + mailbox + best-effort wake | | `task_claim` / `task_complete` / `task_fail` | Receiver-driven states: claim (delivered→claimed→in_progress) / complete with result / fail with reason | | `task_list` / `task_status` / `task_cancel` | Query tasks (filter by status/member) / single-task detail / cancel (**dispatcher only**, optional attemptId generation check) | | `team_status` | Recorder's structured progress (task states & summaries, per-member in-flight, team timeline) | | `flat_teams_ping` | Plugin self-check | All permissions are enforced in code: caller identity is resolved from the roster via the calling session, cancel is dispatcher-only, claim/complete are restricted to the designated recipient member. ## Usage (natural language) Create a team with members: > Create a flat team "Website Revamp" with two members: Xiaotest (testing) and Awen (docs). Dispatch work (no tool names needed): > Have Xiaotest cover the edge cases of the login page by Friday. The receiving window gets a `[task from … #task-…]` message and follows the prompt protocol: claim first, report back when done. Ask for progress (in any member window): > How is the project going? Is anything stuck? The agent calls `team_status` and answers from recorder data. ## Known Limitations (MVP) 1. **Same process only**: all member windows must live in one DSH process (cross-workspace within the same process **is** supported — team state is shared globally at the DSH_HOME level); cross-process / cross-machine transport is planned for v0.2 (file-registry based). 2. **Member windows do not auto-revive across restarts**: programmatically created member windows are held by the plugin fiber and are destroyed on plugin unload or process exit; member identity and task data persist under `/flat-teams/`, but the windows themselves must be recreated/rejoined. 3. **No automatic redelivery for offline targets**: if cold-resume fails, the task stays in the mailbox (visible as `delivered`), but nothing re-pushes it automatically — it needs a new touch once the member is back (v0.2). 4. **Cancel does not retract a delivered turn**: `task_cancel` only transitions the state machine; a message already delivered to the receiver's window is not withdrawn. 5. **Areas are not exclusive**: area is routing metadata only; multiple members may share one area, in which case dispatch must name the member explicitly. Also: concurrent writes to `/flat-teams/` from multiple processes are not guarded (declared MVP limitation, to be solved in v0.2). **Migration note (within v0.1.0)**: early internal builds (pre-open-source) stored team state under `/.flat-teams/` — that layout is deprecated: old directories are not migrated automatically and are no longer read or written; they can be deleted manually. ## State Directory Team state is workspace-independent and lives at the DSH_HOME level (a window in any workspace can join the same team): ``` /flat-teams// ├── team.json # roster (disk is the source of truth) ├── tasks/.json # task records (state machine + attemptId) ├── events.jsonl # event stream (the only event truth, replayable) ├── inbox/.jsonl # delivery mailboxes (persistent copies) └── progress.json # recorder cache projection (fully rebuildable from events.jsonl) ``` ## Development ```sh pnpm install pnpm build # tsc (host + client) → tsdown → lib/ ``` > Note: the smoke verification scripts (`scripts/`) are not shipped in this repository; `pnpm verify` is only available to contributors with a full development copy. ## License MIT