Skip to content

feat(agents): add a Hermes Agent agent - #384

Merged
earakely-scale merged 5 commits into
scaleapi:mainfrom
teknium1:feat/hermes-agent
Oct 29, 2026
Merged

earakely-scale merged 5 commits into
scaleapi:mainfrom
teknium1:feat/hermes-agent

Conversation

@teknium1

@teknium1 teknium1 commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

What and why

Adds agents/hermes/, an agent for Nous Research's Hermes Agent, built the same way as the other agents in the bundle. Tasks can name it as hermes (alias in aliases.toml) or as @local/agentenv-framework/agents/hermes.

  • How it runs: each turn is one hermes agentenv turn, a subcommand that the agent's own Hermes plugin (agentenv_plugin/) adds. It runs the turn in that process, the way hermes -z does, instead of handing it to a Hermes gateway. So the turn ends when that process ends, and a timeout or cancel kills the process and every tool it started.
  • Trajectory: the plugin registers Hermes' public plugin hooks (on_session_start, pre_tool_call, post_tool_call, post_api_request, api_request_error, post_llm_call, on_session_end). It writes each event as a JSON line to a pipe the bridge reads while the turn runs (trajectory/v1, live, format hermes-hook-events/1).
  • Per-context isolation: each A2A context is its own Hermes profile (/data/.hermes/profiles/ctx-<hash>), with its own config, sessions database and memory. A context's first turn starts a Hermes session, and later turns resume it with --resume.
  • Model endpoint: $LITELLM_BASE_URL/v1 is configured as a named OpenAI-compatible custom provider, keyed from LITELLM_API_KEY. [model.headers] and the run's project_id/task_id attribution go out as extra_headers on every model call, Hermes' auxiliary calls (titles, vision, compression) included. Tag joining matches the OpenCode agent.
  • System prompt: the task's system_prompt is written as the profile's SOUL.md, the slot Hermes' own identity normally fills. Hermes then sends it as the system prompt.
  • MCP / skills / peers: each registered MCP server, peer_agents included, goes into the profile's mcp_servers, with the AgentEnv-Role override. Installed skills go into one shared folder that every profile reads through skills.external_dirs.
  • Image: python:3.11-slim with Hermes installed by its own installer, pinned to the v0.22.5 release commit. The installer script itself is checked against a SHA-256. Hermes' runtime (its own Python, Node and ripgrep) lives under /data/.hermes, separate from the A2A server's Python. Image size is about 2.9 GB.
  • Release plumbing: added to infra_build.py (image and smoke command), dependabot.yml, the aliases test, the infra-images test, the agents README and CONTRIBUTING. Adds a hermes-smoke task.

The agent's README.md has the Extension | Implementation table. agent-config, mcp-config, skill-config (inline and bundle), trajectory, snapshot (save/load), peer-agents and attribution-probe are implemented; staging/v1 is the SDK default. Not included in this PR: install/v1 and the snapshot changelog. Both can follow if you want them.

Verification

Run locally against the local backends, with the [model] endpoint pointed at an OpenAI-compatible proxy and anthropic/claude-haiku-5.5 as the model:

  • agent-env run agents --task hermes-smoke: passed (deploy 3.8 s, prompt 12.2 s).
  • agent-env a2a-agent put --id hermes-dev --dockerfile .../hermes/Dockerfile (full validator run against the mail env): every advertised check passed. That covers agent card, message/send/tasks/get, identity round trip, role, system prompt, MCP (search_emails via the gateway), trajectory, skill-config (skill_md and skill_bundle), peer-agents (peer call returned the token), snapshot save + load with recall after load, and tool_call_count. It also covers every modality probe: text, png/jpeg/gif, png by https URI, pdf, and also the audio and video probes, which Hermes handled with its own tools. The card declares only text, images and PDF. The only false rows are the two features this PR does not advertise: changelog and install/v1.
  • tst/unit/agents/hermes/: 21 tests. Card contract, message contract (concurrency across contexts, one turn at a time within a context, session binding on cancel, config/MCP/file parts, snapshot round trip), and bridge tests that drive a scripted hermes in a real subprocess: profile config, events read live, failure mapping, output_format, no fd leak. Timeout and cancel both kill Hermes and the tool it started. That last test was confirmed to fail with the process-tree kill swapped for a plain proc.kill().
  • make lint is clean; the rest of the unit suite is left to CI.

A Hermes turn peaked near 210 MB in the image (cgroup memory.peak, running shell and Python tools). The concurrency cap budgets 400 MB per turn past 1 GB, overridable with HERMES_MAX_CONCURRENT_TURNS.

