Skip to content

[codex] PR1: record evidence-backed harness hypotheses - #34

Merged
divo12 merged 26 commits into
freshfrom
stack/v2-01-experiment
Sep 3, 2026
Merged

[codex] PR1: record evidence-backed harness hypotheses#34
divo12 merged 26 commits into
freshfrom
stack/v2-01-experiment

Conversation

@divo12

@divo12 divo12 commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Summary

PR1 makes the prepared experiment the sole authority boundary and records one evidence-backed harness hypothesis before any candidate edit.

  • preparation now publishes a strict canonical ExperimentPolicySnapshot from validated PrepareWorkspaceInput, Harbor configuration, and Git preparation results;
  • policy and hypothesis JSON use bounded descriptor-anchored no-follow regular-file operations with file/directory fsync, directory identity rechecks, atomic publication, identical retry, and conflict rejection;
  • HypothesisService re-runs the existing exact FailurePatternMiningService over at most 50 explicitly supplied diagnosis IDs, rejects incomplete or misassigned evidence, validates the prepared branch/commit/clean state, and authorizes only exact policy editable paths;
  • record_hypothesis, $hypothesis-former, package exports, and generated PROGRAM.md stop at the stable receipt and expose no candidate-editing path.

Cause and user effect

The prior preparation output committed a human-readable YAML file but had no typed internal authority snapshot and no service after exact failure-pattern mining. An agent could describe a hypothesis, but the system could not prove that its evidence partition was exact, its source commit was current, or its targets were actually editable. This change makes those checks deterministic and fail-closed before edits.

Data flow and trust seams

prepare_workspace
  -> HarborBaselineRunner.validate (model, ordered task IDs, config digest)
  -> GitWorktreeGateway.prepare (branch, base commit, initialization commit)
  -> FileExperimentPolicyRepository.publish (Git common control directory)

record_hypothesis MCP
  -> HypothesisService
  -> FileHypothesisRepository (policy + local Git authority)
  -> existing FailurePatternMiningService/FileFailureWorkspace (compact diagnoses only)
  -> FileHypothesisRepository.store (.workspace/hypotheses)

Langfuse remains the trace source of truth. Hypothesis recording performs no provider I/O and stores no trace input, output, span, or provider payload. Git is local authority for branch, commit, and tracked cleanliness; the policy exact editable allowlist is the only path authorization source. ComponentKind is taxonomy only.

Immutable contracts

  • Inputs are strict/frozen, contain 1–50 globally unique diagnosis IDs, at least one unique pattern and target path, bounded text, at most 10 unique risks, and contained relative paths.
  • Policies contain experiment/branch/base/initialization commits, exact editable paths, goal/budgets/stops, ordered unique task IDs, benchmark digest, model/verifier/environment, concurrency 1, retries 0, and a canonical controls digest.
  • Pattern and artifact IDs, target paths, and risks canonicalize deterministically; identical semantic input produces the same HypothesisId and byte-identical receipt.
  • Missing, extra, misassigned, inconclusive, stale, dirty, escaped, frozen, non-regular, oversized, or conflicting state returns a sanitized typed error.
  • Policy files are limited to 256 KiB and hypothesis files to 64 KiB. Valid field maxima fit below those bounds. Evidence is globally count-bounded, so there is no partial page or implicit completion claim.
  • The atomic hard-link publication is the commit point. Identical retries are idempotent; differing existing bytes conflict.

