Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-07-15
71 changes: 71 additions & 0 deletions rasen/changes/archive/2026-07-16-agent-adapters-hermes/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
## Context

`agent-adapters-visible-tools` (shipped, commit a2a52ce) established: an `adapted?: boolean` flag on `AIToolOption`, `getToolsWithSkillsDir()` narrowed to `t.skillsDir && t.adapted` as the single selection chokepoint, an `isKnownUnadaptedTool()` rejection helper, and the `adapted-agent-visibility` capability. Adopting a new agent on that surface was designed to be "flip one flag." This change tests that design against a real agent and finds it holds for *selection* but not for *skill delivery*, because Hermes's skill model differs from the project-local model every current tool uses.

**Verified Hermes conventions** (Nous Research `hermes-agent` docs — configuration and CLI-commands references; no local binary available to live-verify):
- Binary `hermes`; home directory `~/.hermes/`, overridable via `HERMES_HOME`.
- **Skills are global-only**: stored under `~/.hermes/skills/<name>/SKILL.md` with YAML frontmatter; each installed skill auto-registers as a `/<name>` slash command. The docs show no project-local skills mechanism.
- **No per-file custom-command directory**: `quick_commands:` live inline in `config.yaml` as `exec`/`alias` shell shortcuts — not LLM prompt files. There is no analog of `.claude/commands/*.md` or `~/.codex/prompts/*.md`.
- Instruction/context files injected from the working directory include `AGENTS.md`, `CLAUDE.md`, `.hermes.md`, `SOUL.md`, `.cursorrules`.
- Non-interactive one-shot: `hermes -z "<prompt>"` (clean stdout, no banner); `hermes chat -q`. Session resume: `--resume`/`-r <session>`, `--continue`/`-c [name]`; `hermes sessions list|browse`.

**Current Rasen skill-install reality** (ground truth): `src/core/init.ts:641` writes skills to `path.join(projectPath, tool.skillsDir, 'skills')` — hardcoded project-local. `tool-detection.ts:106/189` read skill status/version from the same project-local path. The command generator, by contrast, already supports absolute/global paths (`init.ts:679` `path.isAbsolute(cmd.path)`), which is how Codex writes to `~/.codex/prompts/`. So global *command* files are already a solved pattern; global *skills* are not.

## Goals / Non-Goals

**Goals:**
- Make `hermes` a selectable, adapted agent, accepted by `--tools hermes`.
- Install Rasen's workflow skills where Hermes discovers them (its global skills home) so `/rasen-*` commands appear.
- Let `rasen update` correctly detect and refresh an installed Hermes.
- Reuse the shipped visibility mechanism unchanged (`getToolsWithSkillsDir` already includes any `adapted` tool with a `skillsDir`).

**Non-Goals:**
- Any runtime/dispatch/resume bridge for Hermes (a follow-up, mirroring `codex-exec-runtime`).
- A command-file adapter for Hermes (Hermes has no such convention).
- Changing behavior for any other tool; the skills-root resolver defaults to today's project-local path for all non-Hermes tools.
- Inventing project-local Hermes conventions the docs do not support.

## Decisions

### D1: Register `hermes` as an adapted tool; no command adapter

Add to `AI_TOOLS` (`config.ts`): `{ name: 'Hermes', value: 'hermes', available: true, successLabel: 'Hermes', skillsDir: '.hermes', adapted: true }`. The `skillsDir` value satisfies the shipped `getToolsWithSkillsDir()` filter (`t.skillsDir && t.adapted`) so Hermes is offered and `--tools hermes` is accepted — the "flip one flag" property holds for selection. Do **not** add or register a `hermesAdapter`. With no registered command adapter, `init.ts:696` routes Hermes into `commandsSkipped` — exactly the Kimi CLI behavior — so command-file generation is skipped while skills are still installed.

- **Alternative — a codex-style command adapter writing `~/.hermes/prompts/*.md`:** rejected. Hermes has no `prompts/` directory or per-file command convention; that path would invent a location Hermes never reads, violating the "do not invent" constraint.

### D2: Per-tool skills-root resolver; Hermes resolves to its global skills home

Replace the hardcoded `path.join(projectPath, tool.skillsDir, 'skills')` with a single helper, e.g. `resolveToolSkillsRoot(tool, projectPath)`:
- Default (all current tools): `path.join(projectPath, tool.skillsDir, 'skills')` — unchanged behavior.
- Hermes: `path.join(resolveHermesHome(), 'skills')` where `resolveHermesHome()` returns `HERMES_HOME` or `~/.hermes` (new `src/core/hermes/hermes-home.ts`, mirroring `codex/codex-home.ts`).

