Skip to content
Open
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
65 changes: 65 additions & 0 deletions .github/workflows/publish-hermes-plugin.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: Publish openwolf-hermes to PyPI

on:
push:
tags:
- "openwolf-hermes-v*"
workflow_dispatch:
inputs:
dry_run:
description: "Build only, do not upload to PyPI"
type: boolean
default: false

permissions:
contents: read

jobs:
build:
name: Build sdist + wheel
runs-on: ubuntu-latest
defaults:
run:
working-directory: src/agents/hermes/python
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Install build tools
run: python -m pip install --upgrade build twine

- name: Build distributions
run: python -m build

- name: Verify with twine
run: python -m twine check dist/*

- uses: actions/upload-artifact@v4
with:
name: openwolf-hermes-dist
path: src/agents/hermes/python/dist/*

publish:
name: Publish to PyPI
needs: build
if: ${{ startsWith(github.ref, 'refs/tags/openwolf-hermes-v') && github.event.inputs.dry_run != 'true' }}
runs-on: ubuntu-latest
# Auth: PyPI Trusted Publishing (OIDC). PyPI must have a publisher
# registered for ChasLui/openwolf + this workflow + pypi environment.
# First release uses a "pending publisher" since the project does not
# yet exist on PyPI; the pending entry auto-promotes on first upload.
# See src/agents/hermes/python/RELEASE.md for setup steps.
environment:
name: pypi
url: https://pypi.org/p/openwolf-hermes
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v4
with:
name: openwolf-hermes-dist
path: dist
- uses: pypa/gh-action-pypi-publish@release/v1
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,5 @@ reframe/
openwolf-icon.zip
openwolf-blueprint.md
openwolf-readme-prompt.md

.pnpm-store
161 changes: 161 additions & 0 deletions docs/UPSTREAM-PR.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
# Multi-Agent Runtime PR

## Summary

This PR adds a `--agent <name>` flag to `openwolf init`, supporting **8 new
AI coding agents** in addition to Claude Code:

| Agent | Notes | Closes |
|-------|-------|--------|
| **codex** | `~/.codex/AGENTS.md` marker block | #2 |
| **gemini** | `~/.gemini/GEMINI.md` marker block | #22 |
| **opencode** | `~/.config/opencode/openwolf-instructions.md` + patches `opencode.json` `instructions[]` | #5, #6 |
| **openclaw** | `~/.openclaw/workspace/AGENTS.md` marker block | — |
| **hermes** | Python plugin (entry-point `hermes_agent.plugins.openwolf`), installed into Hermes' venv via `uv pip install` + `~/.hermes/config.yaml` `plugins.enabled` patch. **Published to PyPI as [`openwolf-hermes 0.1.0`](https://pypi.org/project/openwolf-hermes/0.1.0/)** | — |
| **cline** | OS-aware Cline rules path (`~/Library/Application Support/cline/rules.md` on macOS, XDG/AppData on Linux/Windows) | — |
| **cursor** | `~/.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-mono** | `~/.pi/agent/AGENTS.md` (or `$PI_CODING_AGENT_DIR/AGENTS.md`). Verified against [badlogic/pi-mono](https://github.com/badlogic/pi-mono) docs | — |

Default behavior (no `--agent` flag) is **bit-for-bit identical to v1.0.4**:
the original `initCommand()` flow runs unchanged. Existing users see no
difference.

## Architecture

A new `src/agents/` directory introduces an `AgentAdapter` interface:

```typescript
interface AgentAdapter {
name: AgentName;
detect(): boolean;
installGlobal(opts: InstallOpts): Promise<void>;
uninstallGlobal(): Promise<void>;
parseHookInput(stdin: string): NormalizedHookInput;
emitHookOutput(decision: HookDecision): string;
projectDirEnvVar: string;
}
```

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_SNIPPET` constant in
`src/agents/openwolf-snippet.ts` is the single source of truth for the
soft-instruction protocol; both `snippets/openwolf-cross-agent.md` (human
docs) 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`](https://github.com/ChasLui/openwolf/blob/dev/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` / `Edit` tool boundaries. Of the 8 new agents:

- **codex / gemini**: hook protocols only support shell-command
interception (no file-op matchers). Soft instructions only.
- **opencode**: has `instructions[]` array — clean injection, but no
runtime hook (we do not ship a TS plugin; agent reads `.wolf/`
voluntarily per the instructions).
- **openclaw / cline / cursor / pi-mono**: same soft-instruction pattern
via their respective rules / instructions file.
- **hermes**: Python plugin with `pre_tool_call` hook can write to
`.wolf/memory.md` + `token-ledger.json` on file ops, plus a
`/openwolf` slash command (status / scan). Hermes API has no
system-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 is
strictly better than no integration but weaker than Claude's. Documented
per-adapter in `src/agents/*.ts` headers and in ADR-001 "Findings during
Phase 1a".

## Hermes plugin distribution

The `openwolf-hermes` Python plugin ships as a separate PyPI package, not
bundled in the npm `openwolf` package:

- Source lives in `src/agents/hermes/python/` (this PR adds it)
- `.github/workflows/publish-hermes-plugin.yml` builds + publishes via
PyPI Trusted Publishing (OIDC), tag-triggered on `openwolf-hermes-v*`
- v0.1.0 already published 2026-05-09 → https://pypi.org/project/openwolf-hermes/0.1.0/
- After this PR merges, the HermesAdapter can switch from
`uv pip install -e <path>` (dev install) to plain
`uv pip install openwolf-hermes` for users without a fork checkout

## Testing

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:

| Agent | Install | Idempotent (reinstall ×2) | Uninstall | Reinstall |
|-------|---------|--------------------------|-----------|-----------|
| codex | ✅ | ✅ count=1 | ✅ | ✅ |
| gemini | ✅ | ✅ count=1 | ✅ | ✅ |
| opencode | ✅ instructions.md + opencode.json | ✅ no dup path | ✅ both removed | ✅ |
| openclaw | ✅ | ✅ count=1 | ✅ | ✅ |
| hermes | ✅ openwolf-hermes 0.1.0 in venv + config.yaml plugins.enabled | ✅ entry count=1 | ✅ pkg uninstalled, config rolled back | ✅ |
| cline | ✅ ~/Library/Application Support/cline/rules.md | ✅ count=1 | ✅ | ✅ |
| pi-mono / cursor | not installed locally — adapters fail-fast on `detect()` as expected | — | — | — |

`tsc --noEmit` reports 0 type errors on all new code. Pre-existing errors
in `src/cli/`, `src/hooks/`, `bin/` are upstream and untouched (need
`@types/node` install).

## Phased landing

If a single 13-commit PR is too much, this can land in 5 separate PRs:

1. **ADR-only** (`bddf690`, `be6c54e`): documentation + AgentAdapter
skeleton. Zero behavior change.
2. **Codex + Gemini** (`13e3d66`, `963fbc8`): two soft-instruction
adapters + `--agent` flag.
3. **OpenClaw + OpenCode** (`981084f`): two more adapters.
4. **Hermes** (`c08bb69`, `a40b0dd`): Python plugin + adapter.
5. **CI + extra agents + docs** (`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)

- **ClaudeAdapter refactor**: `src/cli/init.ts` `HOOK_SETTINGS` + Claude-
specific `.claude/settings.json` write logic still lives in `init.ts`.
Phase 1d in ADR-001 plans to refactor it into
`ClaudeAdapter.installGlobal()` so Claude becomes "just another
adapter". Behavior identical, code organization cleaner. Holding back
to keep this PR review-able.
- **Copilot adapter**: per-project (`.github/copilot-instructions.md`),
doesn't fit the `installGlobal` interface. Needs an
`installPerProject(projectDir)` extension first.
- **Windsurf / kilocode / antigravity**: global instruction file paths
uncertain; defer until verified or feature requested.
- **AGPL-3.0 derivative concerns**: the OpenCode soft-instruction
approach avoids any in-process AGPL plugin. The Hermes Python plugin
is in-process — its `pyproject.toml` declares
`license = "AGPL-3.0-only"`, but Hermes itself is not AGPL. Would
value upstream's perspective on whether this license posture is OK or
needs adjustment.
- **Test infrastructure**: no integration tests for adapters yet.
Manual testing only. Would add `vitest` + per-adapter tests if
direction 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)
146 changes: 146 additions & 0 deletions docs/adr/ADR-001-multi-agent-runtime.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# ADR-001: Multi-Agent Runtime

**Status**: Proposed (2026-05-09)
**Author**: Chao Liu (@ChasLui)
**Target branch**: `dev` of `ChasLui/openwolf` fork; intended for upstream PR after Phase 2.

## Context

OpenWolf v1.0.4 hard-codes the Claude Code hook protocol:
- `src/cli/init.ts` writes `.claude/settings.json` with Claude-specific `PreToolUse` / `PostToolUse` matchers (`Read`, `Write|Edit|MultiEdit`).
- Hook scripts in `src/hooks/*.ts` parse Claude's `tool_input` JSON shape.
- Templates in `src/templates/` reference `$CLAUDE_PROJECT_DIR` env var.

Real-world need: a single user runs **6 different AI coding agents** (claude, codex, gemini, opencode, openclaw, hermes) and wants OpenWolf's anatomy / cerebrum / memory / token-ledger benefits across all of them. Currently 5/6 agents get nothing.

Comparable projects (RTK, PandaFilter) already ship 8–11 agent enums via `--agent <X>` flag. OpenWolf is a generation behind on this dimension.

## Decision

**Refactor OpenWolf into a multi-agent runtime** with three layers:

1. **Agent registry** (`src/agents/`): one `AgentAdapter` per supported agent, encapsulating:
- Hook installation point (e.g. `~/.claude/settings.json` vs `~/.codex/hooks.json` vs `~/.config/opencode/plugins/openwolf.ts`)
- Hook input/output JSON schema (Claude `tool_input` vs Codex `shell` matcher vs OpenCode plugin TS API)
- Tool-name normalization (Claude "Read" / Gemini "run_shell_command" / OpenCode "edit" → canonical `FileOp`)
- Project-dir env var (Claude `$CLAUDE_PROJECT_DIR` vs others)

2. **Init layer** (`src/cli/init.ts`): expose `--agent <X>` flag with enum:
- `claude` (default, current behavior)
- `codex`
- `gemini`
- `opencode`
- `openclaw`
- `hermes`
- `all` — auto-detect installed agents and install for each

3. **Hook implementation layer** (`src/hooks/*.ts`): refactor 6 hooks (session-start, pre-read, pre-write, post-read, post-write, stop) to:
- Read agent name from env / arg
- Use the corresponding `AgentAdapter` to parse input + emit output
- Keep core OpenWolf logic (anatomy injection, cerebrum lookup, token-ledger update) agent-agnostic

## Architecture

```
src/
├── agents/ # NEW
│ ├── types.ts # AgentAdapter interface, FileOp canonical type
│ ├── claude.ts # ClaudeAdapter (refactor existing logic)
│ ├── codex.ts # CodexAdapter
│ ├── gemini.ts # GeminiAdapter
│ ├── opencode.ts # OpenCodeAdapter (TS plugin host)
│ ├── openclaw.ts # OpenClawAdapter
│ ├── hermes.ts # HermesAdapter (Python plugin host — see Phase 3)
│ └── index.ts # registry + detect()
├── cli/init.ts # CHANGED — accept --agent flag
├── hooks/ # CHANGED — adapter-aware
└── templates/
├── claude-md-snippet.md # existing
├── codex-md-snippet.md # NEW
├── gemini-md-snippet.md # NEW
├── opencode-plugin-template.ts # NEW
└── hermes-plugin-template.py # NEW (Phase 3)
```

### `AgentAdapter` interface (preliminary)

```typescript
export interface AgentAdapter {
name: string; // "claude" | "codex" | ...
detect(): boolean; // is this agent installed?
installGlobal(opts: InstallOpts): Promise<void>; // patch settings file
uninstallGlobal(): Promise<void>;
hookInput(stdin: string): NormalizedHookInput; // parse agent-specific JSON
hookOutput(decision: HookDecision): string; // emit agent-specific JSON
projectDirEnvVar: string; // "$CLAUDE_PROJECT_DIR" | ...
}

export interface NormalizedHookInput {
tool: "read" | "write" | "edit" | "shell" | "session-start" | "stop";
filePath?: string;
command?: string;
raw: unknown;
}
```

## Rollout phases

| Phase | Scope | Effort | Deliverable |
|-------|-------|--------|-------------|
| **0** | This ADR + dev branch placeholder commit | 1 h | docs/adr/ADR-001 + src/agents/ skeleton dir |
| **1** | ClaudeAdapter (refactor) + CodexAdapter + GeminiAdapter + init --agent flag | 4–8 h | `openwolf init --agent codex` works |
| **2** | OpenCodeAdapter (TS plugin) + OpenClawAdapter | 4–8 h | 5/6 agents covered (hermes still TODO) |
| **3** | HermesAdapter via Python sub-package + PyPI publish | 8–16 h | 6/6 agents |
| **4** | PR to `cytostack/openwolf` upstream OR independent npm release `openwolf-multi-agent` | 4–8 h | Public release |

Total realistic estimate: **20–40 hours of focused work** spread over 1–2 weeks.

## Alternatives considered

1. **Plugin-only path** (each agent = independent npm/pip plugin, no fork):
- Pros: zero upstream coupling, each plugin can be released independently
- Cons: 5× duplicated boilerplate (anatomy parser, cerebrum logic, token-ledger writer); upstream changes break N plugins simultaneously
- **Rejected** because the hard part (anatomy/cerebrum/token-ledger) is shared logic, not agent-specific

2. **Soft-instructions only** (just inject `@OPENWOLF.md` into each agent's `AGENTS.md`):
- Pros: zero code change; 30-minute job
- Cons: degraded experience — agents must voluntarily read `.wolf/`, no hook-level enforcement; loses ~70% of OpenWolf's value
- **Rejected** because user requested "long-term, hard, correct" path

3. **Fork without abstraction** (just hard-code each agent's hook in init.ts):
- Pros: faster initial implementation
- Cons: every new agent = N×M coupling; no plugin model for community contributions
- **Rejected** for long-term maintainability

## Findings during Phase 1a (2026-05-09)

**Codex and Gemini hook protocols cannot host file-op hooks.** Verified against:
- `~/.codex/hooks.json` written by `panda init --agent codex` v1.3.5
- `~/.codex/panda-rewrite.sh` source (panda 1.3.5)
- `~/.gemini/settings.json` written by `rtk init --gemini` and `panda init --gemini`

Both agents only support shell-command interception:
- Codex: `matcher: "shell"` in PreToolUse / PostToolUse, no Read/Write/Edit
- Gemini: `matcher: "run_shell_command"` in BeforeTool, no file-op matchers

Implication for OpenWolf 6-hook surface (session-start / pre-read / pre-write / post-read / post-write / stop):
- **Claude**: 6/6 hooks installable (current behavior)
- **Codex / Gemini**: 0/6 file-op hooks; only shell-command hook + soft-instructions (`@OPENWOLF.md` ref in AGENTS.md / GEMINI.md)
- **OpenCode / OpenClaw**: TBD in Phase 2 (TS plugin / openclaw.json hook system)
- **Hermes**: TBD in Phase 3 (Python plugin via `pre_tool_call`)

**Decision**: Mark codex/gemini as "degraded" tier. The adapter still does what's possible (shell hook + soft-instruction injection) but documents the gap. Users on codex/gemini are pointed to claude for full OpenWolf experience.

## Open questions

1. **Upstream relationship**: Will `cytostack/openwolf` accept this PR? Should we `git remote add upstream` and PR-driven from the start, or develop independently and propose later?
2. **License**: AGPL-3.0 → any in-process plugin (TS plugin in OpenCode, Python plugin in Hermes) becomes a derivative work. Does that block opencode/hermes integration?
3. **Hermes `pre_tool_call` hook**: The `rtk-hermes` PyPI plugin proves it's feasible — we should study its source as reference.
4. **OpenCode plugin API stability**: Plugin TS API may change between OpenCode versions. Does OpenWolf pin a min OpenCode version?
5. **Test strategy**: How to integration-test 6 agents in CI without spinning up real LLM sessions?

## Out of scope

- Hook protocol unification across agents (each agent's native hook is what users get; OpenWolf adapter just normalizes input/output, doesn't change Claude's PreToolUse vs Codex's `shell` matcher semantics).
- Replacing the daemon / dashboard (Claude-only by design, kept as-is).
- Adding new agents not in the 6-agent target list (cursor / windsurf / cline / copilot — defer until v2).
Loading