All notable changes to this project are documented in this file.
The broker now speaks the self-use Python relay's v3 protocol alongside v2, closing the divergence that left v3 clients (the Hermes adapter and the Python relay agents) locked out of a v2-only broker:
- Bilingual signatures:
X-Agent-Relay-Key-Idpresent → v3 scheme (agent\nkeyId\nts\nMETHOD\npath\ndigest); absent → v2 scheme. Per-agent keyrings (agents.<name>.keys, withnot_afterexpiry) support key rotation; the implicitlegacykey is the agent's single secret. Unknown or expired key ids are rejected with401 unknown_key./healthzreportsprotocol_version: 3andsignature_schemes: ["v2", "v3"]. - Lease credentials (v3):
pullresponses includelease_token/lease_until;POST /v1/ackwith a token is guarded on the active lease (409 lease_mismatchon mismatch/replay); newPOST /v1/lease/renewextends an active lease. Acks without a token keep v2 semantics. allow_shared_writeround-trips on requests (and is stripped from replies), matching the self-use workspace-leasing semantics.- Admin authz mirrors the self-use broker:
security.admin_agentsmay act on anything; everyone else only on their own side (recipient requeues an unfinished request, originator cancels their request). Admin operations are audit-logged (ids only).queuesin/healthzgainsoldest_queued_at. - SQLite schema migration: pre-v3 databases gain the
lease_token/allow_shared_writecolumns in place on startup.
- JS client (
lib/client-v2.js): optionalkeyIdopts the client into v3 signing;ack()accepts aleaseTokenfor the strict single-use guard; newrenewLease()extends an active delivery lease. - Standalone relay agent (
adapters/relay-agent.mjs): renews the broker delivery lease every 4 minutes while a backend CLI run is in flight, so a turn longer than the lease window is no longer re-queued and re-delivered mid-flight; acks with the lease token when the broker provides one. - Published Python client (
adapters/hermes/relay_client_v2.py):key_id=constructor option (v3 scheme),ack(..., lease_token=)and arenew_lease()method — byte-compatible with the self-userelay/client.py. - v3 golden vectors locked in
test/protocol_v2_golden.pyandtest/protocol-v2.test.mjs(generated by the self-use reference implementation), plus a v3 leg in the cross-language client round-trip test. - CI runs on Windows too — the primary deployment platform.
- v2 store
dataDirresolves against the broker directory — a relativedataDir(the./datadefault) no longer depends on the process CWD, so the v2 SQLite/JSONL files always land beside the v1 store. The broker entrypoint now creates the v2 store itself and closes it on shutdown (previously only the v1 store was closed);close()also runsPRAGMA wal_checkpoint(TRUNCATE). - Lifecycle transitions persist as single-row UPDATEs instead of rewriting the whole table on every ack/pull/requeue/cancel. Full-table rewrites caused heavy WAL growth (a multi-MB WAL against a 128 KB database was observed in production). The JSONL fallback keeps its rewrite semantics.
- The idempotency index is pruned together with retention purges — it previously grew without bound for the lifetime of the process.
- v2
POST /v1/ackrequires the message to be leased (400otherwise) — a late or duplicated ack can no longer resurrect a terminal message back toqueued(e.g. a retry ack arriving after a redelivery was acked mid-flight).
/v1/admin/requeueand/v1/admin/cancelare restricted to the originator or the recipient of the message (403otherwise), matching the visibility rules of/v1/statusand the per-mode ACL.- dsh plugin: the credential-vault fallback for
secretRefis configured, not built in. Resolving a secret through a Python module exposingreveal_entry(name)requires the newvaultModulesetting (DSH_RELAY_VAULT_MODULEenv /vault_modulein~/.dsh/agent-relay.json); no installation layout is hard-coded anymore. - dsh plugin: the system-prompt guidance and the
agent_relay_senddescription derive the roster and broker endpoint from configuration (circleMembers/DSH_RELAY_CIRCLE_MEMBERS/circle_members), falling back to pointing atagent_relay_peersinstead of a hard-coded member list. - dsh plugin: the default relay-session cwd falls back to the user's home
directory instead of a Windows-specific
C:/path.
- v2 wire protocol (
docs/PROTOCOL-V2.md): canonical-JSON signing (sort_keys+ compact + raw UTF-8),X-Agent-Relay-*headers, snake_case envelope (message_id/origin/target/kind/body/session_ref/created_at/expires_at/execution_mode/context/topic). Byte-for-byte compatible with the self-use Pythonrelay/protocol.py(golden vectors locked intest/protocol-v2.test.mjs+test/protocol_v2_golden.py). - v2 broker endpoints:
/healthz,/v1/messages,/v1/pull,/v1/ack,/v1/status,/v1/recent,/v1/messages/query,/v1/admin/requeue|cancel|status. - v2 state machine in the transitional
store-v2.js: queued → leased → completed/failed/expired,(origin, idempotency_key)idempotency, TTL + 30-day retention, SQLite (node:sqlite) with JSONL fallback. - per-mode ACL (
allowed_read/continue/write_targets; write closed by default) and undelivered notices (notify_failed_to_sender). - v2 clients:
lib/client-v2.js(RelayClientV2),adapters/hermes/relay_client_v2.py(pure stdlib), CLIv2 <subcommand>. - dsh plugin upgrade (
lib/index.js): adaptive-backoff inbox polling, per-root relay sessions (agent-relay-<root_id>) with archive + idle recycle, 5 tools (agent_relay_send/status/history/peers/retry), execution-mode permission presets, per-agent receipts/routes persistence, systemPrompt guidance. Replaces the v1 relay_send/recv/peers/history tools. - v1 compatibility preserved — every existing v1 endpoint/client/test still
works (the v1 protocol is a separate path, documented in
docs/PROTOCOL.md).
- Lease-based delivery state machine:
POST /v1/pullleases queued messages (queued → leased),POST /v1/ackfinalizes (completed → done) or re-queues (retry, attempts+1, overmaxAttempts→failed); expired leases are re-queued by a sweep. Config:broker.leaseSeconds(600),broker.maxAttempts(3). - Request/reply correlation: optional envelope fields
kind(message|request|reply),rootId,parentId; broker-visiblestatus,attempts,leaseUntil. - History/status endpoints:
POST /v1/status(batch lookup),POST /v1/recent,POST /v1/messages/query(read-only filtered search). - Routing ACL: optional
agents.<name>.allowed_targetswhitelist inbroker/config.yaml; disallowed sends return403 forbidden; absent entry = allow all (v1.0 default). - Client support: JS
RelayClient(pull/ackOutcome/status/recent/querykind/rootId/parentIdonsend), CLI subcommands (pull/ack/status/recent/query), Python client equivalents.
- Plugin receipts: the dsh plugin polls via lease-pull and persists completed
replies to
~/.dsh-agent-relay-receipts.json(TTL 1 day, atomic write); a redelivered request whose id is in the receipts is replayed (idempotent) and ackedcompleted— never re-run after a restart.relay_sendacceptsreplyTo. - Docs: PROTOCOL v1.1 extension, DEPLOY v1.1 config, SECURITY ACL, README/zh, AGENT-DEPLOY checklist.
- SQLite is now the default persistent backend, creates a missing data directory on first start,
and falls back to JSONL on Node runtimes where
node:sqlitecannot be loaded (Node < 22.5, and 22.5–22.12 which gate it behind--experimental-sqlite).GET /reports the active backend and optional capabilities without changing protocol 1.0 compatibility. - The Python adapter now sends legacy acknowledgements with the correct HTTP method and retries
408/429responses consistently with the JavaScript client. - Configuration parsing now supports the documented nested agent ACL structure and rejects invalid
safety, delivery, rate-limit, and lockout values at startup. The removed
broker.tlslabel now fails loudly at startup (terminate TLS at a trusted reverse proxy) instead of being silently ignored — config templates no longer emit it.
test/lease.test.mjs(state machine, lease expiry, attempts, status/recent/query) andtest/acl.test.mjs(whitelist allow/deny + v1.1 pull/ack round trip). 29/29 green.
- Agent-driven deployment: new
docs/AGENT-DEPLOY.mddeploy task book for DSH — the npm package now ships the broker + setup + adapters, so a single-machine deployment needs no git clone; DSH follows the task book to generate the secret, start the broker, wire the plugin/CLI/Python clients and verify the round trip. - npm package is self-contained:
filesnow includesbroker/,setup/,adapters/,docs/andREADME.zh.md; a new.npmignorekeeps secrets, runtime data andtest/out of the tarball.
- Broker version no longer hard-coded:
broker/src/server.jsreads its own manifest (broker/package.json) so/andselfcheckalways report the real version (was stuck at 0.1.0), and the path works in both the repo and the Docker image layout. - Plugin protocol handshake: the dsh plugin now performs the PROTOCOL §4 version negotiation on first poll (matching the Python client), surfacing an incompatible-broker error instead of proceeding silently.
- Plugin not-configured guidance: points to
docs/AGENT-DEPLOY.mdand is npm-aware (no longer assumes a git checkout withsetup/setup.js). - CLI:
peers/handshakeno longer require--agent(docs already assumed it); removed a duplicated ack loop. - Docs accuracy: privacy claims now correctly state that message content
lives only in the broker's TTL-limited queue (default 7 days) and is
never written to application logs — aligning README/README.zh and the
AGENT-DEPLOY.mdchecklist with the actual persistence behavior.
- Unit tests now run on macOS as well as Linux (matrix).
setup.js selfchecknow reads host/port frombroker/config.yaml(reusing the broker's own config loader) instead of hard-coding127.0.0.1:19121— a custom port no longer produces a false "broker not reachable" failure.
- Wire protocol v1.0 (docs/PROTOCOL.md): envelope, HMAC auth headers, version negotiation, error codes, retry/idempotency rules, brute-force protection.
- Broker (broker/): zero-dependency Node HTTP service — register/heartbeat, peers, send, incremental poll, ack; JSONL persistence; 7-day TTL; per-agent lockout; per-IP rate limiting.
- dsh plugin (repo root): relay_send / relay_recv / relay_peers / relay_history model tools; background heartbeat + inbox polling; optional sidebar status panel; graceful degradation when not configured.
- CLI client (adapters/cli/relay.mjs): zero-dependency, send/recv/peers/register/watch/handshake.
- Python client (adapters/hermes/relay_client.py): pure stdlib, Hermes-style integration example included.
- Setup: setup.js init|start|selfcheck, selfcheck.js, optional docker-compose demo, broker Dockerfile.
- Docs: PROTOCOL / ARCHITECTURE / DEPLOY (single-machine + TLS distributed) / SECURITY; bilingual README.
- CI: unit tests (node --test), gitleaks secrets scan, license check, lockfile check on every push.