Signal the global case with an optional marker on `AIToolOption` (e.g. `skillsHome?: 'global'`) or by keying the resolver on `toolId`; the resolver is the single source of truth. Thread it through the four skill-path call sites: install (`init.ts:641`), skill status (`tool-detection.ts:106`), version status (`tool-detection.ts:189`), and update's prune/refresh (`update.ts:168` and the update install path). Rasen's skills already carry the `rasen-` prefix (`SKILL_NAMES`), so writing into the shared `~/.hermes/skills/` namespace does not collide with a user's own skills.

- **Why this is in scope:** the task scopes "the installer/adapter layer" as the deliverable. For a global-home agent, the installer layer *is* global skill placement. This directly parallels the already-accepted global-command-path pattern for Codex.
- **Alternative — install Hermes skills project-locally (`.hermes/skills/`), zero shared-code change:** rejected. Hermes does not read project-local skills, so the install would be inert — "support" in name only, contradicting the user's intent to actually implement Hermes support.
- **Alternative — a Hermes-specific install branch instead of a shared resolver:** rejected. A resolver keeps one code path, avoids Hermes special-cases scattered across init/update/detection, and makes the next global-home agent trivial.

### D3: Configured-state detection reads the global home; presence auto-detection stays weak

"Is Hermes Rasen-configured?" (`getToolSkillStatus`/`getToolVersionStatus`, used by `update`) uses the D2 resolver, so it inspects `~/.hermes/skills/rasen-*` and update refreshes correctly. Tool *presence* auto-detection (`getAvailableTools`, which scans the project directory for `skillsDir`) will rarely fire for Hermes because Hermes keeps no project-local footprint — the same weakness Codex has. This is acceptable: Hermes is chosen explicitly (`--tools hermes`) or from the interactive list. Not worth adding a global-`~/.hermes` probe to project detection in this change.

### D4: Runtime bridge deferred, but the primitives are recorded

`hermes -z` (clean one-shot) and `--resume`/`--continue` (session resume by id/title) are the building blocks a future dispatch/resume bridge needs — the Hermes analog of Codex's `exec` + threadId resume. This change records them (here and in the spec's Why) and stops at the install layer. Pin a `HERMES_CLI_VERSION_PREMISE` (mirroring `CODEX_CLI_VERSION_PREMISE`) only once a local binary can live-verify behavior; until then version pinning is an open question, not an assumption.

## Risks / Trade-offs

- **[Global skills install writes outside the project]** → Rasen skills for Hermes land in `~/.hermes/skills/`, a machine-global location, so `rasen init` for Hermes affects all Hermes projects on the machine. This is inherent to Hermes's design (global skills), not a Rasen choice; the `rasen-` prefix scopes what Rasen writes, and update/uninstall operate on that same prefix. Document it in the success output so the user knows where skills went.
- **[Conventions verified from docs, not a running binary]** → No local `hermes` to live-verify. Mitigation: the install-layer facts used (home dir, global `~/.hermes/skills/<name>/SKILL.md`, skills-as-slash-commands, no command-file dir) are stated directly in the Nous docs and are low-volatility; anything version-sensitive (exact resume/exec event shapes) is deferred to the runtime change with a version premise.
- **[Threading a resolver through four call sites risks a missed path]** → A single shared helper with a default that preserves current behavior means non-Hermes tools are provably unchanged; tests assert both the Hermes global path and an unchanged project-local path for another tool.
- **[`hermes` was the example in a visible-tools rejection test]** → If any shipped test used `hermes` as the "not yet adapted" example, switch it to a still-unadapted tool (e.g. `cursor`) so the rejection test stays meaningful.

## Migration Plan

No data migration. Additive: a new tool entry, a new home resolver, and a resolver indirection with a behavior-preserving default. Rollback = remove the hermes entry + resolver (no persisted state beyond skills the user can delete from `~/.hermes/skills/rasen-*`). Existing installs of other tools are provably unaffected (resolver default is the current path).

## Open Questions

- **Version pinning**: no local `hermes` binary to live-verify; defer `HERMES_CLI_VERSION_PREMISE` and any runtime-behavior assumptions to the runtime follow-up.
- **Instruction injection**: should Rasen also write a Hermes-read instruction file (`AGENTS.md`/`.hermes.md`) for project-level guidance, or are skills sufficient? Recommend skills-only for this change; revisit with the runtime bridge.
- **Uninstall/update semantics for global skills**: confirm `rasen update` and any future uninstall correctly scope to `~/.hermes/skills/rasen-*` and never touch user-authored Hermes skills. (The `rasen-` prefix makes this tractable; verify in tests.)
31 changes: 31 additions & 0 deletions rasen/changes/archive/2026-07-16-agent-adapters-hermes/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
## Why

