Skip to content

A domain plugin, without touching core

The first tutorial registered a node and emitted a custom event. This one closes the loop that makes the node provable: a contract over your own event vocabulary, a proof bundle, and an offline verification — still without modifying a line of Evarness. Every output shown was captured from the tutorial package.

Declare a contract over your own event

Drop invariants.yaml next to graph.json — pattern-local contracts travel with the graph and load automatically:

invariants:
  shout-before-output:
    title: The shout happened
    description: Every run emits shout_applied before it finishes.
    assert:
      eventually:
        type: shout_applied

The graph opts in (contracts are declared, never ambient):

"params": {"invariants": ["shout-before-output"]}

shout_applied is nothing special to the kernel — it's a name your domain made meaningful. The contract machinery (never / eventually / every / precedes, where clauses) works over any vocabulary a domain emits. That's the extension thesis in one sentence: nodes and events are cheap to add; determinism, tracing, proof, and verification come with them.

Prove it, verify it

evarness prove graph.json --fixture fixture.yaml -o proof.json
  fixture          completed  invariants 1✓/0✗    reproduced      c1:sha256:89b45c4909c1d9…
PROOF: HOLDS — 1 scenario(s), invariants_pass=True, reproduced=True
wrote proof.json

prove ran your scenario twice and compared canonical digests — your custom node just inherited the reproducibility demonstration. Then:

evarness verify proof.json
  ✓ event chain recomputes [fixture] — c1:chain-sha256:78977bc01c3be821…
  ✓ event count matches [fixture] — 10 events
  ✓ verdict consistent with scenario rows — ok=True invariants_pass=True reproduced=True
  - signature — bundle is unsigned — integrity checks above still hold
VERIFY: OK — bundle is internally consistent

A bundle carrying evidence about your node, checkable on a machine that has never seen your code run.

The full seam surface

A node plus a contract is the small end of a domain. The registries in evarness.core.registry are the whole list of what a domain can contribute — each one is how the packaged agents domain wires itself in, so evarness/domains/agents/ is a complete worked example:

Seam What your domain supplies
NODE_TYPES / register_node node types
register_lint_rule policy lints in your vocabulary (the kernel checks structure only)
register_context_extension your per-run state object, namespaced under ctx.ext
register_contract_source a packaged invariant library
register_determinism_inspector "does this graph leave simulation?"
register_subject_pinner extra artifacts pinned into proof subjects
set_provider_factory / set_environment_loader how provider specs and scenario documents resolve

Two boundaries to respect, both enforced by tests in this repo:

  • Core never imports you — you register, it calls. If your design needs a core edit, that's a conversation (an issue), not a patch.
  • The trace must not lie — emit what happened, when it happened. An event your node didn't earn is worse than no event.

The agents domain is currently the only in-tree domain; a second, non-agent domain is the architecture's declared next test (see the honesty ledger in ARCHITECTURE.md). If you're building one, open an issue early — the seams above are exactly what community feedback should pressure-test.