@herjarsa/omo-meta-governor 0.30.0 → 0.31.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,612 +1,693 @@
1
- # @herjarsa/omo-meta-governor
2
-
3
- > Self-judging agent orchestration layer for [OpenCode](https://opencode.ai).
4
- > Observes tool executions, scores progress, dispatches decisions, and exposes
5
- > **12 custom tools** the agent can invoke across CodeGraph, Graphify,
6
- > AgentMemory, and SQLite — for cheaper, more accurate code understanding.
7
-
8
- **Current version:** `0.26.0` · **License:** MIT · **Status:** stable
9
-
10
- ---
11
-
12
- ## Table of Contents
13
-
14
- - [Install](#install)
15
- - [What it does](#what-it-does)
16
- - [12 Custom Tools](#12-custom-tools)
17
- - [Code search & navigation](#code-search--navigation)
18
- - [Lesson & memory](#lesson--memory)
19
- - [File & symbol lookup](#file--symbol-lookup)
20
- - [Safety & status](#safety--status)
21
- - [Governance pipeline](#governance-pipeline)
22
- - [Scoring engine](#scoring-engine)
23
- - [Intervention modes](#intervention-modes)
24
- - [Protocol enforcement](#protocol-enforcement)
25
- - [Skill priming](#skill-priming)
26
- - [Multi-phase plans](#multi-phase-plans)
27
- - [Graph sync (codegraph + graphify)](#graph-sync-codegraph--graphify)
28
- - [Auto-init](#auto-init)
29
- - [Auto-upgrade (v0.26.0)](#auto-upgrade-v0260)
30
- - [Git hooks](#git-hooks)
31
- - [Process safeguards](#process-safeguards)
32
- - [Persistence & observability](#persistence--observability)
33
- - [CI monitor (v0.25.0)](#ci-monitor-v0250)
34
- - [Configuration reference](#configuration-reference)
35
- - [Architecture overview](#architecture-overview)
36
- - [Testing](#testing)
37
- - [Migration from earlier versions](#migration-from-earlier-versions)
38
- - [License](#license)
39
-
40
- ---
41
-
42
- ## Install
43
-
44
- ```bash
45
- npm install @herjarsa/omo-meta-governor
46
- ```
47
-
48
- Add as a plugin in your OpenCode config (`~/.config/opencode/opencode.jsonc`):
49
-
50
- ```jsonc
51
- {
52
- "plugins": ["@herjarsa/omo-meta-governor"]
53
- }
54
- ```
55
-
56
- The 12 custom tools register automatically on every load. To also enable
57
- the governance pipeline (scoring, intervention, protocol enforcement):
58
-
59
- ```jsonc
60
- {
61
- "meta_governor": {
62
- "enabled": true,
63
- "intervention": { "mode": "message", "minActionForMessage": "warn" }
64
- }
65
- }
66
- ```
67
-
68
- ---
69
-
70
- ## What it does
71
-
72
- `omo-meta-governor` is a single OpenCode plugin that ships **five
73
- interconnected subsystems**:
74
-
75
- | Subsystem | Purpose | Surface |
76
- |---|---|---|
77
- | **Graph sync** | Auto-install codegraph + graphify, build initial index, wire git hooks, auto-upgrade binaries on every load | `graphSync.*` config |
78
- | **12 custom tools** | Semantic code search, impact analysis, symbol lookup, lesson recall, file/caller/node queries, health | `omo_*` tools |
79
- | **Governance pipeline** | Score session progress → dispatch decision (`continue` / `warn` / `escalate` / `stop`) → optionally inject it into the agent's context | `meta_governor.enabled` |
80
- | **Memory + lessons** | Persist decisions and lessons in SQLite (FTS5) + bridge to AgentMemory for cross-session recall | `omo_recall`, `omo_remember`, `omo_recall_mcp` |
81
- | **Observability** | Health JSON, rotating JSONL logs, metrics, audit state, CI monitor | `omo_health`, `~/.config/opencode/meta-governor-health.json` |
82
-
83
- All five run **inside the plugin** — no daemon, no sidecar. They share
84
- process boundaries, lifecycle, and the opencode event hooks
85
- (`tool.execute.before` / `tool.execute.after` / `chat.messages.transform`).
86
-
87
- ---
88
-
89
- ## 12 Custom Tools
90
-
91
- The plugin registers 12 tools the LLM can invoke. All are available
92
- immediately on install (no `enabled: true` required for tools — only the
93
- governance pipeline needs `meta_governor.enabled: true`).
94
-
95
- ### Code search & navigation
96
-
97
- | Tool | What it does | Use case |
98
- |------|--------------|----------|
99
- | `omo_search` | Semantic code search via codegraph or graphify | "Where is authentication handled?" — USE THIS FIRST for any architecture question |
100
- | `omo_find` | Exact symbol lookup (definition + direct callers) via `codegraph node` | "Find the function `validateToken`" |
101
- | `omo_impact` | Impact analysis: direct + transitive callers, test files, doc files | Run BEFORE modifying a function |
102
- | `omo_path` | Shortest conceptual path between two concepts via graphify | "How does auth connect to database?" |
103
- | `omo_explain` | Plain-language explanation of a concept via graphify | "What is the SwinTransformer?" |
104
-
105
- ### Lesson & memory
106
-
107
- | Tool | What it does | Use case |
108
- |------|--------------|----------|
109
- | `omo_recall` | Search past lessons via local SQLite FTS5 (fast, always available) | "How did we set up auth before?" |
110
- | `omo_recall_mcp` | Search cross-session memory via AgentMemory | "What did we learn about X in previous sessions?" |
111
- | `omo_remember` | Save a fact / observation / pattern to cross-session AgentMemory | "Remember this bug pattern for next time" |
112
-
113
- ### File & symbol lookup
114
-
115
- | Tool | What it does | Use case |
116
- |------|--------------|----------|
117
- | `omo_files` | List files indexed by codegraph or graphify | "What files are in the graph?" |
118
- | `omo_callers` | List all call sites of a symbol via `codegraph callers` | "Who calls `UserService.create`?" |
119
- | `omo_node` | Get source + direct callers of a symbol via `codegraph node` | "Show me the source of `validateToken` and its callers" |
120
-
121
- ### Safety & status
122
-
123
- | Tool | What it does | Use case |
124
- |------|--------------|----------|
125
- | `omo_health` | Show plugin runtime status: metrics, decisions, errors | "Is the plugin working?" |
126
-
127
- All tools return a typed `ToolResult` with `title`, `output`, and
128
- `metadata` (`{tool, kind, durationMs, sessionID}`). They degrade
129
- gracefully — when codegraph or graphify is missing, they return a
130
- **friendly hint** (e.g. `npx codegraph init` to recover) instead of
131
- crashing.
132
-
133
- ---
134
-
135
- ## Governance pipeline
136
-
137
- When `meta_governor.enabled: true`, the plugin attaches to opencode's
138
- tool-execution stream and runs an **observe → score → decide → (optionally)
139
- intervene** loop on every turn.
140
-
141
- ### Scoring engine
142
-
143
- `src/scoring-engine.ts` computes a single composite score in `[-1, 1]`
144
- from weighted signals:
145
-
146
- | Signal | Weight | Source |
147
- |--------|--------|--------|
148
- | `progress-detector` | 0.30 | did the last 5 tool calls make forward progress? |
149
- | `deviation-detector` | 0.20 | accumulated protocol violations (capped at 5/session) |
150
- | `no-progress-detector` | 0.20 | is the agent reading without writing? |
151
- | `iteration-budget` | 0.15 | are we approaching `maxIterations`? |
152
- | `oracle-burn` | 0.10 | did recent oracle calls detect issues? |
153
- | `stop-advice` | 0.05 | did prior lessons recommend stop? |
154
-
155
- The score maps to an action via configurable thresholds (see
156
- [Configuration reference](#configuration-reference)):
157
-
158
- - `score ≥ continueThreshold` → **continue** (silent)
159
- - `score ≤ -warnThreshold` → **warn** (log + nudge)
160
- - `score ≤ -escalateThreshold` → **escalate** (block + inject)
161
- - `score ≤ -stopThreshold` → **stop** (latch intervention)
162
-
163
- Default thresholds: `continue: 0.05`, `warn: 0.3`, `escalate: 0.45`,
164
- `stop: 0.55` (worst-case math gives `stop ≈ -0.55`, so it actually
165
- fires — verified via Gap C audit).
166
-
167
- ### Intervention modes
168
-
169
- When the decision is `warn` / `escalate` / `stop`, the plugin can inject
170
- the rationale into the agent's context via `experimental.chat.messages.transform`:
171
-
172
- | Mode | Mechanism | Effect |
173
- |------|-----------|--------|
174
- | `silent` | (none) | Decision is logged only |
175
- | `message` | `chat.messages.transform` | Injects a synthetic user message visible to the LLM |
176
- | `system` | `chat.system.transform` | Appends guidance to the system prompt |
177
-
178
- `maxInterventionsPerSession: 3` (default) hard-stops injection after 3
179
- interventions per session to prevent infinite instruction loops
180
- (v0.10.0). When `respectDoneSignal: true` (default), injection stops
181
- once the agent emits the terminal signal AND Oracle has verified.
182
-
183
- ### Protocol enforcement
184
-
185
- `src/protocol-enforcer.ts` audits tool calls against a configurable
186
- protocol markdown file. Use it to enforce rules like "do not save
187
- routine operations to memory" or "always invoke Oracle before declaring
188
- done".
189
-
190
- ```jsonc
191
- {
192
- "meta_governor": {
193
- "enabled": true,
194
- "protocolEnforcement": {
195
- "enabled": true,
196
- "path": "./PROTOCOL.md",
197
- "injectIntoSystem": true,
198
- "auditToolCalls": true
199
- }
200
- }
201
- }
202
- ```
203
-
204
- Violations accumulate in `state.accumulatedDeviations` (capped at 5 per
205
- session) and feed the `deviation-detector` scoring signal.
206
-
207
- ### Skill priming
208
-
209
- `src/skill-priming.ts` (v0.20.0) injects **one** synthetic user message
210
- at session start (or once implementation work begins) prompting the
211
- agent to select precise skills for the task via the AAS skill catalog
212
- (`aas search_skills` / `get_skill` / `compose_stack`) and/or the
213
- task-appropriate superpowers skill — before writing code. Minimal context
214
- cost: the directive forbids enumerating the full catalog.
215
-
216
- ```jsonc
217
- {
218
- "meta_governor": {
219
- "enabled": true,
220
- "skillPriming": {
221
- "enabled": true,
222
- "trigger": "firstImplement",
223
- "router": "both"
224
- }
225
- }
226
- }
227
- ```
228
-
229
- ### Multi-phase plans
230
-
231
- For work plans with multiple phases (e.g. Sisyphus/Prometheus work
232
- plans), set `phaseAwareDoneSignal: true` and emit
233
- `<promise>PLAN-COMPLETE</promise>` only when the **entire** plan is
234
- verified done by Oracle.
235
-
236
- | Marker | Effect |
237
- |--------|--------|
238
- | `<promise>DONE</promise>` | Per-phase hint. Logged but does NOT latch intervention (when `phaseAwareDoneSignal: true`). |
239
- | `<promise>PHASE-N-COMPLETE</promise>` | Per-phase hint (e.g. `<promise>PHASE-1-COMPLETE</promise>`). Same as `DONE`. |
240
- | `<promise>PLAN-COMPLETE</promise>` | Terminal. Latches intervention when Oracle has verified. |
241
-
242
- ---
243
-
244
- ## Graph sync (codegraph + graphify)
245
-
246
- The plugin wires the native git hooks of **codegraph** and **graphify**
247
- so each commit automatically reindexes both graphs.
248
-
249
- ### Auto-init
250
-
251
- On first load in a project (when `graphSync.enabled: true`, default):
252
-
253
- 1. **Auto-install** codegraph via `npm i -D @colbymchenry/codegraph` and
254
- graphify via `pip install graphifyy` (falls back to
255
- `uv tool install graphifyy`) if not already on PATH.
256
- 2. **Run `codegraph init`** + **`graphify . --no-viz`** to build the
257
- initial indexes for the project.
258
- 3. **Run `graphify hook install`** to wire up the native `post-commit`
259
- and `post-checkout` git hooks.
260
-
261
- ### Auto-upgrade (v0.26.0)
262
-
263
- Before v0.26.0, `autoUpgrade: true` (default) silently failed. Six
264
- bugs in `src/graph-sync.ts:503-628` forced users to manually run
265
- `npm install -g @colbymchenry/codegraph@latest` and
266
- `pip install --upgrade graphifyy`.
267
-
268
- **Root cause bugs fixed in v0.26.0:**
269
-
270
- 1. `getInstalledCodegraphVersion` only probed `npx` — failed when the
271
- binary was at `node_modules/.bin/codegraph` (Windows users).
272
- 2. `getInstalledGraphifyVersion` had no DI runner — Windows dual-python
273
- fallback was untestable.
274
- 3. `shouldUpgrade` ignored the cache value (`latest=null`).
275
- 4. Cache cold + undetectable binary → silent noop (no diagnostic code).
276
- 5. **`pip install` without `--upgrade` returned 0** with
277
- "Requirement already satisfied" but **did NOT upgrade** — most
278
- visible bug.
279
- 6. `graphify check-update` was ignored — semantic re-extraction flag
280
- never triggered.
281
-
282
- **Fixes:**
283
-
284
- - Tiered probe matching `checkToolAvailability`: `npx` +
285
- `node node_modules/.bin/codegraph` for codegraph; `graphify` →
286
- `python -m pip show` → `python3 -m pip show` for graphify.
287
- - Runner DI seam on `getInstalledCodegraphVersion`,
288
- `getInstalledGraphifyVersion`, `installCodegraph`, `installGraphify`
289
- — hermetic tests, no real network in CI.
290
- - `resolveLatest()` inlines cache into `shouldUpgrade` — avoids
291
- double-fetch from the registry.
292
- - Cache written **ONCE** at the end of the upgrade block (was being
293
- fetched 3× per run).
294
- - `pip install --upgrade graphifyy` / `uv tool install --upgrade graphifyy`
295
- flags.
296
- - `graphify check-update` integration emits
297
- `graphify-reextract-triggered` when semantic re-extraction is pending.
298
- - New codes: `codegraph-upgrade-broken`, `graphify-reextract-triggered`,
299
- `upgrade-cache-written`.
300
- - New config fields: `autoUpgrade`, `upgradeCachePath`,
301
- `checkGraphifyNeedsUpdate`.
302
-
303
- **Verified surface run:** `codegraph 0.6.8 → 1.5.0` and
304
- `graphify 0.8.30 → 0.9.46` upgraded silently without manual
305
- intervention.
306
-
307
- **Configuration:**
308
-
309
- ```jsonc
310
- {
311
- "meta_governor": {
312
- "graphSync": {
313
- "enabled": true, // default true
314
- "autoUpgrade": true, // v0.26.0: default true
315
- "upgradeCachePath": "~/.omo-meta-governor/upgrade-cache.json",
316
- "checkGraphifyNeedsUpdate": true // emit graphify-reextract-triggered when schema changed
317
- }
318
- }
319
- }
320
- ```
321
-
322
- ### Git hooks
323
-
324
- On every `git commit`:
325
-
326
- - **Primary path** (native git hook): `graphify update` runs in background.
327
- - **Backup path** (plugin's `tool.execute.after`): detects `git commit`
328
- in bash commands and runs `codegraph sync -q [path]`.
329
-
330
- ### Process safeguards
331
-
332
- Every subprocess the plugin spawns (graphify, codegraph, npx, python,
333
- npm/pip) is guaranteed to die after use — on success, error, AND
334
- timeout — including its descendant tree. On Windows this uses
335
- `taskkill /pid <pid> /T /F` (plain `child.kill()` only kills the direct
336
- shell, orphaning grandchildren — the confirmed cause of the
337
- Bun/OpenChamber crashes).
338
-
339
- Config: `graphSync.killOrphanedOnInit` (default `true`) — on graph-sync
340
- init the plugin sweeps orphaned `graphify`/`codegraph` processes left
341
- by previous crashed runs. Set to `false` to disable the sweep.
342
-
343
- ---
344
-
345
- ## Persistence & observability
346
-
347
- **Lesson storage.** Decisions and lessons persist in **SQLite** at
348
- `~/.omo-meta-governor/meta-governor.db` with full-text search (FTS5) for
349
- fast recall. Zero dependencies — uses Bun's built-in `bun:sqlite`.
350
-
351
- **Cross-session memory.** The `omo_remember` / `omo_recall_mcp` tools
352
- bridge to AgentMemory via `session.prompt()` — the LLM receives a
353
- structured instruction to call the appropriate MCP tool.
354
-
355
- **Health JSON** at `~/.config/opencode/meta-governor-health.json`:
356
-
357
- ```bash
358
- cat ~/.config/opencode/meta-governor-health.json
359
- ```
360
-
361
- Or invoke `omo_health` directly for a formatted report.
362
-
363
- **Structured JSONL logs** at `~/.config/opencode/meta-governor.log` with
364
- size-based rotation (10MB max, 5 rotated files). Secret redaction layer
365
- strips JWT, OpenAI keys, Bearer tokens, GitHub PATs, and generic
366
- `key:value` patterns before writing.
367
-
368
- ---
369
-
370
- ## CI monitor (v0.25.0)
371
-
372
- `src/ci-monitor.ts` auto-triggers GitHub Actions on `git push` and
373
- surfaces failures to the agent:
374
-
375
- - Detects `git push` in bash commands via the `tool.execute.after` hook.
376
- - Polls the GH Actions API for the resulting run (5s initial delay,
377
- exponential backoff).
378
- - On failure, injects a synthetic message with the failed logs into the
379
- agent's context so it can fix and retry.
380
-
381
- Configurable via `meta_governor.ciMonitor` (disabled by default — opt-in
382
- feature).
383
-
384
- ---
385
-
386
- ## Configuration reference
387
-
388
- All configuration lives under the `meta_governor` key in
389
- `opencode.jsonc`. Full schema:
390
- [assets/omo-meta-governor.schema.json](assets/omo-meta-governor.schema.json).
391
-
392
- ### Top-level
393
-
394
- | Field | Type | Default | Description |
395
- |-------|------|---------|-------------|
396
- | `enabled` | boolean | `false` | Master feature flag — must be true to run the orchestrator. |
397
- | `decision` | object | — | Decision handler tuning. |
398
- | `memory` | object | — | Memory aggregator config. |
399
- | `tokenPredictor` | object | — | Token predictor (compact-now / switch-model / delegate recommendations). |
400
- | `scoring` | object | — | Scoring engine thresholds. |
401
- | `closedLoop` | object | — | Closed-loop learning (save decisions + lessons). |
402
- | `modelOverride` | object | — | Model override for MetaGovernor's internal LLM usage. |
403
- | `intervention` | object | — | Visible decision injection config. |
404
- | `protocolEnforcement` | object | — | Sisyphus protocol enforcement. |
405
- | `skillPriming` | object | — | Proactive skill-selection nudge (v0.20.0). |
406
- | `graphSync` | object | — | Graph synchronization (auto-init codegraph/graphify). |
407
-
408
- ### `decision`
409
-
410
- | Field | Type | Default | Description |
411
- |-------|------|---------|-------------|
412
- | `maxHistoryPerSession` | integer | — | Maximum history entries per session before oldest are trimmed. |
413
- | `forceContinueAfterStops` | integer | — | How many consecutive stops before forcing continue. |
414
-
415
- ### `memory`
416
-
417
- | Field | Type | Default | Description |
418
- |-------|------|---------|-------------|
419
- | `agentmemoryTimeoutMs` | integer | — | Timeout for agentmemory queries in milliseconds. |
420
- | `boulderStateTimeoutMs` | integer | — | Timeout for boulder-state queries in milliseconds. |
421
- | `query` | string | — | Natural-language query for memory recall. |
422
-
423
- ### `tokenPredictor`
424
-
425
- | Field | Type | Default | Description |
426
- |-------|------|---------|-------------|
427
- | `compactBurnRateThreshold` | integer | — | Burn rate (tokens/turn) above which to recommend compact-now. |
428
- | `compactUsageThreshold` | number | — | Context usage ratio (0..1) above which to recommend compact-now. |
429
- | `switchModelUsageThreshold` | number | — | Context usage ratio above which to recommend switch-model. |
430
- | `delegateConsecutiveHighBurn` | integer | — | Max consecutive high-burn turns before recommending delegate. |
431
-
432
- ### `scoring`
433
-
434
- | Field | Type | Default | Description |
435
- |-------|------|---------|-------------|
436
- | `continueThreshold` | number | `0.05` | Score ≥ this → continue silently. |
437
- | `warnThreshold` | number | `0.3` | Score ≤ -this → warn. |
438
- | `escalateThreshold` | number | `0.45` | Score ≤ -this → escalate. |
439
- | `stopThreshold` | number | `0.55` | Score ≤ -this → stop. |
440
-
441
- ### `closedLoop`
442
-
443
- | Field | Type | Default | Description |
444
- |-------|------|---------|-------------|
445
- | `saveDecisions` | boolean | `true` | Whether to save decision records. |
446
- | `saveLessons` | boolean | `true` | Whether to save lessons. |
447
-
448
- ### `modelOverride`
449
-
450
- | Field | Type | Default | Description |
451
- |-------|------|---------|-------------|
452
- | `providerID` | string | — | Provider ID (e.g. `'openai'`, `'anthropic'`). |
453
- | `modelID` | string | — | Model ID (e.g. `'gpt-4o-mini'`, `'claude-sonnet-4-20250514'`). |
454
- | `modelLimit` | integer | — | Context window size for token predictor (min 1000). |
455
- | `temperature` | number | `0.2` | Sampling temperature (0..2). |
456
- | `topP` | number | `1` | Top-p nucleus sampling (0..1). |
457
- | `maxTokens` | integer | — | Max output tokens for internal reasoning. |
458
- | `reasoning` | boolean | — | Enable extended reasoning / thinking mode. |
459
- | `verbosity` | enum | — | `'silent'` \| `'minimal'` \| `'verbose'`. |
460
-
461
- ### `intervention`
462
-
463
- | Field | Type | Default | Description |
464
- |-------|------|---------|-------------|
465
- | `mode` | enum | — | `'silent'` \| `'message'` \| `'system'`. |
466
- | `includeDecisionHistory` | boolean | — | Whether to include recent decision history in injection. |
467
- | `maxHistoryMessages` | integer | `5` | Max history entries when `includeDecisionHistory: true`. |
468
- | `minActionForMessage` | enum | — | Minimum action: `'warn'` (all non-continue), `'escalate'`, `'stop'`. |
469
- | `persistToSession` | boolean | `true` | v0.19.0: when true, intervention messages ALSO persist to the session. |
470
- | `maxInterventionsPerSession` | integer | `3` | v0.10.0: hard cap before auto-disable. |
471
- | `respectDoneSignal` | boolean | `true` | Stop injecting once terminal signal + Oracle verified. |
472
- | `phaseAwareDoneSignal` | boolean | `false` | v0.15.0: split per-phase hint from terminal signal. |
473
-
474
- ### `protocolEnforcement`
475
-
476
- | Field | Type | Default | Description |
477
- |-------|------|---------|-------------|
478
- | `enabled` | boolean | — | Master switch. |
479
- | `path` | string | — | Path to protocol markdown file. |
480
- | `injectIntoSystem` | boolean | — | Whether to inject protocol rules into the system prompt. |
481
- | `auditToolCalls` | boolean | — | Whether to audit tool calls for violations. |
482
-
483
- ### `skillPriming`
484
-
485
- | Field | Type | Default | Description |
486
- |-------|------|---------|-------------|
487
- | `enabled` | boolean | `false` | Master switch. |
488
- | `trigger` | enum | `'firstImplement'` | `'sessionStart'` (first transform) or `'firstImplement'` (once write-like tool observed). |
489
- | `router` | enum | `'both'` | `'aas'` \| `'superpowers'` \| `'both'`. |
490
-
491
- ### `graphSync`
492
-
493
- | Field | Type | Default | Description |
494
- |-------|------|---------|-------------|
495
- | `enabled` | boolean | `true` | Enable auto-initialization. |
496
- | `watch` | boolean | `false` | Enable watch mode (re-index on file changes). |
497
- | `killOrphanedOnInit` | boolean | `true` | Sweep orphaned processes on init. |
498
- | `autoUpgrade` | boolean | `true` | **v0.26.0** — auto-upgrade installed codegraph + graphify binaries. |
499
- | `upgradeCachePath` | string | — | **v0.26.0** — path for the upgrade cache file. |
500
- | `checkGraphifyNeedsUpdate` | boolean | `true` | **v0.26.0** — run `graphify check-update` after upgrade. |
501
-
502
- ---
503
-
504
- ## Architecture overview
505
-
506
- The plugin is a single ESM module with five layers wired through opencode
507
- event hooks:
508
-
509
- ```
510
- ┌─────────────────────────────────────────────────────┐
511
- │ opencode event hooks │
512
- │ tool.execute.before tool.execute.after │
513
- │ chat.messages.transform chat.system.transform │
514
- └───────────────┬─────────────────────┬────────────────┘
515
- │ │
516
- ┌───────────────────▼────────┐ ┌──────────▼─────────────┐
517
- │ AuditStateCache (TTL) │ │ Decision + Scoring │
518
- │ recentWriteFilePaths │ │ Engine (-1..+1 score) │
519
- │ accumulatedDeviations │ └──────────┬─────────────┘
520
- │ recentInterventionTexts │ │
521
- └───────────────────────────┘ │
522
- │ │
523
- ┌─────────────────────────▼─────────────────────▼──────────────┐
524
- │ Governance pipeline │
525
- │ Protocol Enforcer → Scoring → Decision Handler → │
526
- │ Intervention │
527
- └──────────────────────────────────────────────────────────────┘
528
- │
529
- │
530
- ┌─────────────────────────▼──────────────────────────────────────┐
531
- │ Graph sync + tool layer │
532
- │ codegraph + graphify (auto-init, git hooks, auto-upgrade) │
533
- │ 12 omo_* tools (search, find, impact, recall, files, etc.) │
534
- └───────────────────────────────────────────────────────────────┘
535
- │
536
- ▼
537
- ┌──────────────────────────────────┐
538
- │ SQLite (bun:sqlite) + AgentMem │
539
- │ meta-governor.db / decisions │
540
- │ / lessons / audit state │
541
- └──────────────────────────────────┘
542
- ```
543
-
544
- See [ARCHITECTURE.md](ARCHITECTURE.md) for module-level relationships and
545
- [STRUCTURE.md](STRUCTURE.md) for the file layout.
546
-
547
- ---
548
-
549
- ## Testing
550
-
551
- ```bash
552
- bun test # full suite (672+ tests)
553
- bun test src/upgrade-autofix.test.ts # Wave 1: auto-upgrade regression
554
- bun test src/custom-tools.test.ts # Wave 2: 12 tools
555
- ```
556
-
557
- **Coverage highlights (v0.26.0):**
558
-
559
- - 10 tests in `src/upgrade-autofix.test.ts` (AUT-1..AUT-7) — tiered probe,
560
- pip `--upgrade` flag, `graphify check-update` integration, cache
561
- write-once semantics.
562
- - 7 tests in `src/custom-tools.test.ts` for the new tools
563
- (FIL-1..3, CAL-1..2, NOD-1..2) plus full coverage of the existing 9.
564
- - 686+ tests across `decision-store`, `token-predictor`,
565
- `protocol-enforcer`, `graph-sync`, `skill-priming`, `ci-monitor`,
566
- `audit-state-cache`, `closed-loop-learning`, `closed-loop`,
567
- `config-file`, `session-bridge`, `sqlite-backend`, `memory-aggregator`,
568
- `proc-guard`, `ttl-queue`, `mcp-client`, `scoring-engine`, `v018-fixes`,
569
- `v172`, `v173-f51`, `v173-gap-d`, `intervention-fix`, `graphsink-fix`,
570
- `plugin`, `plugin-graphsync`, `plugin-audit-postwave`, `postwave-wire`,
571
- `postwave-gate`.
572
-
573
- Known flaky test: `runGuarded > times out` (1 test) — pre-existing,
574
- unrelated to v0.26.0, confirmed by Oracle audit.
575
-
576
- ---
577
-
578
- ## Migration from earlier versions
579
-
580
- **From v0.24.x → v0.26.0:**
581
-
582
- - **Stale-cache detection (v0.24.3):** On plugin load, an async npm
583
- version check runs in the background. If the loaded version differs
584
- from the latest published version, a warning is logged with
585
- cache-clearing instructions. If you see `STALE_CACHE` in
586
- `meta-governor.log`, run:
587
-
588
- ```bash
589
- npm cache clean --force && rm -rf ~/.cache/opencode/packages/@herjarsa/omo-meta-governor*
590
- ```
591
-
592
- Then restart opencode.
593
-
594
- - **Auto-upgrade (v0.26.0):** No user action required. The plugin now
595
- upgrades codegraph and graphify silently on every load. If you
596
- previously disabled `graphSync.enabled` to work around the broken
597
- upgrade, re-enable it.
598
-
599
- - **New config fields:** `graphSync.autoUpgrade`,
600
- `graphSync.upgradeCachePath`, `graphSync.checkGraphifyNeedsUpdate` —
601
- all default true. Schema is backward-compatible.
602
-
603
- **From earlier versions:** no user action required. All changes through
604
- v0.18.0 were transparent — the audit gaps (config drops, circular refs,
605
- metrics crashes) only affected edge cases where users set obscure
606
- fields. See [CHANGELOG.md](CHANGELOG.md) for the full history.
607
-
608
- ---
609
-
610
- ## License
611
-
1
+ # @herjarsa/omo-meta-governor
2
+
3
+ > Self-judging agent orchestration layer for [OpenCode](https://opencode.ai).
4
+ > Observes tool executions, scores progress, dispatches decisions, and exposes
5
+ > **12 custom tools** the agent can invoke across CodeGraph, Graphify,
6
+ > AgentMemory, and SQLite — for cheaper, more accurate code understanding.
7
+
8
+ **Current version:** `0.26.0` · **License:** MIT · **Status:** stable
9
+
10
+ ---
11
+
12
+ ## Table of Contents
13
+
14
+ - [Install](#install)
15
+ - [What it does](#what-it-does)
16
+ - [12 Custom Tools](#12-custom-tools)
17
+ - [Code search & navigation](#code-search--navigation)
18
+ - [Lesson & memory](#lesson--memory)
19
+ - [File & symbol lookup](#file--symbol-lookup)
20
+ - [Safety & status](#safety--status)
21
+ - [Governance pipeline](#governance-pipeline)
22
+ - [Scoring engine](#scoring-engine)
23
+ - [Intervention modes](#intervention-modes)
24
+ - [Protocol enforcement](#protocol-enforcement)
25
+ - [Skill priming](#skill-priming)
26
+ - [Multi-phase plans](#multi-phase-plans)
27
+ - [Graph sync (codegraph + graphify)](#graph-sync-codegraph--graphify)
28
+ - [Auto-init](#auto-init)
29
+ - [Auto-upgrade (v0.26.0)](#auto-upgrade-v0260)
30
+ - [Git hooks](#git-hooks)
31
+ - [Process safeguards](#process-safeguards)
32
+ - [Persistence & observability](#persistence--observability)
33
+ - [CI monitor (v0.25.0)](#ci-monitor-v0250)
34
+ - [Configuration reference](#configuration-reference)
35
+ - [Architecture overview](#architecture-overview)
36
+ - [Testing](#testing)
37
+ - [Migration from earlier versions](#migration-from-earlier-versions)
38
+ - [License](#license)
39
+
40
+ ---
41
+
42
+ ## Install
43
+
44
+ ```bash
45
+ npm install @herjarsa/omo-meta-governor
46
+ ```
47
+
48
+ Add as a plugin in your OpenCode config (`~/.config/opencode/opencode.jsonc`):
49
+
50
+ ```jsonc
51
+ {
52
+ "plugins": ["@herjarsa/omo-meta-governor"]
53
+ }
54
+ ```
55
+
56
+ The 12 custom tools register automatically on every load. To also enable
57
+ the governance pipeline (scoring, intervention, protocol enforcement):
58
+
59
+ ```jsonc
60
+ {
61
+ "meta_governor": {
62
+ "enabled": true,
63
+ "intervention": { "mode": "message", "minActionForMessage": "warn" }
64
+ }
65
+ }
66
+ ```
67
+
68
+ ---
69
+
70
+ ## What it does
71
+
72
+ `omo-meta-governor` is a single OpenCode plugin that ships **five
73
+ interconnected subsystems**:
74
+
75
+ | Subsystem | Purpose | Surface |
76
+ |---|---|---|
77
+ | **Graph sync** | Auto-install codegraph + graphify, build initial index, wire git hooks, auto-upgrade binaries on every load | `graphSync.*` config |
78
+ | **12 custom tools** | Semantic code search, impact analysis, symbol lookup, lesson recall, file/caller/node queries, health | `omo_*` tools |
79
+ | **Governance pipeline** | Score session progress → dispatch decision (`continue` / `warn` / `escalate` / `stop`) → optionally inject it into the agent's context | `meta_governor.enabled` |
80
+ | **Memory + lessons** | Persist decisions and lessons in SQLite (FTS5) + bridge to AgentMemory for cross-session recall | `omo_recall`, `omo_remember`, `omo_recall_mcp` |
81
+ | **Observability** | Health JSON, rotating JSONL logs, metrics, audit state, CI monitor | `omo_health`, `~/.config/opencode/meta-governor-health.json` |
82
+
83
+ All five run **inside the plugin** — no daemon, no sidecar. They share
84
+ process boundaries, lifecycle, and the opencode event hooks
85
+ (`tool.execute.before` / `tool.execute.after` / `chat.messages.transform`).
86
+
87
+ ---
88
+
89
+ ## 12 Custom Tools
90
+
91
+ The plugin registers 12 tools the LLM can invoke. All are available
92
+ immediately on install (no `enabled: true` required for tools — only the
93
+ governance pipeline needs `meta_governor.enabled: true`).
94
+
95
+ ### Code search & navigation
96
+
97
+ | Tool | What it does | Use case |
98
+ |------|--------------|----------|
99
+ | `omo_search` | Semantic code search via codegraph or graphify | "Where is authentication handled?" — USE THIS FIRST for any architecture question |
100
+ | `omo_find` | Exact symbol lookup (definition + direct callers) via `codegraph node` | "Find the function `validateToken`" |
101
+ | `omo_impact` | Impact analysis: direct + transitive callers, test files, doc files | Run BEFORE modifying a function |
102
+ | `omo_path` | Shortest conceptual path between two concepts via graphify | "How does auth connect to database?" |
103
+ | `omo_explain` | Plain-language explanation of a concept via graphify | "What is the SwinTransformer?" |
104
+
105
+ ### Lesson & memory
106
+
107
+ | Tool | What it does | Use case |
108
+ |------|--------------|----------|
109
+ | `omo_recall` | Search past lessons via local SQLite FTS5 (fast, always available) | "How did we set up auth before?" |
110
+ | `omo_recall_mcp` | Search cross-session memory via AgentMemory | "What did we learn about X in previous sessions?" |
111
+ | `omo_remember` | Save a fact / observation / pattern to cross-session AgentMemory | "Remember this bug pattern for next time" |
112
+
113
+ ### File & symbol lookup
114
+
115
+ | Tool | What it does | Use case |
116
+ |------|--------------|----------|
117
+ | `omo_files` | List files indexed by codegraph or graphify | "What files are in the graph?" |
118
+ | `omo_callers` | List all call sites of a symbol via `codegraph callers` | "Who calls `UserService.create`?" |
119
+ | `omo_node` | Get source + direct callers of a symbol via `codegraph node` | "Show me the source of `validateToken` and its callers" |
120
+
121
+ ### Safety & status
122
+
123
+ | Tool | What it does | Use case |
124
+ |------|--------------|----------|
125
+ | `omo_health` | Show plugin runtime status: metrics, decisions, errors | "Is the plugin working?" |
126
+
127
+ All tools return a typed `ToolResult` with `title`, `output`, and
128
+ `metadata` (`{tool, kind, durationMs, sessionID}`). They degrade
129
+ gracefully — when codegraph or graphify is missing, they return a
130
+ **friendly hint** (e.g. `npx codegraph init` to recover) instead of
131
+ crashing.
132
+
133
+ ---
134
+
135
+ ## Governance pipeline
136
+
137
+ When `meta_governor.enabled: true`, the plugin attaches to opencode's
138
+ tool-execution stream and runs an **observe → score → decide → (optionally)
139
+ intervene** loop on every turn.
140
+
141
+ ### Scoring engine
142
+
143
+ `src/scoring-engine.ts` computes a single composite score in `[-1, 1]`
144
+ from weighted signals:
145
+
146
+ | Signal | Weight | Source |
147
+ |--------|--------|--------|
148
+ | `progress-detector` | 0.30 | did the last 5 tool calls make forward progress? |
149
+ | `deviation-detector` | 0.20 | accumulated protocol violations (capped at 5/session) |
150
+ | `no-progress-detector` | 0.20 | is the agent reading without writing? |
151
+ | `iteration-budget` | 0.15 | are we approaching `maxIterations`? |
152
+ | `oracle-burn` | 0.10 | did recent oracle calls detect issues? |
153
+ | `stop-advice` | 0.05 | did prior lessons recommend stop? |
154
+
155
+ The score maps to an action via configurable thresholds (see
156
+ [Configuration reference](#configuration-reference)):
157
+
158
+ - `score ≥ continueThreshold` → **continue** (silent)
159
+ - `score ≤ -warnThreshold` → **warn** (log + nudge)
160
+ - `score ≤ -escalateThreshold` → **escalate** (block + inject)
161
+ - `score ≤ -stopThreshold` → **stop** (latch intervention)
162
+
163
+ Default thresholds: `continue: 0.05`, `warn: 0.3`, `escalate: 0.45`,
164
+ `stop: 0.55` (worst-case math gives `stop ≈ -0.55`, so it actually
165
+ fires — verified via Gap C audit).
166
+
167
+ ### Intervention modes
168
+
169
+ When the decision is `warn` / `escalate` / `stop`, the plugin can inject
170
+ the rationale into the agent's context via `experimental.chat.messages.transform`:
171
+
172
+ | Mode | Mechanism | Effect |
173
+ |------|-----------|--------|
174
+ | `silent` | (none) | Decision is logged only |
175
+ | `message` | `chat.messages.transform` | Injects a synthetic user message visible to the LLM |
176
+ | `system` | `chat.system.transform` | Appends guidance to the system prompt |
177
+
178
+ `maxInterventionsPerSession: 3` (default) hard-stops injection after 3
179
+ interventions per session to prevent infinite instruction loops
180
+ (v0.10.0). When `respectDoneSignal: true` (default), injection stops
181
+ once the agent emits the terminal signal AND Oracle has verified.
182
+
183
+ ### Protocol enforcement
184
+
185
+ `src/protocol-enforcer.ts` audits tool calls against a configurable
186
+ protocol markdown file. Use it to enforce rules like "do not save
187
+ routine operations to memory" or "always invoke Oracle before declaring
188
+ done".
189
+
190
+ ```jsonc
191
+ {
192
+ "meta_governor": {
193
+ "enabled": true,
194
+ "protocolEnforcement": {
195
+ "enabled": true,
196
+ "path": "./PROTOCOL.md",
197
+ "injectIntoSystem": true,
198
+ "auditToolCalls": true
199
+ }
200
+ }
201
+ }
202
+ ```
203
+
204
+ Violations accumulate in `state.accumulatedDeviations` (capped at 5 per
205
+ session) and feed the `deviation-detector` scoring signal.
206
+
207
+ ### Skill priming
208
+
209
+ `src/skill-priming.ts` (v0.20.0) injects **one** synthetic user message
210
+ at session start (or once implementation work begins) prompting the
211
+ agent to select precise skills for the task via the AAS skill catalog
212
+ (`aas search_skills` / `get_skill` / `compose_stack`) and/or the
213
+ task-appropriate superpowers skill — before writing code. Minimal context
214
+ cost: the directive forbids enumerating the full catalog.
215
+
216
+ ```jsonc
217
+ {
218
+ "meta_governor": {
219
+ "enabled": true,
220
+ "skillPriming": {
221
+ "enabled": true,
222
+ "trigger": "firstImplement",
223
+ "router": "both"
224
+ }
225
+ }
226
+ }
227
+ ```
228
+
229
+ ### Multi-phase plans
230
+
231
+ For work plans with multiple phases (e.g. Sisyphus/Prometheus work
232
+ plans), set `phaseAwareDoneSignal: true` and emit
233
+ `<promise>PLAN-COMPLETE</promise>` only when the **entire** plan is
234
+ verified done by Oracle.
235
+
236
+ | Marker | Effect |
237
+ |--------|--------|
238
+ | `<promise>DONE</promise>` | Per-phase hint. Logged but does NOT latch intervention (when `phaseAwareDoneSignal: true`). |
239
+ | `<promise>PHASE-N-COMPLETE</promise>` | Per-phase hint (e.g. `<promise>PHASE-1-COMPLETE</promise>`). Same as `DONE`. |
240
+ | `<promise>PLAN-COMPLETE</promise>` | Terminal. Latches intervention when Oracle has verified. |
241
+
242
+ ---
243
+
244
+ ## Graph sync (codegraph + graphify)
245
+
246
+ The plugin wires the native git hooks of **codegraph** and **graphify**
247
+ so each commit automatically reindexes both graphs.
248
+
249
+ ### Auto-init
250
+
251
+ On first load in a project (when `graphSync.enabled: true`, default):
252
+
253
+ 1. **Auto-install** codegraph via `npm i -D @colbymchenry/codegraph` and
254
+ graphify via `pip install graphifyy` (falls back to
255
+ `uv tool install graphifyy`) if not already on PATH.
256
+ 2. **Run `codegraph init`** + **`graphify . --no-viz`** to build the
257
+ initial indexes for the project.
258
+ 3. **Run `graphify hook install`** to wire up the native `post-commit`
259
+ and `post-checkout` git hooks.
260
+
261
+ ### Auto-upgrade (v0.26.0)
262
+
263
+ Before v0.26.0, `autoUpgrade: true` (default) silently failed. Six
264
+ bugs in `src/graph-sync.ts:503-628` forced users to manually run
265
+ `npm install -g @colbymchenry/codegraph@latest` and
266
+ `pip install --upgrade graphifyy`.
267
+
268
+ **Root cause bugs fixed in v0.26.0:**
269
+
270
+ 1. `getInstalledCodegraphVersion` only probed `npx` — failed when the
271
+ binary was at `node_modules/.bin/codegraph` (Windows users).
272
+ 2. `getInstalledGraphifyVersion` had no DI runner — Windows dual-python
273
+ fallback was untestable.
274
+ 3. `shouldUpgrade` ignored the cache value (`latest=null`).
275
+ 4. Cache cold + undetectable binary → silent noop (no diagnostic code).
276
+ 5. **`pip install` without `--upgrade` returned 0** with
277
+ "Requirement already satisfied" but **did NOT upgrade** — most
278
+ visible bug.
279
+ 6. `graphify check-update` was ignored — semantic re-extraction flag
280
+ never triggered.
281
+
282
+ **Fixes:**
283
+
284
+ - Tiered probe matching `checkToolAvailability`: `npx` +
285
+ `node node_modules/.bin/codegraph` for codegraph; `graphify` →
286
+ `python -m pip show` → `python3 -m pip show` for graphify.
287
+ - Runner DI seam on `getInstalledCodegraphVersion`,
288
+ `getInstalledGraphifyVersion`, `installCodegraph`, `installGraphify`
289
+ — hermetic tests, no real network in CI.
290
+ - `resolveLatest()` inlines cache into `shouldUpgrade` — avoids
291
+ double-fetch from the registry.
292
+ - Cache written **ONCE** at the end of the upgrade block (was being
293
+ fetched 3× per run).
294
+ - `pip install --upgrade graphifyy` / `uv tool install --upgrade graphifyy`
295
+ flags.
296
+ - `graphify check-update` integration emits
297
+ `graphify-reextract-triggered` when semantic re-extraction is pending.
298
+ - New codes: `codegraph-upgrade-broken`, `graphify-reextract-triggered`,
299
+ `upgrade-cache-written`.
300
+ - New config fields: `autoUpgrade`, `upgradeCachePath`,
301
+ `checkGraphifyNeedsUpdate`.
302
+
303
+ **Verified surface run:** `codegraph 0.6.8 → 1.5.0` and
304
+ `graphify 0.8.30 → 0.9.46` upgraded silently without manual
305
+ intervention.
306
+
307
+ **Configuration:**
308
+
309
+ ```jsonc
310
+ {
311
+ "meta_governor": {
312
+ "graphSync": {
313
+ "enabled": true, // default true
314
+ "autoUpgrade": true, // v0.26.0: default true
315
+ "upgradeCachePath": "~/.omo-meta-governor/upgrade-cache.json",
316
+ "checkGraphifyNeedsUpdate": true // emit graphify-reextract-triggered when schema changed
317
+ }
318
+ }
319
+ }
320
+ ```
321
+
322
+ ### Git hooks
323
+
324
+ On every `git commit`:
325
+
326
+ - **Primary path** (native git hook): `graphify update` runs in background.
327
+ - **Backup path** (plugin's `tool.execute.after`): detects `git commit`
328
+ in bash commands and runs `codegraph sync -q [path]`.
329
+
330
+ ### Process safeguards
331
+
332
+ Every subprocess the plugin spawns (graphify, codegraph, npx, python,
333
+ npm/pip) is guaranteed to die after use — on success, error, AND
334
+ timeout — including its descendant tree. On Windows this uses
335
+ `taskkill /pid <pid> /T /F` (plain `child.kill()` only kills the direct
336
+ shell, orphaning grandchildren — the confirmed cause of the
337
+ Bun/OpenChamber crashes).
338
+
339
+ Config: `graphSync.killOrphanedOnInit` (default `true`) — on graph-sync
340
+ init the plugin sweeps orphaned `graphify`/`codegraph` processes left
341
+ by previous crashed runs. Set to `false` to disable the sweep.
342
+
343
+ ## MCP server mode (v0.31.0)
344
+
345
+ OpenCode Desktop and OpenChamber spawn `opencode serve` in HTTP/sidecar mode
346
+ where plugin `hooks.tool` registrations don't reach the UI (the factory
347
+ is never invoked). The MCP server mode exposes the same `omo_*` tools via
348
+ an independent MCP server process — the same delivery mechanism that powers
349
+ `codegraph`, `graphify`, `agentmemory`, etc.
350
+
351
+ Both modes can be active simultaneously without conflict.
352
+
353
+ ### Setup
354
+
355
+ Add to your `~/.config/opencode/opencode.jsonc`:
356
+
357
+ ```json
358
+ {
359
+ "mcp": {
360
+ "omo-meta-governor": {
361
+ "type": "local",
362
+ "command": ["npx", "-y", "@herjarsa/omo-meta-governor", "omo-meta-governor-mcp"]
363
+ }
364
+ }
365
+ }
366
+ ```
367
+
368
+ To target a specific project directory, set the `OMO_CWD` environment
369
+ variable in the MCP config:
370
+
371
+ ```json
372
+ {
373
+ "mcp": {
374
+ "omo-meta-governor": {
375
+ "type": "local",
376
+ "command": ["npx", "-y", "@herjarsa/omo-meta-governor", "omo-meta-governor-mcp"],
377
+ "environment": { "OMO_CWD": "/absolute/path/to/project" }
378
+ }
379
+ }
380
+ }
381
+ ```
382
+
383
+ ### Tools exposed
384
+
385
+ The MCP server exposes a curated subset of the full tool surface:
386
+
387
+ | Tool | Description |
388
+ |------|-------------|
389
+ | `omo_search` | Semantic code search via codegraph/graphify |
390
+ | `omo_recall` | Search past lessons in the project memory |
391
+ | `omo_health` | Show plugin runtime status |
392
+ | `omo_find` | Find a symbol by name in the codegraph index |
393
+ | `omo_impact` | Show what a symbol affects |
394
+ | `omo_path` | Find shortest path between two graph nodes |
395
+ | `omo_explain` | Explain a graph node |
396
+ | `omo_status` | Show graphify status |
397
+ | `omo_index` | Run graphify indexing |
398
+ | `omo_visualize` | Open the graphify visualisation server |
399
+ | `omo_serve` | Start the graphify HTTP API server |
400
+ | `omo_diagnose` | Diagnose graph inconsistencies |
401
+ | `omo_uninit` | Remove the codegraph index from disk |
402
+ | `omo_sync_if_dirty` | Trigger codegraph reindex if stale |
403
+ | `omo_mark_dirty` | Mark the codegraph index as stale |
404
+ | `omo_hook_status` | Check whether the graphify post-commit hook is installed |
405
+
406
+ Some tools from the plugin mode (`omo_remember`, `omo_recall_mcp`,
407
+ `omo_unlock`, `omo_clone`, etc.) are intentionally NOT exposed via the MCP
408
+ server — they either require the session client or lack browser-side
409
+ visibility. Use the CLI or plugin hooks for those.
410
+
411
+ ### Technical notes
412
+
413
+ - Tool implementations are reused from `custom-tools.ts` via the adapter
414
+ pattern — fixes in the plugin surface are automatically available in MCP
415
+ mode.
416
+ - The MCP server process is independent of the opencode sidecar. It has
417
+ its own `GraphRetrieval`, `SqliteBackend`, and `MetricsCollector`
418
+ singletons.
419
+ - Backward-compatible: existing users who only use the `plugin` key in
420
+ `opencode.jsonc` see no behavior change.
421
+
422
+ ---
423
+
424
+ ## Persistence & observability
425
+
426
+ **Lesson storage.** Decisions and lessons persist in **SQLite** at
427
+ `~/.omo-meta-governor/meta-governor.db` with full-text search (FTS5) for
428
+ fast recall. Zero dependencies — uses Bun's built-in `bun:sqlite`.
429
+
430
+ **Cross-session memory.** The `omo_remember` / `omo_recall_mcp` tools
431
+ bridge to AgentMemory via `session.prompt()` — the LLM receives a
432
+ structured instruction to call the appropriate MCP tool.
433
+
434
+ **Health JSON** at `~/.config/opencode/meta-governor-health.json`:
435
+
436
+ ```bash
437
+ cat ~/.config/opencode/meta-governor-health.json
438
+ ```
439
+
440
+ Or invoke `omo_health` directly for a formatted report.
441
+
442
+ **Structured JSONL logs** at `~/.config/opencode/meta-governor.log` with
443
+ size-based rotation (10MB max, 5 rotated files). Secret redaction layer
444
+ strips JWT, OpenAI keys, Bearer tokens, GitHub PATs, and generic
445
+ `key:value` patterns before writing.
446
+
447
+ ---
448
+
449
+ ## CI monitor (v0.25.0)
450
+
451
+ `src/ci-monitor.ts` auto-triggers GitHub Actions on `git push` and
452
+ surfaces failures to the agent:
453
+
454
+ - Detects `git push` in bash commands via the `tool.execute.after` hook.
455
+ - Polls the GH Actions API for the resulting run (5s initial delay,
456
+ exponential backoff).
457
+ - On failure, injects a synthetic message with the failed logs into the
458
+ agent's context so it can fix and retry.
459
+
460
+ Configurable via `meta_governor.ciMonitor` (disabled by default — opt-in
461
+ feature).
462
+
463
+ ---
464
+
465
+ ## Configuration reference
466
+
467
+ All configuration lives under the `meta_governor` key in
468
+ `opencode.jsonc`. Full schema:
469
+ [assets/omo-meta-governor.schema.json](assets/omo-meta-governor.schema.json).
470
+
471
+ ### Top-level
472
+
473
+ | Field | Type | Default | Description |
474
+ |-------|------|---------|-------------|
475
+ | `enabled` | boolean | `false` | Master feature flag — must be true to run the orchestrator. |
476
+ | `decision` | object | — | Decision handler tuning. |
477
+ | `memory` | object | — | Memory aggregator config. |
478
+ | `tokenPredictor` | object | — | Token predictor (compact-now / switch-model / delegate recommendations). |
479
+ | `scoring` | object | — | Scoring engine thresholds. |
480
+ | `closedLoop` | object | — | Closed-loop learning (save decisions + lessons). |
481
+ | `modelOverride` | object | — | Model override for MetaGovernor's internal LLM usage. |
482
+ | `intervention` | object | — | Visible decision injection config. |
483
+ | `protocolEnforcement` | object | — | Sisyphus protocol enforcement. |
484
+ | `skillPriming` | object | — | Proactive skill-selection nudge (v0.20.0). |
485
+ | `graphSync` | object | — | Graph synchronization (auto-init codegraph/graphify). |
486
+
487
+ ### `decision`
488
+
489
+ | Field | Type | Default | Description |
490
+ |-------|------|---------|-------------|
491
+ | `maxHistoryPerSession` | integer | — | Maximum history entries per session before oldest are trimmed. |
492
+ | `forceContinueAfterStops` | integer | — | How many consecutive stops before forcing continue. |
493
+
494
+ ### `memory`
495
+
496
+ | Field | Type | Default | Description |
497
+ |-------|------|---------|-------------|
498
+ | `agentmemoryTimeoutMs` | integer | — | Timeout for agentmemory queries in milliseconds. |
499
+ | `boulderStateTimeoutMs` | integer | — | Timeout for boulder-state queries in milliseconds. |
500
+ | `query` | string | — | Natural-language query for memory recall. |
501
+
502
+ ### `tokenPredictor`
503
+
504
+ | Field | Type | Default | Description |
505
+ |-------|------|---------|-------------|
506
+ | `compactBurnRateThreshold` | integer | — | Burn rate (tokens/turn) above which to recommend compact-now. |
507
+ | `compactUsageThreshold` | number | — | Context usage ratio (0..1) above which to recommend compact-now. |
508
+ | `switchModelUsageThreshold` | number | — | Context usage ratio above which to recommend switch-model. |
509
+ | `delegateConsecutiveHighBurn` | integer | — | Max consecutive high-burn turns before recommending delegate. |
510
+
511
+ ### `scoring`
512
+
513
+ | Field | Type | Default | Description |
514
+ |-------|------|---------|-------------|
515
+ | `continueThreshold` | number | `0.05` | Score ≥ this → continue silently. |
516
+ | `warnThreshold` | number | `0.3` | Score ≤ -this → warn. |
517
+ | `escalateThreshold` | number | `0.45` | Score ≤ -this → escalate. |
518
+ | `stopThreshold` | number | `0.55` | Score ≤ -this → stop. |
519
+
520
+ ### `closedLoop`
521
+
522
+ | Field | Type | Default | Description |
523
+ |-------|------|---------|-------------|
524
+ | `saveDecisions` | boolean | `true` | Whether to save decision records. |
525
+ | `saveLessons` | boolean | `true` | Whether to save lessons. |
526
+
527
+ ### `modelOverride`
528
+
529
+ | Field | Type | Default | Description |
530
+ |-------|------|---------|-------------|
531
+ | `providerID` | string | — | Provider ID (e.g. `'openai'`, `'anthropic'`). |
532
+ | `modelID` | string | — | Model ID (e.g. `'gpt-4o-mini'`, `'claude-sonnet-4-20250514'`). |
533
+ | `modelLimit` | integer | — | Context window size for token predictor (min 1000). |
534
+ | `temperature` | number | `0.2` | Sampling temperature (0..2). |
535
+ | `topP` | number | `1` | Top-p nucleus sampling (0..1). |
536
+ | `maxTokens` | integer | — | Max output tokens for internal reasoning. |
537
+ | `reasoning` | boolean | — | Enable extended reasoning / thinking mode. |
538
+ | `verbosity` | enum | — | `'silent'` \| `'minimal'` \| `'verbose'`. |
539
+
540
+ ### `intervention`
541
+
542
+ | Field | Type | Default | Description |
543
+ |-------|------|---------|-------------|
544
+ | `mode` | enum | — | `'silent'` \| `'message'` \| `'system'`. |
545
+ | `includeDecisionHistory` | boolean | — | Whether to include recent decision history in injection. |
546
+ | `maxHistoryMessages` | integer | `5` | Max history entries when `includeDecisionHistory: true`. |
547
+ | `minActionForMessage` | enum | — | Minimum action: `'warn'` (all non-continue), `'escalate'`, `'stop'`. |
548
+ | `persistToSession` | boolean | `true` | v0.19.0: when true, intervention messages ALSO persist to the session. |
549
+ | `maxInterventionsPerSession` | integer | `3` | v0.10.0: hard cap before auto-disable. |
550
+ | `respectDoneSignal` | boolean | `true` | Stop injecting once terminal signal + Oracle verified. |
551
+ | `phaseAwareDoneSignal` | boolean | `false` | v0.15.0: split per-phase hint from terminal signal. |
552
+ | `compactionLoopGuard.enabled` | boolean | `false` | v0.31.1: opt-in defense against opencode [#27924](https://github.com/anomalyco/opencode/issues/27924) (infinite overflow-compaction loop). |
553
+ | `compactionLoopGuard.maxOverflowRecoveries` | integer | `2` | v0.31.1: consecutive overflow compactions tolerated before the guard trips. |
554
+
555
+ ### `protocolEnforcement`
556
+
557
+ | Field | Type | Default | Description |
558
+ |-------|------|---------|-------------|
559
+ | `enabled` | boolean | — | Master switch. |
560
+ | `path` | string | — | Path to protocol markdown file. |
561
+ | `injectIntoSystem` | boolean | — | Whether to inject protocol rules into the system prompt. |
562
+ | `auditToolCalls` | boolean | — | Whether to audit tool calls for violations. |
563
+
564
+ ### `skillPriming`
565
+
566
+ | Field | Type | Default | Description |
567
+ |-------|------|---------|-------------|
568
+ | `enabled` | boolean | `false` | Master switch. |
569
+ | `trigger` | enum | `'firstImplement'` | `'sessionStart'` (first transform) or `'firstImplement'` (once write-like tool observed). |
570
+ | `router` | enum | `'both'` | `'aas'` \| `'superpowers'` \| `'both'`. |
571
+
572
+ ### `graphSync`
573
+
574
+ | Field | Type | Default | Description |
575
+ |-------|------|---------|-------------|
576
+ | `enabled` | boolean | `true` | Enable auto-initialization. |
577
+ | `watch` | boolean | `false` | Enable watch mode (re-index on file changes). |
578
+ | `killOrphanedOnInit` | boolean | `true` | Sweep orphaned processes on init. |
579
+ | `autoUpgrade` | boolean | `true` | **v0.26.0** — auto-upgrade installed codegraph + graphify binaries. |
580
+ | `upgradeCachePath` | string | — | **v0.26.0** — path for the upgrade cache file. |
581
+ | `checkGraphifyNeedsUpdate` | boolean | `true` | **v0.26.0** — run `graphify check-update` after upgrade. |
582
+
583
+ ---
584
+
585
+ ## Architecture overview
586
+
587
+ The plugin is a single ESM module with five layers wired through opencode
588
+ event hooks:
589
+
590
+ ```
591
+ ┌─────────────────────────────────────────────────────┐
592
+ │ opencode event hooks │
593
+ │ tool.execute.before tool.execute.after │
594
+ │ chat.messages.transform chat.system.transform │
595
+ └───────────────┬─────────────────────┬────────────────┘
596
+ │ │
597
+ ┌───────────────────▼────────┐ ┌──────────▼─────────────┐
598
+ │ AuditStateCache (TTL) │ │ Decision + Scoring │
599
+ │ recentWriteFilePaths │ │ Engine (-1..+1 score) │
600
+ │ accumulatedDeviations │ └──────────┬─────────────┘
601
+ │ recentInterventionTexts │ │
602
+ └───────────────────────────┘ │
603
+ │ │
604
+ ┌─────────────────────────▼─────────────────────▼──────────────┐
605
+ │ Governance pipeline │
606
+ │ Protocol Enforcer → Scoring → Decision Handler → │
607
+ │ Intervention │
608
+ └──────────────────────────────────────────────────────────────┘
609
+ │
610
+ │
611
+ ┌─────────────────────────▼──────────────────────────────────────┐
612
+ │ Graph sync + tool layer │
613
+ │ codegraph + graphify (auto-init, git hooks, auto-upgrade) │
614
+ │ 12 omo_* tools (search, find, impact, recall, files, etc.) │
615
+ └───────────────────────────────────────────────────────────────┘
616
+ │
617
+ ▼
618
+ ┌──────────────────────────────────┐
619
+ │ SQLite (bun:sqlite) + AgentMem │
620
+ │ meta-governor.db / decisions │
621
+ │ / lessons / audit state │
622
+ └──────────────────────────────────┘
623
+ ```
624
+
625
+ See [ARCHITECTURE.md](ARCHITECTURE.md) for module-level relationships and
626
+ [STRUCTURE.md](STRUCTURE.md) for the file layout.
627
+
628
+ ---
629
+
630
+ ## Testing
631
+
632
+ ```bash
633
+ bun test # full suite (672+ tests)
634
+ bun test src/upgrade-autofix.test.ts # Wave 1: auto-upgrade regression
635
+ bun test src/custom-tools.test.ts # Wave 2: 12 tools
636
+ ```
637
+
638
+ **Coverage highlights (v0.26.0):**
639
+
640
+ - 10 tests in `src/upgrade-autofix.test.ts` (AUT-1..AUT-7) — tiered probe,
641
+ pip `--upgrade` flag, `graphify check-update` integration, cache
642
+ write-once semantics.
643
+ - 7 tests in `src/custom-tools.test.ts` for the new tools
644
+ (FIL-1..3, CAL-1..2, NOD-1..2) plus full coverage of the existing 9.
645
+ - 686+ tests across `decision-store`, `token-predictor`,
646
+ `protocol-enforcer`, `graph-sync`, `skill-priming`, `ci-monitor`,
647
+ `audit-state-cache`, `closed-loop-learning`, `closed-loop`,
648
+ `config-file`, `session-bridge`, `sqlite-backend`, `memory-aggregator`,
649
+ `proc-guard`, `ttl-queue`, `mcp-client`, `scoring-engine`, `v018-fixes`,
650
+ `v172`, `v173-f51`, `v173-gap-d`, `intervention-fix`, `graphsink-fix`,
651
+ `plugin`, `plugin-graphsync`, `plugin-audit-postwave`, `postwave-wire`,
652
+ `postwave-gate`.
653
+
654
+ Known flaky test: `runGuarded > times out` (1 test) — pre-existing,
655
+ unrelated to v0.26.0, confirmed by Oracle audit.
656
+
657
+ ---
658
+
659
+ ## Migration from earlier versions
660
+
661
+ **From v0.24.x → v0.26.0:**
662
+
663
+ - **Stale-cache detection (v0.24.3):** On plugin load, an async npm
664
+ version check runs in the background. If the loaded version differs
665
+ from the latest published version, a warning is logged with
666
+ cache-clearing instructions. If you see `STALE_CACHE` in
667
+ `meta-governor.log`, run:
668
+
669
+ ```bash
670
+ npm cache clean --force && rm -rf ~/.cache/opencode/packages/@herjarsa/omo-meta-governor*
671
+ ```
672
+
673
+ Then restart opencode.
674
+
675
+ - **Auto-upgrade (v0.26.0):** No user action required. The plugin now
676
+ upgrades codegraph and graphify silently on every load. If you
677
+ previously disabled `graphSync.enabled` to work around the broken
678
+ upgrade, re-enable it.
679
+
680
+ - **New config fields:** `graphSync.autoUpgrade`,
681
+ `graphSync.upgradeCachePath`, `graphSync.checkGraphifyNeedsUpdate` —
682
+ all default true. Schema is backward-compatible.
683
+
684
+ **From earlier versions:** no user action required. All changes through
685
+ v0.18.0 were transparent — the audit gaps (config drops, circular refs,
686
+ metrics crashes) only affected edge cases where users set obscure
687
+ fields. See [CHANGELOG.md](CHANGELOG.md) for the full history.
688
+
689
+ ---
690
+
691
+ ## License
692
+
612
693
  MIT