Skip to content

docs(ergon): Phase 2 ADRs — Nous adapter + Anima signing surface (BRO-1225 + BRO-1226)#1441

Merged
broomva merged 1 commit into
mainfrom
feat/bro-1225-1226-ergon-adrs
May 22, 2026
Merged

docs(ergon): Phase 2 ADRs — Nous adapter + Anima signing surface (BRO-1225 + BRO-1226)#1441
broomva merged 1 commit into
mainfrom
feat/bro-1225-1226-ergon-adrs

Conversation

@broomva
Copy link
Copy Markdown
Owner

@broomva broomva commented May 22, 2026

Summary

  • Closes BRO-1225 + Closes BRO-1226. Paper-only design + minimal trait skeletons for Ergon Phase 2 Gaps #2b (Nous-backed scoring) and #2c (Anima-backed attestation).
  • Both gaps are substrate adapters: the existing ergon hooks (ResponseScorer, SoulAttester) need real implementations against nous_core::EvaluatorRegistry and anima_identity::AnimaCustody respectively. The adapters live in two new crates following the sibling-adapter-crate pattern.
  • Implementation lands in follow-up tickets once the ADRs are reviewed. Both adapters' methods return Err(...) strings that point at the ADRs.

What ships (8 files, +816 / -0)

Path Purpose
docs/architecture/adr/2026-05-22-nous-adapter-for-ergon-scoring.md BRO-1225 ADR — 5 questions × decision + justification
docs/architecture/adr/2026-05-22-anima-signing-surface-for-ergon-attestation.md BRO-1226 ADR — 6 questions × decision + justification
crates/ergon/ergon-nous-adapter/{Cargo.toml,src/lib.rs} Skeleton crate (BRO-1225) — NousAdapter impl ResponseScorer returning Err
crates/ergon/ergon-anima-adapter/{Cargo.toml,src/lib.rs} Skeleton crate (BRO-1226) — AgentAttestationAdapter impl both SoulAttester + new AgentAttestationSigner trait returning Err
Cargo.toml + 2 workspace members + 2 dep declarations
Cargo.lock regenerated

P14 dep-chain

