Skip to content

feat(core): add agentrust-capture-core and migrate claude-code onto it - #66

Merged
imran-siddique merged 2 commits into
mainfrom
feat/capture-core
Jul 31, 2026
Merged

feat(core): add agentrust-capture-core and migrate claude-code onto it#66
imran-siddique merged 2 commits into
mainfrom
feat/capture-core

Conversation

@imran-siddique

Copy link
Copy Markdown
Contributor

Why

Three engines carried the same logic in three copies, and the cost was not theoretical:

A fourth engine for Copilot would have meant writing both bugs a fourth time.

What ships

packages/agentrust-capture-core, standard library only, with a test asserting it. Owns the parts that are genuinely identical: tree_digest and the exclusion denylist, baseline sealing, the diff shapes plus observed-category and measurement-scope gating, atomic state read/write, and the report honesty vocabulary. 45 tests, ruff clean on 3.9.

Deliberately not unified: where an agent keeps its files, what its categories are called, how its report is laid out, and baseline scoping. Claude Code keeps one baseline per machine, Codex one per workspace because a workspace carries its own instructions and skills. Both are correct for their agent, so an engine supplies its own paths.

On the PyPI choice

You picked PyPI as the sharing mechanism. That conflicts with the stated promise that the SessionStart hook needs nothing installed, so both mechanisms ship: the package is the source of truth, and each engine carries a pinned vendored copy it falls back to when the package is absent.

Vendored copies are free to rot, which is exactly the failure this package exists to end, so they are generated by scripts/sync_vendored_core.py, never hand-edited, and CI fails if a copy disagrees with the package. I verified the check catches a one-line edit.

Not published. That needs credentials and is an irreversible claim on a package name, so it stays a human step. The package is publishable as-is.

claude-code is migrated, and the proof is that its tests did not change

Hashing, the skill tree digest, the denylist, sealing, and state read/write delegate to the core. Public names are retained as aliases, so all 59 existing tests run unchanged. That is the point: behaviour is proven identical by not touching the contract. 973 lines to 854.

Two behaviours deliberately did not move:

  1. _sha_tree keeps its own semantics, returning the digest of the empty string for a missing tree where the core returns None. The core's distinction between "no component here" and "a component with no files" is right for components, but adopting it for the instruction rollup would move system_prompt for every existing baseline.
  2. State writes are now atomic, replacing an in-place write. A strict improvement: a crash mid-write previously left a truncated baseline that read as corrupt on every future session.

Still to do

Codex and scheduled-agents get the vendored copy and are covered by the bare-install job, but still use their own implementations. Migrating them is mechanical now that the core exists and one engine proves it fits, and I kept it out of this PR so the core can be reviewed against one known-good migration rather than three at once.

Test plan

  • Core: 45 passed, ruff clean at py39, no third-party imports (asserted by test).
  • claude-code: 59 passed, unchanged test file.
  • scheduled-agents: 21 passed. Codex: 25 passed.
  • Vendored sync check passes, and was verified to fail on a deliberate one-line edit.
  • Bare-install path verified locally: with the core not installed, the engine loads the vendored copy and _skill_fingerprint is core.tree_digest.

New workflow runs the core on 3.9 through 3.13, the sync check, and a bare-install job that asserts the core is not installed and then imports all three engines. That last one matters because the fallback is the path most users are on, and nothing else would notice it rotting behind the installed path.

The one Codex failure is the pre-existing TRACE conformance check that fix/codex-trace-pins addresses.

imran-siddique and others added 2 commits July 31, 2026 13:48
Three engines carried the same logic in three copies, and the cost was not
theoretical. The skill-fingerprinting bypass closed in #63 had to be found and
fixed again in #65 because the second engine had it too, and the reporting defect
in #62 shipped in one engine while the other kept it. A fourth engine for Copilot
would have meant writing both bugs a fourth time.

This package owns the parts that are genuinely identical across agents:

  hashing  tree_digest over a component directory, file and mapping digests, the
           exclusion denylist, uuid7, now_iso
  seal     sealing a baseline with a content digest and checking it
  compare  map, set, scalar and rollup diffs, plus observed-category and
           measurement-scope gating
  state    atomic write, load-corrupt-as-absent, sealed baseline write
  report   the honesty vocabulary: unmeasured labelling, partial-coverage
           qualification, the baseline-integrity block

Standard library only, with a test asserting it, because the engines run from
shell hooks at session start and must work before anything is installed.

What is deliberately NOT unified: where an agent keeps its files, what its
categories are called, how its report is laid out, and baseline scoping. Claude
Code keeps one baseline per machine and Codex keeps one per workspace, because a
workspace carries its own instructions and skills. Both are correct for their
agent, so an engine supplies its own paths and the core only reads and writes.

The two honesty rules are encoded rather than documented, since both were bugs:
an unmeasured category renders as unmeasured rather than as a zero, and a
no-changes verdict is qualified as "in the categories checked" when coverage is
partial.

45 tests, including the bypass cases that were live in shipped engines, the
exclusion behaviour that keeps ordinary use from crying wolf, and one that makes
the sealing limit executable: a resealed rewrite passes the local check while its
digest no longer matches what was approved.

ruff clean at py39, which is the floor because the scheduled-agents matrix tests
3.9. Not yet published to PyPI and not yet wired into any engine; both follow.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Imran Siddique <imran.siddique@opaque.co>
…onto it

You chose PyPI as the sharing mechanism. That conflicts with the stated promise
that the SessionStart hook needs nothing installed, so both mechanisms ship: the
package is the source of truth, and each engine carries a pinned vendored copy it
falls back to when the package is absent. A bare plugin install therefore still
gets drift detection, and a user who pip installs gets the shared package.

Vendored copies are free to rot, which is exactly the failure this package exists
to end, so they are generated by scripts/sync_vendored_core.py and never
hand-edited. CI fails if a copy and the package disagree, and the check was
verified to catch a one-line edit.

claude-code is migrated. Hashing, the skill tree digest, the exclusion denylist,
sealing, and state read/write now delegate to the core. Public names are retained
as aliases so the 59 existing tests run unchanged, which is the point: behaviour is
proven identical by not touching the contract. 973 lines to 854.

Two behaviours deliberately did not move:

  _sha_tree keeps its own semantics, returning the digest of the empty string for a
  missing tree where the core returns None. The core's distinction between "no
  component here" and "a component with no files" is right for components, but
  adopting it for the instruction rollup would move system_prompt for every
  existing baseline.

  State writes are now atomic via the core, replacing an in-place write. That is a
  strict improvement: a crash mid-write previously left a truncated baseline that
  read as corrupt on every future session.

Codex and scheduled-agents get the vendored copy and are covered by the
bare-install job, but still use their own implementations; migrating them is the
remaining work and is mechanical now that the core exists and one engine proves it
fits.

New CI workflow: core tests on 3.9 through 3.13, the vendored-in-sync check, and a
bare-install job that asserts the core is NOT installed and then imports all three
engines. That last one matters because the fallback is the path most users are on,
and nothing else would notice it rotting behind the installed path.

Suites: claude-code 59, core 45, scheduled-agents 21, codex 25. The one codex
failure is the pre-existing TRACE conformance check that fix/codex-trace-pins
addresses.

Not published to PyPI. That needs credentials and is an irreversible claim on a
package name, so it stays a human step.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Imran Siddique <imran.siddique@opaque.co>
@imran-siddique
imran-siddique merged commit 140be9b into main Jul 31, 2026
26 checks passed
@imran-siddique
imran-siddique deleted the feat/capture-core branch July 31, 2026 21:52
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