feat: multi-agent runtime + 8 new agents (Hermes via PyPI plugin)#39
Open
ChasLui wants to merge 14 commits into
Open
feat: multi-agent runtime + 8 new agents (Hermes via PyPI plugin)#39ChasLui wants to merge 14 commits into
ChasLui wants to merge 14 commits into
Conversation
Adds proposal for refactoring OpenWolf to support 6 agents (claude/codex/gemini/opencode/openclaw/hermes) via per-agent AgentAdapter abstraction. Phase 0 lands the ADR text and the src/agents/types.ts interface skeleton; subsequent phases implement adapters and refactor src/cli/init.ts + src/hooks/. See: docs/adr/ADR-001-multi-agent-runtime.md
Lands AgentAdapter implementations as placeholders (methods throw "not yet implemented"). Phase 1b refactors src/cli/init.ts + src/hooks/* to consume these adapters. Findings during this phase (recorded in ADR-001): - Codex hook protocol only supports matcher "shell" — no file-op hooks. OpenWolf on Codex is degraded to shell + soft-instruction. - Gemini hook protocol only supports BeforeTool matcher "run_shell_command". Same degradation. Verified against pandafilter v1.3.5 implementations of ~/.codex/panda-rewrite.sh and ~/.gemini/settings.json. Adapters added: - src/agents/types.ts — AgentAdapter interface (Phase 0, refresher) - src/agents/claude.ts — full-fidelity adapter, mirrors current behavior - src/agents/codex.ts — shell-only adapter (degraded) - src/agents/gemini.ts — shell-only adapter (degraded) - src/agents/index.ts — registry + detectInstalled() See: docs/adr/ADR-001-multi-agent-runtime.md
…--agent flag Codex and Gemini hook protocols cannot host OpenWolf's file-op hooks (see ADR-001). Instead, deliver OpenWolf as a soft-instruction: write a marker-delimited section to ~/.codex/AGENTS.md and ~/.gemini/GEMINI.md telling the agent to follow the OpenWolf protocol when cwd has a .wolf/ directory. Idempotent — re-running `openwolf init --agent codex` only refreshes content between <!-- openwolf:start --> and <!-- openwolf:end -->. Changes: - src/agents/codex.ts: real detect/installGlobal/uninstallGlobal - src/agents/gemini.ts: real detect/installGlobal/uninstallGlobal - src/agents/snippets/openwolf-cross-agent.md: shared instruction body - src/cli/index.ts: `openwolf init --agent <name>` flag with values claude (default, current per-project behavior unchanged) | codex | gemini | all. Plus `--uninstall` to remove integration. ClaudeAdapter installGlobal still throws — Phase 1c refactors src/cli/init.ts HOOK_SETTINGS into ClaudeAdapter without changing behavior. Default (no --agent) flow preserved bit-for-bit. Tested: tsc --noEmit emits zero errors on agents/ and cli/index.ts.
Phase 1b's CodexAdapter/GeminiAdapter readSnippet() tried to fs-read src/agents/snippets/openwolf-cross-agent.md at runtime with a wrong relative path; tsc doesn't copy .md to dist anyway. Result: install fell through to the embedded short fallback (one paragraph) instead of the full 7-step protocol. Fix: extract OPENWOLF_SNIPPET as a TypeScript const in src/agents/openwolf-snippet.ts (single source of truth), with shared stripMarkerBlock() and withSnippet() helpers. Both adapters import it — zero runtime fs lookup, snippet ships inside the JS bundle. src/agents/snippets/openwolf-cross-agent.md is kept as human-readable documentation (ADR/PR diff readability); both files must stay in sync when the protocol changes. End-to-end tested 2026-05-09: - openwolf init --agent codex → ~/.codex/AGENTS.md gets full 7-step snippet - openwolf init --agent gemini → ~/.gemini/GEMINI.md ditto - Idempotent: reinstall 2× → exactly 1 marker block - --uninstall → marker block stripped, surrounding content preserved
OpenClaw adapter:
- target: ~/.openclaw/workspace/AGENTS.md
- same marker-block strategy as Codex/Gemini (OpenClaw hook system
is shell-only, no file-op matchers)
- mkdirs ~/.openclaw/workspace/ if it doesn't exist yet
OpenCode adapter:
- OpenCode supports instructions[] array in opencode.json
- write snippet body to ~/.config/opencode/openwolf-instructions.md
(OpenWolf-owned file, no marker needed)
- patch opencode.json instructions[] to include the path
- uninstall removes path AND deletes the file
- idempotent: re-running adds path only once
End-to-end tested 2026-05-09:
- install/uninstall both adapters
- idempotent (reinstall 2× → marker count 1, instructions[] no dup)
- ~/.openclaw/workspace/AGENTS.md gets marker block coexisting with
user's existing manual content
- ~/.config/opencode/opencode.json instructions[] grows from 6→7
5/6 agents now covered by openwolf init --agent. Hermes (Phase 3)
requires Python plugin — see ADR-001.
Hermes has no instructions[] / soft-prompt injection point — its
only extension surface is the Python plugin system (entry-point
group hermes_agent.plugins). So Phase 3 ships:
1. src/agents/hermes/python/ — openwolf-hermes Python package
- pyproject.toml (entry-point: openwolf = "openwolf_hermes")
- src/openwolf_hermes/__init__.py (~180 lines)
· pre_tool_call hook: detect cwd .wolf/, append memory.md
on file read/write, bump token-ledger.json counters
· /openwolf slash command: status (read .wolf/ state) and
scan (subprocess `openwolf scan`)
· register(ctx) entry point per Hermes plugin protocol
- README.md explains what Hermes API allows vs not
2. src/agents/hermes.ts — HermesAdapter
- detect: ~/.hermes exists + hermes binary in PATH
- installGlobal: locate hermes venv via realpath of `hermes`
binary → its `python` neighbor → `uv pip install --python
<venv-py> -e <fork>/src/agents/hermes/python` + patch
~/.hermes/config.yaml plugins.enabled to add "openwolf"
- uninstallGlobal: uv pip uninstall + strip from config.yaml
- YAML patcher uses regex (no yaml lib dep) supporting both
inline `[a, b]` and block `\n - a\n - b` forms
3. src/agents/index.ts — register HermesAdapter
Bug fixed during Phase 3: pyproject.toml had both `license =
"AGPL-3.0-only"` and `License :: OSI Approved` classifier —
setuptools >= 80 enforces PEP 639 mutual exclusion. Removed the
classifier; license expression remains.
End-to-end tested 2026-05-09 in real ~/.hermes/update-candidates/
hermes-agent-v2026.4.13/venv:
- install: plugin appears in entry_points("hermes_agent.plugins")
alongside rtk-rewrite; config.yaml plugins.enabled grows from
[rtk-rewrite] to [rtk-rewrite, openwolf]
- import: `python -c "import openwolf_hermes"` works
- register(FakeCtx): registers /openwolf command and pre_tool_call
- pre_tool_call(tool_name="read_file", args={path}): noop when
cwd has no .wolf/ (correct)
- idempotent: reinstall keeps openwolf entry count at 1
- uninstall: package gone, config rolled back, plugin no longer
importable — clean
- reinstall: full state restored
Phase 3 closes 6/6 agent coverage. Phase 4 (PyPI publish, ADR
"out of scope" → "PR to upstream") remaining.
Phase 3 commit accidentally tracked .egg-info/ and __pycache__/ generated by `uv pip install -e`. Add a local .gitignore and untrack the artifacts.
Adds: - .github/workflows/publish-hermes-plugin.yml — tag-triggered PyPI publish via OIDC Trusted Publishing (no long-lived API token). Build + twine check on every tag; upload only when tag matches openwolf-hermes-v* and dry_run is not set. - src/agents/hermes/python/RELEASE.md — one-time PyPI Trusted Publisher setup steps + per-release flow (version bump, tag, push) + yank guidance. Phase 4b: removes PyPI-account-dependence from the publish loop once a maintainer (or @ChasLui) configures the trusted publisher in PyPI. Publishing is then `git tag openwolf-hermes-v0.2.0 && git push --tags`. Adapter still uses dev-install (`uv pip install -e <fork>/...`) during alpha; will switch to plain `uv pip install openwolf-hermes` after first PyPI release.
This was referenced May 9, 2026
Open
Open
Adds 3 more agents to the multi-agent runtime, all via the same soft-instruction marker pattern (no hooks). Total: 9/9 agents covered by `openwolf init --agent`. - src/agents/pi-mono.ts — writes ~/.pi/agent/AGENTS.md (or $PI_CODING_AGENT_DIR/AGENTS.md if set). Clean global injection point per pi-mono CLI's documented config layout. See https://github.com/badlogic/pi-mono. - src/agents/cline.ts — writes the OS-specific Cline global rules file: ~/Library/Application Support/cline/rules.md (macOS), $XDG_CONFIG_HOME/cline/rules.md (Linux), %APPDATA%/cline/rules.md (Windows). Verified against panda init --agent cline 1.3.5 install layout on macOS. - src/agents/cursor.ts — EXPERIMENTAL. Cursor IDE has no documented global rules path as of 2026-05; User Rules go through Settings UI. Adapter writes ~/.cursor/USER_RULES.md and prints guidance instructing the user to paste content into Cursor → Settings → Rules → User Rules manually. If Cursor adopts a global path in the future, content is already there. types.ts: AgentName union extends to 9 agents. index.ts: REGISTRY adds cline / cursor / pi-mono entries. cli/index.ts: --help string lists all 9 agents. End-to-end tested 2026-05-09 on macOS: - detect: pi-mono / cursor → false (not installed locally), cline → true (~/Library/Application Support/cline already exists from prior panda install). - cline install/uninstall/reinstall — all ✓ idempotent (marker count 1 after 2 installs, 0 after uninstall). Skipped (per ADR-001 deferred): - copilot — per-project (.github/copilot-instructions.md), not a global install target. Needs interface extension for installPerProject(projectDir) before it can land. - windsurf / kilocode / antigravity — global instruction file paths uncertain; defer until verified or feature requested.
… TODO)
User has set PYPI_API_TOKEN as a repo secret (project-scoped legacy
token). Switch the publish job from OIDC Trusted Publishing to API
token auth so the first releases can ship without additional PyPI
dashboard configuration.
Trusted Publishing is still the long-term goal (no long-lived
token, finer-grained per-workflow scope, no rotation burden). The
migration recipe is now documented in RELEASE.md "Recommended
long-term" section — when ready, swap permissions/environment in
the workflow and delete the secret in one PR.
Workflow changes:
- Removed environment: pypi (only needed for OIDC).
- Removed permissions: id-token: write.
- Added with.password: ${{ secrets.PYPI_API_TOKEN }} on the
pypa/gh-action-pypi-publish step.
Tag a release as before:
git tag openwolf-hermes-v0.1.0 && git push --tags
The PYPI_API_TOKEN secret value is never logged — gh-action-pypi-publish
masks it. If the token is ever exposed in logs, revoke at
https://pypi.org/manage/account/token/ and rotate.
Replaces API token auth with Trusted Publishing per https://pypi.org/manage/account/publishing/. Project doesn't exist on PyPI yet, so first publish uses a Pending Publisher — see RELEASE.md for setup steps. Workflow changes: - Re-add environment: { name: pypi, url: ... } - Re-add permissions: id-token: write - Remove with.password (OIDC fills it in) After PyPI Pending Publisher is registered + first tag is pushed, the PYPI_API_TOKEN secret can be deleted: gh secret delete PYPI_API_TOKEN --repo ChasLui/openwolf RELEASE.md now leads with Trusted Publishing as the canonical path and keeps API token as a documented fallback for outages.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Multi-Agent Runtime PR
Summary
This PR adds a
--agent <name>flag toopenwolf init, supporting 8 newAI coding agents in addition to Claude Code:
~/.codex/AGENTS.mdmarker block~/.gemini/GEMINI.mdmarker block~/.config/opencode/openwolf-instructions.md+ patchesopencode.jsoninstructions[]~/.openclaw/workspace/AGENTS.mdmarker blockhermes_agent.plugins.openwolf), installed into Hermes' venv viauv pip install+~/.hermes/config.yamlplugins.enabledpatch. Published to PyPI asopenwolf-hermes 0.1.0~/Library/Application Support/cline/rules.mdon macOS, XDG/AppData on Linux/Windows)~/.cursor/USER_RULES.md(experimental — Cursor has no documented global rules path as of 2026-05; adapter writes the file and prints guidance to paste into Cursor → Settings → Rules)~/.pi/agent/AGENTS.md(or$PI_CODING_AGENT_DIR/AGENTS.md). Verified against badlogic/pi-mono docsDefault behavior (no
--agentflag) is bit-for-bit identical to v1.0.4:the original
initCommand()flow runs unchanged. Existing users see nodifference.
Architecture
A new
src/agents/directory introduces anAgentAdapterinterface:Each adapter encapsulates one agent's quirks: where to write the integration
file, what hook protocol the agent speaks (if any), how to parse/emit hook
JSON. Core OpenWolf logic (anatomy / cerebrum / token-ledger) stays
agent-agnostic. The shared
OPENWOLF_SNIPPETconstant insrc/agents/openwolf-snippet.tsis the single source of truth for thesoft-instruction protocol; both
snippets/openwolf-cross-agent.md(humandocs) and the embedded const must stay in sync when the protocol changes.
Full design rationale + rejected alternatives in
docs/adr/ADR-001-multi-agent-runtime.md.Honest Limitations
OpenWolf's full power (auto-injecting anatomy descriptions before reads,
catching repeated reads, post-write anatomy refresh) requires hooks at the
agent's
Read/Write/Edittool boundaries. Of the 8 new agents:interception (no file-op matchers). Soft instructions only.
instructions[]array — clean injection, but noruntime hook (we do not ship a TS plugin; agent reads
.wolf/voluntarily per the instructions).
via their respective rules / instructions file.
pre_tool_callhook can write to.wolf/memory.md+token-ledger.jsonon file ops, plus a/openwolfslash command (status / scan). Hermes API has nosystem-prompt injection point so it cannot auto-inject anatomy
descriptions like Claude does.
Only claude still gets the full hook-driven experience. The other 8
agents get the "agent reads
.wolf/voluntarily" experience, which isstrictly better than no integration but weaker than Claude's. Documented
per-adapter in
src/agents/*.tsheaders and in ADR-001 "Findings duringPhase 1a".
Hermes plugin distribution
The
openwolf-hermesPython plugin ships as a separate PyPI package, notbundled in the npm
openwolfpackage:src/agents/hermes/python/(this PR adds it).github/workflows/publish-hermes-plugin.ymlbuilds + publishes viaPyPI Trusted Publishing (OIDC), tag-triggered on
openwolf-hermes-v*uv pip install -e <path>(dev install) to plainuv pip install openwolf-hermesfor users without a fork checkoutTesting
End-to-end tested locally (macOS arm64, all reachable agents installed and
live). The 4 agents the test machine had installed all passed
install/uninstall/idempotent/reinstall flows:
detect()as expectedtsc --noEmitreports 0 type errors on all new code. Pre-existing errorsin
src/cli/,src/hooks/,bin/are upstream and untouched (need@types/nodeinstall).Phased landing
If a single 13-commit PR is too much, this can land in 5 separate PRs:
bddf690,be6c54e): documentation + AgentAdapterskeleton. Zero behavior change.
13e3d66,963fbc8): two soft-instructionadapters +
--agentflag.981084f): two more adapters.c08bb69,a40b0dd): Python plugin + adapter.adef463,db60152,bdd4b96,e115b3f,48c7a3e,d5821fe): GitHub Actions for PyPI publishing,pi-mono / cline / cursor adapters, OIDC Trusted Publishing config,
PR description draft.
Happy to split if preferred.
Out of scope (future PRs)
src/cli/init.tsHOOK_SETTINGS+ Claude-specific
.claude/settings.jsonwrite logic still lives ininit.ts.Phase 1d in ADR-001 plans to refactor it into
ClaudeAdapter.installGlobal()so Claude becomes "just anotheradapter". Behavior identical, code organization cleaner. Holding back
to keep this PR review-able.
.github/copilot-instructions.md),doesn't fit the
installGlobalinterface. Needs aninstallPerProject(projectDir)extension first.uncertain; defer until verified or feature requested.
approach avoids any in-process AGPL plugin. The Hermes Python plugin
is in-process — its
pyproject.tomldeclareslicense = "AGPL-3.0-only", but Hermes itself is not AGPL. Wouldvalue upstream's perspective on whether this license posture is OK or
needs adjustment.
Manual testing only. Would add
vitest+ per-adapter tests ifdirection is acceptable.
Author note
I'm a heavy OpenWolf user across 9 agents (claude + 8 above) and was
about to write 8 separate plugins as workarounds. The adapter pattern in
this PR pushes the common code (anatomy / cerebrum / ledger maintenance)
back into OpenWolf core where it belongs, with thin per-agent wrappers.
The PyPI Trusted Publishing pipeline is also configured so Hermes plugin
releases are cryptographically tied to this repo's CI — no long-lived
secrets.
Happy to discuss direction, scope, or split the PR however maintainers
prefer.
— Chao Liu (@ChasLui)