Approved plan mutation

  • Base ancestry: the branch is rebased onto latest fresh commit 8416e24781d5332cb40e5a7ef8cb14254e7c3905, preserving the base-owned 0.7.1 release/runtime pin. The System Book deletion is inherited from fresh and absent from this PR's three-dot content diff.
  • Package changed: PR1 source-revision and target-ownership requirements from the original six-package specification.
  • Evidence: fresh commit 1d91d03402499377a6de97aff5e81323d1dc0747 removed src/ofw/harness.py, src/ofw/runtime.py, HarnessRevision, asset ownership/access, E2B/canary, and scenario/install planes.
  • Replacement: the initialization Git commit replaces harness-revision authority, and exact policy editable_paths replaces component ownership/access authorization. No removed module or downstream interface is recreated.
  • Downstream effect: later packages must consume the policy commit/digests and exact allowlist rather than legacy revision or ownership types. This PR does not invent those downstream interfaces.
  • Compatibility: older v1 preparation state or workspaces without policy.json fail with policy_snapshot_required and must be re-prepared under a new experiment ID; YAML is not loosely migrated. Failure diagnoses now use schema v2 with a verified semantic content digest, so earlier diagnosis files must be recorded again before hypothesis formation.
  • Approval: this mutation was explicitly approved in the task that requested PR1.

Local verification (not CI)

  • uv sync --extra dev --extra plugin
  • uv run ruff check src tests plugins/openflywheel/scripts/mcp_server.py — passed
  • uv run mypy src tests plugins/openflywheel/scripts/mcp_server.py — passed, 50 files
  • uv run pytest --cov=ofw --cov-report=term-missing --cov-fail-under=90 -q — 236 passed, 2 opt-in live tests skipped, 95.35% project coverage
  • new modules: policy 94%, hypothesis 97%, hypothesis repository 96%, safe-file core 93%
  • full Radon — 915 blocks, average A (2.32); changed production functions are CC 5 or lower
  • quick_validate.py — trace-query-planner, outcome-recorder, failure-miner, failure-pattern-miner, failure-curator, workspace-init, and hypothesis-former all passed
  • plugin validation passed
  • git diff --check, excluded-artifact scan, secret scan, base/merge-base verification, and status audit passed

Non-goals

No harness/runtime resurrection, generic live-agent adapter, candidate workspace, editor, runner, gate, controller, publication, release, scenario, install, or live-loop interface is included. No dependency was added, and no Langfuse collection, storage, judging, or benchmark behavior changed.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed

Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Re-trigger cubic

Comment thread src/ofw/preparation/service.py Outdated
Comment thread src/ofw/evolution/hypothesis.py
Comment thread src/ofw/safe_file.py
Comment thread src/ofw/preparation/service.py
Comment thread src/ofw/preparation/contracts.py
Comment thread src/ofw/preparation/policy.py
Comment thread src/ofw/evolution/hypothesis_repository.py Outdated
Comment thread tests/test_safe_file.py
Comment thread tests/test_experiment_policy.py
Comment thread src/ofw/safe_file.py Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 13 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/ofw/preparation/contracts.py
Comment thread src/ofw/preparation/service.py
Comment thread tests/test_safe_file.py Outdated
Comment thread tests/test_failure_workspace.py Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 5 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread src/ofw/preparation/service.py Outdated
Comment thread src/ofw/preparation/contracts.py Outdated
@divo12
divo12 force-pushed the stack/v2-01-experiment branch from dd67464 to d6d19a7 Compare September 3, 2026 07:49

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6 issues found across 15 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/ofw/preparation/policy.py">

<violation number="1" location="src/ofw/preparation/policy.py:57">
P1: Existing policy snapshots become unloadable after this change. Missing `baseline_reused` defaults to `False`, but `validate_controls_digest()` recomputes the digest with the new field and rejects the old digest; preserve legacy canonicalization or version and migrate the policy schema before deployment.</violation>
</file>

<file name="tests/test_hypothesis.py">

<violation number="1" location="tests/test_hypothesis.py:205">
P2: The new fixture supplies prediction fields, but the main successful recording test never verifies that either field reaches the receipt or persisted hypothesis. Add assertions for both fields (and, preferably, the serialized artifact) so this change cannot regress silently.</violation>
</file>

<file name="plugins/openflywheel/skills/workspace-init/SKILL.md">

<violation number="1" location="plugins/openflywheel/skills/workspace-init/SKILL.md:28">
P2: Because `experiment_id` is collected only in step 4, the assistant cannot evaluate this condition while following the required step-3 ordering. Defer the check until after collecting the ID and specify that declining adoption requires a new ID; otherwise `prepare_workspace` rejects the existing job with `launch_failed`.</violation>
</file>

