Durable Hermes Agent delegation to Oh My Pi. Serious coding missions run through a detached OMP RPC worker, survive Hermes process loss, expose bounded live progress when supported, and remain recoverable from owner-only state.
Status: community release v1.2.1. Independent project; not affiliated with, endorsed by, or officially supported by Nous Research, Hermes Agent, or Oh My Pi. No license has been selected for this repository.
A normal synchronous child process inherits host lifetime and open pipes. If Hermes exits, a long coding mission can die with it or become impossible to identify safely. This plugin moves ownership to a durable runner:
omp_rpc_coding_taskvalidates request and atomically creates job state.- Hermes launches
job_runner.pywithstdin=DEVNULL, owner-only stdout/stderr files,start_new_session=True, and closed inherited descriptors. - Detached runner acquires per-job lock, records actual runner identity, starts OMP in its own process group, and owns all RPC pipes.
- Runner persists bounded progress and terminal result independently of Hermes.
- Same synchronous tool call waits when host remains alive. After host loss, later call attaches by persisted
job_id.
New missions deduplicate only against identical active full-request fingerprints. Fingerprint covers task, resolved cwd, timeout, thinking level, resolved session_file or null, OMP executable, and system prompt. Changed field means distinct work. Terminal jobs never deduplicate new submissions.
Runner requires RPC ready, prompt acknowledgement, agent_end, get_last_assistant_text, and get_state before success. It then gathers bounded Git status/diff-stat, terminates remaining OMP process-group members, and reaps leader. Interactive extension UI requests are cancelled because detached execution has no headless answer channel.
One tool call remains synchronous, but progress updates replace current Hermes tool row instead of creating conversation messages. Typical sequence:
OMP job <job-id> · queued
OMP process started · awaiting RPC ready
OMP · read — Inspecting (1 tool)
OMP · edit complete (2/2 tools)
OMP process alive · no RPC activity for 42s
OMP job succeeded
Progress includes lifecycle state, allowlisted tool name, optional allowlisted generic action word, bounded counters, timestamps, and process-liveness observations. It excludes mission text, tool arguments/output, assistant/model text, and chain-of-thought. OMP process alive reports process observation only; it does not claim model health or forward progress.
Durable execution and recovery still work when Hermes lacks progress-reporter support. Plugin falls back cleanly and omits live UI updates. Live in-place progress currently requires evanstinger/hermes-agent branch feat/omp-tool-progress until equivalent support is upstream.
- macOS with Homebrew OMP executable at
/opt/homebrew/bin/omp. - OMP configured and able to start RPC mode using default profile.
- Hermes Agent version with user-plugin discovery and enablement.
- Python 3.10 or newer.
- Git available for optional repository summary.
Verify fixed OMP path before enabling:
test -x /opt/homebrew/bin/omp
/opt/homebrew/bin/omp --versionPlugin does not create or modify OMP profile, provider, credential, memory, or Second Brain configuration. Optional durable-memory integration is used only when already configured and available.
HERMES_HOME defaults to ~/.hermes. Recommended public install:
export HERMES_HOME="${HERMES_HOME:-$HOME/.hermes}"
hermes plugins install evanstinger/omp-coding-agent --enableManual equivalent:
export HERMES_HOME="${HERMES_HOME:-$HOME/.hermes}"
mkdir -p "$HERMES_HOME/plugins"
git clone https://github.com/evanstinger/omp-coding-agent.git \
"$HERMES_HOME/plugins/omp-coding-agent"
hermes plugins enable omp-coding-agentUpdate later:
hermes plugins update omp-coding-agentPlugin code loads at process start. Reload every long-lived consumer after install, enable, or update:
hermes gateway restart
hermesSecond command starts fresh CLI/TUI process. Fully quit and reopen Hermes Desktop separately. Do not expect already-running gateway, desktop, or TUI process to load changed Python code.
- Toolset:
omp-coding - Tool:
omp_rpc_coding_task
{
"task": "Implement requested change end to end, run relevant checks, and return exact handoff.",
"cwd": "/absolute/path/to/repository",
"timeout_seconds": 1800,
"thinking": "xhigh"
}task and cwd are required. thinking accepts high or xhigh; default xhigh. Timeout default is 1800 seconds.
{
"job_id": "<persisted-job-id>"
}Same call works for both states. Active attach waits on existing runner and forwards its progress; terminal attach returns persisted result immediately. job_id cannot be combined with any new-mission field.
{
"task": "Continue prior OMP session, address remaining issue, verify result, and return handoff.",
"cwd": "/absolute/path/to/repository",
"session_file": "/absolute/path/to/existing-session.jsonl",
"timeout_seconds": 1800,
"thinking": "xhigh"
}session_file must be absolute existing file. Plugin passes it through OMP --resume. This is OMP conversation resume, not durable-job attachment; use job_id for latter.
Success JSON includes job_id, final_handoff, OMP session_file and session_id, elapsed seconds, unique OMP tool names, resolved cwd, and optional Git summary. Failures return structured JSON with success: false, stage, bounded error, available job/session context, and bounded stderr; handler does not raise into Hermes.
Do not blindly resubmit. Job is created before runner launch, so registry may contain live or completed work even when original tool result never reached Hermes.
Default registry:
export HERMES_HOME="${HERMES_HOME:-$HOME/.hermes}"
export OMP_JOB_REGISTRY="$HERMES_HOME/omp-coding-agent/jobs"Inspect bounded summaries without printing raw task text:
python3 - <<'PY'
import json
import os
from pathlib import Path
root = Path(os.environ["OMP_JOB_REGISTRY"]).expanduser()
for state_file in sorted(root.glob("*/job.json")):
try:
state = json.loads(state_file.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
print(state_file.parent.name, "unreadable", type(exc).__name__)
continue
print(
state_file.parent.name,
state.get("state"),
state.get("cwd"),
state.get("created_at"),
state.get("updated_at"),
state.get("request_fingerprint"),
)
PYMatch resolved cwd, state, timestamps, task fingerprint/request fingerprint, and local mission. For candidate only, inspect owner-local records:
python3 -m json.tool "$OMP_JOB_REGISTRY/<candidate-job-id>/job.json"
python3 -m json.tool "$OMP_JOB_REGISTRY/<candidate-job-id>/request.json"request.json contains full mission text; keep output local. Once match is unambiguous, attach:
{
"job_id": "<candidate-job-id>"
}Corrupt, unreadable, symlinked, mismatched, or otherwise unclassifiable registry entry blocks new launch with bounded registry_corrupt or registry_conflict failure. This fail-closed behavior avoids duplicate work.
Treat plugin as privileged code-execution bridge:
- OMP starts with
--approval-mode yoloinside suppliedcwd. Only delegate trusted repositories and missions. - Plugin itself does not add network transport, but OMP, its tools, model provider, Git, and configured integrations may access network or secrets under their own configuration.
- Registry root and job directories are forced owner-only (
0700); JSON, lock, and log files are created owner-only (0600). Ownership and symlink checks fail closed. request.jsonstores full mission and resolved paths.job.jsonstores hashes, process metadata, progress, session identity, log paths, and terminal result. SHA-256 fingerprints are identifiers, not encryption.- Progress never copies raw mission, args, result, assistant/model text, or reasoning. Untrusted intent is stripped, secret/high-entropy patterns are rejected, and only fixed generic action word survives.
- RPC tool-call IDs become fixed-size SHA-256 correlation keys. Malformed response IDs fail as protocol errors.
- No automatic registry retention exists. Archive or remove terminal job directories only after results are no longer needed; never remove active directory.
- Tests use inert fake OMP process and explicitly synthetic credential-shaped privacy fixtures. They never contact model or credential provider.
| Resource | Bound |
|---|---|
| RPC NDJSON frame | 4,194,304 bytes (4 MiB) |
| Raw frame queue | 4 frames; 16,777,216-byte (16 MiB) envelope |
| Task input | 100,000 characters and 262,144 UTF-8 bytes |
cwd / session_file
|
4,096 characters and 8,192 UTF-8 bytes each |
job_id |
80 ASCII characters / 80 bytes |
| Timeout | 60–7,200 seconds |
| Serialized tool result | strictly below 48,000 UTF-8 bytes |
| Final handoff | 28,000 UTF-8 bytes before whole-result compaction |
| Error / returned stderr | 4,000 UTF-8 bytes each |
| Persisted OMP stderr | 65,536 bytes; continues draining after truncation |
| Each Git stdout/stderr capture | 65,536 bytes; both pipes continue draining |
| Returned Git status / diff-stat | 6,000 UTF-8 bytes each |
| Unique OMP tool names | 128 |
| Progress intent / tool name / preview | 160 / 96 / 320 UTF-8 bytes |
| Progress counters / sequence | 1,000,000 / |
Runner heartbeat persists at most every 15 seconds. Host forwards changed sequences and 30-second silence fallback. Host wait adds fixed 60-second finalization allowance covering queued-start grace, bounded process cleanup/reap, five bounded Git commands, terminal persistence, and reader shutdown; successful calls do not sleep for unused allowance.
- Runtime target currently macOS/Homebrew because executable path is fixed to
/opt/homebrew/bin/omp. - CI exercises stdlib plugin and detached-runner semantics on Linux and macOS with fake OMP. This does not claim supported Linux OMP deployment with current fixed path.
- POSIX process groups, signals, file locking, and owner permission checks are required. Native Windows runtime is not supported by this release.
- Default OMP profile is used; plugin intentionally passes no
--profile. - Interactive OMP extension UI is cancelled in detached mode. Passive UI frames are ignored.
- One Hermes tool call waits synchronously while host lives; durability protects underlying work, not host call continuity.
- Registry retention/cleanup is manual.
- Live UI progress requires compatible Hermes core; durability does not.
test -x /opt/homebrew/bin/omp
/opt/homebrew/bin/omp --versionCurrent release does not resolve OMP from PATH.
hermes plugins list
hermes plugins enable omp-coding-agentThen restart CLI/TUI/Desktop or run hermes gateway restart.
Inspect job.json.progress.sequence, phase, updated_at, last_rpc_activity_at, runner_heartbeat_at, liveness booleans, bounded counters, and safe current-tool fields. Inspect owner-only runner/OMP stderr logs for failure details. Changing heartbeat proves runner observation only. Reattach by job_id; do not submit duplicate mission.
Do not delete unknown active state. Inspect named entry ownership, symlink status, job.json, request.json, and runner lock. Repair/archive only after confirming no live runner owns directory.
No real model is called. Suite uses tests/fake_omp.py as local NDJSON RPC executable. Select an explicit Python 3.10+ interpreter; macOS may still resolve bare /usr/bin/python3 to Python 3.9.
# Override on other platforms or when Homebrew Python lives elsewhere.
PYTHON_BIN="${PYTHON_BIN:-/opt/homebrew/bin/python3}"
"$PYTHON_BIN" -c \
'import sys; assert sys.version_info >= (3, 10), "Python 3.10+ required"'
"$PYTHON_BIN" -m venv .venv
. .venv/bin/activate
python -m pip install --disable-pip-version-check --no-cache-dir ruff==0.15.22
python -m unittest discover -s tests -p 'test_*.py' -v
python -m py_compile __init__.py job_runner.py tests/*.py
python -m ruff check .CI matrix covers Python 3.10–3.14 on Linux and macOS.
| Path | Purpose |
|---|---|
__init__.py |
Hermes registration, validation, durable registry, progress, RPC, cleanup, and result bounds |
job_runner.py |
Minimal detached entry point loading one persisted job |
omp-system-prompt.txt |
Generic worker behavior and optional-memory handoff policy |
plugin.yaml |
Hermes plugin manifest; version 1.2.1 |
tests/fake_omp.py |
Inert local OMP RPC fixture |
tests/test_plugin.py |
Tool contract, lifecycle, registration, resume, and recovery tests |
tests/test_durability.py |
Host-loss survival test |
tests/test_fail_closed.py |
Bounds, process cleanup, registry, and protocol failure tests |
tests/test_progress.py |
Progress privacy, heartbeat, attachment, and terminal-state tests |
.github/workflows/ci.yml |
Least-privilege Linux/macOS verification matrix |