plexus-mesh 0.2.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
plexus/plan.py ADDED
@@ -0,0 +1,120 @@
1
+ """plan.py — turn the mesh into an executable pipeline.
2
+
3
+ Two questions a user actually asks:
4
+ - "I want to feed tool X — what upstream chain produces its inputs?" -> plan_to
5
+ - "I have tool A's output and want tool B's verdict — how do they connect?" -> route
6
+
7
+ Both return ordered steps naming each hop's invocation (the declared CLI) plus
8
+ the capability that justifies it. A hop is auditable always; it is runnable only
9
+ when both ends declare a CLI. When one does not, the hop is marked
10
+ `runnable: false` and the absence is named, never coerced into a command.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ from .mesh import Mesh
15
+ from .receipt import build_plan_receipt
16
+
17
+
18
+ def _cli(mesh: Mesh, organ: str):
19
+ """The declared CLI for an organ, or None when the manifest declares none.
20
+ Absence is returned honestly, never laundered into the organ id."""
21
+ inv = mesh.manifests[organ].invoke if organ in mesh.manifests else {}
22
+ return inv.get("cli")
23
+
24
+
25
+ def _token(cli, organ: str) -> str:
26
+ return cli if cli else f"{organ}(no cli declared)"
27
+
28
+
29
+ def _toposort(mesh: Mesh, nodes: set) -> tuple:
30
+ """Kahn's algorithm over non-self edges induced on `nodes`. Returns
31
+ (order, cyclic): acyclic prefix in dependency order, then any nodes left in a
32
+ feedback loop (reported, never silently linearized)."""
33
+ edges = [(e.producer, e.consumer) for e in mesh.edges
34
+ if not e.self_loop and e.producer in nodes and e.consumer in nodes
35
+ and e.producer != e.consumer]
36
+ indeg = {n: 0 for n in nodes}
37
+ for _, c in edges:
38
+ indeg[c] += 1
39
+ ready = sorted(n for n in nodes if indeg[n] == 0)
40
+ order = []
41
+ while ready:
42
+ n = ready.pop(0)
43
+ order.append(n)
44
+ for p, c in edges:
45
+ if p == n:
46
+ indeg[c] -= 1
47
+ if indeg[c] == 0:
48
+ ready.append(c)
49
+ ready.sort()
50
+ cyclic = sorted(n for n in nodes if n not in order)
51
+ return order + cyclic, cyclic
52
+
53
+
54
+ def plan_to(mesh: Mesh, target: str) -> dict:
55
+ """The upstream producers that (transitively) feed `target`, in dependency
56
+ order. Pure sources first; any feedback loop is reported in `cyclic` rather
57
+ than forced into a false linear order."""
58
+ if target not in mesh.manifests:
59
+ return {"target": target, "error": "unknown organ", "order": []}
60
+
61
+ upstream, frontier = set(), [target]
62
+ while frontier:
63
+ node = frontier.pop()
64
+ for producer in mesh.producers_of(node):
65
+ if producer != target and producer not in upstream:
66
+ upstream.add(producer)
67
+ frontier.append(producer)
68
+ nodes = upstream | {target}
69
+
70
+ order, cyclic = _toposort(mesh, nodes)
71
+ hops = []
72
+ for e in mesh.edges:
73
+ if e.self_loop or e.producer not in nodes or e.consumer not in nodes:
74
+ continue
75
+ p_cli, c_cli = _cli(mesh, e.producer), _cli(mesh, e.consumer)
76
+ hops.append({"producer": e.producer, "consumer": e.consumer,
77
+ "capability": e.capability,
78
+ "invoke": f"{_token(p_cli, e.producer)} -> {_token(c_cli, e.consumer)}",
79
+ "runnable": p_cli is not None and c_cli is not None})
80
+ incoming = {c for _, c in [(e.producer, e.consumer) for e in mesh.edges
81
+ if not e.self_loop and e.producer in nodes
82
+ and e.consumer in nodes and e.producer != e.consumer]}
83
+ plan = {"target": target, "order": order, "hops": hops, "cyclic": cyclic,
84
+ "sources": [n for n in order if n not in incoming]}
85
+ plan["receipt"] = build_plan_receipt(mesh, plan)
86
+ return plan
87
+
88
+
89
+ def route(mesh: Mesh, source: str, target: str) -> dict:
90
+ """Shortest capability-path from `source` to `target` (BFS over edges)."""
91
+ if source not in mesh.manifests or target not in mesh.manifests:
92
+ return {"source": source, "target": target, "error": "unknown organ", "path": []}
93
+ # BFS keeping the edge used to reach each node
94
+ prev: dict = {source: None}
95
+ queue = [source]
96
+ while queue:
97
+ node = queue.pop(0)
98
+ if node == target:
99
+ break
100
+ for e in mesh.edges:
101
+ if e.self_loop or e.producer != node or e.consumer in prev:
102
+ continue
103
+ prev[e.consumer] = e
104
+ queue.append(e.consumer)
105
+ if target not in prev:
106
+ out = {"source": source, "target": target, "path": [], "connected": False}
107
+ out["receipt"] = build_plan_receipt(mesh, out)
108
+ return out
109
+ chain = []
110
+ node = target
111
+ while prev[node] is not None:
112
+ e = prev[node]
113
+ chain.append({"producer": e.producer, "consumer": e.consumer,
114
+ "capability": e.capability})
115
+ node = e.producer
116
+ chain.reverse()
117
+ out = {"source": source, "target": target, "connected": True,
118
+ "hops": len(chain), "path": chain}
119
+ out["receipt"] = build_plan_receipt(mesh, out)
120
+ return out
plexus/receipt.py ADDED
@@ -0,0 +1,66 @@
1
+ """receipt.py -- a re-derivable receipt for a plexus plan or route.
2
+
3
+ A plan is a claim: "given THESE manifests, this is the wiring." The receipt binds the
4
+ derivation to the exact manifests it came from: the content hash of every organ in the
5
+ plan, plus a hash over the derived body. So a stranger holding the mesh and the plan
6
+ re-derives it and confirms the receipt, and a manifest that changed since the plan is
7
+ caught -- the wiring silently shifting under you is the failure this prevents.
8
+
9
+ verify_plan re-derives the plan FROM THE MESH (never trusting the plan's stored body),
10
+ so a forger who tampers the body and recomputes the hash is still caught. Zero-dep.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import hashlib
15
+ import json
16
+
17
+ from .manifest import content_hash
18
+
19
+ SCHEMA = "plexus.plan-receipt/1"
20
+ METHOD_VERSION = "plexus-plan/1"
21
+
22
+
23
+ def _plan_organs(plan: dict) -> set:
24
+ """Every organ the plan references, so each one's manifest is bound to the receipt."""
25
+ organs: set = set()
26
+ organs.update(plan.get("order") or [])
27
+ for key in ("source", "target"):
28
+ if plan.get(key):
29
+ organs.add(plan[key])
30
+ for key in ("hops", "path"):
31
+ rows = plan.get(key)
32
+ if isinstance(rows, list): # route's `hops` is an int count, skip it; `path` is the list
33
+ for hop in rows:
34
+ if isinstance(hop, dict):
35
+ organs.update(x for x in (hop.get("producer"), hop.get("consumer")) if x)
36
+ return organs
37
+
38
+
39
+ def _plan_body_sha(plan: dict) -> str:
40
+ body = {k: v for k, v in plan.items() if k != "receipt"}
41
+ return hashlib.sha256(
42
+ json.dumps(body, sort_keys=True, ensure_ascii=False).encode("utf-8")).hexdigest()
43
+
44
+
45
+ def build_plan_receipt(mesh, plan: dict) -> dict:
46
+ """Bind the plan to the content hash of every organ it references plus its body hash."""
47
+ organs = sorted(o for o in _plan_organs(plan) if o in mesh.manifests)
48
+ manifest_shas = {o: content_hash(mesh.manifests[o]) for o in organs}
49
+ return {"schema": SCHEMA, "method_version": METHOD_VERSION,
50
+ "manifest_shas": manifest_shas, "plan_sha256": _plan_body_sha(plan)}
51
+
52
+
53
+ def verify_plan(plan: dict, mesh) -> bool:
54
+ """Re-derive the plan from the mesh and confirm its receipt. A tampered body or a
55
+ manifest that changed since the plan both fail. Read-only."""
56
+ r = plan.get("receipt")
57
+ if not isinstance(r, dict) or r.get("method_version") != METHOD_VERSION:
58
+ return False
59
+ from .plan import plan_to, route
60
+ if "source" in plan and "target" in plan:
61
+ fresh = route(mesh, plan["source"], plan["target"])
62
+ elif "target" in plan:
63
+ fresh = plan_to(mesh, plan["target"])
64
+ else:
65
+ return False
66
+ return build_plan_receipt(mesh, fresh) == r
plexus/registry.py ADDED
@@ -0,0 +1,418 @@
1
+ """registry.py — the built-in manifests, transcribed from a one-time code survey.
2
+
3
+ Each capability key and module pointer below was transcribed by hand from the
4
+ flagship source during public-source surveys. plexus does not re-read that source
5
+ at runtime, so these pointers are DECLARED citations, not probed receipts: if a
6
+ flagship renames a cited symbol, the manifest here goes stale silently until the
7
+ next manual survey. The Mneme contract was refreshed from public main on
8
+ 2026-09-14. The Canon and Relay contracts were added from public origin/main on
9
+ 2026-09-16. Capability keys are aligned across producers and consumers so
10
+ a declared edge forms where the code composed at survey time: gather.digest/1 ->
11
+ crucible, gather.items/1 -> mneme, mneme.crucible-export/2 (as crucible.thesis/1)
12
+ -> crucible, crucible.replay-template/1 -> mneme,
13
+ crucible.replay-pack/1 -> crucible, index.verification/1 -> crucible, and
14
+ project-telos.flagship-action/v1 envelopes from the organs that declare them ->
15
+ index's spine loader.
16
+
17
+ External manifests can be loaded from a directory of *.interop.json files with
18
+ the same shape (see Manifest.to_dict), so a tool ships its own contract.
19
+ """
20
+ from __future__ import annotations
21
+
22
+ import json
23
+ import os
24
+
25
+ from .manifest import Manifest
26
+
27
+ FLAGSHIP = "project-telos.flagship-action/v1"
28
+
29
+ _SEED = [
30
+ {
31
+ "organ": "gather",
32
+ "invoke": {"cli": "gather", "mcp_server": "gather.mcp:serve", "python_import": "gather"},
33
+ "emits": [
34
+ {"capability": FLAGSHIP, "title": "flagship-action envelope",
35
+ "module": "src/gather/flagship.py:envelope"},
36
+ {"capability": "gather.digest/1", "title": "witnessed digest seal",
37
+ "module": "src/gather/digest.py:Digest.to_json"},
38
+ {"capability": "gather.items/1", "title": "per-item provenance receipts",
39
+ "module": "src/gather/digest.py:_receipt"},
40
+ {"capability": "gather.catalog-digest/1", "title": "docs/arxiv catalog + digest",
41
+ "module": "src/gather/payloads.py:catalog_digest_payload"},
42
+ ],
43
+ "consumes": [
44
+ {"capability": "provenance.verdict/1", "title": "external provenance verdict",
45
+ "module": "src/gather/provenance.py:SubprocessProvenanceProvider.origin"},
46
+ {"capability": "gather.run-config/1", "title": "multi-source run config",
47
+ "module": "src/gather/run_config.py:load_run_config"},
48
+ ],
49
+ "evidence": ["src/gather/interop.py", "src/gather/digest.py",
50
+ "src/gather/payloads.py", "src/gather/flagship.py"],
51
+ },
52
+ {
53
+ "organ": "crucible",
54
+ "invoke": {"cli": "crucible", "mcp_server": "crucible.mcp:serve", "python_import": "crucible"},
55
+ "emits": [
56
+ {"capability": FLAGSHIP, "title": "flagship-action envelope",
57
+ "module": "src/crucible/flagship.py:envelope"},
58
+ {"capability": "crucible.assessment/1", "title": "witnessed assessment",
59
+ "module": "src/crucible/assess.py:Assessment.to_dict"},
60
+ {"capability": "project-telos.crucible.measurement-gate/v1", "title": "measurement gate",
61
+ "module": "src/crucible/measurement_gate.py:_result"},
62
+ {"capability": "crucible.thesis-export/1", "title": "public thesis contract",
63
+ "module": "src/crucible/gate.py:export_thesis"},
64
+ {"capability": "crucible.replay-template/1", "title": "oracle replay template",
65
+ "module": "src/crucible/recheck_cmd.py:_write_template"},
66
+ ],
67
+ "consumes": [
68
+ {"capability": "crucible.thesis/1", "title": "thesis JSON",
69
+ "module": "src/crucible/commands.py:_thesis_from_data"},
70
+ {"capability": "crucible.measurements/1", "title": "per-claim measurements",
71
+ "module": "src/crucible/commands.py:_load_measurements"},
72
+ {"capability": "crucible.replay-pack/1", "title": "assessment-bound replay pack",
73
+ "module": "src/crucible/recheck_cmd.py:_load_replay_pack"},
74
+ {"capability": "gather.digest/1", "title": "gather digest as evidence",
75
+ "module": "src/crucible/ecosystem_measure.py:GatherDigestMeasure"},
76
+ {"capability": "index.verification/1", "title": "index structural verdict",
77
+ "module": "src/crucible/ecosystem_measure.py:verify_index_verification"},
78
+ ],
79
+ "evidence": ["src/crucible/ecosystem_measure.py", "src/crucible/commands.py",
80
+ "src/crucible/assess.py", "src/crucible/gate.py",
81
+ "src/crucible/recheck_cmd.py"],
82
+ },
83
+ {
84
+ "organ": "forum",
85
+ "invoke": {"cli": "forum", "mcp_server": "forum.mcp_surface:serve_stdio", "python_import": "forum"},
86
+ "emits": [
87
+ {"capability": FLAGSHIP, "title": "flagship-action envelope",
88
+ "module": "src/forum/flagship.py:envelope"},
89
+ {"capability": "forum.flight-recorder/1", "title": "trace -> verifiable ledger",
90
+ "module": "src/forum/flight_recorder.py:import_trace"},
91
+ {"capability": "project-telos.action-receipt/v1", "title": "submit action receipt",
92
+ "module": "src/forum/receipts.py:submit_receipt"},
93
+ {"capability": "forum.context-capsule/v1", "title": "witnessed context capsule",
94
+ "module": "src/forum/context_capsule.py:build_context_capsule"},
95
+ ],
96
+ "consumes": [
97
+ {"capability": "external-agent-trace/1", "title": "LangSmith/OTel/AgentOps trace",
98
+ "module": "src/forum/flight_recorder.py:normalize_trace"},
99
+ ],
100
+ "evidence": ["src/forum/ledger.py", "src/forum/flight_recorder.py",
101
+ "src/forum/flagship.py", "src/forum/receipts.py"],
102
+ },
103
+ {
104
+ "organ": "index",
105
+ "invoke": {"cli": "index", "mcp_server": "index_graph.mcp:serve", "python_import": "index_graph"},
106
+ "emits": [
107
+ {"capability": FLAGSHIP, "title": "flagship-action envelope",
108
+ "module": "src/index_graph/flagship.py:envelope"},
109
+ {"capability": "project-telos.context-envelope/v1", "title": "budgeted context envelope",
110
+ "module": "src/index_graph/context/envelope.py:build_context_envelope"},
111
+ {"capability": "index.verification/1", "title": "structural-claim verdict",
112
+ "module": "src/index_graph/verify.py:build_verification"},
113
+ {"capability": "index.invalidation-pin/1", "title": "freshness pin",
114
+ "module": "src/index_graph/freshness/invalidate.py:mint_pin"},
115
+ {"capability": "index.wiki/1", "title": "sealed commit-pinned wiki",
116
+ "module": "src/index_graph/wiki/pack.py:build_wiki_pack"},
117
+ ],
118
+ "consumes": [
119
+ {"capability": FLAGSHIP, "title": "peer envelopes into the operator spine",
120
+ "module": "src/index_graph/workbench.py:load_spine"},
121
+ {"capability": "index.invalidation-pin/1", "title": "pin to diff against the tree",
122
+ "module": "src/index_graph/freshness/invalidate.py:invalidation_report"},
123
+ {"capability": "index.wiki/1", "title": "sealed wiki to re-verify",
124
+ "module": "src/index_graph/wiki/seal.py:verify_wiki"},
125
+ ],
126
+ "evidence": ["src/index_graph/flagship.py", "src/index_graph/context/envelope.py",
127
+ "src/index_graph/verify.py", "src/index_graph/workbench.py"],
128
+ },
129
+ {
130
+ "organ": "mneme",
131
+ "invoke": {"cli": "mneme", "mcp_server": "mneme.mcp:serve", "python_import": "mneme"},
132
+ "emits": [
133
+ {"capability": "mneme.crucible-export/2", "title": "memory as crucible thesis",
134
+ "module": "src/mneme/compose.py:to_crucible_thesis",
135
+ "summary": "claims, drift measurements, and declarative mneme.recheck/1 descriptors",
136
+ "consumable_as": ["crucible.thesis/1"]},
137
+ {"capability": "crucible.replay-pack/1", "title": "assessment-bound Mneme drift replay",
138
+ "module": "src/mneme/replay.py:replay_crucible",
139
+ "summary": "replays descriptor-bound measurements without importing Crucible or executing commands"},
140
+ {"capability": "mneme.local-origin-recheck/1", "title": "supported local origin freshness report",
141
+ "module": "src/mneme/origin.py:recheck_local_origins",
142
+ "summary": "explicit read-only local docs/file-read freshness checks under a caller-approved root"},
143
+ {"capability": "mneme.provenance-chain/1", "title": "memory -> source origin",
144
+ "module": "src/mneme/ingest.py:provenance_chain",
145
+ "summary": "memory-to-origin receipt chain; external freshness is checked separately when supported"},
146
+ {"capability": "mneme.recall/1", "title": "re-derivable recall receipt",
147
+ "module": "src/mneme/receipt.py:RecallReceipt.as_dict"},
148
+ {"capability": "mneme.drift-report/1", "title": "memory faithfulness verdicts",
149
+ "module": "src/mneme/drift.py:drift_report"},
150
+ ],
151
+ "consumes": [
152
+ {"capability": "gather.items/1", "title": "accountable intake items",
153
+ "module": "src/mneme/ingest.py:from_gather"},
154
+ {"capability": "conversation-turns/1", "title": "raw turns to remember",
155
+ "module": "src/mneme/memory.py:AgentMemory.remember"},
156
+ {"capability": "crucible.replay-template/1", "title": "assessment-bound measurement replay template",
157
+ "module": "src/mneme/replay.py:replay_crucible",
158
+ "summary": "decoded Crucible template plus an explicitly supplied Mneme state"},
159
+ ],
160
+ "evidence": ["src/mneme/compose.py", "src/mneme/ingest.py",
161
+ "src/mneme/origin.py", "src/mneme/receipt.py",
162
+ "src/mneme/drift.py", "src/mneme/replay.py",
163
+ "tests/test_crucible_replay.py"],
164
+ },
165
+ {
166
+ "organ": "canon",
167
+ "invoke": {"cli": "canon", "mcp_server": "canon.local_mcp:serve", "python_import": "canon"},
168
+ "emits": [
169
+ {"capability": "canon.record/v1", "title": "provider-neutral context record",
170
+ "module": "src/canon/schema.py:Record.to_dict",
171
+ "summary": "one typed envelope for personality blocks, memories, personas, decisions, and research references"},
172
+ {"capability": "canon.capsule/v1", "title": "continuity capsule",
173
+ "module": "src/canon/capsule_build.py:compile_capsule",
174
+ "summary": "explicit records.jsonl and atoms.jsonl compile into capsule JSON, Canon Markdown, and readiness artifacts"},
175
+ {"capability": "canon.readiness-probe/1", "title": "capsule readiness probe",
176
+ "module": "src/canon/readiness.py:ReadinessProbe.to_dict",
177
+ "summary": "target acknowledgement challenge bound to one compiled capsule"},
178
+ {"capability": "canon.bootstrap-witness/1", "title": "context bootstrap witness",
179
+ "module": "src/canon/witness.py:BootstrapWitness.to_dict",
180
+ "summary": "records source state, capsule identity, readiness outcome, and does-not-prove limits"},
181
+ {"capability": "canon.local-mcp-readonly/1", "title": "read-only Canon MCP surface",
182
+ "module": "src/canon/local_mcp.py:TOOLS",
183
+ "summary": "status, doctor, blocks, render, validate, and check; this server writes no files"},
184
+ ],
185
+ "consumes": [
186
+ {"capability": "canon.record/v1", "title": "validated records from explicit source files",
187
+ "module": "src/canon/bootstrap_runtime_inputs.py:_records_from_source",
188
+ "summary": "JSONL records are parsed and validated before capsule compile or bootstrap"},
189
+ {"capability": "canon.atom/v1", "title": "validated bootstrap atoms from explicit source files",
190
+ "module": "src/canon/bootstrap_runtime_inputs.py:_atoms_from_source",
191
+ "summary": "JSONL atoms are parsed and validated before capsule compile or bootstrap"},
192
+ {"capability": "canon.readiness-response/1", "title": "host readiness response",
193
+ "module": "src/canon/readiness.py:evaluate_readiness_response",
194
+ "summary": "an optional host response is evaluated against the probe; absent response is reported as unknown"},
195
+ {"capability": "canon.capsule/v1", "title": "capsule Markdown carrier verification",
196
+ "module": "src/canon/canonmd.py:verify_canon_md",
197
+ "summary": "re-renders the embedded carrier capsule and reports drift or mismatch"},
198
+ ],
199
+ "evidence": ["README.md", "pyproject.toml", "src/canon/schema.py",
200
+ "src/canon/local_mcp.py", "src/canon/capsule_build.py",
201
+ "src/canon/bootstrap_runtime_inputs.py", "src/canon/readiness.py",
202
+ "src/canon/witness.py", "src/canon/canonmd.py"],
203
+ },
204
+ {
205
+ "organ": "relay",
206
+ "invoke": {"cli": "relay", "mcp_server": "relay.local_mcp:serve", "python_import": "relay"},
207
+ "emits": [
208
+ {"capability": "relay.endpoint-ladder/1", "title": "local and online endpoint ladder",
209
+ "module": "src/relay/endpoints.py:build_endpoints",
210
+ "summary": "configured provider/API/gateway/cloud rungs are built only from caller-supplied environment"},
211
+ {"capability": "relay.agent-run-result/1", "title": "gated agent run result",
212
+ "module": "src/relay/local_loop.py:run_agent",
213
+ "summary": "final answer, checkpoint, verification fields, acceptance check, reviewability, and observed route"},
214
+ {"capability": "relay.session-ledger/1", "title": "hash-chained session ledger",
215
+ "module": "src/relay/local_session.py:SessionLedger.to_jsonl",
216
+ "summary": "append-only run/session trajectory that re-verifies on load"},
217
+ {"capability": "relay.rvc/v1", "title": "Relay-Verified-Correctness certificate",
218
+ "module": "src/relay/cert.py:emit_cert",
219
+ "summary": "self-contained certificate embedding a witnessed ledger and typed acceptance contract"},
220
+ {"capability": "relay.remote-mcp/1", "title": "remote MCP endpoint",
221
+ "module": "src/relay/remote_mcp.py:process",
222
+ "summary": "Streamable HTTP MCP endpoint with bearer/OAuth authorization and remote exec forced off unless opted in"},
223
+ ],
224
+ "consumes": [
225
+ {"capability": "relay.mcp-run-request/v1", "title": "bounded MCP run request",
226
+ "module": "src/relay/local_mcp.py:_request_binding",
227
+ "summary": "binds goal, root, backend/model hints, write/exec gates, checks, and compaction budget"},
228
+ {"capability": "relay.rvc/v1", "title": "offline certificate verification",
229
+ "module": "src/relay/cert.py:verify_cert",
230
+ "summary": "re-derives ALLOW, UNVERIFIABLE, or REFUTED from the embedded ledger and contract"},
231
+ {"capability": "relay.session-ledger/1", "title": "saved session listing and reopening",
232
+ "module": "src/relay/session_store.py:get_session",
233
+ "summary": "saved ledgers are listed, reloaded, and re-verified from RELAY_SESSION_DIR"},
234
+ ],
235
+ "evidence": ["README.md", "pyproject.toml", "src/relay/endpoints.py",
236
+ "src/relay/local_mcp.py", "src/relay/local_loop.py",
237
+ "src/relay/local_session.py", "src/relay/session_store.py",
238
+ "src/relay/cert.py", "src/relay/remote_mcp.py"],
239
+ },
240
+ {
241
+ "organ": "learn",
242
+ "invoke": {"cli": "learn", "mcp_server": "src/mcp.mjs", "node_entry": "src/mcp.mjs"},
243
+ "emits": [
244
+ {"capability": "learn-receipt", "title": "tutor credential/mastery ledger entries",
245
+ "module": "src/interop.mjs:receiptEntry"},
246
+ {"capability": "learn.tutor-mastery/1", "title": "mastery gate verdict",
247
+ "module": "src/tutor/tutor.mjs:mastery"},
248
+ {"capability": "learn.tutor-prooflesson/1", "title": "proof packet to lesson scaffold",
249
+ "module": "src/tutor/prooflesson.mjs:proofLesson"},
250
+ {"capability": "learn.tutor-misconceptions/1", "title": "ranked misconception aggregation",
251
+ "module": "src/tutor/misconception.mjs:misconceptions"},
252
+ {"capability": FLAGSHIP, "title": "flagship-action envelope",
253
+ "module": "src/interop.mjs:receiptEntry"},
254
+ ],
255
+ "consumes": [
256
+ {"capability": "crucible.thesis/1", "title": "crucible thesis for proof lesson",
257
+ "module": "src/tutor/prooflesson.mjs:proofLesson"},
258
+ ],
259
+ "evidence": ["src/interop.mjs", "src/tutor/tutor.mjs",
260
+ "src/tutor/prooflesson.mjs", "src/tutor/misconception.mjs"],
261
+ },
262
+ {
263
+ "organ": "telos",
264
+ "invoke": {"cli": "telos", "mcp_server": "demo/telos-mcp.mjs", "node_entry": "demo/telos-mcp.mjs"},
265
+ "emits": [
266
+ {"capability": "telos.room/1", "title": "five-flagship room summary",
267
+ "module": "src/telos-mcp.mjs:telos_room"},
268
+ {"capability": "telos.workflow/1", "title": "golden workflow verification",
269
+ "module": "src/telos-mcp.mjs:telos_workflow"},
270
+ {"capability": "telos.status/1", "title": "workbench readiness + next actions",
271
+ "module": "src/telos-mcp.mjs:telos_status"},
272
+ {"capability": FLAGSHIP, "title": "flagship-action envelope",
273
+ "module": "src/telos-mcp.mjs:telos_status"},
274
+ ],
275
+ "consumes": [
276
+ {"capability": "project-telos.flagship-action/v1", "title": "cross-tool action",
277
+ "module": "src/telos-mcp.mjs:telos_room"},
278
+ ],
279
+ "evidence": ["demo/telos-mcp.mjs"],
280
+ },
281
+ {
282
+ "organ": "flywheel-infra",
283
+ "invoke": {"cli": "flywheel", "python_import": "harness"},
284
+ "emits": [
285
+ {"capability": "flywheel.egress/1", "title": "sealed egress event receipt",
286
+ "module": "harness/infra/egress.py:build_egress_receipt"},
287
+ {"capability": "flywheel.lesson/1", "title": "sealed organizational lesson",
288
+ "module": "harness/lesson.py:build_lesson"},
289
+ {"capability": "flywheel.tool-call-receipt/1", "title": "sealed tool-call receipt",
290
+ "module": "harness/tool_call_receipt.py:build_receipt"},
291
+ {"capability": "flywheel.tadr-classification/1", "title": "sealed tier classification",
292
+ "module": "harness/governance/tadr_receipt.py:build_classification_receipt"},
293
+ {"capability": "flywheel.governance-envelope/1", "title": "cross-lane governance state",
294
+ "module": "harness/governance_envelope.py:GovernanceEnvelope"},
295
+ {"capability": "flywheel.credential-scan/1", "title": "sealed credential exposure report",
296
+ "module": "harness/infra/credential_scanner.py:build_credential_scan_receipt"},
297
+ {"capability": "flywheel.correlated-event/1", "title": "cross-layer correlated detection",
298
+ "module": "harness/infra/correlator.py:build_correlated_receipt"},
299
+ {"capability": "flywheel.isolation-test/1", "title": "boundary test verdict",
300
+ "module": "harness/infra/isolation_test.py:run_isolation_test"},
301
+ {"capability": "flywheel.kill-switch/1", "title": "dual-confirmed infrastructure stop",
302
+ "module": "harness/infra/kill_switch.py:build_kill_receipt"},
303
+ {"capability": "flywheel.run-bom/1", "title": "sealed run bill of materials",
304
+ "module": "harness/infra/run_bom.py:RunBOM.sealed"},
305
+ ],
306
+ "consumes": [
307
+ {"capability": "accountable-surface.actuation-outcome/1",
308
+ "title": "intent-vs-outcome for lesson derivation",
309
+ "module": "harness/lesson_mappers.py:intent_outcome_lessons"},
310
+ {"capability": "mneme.drift-report/1",
311
+ "title": "memory drift for lesson derivation",
312
+ "module": "harness/lesson_mappers.py:drift_lessons"},
313
+ {"capability": "learn.tutor-misconceptions/1",
314
+ "title": "graded failures for lesson derivation",
315
+ "module": "harness/lesson_mappers.py:misconception_lessons"},
316
+ ],
317
+ "evidence": ["harness/lesson.py", "harness/infra/egress.py",
318
+ "harness/governance/tadr_receipt.py",
319
+ "harness/tool_call_receipt.py"],
320
+ },
321
+ ]
322
+
323
+
324
+ def builtin_manifests() -> list:
325
+ """The built-in flagship manifests, tagged with their in-code source.
326
+
327
+ Covers: gather, crucible, index, forum, mneme, canon, relay, learn, telos,
328
+ flywheel-infra.
329
+ """
330
+ out = []
331
+ for d in _SEED:
332
+ m = Manifest.from_dict(d)
333
+ m.source = "builtin:registry"
334
+ out.append(m)
335
+ return out
336
+
337
+
338
+ def probe_lane(name: str, timeout: float = 10.0) -> dict:
339
+ """Probe a lane by spawning its MCP server and calling status/doctor.
340
+
341
+ Unlike the declared manifest (which cites source files without running
342
+ them), this actually spawns the lane and verifies it responds. Returns
343
+ {name, reachable, tools, error}.
344
+
345
+ Requires the lane's MCP server to be installed and on PATH.
346
+ """
347
+ result = {"name": name, "reachable": False, "tools": [], "error": ""}
348
+ try:
349
+ from harness.lanes import resolve_mcp_command, LANES
350
+ if name not in LANES:
351
+ result["error"] = f"unknown lane: {name}"
352
+ return result
353
+ command = resolve_mcp_command(name)
354
+ except ImportError:
355
+ result["error"] = "harness.lanes not available (run from flywheel)"
356
+ return result
357
+ except Exception as e:
358
+ result["error"] = f"resolve failed: {e}"
359
+ return result
360
+
361
+ try:
362
+ with MCPClient(command, timeout=timeout,
363
+ client_name=f"plexus-probe-{name}") as c:
364
+ c.start()
365
+ tools_res = c.call_text("tools/list", {})
366
+ if tools_res["ok"]:
367
+ import json as _json
368
+ try:
369
+ data = _json.loads(tools_res["text"])
370
+ if isinstance(data, list):
371
+ result["tools"] = data
372
+ elif isinstance(data, dict) and "tools" in data:
373
+ result["tools"] = data["tools"]
374
+ except _json.JSONDecodeError:
375
+ pass
376
+ status_res = c.call_text("status", {})
377
+ result["reachable"] = status_res["ok"]
378
+ except Exception as e:
379
+ result["error"] = f"{type(e).__name__}: {e}"
380
+ return result
381
+
382
+
383
+ def probe_all(timeout: float = 10.0) -> list[dict]:
384
+ """Probe all built-in lanes. Returns a list of probe results."""
385
+ try:
386
+ from harness.lanes import LANES
387
+ names = sorted(LANES.keys())
388
+ except ImportError:
389
+ names = [m.organ for m in builtin_manifests()]
390
+ return [probe_lane(name, timeout=timeout) for name in names]
391
+
392
+
393
+ def load_dir(path: str) -> list:
394
+ """Load every *.interop.json manifest in a directory (external tools). Each
395
+ manifest records the file it was read from as its provenance source."""
396
+ out = []
397
+ for name in sorted(os.listdir(path)):
398
+ if name.endswith(".interop.json"):
399
+ full = os.path.join(path, name)
400
+ with open(full, encoding="utf-8") as f:
401
+ m = Manifest.from_dict(json.load(f))
402
+ m.source = full
403
+ out.append(m)
404
+ return out
405
+
406
+
407
+ def export_all(out_dir: str) -> list:
408
+ """Write each built-in manifest as <organ>.interop.json — the exact file a
409
+ flagship would ship to join the mesh. Returns the paths written."""
410
+ os.makedirs(out_dir, exist_ok=True)
411
+ written = []
412
+ for m in builtin_manifests():
413
+ p = os.path.join(out_dir, f"{m.organ}.interop.json")
414
+ with open(p, "w", encoding="utf-8") as f:
415
+ json.dump(m.to_dict(), f, indent=2, ensure_ascii=False)
416
+ f.write("\n")
417
+ written.append(p)
418
+ return written
plexus/run.py ADDED
@@ -0,0 +1,44 @@
1
+ """run.py — turn a plan into runnable output.
2
+
3
+ `plan_to` says what feeds a target; this emits the concrete, ordered invocation
4
+ sequence to actually produce it — a shell script you can read, edit, and run. It
5
+ does NOT execute anything itself (the tools live in their own environments); it
6
+ makes the plan actionable and auditable instead of leaving it as advice. Feedback
7
+ loops are emitted as a labelled comment, never silently linearized into a lie.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ from .mesh import Mesh
12
+ from .plan import _cli, plan_to
13
+
14
+
15
+ def pipeline_script(mesh: Mesh, target: str) -> str:
16
+ """A runnable, commented shell script that invokes each upstream tool in
17
+ dependency order to feed `target`."""
18
+ plan = plan_to(mesh, target)
19
+ if plan.get("error"):
20
+ return f"# error: {plan['error']} ({target!r})"
21
+
22
+ lines = ["#!/usr/bin/env bash", "set -euo pipefail",
23
+ f"# plexus pipeline to feed: {target}",
24
+ f"# sources: {', '.join(plan['sources']) or '(none)'}"]
25
+ if plan["cyclic"]:
26
+ lines.append(f"# NOTE: feedback loop {' <-> '.join(plan['cyclic'])} : "
27
+ "these tools mutually depend; run order among them is not linear")
28
+ lines.append("")
29
+
30
+ for organ in plan["order"]:
31
+ if organ == target:
32
+ continue
33
+ inbound = [h for h in plan["hops"] if h["consumer"] == organ]
34
+ produced = sorted({h["capability"] for h in plan["hops"] if h["producer"] == organ})
35
+ note = f"produces: {', '.join(produced)}" if produced else "source"
36
+ lines.append(f"# {organ}: {note}")
37
+ lines.append(f"{_cli(mesh, organ)} run # -> feeds "
38
+ f"{', '.join(sorted({h['consumer'] for h in plan['hops'] if h['producer'] == organ}))}")
39
+ lines.append("")
40
+
41
+ feeders = sorted({h["producer"] for h in plan["hops"] if h["consumer"] == target})
42
+ lines.append(f"# {target}: consumes from {', '.join(feeders) or '(external inputs only)'}")
43
+ lines.append(f"{_cli(mesh, target)} run")
44
+ return "\n".join(lines)