<file name="src/ofw/preparation/service.py">

<violation number="1" location="src/ofw/preparation/service.py:204">
P2: When a reused baseline is already READY, a repeat `prepare` call re-runs `summarize` instead of returning the persisted observation. Re-adopt only while the state is RUNNING so identical retries remain stable.</violation>
</file>

<file name="src/ofw/evolution/hypothesis.py">

<violation number="1" location="src/ofw/evolution/hypothesis.py:416">
P2: `record_hypothesis` accepts repeated IDs within `predicted_task_ids` or `at_risk_task_ids` and persists them as separate entries. Reject duplicates within each list as well as overlap so task predictions remain a set-like, bounded contract.</violation>

<violation number="2" location="src/ofw/evolution/hypothesis.py:512">
P2: Reordering the same predicted or at-risk task IDs changes `HypothesisId`, so semantically identical hypotheses are not idempotent. Canonicalize both task lists after uniqueness validation and reuse those canonical tuples in the content, artifact, and observation.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

quality_target: NormalizedScore
max_iterations: IterationCount
no_improvement_limit: IterationCount
baseline_reused: bool = False

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Existing policy snapshots become unloadable after this change. Missing baseline_reused defaults to False, but validate_controls_digest() recomputes the digest with the new field and rejects the old digest; preserve legacy canonicalization or version and migrate the policy schema before deployment.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/ofw/preparation/policy.py, line 57:

<comment>Existing policy snapshots become unloadable after this change. Missing `baseline_reused` defaults to `False`, but `validate_controls_digest()` recomputes the digest with the new field and rejects the old digest; preserve legacy canonicalization or version and migrate the policy schema before deployment.</comment>

<file context>
@@ -54,6 +54,7 @@ class _ExperimentPolicyContent(StrictModel):
     quality_target: NormalizedScore
     max_iterations: IterationCount
     no_improvement_limit: IterationCount
+    baseline_reused: bool = False
     max_cost_per_task_usd: PositiveMetric | None = None
     max_latency_seconds: PositiveMetric | None = None
</file context>