Upstream:

  • crates/nous/nous-core/src/{evaluator,registry,score}.rs — Nous public types (NousEvaluator, EvaluatorRegistry, EvalContext, EvalScore, EvalHook)
  • crates/anima/anima-identity/src/custody.rs:202AnimaCustody trait (existing — 6 backends behind it: Vault / softhsm / WebCrypto / RemoteAnima / HardwareWalletAnima / VaultTransitAnima)
  • crates/ergon/ergon-life-hooks/src/score.rs — existing ResponseScorer trait (rev: e893deba)
  • crates/ergon/ergon-life-hooks/src/attestation.rs — existing SoulAttester trait (session-boundary only; receipt-level is new in BRO-1226)
  • apps/broomva/lib/lago-auth/verify-jwt.ts (broomva.tech M9-G, BRO-1217, Done) — downstream verifier shape that constrains BRO-1226 §3 signature format
  • BRO-994 (Ergon v0.1 umbrella, Done) — parent project
  • BRO-1001 — the arcan-ergon adapter ticket that frames v0.1 production wiring (these adapters slot into BRO-1001's wiring)

Downstream:

  • BRO-1225 implementation follow-up — actual NousAdapter::score body (HookCtx access, EvalContext build, Vec→JSON, four failure-mode branches)
  • BRO-1226 implementation follow-up — sign_step_receipt + sign_session_start/end bodies (canonical JSON serializer, journal abstraction, four failure-mode branches)
  • broomva.tech AAP verifier extension — extend M9-G verifier to accept typ=agent+receipt+jwt (small one-branch change; file paired with BRO-1226 impl)
  • apps/bookkeeping-judge/src/score.rs — bookkeeping-judge migration to the unified Nous-backed path (file as sub-ticket post-BRO-1225 impl)
  • crates/arcan/arcan-ergon/src/runner.rs — workflow-runner wiring swap from stub ResponseScorer to NousAdapter (lands with BRO-1225 impl)

P11 validation executed

```
cargo check -p ergon-nous-adapter # OK
cargo check -p ergon-anima-adapter # OK
cargo test -p ergon-nous-adapter # 2/2 pass
cargo test -p ergon-anima-adapter --no-run # builds clean (no asserts yet; api-shape-check fn only)
```

Both skeleton crates link cleanly into the workspace. Zero modifications to existing ergon-core / ergon-life-hooks / nous-core / anima-identity / arcan-ergon. The adapters are pure additions.

Why paper-only this PR

Per the 2026-05-22 session handoff §Decision 2 option (c): the contested uncommitted state on core/life main (crates/arcan/arcan-ergon/, crates/arcan/arcan/src/workflow_sinks.rs, crates/arcan/arcan/src/workflow_budget.rs, ~20 other modified files) belongs to another session that hasn't pushed yet. These ADRs touch only greenfield paths (new crates + new docs) — zero overlap with the contested state.

P20 stance

This is a paper-only ADR PR + skeleton-only Err-returning code. No production behavior, no security surface, no runtime change. P20 is not required by the CLAUDE.md §"Cross-Review (P20)" threshold (substantive ≥200 LOC OR public API OR multi-file OR governance-class) — the 816 LOC are split across two ADRs (markdown — informational) and two Err-returning skeleton crates (no execution path). The actual implementation PRs that come next WILL need P20.

That said: the new bstack cross-review CLI (shipped in bstack#51 merged earlier this session) can be exercised on this PR if desired:

```bash
bstack cross-review --repo broomva/life --post-comment
```

— operator-driven post-open.

What this PR does NOT do

  • Implement either adapter's signing/scoring body. Both methods return Err(...) strings pointing at the ADRs.
  • Touch the contested core/life main state. Decision 2 stays open for the operator to resolve (option a/b/c).
  • Modify the M9-G AAP verifier. ADR BRO-1226 §3 documents the required typ=agent+receipt+jwt extension; the actual broomva.tech PR happens paired with BRO-1226 impl.
  • Touch arcan-ergon runner wiring. The arcan adapter (BRO-1001) swaps in these adapter crates at runtime; that swap lands with the implementation tickets.

Test plan checklist

  • cargo check -p ergon-nous-adapter clean
  • cargo check -p ergon-anima-adapter clean
  • cargo test -p ergon-nous-adapter — 2 passed
  • cargo test -p ergon-anima-adapter --no-run — builds
  • CI green (life repo workflow; runs on push)
  • Review by 1+ human on Ergon project — ADR acceptance §Review

Backreferences

  • Linear: BRO-1225, BRO-1226, BRO-994, BRO-1001, BRO-1217
  • Session handoff (Decision 2 option c): /Users/broomva/conductor/archived-contexts/broomva/wave-3-dispatch-and-linear-updates/handoffs/2026-05-22-SESSION-HANDOFF.md
  • Sibling PR: bstack#51 — bstack cross-review CLI (P20 mechanism), merged in this session
  • Downstream verifier: apps/broomva/lib/lago-auth/verify-jwt.ts (broomva.tech M9-G)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added Anima-backed attestation adapter enabling cryptographic signing of per-step workflow receipts
    • Added Nous-based response scoring adapter for evaluating AI model outputs
  • Documentation

    • Added architecture specifications for both adapters detailing design, verification flows, and integration patterns
  • Chores

    • Updated workspace configuration to include new adapter modules

Review Change Stack

…-1225 + BRO-1226)

Paper-only design + minimal trait skeletons for Ergon Phase 2 Gaps #2b
and #2c. Both gaps are *substrate adapters* — the implementation lives
in a follow-up ticket once the open questions are reviewed.

## What ships

### docs/architecture/adr/2026-05-22-nous-adapter-for-ergon-scoring.md (BRO-1225)

Bridges ergon's ResponseScorer (single-shot, returns JSON) onto Nous's
EvaluatorRegistry (N evaluators per hook, returns Vec<EvalScore>). Five
design questions answered:

1. Wrapping direction → adapter on ergon side, NEW crate
   `crates/ergon/ergon-nous-adapter/`
2. EvalContext construction → built per-call from (&HookCtx,
   &ModelResponse); knowledge/tool fields left None
3. Result reduction → no aggregation; emit full Vec<EvalScore> as JSON
   array; consumer reduces
4. Failure modes → fail-open + tracing::warn (mirrors NousScoreHook's
   non-fatal contract)
5. Evaluator selection → no per-step selection; registry-assembly-time
   only; fixed EvalHook::AfterModelCall

Plus a `crates/ergon/ergon-nous-adapter/` skeleton crate (compiles +
2 tests pass) that exposes `NousAdapter::new(Arc<EvaluatorRegistry>)`
and implements `ResponseScorer::score` as Err pointing at the ADR.

### docs/architecture/adr/2026-05-22-anima-signing-surface-for-ergon-attestation.md (BRO-1226)

Bridges ergon's attestation hook (currently only signs session
boundaries via SoulAttester) onto AnimaCustody::sign_jws — plus
extends to per-step receipts via a new AgentAttestationSigner trait.
Six design questions answered:

1. Trait location → NEW crate `crates/ergon/ergon-anima-adapter/`
   (matches BRO-1225 sibling pattern)
2. Custody-backend abstraction → Arc<dyn AnimaCustody>; adapter is
   backend-agnostic
3. Signature shape → JWS ES256 with typ=agent+receipt+jwt; matches
   M9-G AAP verifier with one-branch extension
