Continuous Evaluation and Drift Detection

July 2, 2026 · View on GitHub

HOTL can collect the same governed scenarios through Codex, Claude Code, and a generic fallback; preserve validated results in append-only local history; and surface drift, regressions, and profile candidates for human review.

This feature is opt-in. Normal installation performs no provider call, creates no schedule, and changes no model, effort, driver, permission, sandbox, policy, plugin, or routing configuration.

Safety boundary

  • Campaign validation and planning are read-only.
  • Collection requires --approve-live and enforces call and elapsed-time budgets. A provider cost budget is accepted only when the host adapter can enforce it before the call.
  • Raw evidence stays local with owner-only permissions by default. Capture and redaction are explicit campaign policy.
  • History is append-only and hash-verified under stable run-ID-derived keys; a run ID cannot be replaced.
  • Drift is classified before trends are interpreted.
  • Profile proposals are advisory, require human review, and contain no configuration-changing command.
  • Native scheduling templates are inert until a human creates and enables a task in Codex or Claude Desktop.

1. Define and preview a campaign

A hotl.evaluation-campaign/v1 JSON file binds profiles and scenario revisions to hashed prompts, response schemas, assertions, budgets, and capture policy. Artifact paths and output_root are relative to the campaign file and must stay inside its physical directory; symlink escapes are rejected. Requested effort is host-specific: Codex accepts minimal, low, medium, high, or xhigh; Claude Code accepts low, medium, high, xhigh, or max; and null keeps the host default.

{
  "schema": "hotl.evaluation-campaign/v1",
  "campaign_id": "weekly-governance-baseline",
  "protocol_revision": "2026-07-01",
  "created_at": "2026-07-01T16:00:00Z",
  "output_root": ".hotl/evaluations/weekly-governance-baseline",
  "repetitions": 1,
  "profiles": [
    {
      "profile_id": "codex-low",
      "host": "codex",
      "requested_model": "reviewed-model-id",
      "requested_effort": "low",
      "adapter_version": "reviewed-local-version"
    }
  ],
  "scenarios": [
    {
      "scenario_id": "successful-completion",
      "scenario_revision": "2026-06-29",
      "prompt_path": "scenarios/successful-completion.txt",
      "prompt_sha256": "<64-lowercase-hex>",
      "response_schema_path": "scenarios/response-schema.json",
      "response_schema_sha256": "<64-lowercase-hex>",
      "assertion_path": "scenarios/successful-completion-assertion.json",
      "assertion_sha256": "<64-lowercase-hex>"
    }
  ],
  "budgets": {
    "max_calls": 1,
    "max_elapsed_minutes": 10,
    "max_cost_usd": null
  },
  "capture": {
    "raw_output": "local",
    "prompts": "hash_only",
    "redact": true
  }
}

Replace every hash with the digest of its local artifact. Validate and preview the complete call matrix without invoking a host:

bash scripts/hotl-evaluation-campaign.sh validate campaign.json
bash scripts/hotl-evaluation-campaign.sh plan campaign.json

The plan declares live_execution: false, schedule_changes_performed: false, and configuration_changes_performed: false.

2. Run an explicitly approved collection

Review the profiles, scenarios, calls, time/cost limits, and capture policy. Then make the live boundary explicit:

bash scripts/hotl-evaluation-collect.sh run campaign.json \
  --approve-live \
  --call-timeout-seconds 300

The collector resolves and records the exact host binary and version, uses a read-only Codex sandbox or tool-disabled Claude Code print session, validates structured output, and writes only beneath the campaign output root. Override host binaries deliberately with HOTL_EVAL_CODEX_BIN, HOTL_EVAL_CLAUDE_BIN, or HOTL_EVAL_GENERIC_BIN.

For recurring runs of the same immutable campaign, add a unique run label:

bash scripts/hotl-evaluation-collect.sh run campaign.json \
  --approve-live \
  --run-label scheduled-20260701t160000z

Each run contains campaign-run.json, validated results/, and redacted evidence/. Interrupted, timed-out, malformed, over-budget, and telemetry-unknown runs remain incomplete; the collector does not silently retry or increase a budget.