Comment thread tests/test_hypothesis.py
source_commit=commit,
curation_id=curation_id,
curation_group_id=curation_group_id,
predicted_task_ids=("task-1", "task-2"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The new fixture supplies prediction fields, but the main successful recording test never verifies that either field reaches the receipt or persisted hypothesis. Add assertions for both fields (and, preferably, the serialized artifact) so this change cannot regress silently.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/test_hypothesis.py, line 205:

<comment>The new fixture supplies prediction fields, but the main successful recording test never verifies that either field reaches the receipt or persisted hypothesis. Add assertions for both fields (and, preferably, the serialized artifact) so this change cannot regress silently.</comment>

<file context>
@@ -202,6 +202,8 @@ def _request(
         source_commit=commit,
         curation_id=curation_id,
         curation_group_id=curation_group_id,
+        predicted_task_ids=("task-1", "task-2"),
+        at_risk_task_ids=("task-3",),
         patterns=(
</file context>

2. Optimization goal, primary metric, target, and stopping condition. Keep quality, cost,
and latency constraints separate rather than hiding them in one average.
3. ITSM-bench root, Harbor executable, Harbor configuration, and expected task count.
3. ITSM-bench root, Harbor executable, Harbor configuration, and expected task count. If an

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Because experiment_id is collected only in step 4, the assistant cannot evaluate this condition while following the required step-3 ordering. Defer the check until after collecting the ID and specify that declining adoption requires a new ID; otherwise prepare_workspace rejects the existing job with launch_failed.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At plugins/openflywheel/skills/workspace-init/SKILL.md, line 28:

<comment>Because `experiment_id` is collected only in step 4, the assistant cannot evaluate this condition while following the required step-3 ordering. Defer the check until after collecting the ID and specify that declining adoption requires a new ID; otherwise `prepare_workspace` rejects the existing job with `launch_failed`.</comment>

<file context>
@@ -25,7 +25,10 @@ when the evidence supports one. Collect, in order:
 2. Optimization goal, primary metric, target, and stopping condition. Keep quality, cost,
    and latency constraints separate rather than hiding them in one average.
-3. ITSM-bench root, Harbor executable, Harbor configuration, and expected task count.
+3. ITSM-bench root, Harbor executable, Harbor configuration, and expected task count. If an
+   exact terminal Harbor job already exists at `benchmark_root/jobs/<experiment-id>`, ask whether
+   to adopt it; set `reuse_existing_baseline=true` only after explicit confirmation so policy is
</file context>

terminal = _terminal_observation(request, state)
if terminal is not None:
return terminal
if state.baseline_reused:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When a reused baseline is already READY, a repeat prepare call re-runs summarize instead of returning the persisted observation. Re-adopt only while the state is RUNNING so identical retries remain stable.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/ofw/preparation/service.py, line 204:

<comment>When a reused baseline is already READY, a repeat `prepare` call re-runs `summarize` instead of returning the persisted observation. Re-adopt only while the state is RUNNING so identical retries remain stable.</comment>

<file context>
@@ -176,9 +201,19 @@ def _poll(
                 PreparationErrorCode.REQUEST_CONFLICT,
                 request.experiment_id,
             )
+        if state.baseline_reused:
+            return self._adopt_existing(request, state_directory, state)
         continuation = self._continue_existing(request, state_directory, state)
</file context>
Suggested change
if state.baseline_reused:
if state.baseline_reused and state.phase is PreparationPhase.RUNNING:

source_commit=request.source_commit,
curation_id=request.curation_id,
curation_group_id=request.curation_group_id,
predicted_task_ids=request.predicted_task_ids,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Reordering the same predicted or at-risk task IDs changes HypothesisId, so semantically identical hypotheses are not idempotent. Canonicalize both task lists after uniqueness validation and reuse those canonical tuples in the content, artifact, and observation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/ofw/evolution/hypothesis.py, line 512:

<comment>Reordering the same predicted or at-risk task IDs changes `HypothesisId`, so semantically identical hypotheses are not idempotent. Canonicalize both task lists after uniqueness validation and reuse those canonical tuples in the content, artifact, and observation.</comment>

<file context>
@@ -479,6 +509,8 @@ def _content(
         source_commit=request.source_commit,
         curation_id=request.curation_id,
         curation_group_id=request.curation_group_id,
+        predicted_task_ids=request.predicted_task_ids,
+        at_risk_task_ids=request.at_risk_task_ids,
         patterns=tuple(
</file context>

predicted: tuple[str, ...],
at_risk: tuple[str, ...],
) -> None:
if set(predicted) & set(at_risk):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: record_hypothesis accepts repeated IDs within predicted_task_ids or at_risk_task_ids and persists them as separate entries. Reject duplicates within each list as well as overlap so task predictions remain a set-like, bounded contract.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/ofw/evolution/hypothesis.py, line 416:

<comment>`record_hypothesis` accepts repeated IDs within `predicted_task_ids` or `at_risk_task_ids` and persists them as separate entries. Reject duplicates within each list as well as overlap so task predictions remain a set-like, bounded contract.</comment>

<file context>
@@ -389,6 +409,14 @@ def _curated_artifact_ids(group: FailureGroupArtifact) -> tuple[str, ...]:
+    predicted: tuple[str, ...],
+    at_risk: tuple[str, ...],
+) -> None:
+    if set(predicted) & set(at_risk):
+        raise ValueError("predicted_task_ids and at_risk_task_ids must be disjoint")
+
</file context>
Suggested change
if set(predicted) & set(at_risk):
if (
len(set(predicted)) != len(predicted)
or len(set(at_risk)) != len(at_risk)
or set(predicted) & set(at_risk)
):

@divo12
divo12 merged commit 0d59da1 into fresh Sep 3, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant