From 0a03c43f903ae8a7112c573da3ab77dcacbd2a15 Mon Sep 17 00:00:00 2001 From: Ciphercat <78522797+0xrsydn@users.noreply.github.com> Date: Wed, 18 Mar 2026 01:51:03 +0000 Subject: [PATCH] docs: remove superseded skills PRD --- docs/SKILLS-DECLARATIVE-PRD.md | 776 --------------------------------- module.nix | 2 +- tests/skills-coexistence.nix | 56 +-- 3 files changed, 32 insertions(+), 802 deletions(-) delete mode 100644 docs/SKILLS-DECLARATIVE-PRD.md diff --git a/docs/SKILLS-DECLARATIVE-PRD.md b/docs/SKILLS-DECLARATIVE-PRD.md deleted file mode 100644 index ad4e437..0000000 --- a/docs/SKILLS-DECLARATIVE-PRD.md +++ /dev/null @@ -1,776 +0,0 @@ -# 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. diff --git a/module.nix b/module.nix index df9e682..4f7c24e 100644 --- a/module.nix +++ b/module.nix @@ -36,7 +36,7 @@ let customSkillSpecs = lib.mapAttrsToList (name: value: { inherit name; - category = value.category; + inherit (value) category; source = toString value.source; }) cfg.skills.custom; diff --git a/tests/skills-coexistence.nix b/tests/skills-coexistence.nix index e7c9e1c..97a4427 100644 --- a/tests/skills-coexistence.nix +++ b/tests/skills-coexistence.nix @@ -1,4 +1,8 @@ -{ self, nixpkgs, system }: +{ + self, + nixpkgs, + system, +}: let pkgs = import nixpkgs { inherit system; }; @@ -10,36 +14,38 @@ in pkgs.testers.runNixOSTest { name = "hermes-skills-coexistence"; - nodes.machine = { ... }: { - imports = [ self.nixosModules.hermes-agent ]; + nodes.machine = + { ... }: + { + imports = [ self.nixosModules.hermes-agent ]; - services.hermes-agent = { - enable = true; - package = self.packages.${system}.hermes-agent; - skills = { - bundled.enable = false; - custom.repo-watch = { - category = "research"; - source = testSkill; + services.hermes-agent = { + enable = true; + package = self.packages.${system}.hermes-agent; + skills = { + bundled.enable = false; + custom.repo-watch = { + category = "research"; + source = testSkill; + }; }; - }; - documents = { - "SOUL.md" = "# SOUL.md\nTest soul\n"; - "AGENTS.md" = "# AGENTS.md\nTest agents\n"; - "USER.md" = "# USER.md\nTest user\n"; - }; - config = { - toolsets = [ "all" ]; - model = { - default = "moonshotai/kimi-k2.5"; - provider = "openrouter"; + documents = { + "SOUL.md" = "# SOUL.md\nTest soul\n"; + "AGENTS.md" = "# AGENTS.md\nTest agents\n"; + "USER.md" = "# USER.md\nTest user\n"; + }; + config = { + toolsets = [ "all" ]; + model = { + default = "moonshotai/kimi-k2.5"; + provider = "openrouter"; + }; }; }; + + system.stateVersion = "25.05"; }; - system.stateVersion = "25.05"; - }; - testScript = '' machine.wait_for_unit("multi-user.target")