Skip to content

feat: extract the write-audit cycle and verifier contracts into AgentCore - #28

Merged
zhanghanduo merged 1 commit into
mainfrom
feat/extract-cycle-verifier
Sep 4, 2026
Merged

zhanghanduo merged 1 commit into
mainfrom
feat/extract-cycle-verifier

Conversation

@zhanghanduo

Copy link
Copy Markdown
Collaborator

Moves components/cycle and components/verifier out of ApodexHarness. Releases as 0.5.0 (new capability ⇒ MINOR).

Why this is a lift, not a redesign

Both packages were already product-neutral. Their entire set of outside imports was:

  • miroharness.core.tool → a sys.modules alias for agent_core.tool
  • miroharness.components.observers.conclude_phase_observer → moves with them

No signature changed. SessionBackedWriter/SessionBackedAuditor keep bus: Any and call create_session/submit_task_to_session/collect structurally, so they work against agent_core.components.agent_bus without a Protocol the product doesn't otherwise need.

The boundary

Core owns the loop mechanics: termination rules, wall-clock budget with between-round estimation, the missing-artifact output check, writer-exception and missing-output capture as synthetic audits (a crash becomes a finding the next round can read, rather than an aborted cycle), per-round persistence, and failure-isolated observer dispatch.

The product owns everything that decides whether an artifact is good. Two deliberate non-decisions, preserved from the original:

  • AuditFinding.category is a free-form str, not an enum. The useful vocabulary differs between a paper, a patch, a dataset and a plan; freezing one here would force every product to translate into someone else's taxonomy.
  • AuditReport.verdict is likewise free-form; the cycle compares against a caller-supplied terminal_verdicts set, defaulting to {"success", "abandon"} only so the common case needs no config.

GroundTruth's oracle fields (_reference, _formal_spec, _test_cases) are stripped by core when is_runtime=True — core enforces the isolation, the product decides what the reference answer is. See docs/cycle-verifier-boundary.md.

Four changes beyond the move

AgentCore's gates are stricter than the product's (pyright strict over agent_core, ruff over tests/ too), so the move surfaced things the product's config never checked:

  1. default_factory=dict/=list → the parametrised spelling already used in protocols.py/loop_types.py/llm.py. Verdict.sub_verdicts can't use it — the name doesn't exist while its own class body runs — so it gets a named factory whose return annotation stays lazy under from __future__ import annotations.
  2. json.loads results narrowed explicitly instead of being left dict[Unknown, Unknown] after isinstance.
  3. RoundIntervention: (str, Enum) → StrEnum, matching the three existing StrEnums here. Safe: it is only ever compared (== RoundIntervention.ABORT), never stringified or serialised; no product code references it at all and no test asserts on its string form. I checked before changing it, because str(x) does differ between the two spellings.
  4. A real, if latent, defect. SessionBackedWriter._ensure_session declared -> str while returning the str | None attribute it caches into. Binding through a declared local makes the return provable. Behavior is unchanged — the bus does return an id — the annotation simply no longer overstates what was proven.

Verification

  • ruff check agent_core tests scripts → clean
  • pyright agent_core (strict) → 0 errors
  • pytest -q → 1302 passed (1146 baseline + 156 ported)
  • uv build + twine check → both PASSED
  • 0.5.0 wheel installed into a clean 3.12 venv: all 146 submodules import, new public surface reachable. This is stronger than the release workflow's own smoke test, which only does import agent_core and so cannot catch a missing runtime dependency in a leaf module.
  • scripts/check_version_bump.py --base origin/main → Published code changed and version increased 0.4.0 -> 0.5.0.

Two things a reviewer should weigh, not rubber-stamp

This code has one consumer, and it is not two products. cycle is used only by ApodexHarness workflows/imo_proof; verifier's six composers have zero production call sites in either product (tests only). FrontierAgentInternal has no analogue of either under any name — I searched for audit/critic/revise/verifier/validator/judge across the whole repo — and it has no LLMMiddleware/ExecutionMiddleware subclass at all; its extension mechanism is observers. So this lands here on the expectation that FrontierAgent will consume it, not because a two-product contract exists today. That is a deliberate call, made with the facts on the table.

scripts/check_version_bump.py does not see new files until they are committed. It diffs merge_base..HEAD, so brand-new files under agent_core/ are invisible while staged. Fine for PR CI (which always sees commits), but worth knowing if anyone runs the gate locally mid-change — it reported "No published code changed" for this very PR until I committed.

Follow-up, not in this PR

ApodexHarness needs the shim-back + pin bump to ==0.5.0. Doing that next, so workflows/imo_proof keeps its import paths untouched.

…Core

Both packages were already product-neutral in ApodexHarness: their only
outside imports were ``miroharness.core.tool`` (a ``sys.modules`` alias for
``agent_core.tool``) and one observer, which moves with them. So this is a
lift, not a redesign — no signature changed and no caller needs editing.

What core now owns: the write → audit → feedback → re-write loop and its
termination rules, the wall-clock budget with between-round estimation, the
missing-artifact output check, writer-exception and missing-output capture as
synthetic audits, per-round persistence of the audit trail, failure-isolated
round-observer dispatch, the AuditReport/WriterOutput/AuditFinding/CycleOutput
contracts with JSON round-trip, the Verifier/Generator protocols, the Verdict
model, the six composers, and the AuditReport ↔ Verdict bridge.

What the product keeps: concrete writers and auditors. ``AuditFinding.category``
and ``AuditReport.verdict`` stay free-form strings on purpose — the useful
vocabulary differs between a paper, a patch, a dataset and a plan, and freezing
one taxonomy here would force every product to translate into someone else's.
``GroundTruth``'s oracle fields are stripped by core when ``is_runtime=True``;
what the reference answer *is* remains a product decision.
``docs/cycle-verifier-boundary.md`` records the split.

Four changes beyond the move, all forced by AgentCore's stricter gates:

- ``default_factory=dict`` / ``=list`` became the parametrised spelling this
  repo already uses (``field(default_factory=dict[str, Any])``), which pyright
  strict requires. ``Verdict.sub_verdicts`` cannot use it — the name does not
  exist while its own class body runs — so it gets a named factory whose return
  annotation stays lazy under ``from __future__ import annotations``.
- ``json.loads`` results are narrowed explicitly rather than left as
  ``dict[Unknown, Unknown]`` after ``isinstance``.
- ``RoundIntervention`` moved from ``(str, Enum)`` to ``StrEnum``, matching the
  three existing StrEnums here. Safe because it is only ever compared, never
  stringified or serialised — no product code references it at all, and no test
  asserts on its string form.
- ``SessionBackedWriter._ensure_session`` declared ``-> str`` while returning
  the ``str | None`` attribute it caches into. Binding through a declared local
  makes the return provable. Behavior is unchanged; the annotation simply no
  longer overstates what was proven.

Verified: ruff clean over agent_core/tests/scripts, pyright strict 0 errors,
pytest 1302 passed (1146 + the 156 ported tests), uv build + twine check both
PASSED, and the 0.5.0 wheel installed into a clean 3.12 venv imports all 146
submodules with the new public surface reachable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@zhanghanduo
zhanghanduo merged commit 1284ab3 into main Sep 4, 2026
5 checks passed
@zhanghanduo
zhanghanduo deleted the feat/extract-cycle-verifier branch September 4, 2026 06:05
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