From c2966d25152961c064db6abd116abb786b534b01 Mon Sep 17 00:00:00 2001 From: Ciphercat <78522797+0xrsydn@users.noreply.github.com> Date: Tue, 17 Mar 2026 23:23:53 +0000 Subject: [PATCH] docs: add declarative skills PRD --- docs/SKILLS-DECLARATIVE-PRD.md | 776 +++++++++++++++++++++++++++++++++ 1 file changed, 776 insertions(+) create mode 100644 docs/SKILLS-DECLARATIVE-PRD.md diff --git a/docs/SKILLS-DECLARATIVE-PRD.md b/docs/SKILLS-DECLARATIVE-PRD.md new file mode 100644 index 0000000..ad4e437 --- /dev/null +++ b/docs/SKILLS-DECLARATIVE-PRD.md @@ -0,0 +1,776 @@ +# Declarative Skills for nix-hermes-agent + +Status: Draft +Owner: 0xrsydn / Ciphercat +Branch: `feat/skills` + +## TL;DR + +`nix-hermes-agent` already makes Hermes config, documents, auth seeding, and service wiring declarative. +The next maturity step is to make **skills** first-class declarative state too. + +This document proposes a practical, upstream-aware design that: + +1. preserves Hermes' filesystem-based skill model, +2. respects upstream bundled/optional/hub distinctions, +3. keeps Nix-managed state reproducible, +4. avoids fragile runtime-only snowflake setup, +5. leaves room for future hub/external skills without making the first version messy. + +The recommended rollout is: + +- **Phase 1:** declarative bundled + optional + local custom skills +- **Phase 2:** declarative enable/disable controls per platform +- **Phase 3:** optional support for declarative external/hub skills +- **Phase 4:** profile abstractions on top + +--- + +## Problem + +Today `nix-hermes-agent` can declaratively manage: + +- package version +- service wiring +- `HERMES_HOME` +- generated config +- documents like `SOUL.md`, `AGENTS.md`, `USER.md` +- auth seed file +- env files +- MCP servers + +But skills remain effectively under-managed. + +That creates several problems: + +### 1. Reproducibility gap + +Useful Hermes deployments are shaped heavily by available skills. +Right now those are not modeled as deployable state in the Nix module. + +### 2. Snowflake drift + +A machine can gain skills interactively via Hermes CLI, but the resulting state is not cleanly captured in Nix. +Rebuilds and reprovisioning become less trustworthy. + +### 3. Poor fleet ergonomics + +If we want Hermes to live as: + +- a gateway agent, +- a CLI/TUI agent over SSH, +- an ACP-connected IDE agent, +- a research/creative/coding sandbox on a dedicated VM, + +then the skill substrate needs to be reproducible and composable. + +### 4. Missing product maturity + +A declarative agent deployment layer that cannot declaratively express skills is not finished. + +--- + +## Goal + +Make `nix-hermes-agent` mature enough that a user can declare Hermes skill state in Nix with the same confidence they already declare config and documents. + +Success means a user can express: + +- which built-in/bundled skills exist, +- which upstream optional skills are installed, +- which local custom skills are present, +- which skills are disabled globally or per platform, +- eventually, which external/hub skills are desired, + +and rebuild a host into the same functional Hermes deployment. + +--- + +## Non-goals + +### Not a full rewrite of upstream skill management + +We should not fork or replace Hermes' skill architecture. +We should layer on top of real upstream semantics. + +### Not immediate perfect support for every hub workflow + +Hub-managed skills involve remote registries, quarantine, audit, taps, lock files, and provenance. +We should not block the entire feature set on solving every edge case in v1. + +### Not soul/prompt redesign + +This effort is about capability substrate and deployment maturity, not voice tuning. + +--- + +## Upstream architecture findings + +This section captures the relevant upstream seams that matter for Nix integration. + +## 1. Single runtime skill root + +Hermes treats this as the runtime source of truth: + +- `HERMES_HOME = Path(os.getenv("HERMES_HOME", Path.home() / ".hermes"))` +- `SKILLS_DIR = HERMES_HOME / "skills"` + +Relevant upstream files: + +- `tools/skills_tool.py` +- `tools/skills_hub.py` +- `tools/skills_sync.py` + +Implication: + +**Nix should target `${HERMES_HOME}/skills` as the canonical runtime location.** +Any declarative solution that ignores this will fight upstream. + +## 2. Filesystem-based discovery + +Skills are discovered by scanning `SKILLS_DIR` recursively for `SKILL.md`. + +Relevant upstream behavior: + +- `tools/skills_tool.py:_find_all_skills()` +- category derived from relative path under `SKILLS_DIR` +- category descriptions can come from `DESCRIPTION.md` + +Implication: + +**Declarative skills do not need a custom registry format.** +They mainly need correct directory materialization. + +## 3. Bundled skills are synced via a manifest + +Upstream has a sync layer for bundled skills: + +- `tools/skills_sync.py` +- manifest file: `~/.hermes/skills/.bundled_manifest` + +Behavior summary: + +- bundled skills are synced into `~/.hermes/skills/` +- manifest tracks origin hashes +- user modifications/deletions are handled intentionally +- removed bundled skills are cleaned from manifest + +Implication: + +**Bundled skills are not just copied blindly.** +If we want to align with upstream behavior, we should respect that bundled skills have their own lifecycle semantics. + +## 4. Optional skills are shipped in repo, but not bundled into runtime by default + +Upstream has `optional-skills/` and exposes them through hub-style install/search flows. + +Relevant upstream behavior: + +- `tools/skills_hub.py: OptionalSkillsSource` +- source path: repo `optional-skills/` +- install identifiers look like `official/category/skill` or `official/skill` + +Implication: + +Optional skills are a clean fit for declarative install because they are effectively pinned to the package revision already. + +## 5. Skill enable/disable is config-driven + +Skill disabling lives in Hermes config, not the filesystem. + +Relevant upstream behavior: + +- `hermes_cli/skills_config.py` +- `tools/skills_tool.py:_get_disabled_skill_names()` +- config keys: + +```yaml +skills: + disabled: [] + platform_disabled: {} +``` + +Implication: + +**Installation and enablement are separate concerns.** +The Nix module should model both. + +## 6. Hub/external skills carry provenance state + +Hub state lives under: + +- `~/.hermes/skills/.hub/lock.json` +- `~/.hermes/skills/.hub/taps.json` +- plus quarantine/audit state + +Relevant upstream behavior: + +- `tools/skills_hub.py:HubLockFile` +- `tools/skills_hub.py:TapsManager` +- install flow records source/trust/provenance/install_path/hash + +Implication: + +External skill support has real statefulness and should be treated as a separate phase. + +--- + +## Product principles + +## 1. Reproducibility first + +The primary value of `nix-hermes-agent` is that Hermes deployment stops being snowflake state. +Skills must follow the same principle. + +## 2. Align with upstream instead of papering over it + +Where upstream already has a lifecycle concept: + +- bundled manifest, +- optional skill source, +- hub lock file, +- disabled config, + +we should design with those semantics in mind. + +## 3. Prefer pinned/local skill materialization over runtime network installs + +For Nix-managed deployments, local/pinned skills are cleaner than runtime remote fetches. + +## 4. Separate “present on disk” from “enabled for runtime” + +This mirrors upstream and keeps the module more expressive. + +## 5. Make the first version boring and reliable + +Copy/sync into runtime state first. +Do not over-optimize for immutable symlink purity if it breaks upstream assumptions. + +--- + +## Proposed module design + +## Phase 1: Declarative local skill materialization + +### New option group: `services.hermes-agent.skills` + +Suggested top-level shape: + +```nix +services.hermes-agent.skills = { + bundled.enable = true; + + optional = [ + "research/foo" + "creative/bar" + ]; + + custom = { + my-playbook = { + category = "research"; + source = ./skills/my-playbook; + }; + }; + + disabled = [ "foo" ]; + + platformDisabled = { + telegram = [ "shell-heavy-skill" ]; + cli = [ ]; + }; +}; +``` + +### Sub-feature A: bundled skills + +#### Goal +Allow `nix-hermes-agent` to ensure upstream bundled skills are present declaratively. + +#### Why +This makes package upgrades and fresh machines deterministic. + +#### Design options + +##### Option A — let upstream sync bundled skills at runtime +Pros: +- closest to upstream lifecycle +- manifest semantics stay upstream-owned + +Cons: +- requires that sync path is guaranteed to run in the packaged flow +- harder to reason about from Nix module alone + +##### Option B — module-managed bundled sync/materialization +Pros: +- explicit and controllable from module +- can be tested from Nix side + +Cons: +- risks partially duplicating upstream logic unless done carefully + +#### Recommendation +Use **module-managed materialization** initially, but preserve upstream-compatible structure and manifest awareness. +If we later find an upstream-supported sync entrypoint that is stable, we can switch implementation without changing user-facing options. + +### Sub-feature B: optional skills + +#### Goal +Install upstream `optional-skills/` declaratively by package revision. + +#### Why +These are the cleanest next-step skills because they are already version-pinned by the packaged source tree. + +#### Recommendation +Expose a list of relative skill paths from upstream optional-skills. +Example: + +```nix +services.hermes-agent.skills.optional = [ + "research/deep-research" + "creative/story-ideation" +]; +``` + +The module activation step should materialize these into `${HERMES_HOME}/skills/...` preserving category layout. + +### Sub-feature C: custom local skills + +#### Goal +Allow users to declaratively ship their own skills. + +#### Recommendation +Support both: + +- `source = ./path/to/skill-dir` +- later maybe `text = '' ... SKILL.md ... ''` for convenience + +Suggested shape: + +```nix +services.hermes-agent.skills.custom = { + repo-watch = { + category = "research"; + source = ./skills/repo-watch; + }; +}; +``` + +This should build a derivation containing normalized skill trees, then copy/sync them into runtime state. + +--- + +## Phase 2: Declarative enable/disable controls + +### Goal +Manage the upstream config shape declaratively. + +Suggested mapping: + +```nix +services.hermes-agent.skills.disabled = [ "foo" ]; +services.hermes-agent.skills.platformDisabled.telegram = [ "bar" ]; +``` + +Module implementation should merge this into generated Hermes config: + +```nix +config.skills = { + disabled = ...; + platform_disabled = ...; +}; +``` + +### Why separate from installation? +Because upstream separates them, and it enables useful patterns: + +- skill present but disabled on messaging platforms +- skill enabled only in CLI/ACP contexts +- same deployment with different platform affordances + +This matters a lot if Hermes is used across: + +- gateway, +- SSH CLI/TUI, +- ACP/IDE connection. + +--- + +## Phase 3: Declarative external / hub skills + +This should be treated as a separate feature set, not bundled into v1. + +### Problem +Hub skills are not just files. +They have upstream lifecycle semantics: + +- provenance +- trust level +- lock entries +- taps +- audit log +- quarantine/security scanning + +### Two viable models + +#### Model A — runtime installation via Hermes CLI +Declare desired hub skills in Nix, then reconcile at activation/runtime using Hermes itself: + +```nix +services.hermes-agent.skills.hub = [ + { + identifier = "owner/repo/path/to/skill"; + source = "github"; + } +]; +``` + +Pros: +- preserves upstream lock/audit/scan behavior + +Cons: +- network-dependent +- less reproducible +- slower/more stateful + +#### Model B — pin external skill sources in Nix and treat them as local skills +Use `fetchFromGitHub` or flake inputs, then place them under `custom`/local managed skills. + +Pros: +- reproducible +- reviewable +- pinned by hash +- easier to reason about in infra repos + +Cons: +- bypasses hub provenance model unless explicitly emulated + +### Recommendation +For `nix-hermes-agent`, **prefer Model B philosophically**. +Support Model A later as an escape hatch for people who want native Hermes Hub behavior. + +--- + +## Phase 4: Profiles + +Once the substrate exists, we can define higher-level role profiles. +Examples: + +- `researcher` +- `creative-lab` +- `ops-lite` +- `coding-explorer` + +These profiles would be syntactic sugar over: + +- installed skills +- disabled skills +- model/tool config +- maybe documents + +This should come after raw primitives are solid. + +--- + +## Proposed option schema + +This is a proposed user-facing module API, not final code. + +```nix +services.hermes-agent.skills = { + enable = lib.mkEnableOption "declarative Hermes skills"; + + bundled = { + enable = lib.mkOption { + type = lib.types.bool; + default = true; + }; + }; + + optional = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ "research/deep-research" ]; + }; + + custom = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule ({ ... }: { + options = { + category = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + }; + source = lib.mkOption { + type = lib.types.path; + }; + }; + })); + default = { }; + }; + + disabled = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + }; + + platformDisabled = lib.mkOption { + type = lib.types.attrsOf (lib.types.listOf lib.types.str); + default = { }; + }; + + hub = lib.mkOption { + type = lib.types.listOf (lib.types.submodule ({ ... }: { + options = { + identifier = lib.mkOption { type = lib.types.str; }; + source = lib.mkOption { + type = lib.types.enum [ "github" "official" "well-known" "skills-sh" ]; + default = "github"; + }; + }; + })); + default = [ ]; + }; +}; +``` + +--- + +## Implementation plan + +## Milestone 1 — groundwork and packaging introspection + +### Deliverables +- confirm packaged source paths for bundled skills and optional-skills +- add tests or at least build-time assertions that those paths exist in the package output +- document path assumptions + +### Notes +Current package already fetches upstream source with submodules and builds Hermes as a Python app. +We need a stable way to refer to upstream skills trees from the installed package or derivation source. + +### Risks +- packaged output may not expose source trees exactly where activation expects them +- upstream package layout may shift across releases + +### Mitigation +Add explicit path probes in checks/docs and keep implementation centralized. + +--- + +## Milestone 2 — declarative bundled + optional + custom skill materialization + +### Deliverables +- module options for bundled/optional/custom skills +- activation step that creates `${HERMES_HOME}/skills` +- copy/sync logic preserving category layout +- initial tests covering presence of selected skills in state dir + +### Implementation guidance +Use a generated derivation to normalize all Nix-managed skill content into one tree, then reconcile that tree into runtime state. + +Example conceptual pipeline: + +1. build a Nix store tree containing: + - selected bundled skills + - selected optional skills + - custom local skills +2. activation script syncs that tree into `${cfg.stateDir}/.hermes/skills` +3. preserve non-Nix-managed hub state unless explicitly managing it + +### Important constraint +Do **not** clobber `.hub/` blindly. +Do **not** wipe user-managed runtime state unless explicitly requested. + +--- + +## Milestone 3 — declarative disabled/platformDisabled wiring + +### Deliverables +- module options +- merge into generated Hermes config +- tests that rendered config contains expected shape + +### Why this milestone is low risk +This is a direct mapping to upstream config semantics and does not require custom lifecycle logic. + +--- + +## Milestone 4 — docs and examples + +### Deliverables +- README examples for: + - basic bundled skill deployment + - optional skills + - custom local skills + - platform-specific disabling +- migration guidance for users already managing skills interactively + +--- + +## Milestone 5 — optional hub skill support + +### Deliverables +- design decision: native runtime install vs pinned-local strategy +- if implemented, add explicit caveats about reproducibility and network dependency +- preserve upstream lock/audit semantics + +### Recommendation +Do not block the core feature on this milestone. + +--- + +## State reconciliation strategy + +This is the most important implementation choice. + +## Recommended approach: managed subtree reconciliation + +Use a managed subset of `${HERMES_HOME}/skills` while leaving upstream-owned dynamic state alone. + +### Desired behavior +Nix should manage: + +- bundled skills selected by module policy +- optional skills selected by module policy +- custom local skills selected by module policy + +Nix should avoid trampling: + +- `.hub/` +- maybe `.bundled_manifest` unless we intentionally integrate with it +- runtime-installed non-managed skills unless user opts into strict mode + +### Possible implementation pattern +Maintain a managed marker file or managed manifest under Hermes home, e.g.: + +- `${HERMES_HOME}/skills/.nix-managed-manifest.json` + +Track which installed paths belong to Nix-managed state. +On activation: + +- create/update managed paths +- remove managed paths no longer desired +- leave unmanaged paths untouched + +This avoids destructive full-directory replacement and plays better with upstream hub installs. + +--- + +## Testing strategy + +## 1. Evaluation tests +Ensure module options evaluate and merge correctly. + +## 2. Render tests +Validate generated config contains expected skill disable keys. + +## 3. Activation tests +On a NixOS test VM or shell-based checks: + +- deploy with custom skill +- assert `SKILL.md` lands under expected path +- deploy with optional skill +- assert category structure preserved +- rebuild with skill removed +- assert only managed skill removed +- assert `.hub/` untouched + +## 4. Regression tests +Check package path assumptions for upstream bundled and optional-skills trees. + +--- + +## Open questions + +## 1. Should bundled skills be managed by module or left to upstream sync? +Current recommendation: module-managed initially, but keep implementation swappable. + +## 2. Should Nix-managed skills be copied or symlinked into runtime? +Current recommendation: **copy/sync first**. +This is more boring but more compatible with any upstream expectations around writable trees and file operations. + +## 3. Should hub skills be in scope for the first PR series? +Current recommendation: **no**. +Document the plan, but ship local/offline-managed skills first. + +## 4. Should we support inline `SKILL.md` text for custom skills? +Probably yes later, but path-based custom skills are enough for v1. + +--- + +## Recommended PR breakdown + +## PR 1 — groundwork +- package path assertions +- module option scaffolding +- docs stub + +## PR 2 — declarative local skills +- bundled/optional/custom skill options +- managed reconciliation logic +- tests + +## PR 3 — declarative disable controls +- `disabled` +- `platformDisabled` +- config wiring +- tests + +## PR 4 — docs/examples +- README updates +- migration examples +- profile examples + +## PR 5 — optional hub support (if still desired) +- separate decision doc +- explicit caveats + +--- + +## Recommendation + +The repo should focus on **declarative skills maturity now**, not soul tuning. + +The best next step is to implement a boring, reliable, upstream-aware layer for: + +- bundled skills, +- optional skills, +- custom local skills, +- disabled/platform-disabled config. + +That will make `nix-hermes-agent` meaningfully more mature and unlock better Hermes roles across: + +- gateway usage, +- SSH CLI/TUI usage, +- ACP/IDE connections, +- dedicated research/creative sandbox VMs. + +--- + +## Appendix: Upstream files worth watching + +For future maintainers, these upstream files are the primary integration seams: + +- `tools/skills_tool.py` + - `SKILLS_DIR` + - `_find_all_skills()` + - `_get_disabled_skill_names()` + - category/path discovery + +- `tools/skills_sync.py` + - bundled skill sync behavior + - `.bundled_manifest` + +- `tools/skills_hub.py` + - `OptionalSkillsSource` + - `HubLockFile` + - `TapsManager` + - install/uninstall semantics + +- `hermes_cli/skills_config.py` + - user-facing config model for disabled/platform-disabled skills + +- `hermes_cli/config.py` + - `HERMES_HOME` + - merged config semantics + +These are the places likely to matter most when upstream changes skill behavior.