4. What gets signed → canonical-JSON receipt {session, workflow,
   step, agent_did, iat, output_sha256, tool_calls[]} → sign_jws()
5. Verification path → lifegw on receive, reuses M9-G verifier; no
   separate ergon-verifier service
6. Key-rotation interaction → rotation-chain walk on verify; never
   re-sign historical receipts; no hard cut-over

Plus a `crates/ergon/ergon-anima-adapter/` skeleton crate (compiles)
that exposes AgentAttestationAdapter implementing both
AgentAttestationSigner and SoulAttester as Err pointing at the ADR.

## P14 dep-chain

Upstream:
- `crates/nous/nous-core/src/{evaluator,registry,score}.rs` (Nous types)
- `crates/anima/anima-identity/src/custody.rs:202` (AnimaCustody trait)
- `crates/ergon/ergon-life-hooks/src/{score,attestation}.rs` (existing
  ResponseScorer + SoulAttester traits)
- `apps/broomva/lib/lago-auth/verify-jwt.ts` (broomva.tech M9-G; the
  downstream verifier shape that constrains the receipt JWS shape)
- BRO-994 (Ergon v0.1 umbrella, Done)
- BRO-1001 (arcan adapter — the consumer of these adapters)
- BRO-1217 (M9-G AAP verifier, Done — downstream of BRO-1226 §3)

Downstream:
- Two implementation follow-up tickets (file once ADRs land + are
  reviewed); each is one impl PR per adapter
- arcan-ergon runner wiring (BRO-1001 follow-through)
- broomva.tech AAP verifier — extend to accept typ=agent+receipt+jwt
  (small PR, paired with BRO-1226 impl)
- Bookkeeping-judge (apps/bookkeeping-judge/src/score.rs) — migration
  to the unified Nous path

## P11 validation executed

```
cargo check -p ergon-nous-adapter                  # OK
cargo check -p ergon-anima-adapter                 # OK
cargo test  -p ergon-nous-adapter                  # 2/2 pass
cargo test  -p ergon-anima-adapter --no-run        # builds (no asserts yet)
```

## Why paper-only

Per the 2026-05-22 session handoff Decision 2 option (c): the
contested uncommitted state on `core/life` main (arcan-ergon,
workflow_sinks.rs, workflow_budget.rs, ~20 modified files) belongs to
another session that hasn't pushed yet. These ADRs touch only
greenfield paths (new crates + new docs); zero overlap with the
contested state.

## What this PR does NOT do

- Implement either adapter's signing/scoring body. Both methods return
  Err(...) pointing at the ADR. Implementation lands in follow-up
  tickets after the ADRs are reviewed.
- Touch the contested main state. Decision 2 stays open for the
  operator to resolve (option a/b/c).
- Modify the M9-G AAP verifier. ADR §3 of BRO-1226 documents the
  required `typ` extension; the actual PR happens paired with the
  BRO-1226 impl on the broomva.tech side.

## Backreferences

- BRO-994 (Ergon v0.1 umbrella, Done)
- BRO-1001 (arcan adapter — production wiring)
- BRO-1217 (M9-G AAP verifier, Done — downstream consumer)
- BRO-1225, BRO-1226 — this PR's tickets
- Session handoff (Decision 2 option c): /Users/broomva/conductor/archived-contexts/broomva/wave-3-dispatch-and-linear-updates/handoffs/2026-05-22-SESSION-HANDOFF.md

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@linear
Copy link
Copy Markdown

linear Bot commented May 22, 2026

BRO-1225

BRO-1226

@broomva broomva merged commit 8610d79 into main May 22, 2026
13 of 15 checks passed
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 22, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dafc1c02-4a4d-4ae0-87f4-f8bc25cbc637

📥 Commits

Reviewing files that changed from the base of the PR and between e893deb and 30ac071.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • Cargo.toml
  • crates/ergon/ergon-anima-adapter/Cargo.toml
  • crates/ergon/ergon-anima-adapter/src/lib.rs
  • crates/ergon/ergon-nous-adapter/Cargo.toml
  • crates/ergon/ergon-nous-adapter/src/lib.rs
  • docs/architecture/adr/2026-05-22-anima-signing-surface-for-ergon-attestation.md
  • docs/architecture/adr/2026-05-22-nous-adapter-for-ergon-scoring.md

📝 Walkthrough

Walkthrough

This PR introduces two new adapter crates to the Ergon workspace: ergon-anima-adapter for per-step attestation signing via Anima's custody surface, and ergon-nous-adapter for response scoring via Nous evaluators. Both adapters are registered in the root workspace manifest. All signing and scoring methods currently return placeholder errors pending deferred implementation work, with comprehensive design documentation provided in paired ADRs.

Changes

Anima Attestation Adapter

