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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ All notable changes to vouch are documented here. Format follows
relation proposals automatically, tagged `proposed_by: vouch-extractor`.
They land in `proposed/` like any hand-filed relation and need the usual
review; `vouch reject-extracted [--page <id>]` mass-rejects them (#224).
- `vouch install-mcp openclaw` — ninth host in the adapter catalogue.
Declares plugin enablement (`.openclaw/plugins.json`), an `AGENTS.md`
fenced snippet, the four slash commands reused in place from the
`claude-code` adapter, and a project-local trust-boundary policy
(`.openclaw/policy.json`). Complements the repo-root
`openclaw.plugin.json` bundle manifest, which covers loading vouch into
an OpenClaw deployment rather than into one managed project (#230).
### Added
- `vouch sync --vault <dir>` — bidirectional sync between the KB and an
Obsidian/Logseq-style markdown vault. Forward (vault → KB): edits to
Expand Down
1 change: 1 addition & 0 deletions adapters/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ file you need into your project and edit it.
| [cursor/](cursor/) | Cursor IDE | `mcp.json` snippet |
| [codex/](codex/) | OpenAI's Codex CLI | `config.toml` snippet |
| [continue/](continue/) | Continue.dev | `config.json` snippet |
| [openclaw/](openclaw/) | OpenClaw plugin host | `.openclaw/plugins.json`, `AGENTS.md` excerpt |
| [generic-mcp/](generic-mcp/) | Any MCP-speaking host | annotated reference |
| [jsonl-shell/](jsonl-shell/) | bash scripts via the JSONL transport | example pipeline |

Expand Down
21 changes: 21 additions & 0 deletions adapters/openclaw/AGENTS.md.snippet
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Vouch — knowledge base (OpenClaw plugin)

This project loads vouch via the OpenClaw plugin manifest at
[`openclaw.plugin.json`](https://github.com/vouchdev/vouch/blob/main/openclaw.plugin.json)
in the vouch repo. The plugin exports the `vouch serve` MCP surface,
the four bundled slash commands (`/vouch-recall`, `/vouch-status`,
`/vouch-resolve-issue`, `/vouch-propose-from-pr`), and a trust
boundary: every write tool is review-gated, every lifecycle op is
audit-logged, and remote callers' filesystem access is confined.

You **cannot** write durable knowledge directly. Proposals land in
`.vouch/proposed/` and require human approval via `vouch approve`.
This is intentional.

- `kb_search` / `kb_context` to read.
- `kb_propose_claim` / `kb_propose_page` to suggest additions — every
claim MUST cite at least one source or evidence id.
- `kb_supersede` to replace a stale claim, `kb_contradict` to flag a
conflict for a human to resolve.

You are recorded as `proposed_by: openclaw` in the audit log.
33 changes: 33 additions & 0 deletions adapters/openclaw/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# openclaw adapter

Two different things both go by "the OpenClaw integration":

1. **Loading vouch into an OpenClaw deployment.** That's the repo-root
[`openclaw.plugin.json`](../../openclaw.plugin.json) manifest — drop
the vouch repo into a deployment that vendors plugin repos and the
loader picks up the MCP server, the four slash commands, and the
trust-boundary declaration automatically. See the README section
"Running vouch as an OpenClaw plugin".
2. **Enabling vouch in one OpenClaw-managed project.** That's this
adapter, run with `vouch install-mcp openclaw --path <project>`.

The writer drops:

- `.openclaw/plugins.json` — declares the vouch plugin enabled for this
project (T1).
- `AGENTS.md` — a fenced snippet pointing at the plugin manifest and
summarizing the review-gate contract (T2).
- `.claude/commands/vouch-*.md` — the same four slash commands the
claude-code adapter ships, referenced in place rather than duplicated
(T3).
- `.openclaw/policy.json` — the trust boundary as project-local policy:
review-gated writes, audit-logged lifecycle ops, confined filesystem
access for remote callers (T4).

```sh
vouch install-mcp openclaw --path .
```

Re-running is idempotent: existing files are left alone, and `AGENTS.md`
gets the vouch block appended once inside a fence so reruns don't
duplicate it.
37 changes: 37 additions & 0 deletions adapters/openclaw/install.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# OpenClaw adapter manifest.
#
# OpenClaw loads vouch as a bundle plugin via the repo-root
# `openclaw.plugin.json` manifest (see README.md "Running vouch as an
# OpenClaw plugin"). That covers the *deployment* side -- dropping the
# vouch repo into an OpenClaw install. This adapter covers the
# per-*project* side: a project that wants vouch enabled gets a small
# set of OpenClaw-native files, mirroring the claude-code adapter's tiers.
#
# T1 = `.openclaw/plugins.json` -- declares the vouch plugin enabled for
# this project (the project-local analogue of running
# `openclaw plugin enable vouch`).
# T2 = AGENTS.md fenced snippet pointing at the plugin manifest and
# summarizing the review-gate contract.
# T3 = the same four slash commands the claude-code adapter ships,
# referenced in place rather than duplicated (see
# install_adapter.py's docstring; OpenClaw's loader bundles these
# from adapters/claude-code/ directly).
# T4 = `.openclaw/policy.json` -- the trust boundary as project-local
# policy (review-gated writes, audit-logged lifecycle, confined fs).
host: openclaw
pretty: OpenClaw
fence:
begin: "<!-- BEGIN vouch -->"
end: "<!-- END vouch -->"
Comment thread
jsdevninja marked this conversation as resolved.
tiers:
T1:
- { src: plugins.json, dst: .openclaw/plugins.json }
T2:
- { src: AGENTS.md.snippet, dst: AGENTS.md, fenced_append: true }
T3:
- { src: ../claude-code/.claude/commands/vouch-recall.md, dst: .claude/commands/vouch-recall.md }
- { src: ../claude-code/.claude/commands/vouch-status.md, dst: .claude/commands/vouch-status.md }
- { src: ../claude-code/.claude/commands/vouch-resolve-issue.md, dst: .claude/commands/vouch-resolve-issue.md }
- { src: ../claude-code/.claude/commands/vouch-propose-from-pr.md, dst: .claude/commands/vouch-propose-from-pr.md }
Comment thread
jsdevninja marked this conversation as resolved.
T4:
- { src: policy.json, dst: .openclaw/policy.json }
11 changes: 11 additions & 0 deletions adapters/openclaw/plugins.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"plugins": {
"vouch": {
"source": "github:vouchdev/vouch",
"enabled": true,
Comment thread
jsdevninja marked this conversation as resolved.
"config": {
"agent": "openclaw"
}
}
}
}
8 changes: 8 additions & 0 deletions adapters/openclaw/policy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"vouch": {
"review_gated_writes": true,
"audit_logged_lifecycle": true,
"remote_callers_filesystem": "confined",
"agent": "openclaw"
Comment thread
jsdevninja marked this conversation as resolved.
}
}
2 changes: 2 additions & 0 deletions src/vouch/jsonl_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
from pathlib import Path
from typing import Any

import yaml

from . import audit, bundle, health, volunteer_context
from . import lifecycle as life
from . import salience as salience_mod
Expand Down
2 changes: 1 addition & 1 deletion src/vouch/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import uuid
from datetime import UTC, datetime

from . import audit, index_db, volunteer_context
from . import audit, index_db, salience, volunteer_context
from .models import Page, PageType, ProposalStatus, Session
from .proposals import approve
from .storage import KBStore
Expand Down
48 changes: 47 additions & 1 deletion tests/test_install_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,52 @@ def test_install_claude_md_skips_when_already_fenced(tmp_path: Path) -> None:
assert "CLAUDE.md" not in again.appended


# --- openclaw: second adapter with all four tiers, T3 reused from
# claude-code rather than duplicated (vouchdev/vouch#230) --------------------


def test_install_openclaw_t4_writes_all_tiers(tmp_path: Path) -> None:
result = install("openclaw", target=tmp_path, tier="T4")
assert (tmp_path / ".openclaw" / "plugins.json").is_file()
assert (tmp_path / "AGENTS.md").is_file()
cmd_dir = tmp_path / ".claude" / "commands"
assert (cmd_dir / "vouch-recall.md").is_file()
assert (cmd_dir / "vouch-status.md").is_file()
assert (cmd_dir / "vouch-resolve-issue.md").is_file()
assert (cmd_dir / "vouch-propose-from-pr.md").is_file()
assert (tmp_path / ".openclaw" / "policy.json").is_file()
# T1 plugins.json + T2 AGENTS.md + 4 T3 commands + T4 policy.json = 7.
assert len(result.written) == 7, result.written


def test_install_openclaw_t3_commands_match_claude_code(tmp_path: Path) -> None:
"""T3 is declared as a reuse of claude-code's commands, not a fork --
the manifest's `src` points at adapters/claude-code/ directly."""
install("openclaw", target=tmp_path, tier="T3")
for name in (
"vouch-recall.md", "vouch-status.md",
"vouch-resolve-issue.md", "vouch-propose-from-pr.md",
):
installed = (tmp_path / ".claude" / "commands" / name).read_text(encoding="utf-8")
ref_path = REPO_ROOT / "adapters" / "claude-code" / ".claude" / "commands" / name
assert installed == ref_path.read_text(encoding="utf-8")


def test_install_openclaw_is_idempotent(tmp_path: Path) -> None:
install("openclaw", target=tmp_path, tier="T4")
second = install("openclaw", target=tmp_path, tier="T4")
assert second.written == []
assert set(second.skipped) == {
".openclaw/plugins.json",
"AGENTS.md",
".claude/commands/vouch-recall.md",
".claude/commands/vouch-status.md",
".claude/commands/vouch-resolve-issue.md",
".claude/commands/vouch-propose-from-pr.md",
".openclaw/policy.json",
}


# --- error paths ----------------------------------------------------------


Expand All @@ -157,7 +203,7 @@ def test_install_unknown_tier_raises(tmp_path: Path) -> None:

@pytest.mark.parametrize("host", [
"cursor", "continue", "codex", "claude-desktop",
"windsurf", "cline", "zed",
"windsurf", "cline", "zed", "openclaw",
])
def test_each_host_writes_its_t1_file(host: str, tmp_path: Path) -> None:
"""Smoke test: every shipped host must produce at least one file at T1."""
Expand Down
Loading