# Objective Guard

Continuous verification runtime for coding agents. It detects deterministic errors, objective drift and evidence-free scope expansion **before** the agent stacks more work on top of a wrong decision.

The most avoidable cost of an agent mistake is not making it — it is discovering it several steps later, when new decisions already depend on it. Objective Guard closes that gap by giving the agent programmatic feedback at the first moment reproducible evidence of a violation exists.

> **Status:** functional, portable alpha for JavaScript Git repositories. The universal detectors are deliberately narrow: broken syntax, weakened tests, stubs in production code, and new dependencies. A passing guard means only that the checks in scope found no violation — it is not proof that the objective was achieved.

**Language:** English is the canonical documentation language. Every document has a Spanish counterpart beside it as `<name>.es.md` ([README.es.md](README.es.md)).

## Table of contents

- [Why](#why)
- [Quickstart](#quickstart)
- [Core concepts](#core-concepts)
- [The round lifecycle](#the-round-lifecycle)
- [Verification and closing a round](#verification-and-closing-a-round)
- [Agent harness integration](#agent-harness-integration)
- [Governance: KPI policy and scope observability](#governance-kpi-policy-and-scope-observability)
- [Evidence and telemetry](#evidence-and-telemetry)
- [Deliberate exceptions](#deliberate-exceptions)
- [Documentation](#documentation)
- [Development](#development)
- [Epistemic limits](#epistemic-limits)

## Why

An agent works well while the relevant context stays small and observable. As a repository grows, the agent can mislocate the problem, build on a wrong hypothesis, progressively widen the change, weaken tests to make a solution pass, or declare success without sufficient evidence.

Objective Guard does not try to judge intent. It makes three things explicit and checkable:

| Question | Mechanism |
| --- | --- |
| Is this edit deterministically broken? | Universal detectors, no configuration required |
| Does this work still serve the declared objective? | Typed contract with objective, round, key results and scope |
| Is the closing claim backed by reproducible evidence? | Declared checks, criteria and obligations at round close |

## Quickstart

The package is published as `@fyso/objective-guard`. From a clone:

```bash
npm install            # no runtime dependencies; Node >= 20
npm test               # verify the alpha in your environment
npm install -g @fyso/objective-guard  # exposes the `oguard` binary globally
```

Inside this repository, use `node src/cli.mjs <command>`. In any other Git repository, use the linked `oguard` binary (or `node /path/to/objective-guard/src/cli.mjs`).

A minimal first round:

```bash
oguard init
oguard intent --objective-id O-1 --objective "Protect checkout" --task-type bugfix --scope "src/**,test/**"
oguard intent-scan
# Review or revise the local draft, then freeze only the validated hypothesis:
oguard start --from-intent --round-id R-1 --round "Add regression" --kr-id KR-1 --kr "Checkout has a regression test"
git add oguard.contract.json && git commit -m "open round R-1"
oguard task-flow          # required before the first material edit
oguard check --file src/example.mjs
oguard align
oguard round-end
```

Step-by-step, with the meaning of each output: **[docs/getting-started.md](docs/getting-started.md)**.

## Core concepts

**Contract.** `oguard.contract.json` is a versioned task artifact: objective, round, key results, task type, work context, scope globs, non-goals, declared checks, criteria and obligations. Commit it before anchoring a plan.

**Plan.** `oguard plan-template` prints a minimal plan derived from the typed contract. Save, review and commit it as `oguard.plan.json` before `anchor-plan`:

```json
{
  "schema_version": 1,
  "objective": { "id": "O-1", "statement": "Protect checkout" },
  "key_results": [{ "id": "KR-1", "statement": "Checkout has a regression test" }]
}
```

**Local state.** `.oguard/` is local, append-only operational state and should stay git-ignored. `oguard.contract.json`, `oguard.plan.json` and `oguard.kpis.json` are the versioned artifacts.

**Intent.** `oguard intent` is a local, revisable phase *before* the contract. It stores a tentative objective, task type and candidate scope in `.oguard/intent.json`; `intent-scan` resolves only Git-observable files matching those globs and lists candidate tests. It interprets no semantics and grants no preflight. The agent may correct the intent and re-scan; `start --from-intent` copies the chosen version into the frozen contract and records the draft hash and scan count. Once a typed contract exists, `intent` is rejected — a change of direction must be an explicit successor round.

**Checks, criteria, obligations.** Declared when the round opens, never by hand-editing the contract afterwards. A `feature` round can bind one reproducible criterion to both behavior and compatibility:

```bash
oguard start ... --task-type feature \
  --check test=npm,test --check-timeout-ms 900000 \
  --criterion-id C-1 --criterion "Feature behavior and compatibility pass" --criterion-checks test \
  --obligation behavior=C-1 --obligation compatibility=C-1
```

The declared timeout accepts up to one hour. The planner discovers `test`, `lint` and `typecheck` from `package.json`, but only ever runs commands explicitly declared in `oguard.contract.json#checks`. Discovered-but-unconfigured checks remain a visible recommendation: on their own they neither degrade `verification` nor block a round whose declared checks and criteria did pass.

For `feature` rounds, declare `--criterion-id`, `--criterion` and `--criterion-checks` when opening the round. Without reproducible checks the guard keeps behavior and compatibility as `unknown`, and manually executed test results are **not** credited retroactively to a frozen contract. The remedy is a successor round with the checks declared — not mutating the evidence of the round already open.

**Work context.** For merge, rebase, migration or generated changes, declare it before editing: `oguard next-round ... --work-context merge`. If the exogenous change leaves less than 50% of the surface inside scope, the alignment verdict is `not-evaluable`: it preserves failing checks but avoids counting integration noise as agent drift.

## The round lifecycle

```mermaid
flowchart LR
    I[intent + intent-scan<br/>local draft] --> S[start --from-intent<br/>frozen contract]
    S --> T[task-flow<br/>preflight before first edit]
    T --> E[edits<br/>check --file]
    E --> A[align<br/>drift verdict]
    A --> R[round-end<br/>evidence + baseline]
    R --> N[next-round<br/>archived transition]
    N --> T
```

`oguard next-round` requires a clean tree and an active baseline matching the contract. Before installing the new round it archives both artifacts under `.oguard/history/` and records an append-only transition, so it cannot be used to accommodate a diff that already exists.

## Verification and closing a round

`round-end` always applies the universal detectors to modified files, validates the diff, and preserves evidence.

| Mode | Behavior |
| --- | --- |
| `oguard round-end --mode fast` | Preserves uncertainty as `unknown` |
| `oguard round-end --mode final` | Blocks if a required criterion is left `unknown` |

Checks can be narrowed with `modes: ["fast"]` or `modes: ["final"]`; without that field they apply to both. An omitted candidate never widens a `pass`: it is recorded as `skipped` with `verification: "incomplete"`.

Before the first material edit, the agent must run `oguard task-flow --json`. A `ready` result summarizes objective, round, task type, work context, scope, non-goals and governance status. `action-required` exits non-zero and requires resolving contract, baseline or governance before mutating anything.

## Agent harness integration

| Harness | Guide |
| --- | --- |
| Claude Code | [docs/guides/claude-code.md](docs/guides/claude-code.md) |
| Codex | [docs/guides/codex.md](docs/guides/codex.md) |
| Adding another harness | [docs/guides/README.md](docs/guides/README.md) |

In Claude and Codex, the global prompt injects `task-flow` at the start of every request, and the global `PreToolUse` hooks for `Edit|Write` additionally run the local gate. A `ready` authorization is stored against the contract hash and expires after one hour; without it, the structured tool receives `permissionDecision: deny`.

Two deliberate limits: the gate leaves repositories without a contract unblocked, and it does not attempt to classify arbitrary writes performed inside shell commands.

## Governance: KPI policy and scope observability

To make KPIs mandatory for the agent, version `oguard.kpis.json` in the repository root. `oguard task-flow` loads and validates that policy automatically. A violation with severity `block` does not grant preflight, so the edit gate denies the mutation. The policy hash is bound to the authorization: modifying it forces a new `task-flow` run. `warn` preserves visibility without stopping work, and `unknown` only means there is not yet enough sample to conclude.

Scope is also audited for observability: a glob that resolves only to ignored files emits `scope-unobservable`. The accounting files (`oguard.contract.json`, `oguard.plan.json`, `oguard.kpis.json` and `.oguard/**`) never credit a key result by themselves. For discovery work, `task.assumption_ledger_required` demands a list of assumptions with path, textual evidence and, optionally, an already-declared check.

## Evidence and telemetry

```bash
oguard evidence export --output evidence/oguard.json
oguard evidence dataset calibration --output evidence/runs/calibration.json
oguard evidence dataset evaluation --output evidence/runs/evaluation.json
```

`oguard evidence dataset` produces passive, separated datasets for `calibration` and `evaluation`. It records versioned features and formulas; it activates no alerts, thresholds or policy changes.

Every pre-edit decision also reaches the global inventory in sanitized form: harness, repository hash, `allow`/`deny`, reason code and duration. It keeps no paths, prompt or file contents.

| Command | What it reports |
| --- | --- |
| `oguard inventory report --json` | Totals, including `pre_edit` |
| `oguard inventory kpis --json [--policy path.json]` | Preflight coverage, denial rate, p95 latency, finding resolution; validates `{ min?, max?, min_samples?, severity?: "warn"\|"block" }` |
| `oguard inventory dashboard --days 7 --json` | Adoption, flow discipline, interventions and resolution, cut by project and harness |

The dashboard declares `causal_effect: not-evaluable` explicitly: usage telemetry alone does not prove that the guard prevented a deviation or saved time or tokens.

To consolidate telemetry from any SSH source without copying contracts, plans or repository contents, create `~/.oguard/inventory/sources.json` locally (outside Git):

```json
{
  "schema_version": 1,
  "sources": [
    { "id": "mac-mini", "host": "user@host.example.ts.net", "path": ".oguard/inventory/events.jsonl" }
  ]
}
```

```bash
npm run inventory:pull
```

The collector reads exclusively paths under `.oguard/` over SSH, allows only the sanitized telemetry schema, and appends only events whose fingerprint was not already imported. It keeps per-`id` state in `~/.oguard/inventory/remote-imports/`, so repeating it is idempotent. `--dry-run` inspects the delta without writing, `--source <id>` selects one source, and `--config <path>` uses a different local configuration.

## Deliberate exceptions

| Command | When | Guarantee |
| --- | --- | --- |
| `oguard accept-baseline --reason <text> --actor <text>` | A closed round whose non-promotable result was accepted — for example performance debt during a pilot | Requires a clean tree; preserves the previous baseline and the failed/unknown status in an append-only event. It does **not** turn that result into `pass` |
| `oguard adopt-baseline` | One-time migration for a historical typed contract with no baseline | Requires a clean tree, does not rewrite the contract, records `baseline-adopted`. Afterwards the normal transition is `next-round` again |

## Documentation

| Entry point | Content |
| --- | --- |
| [docs/README.md](docs/README.md) | Canonical documentation index and precedence rule |
| [docs/getting-started.md](docs/getting-started.md) | From an unmodified repository to a first closed round |
| [docs/guides/README.md](docs/guides/README.md) | Per-harness integration guides (Claude Code, Codex, …) |
| [docs/product/01-vision-and-thesis.md](docs/product/01-vision-and-thesis.md) | Problem, current thesis, principles, epistemic limits |
| [docs/product/02-product-contract.md](docs/product/02-product-contract.md) | Objective, users, scope, non-goals, requirements |
| [docs/product/03-signal-and-decision-model.md](docs/product/03-signal-and-decision-model.md) | Signal classes, change surface, evidence, decision policy |
| [docs/product/04-technical-architecture.md](docs/product/04-technical-architecture.md) | Components, flow by levels, persistence, failure modes |
| [docs/product/05-scientific-validation.md](docs/product/05-scientific-validation.md) | Hypotheses, evidence ladder, metrics, falsification criteria |
| [docs/product/06-roadmap-and-criteria.md](docs/product/06-roadmap-and-criteria.md) | Build order, MVP, acceptance criteria, deferred decisions |
| [docs/product/07-phase-1-calibration-protocol.md](docs/product/07-phase-1-calibration-protocol.md) | Phase 1 calibration protocol |
| [docs/functionality/INDEX.md](docs/functionality/INDEX.md) | Actors, capabilities, rules and the functional test matrix |
| [docs/episodes/README.md](docs/episodes/README.md) | Append-only record of real rounds that changed the product |
| [docs/initial-research/README.md](docs/initial-research/README.md) | Historical hypotheses, v0 PRDs and adversarial reviews |

## Development

```bash
npm test                          # node --test test/*.test.mjs
npm run validate:docs             # documentation guard
npm run round:end                 # dogfood the guard on the current round
npm run benchmark                 # calibration benchmark
npm run calibration:gate          # F1 calibration gate
npm run synthetic                 # synthetic corpus
npm run synthetic:trajectories    # Phase 4 task-trajectory corpus
```

`npm run validate:docs` verifies documentation topology, capability contracts, the functional matrix, language pairing and relative links. It does not prove the thesis is true; it proves the documentation deliverable satisfies an explicit, reproducible contract.

`npm run synthetic:trajectories` reproduces the Phase 4 task corpus and writes `synthetic/results/task-trajectories-v1.json`. It is coverage built from known labels — not an efficacy benchmark and not a classifier.

Repository layout:

```
src/          CLI, detectors, contract, alignment, gate, inventory
test/         node:test suites (the executable specification)
scripts/      docs guard, harness prompt scripts, telemetry collector
docs/         canonical English documentation + Spanish .es.md counterparts
synthetic/    synthetic corpora and runners
benchmarks/   calibration and gate benchmarks
evidence/     exported evidence and datasets
```

## Epistemic limits

- A `pass` means the checks in scope found no violation. It is not evidence that the objective was achieved, nor that the guard is useful.
- `unknown` means insufficient evidence, not failure. The guard preserves that distinction on purpose.
- The "small good, medium bad, large structured better" curve remains a confounded hypothesis. Objective Guard does not presume it is universal; it gathers evidence to measure when it appears.
- Usage telemetry proves adoption, not benefit.

Real rounds where the guard missed a signal, fired late, or where the agent substituted its own objective are recorded in [docs/episodes/README.md](docs/episodes/README.md) instead of being quietly fixed.