RetriggerView in GreptileConfidence Score: 5/5

The changes since the previous review appear safe to merge.

Summary

Adds Hermes Agent with per-context sessions, live events, attachments, skills and snapshots.

  • Hermes runs each task in its own profile and keeps that context’s session.
  • Hermes conversations and workspace files can be saved and restored as snapshots.

Reviews (4) · Last reviewed commit: "fix(agents): give Hermes PDF page folder..." · Reviewed by Greptile

Adds agents/hermes/, Nous Research's Hermes Agent behind the A2A agent framework: each turn is one in-process `hermes agentenv turn` (a subcommand of the agent's own Hermes plugin) in the context's own Hermes profile, its plugin hook events streamed as the trajectory. Registers the image, its alias, a smoke task, and the release/dependabot entries.
@teknium1
teknium1 requested a review from a team as a code owner October 29, 2026 05:24
Comment thread src/agent_env/agents/agents/hermes/a2a_server.py Outdated
Comment thread src/agent_env/agents/agents/hermes/a2a_server.py Outdated
Comment thread src/agent_env/agents/agents/hermes/a2a_server.py Outdated
earakely-scale and others added 2 commits October 29, 2026 09:07
…efore letting go, and hold attachments to the turn limit

- A snapshot load stages the archived profile beside the context's, then
  swaps the workspace and the profile in one thread, with the profile swap
  inside the workspace's, and binds the session, in one step a cancel can't
  abandon. A failed swap leaves the previous profile, session and workspace.
- Thread work a cancel would have left running (the snapshot archives, tar,
  the profile write before a turn) now runs to its end before the caller lets
  go of the workspace or the context's lock. A PDF render is an async
  pdftoppm, killed and reaped on timeout or cancel, and http(s) attachments
  stream to disk with an async client.
- A task's attachments are downloaded and rendered inside the turn's slot, so
  HERMES_MAX_CONCURRENT_TURNS bounds them too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@earakely-scale

Copy link
Copy Markdown
Collaborator

Thanks for the Hermes agent. I pushed review fixes to the branch (maintainer edits):

  • fa5a90fb fixes Greptile's three findings (replied in each thread):
    • a snapshot load swaps the workspace and the profile together, then binds the session, in one step a cancel can't abandon; a failed swap leaves both as they were;
    • thread work a cancel would have left running now finishes before the lock is released; PDF pages render through an async pdftoppm that is killed and reaped on timeout or cancel, and http(s) attachments stream to disk;
    • attachments are fetched and rendered inside the turn's slot.
      Four new tests in tst/unit/agents/hermes/test_held_work.py fail on e74959a0.
  • 1b636735 merges main (clean).

Verified on an Ubuntu 24.04 VM, against Scale's LiteLLM proxy:

  • tst/unit/agents/hermes: 25 passed, including the /proc test macOS can't run.

  • agent-env run agents --task hermes-smoke: passed (deploy 15.2 s, prompt 12.3 s, default model).

  • agent-env a2a-agent put validator on this head: every advertised check passes, including snapshot save and load with recall after load. The report is identical to e74959a0's, and it passes again with anthropic/claude-haiku-5-5.

    • The only tolerated failures, the same on both heads, are the changelog steps (not advertised) and the audio probes (not declared on the card). With Sonnet 4.6 all four audio probes time out at 120 s; with Haiku 5.5 the mp3 one passes.
  • Live, against the image with HERMES_MAX_CONCURRENT_TURNS=1:

    Check This head e74959a0
    Cancel during a 400-page PDF render no pdftoppm left 0.06 s later still running 4.6 s later
    A second context's attachment while the first context's turn runs written only after that turn ends written during it
    A PNG fetched by https streamed to disk, turn completed

CodeBuild needs a maintainer approval to start on this fork PR.

🤖 Generated with Claude Code

Comment thread src/agent_env/agents/agents/hermes/a2a_server.py Outdated
… its own

Two PDFs whose names share a stem (report.pdf and report, or report.PDF on a
case-sensitive filesystem) rendered their pages under one prefix, so the second
overwrote the first's and listed its leftovers. Each PDF's pages now go in
a folder of their own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread src/agent_env/agents/agents/hermes/a2a_server.py Outdated
A page folder named after the PDF passed the filesystem's 255-byte limit for a
name near it, and the OSError failed the task. The folder now takes a fixed
prefix; its random suffix keeps two PDFs apart.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@earakely-scale
earakely-scale merged commit d8b7444 into scaleapi:main Oct 29, 2026
25 of 26 checks passed
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