Skip to content
Closed
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
38 changes: 38 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ All notable changes to vouch are documented here. Format follows
committed SVGs stay reproducible (#286).

### Added
- `kb.timeline` / `vouch timeline <entity>` — a read-only chronological
trajectory of an entity's approved claims and relations (#313). orders them
along a time axis oldest-first: `--order effective` uses artifact `created_at`,
`--order decided` recovers approval time from the audit log. `--since` /
`--until` / `--types` / `--limit` filters; `--json` for the machine shape.
superseded/archived claims still appear, flagged by current status; relations
carry `status = null`; pending proposals never appear. pure read — no write
path is reachable from it. registered at all four surfaces (mcp/jsonl/
capabilities/cli) and attaches `_meta.vouch_salience` when a `session_id` is
passed.
- auto-capture: claude code sessions are harvested via hooks and filed as a
single pending session-summary proposal for human approval. a `PostToolUse`
hook (`vouch capture observe`) appends compact tool-use observations to an
Expand All @@ -37,6 +47,11 @@ All notable changes to vouch are documented here. Format follows
strategy) instead of skipping it, so the capture / recall hooks land on
projects that already have a settings file. idempotent; user entries are
preserved.
- `vouch new <kind>` — scaffold a typed page or entity proposal from the
page-kind registry: stubs required frontmatter fields, supports
`--field key=value`, `--interactive`, `--dry-run`, and `--json`; entity
kinds (`person`, `project`, …) route to `propose_entity`, with page kinds
taking precedence on name collisions unless `--entity` is set (#330).
- GitHub PR auto-labeling: a pull-request metadata-only labeler workflow now
applies vouch surface labels from `.github/labeler.yml`, keeps those labels
in sync as files change, and adds OpenClaw-style `size: XS` through
Expand Down Expand Up @@ -123,6 +138,29 @@ All notable changes to vouch are documented here. Format follows
KB under `eval/fixture-kb/`, and an `eval` workflow gating retrieval changes
(#226).
### Fixed
- the OpenClaw plugin packaging now targets the current (2026.6) plugin
loader, verified against a real `openclaw plugins install --link` of the
repo: `openclaw.plugin.json` moved to the `id` + JSON-Schema `configSchema`
dialect (`kind: context-engine`, `skills` as SKILL.md directories under
`adapters/openclaw/skills/`), a root `package.json` now carries the
loader-facing `openclaw.extensions` entry-module pointer and the
`openclaw.compat.pluginApi` floor, and the engine id was renamed
`vouch-context` → `vouch` so it matches the plugin id — OpenClaw's
installer auto-binds the contextEngine slot to the *plugin* id and
resolves it by *engine* id, so distinct ids silently quarantined the
engine in favour of the legacy engine. The old dialect's `mcpServers`,
`contracts`, `family`, `shared_deps`, and `openclaw.*` fields were
silently ignored by current loaders and are gone; the kb.* MCP server is
deployment config (`openclaw mcp add vouch -- vouch serve`). A Tier-2
e2e suite (`tests/test_openclaw_plugin_load_real.py`, skipped when the
`openclaw` CLI is absent) now links the repo into an isolated profile and
asserts import, engine registration, slot auto-bind, skill publication,
and a clean plugins doctor.
- `vouch openclaw-rpc` no longer crashes serializing `assemble` responses:
`contextPack.generated_at` is a `datetime`, which `json.dumps` rejected on
any turn that found a KB — OpenClaw quarantined the engine for the process
and silently fell back to its legacy engine. Found by running a real
OpenClaw agent turn against the linked plugin.
- `vouch pending` (and every bulk `list_*` path) no longer crashes when a
single artifact file is unreadable — a corrupt or mojibake yaml is skipped
with a warning instead of aborting the whole listing.
Expand Down
63 changes: 43 additions & 20 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,31 +160,54 @@ about this; it's checked in PR review.
| Sessions | `src/vouch/sessions.py` |
| Manifest-driven adapter writer | `src/vouch/install_adapter.py` |
| Web review-ui (when PR #195 lands) | `src/vouch/web/` |
| OpenClaw plugin manifest | `openclaw.plugin.json` (repo root) |
| OpenClaw plugin manifest | `openclaw.plugin.json` + `package.json` (repo root) |
| Claude Code / Cursor / etc. install templates | `adapters/<host>/` |

Tests mirror module names (`tests/test_<module>.py`); the convention is
strict.

## The OpenClaw plugin manifest

[`openclaw.plugin.json`](./openclaw.plugin.json) at the repo root makes the
vouch repo loadable directly as an OpenClaw plugin: drop the repo into a
deployment, and the loader picks up the MCP server, the four slash commands
under `adapters/claude-code/.claude/commands/`, and the trust-boundary
declaration. Touch it whenever you:

* bump the package version (`version` field must stay in step with
`pyproject.toml`),
* add or rename a slash command (sync the `skills` array),
* add a new MCP method that's safe to expose to remote callers (consider
whether to list it under `contracts.mcpMethods`),
* change the trust boundary (e.g. a new "must-be-confined" surface that
arrives with the HTTP transport).

Keep it small. Anything that would require a runtime decision (which kb to
use, whose audit log to write to) belongs in the deployment's own config,
not in the plugin manifest.
## The OpenClaw plugin packaging

Two files make the vouch repo loadable directly as an OpenClaw plugin
(`openclaw plugins install --link <repo>`):

* [`openclaw.plugin.json`](./openclaw.plugin.json) — the manifest the
loader parses: `id` + `configSchema` (required), `kind: context-engine`,
`version`, and `skills` (directories under `adapters/openclaw/skills/`,
one SKILL.md each; OpenClaw publishes them as skills *and* slash
commands).
* [`package.json`](./package.json) — loader-facing only: the
`openclaw.extensions` pointer at the JS entry module and the
`openclaw.compat.pluginApi` floor. The Python package stays in
`pyproject.toml`.

Invariants, all enforced by `tests/test_openclaw_plugin_manifest.py`:

* **One id everywhere.** The manifest `id`, the Python `ENGINE_ID`, and the
entry module's export id are all `vouch`. OpenClaw's installer auto-binds
`plugins.slots.contextEngine` to the *plugin* id and resolves that slot by
*engine* id — if they ever diverge, the engine is quarantined at resolve
time and OpenClaw silently falls back to its legacy engine.
* **Version in three places.** `pyproject.toml`, `openclaw.plugin.json`,
and `package.json` must agree.
* **Skills mirror the claude-code commands.** Each
`adapters/openclaw/skills/<name>/SKILL.md` body must match the
corresponding `adapters/claude-code/.claude/commands/<name>.md` body.
* **No dead dialect fields.** `mcpServers`, `contracts`, `family`,
`shared_deps`, `openclaw.*` in the manifest are silently ignored by
current loaders — the sync test rejects them. The kb.* MCP server is
deployment config (`openclaw mcp add vouch -- vouch serve`); the trust
boundary lives in `adapters/openclaw/policy.json`.

`tests/test_openclaw_plugin_load_real.py` is the live gate: with the
`openclaw` CLI on PATH it links the repo into an isolated profile and
asserts import, engine registration, slot auto-bind, skill publication,
and a clean `plugins doctor`. It skips (does not fail) where the CLI is
absent, e.g. GitHub CI.

Keep the manifest small. Anything that would require a runtime decision
(which kb to use, whose audit log to write to) belongs in the deployment's
own config, not in the plugin packaging.

## When you add a new `kb.*` method

Expand Down
45 changes: 27 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,15 @@ vouch migrate [--check] [--dry-run] # upgrade .vouch/ format safely

vouch pending # list pending proposals
vouch review [--limit N] [--type KIND] # guided proposal review queue
vouch show <proposal-id>
vouch show <proposal-id> # show pending proposal details
vouch read-claim <claim-id> # read an approved claim
vouch read-page <page-id> # read an approved page
vouch read-entity <entity-id> # read an approved entity
vouch read-relation <relation-id> # read an approved relation
vouch list-claims # list all approved claims
vouch list-pages # list all approved pages
vouch list-entities # list all approved entities
vouch list-relations # list all approved relations
vouch approve <proposal-id> [--reason ...]
vouch reject <proposal-id> --reason "..."
vouch expire [--apply] [--days N] [--json] # GC stale pending proposals
Expand Down Expand Up @@ -322,28 +330,29 @@ Its `.claude/settings.json` (tier T4) registers a `PostToolUse` hook (`vouch cap

## Running vouch as an OpenClaw plugin

Vouch ships an [OpenClaw](https://github.com/dripsmvcp/openclaw) plugin manifest at the
repo root — [`openclaw.plugin.json`](openclaw.plugin.json). Drop the vouch repo
into an OpenClaw deployment and the plugin loader picks it up automatically:
the MCP server, the four slash commands (`/vouch-recall`, `/vouch-status`,
`/vouch-resolve-issue`, `/vouch-propose-from-pr`), and the CLAUDE.md fenced
snippet become available as one bundle.
Vouch ships an [OpenClaw](https://github.com/dripsmvcp/openclaw) plugin at the
repo root — [`openclaw.plugin.json`](openclaw.plugin.json) plus a small
[`package.json`](package.json) that points the loader at the JS entry module.
Install the repo as a linked plugin and OpenClaw picks up two things
automatically:

The manifest declares vouch's trust boundary explicitly — remote callers'
filesystem access is confined, every write tool routes through the review
gate, every lifecycle op is audit-logged. The `configSchema` exposes only
`kb_path`, `agent`, and `transport` — no API keys, no secrets; vouch is
local-first.
* the **vouch context engine** — registered as `vouch` and auto-bound to
`plugins.slots.contextEngine` on install, it injects cited KB context
(retrieval + salience reflex + hot memory) into every agent turn, and
* the **four skills / slash commands** (`/vouch-recall`, `/vouch-status`,
`/vouch-resolve-issue`, `/vouch-propose-from-pr`).

```bash
# Inside an OpenClaw deployment that vendors plugin repos:
openclaw plugin add vouchdev/vouch
openclaw plugin enable vouch
openclaw plugins install --link /path/to/vouch
# the kb.* MCP tool surface is deployment config, same shape as every host:
openclaw mcp add vouch -- vouch serve
```

The plugin's `mcpServers.vouch` block matches the same `.mcp.json` shape
Claude Code uses — both platforms drive the same `vouch serve` process,
so the kb.* surface is identical regardless of host.
The `configSchema` exposes only `kb_path` and `agent` — no API keys, no
secrets; vouch is local-first. The trust boundary (confined filesystem for
remote callers, review-gated writes, audit-logged lifecycle ops) ships as
project-local policy via `vouch install-mcp openclaw` — see
[`adapters/openclaw/`](adapters/openclaw/).

## JSONL request/response shape

Expand Down
14 changes: 9 additions & 5 deletions adapters/openclaw/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@
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".
[`openclaw.plugin.json`](../../openclaw.plugin.json) manifest plus
[`package.json`](../../package.json) (the loader-facing
`openclaw.extensions` pointer at
[`vouch-context-engine.mjs`](./vouch-context-engine.mjs)) —
`openclaw plugins install --link <repo>` registers the context engine
(auto-bound to `plugins.slots.contextEngine`) and publishes the four
skills under [`skills/`](./skills/) as slash commands. The kb.* MCP
server is deployment config: `openclaw mcp add vouch -- vouch serve`.
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>`.

Expand Down
26 changes: 26 additions & 0 deletions adapters/openclaw/skills/vouch-propose-from-pr/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
name: vouch-propose-from-pr
description: Distill a merged PR into vouch claim proposals
---

# /vouch-propose-from-pr

A PR is a decision: someone proposed a change, the team accepted it. The
"why" gets compressed into the merge message and forgotten. This command
preserves the why as cited claims in the KB.

Steps:

1. Parse `$ARGUMENTS` as a PR URL or `<owner>/<repo>#<num>`; default to the
most-recently-merged PR you authored.
2. Fetch the PR title, body, and the merged-commit SHA via `gh`.
3. Register the merge commit as a `kb_register_source` so subsequent claims
can cite it.
4. Read the diff. For each *behavioural* change (not formatting / renaming),
draft one `kb_propose_claim` whose text summarises the new invariant the
code now upholds, citing the source from step 3.
5. Propose at most five claims per PR. If a PR is that big, suggest the
contributor split it next time.

Do not auto-approve. The KB's review gate is intentional; this command
only fills the queue.
21 changes: 21 additions & 0 deletions adapters/openclaw/skills/vouch-recall/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
name: vouch-recall
description: Recall what the project's vouch KB knows about a topic
---

# /vouch-recall

Use vouch's `kb_context` MCP tool to assemble a working set of claims, sources,
and entities the KB already has on the current topic. Print them with their
ids and citations; do not write anything.

Steps:

1. Call `kb_context` with `query: "$ARGUMENTS"`.
2. List every returned claim by id + one-line text; for each, show the
source ids it cites.
3. End with a one-sentence summary of what's *missing* from the KB on this
topic — the gap the user can fill with `/vouch-propose-from-pr` or
`kb_propose_claim`.

Be terse. The KB is meant to remove ambiguity, not pad it.
28 changes: 28 additions & 0 deletions adapters/openclaw/skills/vouch-resolve-issue/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
name: vouch-resolve-issue
description: Use vouch's KB to ground a fix for a GitHub issue
---

# /vouch-resolve-issue

Wire vouch's `kb_context` into an issue-resolution flow: the KB should
inform the fix, and the act of solving should propose new claims that
make the next contributor faster.

Steps:

1. Parse `$ARGUMENTS` as a GitHub issue URL or `<owner>/<repo>#<num>` shorthand.
If neither, ask for clarification.
2. `kb_context` with the issue title + body — what does the KB already know
about this area? Show the top 5 claims.
3. Read the relevant code paths.
4. Propose the smallest fix (run the project's tests first to confirm the
bug reproduces).
5. After the fix is committed, propose **at most three** new claims via
`kb_propose_claim` that capture:
* the root cause in one sentence (cited by the offending file:line),
* the chosen fix pattern (cited by the patch commit), and
* any policy/precedent established (only if novel).

Do not auto-approve. Leave the proposals in `.vouch/proposed/` for the
maintainer to review with `vouch approve` after the PR merges.
24 changes: 24 additions & 0 deletions adapters/openclaw/skills/vouch-status/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
name: vouch-status
description: Show the project's vouch KB at a glance
---

# /vouch-status

Run vouch's `kb_status` MCP tool and surface the result. Use this when the
user asks "what's in our KB?" or before/after a long claim-proposal flow so
they can see the proposal count tick up.

Format:

```
KB at <root>
claims: <n>
sources: <n>
entities: <n>
pending: <n> ← review queue depth
last audit: <iso8601 timestamp>
```

If `pending > 0`, suggest the user run `vouch approve <id>` (or `vouch lint`
if they want to inspect anything first). Do not propose anything yourself.
20 changes: 14 additions & 6 deletions adapters/openclaw/vouch-context-engine.mjs
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
/**
* OpenClaw plugin entry for the vouch-context engine (#228).
*
* The host loads this module via openclaw.plugin.json → openclaw.extensions.
* The host loads this module via package.json → openclaw.extensions; the
* plugin id below must match the `id` in openclaw.plugin.json or the loader
* rejects the import ("plugin id mismatch").
* Runtime assembly delegates to the Python engine through `vouch openclaw-rpc`
* so the cited synthesis path stays identical to unit tests and kb.context.
*
* Enable in openclaw.json:
* plugins.slots.contextEngine: "vouch-context"
* Enable in openclaw.json (the installer auto-binds this on install):
* plugins.slots.contextEngine: "vouch"
*
* The engine id equals the plugin id on purpose — OpenClaw's installer,
* doctor, and gateway-startup paths all treat the contextEngine slot value
* as a plugin id, so distinct ids would quarantine the engine at resolve
* time and silently fall back to the legacy engine.
*/

import { spawnSync } from 'node:child_process';

export const ENGINE_ID = 'vouch-context';
export const ENGINE_ID = 'vouch';
export const ENGINE_NAME = 'Vouch Context Engine';

/** @typedef {import('node:child_process').SpawnSyncReturns<string>} SpawnResult */
Expand Down Expand Up @@ -56,12 +63,13 @@ function callPythonEngine(method, params) {
return parsed.result;
}

/** @type {{ id: string; name: string; description: string; register: (api: any) => void }} */
/** @type {{ id: string; name: string; description: string; kind: string; register: (api: any) => void }} */
const entry = {
id: 'vouch-context-engine',
id: 'vouch',
name: 'Vouch Context Engine',
description:
'Review-gated KB context: cited retrieval + salience reflex + hot memory on every assemble()',
kind: 'context-engine',

register(api) {
api.registerContextEngine(ENGINE_ID, (ctx) => {
Expand Down
Loading