Rasen adapts its orchestration per code agent (Claude Code first-class, Codex second). Hermes Agent (Nous Research, `hermes` CLI) is the next agent to adopt. This change makes Hermes a selectable, adapted agent by installing Rasen's workflow skills where Hermes actually discovers them, so a Hermes user gets Rasen's `/rasen-*` workflow surface. It is the first consumer of the `adapted` flag shipped by `agent-adapters-visible-tools`.

Research note: all conventions below are established from real sources — the Nous Research `hermes-agent` docs (configuration and CLI-commands references). No local `hermes` binary is installed on this machine (`which hermes` → not found), so version-pinned runtime behavior is left to a future change and flagged as an open question rather than assumed.

## What Changes

- **Hermes becomes an adapted, selectable agent.** A `hermes` entry is added to the tool registry with `adapted: true`, so it appears in the init multi-select, in `--tools all`, and is accepted by `--tools hermes` (reversing the "not yet adapted" rejection for `hermes` only).
- **Rasen skills install to Hermes's global skills home.** Hermes discovers skills only from its global home (`~/.hermes/skills/`, overridable via `HERMES_HOME`), where each `<name>/SKILL.md` is auto-registered as a `/<name>` slash command. Rasen therefore installs its workflow skills to `~/.hermes/skills/rasen-<name>/SKILL.md` rather than a project-local directory. This requires the skill-install path (today hardcoded to `<projectRoot>/<skillsDir>/skills/`) to resolve a per-tool skills root — the same indirection the command adapter already has for Codex's global prompts, applied to skills.
- **No command-file adapter for Hermes.** Hermes has no per-file custom-command directory (its `quick_commands:` are inline `config.yaml` shell shortcuts, not LLM prompts); installed skills already surface as slash commands. Command-file generation is skipped for Hermes via the existing "no registered adapter" path — the same behavior as Kimi CLI. Skills are still always installed.
- **Detection and update tolerate the global home.** Whether Hermes is Rasen-configured is determined from its global skills home, so `rasen update` refreshes an installed Hermes correctly. Auto-detection of Hermes from a project directory is intentionally weak (Hermes keeps no project-local footprint, same limitation as Codex); users select it explicitly or interactively.
- **Runtime bridging is out of scope here.** Hermes exposes a clean non-interactive one-shot (`hermes -z "<prompt>"`) and session resume (`--resume`/`-r`, `--continue`/`-c`), which are promising for Rasen's dispatch/resume needs, but this change delivers only the install/adapter layer. The runtime bridge is left to a follow-up (analogous to `codex-exec-runtime`).
- Tests cover: hermes offered by the adapted surface, skills written to the resolved Hermes home, command generation skipped, and update recognizing an installed Hermes.

## Capabilities

### New Capabilities
- `hermes-integration`: The Hermes install contract — Hermes is an adapted agent; Rasen workflow skills are installed to Hermes's global skills home so they auto-register as slash commands; no per-file command generation is performed for Hermes; configured-state detection reads the global skills home.

### Modified Capabilities
- `adapted-agent-visibility`: the adapted-agent set grows from {claude, codex} to {claude, codex, hermes}; `--tools hermes` is accepted rather than refused as "not yet adapted".
- `ai-tool-paths`: a `hermes` entry is defined (`adapted: true`), and the tool-paths contract gains the notion of a per-tool skills root that MAY resolve to a global home (Hermes) rather than a project-local directory.
- `cli-init`: for Hermes, skills are installed to Hermes's resolved global skills home and command-file generation is skipped (no adapter), while skills remain always-installed under every delivery setting.

## Impact

- **Code**: `src/core/config.ts` (hermes `AI_TOOLS` entry + optional global-skills-home marker on `AIToolOption`); new `src/core/hermes/hermes-home.ts` (resolve `HERMES_HOME`/`~/.hermes`, mirrors `codex/codex-home.ts`); a per-tool skills-root resolver threaded through the hardcoded project-local skill paths in `src/core/init.ts` (~641), `src/core/shared/tool-detection.ts` (~106/189 status + version), and the `src/core/update.ts` refresh/prune paths (~168). No command adapter is added or registered. `getToolsWithSkillsDir()` already includes hermes once `adapted: true` + `skillsDir` are set — no change to the shipped visibility chokepoint.
- **Tests**: new hermes cases in `test/core/init.test.ts` and `test/core/shared/tool-detection.test.ts`; the `agent-adapters-visible-tools` "not yet adapted" test for a still-unadapted tool stays (use a different unadapted example if `hermes` was used there).
- **Scope**: minimal additions on top of `agent-adapters-visible-tools`; the one non-trivial shared-code touch is the skills-root resolver, which is the installer layer for a global-home agent (in scope). No runtime/dispatch code changes.
- **Dependency**: builds on `agent-adapters-visible-tools` (the `adapted` flag and `getToolsWithSkillsDir` narrowing). No new external dependency.
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
## MODIFIED Requirements