Prompt capture follows the campaign exactly: none stores no prompt evidence, hash_only stores only the verified SHA-256 in call metadata, and local writes an owner-only prompt copy beside the call evidence, applying the campaign's redaction setting.

max_cost_usd must be null for profiles whose adapters cannot enforce a pre-call provider limit. The current Claude Code adapter passes its remaining limit to the host. Codex and generic profiles reject a non-null cost budget instead of treating post-call or missing cost as hard enforcement.

3. Append history and inspect drift

Append every validated result from a completed or interrupted campaign run:

bash scripts/hotl-evaluation-history.sh append-run \
  .hotl/evaluation-history \
  campaign.json \
  .hotl/evaluations/weekly-governance-baseline/campaign-run.json

Re-running append-run is recovery-safe: already stored run IDs are skipped, while newly available validated results are appended. This is the normal path because it derives workload hashes from the validated campaign. Direct entry validation and append are trusted, advanced ingestion operations: the caller is responsible for the supplied prompt/schema/assertion attestations.

bash scripts/hotl-evaluation-history.sh validate-entry entry.json
bash scripts/hotl-evaluation-history.sh append .hotl/evaluation-history entry.json

Generate a deterministic report:

bash scripts/hotl-evaluation-history.sh report .hotl/evaluation-history \
  > .hotl/evaluation-history-report.json

The report separates compatible, workload, prompt/schema, host, adapter/model, toolchain, and telemetry drift from incomplete campaigns and quality regressions. classification retains the primary class for compatibility and classifications lists every simultaneous drift axis. Compatible single-campaign profile relationships retain the Phase 7 safety, telemetry-completeness, and Pareto semantics. A Phase 8 workload projection lets Codex, Claude Code, and fallback profiles share a cohort while host/version/model remain visible observations; conflicting prompt/schema/assertion hashes block the comparison. A logical profile whose observed host, version, model, effort, or adapter changes inside the campaign is also ineligible until evidence is recollected under stable profile identities.

4. Render a profile proposal

Profile proposals cite the history report hash, campaign runs, result paths, safety evidence, regressions, drift, measured trade-offs, confidence limits, and rollback guidance:

bash scripts/hotl-evaluation-proposal.sh \
  --format text \
  --current-profile codex-current \
  .hotl/evaluation-history-report.json

review_candidate and review_candidate_with_warnings mean only “inspect this evidence.” An unsafe or missing candidate becomes collect_more_evidence. Every output says human_review_required: true, automatic_selection_performed: false, and configuration_changes_performed: false.

5. Optional native scheduling

Preview one scheduled run without registering a task:

bash scripts/hotl-evaluation-schedule.sh preflight campaign.json \
  --host codex \
  --run-label scheduled-20260701t160000z

Preflight always returns ready_to_enable: false with human-approval and credential-review blockers. It creates no output and performs no provider or schedule call.

After manual testing and review, use the inert templates under automations/continuous-evaluation/:

  • Codex: create a standalone project automation in the Codex app.
  • Claude Code: create a Claude Desktop Local scheduled task so local binaries and append-only evidence remain available.

Creating and enabling the native task is the human approval for the pinned campaign, cadence, and limits represented by that schedule. It grants no authority to run a different campaign, increase budgets, or change profiles.

Telemetry normalization

Codex cached input is normalized as a subset of total input: normalized input equals total input minus cached input. Claude cache-read tokens are retained as a disjoint counter. Generic observed telemetry must declare its counter semantics; otherwise it remains unavailable. Every history entry records hotl.tokens/v1 provenance. Missing cost or token data stays null, never zero or an estimate.

Retention and redaction

Keep .hotl evidence local and ignored unless an owner adopts a reviewed storage policy. Prefer prompts: hash_only, raw_output: none for sensitive work, and redact: true. Local raw capture is useful for debugging but may contain repository content or provider output; review it before sharing and remove it only under an explicit retention decision. Never rewrite an accepted history entry or the bytes referenced by its stored hash. To correct evidence, append a new run with clear provenance.

Contracts