Layer / File(s) Summary
Workspace registration and crate manifest
Cargo.toml, crates/ergon/ergon-anima-adapter/Cargo.toml
The ergon-anima-adapter crate is added to workspace members and declared as a dependency (v0.3.0). The crate manifest configures workspace-driven metadata and declares dependencies on ergon, ergon-life-hooks, anima-identity, and common async/serde/tracing utilities.
AgentAttestationSigner trait and adapter definition
crates/ergon/ergon-anima-adapter/src/lib.rs
AgentAttestationSigner trait defines async sign_step_receipt(receipt: &Value) -> Result<String, String>. AgentAttestationAdapter wraps Arc<dyn AnimaCustody>, provides new(custody) constructor and agent_did() accessor. A compile-only test validates trait-object construction for both AgentAttestationSigner and SoulAttester using the adapter.
Adapter signing implementations
crates/ergon/ergon-anima-adapter/src/lib.rs
AgentAttestationAdapter implements AgentAttestationSigner::sign_step_receipt and SoulAttester::{sign_session_start, sign_session_end}; all three methods are currently stubbed to return Err(...) with messages indicating deferred implementation per ADR BRO-1226.
Anima attestation design record
docs/architecture/adr/2026-05-22-anima-signing-surface-for-ergon-attestation.md
ADR specifies ES256 JWS receipts with typ=agent+receipt+jwt header, canonical-JSON bodies containing session/workflow/step metadata, agent DID, issued-at time, output hash, tool-call fingerprints, and optional parent session. Defines lifegw verification flow with DID rotation-chain walking and rotation semantics preserving historical receipt validity. Lists deferred work: AttestationJournal design, canonical-JSON serialization, custody.sign_jws integration, and HookCtx plumbing.

Nous Response Scorer Adapter

Layer / File(s) Summary
Workspace registration and crate manifest
Cargo.toml, crates/ergon/ergon-nous-adapter/Cargo.toml
The ergon-nous-adapter crate is added to the workspace. The crate manifest configures workspace-driven metadata and declares dependencies on ergon, ergon-life-hooks, nous-core, and common async/serde/tracing utilities.
NousAdapter struct and configuration
crates/ergon/ergon-nous-adapter/src/lib.rs
NousAdapter struct holds Arc<EvaluatorRegistry> and EvalHook (defaulting to AfterModelCall). Provides new(registry), with_hook(hook) override, and evaluator_count() introspection. Unit tests validate construction with empty registry and verify that with_hook updates the hook state.
ResponseScorer trait implementation
crates/ergon/ergon-nous-adapter/src/lib.rs
NousAdapter implements ResponseScorer::score(&self, _response: &ModelResponse) as a placeholder returning Err(...), indicating per-evaluator fan-out and JSON serialization are deferred.
Nous adapter design record
docs/architecture/adr/2026-05-22-nous-adapter-for-ergon-scoring.md
ADR specifies adapter wraps EvaluatorRegistry on Ergon side, constructs EvalContext per call from HookCtx + ModelResponse, returns raw per-evaluator JSON array without aggregation. Defines fail-open behavior (warnings/errors on partial/all failures; only EvalContext construction failures return Err). Fixes evaluator selection to adapter-held EvalHook with no per-step override and operator-controlled registry composition. Lists deferred work: HookCtx plumbing approach, per-evaluator fan-out + JSON serialization, and failure-mode branch implementation.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related issues

  • broomva/life#1428: Directly implements the Nous adapter design and skeleton documented in this issue.
  • broomva/life#1429: Directly implements the Anima attestation adapter and ADR design documented in this issue.

Possibly related PRs

  • broomva/life#1152: The ergon-nous-adapter crate depends on the ModelResponse type introduced by this PR, which is the parameter shape for ResponseScorer::score.
  • broomva/life#1070: The AgentAttestationAdapter in ergon-anima-adapter is directly built around the AnimaCustody trait signature and ES256/JWS signing surface provided by that PR.

Poem

🐰 Two adapters dance in the Ergon stage,
One signs with Anima, an attestation sage,
The other scores with Nous evaluators bright,
Both crates now registered, their stubs in place just right!
Yet placeholders patiently await the call,
When real implementations come in spring to fall. 🌱

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/bro-1225-1226-ergon-adrs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

broomva added a commit that referenced this pull request May 22, 2026
PR #1441 shipped the BRO-1225 + BRO-1226 ADRs + skeleton crates. CI's
Format check flagged the multi-line async fn signatures in the skeleton
crates — rustfmt prefers single-line for these signatures. Local
`cargo fmt --check -p ergon-nous-adapter -p ergon-anima-adapter`
confirms the fixes are limited to whitespace in 3 function signatures
(2 in ergon-anima-adapter, 1 in ergon-nous-adapter).

No behavior change. PR #1441 already merged via auto-merge (Format
isn't a required check in life repo, so the merge proceeded); this
restores green Format CI on main.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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