docs(governance): add AGENTS.md bridge + 3 historical ADRs (#36)

Integrates OCP with Tao's cross-device development system (Phase 1 L1).

- AGENTS.md: project-level tool-agnostic instructions (read by Cursor,
  OpenCode, Copilot, etc. in addition to Claude Code). Inherits from
  ~/.cc-rules/AGENTS.md.
- CLAUDE.md: prepends @AGENTS.md + @~/.cc-rules/AGENTS.md imports.
- docs/adr/0002-alignment-constitution.md: captures why ALIGNMENT.md
  exists (2026-04-11 drift response).
- docs/adr/0003-models-json-spot.md: captures v3.11.0 SPOT refactor.
- docs/adr/0004-openclaw-auto-sync.md: captures v3.11.0 auto-sync design.

ADRs numbered 0002-0004 because an untracked 0001-cross-device-system.md
already exists locally and was out of scope for this PR.

Governance/docs only. No code change. No version bump (not a release).

Co-authored-by: Tao Deng <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
dtzp555-max
2026-04-21 19:20:26 +10:00
committed by GitHub
co-authored by taodeng Claude Opus 4.7
parent 497ff1dcd2
commit 2ecc4945ad
5 changed files with 277 additions and 0 deletions
+72
View File
@@ -0,0 +1,72 @@
Inherits: @~/.cc-rules/AGENTS.md
# OCP — Open Claude Proxy — Agent Guidelines
**Scope**: the `dtzp555-max/ocp` repository.
**Audience**: any AI coding agent (Claude Code / Cursor / OpenCode / Copilot / Codex / Gemini) touching OCP source.
---
## What this project is
OCP (Open Claude Proxy) is an open-source HTTP gateway that sits between the Claude Code CLI (`cli.js`) and Anthropic's public API. It forwards, observes, and multiplexes traffic that `cli.js` already emits — it is explicitly **not** an extension layer. A secondary role: registering OCP as a local provider inside OpenClaw (a sibling IDE-agnostic tool), so that users running OpenClaw against OCP see the same model list as native Claude Code.
Runtime: Node.js (ESM, `.mjs` throughout). No build step. No bundler. `server.mjs` is the single executable entrypoint; `ocp` and `ocp-connect` are CLI wrappers.
---
## Stack
- Node.js >=18, native ESM modules
- `http`/`https` built-ins for the proxy core (no Express, no Fastify)
- `models.json` as the single source of truth for model metadata
- GitHub Actions for CI (`alignment.yml`, `release.yml`)
- `gh` CLI assumed for PR creation and release automation
- No TypeScript. No test framework beyond `test-features.mjs`. Keep dependencies minimal.
---
## Key files to know
- `server.mjs` — the proxy itself; every request path lives here. Governed by `ALIGNMENT.md`.
- `models.json` — single source of truth for model IDs, aliases, and context windows. See ADR 0003.
- `setup.mjs` — first-time installer; reads `models.json` to derive bootstrap config.
- `scripts/sync-openclaw.mjs` — idempotent OpenClaw registry sync invoked by `ocp update`. See ADR 0004.
- `ocp` — user-facing CLI (install, update, start, stop, status, logs, etc.).
- `ALIGNMENT.md` — the constitution. Binding for any `server.mjs` change. See ADR 0002.
- `.github/workflows/alignment.yml` — CI blacklist grep; fails the build on known-hallucinated tokens.
- `CLAUDE.md` — Claude-Code-specific session instructions + release_kit overlay (Iron Rule 5.5).
- `docs/adr/` — Architecture Decision Records. Read these before proposing governance or SPOT changes.
---
## Project-specific constraints
- **`ALIGNMENT.md` is binding.** Any PR touching `server.mjs` must cite `cli.js:NNNN` (or `cli.js vE4 <functionName>`) in the commit body and PR description. See `CLAUDE.md` § "Hard requirements for `server.mjs` changes" and ADR 0002.
- **Alignment CI is not suppressible.** The `alignment.yml` workflow greps `server.mjs` for known-hallucinated tokens (including `api/oauth/usage`, `api/usage`). Adding to the blacklist is fine; removing entries requires an `ALIGNMENT.md` amendment PR.
- **No self-approval.** Implementation author cannot merge their own PR (Iron Rule 10). A fresh-context reviewer must open `cli.js` at the cited lines and confirm in the review comment.
- **`models.json` is the only place to add/edit models.** Do not touch `MODEL_MAP` or `MODELS` arrays directly in `server.mjs` or `setup.mjs`. See ADR 0003.
- **OpenClaw boundary.** `scripts/sync-openclaw.mjs` only writes `models.providers["claude-local"].models` and `agents.defaults.models["claude-local/*"]` in `~/.openclaw/openclaw.json`. Do not expand scope. See ADR 0004.
---
## Release protocol
OCP follows the machine-readable `release_kit:` overlay in `CLAUDE.md` (Iron Rule 5.5). Before any version bump or tag push, re-read that YAML block and walk every item in `new_feature_doc_expectations` and `bootstrap_quirk_policy`. Tag push triggers `.github/workflows/release.yml`, which creates the GitHub Release automatically — do not create the release manually.
Version is sourced from `package.json`; changelog from `CHANGELOG.md`; user-facing docs from `README.md`.
---
## Handoff expectations
A fresh session picking up OCP work should read, in order:
1. This file (`AGENTS.md`).
2. `ALIGNMENT.md` — constitution; non-optional.
3. `CLAUDE.md` — tool-specific instructions and release_kit overlay.
4. `docs/adr/` — most recent ADRs first; they explain why the current structure exists.
5. Any active spec under `docs/superpowers/specs/*/tasks.md` (if present).
6. `~/.cc-rules/memory/auto/MEMORY.md` — cross-machine memory index.
Only after these should the session touch code.
+3
View File
@@ -1,3 +1,6 @@
@AGENTS.md
@~/.cc-rules/AGENTS.md
# OCP Project Session Instructions # OCP Project Session Instructions
> **WARNING — READ BEFORE WRITING ANY CODE IN THIS REPO** > **WARNING — READ BEFORE WRITING ANY CODE IN THIS REPO**
+70
View File
@@ -0,0 +1,70 @@
# 0002 — Alignment Constitution
- **Date**: 2026-04-20
- **Status**: Accepted
- **Authors**: Tao Deng, Claude Opus 4.7 (drafting)
- **Related**: PR #20, commit 2853088; supersedes implicit "keep the proxy honest" discipline
## Context
On 2026-04-11 an OCP commit (`b87992f`, "fix: use dedicated `/api/oauth/usage` endpoint for reliable plan data") was merged. The commit asserted that `/api/oauth/usage` was "the dedicated usage endpoint that Claude Code CLI uses." The assertion was false: the string `/api/oauth/usage` does not appear anywhere in `cli.js`. The endpoint was fabricated by an LLM-assisted authoring pass generalizing from adjacent OAuth paths, without anyone running `grep` against `cli.js`.
The hallucination was not an isolated slip. It persisted across nine days and two additional commits of compensation:
- `cb6c2a8` extended the stale cache to 15 minutes and added a fallback path on HTTP 429 — a workaround that masked the fabricated endpoint's 4xx failures rather than investigating them.
- The dashboard `/usage` progress bar was broken for the entire window (2026-04-11 through 2026-04-20).
Root cause analysis identified three structural gaps:
1. No binding rule that OCP must mirror `cli.js` behavior exactly. "Proxy-only" was aspirational, not enforced.
2. No CI check that would fail builds containing known-hallucinated tokens.
3. No reviewer gate that required the reviewer to verify the `cli.js` citation before approving.
Without all three, the same class of drift was re-occurrence-probable rather than preventable.
## Decision
Adopt `ALIGNMENT.md` as the project constitution. It encodes five binding Rules:
1. **Grep First** — before changing any endpoint/header/parameter/response shape, the author must `grep` `cli.js` and record the line numbers.
2. **No Invention** — OCP must not introduce surface area not present in `cli.js`. Speculative "Claude Code probably uses X" statements are prohibited.
3. **Match the Implementation** — where `cli.js` does perform the operation, OCP matches it byte-for-byte on the wire.
4. **Unalignable Features Are Deleted** — features that cannot be traced to a `cli.js` reference are removed, not deprecated, not feature-flagged.
5. **Cite Line Numbers in Commits** — every `server.mjs`-touching commit references `cli.js:NNNN` or `cli.js vE4 <functionName>`.
Supporting mechanisms:
- `CLAUDE.md` enshrines hard requirements for `server.mjs` PRs: `cli.js` citation, CI blacklist pass, independent reviewer who opens `cli.js` at the cited lines.
- `.github/workflows/alignment.yml` greps `server.mjs` on every PR for the known-hallucinated token set (`api/oauth/usage`, `api/usage`, et al.) and fails the build on any hit.
- `.github/PULL_REQUEST_TEMPLATE.md` makes the `cli.js` citation and the reviewer's cli.js-opened confirmation mandatory fields.
- A bootstrap audit pin: Claude Code `2.1.89`, `cli.js` SHA-256 `a9950ef6407fdc750bddb673852485500387e524a99d42385cb81e7d17128e01`, auditor Tao Deng, date 2026-04-20. The pin is refreshed annually on 11 April (the drift anniversary) or on any re-verification event.
- A documented Historical Lesson section in `ALIGNMENT.md` that names the drift commits by SHA, so the incident cannot be rewritten or quietly forgotten.
## Consequences
**Positive**
- Every `server.mjs` change is now provably aligned to a specific `cli.js` line range before merge.
- CI hard-fails any reintroduction of the specific hallucinated tokens; the failure mode is loud and immediate, not silent-and-cached.
- The reviewer gate makes self-approval a policy violation, which structurally prevents the "fix my own hallucination" cycle that produced `cb6c2a8`.
- The constitution becomes the foundation that later governance (iron rule v1.4, per-project release kit, cross-device dev system) builds on.
**Negative**
- `server.mjs` changes are meaningfully slower: the `grep` step and the reviewer's cli.js verification are real costs on every PR.
- New contributors face a steeper ramp — they must read `ALIGNMENT.md` fully before their first server-side PR will pass review.
- The CI blacklist is a moving target; as future drift patterns are discovered, the list grows, and each addition is governance work.
**Follow-ons**
- ADR 0003 (models.json SPOT) and ADR 0004 (OpenClaw auto-sync) both lean on the constitution's "one reviewable layer" structure.
- Annual audit on 11 April is a recurring calendar obligation; failure to perform it is itself an alignment violation.
- The `cli.js` bundle became opaque at v2.1.90 (binary packaging). Future audits require a different verification strategy — see Alternatives (b) below.
## Alternatives considered
**(a) Pure human discipline — no CI, no template, no ADR.** Tao would simply commit to grepping `cli.js` on every change, and reviewers would commit to verifying. Rejected: the 2026-04-11 drift already happened under exactly this regime. Tao is meticulous, and the drift still shipped. Social discipline alone cannot prevent LLM hallucination from slipping through, especially when the LLM's output is superficially plausible.
**(b) Automatic `cli.js` diff on every PR.** A CI step that diffs `server.mjs`'s network surface against a parsed `cli.js` AST, blocking on any mismatch. Rejected as too fragile: `cli.js` v2.1.90+ ships as a minified/obfuscated binary, making AST-level grep invalid without an unofficial unbundling step. Any such pipeline becomes a maintenance burden on Anthropic's release cadence, and would routinely false-positive. The blacklist approach is lower-precision but dramatically more robust.
**(c) Freeze OCP and fork a new `ocp-v2` from scratch.** Start over with alignment baked in from day one. Rejected: the existing user base depends on OCP, and the drift affected one endpoint, not the architecture. Retrofitting a constitution onto the existing repo is cheaper and preserves user trust.
+65
View File
@@ -0,0 +1,65 @@
# 0003 — `models.json` as Single Source of Truth
- **Date**: 2026-04-20
- **Status**: Accepted
- **Authors**: Tao Deng, Claude Opus 4.7 (drafting)
- **Related**: PR #30, commit c6f7850; precursor to ADR 0004
## Context
OCP's model catalog (the mapping from short aliases like `sonnet` and `opus` to full model IDs with context-window metadata) had organically drifted into three independent locations:
1. `server.mjs``MODEL_MAP` and `MODELS` arrays, hardcoded at the top of the file. This was the runtime authority for `/v1/models` responses and alias resolution.
2. `setup.mjs` — a separate `MODELS` constant, unchanged since the v3.0 era. Used only at first-install time to seed user config; by v3.10 it was stale and listed no Claude 4.x models at all.
3. `~/.openclaw/openclaw.json` (on user machines) — written exactly once by `setup.mjs` during initial OCP install and never refreshed. A user who installed OCP in v3.0 and ran `ocp update` faithfully through v3.10 still had their OpenClaw config listing only three pre-Claude-4 models.
By the v3.10.0 release, Opus 4.7 was correctly present in location (1) and absent from (2) and (3). The symptom reaching users: native Claude Code saw the new model immediately (because it queries `/v1/models` live from server.mjs), but OpenClaw users saw nothing new, and new-installers via `setup.mjs` got an incomplete initial config. Three distinct bug reports in the two weeks following v3.10.0.
The drift was structural, not a bug in any one file. The files disagreed because there was no mechanism requiring them to agree.
## Decision
Extract all model metadata into `models.json` at the repo root. `server.mjs` and `setup.mjs` both read this file and derive their in-memory `MODEL_MAP`/`MODELS` structures from it. The file is committed to the repo; it is neither generated nor cached.
Shape (summarized):
- `models` — array of entries, each with `id` (full model ID), `alias` (short name), `context_window`, and flags where relevant.
- `default_alias` — which alias resolves when the client sends an unknown or empty model.
Migration approach:
1. Hand-populate `models.json` from the v3.10.0 `server.mjs` `MODEL_MAP` values.
2. Rewrite `server.mjs` to load and index `models.json` at startup.
3. Rewrite `setup.mjs` to derive its `MODELS` constant from the same file.
4. Verify byte-equivalence: the derived `MODEL_MAP` in v3.11.0 must be a byte-identical superset of the v3.10.0 hardcoded `MODEL_MAP`. This is checked by a one-shot comparison script during the refactor PR; no regression is permitted.
Post-refactor, the contract for adding a model is: edit `models.json`, open a PR, reviewer sanity-checks the `id` string against Anthropic's model announcement, merge. No other file changes.
## Consequences
**Positive**
- Single edit point eliminates the "updated one place, forgot the other" failure mode structurally.
- `setup.mjs`'s latent staleness is repaired as a side effect — new-installers now get a fresh model list.
- Opens the door to ADR 0004 (OpenClaw auto-sync), which requires a file-based SPOT to sync from.
- The `models.json` format is stable, Markdown-friendly JSON, easy to diff in code review.
**Negative**
- One additional file to load at server startup (negligible cost, but now a startup dependency).
- Schema drift risk: if anyone adds a new field to `models.json` that `server.mjs` or `setup.mjs` doesn't know about, the field is silently ignored. A future schema version tag may be warranted if the format grows.
- `models.json` parse failure is now a fatal startup error; previously, bad model config required editing source. Consider this a feature, not a regression.
**Follow-ons**
- ADR 0004 (OpenClaw auto-sync) consumes `models.json` directly in `scripts/sync-openclaw.mjs`.
- Future additions (per-model pricing, per-model capability flags, etc.) belong in `models.json`, not scattered back across `server.mjs`.
- The README "Available Models" table is now derived documentation and its source of truth should be pinned to `models.json` in the release_kit overlay.
## Alternatives considered
**(a) Keep the three locations, enforce sync by manual review discipline.** A reviewer checklist item: "did you update all three places?" Rejected: the drift had already demonstrated that manual discipline is insufficient when the three files are in unrelated sections of the diff. Human reviewers routinely miss the third file. The 2026-04-11 alignment drift had already taught the project that discipline-only approaches fail.
**(b) YAML with SOPS field-level encryption.** Some projects prefer YAML for multi-line string readability and use SOPS to encrypt sensitive fields. Rejected: OCP's model catalog contains no secrets — model IDs, aliases, and context windows are all public information published by Anthropic. YAML adds a parser dependency and SOPS adds a decryption step at startup, both for zero benefit. JSON is already native to Node, and `models.json` is easy to diff line-by-line in GitHub review UI.
**(c) Fetch the model list live from Anthropic at server start.** Rejected: `cli.js` does not perform this operation, so per `ALIGNMENT.md` Rule 2 it is out of scope for OCP. Additionally, a live fetch introduces a startup-time network dependency and an availability coupling to Anthropic that OCP is explicitly designed to avoid (OCP is the gateway, not another consumer).
+67
View File
@@ -0,0 +1,67 @@
# 0004 — OpenClaw Auto-Sync on `ocp update`
- **Date**: 2026-04-20
- **Status**: Accepted
- **Authors**: Tao Deng, Claude Opus 4.7 (drafting)
- **Related**: PR #31, commit 5ef163a; builds on ADR 0003
## Context
v3.10.0 added Claude Opus 4.7 to OCP's `server.mjs` `MODEL_MAP`. Native Claude Code users and other IDE consumers (Cline, Aider, Cursor) saw the new model immediately, because every one of those clients queries `/v1/models` live at session start.
OpenClaw is different. OpenClaw caches its provider/model list in `~/.openclaw/openclaw.json`, written exactly once during OCP's `setup.mjs` run, then treated as immutable until the user manually edits it. An OpenClaw user who installed OCP in, say, v3.7 and diligently ran `ocp update` through v3.10 still saw only the pre-Claude-4 model list. From their perspective, `ocp update` "did not do what it said."
Within two weeks of v3.10.0, three separate bug reports surfaced, all with the same root cause: OpenClaw's cache was stale. Users tried the obvious workarounds (reinstall OpenClaw, edit the JSON by hand) and reported those as additional bugs when they misformatted the file.
The underlying asymmetry: every other IDE integration is pull-based (asks OCP for models on demand); OpenClaw is push-based (was told once, caches forever). OCP had no mechanism for a subsequent push.
Additionally, ADR 0003 had just landed `models.json` as the single source of truth — meaning the data a sync mechanism would need was now available in a machine-readable file rather than scattered across `server.mjs`.
## Decision
Add `scripts/sync-openclaw.mjs`, invoked automatically at the end of `ocp update`, plus a passive drift self-check in `server.mjs` startup. Design constraints:
1. **Strictly scoped.** The script only touches two sub-trees of `~/.openclaw/openclaw.json`:
- `models.providers["claude-local"].models` — the provider's model list.
- `agents.defaults.models["claude-local/*"]` — per-agent defaults that reference claude-local models.
All other OpenClaw config (user-defined agents, non-claude-local providers, UI preferences) is left untouched.
2. **Idempotent.** Running the script twice with the same `models.json` produces the same file both times — byte-identical. The script diffs before writing and no-ops if there is nothing to change.
3. **Safe.** Before any write, the script creates a timestamped backup at `~/.openclaw/openclaw.json.bak.<ISO8601>`. The user can always roll back.
4. **Non-fatal.** If `~/.openclaw/openclaw.json` is missing (OpenClaw not installed), malformed, or otherwise unwriteable, the script logs a single-line warning and exits 0. `ocp update` never fails because of sync.
5. **Manually invocable.** `node scripts/sync-openclaw.mjs` runs the sync as a standalone operation, for users who want to trigger it without a full `ocp update`.
6. **Passive drift self-check.** On server startup, `server.mjs` reads the `claude-local` model list from `openclaw.json` (if present) and compares against the models derived from `models.json`. Mismatches produce a single WARN log line — enough to alert the user without taking action. This is the "we noticed" signal; the fix is to run `ocp update`.
Implementation source: the sync script reads the SPOT (`models.json`), produces the canonical claude-local model list, merges it into the OpenClaw config in the two scoped locations, writes atomically (write-to-temp then rename), and logs the diff.
## Consequences
**Positive**
- Users get new models on the next `ocp update` with no manual action. The invariant OCP's update flow was advertising is now actually true.
- Manual invocation remains available for users who want to sync without updating OCP itself (edge case, but cheap to support).
- Passive self-check means even users who somehow skip `ocp update` receive a runtime heads-up instead of silent drift.
- The script is short (under 150 lines) and testable in isolation.
**Negative**
- One-time bootstrap quirk: users upgrading from v3.10 → v3.11 have a cached `cmd_update` in their existing installation that does not yet invoke the new script. The first `ocp update` to v3.11 still misses the sync; the second `ocp update` (now running v3.11's code) performs it. This is documented in README § "Troubleshooting" per the release_kit `bootstrap_quirk_policy`.
- A new script to maintain. If OpenClaw's config schema changes, this script needs updating. The strict-scope constraint bounds the maintenance surface.
- Non-fatal-on-error means a broken `openclaw.json` silently stays broken from OCP's perspective. Accepted trade-off: `ocp update` failing because of a sibling tool's config would be worse.
**Follow-ons**
- If OpenClaw ever adopts live `/v1/models` polling upstream, this script becomes redundant and can be deleted per ADR 0002's Rule 4 (unalignable-to-upstream features are deleted).
- Similar sync needs for future sibling tools would follow this pattern: separate script, strictly scoped, idempotent, non-fatal, invoked by `ocp update`.
## Alternatives considered
**(a) Modify OpenClaw itself to poll `/v1/models` live.** The "correct" fix at the architecture level. Rejected: OCP is a tenant in OpenClaw's plugin model, not its owner. Opening an upstream PR creates a cross-repo coordination dependency (review timeline, release timeline, version matrix) that leaves current OCP users broken for weeks or months. The sync script is something OCP can ship unilaterally and remove later if the upstream change lands.
**(b) Re-run `setup.mjs` in full.** `setup.mjs` already knows how to write `openclaw.json` from scratch. Rejected: `setup.mjs` has many side effects beyond OpenClaw registration — it rewrites user shell rc files, regenerates systemd units, touches credential storage. It is explicitly not idempotent, and running it a second time on an already-configured system produces duplicate entries or regressions. The sync script's strict scope is the whole point; re-running `setup.mjs` would blow past it.
**(c) Do nothing — tell users to manually edit `~/.openclaw/openclaw.json`.** Rejected for two reasons. First, UX: OCP's value proposition includes "`ocp update` keeps your toolchain current," and asking users to hand-edit a third party's JSON breaks that promise. Second, error rate: the three bug reports that motivated this ADR included two malformed-JSON follow-ups from users who tried the manual approach. A machine-written file is strictly safer than a hand-edited one.