From e7e96f961a946a9726a67778baad5a580e433c1f Mon Sep 17 00:00:00 2001 From: Zain Dana Harper <17142659+HarperZ9@users.noreply.github.com> Date: Tue, 15 Sep 2026 19:21:45 -0700 Subject: [PATCH 1/2] Add Canon and Relay interop manifests --- README.md | 63 +++++++++++++------- manifests/canon.interop.json | 86 +++++++++++++++++++++++++++ manifests/relay.interop.json | 79 +++++++++++++++++++++++++ src/plexus/registry.py | 88 ++++++++++++++++++++++++++-- tests/test_extended_registry.py | 16 ++++- tests/test_flagship_interop_roles.py | 74 +++++++++++++++++++++++ 6 files changed, 378 insertions(+), 28 deletions(-) create mode 100644 manifests/canon.interop.json create mode 100644 manifests/relay.interop.json create mode 100644 tests/test_flagship_interop_roles.py diff --git a/README.md b/README.md index bb3c152..6e49815 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ plexus is the discovery layer that sits *above* an executor, not another executo ## The problem -You wire up five tools. Each one produces artifacts and accepts inputs, but +You wire up a set of tools. Each one produces artifacts and accepts inputs, but nothing knows how they connect, so you hand-wire `A | B | C` every time and rediscover the plumbing on every new task. plexus makes the toolchain self-describing: each tool ships a small manifest of what it emits and consumes, @@ -41,18 +41,23 @@ and plexus computes the wiring graph: which tool's output is which tool's input. ## What you get -**Discover the mesh.** Every producer→consumer edge, by capability: +**Discover the mesh.** Producer-to-consumer edges by capability, shown here as +an excerpt: ``` $ plexus wiring --builtin { + "canon.capsule/v1": [["canon","canon"]], + "canon.record/v1": [["canon","canon"]], "crucible.replay-pack/1": [["mneme", "crucible"]], "crucible.replay-template/1": [["crucible", "mneme"]], "crucible.thesis/1": [["mneme", "crucible"]], "gather.digest/1": [["gather", "crucible"]], "gather.items/1": [["gather", "mneme"]], "index.verification/1": [["index", "crucible"]], - "project-telos.flagship-action/v1": [["crucible","index"],["forum","index"],["gather","index"]] + "project-telos.flagship-action/v1": [["crucible","index"],["forum","index"],["gather","index"]], + "relay.rvc/v1": [["relay","relay"]], + "relay.session-ledger/1": [["relay","relay"]] } ``` @@ -66,9 +71,9 @@ satisfied by Mneme's native `mneme.crucible-export/2` export. ``` $ plexus plan --goal crucible - order: forum -> gather -> crucible -> index -> mneme + order: forum -> gather -> crucible -> index -> learn -> mneme -> telos sources: forum, gather - cyclic: crucible, index, mneme # both feedback loops, reported not hidden + cyclic: crucible, index, learn, mneme, telos # feedback loops, reported not hidden ``` **Route between two tools.** "I have `gather` output and want a `crucible` @@ -125,16 +130,31 @@ consumable as X"). Matching is by capability string, so an edge exists wherever the tools DECLARE compatible capabilities. plexus does not run the tools, so the edge is a declared claim, not a probed result. -The five flagship manifests are committed under [`manifests/`](manifests/) and -were generated by `plexus export`: the exact files each tool ships. Discovery -from those JSON files produces the same mesh as the built-in registry (a -round-trip test enforces it), so the format faithfully represents a real tool. - -### Extended manifests (August 2026) - -The registry now covers **eight organs** (not five): the original flagships -(gather, crucible, forum, index, mneme) plus three new manifests: - +The built-in manifests are committed under [`manifests/`](manifests/) and were +generated by `plexus export`: the exact files each tool ships. Discovery from +those JSON files produces the same mesh as the built-in registry (a round-trip +test enforces it), so the format faithfully represents a real tool. + +### Extended manifests (September 2026) + +The registry now covers **ten organs**. Canon and Relay are the two visible +flagship roles for context and connectivity; Index, mneme, and Plexus keep their +component boundaries instead of being collapsed into a database or executor. + +- **canon**: context and continuity flagship. It declares the shipped record + envelope, continuity capsule, readiness probe, bootstrap witness, and read-only + MCP surface. It does not declare universal context capture or shared preflight + as shipped. +- **relay**: connectivity and execution flagship. It declares the endpoint + ladder, MCP run request/result surfaces, hash-chained session ledgers, + Relay-Verified-Correctness certificates, and remote MCP endpoint. It does not + declare Canon capsules or Plexus route receipts as consumed until Relay ships + that adapter. +- **mneme**: memory recall, provenance chain, and drift component. It remains a + component under the context role, with its CLI/MCP compatibility intact. +- **index**: gatherer and source-context component. It still owns workspace + scanning, context envelopes, freshness, and verification outputs; those are + derived evidence, not authoritative memory. - **learn**: tutor credential/mastery ledger entries, proof lessons, misconceptions. Consumes crucible theses for proof-lesson derivation. - **telos**: room summary, golden workflow verification, workbench status. @@ -166,12 +186,13 @@ for r in results: Every edge is tagged `evidence: "declared"` and cites the **module** its producer names as the source (`file:function`). plexus does not import, resolve, or run that pointer, so the citation is a self-reported claim to check, not a verified -receipt. The built-in manifests for the five flagships (gather, crucible, forum, -index, mneme) began as a hand transcription from a source survey; the running -tool re-checks none of it, so treat every edge as declared until you follow the -pointer yourself. Mneme's contract was refreshed from public main on -2026-09-14, including `mneme.crucible-export/2` and -`mneme.local-origin-recheck/1`. +receipt. The running tool re-checks none of the built-in manifests, so treat +every edge as declared until you follow the pointer yourself. Mneme's contract +was refreshed from public main on 2026-09-14, including +`mneme.crucible-export/2` and `mneme.local-origin-recheck/1`. Canon and Relay +were added from public origin-main source on 2026-09-16, and their manifests use +only repo-relative public paths. They intentionally leave Canon-to-Relay and +Relay-to-Plexus routes disconnected until a public shipped consumer exists. plexus is also honest about what does **not** connect: diff --git a/manifests/canon.interop.json b/manifests/canon.interop.json new file mode 100644 index 0000000..987bc76 --- /dev/null +++ b/manifests/canon.interop.json @@ -0,0 +1,86 @@ +{ + "organ": "canon", + "invoke": { + "cli": "canon", + "mcp_server": "canon.local_mcp:serve", + "python_import": "canon" + }, + "emits": [ + { + "capability": "canon.record/v1", + "title": "provider-neutral context record", + "module": "src/canon/schema.py:Record.to_dict", + "summary": "one typed envelope for personality blocks, memories, personas, decisions, and research references", + "consumable_as": [] + }, + { + "capability": "canon.capsule/v1", + "title": "continuity capsule", + "module": "src/canon/capsule_build.py:compile_capsule", + "summary": "explicit records.jsonl and atoms.jsonl compile into capsule JSON, Canon Markdown, and readiness artifacts", + "consumable_as": [] + }, + { + "capability": "canon.readiness-probe/1", + "title": "capsule readiness probe", + "module": "src/canon/readiness.py:ReadinessProbe.to_dict", + "summary": "target acknowledgement challenge bound to one compiled capsule", + "consumable_as": [] + }, + { + "capability": "canon.bootstrap-witness/1", + "title": "context bootstrap witness", + "module": "src/canon/witness.py:BootstrapWitness.to_dict", + "summary": "records source state, capsule identity, readiness outcome, and does-not-prove limits", + "consumable_as": [] + }, + { + "capability": "canon.local-mcp-readonly/1", + "title": "read-only Canon MCP surface", + "module": "src/canon/local_mcp.py:TOOLS", + "summary": "status, doctor, blocks, render, validate, and check; this server writes no files", + "consumable_as": [] + } + ], + "consumes": [ + { + "capability": "canon.record/v1", + "title": "validated records from explicit source files", + "module": "src/canon/bootstrap_runtime_inputs.py:_records_from_source", + "summary": "JSONL records are parsed and validated before capsule compile or bootstrap", + "consumable_as": [] + }, + { + "capability": "canon.atom/v1", + "title": "validated bootstrap atoms from explicit source files", + "module": "src/canon/bootstrap_runtime_inputs.py:_atoms_from_source", + "summary": "JSONL atoms are parsed and validated before capsule compile or bootstrap", + "consumable_as": [] + }, + { + "capability": "canon.readiness-response/1", + "title": "host readiness response", + "module": "src/canon/readiness.py:evaluate_readiness_response", + "summary": "an optional host response is evaluated against the probe; absent response is reported as unknown", + "consumable_as": [] + }, + { + "capability": "canon.capsule/v1", + "title": "capsule Markdown carrier verification", + "module": "src/canon/canonmd.py:verify_canon_md", + "summary": "re-renders the embedded carrier capsule and reports drift or mismatch", + "consumable_as": [] + } + ], + "evidence": [ + "README.md", + "pyproject.toml", + "src/canon/schema.py", + "src/canon/local_mcp.py", + "src/canon/capsule_build.py", + "src/canon/bootstrap_runtime_inputs.py", + "src/canon/readiness.py", + "src/canon/witness.py", + "src/canon/canonmd.py" + ] +} diff --git a/manifests/relay.interop.json b/manifests/relay.interop.json new file mode 100644 index 0000000..f443067 --- /dev/null +++ b/manifests/relay.interop.json @@ -0,0 +1,79 @@ +{ + "organ": "relay", + "invoke": { + "cli": "relay", + "mcp_server": "relay.local_mcp:serve", + "python_import": "relay" + }, + "emits": [ + { + "capability": "relay.endpoint-ladder/1", + "title": "local and online endpoint ladder", + "module": "src/relay/endpoints.py:build_endpoints", + "summary": "configured provider/API/gateway/cloud rungs are built only from caller-supplied environment", + "consumable_as": [] + }, + { + "capability": "relay.agent-run-result/1", + "title": "gated agent run result", + "module": "src/relay/local_loop.py:run_agent", + "summary": "final answer, checkpoint, verification fields, acceptance check, reviewability, and observed route", + "consumable_as": [] + }, + { + "capability": "relay.session-ledger/1", + "title": "hash-chained session ledger", + "module": "src/relay/local_session.py:SessionLedger.to_jsonl", + "summary": "append-only run/session trajectory that re-verifies on load", + "consumable_as": [] + }, + { + "capability": "relay.rvc/v1", + "title": "Relay-Verified-Correctness certificate", + "module": "src/relay/cert.py:emit_cert", + "summary": "self-contained certificate embedding a witnessed ledger and typed acceptance contract", + "consumable_as": [] + }, + { + "capability": "relay.remote-mcp/1", + "title": "remote MCP endpoint", + "module": "src/relay/remote_mcp.py:process", + "summary": "Streamable HTTP MCP endpoint with bearer/OAuth authorization and remote exec forced off unless opted in", + "consumable_as": [] + } + ], + "consumes": [ + { + "capability": "relay.mcp-run-request/v1", + "title": "bounded MCP run request", + "module": "src/relay/local_mcp.py:_request_binding", + "summary": "binds goal, root, backend/model hints, write/exec gates, checks, and compaction budget", + "consumable_as": [] + }, + { + "capability": "relay.rvc/v1", + "title": "offline certificate verification", + "module": "src/relay/cert.py:verify_cert", + "summary": "re-derives ALLOW, UNVERIFIABLE, or REFUTED from the embedded ledger and contract", + "consumable_as": [] + }, + { + "capability": "relay.session-ledger/1", + "title": "saved session listing and reopening", + "module": "src/relay/session_store.py:get_session", + "summary": "saved ledgers are listed, reloaded, and re-verified from RELAY_SESSION_DIR", + "consumable_as": [] + } + ], + "evidence": [ + "README.md", + "pyproject.toml", + "src/relay/endpoints.py", + "src/relay/local_mcp.py", + "src/relay/local_loop.py", + "src/relay/local_session.py", + "src/relay/session_store.py", + "src/relay/cert.py", + "src/relay/remote_mcp.py" + ] +} diff --git a/src/plexus/registry.py b/src/plexus/registry.py index 017f942..840f15a 100644 --- a/src/plexus/registry.py +++ b/src/plexus/registry.py @@ -1,16 +1,18 @@ """registry.py — the built-in manifests, transcribed from a one-time code survey. Each capability key and module pointer below was transcribed by hand from the -flagship source during a 2026-07-07 survey. plexus does not re-read that source +flagship source during public-source surveys. plexus does not re-read that source at runtime, so these pointers are DECLARED citations, not probed receipts: if a flagship renames a cited symbol, the manifest here goes stale silently until the next manual survey. The Mneme contract was refreshed from public main on -2026-09-14. Capability keys are aligned across producers and consumers so +2026-09-14. The Canon and Relay contracts were added from public origin/main on +2026-09-16. Capability keys are aligned across producers and consumers so a declared edge forms where the code composed at survey time: gather.digest/1 -> crucible, gather.items/1 -> mneme, mneme.crucible-export/2 (as crucible.thesis/1) -> crucible, crucible.replay-template/1 -> mneme, -crucible.replay-pack/1 -> crucible, index.verification/1 -> crucible, and every flagship's -project-telos.flagship-action/v1 envelope -> index's spine loader. +crucible.replay-pack/1 -> crucible, index.verification/1 -> crucible, and +project-telos.flagship-action/v1 envelopes from the organs that declare them -> +index's spine loader. External manifests can be loaded from a directory of *.interop.json files with the same shape (see Manifest.to_dict), so a tool ships its own contract. @@ -160,6 +162,81 @@ "src/mneme/drift.py", "src/mneme/replay.py", "tests/test_crucible_replay.py"], }, + { + "organ": "canon", + "invoke": {"cli": "canon", "mcp_server": "canon.local_mcp:serve", "python_import": "canon"}, + "emits": [ + {"capability": "canon.record/v1", "title": "provider-neutral context record", + "module": "src/canon/schema.py:Record.to_dict", + "summary": "one typed envelope for personality blocks, memories, personas, decisions, and research references"}, + {"capability": "canon.capsule/v1", "title": "continuity capsule", + "module": "src/canon/capsule_build.py:compile_capsule", + "summary": "explicit records.jsonl and atoms.jsonl compile into capsule JSON, Canon Markdown, and readiness artifacts"}, + {"capability": "canon.readiness-probe/1", "title": "capsule readiness probe", + "module": "src/canon/readiness.py:ReadinessProbe.to_dict", + "summary": "target acknowledgement challenge bound to one compiled capsule"}, + {"capability": "canon.bootstrap-witness/1", "title": "context bootstrap witness", + "module": "src/canon/witness.py:BootstrapWitness.to_dict", + "summary": "records source state, capsule identity, readiness outcome, and does-not-prove limits"}, + {"capability": "canon.local-mcp-readonly/1", "title": "read-only Canon MCP surface", + "module": "src/canon/local_mcp.py:TOOLS", + "summary": "status, doctor, blocks, render, validate, and check; this server writes no files"}, + ], + "consumes": [ + {"capability": "canon.record/v1", "title": "validated records from explicit source files", + "module": "src/canon/bootstrap_runtime_inputs.py:_records_from_source", + "summary": "JSONL records are parsed and validated before capsule compile or bootstrap"}, + {"capability": "canon.atom/v1", "title": "validated bootstrap atoms from explicit source files", + "module": "src/canon/bootstrap_runtime_inputs.py:_atoms_from_source", + "summary": "JSONL atoms are parsed and validated before capsule compile or bootstrap"}, + {"capability": "canon.readiness-response/1", "title": "host readiness response", + "module": "src/canon/readiness.py:evaluate_readiness_response", + "summary": "an optional host response is evaluated against the probe; absent response is reported as unknown"}, + {"capability": "canon.capsule/v1", "title": "capsule Markdown carrier verification", + "module": "src/canon/canonmd.py:verify_canon_md", + "summary": "re-renders the embedded carrier capsule and reports drift or mismatch"}, + ], + "evidence": ["README.md", "pyproject.toml", "src/canon/schema.py", + "src/canon/local_mcp.py", "src/canon/capsule_build.py", + "src/canon/bootstrap_runtime_inputs.py", "src/canon/readiness.py", + "src/canon/witness.py", "src/canon/canonmd.py"], + }, + { + "organ": "relay", + "invoke": {"cli": "relay", "mcp_server": "relay.local_mcp:serve", "python_import": "relay"}, + "emits": [ + {"capability": "relay.endpoint-ladder/1", "title": "local and online endpoint ladder", + "module": "src/relay/endpoints.py:build_endpoints", + "summary": "configured provider/API/gateway/cloud rungs are built only from caller-supplied environment"}, + {"capability": "relay.agent-run-result/1", "title": "gated agent run result", + "module": "src/relay/local_loop.py:run_agent", + "summary": "final answer, checkpoint, verification fields, acceptance check, reviewability, and observed route"}, + {"capability": "relay.session-ledger/1", "title": "hash-chained session ledger", + "module": "src/relay/local_session.py:SessionLedger.to_jsonl", + "summary": "append-only run/session trajectory that re-verifies on load"}, + {"capability": "relay.rvc/v1", "title": "Relay-Verified-Correctness certificate", + "module": "src/relay/cert.py:emit_cert", + "summary": "self-contained certificate embedding a witnessed ledger and typed acceptance contract"}, + {"capability": "relay.remote-mcp/1", "title": "remote MCP endpoint", + "module": "src/relay/remote_mcp.py:process", + "summary": "Streamable HTTP MCP endpoint with bearer/OAuth authorization and remote exec forced off unless opted in"}, + ], + "consumes": [ + {"capability": "relay.mcp-run-request/v1", "title": "bounded MCP run request", + "module": "src/relay/local_mcp.py:_request_binding", + "summary": "binds goal, root, backend/model hints, write/exec gates, checks, and compaction budget"}, + {"capability": "relay.rvc/v1", "title": "offline certificate verification", + "module": "src/relay/cert.py:verify_cert", + "summary": "re-derives ALLOW, UNVERIFIABLE, or REFUTED from the embedded ledger and contract"}, + {"capability": "relay.session-ledger/1", "title": "saved session listing and reopening", + "module": "src/relay/session_store.py:get_session", + "summary": "saved ledgers are listed, reloaded, and re-verified from RELAY_SESSION_DIR"}, + ], + "evidence": ["README.md", "pyproject.toml", "src/relay/endpoints.py", + "src/relay/local_mcp.py", "src/relay/local_loop.py", + "src/relay/local_session.py", "src/relay/session_store.py", + "src/relay/cert.py", "src/relay/remote_mcp.py"], + }, { "organ": "learn", "invoke": {"cli": "learn", "mcp_server": "src/mcp.mjs", "node_entry": "src/mcp.mjs"}, @@ -247,7 +324,8 @@ def builtin_manifests() -> list: """The built-in flagship manifests, tagged with their in-code source. - Covers: gather, crucible, index, forum, mneme, learn, telos, flywheel-infra. + Covers: gather, crucible, index, forum, mneme, canon, relay, learn, telos, + flywheel-infra. """ out = [] for d in _SEED: diff --git a/tests/test_extended_registry.py b/tests/test_extended_registry.py index d27f94b..efef82e 100644 --- a/tests/test_extended_registry.py +++ b/tests/test_extended_registry.py @@ -5,9 +5,9 @@ def test_builtin_manifests_count(): - """Should now have 8 manifests (5 original + learn + telos + flywheel-infra).""" + """Should now have 10 manifests (8 prior + canon + relay).""" manifests = builtin_manifests() - assert len(manifests) == 8 + assert len(manifests) == 10 def test_learn_manifest_present(): @@ -28,6 +28,18 @@ def test_flywheel_infra_manifest_present(): assert "flywheel-infra" in organs +def test_canon_manifest_present(): + manifests = builtin_manifests() + organs = [m.organ for m in manifests] + assert "canon" in organs + + +def test_relay_manifest_present(): + manifests = builtin_manifests() + organs = [m.organ for m in manifests] + assert "relay" in organs + + def test_learn_manifest_emits(): manifests = builtin_manifests() learn = next(m for m in manifests if m.organ == "learn") diff --git a/tests/test_flagship_interop_roles.py b/tests/test_flagship_interop_roles.py new file mode 100644 index 0000000..01d4b5d --- /dev/null +++ b/tests/test_flagship_interop_roles.py @@ -0,0 +1,74 @@ +"""Role-boundary tests for the Canon and Relay interop manifests. + +These tests are deliberately about declared contracts, not live probes. Plexus +matches manifest strings and cited modules; it does not import Canon or Relay. +""" +from __future__ import annotations + +from pathlib import PurePosixPath + +from plexus.mesh import discover +from plexus.plan import route +from plexus.registry import builtin_manifests + + +def _manifests(): + return {manifest.organ: manifest for manifest in builtin_manifests()} + + +def _caps(ports): + return {port.capability for port in ports} + + +def _edge(mesh, producer, consumer, capability): + return any( + edge.producer == producer + and edge.consumer == consumer + and edge.capability == capability + for edge in mesh.edges + ) + + +def test_canon_declares_shipped_context_artifacts_without_capture_claims(): + canon = _manifests()["canon"] + emits = _caps(canon.emits) + consumes = _caps(canon.consumes) + + assert {"canon.record/v1", "canon.capsule/v1", "canon.readiness-probe/1"} <= emits + assert {"canon.record/v1", "canon.atom/v1", "canon.readiness-response/1"} <= consumes + assert "canon.context-capture/1" not in emits + assert "canon.shared-preflight/1" not in emits + + +def test_relay_declares_shipped_connectivity_and_execution_artifacts_only(): + relay = _manifests()["relay"] + emits = _caps(relay.emits) + consumes = _caps(relay.consumes) + + assert {"relay.agent-run-result/1", "relay.session-ledger/1", "relay.rvc/v1"} <= emits + assert {"relay.mcp-run-request/v1", "relay.rvc/v1", "relay.session-ledger/1"} <= consumes + assert "plexus.route-plan/1" not in consumes + assert "canon.capsule/v1" not in consumes + + +def test_canon_and_relay_form_only_edges_their_sources_actually_support(): + mesh = discover(builtin_manifests()) + + assert _edge(mesh, "canon", "canon", "canon.record/v1") + assert _edge(mesh, "canon", "canon", "canon.capsule/v1") + assert _edge(mesh, "relay", "relay", "relay.session-ledger/1") + assert _edge(mesh, "relay", "relay", "relay.rvc/v1") + + assert route(mesh, "canon", "relay")["connected"] is False + assert route(mesh, "relay", "canon")["connected"] is False + + +def test_canon_and_relay_evidence_paths_are_repo_relative_public_paths(): + for organ in ("canon", "relay"): + manifest = _manifests()[organ] + for path in manifest.evidence: + parsed = PurePosixPath(path) + assert not parsed.is_absolute() + assert "\\" not in path + assert ":" not in path + assert not path.startswith(("private/", "protected/", "secrets/")) From 2944f990d6c62c32723571b2fecb6e0da4ffa25c Mon Sep 17 00:00:00 2001 From: Zain Dana Harper <17142659+HarperZ9@users.noreply.github.com> Date: Wed, 16 Sep 2026 02:30:31 -0700 Subject: [PATCH 2/2] Clarify ownership of declared capability manifests --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6e49815..e92a245 100644 --- a/README.md +++ b/README.md @@ -130,10 +130,11 @@ consumable as X"). Matching is by capability string, so an edge exists wherever the tools DECLARE compatible capabilities. plexus does not run the tools, so the edge is a declared claim, not a probed result. -The built-in manifests are committed under [`manifests/`](manifests/) and were -generated by `plexus export`: the exact files each tool ships. Discovery from -those JSON files produces the same mesh as the built-in registry (a round-trip -test enforces it), so the format faithfully represents a real tool. +Plexus commits the built-in registry's exported JSON manifests under +[`manifests/`](manifests/). Discovery from these Plexus-side files produces the +same declared mesh as the built-in registry, as checked by a round-trip test. +Their presence here does not establish that each tool publishes its own manifest +or that a declared route has been exercised. ### Extended manifests (September 2026)