# PRD: Vigía — the reflection before the control plane

## Document status

- Status: initial draft
- Type: counterpoint to `prd-objective-guard.md` — same product, different zoom
- Position: this document does not compete with Objective Guard; it advocates for the **reverse build order** and brings pieces missing from that PRD
- Provisional name: Vigía

## 1. Executive summary

Vigía is a deterministic verification companion for code agents: an always-on eye that validates every edit in milliseconds and alerts the model **on the turn it introduces the problem**, not N turns later.

The central thesis:

> The expensive failure mode of an agent is not the error — it is the error **discovered late**. Every turn built on a broken foundation is 100% waste, plus the cost of undoing.

Vigía is, before anything else a quality tool, a **token saver**. That economic frame governs all design decisions: when it runs, what it reports, how much its output occupies, and what it should never do.

The correct analogy is not SonarQube but **rust-analyzer**: an incremental sub-second latency verifier whose client is not an IDE, but a model.

## 2. Where it converges and where it differs from Objective Guard

Two independent designs arrived at the same principles: determinism that only blocks with evidence, LLM never as final authority, cost proportional to impact radius, structured output for agents. That convergence de-risks the concept. The differences are in zoom and order:

| Dimension | Objective Guard | Vigía |
| --- | --- | --- |
| Latency target | fast: p50 < 2 s, p95 < 8 s | **p95 < 200 ms** warm, < 400 ms cold |
| Timing | per patch / per agent invocation | **per edit**, automatic |
| Integration mode | pull (`oguard check`, agent decides to call) | **push** (hook: agent doesn't spend turns asking) |
| Entry ceremony | declared session (`start --kind --goal --acceptance`) | **zero config**: works in any repo from minute one |
| Reporting | complete state per invocation | **delta-only**: only what's new since last check |
| First delivery | MVP of 12 items, 7 subsystems | 4 detectors + hook + protocol, in days |
| Final scope | complete control plane | the reflection; control plane built after, guided by data |

### 2.1 Concrete critiques of Objective Guard

1. **It's an enterprise roadmap, not a v1.** Seven subsystems, plugin SDK, exceptions with human approval, signed policies. Even its Phase 0 demands YAML contract and scenario repo. Risk: months designing in vacuum before first real catch. Its own risk table admits it ("costly adoption").
2. **Abandoned the millisecond tier.** With p95 = 8 s you cannot hook every edit; the check becomes an agent decision (pull), which must remember to invoke it and pay the tool call — exactly the cost we wanted to eliminate. Below its `fast` mode is a gap, and that gap is Vigía.
3. **Has no delta protocol.** "Terse JSON" doesn't prevent re-reporting the same findings each run. The fingerprint + only-what's-new is one of the most token-saving pieces.
4. **Lacks universal drift detectors.** Weakened test, stub in production: zero contract, zero config, work on day 1. Its open question "how do you distinguish a new regression test from a preexisting one?" is partly answered by the `weak-test` detector.
5. **Ceremony as precondition to value.** If the session contract is poorly formalized, the system validates garbage with confidence — and the user concludes "it doesn't work" due to the contract, not the engine.

### 2.2 Proposed amendments to Objective Guard

1. Add a **push-based reflection tier < 200 ms** below the `fast` mode (this document specifies it).
2. Elevate **delta-only** to protocol output requirement.
3. Incorporate the **`weak-test` and `stub-prod`** detectors into the MVP.
4. Make the **zero-ceremony mode the default**: sessions/contracts are opt-in that activate additional checks, never precondition for universal verifications.

### 2.3 What Vigía adopts from Objective Guard

1. **Evidence states** (`proven / violated / unknown / not_applicable`) as output vocabulary — and its guiding principle: don't convert absence of knowledge into assertion of correctness.
2. **Proof obligations by task type** — "a bugfix without a regression test doesn't close" is deterministic, cheap, and extremely high-value (enters with the contracts layer).
3. **Facts ≠ inferences ≠ decisions** — an inference can produce `warn`; only reproducible evidence blocks.
4. **Benchmark with planted deviations** as success measure, rather than usage impressions.

## 3. The value model: token economy

Two drains the tool attacks:

1. **Late discovery.** An error introduced in turn N and discovered in turn N+k wastes k turns of generation, context, and undo cost. Vigía's value = (turn where it alerts) vs (turn where the model would discover it alone).
2. **Expensive self-verification.** Today the model verifies on its own: runs the suite, reads 400 lines of output, reasons over it. A 3-line diagnosis pushed at the exact moment replaces that verification.

From that fall four principles no current linter satisfies:

1. **Push, not pull** — the eye alerts; the model doesn't spend turns asking "is this okay?".
2. **Delta-only** — only what *changed* since last check is reported. Never re-send the 50 preexisting warnings: for a human it's noise, for a model it's money.
3. **Budgeted output** — stable codes, one line per finding, ordered by severity, explicitly truncated. Output is designed in tokens, not screens.
4. **Latency ladder** — verification cost scales with timing, not repo size:

| Tier | When | Latency | What runs |
| --- | --- | --- | --- |
| 0 | each edit | ms | syntax, universal drift, (future: scope contract, incremental types) |
| 1 | each save / turn | ~1 s | affected tests, coverage of changed lines, duplication |
| 2 | task checkpoint | min | full build, e2e selection, gate prediction |

Objective Guard's `fast` mode is tier 1; its `final` mode is tier 2. Vigía v1 is tier 0 — the layer no existing tool occupies.

## 4. "Didn't drift from objective": the honest boundary

A deterministic verifier cannot read intent. It can verify two things:

1. **Universal drift** (no contract): the real ways agents drift are mostly syntactically detectable — weakening tests to "make them pass", stubbing and moving on, adding dependencies, copying instead of reusing. This works in any repo, any task, without declaring anything.
2. **Formalizing intent** (with contract, layer 2): declared scope, frozen API, boundaries, TDD anchors. "Drifting" becomes observable — but only as well as the contract is formalized.

Vigía v1 delivers (1) complete and leaves the hook for (2): each detector receives `(diff, optional_config)`. Contract is just config that activates additional detectors.

Declared limits: if the contract formalizes the objective poorly, the eye validates garbage with confidence; and "the code is correct" has only one real oracle — the tests. The eye frames and accelerates; it does not replace the oracle.

## 5. v1: the guardian without ceremony

### 5.1 Scope — four detectors, zero config

All operate on `(diff of edited file vs HEAD, current content)`. Initial target stack: JS/TS + Java.

| Code | Detector | What it watches |
| --- | --- | --- |
| `E weak-test` | Weakened test | `.skip` / `xit` / `xdescribe` / `@Disabled` / `@Ignore` newly added; drop in assertion count in a modified test (tree-sitter query before/after); massive snapshot regeneration |
| `E syntax` | Broken syntax | Edited file does not parse |
| `W stub-prod` | Stub in production | TODO / FIXME / placeholder / mock introduced by this diff in non-test code |
| `W new-dep` | New dependency | Diff touches `dependencies` in `package.json` / `dependencies` block in `build.gradle` |

### 5.2 Architecture and flow

Single binary/executable CLI, no daemon:

```text
PostToolUse hook (Edit|Write)         [first consumer: Claude Code]
        │  JSON payload via stdin
        ▼
vigia hook ──► git diff HEAD -- <file>
        ▼
detectors in parallel (50 ms timeout each)
        ▼
merge ──► diff against previous fingerprints (.vigia/state.json, gitignored)
        ▼
emit ONLY what's new ──► update state
```

One engineering fact that eliminates daemon need in v1: the hook delivers *one* edited file. Parsing a file with tree-sitter costs ~2-5 ms; `git diff`, ~30 ms. The daemon with resident indexes (salsa/rust-analyzer style) is the evolution for when tier 1 demands it — not the starting point.

**Budget (acceptance criterion, not aspiration): p95 < 200 ms warm, < 400 ms cold.**

### 5.3 Output protocol

- **Fingerprint** = `(rule, file, normalized window, message hash)`. Only new findings are emitted; resolved ones collapse to `✓ N resolved` (one line).
- **Budget**: max 5 lines, order E > W, overflow → `…and N more (vigia list)`.
- **Format**: `SEV rule file:line — message ≤ 100 chars`.
- `vigia list` = escape hatch pull: complete state when the model asks for it.
- Future compatibility with Objective Guard: `--format json` with exit codes `pass / warn / block / error`.

Example of what the model sees:

```text
[vigia] 2 new findings (edit: DetailView.jsx)
E weak-test  tests/detail.spec.ts:88 — assertion removed: expect(nav).toHaveBeenCalled
W stub-prod  src/DetailView.jsx:1502 — "TODO: handle missing id" introduced
```

### 5.4 Errors and shutdown

Golden rule: **the eye never blocks the loop.**

- Internal error or timeout → exit 0 silent, log to `.vigia/log`.
- More than 10 findings in a file → collapses to one-line summary (anti-spam).
- Kill switch: `VIGIA_OFF=1`.

An eye that annoys is worse than no eye. The project death metric is the user turning it off.

### 5.5 Explicitly out of v1

- Duplication (needs repo-wide index → v1.1).
- Contracts / sessions (layer 2; hook remains in detector signature).
- Resident daemon (only if v1 latency budget breaks at tier 1).
- Impact analyzer, check planner, evidence store (built when v1 data justifies it — see roadmap).

## 6. Language decision: deferred, with measurable criterion

The spec sets the latency budget; language is chosen with a ~30-minute spike at plan start, measuring cold-start + parse of a 3,000-line file on target machine.

- Candidates: Rust (cold-start ~5 ms, native tree-sitter), Go (~10 ms), Bun/TS (~30-80 ms, faster iteration).
- Under Objective Guard's budget (2-8 s), TypeScript is overkill — their stack recommendation is correct *for their layer*.
- Under 200 ms per edit, cold-start matters: it is measured, not debated.
- If a daemon tier 1 appears later, the choice is revisited with v1 data.

## 7. Convergent roadmap

Vigía v1 is the Phase 0 that Objective Guard lacks. Convergence is explicit:

```text
Objective Guard (north star)
├── Phase 0        ← Vigía v1 (push reflection < 200 ms, zero ceremony, delta-only)   ← START HERE
├── Contracts layer ← Vigía "layer 2" = OG Session + Contract Compiler
├── Tiers 1/2       ← Impact Analyzer + Check Planner + test selection
└── Evidence       ← Evidence Store + states proven/unknown (when sessions exist)
```

Each component of the big PRD is built **when v1 data justifies it**, not speculatively. The two documents should merge into one when v1 produces its first real metrics.

## 8. Testing and success criteria

- **Golden corpus**: `(before, after) → expected findings` pairs per detector, extracted from real patterns (`it.skip` in CI, `@Disabled`, assertions deleted).
- **Seeded benchmark** (adopted from Objective Guard §17): tasks with planted deviations; measures early detection and false-positive rate.
- **Latency test** with budgeted assertion in CI.
- **Success after one week of real use** (Claude Code sessions on work repos):
  1. p95 < 200 ms;
  2. ≥ 1 catch that saved a wrong path;
  3. `VIGIA_OFF` never set;
  4. false-positive rate that doesn't train the model (or human) to ignore output.

## 9. Risks specific to this approach

| Risk | Mitigation |
| --- | --- |
| Universal detectors turn out too obvious and model already avoided them | Seeded benchmark measures it in week 1; if so, contracts layer moves up in priority and v1 cost days, not months |
| False positives that train users to ignore output | 5-line budget, delta-only, separated severities; measure rate and prune noisy detectors |
| Delta state corrupted or out of sync | State is disposable: deleting `.vigia/state.json` only causes re-report, never false silence |
| Hook fatigue (perceived latency at each edit) | p95 as acceptance criterion; fail-silent on timeout |
| Reflection doesn't cover deep semantic drift | Declared limit (§4): that's the contracts layer + the tests oracle, not tier 0 |

## 10. Main open question

When do the two PRDs merge? Proposal: after the first week of real v1 metrics — with data, the "reflection first vs control plane first" discussion stops being opinion.
