Back to home@Wang-Lin-Chang

dsh-vap

VAP - Verifiable Agent Protocol: a tested trust machine for agent-to-agent messages (zero third-party dependencies, 217 tests, full experiment ledger)

Stars
0
Language
JavaScript
Created
Aug 20, 2026
Updated
Aug 20, 2026
GitHub repo

Introduction

VAP — Verifiable Agent Protocol

A verifiable agent-interaction protocol — a tested trust machine. Any Node.js agent that installs this plugin and joins the same trust domain (a shared workspace) can claim tasks, submit ≤100-char reports, be adjudicated by a three-gate verification gate, and be adopted on crash. Zero third-party dependencies (only node: built-ins).

中文版见 README.zh-CN.md。Specification: vap-spec.md.

Capability overview

Every capability ships with an experiment device number and a control group.

LayerCapabilityDevice(s)
IdentityEd25519 envelope + nonce replay protection + behavior-history scarcityE01/E02/E03, P0-1..P0-4, P05
TrustThree-gate verification + credential-chain bootstrap + distributed 2/3 QCE02, P3, D1-D5
OrderingLockstep QC-chain consensus (total order / fork & double-spend prevention)T1-T6
TransportFile / HTTP gateway / UDP P2P / relay NAT traversalP2-1..P2-4, R1-R4
GovernanceDynamic membership + laws-on-chain + slash auto-expulsionD1-D5

Quick start — end-to-end in about two minutes

Requirement: Node.js 22+ (package.json declares engines.node >= 22). No third-party dependencies, nothing to install.

# 1. Get the code
git clone https://github.com/Wang-Lin-Chang/dsh-vap.git   # or download the ZIP and unpack it
cd dsh-vap

# 2. Check your Node.js version (22 or newer)
node --version

# 3. Terminal A — start the HTTP gateway on a fixed port
node bin/vap-gateway.mjs --port 3081

# 4. Terminal B — send your first verifiable envelope
node bin/vap-send.mjs --to brain --summary "hello vap" --gateway http://127.0.0.1:3081

# 5. Read the verdict — vap-send prints it; the gateway counter confirms it
curl http://127.0.0.1:3081/health

# 6. Run the tests
node --test

Step 3 tells you what to do next instead of leaving you at a bare prompt:

✓ vap-gateway listening http=127.0.0.1:3081
  health    : http://127.0.0.1:3081/health
  envelopes : POST http://127.0.0.1:3081/envelopes (GET 同址拉取未投递信封)
  next      : send envelopes via POST /envelopes — node bin/vap-send.mjs --to brain --summary "hello vap" --gateway http://127.0.0.1:3081

Step 4 prints the verdict in plain words (exit code 0):

✓ 已投递 envelopeId=evt-a2d6aa6ab72547f3 网关已接收(HTTP 202 Accepted)
  boundary : L0(未给 --evidence,按诚实边界降级;加 --evidence '{"devices":["E01"]}' 才能声明 L2a)
  本地三闸 : pass(身份 ✓ / 军法 ✓ / 诚实边界 ✓)
  裁决     : 网关验签通过并落盘 inbox-http/(军法与诚实边界由下游裁决)
  next     : 看网关计数 → curl http://127.0.0.1:3081/health   (envelopesIn 会 +1)

Step 5 shows the gateway's own count: {"ok":true,"envelopesIn":1,"envelopesOut":0,"peers":0,"relayed":0}.

Reading a verdict

vap-send saysHTTPWhat it meansExit code
✓ 已投递 … 网关已接收202Signature verified, envelope stored in inbox-http/0
✗ 拒绝: 签名无效403sig does not match from.pubKey, or the envelope was edited after signing1
✗ 拒绝: 格式错400Missing / malformed nonce or envelope.id, or invalid JSON1
✗ 拒绝: 重放拦截409That nonce was already spent — replay protection1
✗ 连不上网关 … ECONNREFUSEDNo gateway is listening on that port (start it first)1
✗ vap-send: unknown option …Usage error (unknown flag, missing value, bad --boundary)2

Every rejection also prints a 为什么(why) and 怎么办(fix) line, so a failure tells you what to do next instead of only what went wrong.

CLI commands

All four entries share one argument parser: --help prints usage to stdout and exits 0, an unknown flag (or a missing / non-numeric value) prints the error to stderr and exits 2. Errors go to stderr, help and results go to stdout.

