agpa-dsh-plugin
English | 中文
Connects AGPA (Agent Player Achievements) to DeepSeek Harness (dsh) as a Cordis plugin.
Design goal (maximum value per unit of code): the DSH side reimplements no
achievement logic. Every achievement_* tool is a thin shell that forwards its
arguments verbatim to the published AGPA MCP server (the agpa-mcp bin from
@eiainano/agpa). The AGPA engine, achievement definitions, profiles and the
~/.agent-achievements data store are therefore shared across Claude Code, DSH
and any other harness — so cross_agent achievements fire automatically.
Layers and files
| Layer | File | Purpose |
|---|---|---|
| Entry (Cordis row) | src/index.ts + cordis.patch.yml |
name='agpa', the patch id must match |
| Tool bridge | src/tools.ts |
7 native achievement_* tool shells |
| MCP client | src/agpa-bridge.ts |
Zero-runtime-dependency MCP-over-stdio (Node builtins only), lazily spawns one AGPA process |
| Auto-tracking | src/events.ts |
Subscribes to dsh session/event, normalizes into CC-style payloads (source:'dsh') |
| Ingest subprocess | src/hook-runner.ts |
Serially spawns short-lived agpa-hook auto processes, feeds them stdin |
| Model workflow manual | skills/agpa/SKILL.md |
Teaches the model when to track / poll / announce unlocks |
Directory layout
forDSH/agpa-dsh-plugin/
├─ cordis.patch.yml # insert row: id=agpa name=agpa-dsh-plugin
├─ package.json # dsh.bundle.patch → cordis.patch.yml
├─ tsconfig.json # NodeNext → lib/
├─ src/
│ ├─ index.ts # export name='agpa'; inject=['tools']; apply()
│ ├─ tools.ts # 7 × ctx.tools.register(defineTool(...))
│ ├─ agpa-bridge.ts # minimal MCP client (spawns agpa-mcp)
│ ├─ events.ts # session/event → CC-style normalization (AutoTrackFeed)
│ └─ hook-runner.ts # serially spawns `agpa-hook auto` ingest processes
├─ scripts/
│ ├─ install-skills.mjs # copies skills → $DSH_AGENTS_HOME/skills
│ └─ smoke-bridge.mjs # on-machine smoke test: reads AGPA stats (read-only)
└─ skills/agpa/SKILL.md # AGPA workflow manual
Commands
npm install --legacy-peer-deps # dev dependencies, for typechecking only
npm run typecheck # typecheck against the real @deepseek-ai/dsh-tools types
npm run build # tsc → lib/
npm run smoke:bridge # on-machine check of bridge ↔ AGPA MCP (read-only stats)
npm run install-skills # copy skills/agpa → ~/.agents/skills/agpa
npm pack # produce agpa-dsh-plugin-<version>.tgz
Installing into dsh
Published to npm as agpa-dsh-plugin.
# 1) add to a profile from the registry (a bundle layer; takes effect after restart)
dsh plugin --profile web add agpa-dsh-plugin
# 2) install the model workflow skill: the package ships skills/agpa, just copy it
# (when working from a git clone, use: npm run install-skills)
cp -r ~/.dsh/profiles/web/node_modules/agpa-dsh-plugin/skills/agpa ~/.agents/skills/
# 3) after restarting, check offline that the agpa row appears in the composed tree
dsh --profile web --dump-config | grep -A3 agpa
Local development (when editing the source):
npm run build && npm pack # prepack runs build automatically
dsh plugin --profile web add ./agpa-dsh-plugin-<version>.tgz
If the row appears but the tools do not, it is a code problem (check the dsh
logs); if the row never appears, it is a composition problem (the name did not
resolve, or files dropped cordis.patch.yml).
Environment variables
| Variable | Purpose |
|---|---|
AGPA_MCP_CMD |
Override the AGPA MCP launch command (space-separated). Defaults to npx -y -p @eiainano/agpa@0.1.10 agpa-mcp. For local development, point it at tsx /path/to/agpa/src/main.ts |
AGPA_AUTOTRACK=1 |
Enable automatic event tracking: one agpa-hook auto ingest process per normalized event |
AGPA_HOOK_CMD |
Override the ingest command (space-separated). Defaults to npx -y -p @eiainano/agpa@0.1.10 agpa-hook auto (works out of the box); for local development point it at tsx /path/to/agpa/src/cli/hook.ts auto |
AGPA_DEBUG=1 |
Print AGPA MCP stderr plus every received event type ([agpa][ev]) and normalized payload ([agpa][autotrack]) |
DSH_AGENTS_HOME |
Target skills root for install-skills (defaults to ~/.agents) |
The bridge and ingest subprocesses always inject AGPA_TOOL_SOURCE=dsh, so
events are tagged with the correct tool source (stored as tool_source:"dsh",
coexisting with Claude Code's claude-code in the same store).
Status / roadmap
- Phase 1 (this repo): 7 tool shells + MCP bridge (verified against the real AGPA engine — returns stats with
tool_source=dsh) + SKILL- 2026-09-09: verified end-to-end on a real dsh 0.1.2-rc.1: installed into both profiles with
dsh plugin --profile web|headless add <tgz>; a real headless task successfully calledachievement_stats, and the bridge returned real data from the shared store (75/212, level 6, 10,592 XP).
- 2026-09-09: verified end-to-end on a real dsh 0.1.2-rc.1: installed into both profiles with
- Phase 2 (this repo, 0.1.11 on npm): automatic event tracking, verified end-to-end
on a real machine and working out of the box.
events.tssubscribes to dshsession/eventand normalizestool/call+tool/result→ PostToolUse(Failure), anduser/message(source.kind==='user') → UserPromptSubmit, into CC-style payloads (source:'dsh'); each payload spawns oneagpa-hook autoingest process (hook-runner.ts).- After a real headless run (Write/Bash),
~/.agent-achievements/profiles/neo/event.loggained rows withtool_source:"dsh"—file.create/file.write/command.run/tool.completeand friends. - Key gotcha found on the real machine:
tool/result.datahas no top-levelcallId(onlytool/calldoes); the call id needed for the${turn}:${step}:${callId}correlation key lives atresult.data.message.source.callId(mirrored atcontent[].toolCallId). Event names are always taken from the pinned dsh 0.1.2-rc.1 (session/eventalso broadcasts noise types such aspermission/presetandassistant/chunk, all of which are ignored). - AGPA 0.1.10 is published with the
agpa-hookbin, so the plugin's default ingest commandnpx -y -p @eiainano/agpa@0.1.10 agpa-hook autoworks out of the box (verified withAGPA_AUTOTRACK=1alone). For local development you can still pointAGPA_HOOK_CMDat a checkout'stsx src/cli/hook.ts auto. - The ingest subprocess is spawned
detached: a short-lived headless process exiting will not SIGTERM an in-flight ingest (the firstnpxrun has a cold-cache download and can be slow; detaching guarantees no events are lost — long-lived web sessions were never affected).
- Phase 3: an optional
dsh.clientXP/achievement widget, or simply reusing the AGPA dashboard (:3867).
Known caveats
- dsh cannot start on this machine (fixed 2026-09-09, re-fixed 2026-09-15): the managed Node
runtime replaces its versioned prefix on upgrade (
versions/22.22.2-2→versions/22.22.2-3), wiping any globals installed inside it. So dsh and pnpm no longer live in that prefix — they live in the stable~/.local/share/dsh-runtime.~/.local/bin/dshreads the Node version dynamically from~/.workbuddy/binaries/node/versions/currentand puts$DSH_HOME/bin(pnpm) plus that Node's bin on PATH. When installing a global package into such a prefix you must invoke npm explicitly as"$NODE" "$PREFIX/lib/node_modules/npm/bin/npm-cli.js" i -g <pkg> --prefix "$PREFIX", otherwise the npm wrapper's shebang resolves to whatevernodeis on PATH. - Runtime resolution: an out-of-tree plugin's bare imports of
@deepseek-ai/dsh-tools(a value import) and@deepseek-ai/cordis(type-only) are mapped by the dsh host into the in-box tree, and both resolve on 0.1.2-rc.1 (this plugin'sdefineToolvalue import is verified by a real run). - DSH is a developer preview:
cordis.patch.yml, thedshfield and event names can all change between versions.package.jsononly declares@deepseek-ai/cordisas a peer (types and closure injection are provided by dsh). - Event names are taken from the pinned version: the types this plugin subscribes to were
verified on the pinned 0.1.2-rc.1 (see Phase 2). If dsh is upgraded, check that version's
SessionEventMapfirst — do not copy the master docs. - The tool schemas use JSON-schema-style literals; AGPA does not currently do strict validation of numeric ranges, and the descriptions state the allowed range.
- If it will not install into a profile, first confirm that
npm pack'sfilesreally includescordis.patch.yml.
No comments yet. Be the first to write one.