Repository navigation
feat: extract the write-audit cycle and verifier contracts into AgentCore - #28
Merged
Merged
Conversation
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Moves
components/cycleandcomponents/verifierout 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→ asys.modulesalias foragent_core.toolmiroharness.components.observers.conclude_phase_observer→ moves with themNo signature changed.
SessionBackedWriter/SessionBackedAuditorkeepbus: Anyand callcreate_session/submit_task_to_session/collectstructurally, so they work againstagent_core.components.agent_buswithout 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.categoryis a free-formstr, 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.verdictis likewise free-form; the cycle compares against a caller-suppliedterminal_verdictsset, 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 whenis_runtime=True— core enforces the isolation, the product decides what the reference answer is. Seedocs/cycle-verifier-boundary.md.Four changes beyond the move
AgentCore's gates are stricter than the product's (pyright strict over
agent_core, ruff overtests/too), so the move surfaced things the product's config never checked:default_factory=dict/=list→ the parametrised spelling already used inprotocols.py/loop_types.py/llm.py.Verdict.sub_verdictscan'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 underfrom __future__ import annotations.json.loadsresults narrowed explicitly instead of being leftdict[Unknown, Unknown]afterisinstance.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, becausestr(x)does differ between the two spellings.SessionBackedWriter._ensure_sessiondeclared-> strwhile returning thestr | Noneattribute 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→ cleanpyright agent_core(strict) → 0 errorspytest -q→ 1302 passed (1146 baseline + 156 ported)uv build+twine check→ both PASSEDimport agent_coreand 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.
cycleis used only by ApodexHarnessworkflows/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 noLLMMiddleware/ExecutionMiddlewaresubclass 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.pydoes not see new files until they are committed. It diffsmerge_base..HEAD, so brand-new files underagent_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, soworkflows/imo_proofkeeps its import paths untouched.