@herjarsa/omo-meta-governor 0.1.0

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.
@@ -0,0 +1,476 @@
1
+ /**
2
+ * MetaGovernor type contracts.
3
+ *
4
+ * PR 1 of 8. Defines ONLY the type surface that later PRs implement against.
5
+ * No logic, no I/O, no MCP calls. This file is the source of truth for the
6
+ * MetaGovernor architecture; later PRs import these types and conform to them.
7
+ *
8
+ * Architectural invariants (from AGENTS.md, must respect):
9
+ * - All session.promptAsync calls go through prompt-async-gate (not enforced here)
10
+ * - MetaGovernor composes AFT + agentmemory + magic-context + boulder-state
11
+ * - 5 recovery hooks wrap into post-repair-recorder (not enforced here)
12
+ *
13
+ * Public surface (5 contracts):
14
+ * - DecisionContext: input to score()
15
+ * - Decision: output of score()
16
+ * - Evidence: atomic evidence unit attached to a Decision
17
+ * - MemoryRead: cross-system read result
18
+ * - TokenPrediction: token burn rate prediction
19
+ *
20
+ * All enums/actions are union string types (no enums) for Zod compat and
21
+ * JSON-serialisability across the prompt-async-gate.
22
+ */
23
+ /**
24
+ * What the judge sees when deciding continue|warn|escalate|stop.
25
+ *
26
+ * All fields are required. `undefined` is not a valid DecisionContext —
27
+ * collectors must fill every field even if the value is an empty array,
28
+ * false, or 0. Empty inputs are signals, not bugs.
29
+ */
30
+ export interface DecisionContext {
31
+ /** Whether the last Oracle invocation returned verified=true. */
32
+ readonly oracleVerified: boolean;
33
+ /** Whether the last assistant turn produced no progress (zero tokens, no content). */
34
+ readonly noProgress: boolean;
35
+ /** Detected deviations from expected behavior. Empty array = no deviations. */
36
+ readonly deviations: readonly Deviation[];
37
+ /** iteration / maxIterations. 0..1. 1.0 = at the cap. */
38
+ readonly iterationRatio: number;
39
+ /** Lessons retrieved from agentmemory that match the current decision pattern. */
40
+ readonly lessonsRelevant: readonly RelevantLesson[];
41
+ /** Cross-session memory snapshot from the meta_state slot. */
42
+ readonly slotMemory: SlotMemory;
43
+ /** Free-form context the calling site can attach (sessionID, directory, mode). */
44
+ readonly ambient: AmbientContext;
45
+ }
46
+ /**
47
+ * Decision the judge returns. Always carries evidence (cite-or-abstain).
48
+ *
49
+ * Score ∈ [-1, +1]:
50
+ * >= +0.3 → continue silently
51
+ * -0.3..+0.3 → continue with log
52
+ * -0.6..-0.3 → continue with warn
53
+ * -0.8..-0.6 → escalate (oracle or user)
54
+ * < -0.8 → stop loop
55
+ *
56
+ * Note: actual thresholds are config-driven in PR 8. Defaults are above.
57
+ */
58
+ export interface Decision {
59
+ readonly action: "continue" | "warn" | "escalate" | "stop";
60
+ readonly score: number;
61
+ /** Human-readable one-sentence explanation. Required, never empty. */
62
+ readonly reasoning: string;
63
+ /** Cite-or-abstain: at least 1 evidence unit when action !== "continue" silently. */
64
+ readonly evidence: readonly Evidence[];
65
+ /** When action === "escalate", which actor should be invoked. */
66
+ readonly shouldEscalateTo: EscalationTarget | null;
67
+ }
68
+ export type EscalationTarget = "oracle" | "user";
69
+ /**
70
+ * Atomic evidence unit. Carries provenance so the judge can be audited.
71
+ *
72
+ * `confidence` ∈ [0, 1]: how sure the source is about `value`.
73
+ * `weight` ∈ [0, 1]: how much this evidence influences the score
74
+ * (assigned by the scoring function, not the collector).
75
+ */
76
+ export interface Evidence {
77
+ readonly source: EvidenceSource;
78
+ readonly value: string;
79
+ readonly confidence: number;
80
+ readonly weight: number;
81
+ }
82
+ export type EvidenceSource = "oracle-verified" | "no-progress-detector" | "deviation-detector" | "iteration-budget" | "lesson-recall" | "slot-memory" | "ambient" | "token-predictor";
83
+ /**
84
+ * A deviation from expected behavior. Severity follows the prior
85
+ * moderator-gate (PR 4405) taxonomy for backward compat.
86
+ */
87
+ export interface Deviation {
88
+ readonly severity: "leve" | "media" | "grave";
89
+ readonly category: string;
90
+ readonly detail: string;
91
+ readonly filePath?: string;
92
+ }
93
+ /**
94
+ * A lesson retrieved from agentmemory.lesson_recall. Confidence is the
95
+ * stored confidence in the memory store, not the relevance to this query.
96
+ */
97
+ export interface RelevantLesson {
98
+ readonly id: string;
99
+ readonly title: string;
100
+ readonly advice: "continue" | "stop" | "warn" | "info";
101
+ readonly confidence: number;
102
+ readonly concepts: readonly string[];
103
+ }
104
+ /**
105
+ * Cross-session state held in the magic-context `meta_state` slot.
106
+ *
107
+ * `consecutiveStops` is read by the judge to detect paralysis (3 stops
108
+ * in a row → force continue with warning, prevents infinite conservatism).
109
+ */
110
+ export interface SlotMemory {
111
+ readonly lastDecision?: Decision;
112
+ readonly consecutiveStops: number;
113
+ readonly consecutiveContinues: number;
114
+ readonly lastUpdatedISO: string;
115
+ }
116
+ /**
117
+ * Free-form context the calling site can attach. Used for audit trails
118
+ * and to enable the judge to factor in mode (ultrawork vs simple) or
119
+ * session state. Fields are optional to keep the contract lean.
120
+ */
121
+ export interface AmbientContext {
122
+ readonly sessionID: string;
123
+ readonly directory: string;
124
+ readonly mode: "ultrawork" | "ulw" | "simple" | "ralph-loop";
125
+ readonly agentName: string;
126
+ readonly iteration: number;
127
+ readonly maxIterations: number;
128
+ }
129
+ /**
130
+ * Cross-system memory read result. All three sources read in parallel;
131
+ * any of them may be unavailable (graceful degradation in PR 2).
132
+ *
133
+ * `query` is echoed back so callers can correlate the read with the
134
+ * query that produced it.
135
+ */
136
+ export interface MemoryRead {
137
+ readonly query: string;
138
+ readonly timestampISO: string;
139
+ readonly agentmemory: AgentMemoryRead;
140
+ readonly magicContext: MagicContextRead;
141
+ readonly boulderState: BoulderStateRead;
142
+ readonly degradedSources: readonly MemorySource[];
143
+ }
144
+ export type MemorySource = "agentmemory" | "magicContext" | "boulderState";
145
+ export interface AgentMemoryRead {
146
+ readonly available: boolean;
147
+ readonly lessons: readonly RelevantLesson[];
148
+ readonly errorMessage?: string;
149
+ }
150
+ export interface MagicContextRead {
151
+ readonly available: boolean;
152
+ readonly slots: readonly {
153
+ readonly label: string;
154
+ readonly content: string;
155
+ }[];
156
+ readonly errorMessage?: string;
157
+ }
158
+ export interface BoulderStateRead {
159
+ readonly available: boolean;
160
+ readonly tasks: readonly {
161
+ readonly id: string;
162
+ readonly status: string;
163
+ readonly title: string;
164
+ }[];
165
+ readonly planProgress: number;
166
+ readonly errorMessage?: string;
167
+ }
168
+ /**
169
+ * Token burn rate prediction. Computed from recent turn metrics.
170
+ *
171
+ * `willOverflowAt` is an ISO timestamp predicted from current usage +
172
+ * burn rate + model limit. `null` when not expected to overflow.
173
+ *
174
+ * `recommendation` is action-typed; the caller (PR 7 integration) maps
175
+ * these to actual hook invocations:
176
+ * - "compact-now" → invoke preemptive-compaction-degradation-monitor
177
+ * - "switch-model" → invoke model-fallback chat-message-fallback-handler
178
+ * - "delegate-to-subagent" → invoke delegate-task with a sub-scope
179
+ * - "no-action" → silent
180
+ */
181
+ export interface TokenPrediction {
182
+ readonly currentUsage: number;
183
+ readonly burnRate: number;
184
+ readonly budgetLeft: number;
185
+ readonly willOverflowAt: string | null;
186
+ readonly recommendation: TokenRecommendation;
187
+ readonly confidence: number;
188
+ readonly modelLimit: number;
189
+ readonly windowRemaining: number;
190
+ }
191
+ export type TokenRecommendation = "compact-now" | "switch-model" | "delegate-to-subagent" | "no-action";
192
+ /**
193
+ * Closed-loop learning types. PR 3 of 8.
194
+ *
195
+ * After every repair/action cycle, observeAndLearn() decides whether to
196
+ * persist a lesson or decision record to agentmemory. Future sessions
197
+ * retrieve these via aggregateRead() (PR 2) and factor them into
198
+ * scoring (PR 5).
199
+ */
200
+ /**
201
+ * Configuration for the closed-loop learning system.
202
+ */
203
+ export interface ClosedLoopConfig {
204
+ /** Master switch. false = observe only, never write. */
205
+ readonly enabled: boolean;
206
+ /** Minimum severity to trigger a lesson write. */
207
+ readonly minSeverityToLearn: "leve" | "media" | "grave";
208
+ /** Maximum lessons to save per session (prevents flooding). Default 20. */
209
+ readonly maxLessonsPerSession: number;
210
+ /** Whether to save decision records (lighter than lessons). Default true. */
211
+ readonly saveDecisions: boolean;
212
+ }
213
+ /**
214
+ * A record of a decision that was made, saved to agentmemory for future retrieval.
215
+ */
216
+ export interface MemoryDecision {
217
+ readonly id: string;
218
+ readonly timestampISO: string;
219
+ readonly action: Decision["action"];
220
+ readonly score: number;
221
+ readonly reasoning: string;
222
+ readonly sessionID: string;
223
+ readonly directory: string;
224
+ readonly deviations: readonly Deviation[];
225
+ }
226
+ /**
227
+ * A lesson extracted from an outcome, saved to agentmemory.
228
+ */
229
+ export interface LessonLearned {
230
+ readonly id: string;
231
+ readonly title: string;
232
+ readonly content: string;
233
+ readonly type: "pattern" | "bug" | "architecture" | "workflow";
234
+ readonly concepts: readonly string[];
235
+ readonly confidence: number;
236
+ readonly files: readonly string[];
237
+ readonly sessionID: string;
238
+ }
239
+ /**
240
+ * Interface for writing to agentmemory. DI pattern — same as AgentmemoryBackend for reads.
241
+ * The real implementation calls agentmemory_memory_save / agentmemory_memory_lesson_save via MCP.
242
+ */
243
+ export interface AgentmemoryWriteBackend {
244
+ saveMemory(input: {
245
+ content: string;
246
+ concepts: string[];
247
+ type: string;
248
+ files?: string[];
249
+ }): Promise<{
250
+ id: string;
251
+ }>;
252
+ saveLesson(input: {
253
+ content: string;
254
+ context: string;
255
+ confidence?: number;
256
+ tags?: string[];
257
+ }): Promise<{
258
+ id: string;
259
+ }>;
260
+ }
261
+ /** Backend interfaces for memory-aggregator DI. Re-uses existing BoulderStateBackend from memory-aggregator. */
262
+ export interface OrchestratorAgentmemoryBackend {
263
+ smartSearch(input: {
264
+ query: string;
265
+ limit?: number;
266
+ }): Promise<{
267
+ lessons: Array<{
268
+ title: string;
269
+ content: string;
270
+ type: string;
271
+ confidence: number;
272
+ }>;
273
+ crystals: unknown[];
274
+ }>;
275
+ }
276
+ export interface OrchestratorMagicContextBackend {
277
+ slotList(input: {
278
+ directory?: string;
279
+ labelPrefix?: string;
280
+ }): Promise<Array<{
281
+ label: string;
282
+ content: string;
283
+ }>>;
284
+ }
285
+ export interface MemoryBackends {
286
+ agentmemory: OrchestratorAgentmemoryBackend;
287
+ magicContext: OrchestratorMagicContextBackend;
288
+ boulderState: import('./memory-aggregator').BoulderStateBackend;
289
+ }
290
+ /**
291
+ * Input to observeAndLearn(). Carries everything the learning function needs
292
+ * to decide WHAT to learn and WHETHER to learn it.
293
+ */
294
+ export interface LearnFromOutcomeInput {
295
+ readonly decision: Decision;
296
+ readonly memoryRead: MemoryRead;
297
+ readonly config: ClosedLoopConfig;
298
+ readonly sessionID: string;
299
+ readonly directory: string;
300
+ readonly filesChanged: readonly string[];
301
+ }
302
+ /**
303
+ * Output from observeAndLearn(). Reports what was persisted (if anything).
304
+ */
305
+ export interface LearnFromOutcomeOutput {
306
+ readonly lessonSaved: LessonLearned | null;
307
+ readonly decisionSaved: MemoryDecision | null;
308
+ readonly reason: string;
309
+ }
310
+ /**
311
+ * Token Predictor types. PR 4 of 8.
312
+ *
313
+ * Computes token burn rate from recent turn metrics and recommends
314
+ * preemptive actions (compact, switch model, delegate) before context
315
+ * window exhaustion.
316
+ */
317
+ export interface TokenPredictorConfig {
318
+ /** Burn rate threshold (tokens/sec) above which to recommend compact-now. Default: 500. */
319
+ readonly compactBurnRateThreshold: number;
320
+ /** Context usage ratio (0..1) above which to recommend compact-now. Default: 0.85. */
321
+ readonly compactUsageThreshold: number;
322
+ /** Context usage ratio above which to recommend switch-model. Default: 0.95. */
323
+ readonly switchModelUsageThreshold: number;
324
+ /** Max consecutive high-burn turns before recommending delegate. Default: 5. */
325
+ readonly delegateConsecutiveHighBurn: number;
326
+ /** Number of recent turns to use for burn rate calculation. Default: 10. */
327
+ readonly windowSize: number;
328
+ }
329
+ export interface TokenPredictorInput {
330
+ readonly currentUsage: number;
331
+ readonly modelLimit: number;
332
+ readonly recentTurnTokens: readonly number[];
333
+ readonly timestampISO: string;
334
+ readonly providerID: string;
335
+ readonly modelID: string;
336
+ readonly config: TokenPredictorConfig;
337
+ }
338
+ export interface TokenPredictorOutput extends TokenPrediction {
339
+ readonly input: TokenPredictorInput;
340
+ readonly computedAtISO: string;
341
+ readonly turnsAnalyzed: number;
342
+ }
343
+ /**
344
+ * Scoring Engine types. PR 5 of 8.
345
+ *
346
+ * Configuration for the weighted evidence scoring system that maps
347
+ * a DecisionContext to a Decision. Thresholds are configurable; defaults
348
+ * match the Decision contract in types.ts (lines 50-59).
349
+ */
350
+ export interface ScoringConfig {
351
+ /** Score >= this → continue silently. Default: 0.3. */
352
+ readonly continueThreshold: number;
353
+ /** Score in [-warnThreshold, +warnThreshold] → continue with log. Default: 0.3. */
354
+ readonly warnThreshold: number;
355
+ /** Score <= -warnThreshold && > -escalateThreshold → warn. Default: 0.6. */
356
+ readonly escalateThreshold: number;
357
+ /** Score <= -escalateThreshold && > -stopThreshold → escalate. Default: 0.8. */
358
+ readonly stopThreshold: number;
359
+ /** Number of consecutive stops that triggers paralysis override. Default: 3. */
360
+ readonly paralysisThreshold: number;
361
+ /** Default escalation target when action is escalate. Default: "oracle". */
362
+ readonly defaultEscalationTarget: EscalationTarget;
363
+ }
364
+ /**
365
+ * Weighted evidence contribution for a single signal.
366
+ */
367
+ export interface EvidenceContribution {
368
+ readonly source: EvidenceSource;
369
+ readonly rawScore: number;
370
+ readonly weight: number;
371
+ readonly weightedScore: number;
372
+ readonly description: string;
373
+ }
374
+ /**
375
+ * Full scoring result with per-signal breakdown.
376
+ */
377
+ export interface ScoringResult {
378
+ readonly decision: Decision;
379
+ readonly contributions: readonly EvidenceContribution[];
380
+ readonly rawScore: number;
381
+ readonly paralysisOverride: boolean;
382
+ readonly computedAtISO: string;
383
+ }
384
+ export interface DecisionHandlerConfig {
385
+ /** Master switch: false = always continue (pass-through) */
386
+ readonly enabled: boolean;
387
+ /** Max history entries per session before oldest are trimmed */
388
+ readonly maxHistoryPerSession: number;
389
+ /** How many consecutive stops before forcing continue */
390
+ readonly forceContinueAfterStops: number;
391
+ /** Template for warn messages. Placeholders: {score}, {reasoning}, {evidenceCount} */
392
+ readonly warnMessageTemplate: string;
393
+ /** Template for escalation messages. Placeholders: {target}, {reasoning} */
394
+ readonly escalateMessageTemplate: string;
395
+ /** Template for stop messages. Placeholders: {reasoning}, {evidenceCount} */
396
+ readonly stopMessageTemplate: string;
397
+ /** Default escalation target when Decision.shouldEscalateTo is null */
398
+ readonly defaultEscalationTarget?: string;
399
+ }
400
+ export interface DecisionHandlerInput {
401
+ readonly scoringResult: ScoringResult;
402
+ readonly sessionID: string;
403
+ }
404
+ export interface DecisionHistoryEntry {
405
+ readonly decision: Decision;
406
+ readonly action: "continue" | "warn" | "escalate" | "stop";
407
+ readonly timestampISO: string;
408
+ readonly sessionID: string;
409
+ readonly reasoning: string;
410
+ }
411
+ export interface DecisionHandlerOutput {
412
+ readonly action: "continue" | "warn" | "escalate" | "stop";
413
+ readonly message: string | null;
414
+ readonly historyEntry: DecisionHistoryEntry;
415
+ }
416
+ /**
417
+ * Configuration for the orchestrator. Each sub-config overrides the
418
+ * corresponding module's defaults. The orchestrator merges these onto
419
+ * the per-module defaults at init time.
420
+ */
421
+ export interface OrchestratorConfig {
422
+ /** Master switch. false = skip all MetaGovernor processing. */
423
+ readonly enabled: boolean;
424
+ /** Memory aggregator config. */
425
+ readonly memory: {
426
+ readonly enabled: boolean;
427
+ readonly query: string;
428
+ readonly timeoutMs?: number;
429
+ };
430
+ /** Token predictor config overrides. */
431
+ readonly tokenPredictor: Partial<TokenPredictorConfig>;
432
+ /** Scoring config overrides. */
433
+ readonly scoring: Partial<ScoringConfig>;
434
+ /** Decision handler config overrides. */
435
+ readonly decision: Partial<DecisionHandlerConfig>;
436
+ /** Closed-loop learning config overrides. */
437
+ readonly closedLoop: Partial<ClosedLoopConfig>;
438
+ }
439
+ /**
440
+ * Input to runMetaGovernor(). All signals the orchestrator needs to
441
+ * build a DecisionContext and dispatch a decision.
442
+ */
443
+ export interface MetaGovernorInput {
444
+ readonly sessionID: string;
445
+ readonly toolName: string;
446
+ readonly toolInput?: unknown;
447
+ readonly toolOutput?: unknown;
448
+ readonly agentName?: string;
449
+ readonly providerID?: string;
450
+ readonly modelID?: string;
451
+ readonly iteration: number;
452
+ readonly maxIterations: number;
453
+ readonly oracleVerified: boolean;
454
+ readonly noProgress: boolean;
455
+ readonly filesChanged: number;
456
+ readonly recentTurnTokens: readonly number[];
457
+ readonly deviations: readonly Deviation[];
458
+ readonly consecutiveStops?: number;
459
+ readonly backends: MemoryBackends;
460
+ readonly writeBackend: AgentmemoryWriteBackend;
461
+ readonly config?: Partial<OrchestratorConfig>;
462
+ }
463
+ /**
464
+ * Output from runMetaGovernor(). Contains all intermediate results
465
+ * so the caller can log, inject messages, or escalate.
466
+ */
467
+ export interface MetaGovernorOutput {
468
+ readonly memoryRead: MemoryRead;
469
+ readonly tokenPrediction: TokenPredictorOutput;
470
+ readonly scoringResult: ScoringResult;
471
+ readonly decision: DecisionHandlerOutput;
472
+ readonly lessonSaved: LearnFromOutcomeOutput | null;
473
+ readonly decisionHistory: readonly DecisionHistoryEntry[];
474
+ readonly skipped: boolean;
475
+ readonly skipReason?: string;
476
+ }
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@herjarsa/omo-meta-governor",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "Self-judging agent orchestration layer for OpenCode. Reads memory, scores sessions, dispatches decisions.",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "import": "./dist/index.js"
10
+ }
11
+ },
12
+ "types": "./dist/index.d.ts",
13
+ "main": "./dist/index.js",
14
+ "files": [
15
+ "dist",
16
+ "README.md"
17
+ ],
18
+ "scripts": {
19
+ "build": "bun build.ts",
20
+ "typecheck": "tsc --noEmit",
21
+ "test": "bun test src/*.test.ts"
22
+ },
23
+ "peerDependencies": {
24
+ "@opencode-ai/plugin": ">=1.0.0"
25
+ },
26
+ "publishConfig": {
27
+ "access": "public",
28
+ "registry": "https://registry.npmjs.org/"
29
+ },
30
+ "license": "MIT",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/herjarsa/omo-meta-governor.git"
34
+ },
35
+ "devDependencies": {
36
+ "@opencode-ai/plugin": "^1.17.3"
37
+ }
38
+ }