dsh-a2a

Agent2Agent (A2A) Protocol v1.0.1 dual-end plugin for DeepSeek Harness — English · 中文

@hanphone/dsh-a2a is an independent, open-source A2A plugin that turns a DeepSeek Harness profile into a multi-faced agent-to-agent citizen: it can simultaneously serve multiple inbound A2A servers, each bound to its own agent preset with its own endpoint, AgentCard, preset-derived skills and auth, and connect to multiple outbound A2A servers, each with its own preset, whose remote skills appear as model tools. Every server instance is created, started, stopped, edited and removed entirely from the GUI — no config-file editing.

Architecture and design decisions: docs/architecture.md. Tracking DeepSeek Harness releases: docs/harness-compatibility.md.

Features

Installation

From npm

dsh plugin --profile web add @hanphone/dsh-a2a

Any profile name works (web, custom profiles, headless etc.):

dsh plugin --profile <name> add @hanphone/dsh-a2a

From a local build

cd dsh-a2a
pnpm build
npm pack
dsh plugin --profile <name> add <path-to>/hanphone-dsh-a2a-<version>.tgz

Quick start

  1. Install — dsh plugin --profile web add @hanphone/dsh-a2a.
  2. Restart the GUI — the browser half is scanned at host startup, so restart once after installing (pnpm dsh web or your profile launcher).
  3. Open Settings → A2A 连接 — create your first inbound server (pick a preset — its skills are derived automatically — optionally set an auth env). It is enabled immediately and publishes its own endpoint and AgentCard.

Each inbound server listens on the profile’s webServer:

# the created instance's AgentCard (see the GUI for the exact id)
curl http://127.0.0.1:3080/a2a/<id>/agent-card.json

Send a task to an instance (its declared chat skill):

curl -X POST http://127.0.0.1:3080/a2a/<id> \
  -H "content-type: application/json" \
  -d '{"jsonrpc":"2.0","id":"1","method":"SendMessage","params":{"message":{"role":"user","parts":[{"text":"hello"}],"metadata":{"skill":"chat"}}}}'

GUI dashboard

The browser half registers an A2A 连接 page under Settings, organized in three tabs. From it you can, without touching any file:

All dashboard traffic goes through the loopback-only /a2a/api route — remote peers can never drive it.

Configuration

The GUI covers instance management day-to-day. The plugin Config only has host-level defaults, set through the profile’s user patch layer ($DSH_HOME/profiles/<name>/cordis.patch.yml) if you want to override them:

- id: a2a
  config:
    baseUrl: http://127.0.0.1:<port>   # omit to derive from the webServer address
    subagentProvider: in-process
    defaultTimeoutMs: 60000            # default outbound connection timeout

Instances are not configured via patches — they are created in the GUI and persisted in the a2a domain (inbound_servers / outbound_servers tables).

Required host services

Base-backed profiles mount them all: webServer (@deepseek-ai/dsh-host-webserver), the storage stack (@deepseek-ai/dsh-storage + @deepseek-ai/dsh-storage-domain), the tools registry (@deepseek-ai/dsh-tools), an agent loop (@deepseek-ai/dsh-agent + @deepseek-ai/dsh-agent-loop), and agent presets (@deepseek-ai/dsh-agent-presets; the preset pickers and preset-bound session composition need it). The subagent executor additionally needs @deepseek-ai/dsh-subagent.

Storage backend

The task and instance stores live in the a2a storage domain. The base composition routes storage through the json backend; to use SQLite, route the domain and add the backend in the same patch layer:

- id: storage-domain
  config:
    backend: json
    routes:
      a2a: sqlite
- insert:
    - id: storage-sqlite
      name: '@deepseek-ai/dsh-storage-sqlite'
      config:
        path: /absolute/path/to/a2a.sqlite

CLI

A /a2a chat command mirrors the dashboard (a text backup to the GUI):

a2a status | presets | peers |
    inbound list|create|remove|enable|disable |
    outbound list|create|remove|enable|disable|refresh |
    tasks | task get|cancel <id> |
    sessions | session cancel|close <contextId> | help

How it works

See docs/architecture.md for the full design.

Directory structure

src/
  api.ts                  # loopback dashboard API (/a2a/api, /a2a/api/presets)
  index.ts                # Cordis plugin entry (apply)
  protocol.ts             # A2A v1.0.1 protocol constants + types
  jsonrpc.ts              # JSON-RPC framing
  servers/                # multi-instance managers
    inbound-manager.ts    #   inbound server instances (CRUD, routes, lifecycle)
    outbound-manager.ts   #   outbound connection instances (CRUD, tools)
  server/                 # single-instance internals: store, card, a2a-server,
                          #   routes, executors, inbound-registry, session-registry
  outbound/               # outbound internals: A2AClient, registry, tools
  client/                 # browser half: settings dashboard (React)
  service.ts              # ctx.a2a service facade
  commands.ts             # /a2a chat command
tests/
  unit/                   # protocol, framing, card, store, registry, server,
                          #   client, api, inbound-registry, identity
  composition/            # apply() on a real Cordis Context with stub host services
  integration/            # real agent loop + JSONL persistence, mock LLM
  types/                  # compile-time mirror check against real DSH types
scripts/
  e2e-a2a.mjs             # hermetic two-instance A2A smoke
docs/                     # public manual + architecture/compatibility/testing
cordis.patch.yml          # bundle patch (mounts the plugin; instances are GUI-managed)

Documentation

The public manual describes the current behavior in depth:

Deep dives: Architecture, Harness compatibility, and Testing.

Development

The plugin typechecks against the harness source graph through project references; a harness checkout with its built host aggregate is required.

pnpm typecheck   # host + client + compile-time mirror check
pnpm test        # vitest run (unit + composition + real persistence)
pnpm build       # tsc + tsdown → lib/index.js (host) + lib/client.js (browser)
pnpm test:e2e    # hermetic two-instance A2A smoke against the built artifact
pnpm test:all    # typecheck + test + build + e2e

Stub tests alone cannot see host-runtime contract drift. The layers, what each catches, and the e2e flow are in docs/testing.md.

Acknowledgements

This plugin was inspired by and developed alongside ryubyte/dsh-a2a, an earlier A2A plugin for DeepSeek Harness. That project’s design — dual-end scope, AgentCard advertisement, and the settings-dashboard pattern — set the direction this implementation follows. Our protocol layer, task store, and executor seam are independent implementations; the GUI management model owes a direct debt to ryubyte’s connection dashboard.

License

MIT