Skip to content

feat: add Hermes Agent (NousResearch) integration - #1110

Open
kunalcast wants to merge 2 commits into
getkimchi:masterfrom
kunalcast:feat/hermes-integration-v2
Open

feat: add Hermes Agent (NousResearch) integration#1110
kunalcast wants to merge 2 commits into
getkimchi:masterfrom
kunalcast:feat/hermes-integration-v2

Conversation

@kunalcast

@kunalcast kunalcast commented Aug 28, 2026

Copy link
Copy Markdown

feat: add Hermes Agent (NousResearch) integration

Re-introduces the Hermes Agent integration attempted in PR #454, with the fixes required to pass CI and work against the real Hermes CLI.

What changed since PR #454

  • Preserved ToolDefinition interface — the original PR deleted it and only extended ToolId, which broke tsc for every other integration.
  • Fixed installer flags — Hermes's install script does not support --no-prompt / --no-onboard. Uses supported flags: --skip-setup --non-interactive --skip-browser --no-skills.
  • Correct Hermes config shape — Hermes recognizes an active inference provider under the top-level model: block (provider: custom, base_url, api_key, default), not under models.providers. The integration writes that block plus a fallback_providers chain.
  • No daemon commands during setup — removed hermes onboard --install-daemon and hermes gateway restart, both of which hang in container/headless environments.
  • Wired everywhere — Hermes is registered as a ToolId, imported in the setup wizard and setup-tools, and exposed as kimchi hermes.

Files changed

  • src/integrations/hermes.ts — Hermes integration
  • src/integrations/hermes.test.ts — unit tests
  • src/integrations/types.ts — added "hermes" to ToolId
  • src/setup-wizard/index.ts — imported Hermes integration
  • src/commands/setup-tools.ts — imported Hermes integration
  • src/commands/hermes.ts — new kimchi hermes subcommand
  • src/commands/registry.ts — registered kimchi hermes

Verification

  • pnpm run check — lint + typecheck clean
  • pnpm vitest run src/integrations/hermes.test.ts — 33/33 passing
  • node scripts/build-binary.js --target linux-arm64 — builds Linux ARM64 binary

Revisits PR getkimchi#454 and fixes the issues that made it fail CI:
- preserves the ToolDefinition interface in types.ts (was accidentally deleted)
- wires Hermes into setup-wizard and setup-tools loaders
- adds kimchi hermes CLI command

Mirrors the existing OpenClaw integration pattern:
- detect via ~/.hermes dir or hermes on PATH
- CLI-first write via hermes config set with merge fallbacks
- direct YAML write fallback
- env file management for KIMCHI_API_KEY

Includes 29 unit tests covering provider block, models catalog,
env writer, direct and CLI write paths, and registration.
@readme-ai-writer

readme-ai-writer Bot commented Aug 28, 2026

Copy link
Copy Markdown

Documentation Changes Added

Page Section Action Summary
kimchi-cliGuides📝 UpdatedAdd Hermes to the Supported Tools table and the setup-tools command description.

🔗 View all changes in ReadMe


Actions

  • Merge documentation branch with PR merge
  • Delete documentation branch with PR close

If neither actions are selected, on PR close/merge the docs branch in ReadMe will remain open.

@kimchi-review

kimchi-review Bot commented Aug 28, 2026

Copy link
Copy Markdown

Kimchi Code Review

Property Value
Commit 498f663
Author @kunalcast
Files changed 0
Review status Completed
Comments 6 (3 info, 3 warning)
Duration 133s

Summary

📊 Review Score: 80/100 (overall code quality — 0 lowest, 100 highest)
⏱️ Estimated effort to review: 3/5 (1 = trivial, 5 = very complex)

🧪 Tests: yes — A thorough new test file (src/integrations/hermes.test.ts) covers provider block construction, model catalog generation, .env writing/merging, helper utilities (asObject, mergeFallbacks, mergeModelsCatalog), version constants, and both the direct-YAML and CLI-driven integration paths. The command runner itself (src/commands/hermes.ts) is not unit-tested.

📝 Found 6 issue(s). See inline comments for details.

What to expect

Kimchi will analyze the changes in this pull request and post:

  • A summary of the overall changes
  • Inline comments on specific lines with findings categorized by issue type

The review typically completes within a few minutes. This comment will be updated once the review is ready.

Interact with Kimchi
  • @getkimchi review — re-trigger a full review on the latest commit
  • @getkimchi summary — regenerate the PR summary
  • @getkimchi ignore — skip this PR (no review will be posted)
  • Reply to any inline comment to ask follow-up questions or request clarification
Configuration

Reviews are configured by your organization admin.
Review instructions, excluded directories, and severity thresholds can be adjusted per repository in the Kimchi dashboard.


Powered by Kimchi — AI-powered code review by CAST AI

@kimchi-review kimchi-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📊 Review Score: 80/100 (overall code quality — 0 lowest, 100 highest)
⏱️ Estimated effort to review: 3/5 (1 = trivial, 5 = very complex)