CommandWhat it doesExample
bin/vap-send.mjsBuild, sign and POST one envelope, then explain the verdictnode bin/vap-send.mjs --to brain --summary "hello vap" --gateway http://127.0.0.1:3081
bin/vap-gateway.mjsHTTP gateway: POST/GET /envelopes + /healthnode bin/vap-gateway.mjs --port 3081
bin/vap-node.mjsConsensus node (lockstep QC chain + membership) with /healthnode bin/vap-node.mjs --node-id brain --port 3083
bin/vap-relay.mjsRelay: TCP registration + NAT-traversal forwardingnode bin/vap-relay.mjs --port 3082

vap-send options: --to <nodeId>, --summary <text> (≤ 100 chars), --gateway <url>, --from <nodeId>, --claim-type <type>, --boundary <L2a|L1|L0>, --evidence <json>, --request <text>, --key <keyFile>, --root <dir>, --from-file <json>, --timeout <ms>, --dry-run. Two shapes worth knowing:

# Claim the strongest boundary L2a — it requires non-empty evidence.devices
node bin/vap-send.mjs --to brain --summary "巡检完成" --boundary L2a \
  --evidence '{"devices":["E01"]}' --gateway http://127.0.0.1:3081

# Inspect the envelope without sending it, or replay a stored one from a script
node bin/vap-send.mjs --to brain --summary "hello" --dry-run
node bin/vap-send.mjs --from-file ./evt-a2d6aa6ab72547f3.json --gateway http://127.0.0.1:3081

The three service entries accept --host <ip>, --port <n>, --config <path> (JSON config, see config.mjs), --log-file, --log-level, plus VAP_* environment overrides; vap-node also takes --node-id <id> and --ledger-dir <dir>. Default ports are 0 (OS-assigned) — the examples above always pass an explicit --port so you never have to hunt for a random port. Logs go through the structured logger (logger.mjs); SIGINT/SIGTERM (and the Windows stdin shutdown line) trigger graceful exit with code 0. Set VAP_LANG=zh or VAP_LANG=en to force the CLI output language — an escape hatch that overrides console codepage auto-detection (on Windows consoles the CLIs self-heal cp936 to UTF-8).

Testing

node --test

The suite covers the inner ring (envelope / three gates / lease / adoption), the middle ring (HTTP gateway / nonce replay 409-400 / relaying / anti-loop), the CLI experience (tests/cli.test.mjs: help exit 0, unknown flag exit 2, vap-send 202/403/400/409 and gateway-down paths), and every outer-ring phase. node --test must be all green.

Directory structure

vap-core.mjs          # inner ring: envelope, three gates, lease, adoption, five-function contract
vap-transport.mjs     # middle ring: file/HTTP transport, gateway, nonce replay protection
vap-spec.md           # protocol spec (bilingual, v0.2)
config.mjs            # JSON + VAP_* env configuration layer
logger.mjs            # structured JSON-line logger + rotation
key-store.mjs         # PKCS8 private-key persistence (chmod 600)
bin/                  # vap-send (envelope CLI) / vap-relay / vap-gateway / vap-node + process guards
bridges/              # MCP server (mcp-server.mjs) + A2A card/spec (a2a-*.mjs/.md)
tests/                # regression suites (core / transport / security / deploy / bridge / cli)
phase0/ … phase6/     # outer-ring phases: DESIGN + REPORT + experiments
experiments/          # v0 / http / ring2 experiment devices

Honest boundaries

  • Small trust domain: designed for a single-machine shared filesystem (extensible to a shared disk / HTTP gateway); public internet is untested (see phase6/DEPLOYMENT.md).
  • Not Byzantine consensus at internet scale: Ed25519 only proves "whoever holds the private key". The outer-ring consensus (Phase 5/6) tolerates f Byzantine nodes under n = 3f+1; a collusion of ≥ f+1 nodes crosses the mathematical boundary.
  • doWork / respondExpand are stubs (see vap-spec.md §2): the real executor is a later phase.
  • IPv4 multicast only: LAN P2P (phase2/lan-peer.mjs) rejects IPv6 multicast literals.

Phase links

PhaseDesignReport
Phase 0 (behavior-history scarcity)DESIGNP0-REPORT
Phase 0.5 (auditor bootstrap)DESIGNP05-REPORT
Phase 1 (transport SPI)DESIGNreport-phase1
Phase 2 (LAN P2P)DESIGNP2-REPORT
Phase 3 (distributed 2/3 endorsement)DESIGNP3-REPORT
Phase 4 (NAT traversal + relay)DESIGNP4-REPORT
Phase 5 (lockstep QC consensus)DESIGNP5-REPORT
Phase 6 (dynamic membership + laws-on-chain)DESIGNP6-REPORT

License

Apache-2.0