Skills cache

Materialise SKILL.md bundles on every machine so claude / codex can reference them by path without baking the content into agent definitions.

Why#

A skill is a SKILL.md document plus optional companion files (shell scripts, helper configs, etc.). The dashboard stores the source of truth in Postgres; the daemon caches each version locally so the harness can be pointed at a real on-disk directory rather than streaming bytes per session start.

On-disk layout#

text
~/.controlum/skills/
└── <skill-id>/
    └── v<version>/
        ├── SKILL.md
        ├── deploy.sh           # whatever the "files" jsonb contained
        ├── helpers/util.sh
        └── .checksum           # sha256 the daemon computed; matches backend's

<skill-id> is the backend uuid; <version> is the user-supplied semver string. Multiple versions coexist — claude pointed at v1.0.0 keeps working while a fresh session opens v1.1.0.

Sync flow#

  1. Dashboard /skills → row → Sync icon → pick a machine.
  2. POST /skills/{id}/reload-on-machine with { "machine_id": "<uuid>" }. Returns 202 if the daemon is online, 503 if it isn't.
  3. Backend looks up the skill bundle (SKILL.md + files + checksum) and pushes a sync_skill WS message to the chosen machine's daemon.
  4. Daemon writes the bundle to a sibling .tmp-* directory, computes the sha256 over skill_md + "\n" + files-json, and verifies it matches the backend's checksum.
  5. On match: os.Rename the tmp dir into place (atomic; readers of the previous version aren't disturbed). On mismatch: ErrChecksumMismatch — tmp dir is cleaned and the on-disk version stays untouched.
  6. Daemon emits skill_synced (success) or skill_sync_failed (mismatch / IO error) back over WS. Future iterations will surface this as a per-skill badge on the dashboard.

Safety#

  • Path traversal rejected. Entries whose path resolves out of the version dir (../escape, absolute paths) are refused before any write happens.
  • Atomic. A partial bundle (process killed mid-write, disk full) leaves the previous version untouched.
  • Per-user. The HTTP handler verifies the user owns BOTH the skill and the machine; daemons only ever receive bundles from ws.Hub.SendToDaemon (the user's WS connection).

Limitations (v1)#

  • No automatic GC of old versions. Disk pressure for skills is tiny; downgrades stay possible.
  • No retry on the backend if the WS push fails — the user re-clicks Sync. WS-level retries are on the roadmap.
  • No per-machine sync status in the table yet — only a toast on dispatch.