🧪 Tests: yes — A thorough new test file (src/integrations/hermes.test.ts) covers provider block construction, model catalog generation, .env writing/merging, helper utilities (asObject, mergeFallbacks, mergeModelsCatalog), version constants, and both the direct-YAML and CLI-driven integration paths. The command runner itself (src/commands/hermes.ts) is not unit-tested.

📝 Found 6 issue(s). See inline comments for details.


const providerBlock = buildHermesProviderBlock(models)
const modelsCatalog = buildHermesModelsCatalog(models)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️🏗️ Design

writeHermesViaCLI does not accept or respect the scope parameter from writeHermes, so when the hermes binary is on PATH the configuration is always written to Hermes' default/global location. In contrast, writeHermesDirect uses resolveScopePath(scope, HERMES_CONFIG_PATH). This means kimchi hermes --scope project behaves differently depending on whether hermes is installed, surprising users and making scoped configuration unreliable.

💡 Suggestion: Either pass the scope through to the Hermes CLI if it supports scoped config, or validate/reject non-global scopes early in writeHermes so the CLI path is only used when scope === "global".

/** Minimum Hermes version we know the config layout works against. Exported for tests and version gating. */
export const HERMES_VERSION_MIN = "2026.1.0"

/** Matches `hermes --version` output: "Hermes 2026.1.2". Exported for tests and version gating. */

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️🔧 Maintainability

HERMES_VERSION_MIN and HERMES_VERSION_REGEX are documented as being for "version gating" but are never consulted at runtime. Writing the new models.providers.kimchi block to an older Hermes version could produce an incompatible or broken configuration without any diagnostic.

💡 Suggestion: Add a runtime check in writeHermesViaCLI (and optionally writeHermesDirect) that runs hermes --version, parses the version with HERMES_VERSION_REGEX, compares it to HERMES_VERSION_MIN, and throws a clear error when the installed version is unsupported.

const raw = readFileSync(path, "utf-8")
const trimmed = raw.trim()
if (trimmed !== "") {
const parsed = parseYaml(trimmed)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️🏗️ Design

When the Hermes gateway is not running, writeHermesViaCLI silently runs hermes onboard --install-daemon .... Installing a system daemon is a significant side effect for a command whose summary is "Configure Hermes to use Kimchi", and may surprise users who only expected a config file update.

💡 Suggestion: Log that the daemon will be installed before invoking onboard, or split daemon installation into an explicit opt-in step (e.g., kimchi hermes --install-daemon) rather than performing it automatically during configuration.

Comment thread src/integrations/hermes.ts Outdated
const mainSlug = resolved.main?.slug ?? models[0].slug
const fallbacks = ([resolved.coding, resolved.sub] as Array<ModelMetadata | undefined>)
.filter((m): m is ModelMetadata => m !== undefined)
.map((m) => `${PROVIDER_NAME}/${m.slug}`)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️⚠️ Error Handling

In writeHermesDirect, the try/catch only handles readFileSync failures. If the existing config.yaml is malformed, parseYaml(trimmed) throws an unhandled exception that bubbles up as a raw YAML parse error rather than a clean, actionable Kimchi error message.

💡 Suggestion: Extend the try/catch to wrap parseYaml(trimmed) and throw a descriptive error such as throw new Error(\Existing Hermes config at ${path} is not valid YAML`)`.

if (result.status !== 0) {
const detail = (result.stderr || result.stdout || "").trim()
throw new Error(`hermes ${args.slice(0, 2).join(" ")} failed: ${detail || `exit ${result.status}`}`)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️⚠️ Error Handling

runHermesCmd reports exit ${result.status} when spawnSync returns status: null because the child was killed by a signal. The resulting message exit null is unhelpful for diagnosing the real cause.

💡 Suggestion: Check for result.signal when result.status === null and include it in the error: const reason = result.status === null && result.signal ? \signal ${result.signal}` : `exit ${result.status}`; throw new Error(`hermes ${args.slice(0, 2).join(" ")} failed: ${detail || reason}`)`.

Comment thread src/commands/hermes.ts
return 1
}
await tool.write(scope, prepped.apiKey, prepped.models)
console.log("kimchi hermes: configuration written.")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️⚠️ Error Handling

runHermes casts the caught value to Error and reads .message. If the integration throws a string, number, or other non-Error value, the cast will succeed at compile time but the runtime access to .message will produce undefined, and console.error will print an unhelpful message.

💡 Suggestion: Use a safe message extraction such as console.error(\kimchi hermes: ${err instanceof Error ? err.message : String(err)}`)`.

…mands

- Replace invalid --no-prompt/--no-onboard install args with --skip-setup --non-interactive --skip-browser --no-skills

- Remove hermes onboard call that hangs in container/headless installs

- Remove hermes gateway restart to avoid blocking setup-tools when daemon management is unavailable
@kunalcast

Copy link
Copy Markdown
Author
hermes-kimchi

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants