# DSH Agency Market
**An expert market inside DeepSeek Harness: 273 specialists, one-click install, summonable anytime**
[简体中文](README.md) · [License](LICENSE)
[](LICENSE)
[](#catalog-data)
[](#installation)
[](#development)
> dsh-agency-market is a community-maintained DeepSeek Harness (DSH) plugin, not an official DeepSeek AI product.
> All 273 bundled personas are imported from the open-source [agency-agents](https://github.com/msitarzewski/agency-agents) project (MIT).
## What it is
An "expert" market living inside DSH Web: browse expert cards and install any expert as a **user-level agent preset** with one click. Installed experts have **two usage modes** — start a new session on that preset and chat directly, or have the model summon one in **any existing session** (via the `market_experts` / `summon_market_expert` model tools) and switch experts whenever you like.
```
Settings → Expert Market → pick a card → Install ─┬→ new session on the preset → chat with the expert
├→ summon in any session (market_experts / summon_market_expert)
└→ composer "Summon expert" button / @ menu → drafts the instruction (never auto-sends)
```
The parent session keeps the task context, the judgment, and the final answer. Summoned expert children provide a specialist perspective only — and they cannot summon further experts (recursion guard).
## Features
- **Market page** (Settings → Expert Market): a registry-style directory — full-width search, division chips, second-level tabs for oversized divisions, an installed filter, inline remove confirmation, skeleton loading; bilingual cards with cross-language search.
- **One-click install**: installation follows the roster-sanctioned preset-authoring path (copy the base → rewrite metadata → swap the persona → mount-validate). Shipped presets are never written; uninstall removes user-level entries only.
- **Mid-session summoning**: `market_experts()` lists summonable experts; `summon_market_expert(expert, task)` delegates one self-contained task — a one-shot subagent runs with the expert's full persona and returns the answer, so different tasks in one conversation can use different experts.
- **Three summon entry points**: the model calls the tools itself, the composer "Summon expert" button, or the `@` menu; the latter two only write a draft — **nothing is auto-sent**.
- **273 experts × 18 divisions**: Academic, Design, Engineering, Finance, Game Development, GIS, Healthcare, Marketing, Paid Media, Product, Project Management, Research, Sales, Security, Spatial Computing, Specialized, Support, Testing.
- **No build, no runtime dependencies, no install scripts**: plain ESM JavaScript + JSON data, with a single offline smoke command.
## Prerequisites
- A working DeepSeek Harness Web installation with the `dsh` CLI available (Node.js 22+; if `dsh` is not on PATH, use `npx --yes @deepseek-ai/dsh`).
- Examples use the `web` profile; replace it with your target profile.
## Installation
### Option 1: ask another agent to install it
This plugin runs inside DeepSeek Harness Web. Paste the sentence below into DSH (or Codex, WorkBuddy, or any agent that can install npm packages) and let it install the plugin into your local `web` profile:
```text
Install the DSH plugin from source at https://github.com/pbwheel/dsh-agency-market into my local web profile: clone the repo, run dsh plugin --profile web add . from that directory, then run dsh --profile web --dump-config, confirm dsh-agency-market is mounted, and remind me to restart DSH Web and hard-refresh the browser.
```
### Option 2: install from source (debugging or unpublished changes)
```sh
git clone https://github.com/pbwheel/dsh-agency-market.git
cd dsh-agency-market
dsh plugin --profile web add .
dsh --profile web --dump-config
```
`dsh plugin ... add` reads the package metadata and `cordis.patch.yml`, wiring the host half into the composition layer (`dsh.profile.bundles` is registered automatically — no manual edits). The config dump should list `dsh-agency-market`.
Then **restart `dsh web`** (the bundles list is only read at startup) and **hard-refresh the browser**. The plugin has no build step, no runtime dependencies, and no install scripts — the cloned directory is the plugin source; do not copy `src` alone.
### Troubleshooting
- Market page 404 / only the composer button misbehaves: confirm `~/.dsh/profiles/web/package.json` has `"dsh-agency-market"` in its `dsh.profile.bundles` array, then **restart `dsh web`** (bundles load at startup only); the client half is auto-discovered by the loader — refreshing the page is enough.
- No "Expert Market" group in the `@` menu: `dsh.client.inject` composition changes in `package.json` are scanned by the loader **at startup**; restart `dsh web` (the button and market page are unaffected — the trigger degrades silently).
## Usage
### 1. Pick and install experts
Open **Settings → Expert Market**: browse by division or search (Chinese and English keywords both hit), then install a card. Installed rows get a ✓ stamp; removal confirms inline.
### 2. Start a session on the expert (preset mode)
When creating a session, pick the expert in the preset selector (`expert-`, shown with the expert's name and description) and start chatting with the expert persona.
### 3. Summon in any session (tool mode)
In any live session, just ask the model to summon an expert — or use the composer's "Summon expert" button / type `@` and pick one. The button and `@` menu only write the instruction into the draft — **it is never auto-sent**:
```text
Summon the "Code Reviewer" expert (engineering-code-reviewer) for this task:
Review the changes in the current workspace and list reproducible issues by severity.
```
The two model-side tools:
- **`market_experts(category?)`** — lists the currently summonable (= installed) experts: id, name, description, category. Call it first when you don't know the exact id.
- **`summon_market_expert(expert, task)`** — delegates one task to an expert; `expert` accepts an id (preferred) or a display name (Chinese names work; fuzzy matching with a candidate list on ambiguity), `task` must be complete and self-contained, up to 8,000 Unicode code points. The call waits for the expert to finish and returns its answer.
Summoning a not-installed expert fails with a message pointing to Settings → Expert Market.
### What an expert is
An expert = a user-level agent preset (`expert-`, installed under `${DSH_HOME:-~/.dsh}/.agent-presets/`):
| Card field | Where it lands |
|---|---|
| `name` / `description` | the preset's `preset.yml` (name & blurb in the preset selector; the catalog's base/English fields are written) |
| `persona` | the `text` of the `dsh-persona` row in the composition (the expert's system prompt) |
| `base` | the base preset copied from (default `standard`, full toolchain) |
| `avatar` / `tags` / `category` | market-page display fields (catalog data only) |
| localized `name` / `description` / division labels | **never written into the expert files**: they live in the locale overlay `data/i18n.d/zh.json`, display-only (see [i18n](#localization-locale-overlay)) |
The install flow (host side, entirely through the roster service):
1. `agentPresets.copy(base, 'expert-')` — copy the base preset into the user root;
2. rewrite the copy's `preset.yml` (name/blurb);
3. swap the copy's persona row `text` for the expert persona via an anchored replacement (an anchor miss degrades to the base persona with a UI warning — a broken preset is never installed);
4. `agentPresets.standingKeyFor(id)` performs a real mount validation — success requires passing it.
Shipped presets are **never written**; uninstall removes only entries the roster reports as `trust: "user"`.
### Behavior and safety notes
- Mutating routes accept **same-origin POSTs only**, bodies are capped at 4 KiB, and one install/uninstall runs at a time; an install is a preset copy plus two file edits — **no build or install scripts are ever executed**.
- Recursion guard: the child's `toolFilter` denies both summon tool names; the system-prompt section returns an empty string in child sessions (a `parentSession` in the header), so summoned experts never learn to summon.
- A child run ending with `stopReason !== 'completed'` raises an error (with diagnostics and partial output); `run.dispose()` always runs in `finally`.
- `{{model}}` / `{{cwd}}` / `{{provider}}` in personas are registered host prompt variables — they pass through verbatim without sanitization; the importer splits any other complete `{{…}}` group (`{{` → `{ {`) because unregistered variables make child startup fail, so they are treated as literal text.
- Every side effect (routes, tools, prompt section, slots, styles) is collected into disposers and fully reverted on plugin unload.
## Catalog data
The catalog has three layers, merged file by file with per-file isolation: a corrupt or invalid file (bad id / missing persona / duplicate id) is skipped with the reason surfaced in the `/api/state` `warnings[]` — the rest of the catalog keeps working. A corrupt head file degrades the same way; only a fully unusable catalog throws.
| File | Purpose |
|---|---|
| [`data/experts.json`](data/experts.json) | head file: `version` + `categories` (18 divisions) |
| [`data/experts.d/`](data/experts.d/) | one JSON per expert, in division subdirectories (273 × 18) |
| [`data/subgroups.json`](data/subgroups.json) | second-level grouping overlay for oversized divisions |
| [`data/i18n.d/`](data/i18n.d/) | locale overlay: display translations (see [i18n](#localization-locale-overlay)) |
One complete expert definition:
```jsonc
// data/experts.d/engineering/engineering-code-reviewer.json
{
"id": "engineering-code-reviewer", // [a-z0-9-]; installed preset id is expert-engineering-code-reviewer
"name": "Code Reviewer", // card title + preset.yml name
"avatar": "👁️", // ← the agency-agents frontmatter emoji
"category": "engineering", // head categories[].id (= division)
"tags": ["Engineering", "agency-agents"],
"description": "card one-liner + preset.yml description",
"base": "standard", // base preset, defaults to standard
"persona": "# Code Reviewer Agent\n\nYou are **Code Reviewer**…" // = the md body
}
```
**Second-level groups (subgroups)**: engineering alone bundles 59 experts — a single tab row doesn't scale. Subgroups are deliberately a separate overlay rather than fields in the expert files: upstream agency-agents carries no grouping info, a `--replace` re-import wipes and rewrites `experts.d/`, while the overlay survives untouched, exactly like the i18n layer:
```jsonc
// data/subgroups.json
{
"categories": {
"engineering": [
{ "id": "ai-ml", "label": "AI & Machine Learning", // label is the base (English) field
"experts": ["engineering-ai-engineer", /* … */] } // array order = sub-tab order
// …frontend-client / backend-architecture / data-database /
// ops-reliability / cms-platform / practices-quality — 7 groups covering all 59
]
}
}
```
Loading degrades per entry in two steps: shape validation first (bad ids / empty labels / malformed expert lists / duplicate groups skip with warnings), then a reconciliation against the catalog (stale ids, cross-category listings, experts claimed by two groups, emptied groups each skip with warnings). Four oversized divisions are currently covered: engineering (59 × 7 groups), specialized (58 × 6), marketing (36 × 4), game-development (21 × 6); the rest stay single-level (≤13 experts each). Adding subgroups to a new division is data-only — copy the pattern plus the zh overlay, zero code changes.
**Re-importing**: after agency-agents updates upstream, re-run [`scripts/import-agency-agents.mjs`](scripts/import-agency-agents.mjs):
```sh
node scripts/import-agency-agents.mjs --replace # --repo sets the checkout, --dry-run validates only
```
## Localization (locale overlay)
Static UI strings go through the client locale service (zh/en dictionaries). Expert **names/descriptions/division and subgroup labels** localize through the separate locale overlay `data/i18n.d/`: one `.json` per language, pure display metadata layered on top of the catalog — the expert files in `experts.d/` (personas included) are never modified, and `--replace` re-imports leave the overlay untouched:
```jsonc
// data/i18n.d/zh.json (no en file needed: the catalog base fields ARE English; a missing overlay falls back)
{
"categories": { "engineering": "工程", /* …18 divisions */ },
"subcategories": { "engineering/ai-ml": "AI 与机器学习", /* …one per subgroups.json group */ },
"experts": {
"engineering-frontend-developer": {
"name": "前端开发工程师",
"description": "专注现代 Web 技术、React/Vue/Angular 框架、UI 实现与性能优化的前端专家"
}
// …273 experts
}
}
```
How it flows: the market page sends the UI language as `?locale=zh|en` (refetching on language switch); the host overlays field by field, falling back to base fields; the summon side — `market_experts` output and expert name resolution (pasting a Chinese card title resolves too) — consumes the same overlay. Market search is **cross-language**: the API attaches the other language's keywords to every expert, so English keywords hit on a zh interface. The smoke run enforces that zh.json covers every catalog expert — run it after a re-import to catch missing translations. Use `scripts/merge-zh-overlay.mjs` to merge translation fragments; edit zh.json directly for small fixes.
## Architecture
```
Browser DSH host process
─────── ───────────────
client/client.js src/index.js (host entry, dual inject)
settings.section "Expert Market" ├─ inject webServer+agentPresets:
directory rows/groups/search ─same-origin fetch→ │ src/routes.js /dsh-agency-market/api/*
│ src/experts.js catalog + install/uninstall/state
conversation.input.left ├─ inject tools+subagents+systemPrompt+agentPresets:
"Summon expert" button ─────────────→ │ src/summon.js market_experts / summon_market_expert
(pick → setDraft) │ + prompt section (empty in children)
inputTriggers "@" source ├─ agentPresets service (copy / remove / standingKeyFor)
(candidates → /api/state) └─ subagents service (spawn provider, per-run persona)
data/experts.json + data/experts.d/ ~/.dsh/.agent-presets/expert-/
data/subgroups.json + data/i18n.d/.json (display translations)
```
- **Host half** (`src/`): plain ESM JavaScript, no dependencies, no build. Three routes: `GET /api/state` (catalog + installed flags + warnings), `POST /api/install`, `POST /api/uninstall`. `src/summon.js` registers the two model tools plus one prompt section; tool names are deliberately namespaced under `market` to avoid colliding with other expert plugins (e.g. dsh-agency-agents' `list_experts` / `summon_expert`) in the same profile. The two inject groups are independent: the market page never depends on the tool seam, and summoning never depends on the web server.
- **Client half** (`client/client.js`): a hand-written harness client bundle (`window.__ModuleLoader__.load({ id, factory })`, the only external is `react`), registering two slot entries — `settings.section` renders the market page, `conversation.input.left` renders the "Summon expert" button — plus an `@` trigger source when the `inputTriggers` service is composed (skipped silently otherwise). Styling rides theme tokens (`--dsw-alias-*` etc.) with fallbacks, adapts to light/dark themes, and covers focus rings and `prefers-reduced-motion`; `