eaa539552c
Implements the hermes-agent HSP/1 sync CLIENT against the frozen wire
contract (~/src/specs/collective-wisdom/hsp-1-contract.md §8), tested
against an in-process mock HSP server.
tools/skills_sync_client.py (new, low-level; does NOT import the CLI):
* Full 64-hex sha256 content addressing + canonical JSON (§2.1/§2.5,
OI-5) — kept distinct from the truncated local content_hash namespace.
* HSPClient: capabilities/refs/objects GET, batch object upload
(multipart, raw bytes per §1/§4.2), CAS ref (§4.4) with 409->HSPConflict.
* Object building: skill dir -> blob/tree/commit; exec-bit preserved,
symlinks skipped, oversize (413) surfaced; profile-root category trees.
* push/pull + three-way merge (M1-C): reuses the origin/user/incoming
decision semantics of skills_sync.py; non-overlap -> merge commit +
retry CAS; true overlap -> refs/user/<owner>/conflict/<n> + surface.
* DEV-PHASE gate: sync is INERT unless the resolved Nous token carries
tool_gateway_admin===true (decoded from the bearer; server re-verifies).
* Auth reuses resolve_nous_runtime_credentials() (no refresh reimpl).
* maybe_push_skills / maybe_pull_skills gate-and-swallow entrypoints.
Opt-in (M1-D): tools/skill_usage.set_sync / is_sync_enabled — a `sync`
flag on the .usage.json sidecar; nothing syncs unless opted in. Only
agent-created/user-authored skills are eligible (bundled/hub excluded).
Hooks:
* Debounced push in skill_manage success block (after the write gate).
* Periodic pull at the two curator tick sites (gateway housekeeping loop
+ CLI startup).
CLI: hermes sync status|pull|push|now|enable|disable
(hermes_cli/subcommands/sync.py + cmd_sync in main.py).
Tests: tests/tools/test_skills_sync_client.py — 29 tests (addressing,
canonicalization, dev gate, opt-in, object building, merge decisions, and
e2e push/pull/idempotency/conflict against a stdlib mock HSP server).