### Requirement: Only adapted agents are offered for installation

Rasen SHALL offer an AI coding agent for installation only when Rasen has adapted its orchestration for that agent. An agent is "adapted" when Rasen's dispatch, worker lifecycle, and resume behavior are implemented for it. At the time of this capability, the adapted agents SHALL be Claude Code (`claude`), Codex (`codex`), and Hermes (`hermes`). All other known agents SHALL be hidden from every install/selection surface while remaining defined in the tool registry.

#### Scenario: Install surface lists only adapted agents

- **WHEN** the set of installable tools is computed for any selection surface (interactive multi-select, `--tools all` expansion, or `--tools` help text)
- **THEN** the result SHALL contain only adapted agents (`claude`, `codex`, and `hermes`)
- **AND** SHALL NOT contain any unadapted agent

#### Scenario: Hidden agents remain defined but not offered

- **WHEN** an agent is defined in the tool registry but is not adapted
- **THEN** the agent's registry entry, paths, and detection metadata SHALL remain present and unchanged
- **AND** the agent SHALL NOT appear as an installable choice

### Requirement: Explicitly requesting an unadapted agent is refused with a distinguishing message

When a user explicitly names a known-but-unadapted agent as a tool to install, Rasen SHALL refuse and SHALL explain that the agent is recognized but not yet adapted — distinct from the error shown for an unrecognized token. An agent that IS adapted (including Hermes) SHALL be accepted rather than refused.

#### Scenario: Known unadapted agent requested explicitly

- **WHEN** a user requests installation of a tool that exists in the registry, has a skills directory, but is not adapted (e.g. `cursor`)
- **THEN** the system SHALL fail with exit code 1
- **AND** SHALL display a message stating the tool is recognized but not yet adapted in Rasen
- **AND** SHALL name the currently adapted tools (`claude`, `codex`, `hermes`)

#### Scenario: Adapted agent requested explicitly is accepted

- **WHEN** a user requests installation of an adapted tool (`claude`, `codex`, or `hermes`)
- **THEN** the system SHALL proceed with setup for that tool
- **AND** SHALL NOT display the "not yet adapted" message

#### Scenario: Unrecognized token requested explicitly

- **WHEN** a user requests installation of a token that does not correspond to any registry entry (e.g. `not-a-tool`)
- **THEN** the system SHALL fail with exit code 1
- **AND** SHALL display the existing invalid/unknown-tool error rather than the "not yet adapted" message
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
## ADDED Requirements

### Requirement: Hermes paths defined

The `AI_TOOLS` array SHALL include a `hermes` entry marked `adapted: true` with a `skillsDir` so it passes the adapted-selection filter and is offered for installation.

#### Scenario: Hermes entry present and adapted

- **WHEN** looking up the `hermes` tool
- **THEN** an entry SHALL exist with `value: 'hermes'`
- **AND** it SHALL have `adapted: true`
- **AND** it SHALL have a `skillsDir` defined

### Requirement: Per-tool skills root resolution

The location a tool's Rasen skills are written to SHALL be resolved per tool. For tools that keep skills in the project, the skills root SHALL be `<projectRoot>/<skillsDir>/skills/`. For a tool whose skills live in a global home (Hermes), the skills root SHALL resolve to that global home's skills directory (`<HERMES_HOME or ~/.hermes>/skills/`). The default resolution for every existing tool SHALL be unchanged.

#### Scenario: Project-local tool resolves to the project skills directory

- **WHEN** resolving the skills root for a tool without a global skills home (e.g. `claude`)
- **THEN** the skills root SHALL be `<projectRoot>/<skillsDir>/skills/`

#### Scenario: Hermes resolves to its global skills home

- **WHEN** resolving the skills root for `hermes`
- **THEN** the skills root SHALL be `<HERMES_HOME or ~/.hermes>/skills/`
- **AND** SHALL NOT depend on the project path

#### Scenario: Cross-platform resolution

- **WHEN** resolving any tool's skills root
- **THEN** the path SHALL be constructed with platform-safe path joining (never hardcoded separators)
Loading