diff --git a/README.md b/README.md index 17ff4dc..cc2342b 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,19 @@ The operator-only network entrypoint is `mnel fabric-run --config ... --plan ... --manifest ...`; it accepts only Fabric's bounded fixed-argv plan/manifest pair and fails closed when trust material or the declared pre-staged bundle identity is absent. +The recurrent-specialist slice adds a small deterministic reference provider with +separate persistent context and per-query reasoning state. It trains role-specific +centroid models, calibrates a bounded recurrent envelope, records structured +abstention and resource measurements, and exposes an MNEL Provider Protocol JSON-line +boundary. Generate the Forge and Control reference artifacts with: + +```bash +mnel recurrent-specialist-reference --workspace examples/recurrent-specialists +``` + +These artifacts are diagnostic-only and do not issue verifier results, permissions, +trust, or promotion decisions. + ## Core rule **Investigators and learned providers may propose knowledge. They may not declare it true.** diff --git a/crates/mnel-provider-fixture-invalid/src/lib.rs b/crates/mnel-provider-fixture-invalid/src/lib.rs index 5ff576c..b6f1489 100644 --- a/crates/mnel-provider-fixture-invalid/src/lib.rs +++ b/crates/mnel-provider-fixture-invalid/src/lib.rs @@ -24,5 +24,8 @@ static mut DESCRIPTOR: ProviderDescriptorV1 = ProviderDescriptorV1 { #[no_mangle] pub extern "C" fn mnel_provider_entry_v1() -> *const ProviderDescriptorV1 { - &raw const DESCRIPTOR + #[allow(unused_unsafe)] + unsafe { + core::ptr::addr_of!(DESCRIPTOR) + } } diff --git a/crates/mnel-provider-fixture/src/lib.rs b/crates/mnel-provider-fixture/src/lib.rs index d9766c9..847bada 100644 --- a/crates/mnel-provider-fixture/src/lib.rs +++ b/crates/mnel-provider-fixture/src/lib.rs @@ -33,7 +33,10 @@ static mut DESCRIPTOR: ProviderDescriptorV1 = ProviderDescriptorV1 { #[no_mangle] pub extern "C" fn mnel_provider_entry_v1() -> *const ProviderDescriptorV1 { - &raw const DESCRIPTOR + #[allow(unused_unsafe)] + unsafe { + core::ptr::addr_of!(DESCRIPTOR) + } } extern "C" fn infer( diff --git a/examples/recurrent-specialists/control-specialist-g0.json b/examples/recurrent-specialists/control-specialist-g0.json new file mode 100644 index 0000000..38456c2 --- /dev/null +++ b/examples/recurrent-specialists/control-specialist-g0.json @@ -0,0 +1 @@ +{"architecture":{"context_state":"persistent-derived-summary","kind":"bounded-recurrent-centroid","masked_select":true,"max_iterations":4,"reasoning_state":"per-query-fixed-point-lanes","width":4},"architecture_identity":"sha256:0b854e10e4fc3843017c7a264620bcfb2ace844ade15820e313e2a0faf737ab4","artifact_identity":"sha256:f85a6a6128e04a793e4dbf62251053c531ea8ee7e1228b84e4c5a1c462902338","authority":"diagnostic-only","calibration_identity":"sha256:ed45dd8137189986ff154df38594c3ee8f92fcde2db18c3c159db1ebd9e7740b","checkpoint_identity":"sha256:7411beef4eab8ac1d6d0c86c7fe9ec5f2e139b22b38aa67d39beda3ca1051d82","class_centroids":{"filesystem":[900,800,700,200],"forge":[600,500,800,850],"git":[800,700,220,400],"testing":[700,820,600,700]},"generation_identity":"sha256:8056daf8a283b378999995dc96f53cb03b3bea3440adcc279bfab1cf79b2ac27","inherited_strategies":[],"known_counterexamples":[],"model_identity":"sha256:d605a9ee3bf59ad31eac8d712d94a244decbd7455cf176fa70b0054ecebce44a","negative_memory":["destructive-tool-never-authorized"],"operating_envelope":{"convergence_delta":2,"envelope_identity":"sha256:9a6a22dbdc10856fc7e480810f8e2912b59d1bb8be15b8b0082a68fffd942bbd","max_iterations":4,"maximum_context_observations":32,"maximum_distance":180,"maximum_query_abs":1000,"minimum_confidence":850},"parent_model_identity":null,"prior_failure_causes":[],"provider_abi":"mnel-specialist-provider-abi/0.1","provider_id":"mnel-bounded-recurrent-specialist/0.1","schema":"mnel-recurrent-specialist-artifact/0.1","semantics":"identity-bound-learned-specialist; diagnostic-only; not-a-verdict","source_evidence_references":["control-train-files","control-train-forge","control-train-git","control-train-tests"],"target_role":"control.tool-family-routing","training_code_identity":"sha256:cead54014d62e920af40844af0c6556ea9ae7cde2b32bee77dcde0695c9b6d2c","training_dataset_identity":"sha256:1e41109b307452405f2b97082ead6e9604f6841636e37a255d9cba1cb5df5e76","training_record_ids":["control-train-files","control-train-forge","control-train-git","control-train-tests"],"training_spec_identity":"sha256:fbe653836dcaa301da274b9fc1d8d203ea4ca67dff9426510054aa73d57921f9"} \ No newline at end of file diff --git a/examples/recurrent-specialists/forge-specialist-g0.json b/examples/recurrent-specialists/forge-specialist-g0.json new file mode 100644 index 0000000..7bd2224 --- /dev/null +++ b/examples/recurrent-specialists/forge-specialist-g0.json @@ -0,0 +1 @@ +{"architecture":{"context_state":"persistent-derived-summary","kind":"bounded-recurrent-centroid","masked_select":true,"max_iterations":4,"reasoning_state":"per-query-fixed-point-lanes","width":4},"architecture_identity":"sha256:0b854e10e4fc3843017c7a264620bcfb2ace844ade15820e313e2a0faf737ab4","artifact_identity":"sha256:c0a860ad60b33a3cc6d77b55bf6bc5f5fc711232d0b31ca34adcaf2c7c6b3ce2","authority":"diagnostic-only","calibration_identity":"sha256:3a9c61b28bc33a51e5ae1ac50029830d48b8d310c6a539fc874e378ab6540d05","checkpoint_identity":"sha256:92a9795d8d45d9dd1146efcfc1efff9da98524422f194a077c5ad08ccb027c1b","class_centroids":{"irrelevant":[170,150,170,130],"relevant":[860,790,730,840]},"generation_identity":"sha256:2767857f27a108bee26d88acb05597c2233500dd81e69c89ac621d423c4917d4","inherited_strategies":[],"known_counterexamples":[],"model_identity":"sha256:b3c8ba2bd9672b2ea652a92d1fd81c4c2f15db76c59a4ee5ff5fb90b72d94f62","negative_memory":["known-omission-is-escalation"],"operating_envelope":{"convergence_delta":2,"envelope_identity":"sha256:7a7248d840ac9b048935ebdb6b34e5188526f4dea78d850357ed122d18d78a7e","max_iterations":4,"maximum_context_observations":32,"maximum_distance":460,"maximum_query_abs":1000,"minimum_confidence":850},"parent_model_identity":null,"prior_failure_causes":[],"provider_abi":"mnel-specialist-provider-abi/0.1","provider_id":"mnel-bounded-recurrent-specialist/0.1","schema":"mnel-recurrent-specialist-artifact/0.1","semantics":"identity-bound-learned-specialist; diagnostic-only; not-a-verdict","source_evidence_references":["forge-train-irrelevant-1","forge-train-irrelevant-2","forge-train-relevant-1","forge-train-relevant-2"],"target_role":"forge.evidence-relevance","training_code_identity":"sha256:cead54014d62e920af40844af0c6556ea9ae7cde2b32bee77dcde0695c9b6d2c","training_dataset_identity":"sha256:1cec040071b9fb21036bcf75403b17766d14094a4418e4c5835fd29e82140c3e","training_record_ids":["forge-train-irrelevant-1","forge-train-irrelevant-2","forge-train-relevant-1","forge-train-relevant-2"],"training_spec_identity":"sha256:c9d1736ae21cc36285aaecc34b41f656bfc2edab61e99b0a59c93ea37db525ba"} \ No newline at end of file diff --git a/examples/recurrent-specialists/reference-evidence.json b/examples/recurrent-specialists/reference-evidence.json new file mode 100644 index 0000000..432ac36 --- /dev/null +++ b/examples/recurrent-specialists/reference-evidence.json @@ -0,0 +1,324 @@ +{ + "authority": "diagnostic-only", + "baseline": "one-step nearest-centroid classification; deterministic and non-recurrent", + "cost_measurements": { + "control_catalog_bytes_available": 6400, + "control_catalog_bytes_avoided": 4800, + "control_catalog_bytes_selected": 1600, + "forge_context_bytes_available": 4096, + "forge_context_bytes_avoided": 3072, + "forge_context_bytes_selected": 1024, + "larger_model_calls_avoided": 2 + }, + "evaluations": { + "control": { + "abstentions": 1, + "baseline_correct_known": 2, + "cases": 3, + "decisions": [ + { + "abstained": false, + "authority": "diagnostic-only", + "calibration_identity": "sha256:ed45dd8137189986ff154df38594c3ee8f92fcde2db18c3c159db1ebd9e7740b", + "confidence": 1.0, + "confidence_milli": 1000, + "context_state_identity": "sha256:5dbed64b4b6845121d18e0ded5669c5c2b95311b7a5965623317815bdf859236", + "decision": "git", + "decision_identity": "sha256:0ae23c7ab6cf543a9b5d8e0e1e2648ba7a63dc3eef72168c3674ef8582938558", + "elapsed_ns": 58230, + "escalation_reason": null, + "generation_identity": "sha256:8056daf8a283b378999995dc96f53cb03b3bea3440adcc279bfab1cf79b2ac27", + "halting_reason": "converged-mask-deactivated", + "hidden_state": [ + 799, + 699, + 220, + 399 + ], + "input_features": [ + 780, + 680, + 240, + 380 + ], + "lineage_identity": null, + "model_identity": "sha256:d605a9ee3bf59ad31eac8d712d94a244decbd7455cf176fa70b0054ecebce44a", + "operating_envelope_identity": "sha256:9a6a22dbdc10856fc7e480810f8e2912b59d1bb8be15b8b0082a68fffd942bbd", + "operations": 65, + "reasoning_iterations": 4, + "request_identity": "sha256:fd524258c1e9d7925b2e544f56d18547185b1f71330fc95f8139a4669c407745", + "schema": "mnel-specialist-decision/0.1", + "semantics": "bounded-recurrent-structured-decision; not-a-verdict", + "source_observation_identities": [] + }, + { + "abstained": false, + "authority": "diagnostic-only", + "calibration_identity": "sha256:ed45dd8137189986ff154df38594c3ee8f92fcde2db18c3c159db1ebd9e7740b", + "confidence": 1.0, + "confidence_milli": 1000, + "context_state_identity": "sha256:5dbed64b4b6845121d18e0ded5669c5c2b95311b7a5965623317815bdf859236", + "decision": "testing", + "decision_identity": "sha256:71f37a78d21bcc277b8d17846ce8a368afca1d6e61eabef949524cf7d481cff0", + "elapsed_ns": 55871, + "escalation_reason": null, + "generation_identity": "sha256:8056daf8a283b378999995dc96f53cb03b3bea3440adcc279bfab1cf79b2ac27", + "halting_reason": "converged-mask-deactivated", + "hidden_state": [ + 699, + 819, + 599, + 699 + ], + "input_features": [ + 680, + 780, + 580, + 680 + ], + "lineage_identity": null, + "model_identity": "sha256:d605a9ee3bf59ad31eac8d712d94a244decbd7455cf176fa70b0054ecebce44a", + "operating_envelope_identity": "sha256:9a6a22dbdc10856fc7e480810f8e2912b59d1bb8be15b8b0082a68fffd942bbd", + "operations": 65, + "reasoning_iterations": 4, + "request_identity": "sha256:85ed7a33760d4014441cca0f4d9f93ee956785766943c1ff062d0b99bf832413", + "schema": "mnel-specialist-decision/0.1", + "semantics": "bounded-recurrent-structured-decision; not-a-verdict", + "source_observation_identities": [] + }, + { + "abstained": true, + "authority": "diagnostic-only", + "calibration_identity": "sha256:ed45dd8137189986ff154df38594c3ee8f92fcde2db18c3c159db1ebd9e7740b", + "confidence": 1.0, + "confidence_milli": 1000, + "context_state_identity": "sha256:5dbed64b4b6845121d18e0ded5669c5c2b95311b7a5965623317815bdf859236", + "decision": "ABSTAIN", + "decision_identity": "sha256:39a2de24ba04a0c4e22a6a8bab3757b407957996167dc2aa7d107d1a6278be8a", + "elapsed_ns": 56254, + "escalation_reason": "out-of-distribution-distance", + "generation_identity": "sha256:8056daf8a283b378999995dc96f53cb03b3bea3440adcc279bfab1cf79b2ac27", + "halting_reason": "budget-exhausted", + "hidden_state": [ + 599, + 500, + 798, + 848 + ], + "input_features": [ + 500, + 500, + 500, + 500 + ], + "lineage_identity": null, + "model_identity": "sha256:d605a9ee3bf59ad31eac8d712d94a244decbd7455cf176fa70b0054ecebce44a", + "operating_envelope_identity": "sha256:9a6a22dbdc10856fc7e480810f8e2912b59d1bb8be15b8b0082a68fffd942bbd", + "operations": 65, + "reasoning_iterations": 4, + "request_identity": "sha256:d40ced6376561f9682d3e504e995d95d2882c6bc86d28ba6ffe88cea853c00c2", + "schema": "mnel-specialist-decision/0.1", + "semantics": "bounded-recurrent-structured-decision; not-a-verdict", + "source_observation_identities": [] + } + ], + "deterministic_decision_digest": "sha256:c6fad6db7e2949b34a70ed4d5b54093c4135b246493a94b6b7cb29019ae932bf", + "expected": [ + "git", + "testing", + "ABSTAIN" + ], + "iterations": [ + 4, + 4, + 4 + ], + "latency_ns": [ + 58230, + 55871, + 56254 + ], + "operations": [ + 65, + 65, + 65 + ], + "recurrent_correct_known": 2 + }, + "forge": { + "abstentions": 1, + "baseline_correct_known": 2, + "cases": 3, + "decisions": [ + { + "abstained": false, + "authority": "diagnostic-only", + "calibration_identity": "sha256:3a9c61b28bc33a51e5ae1ac50029830d48b8d310c6a539fc874e378ab6540d05", + "confidence": 1.0, + "confidence_milli": 1000, + "context_state_identity": "sha256:c1167d46dbfd0fe5957a883da1359643df58f8b53e220a037b85f78ac7aaf4e8", + "decision": "relevant", + "decision_identity": "sha256:44332ec7a83fbb965c81fffa2bcf4afcfff9568d62dce811b46626b37023f732", + "elapsed_ns": 77886, + "escalation_reason": null, + "generation_identity": "sha256:2767857f27a108bee26d88acb05597c2233500dd81e69c89ac621d423c4917d4", + "halting_reason": "budget-exhausted", + "hidden_state": [ + 859, + 789, + 729, + 839 + ], + "input_features": [ + 760, + 700, + 660, + 720 + ], + "lineage_identity": null, + "model_identity": "sha256:b3c8ba2bd9672b2ea652a92d1fd81c4c2f15db76c59a4ee5ff5fb90b72d94f62", + "operating_envelope_identity": "sha256:7a7248d840ac9b048935ebdb6b34e5188526f4dea78d850357ed122d18d78a7e", + "operations": 65, + "reasoning_iterations": 4, + "request_identity": "sha256:d42a50300dec7eec97211932f9452972f1582a161897a958168e7632f124321c", + "schema": "mnel-specialist-decision/0.1", + "semantics": "bounded-recurrent-structured-decision; not-a-verdict", + "source_observation_identities": [] + }, + { + "abstained": false, + "authority": "diagnostic-only", + "calibration_identity": "sha256:3a9c61b28bc33a51e5ae1ac50029830d48b8d310c6a539fc874e378ab6540d05", + "confidence": 1.0, + "confidence_milli": 1000, + "context_state_identity": "sha256:c1167d46dbfd0fe5957a883da1359643df58f8b53e220a037b85f78ac7aaf4e8", + "decision": "irrelevant", + "decision_identity": "sha256:4df5cd6e9dc9d6ef696b545bc21912cf784d7330e8c86027251eb273f0c2c675", + "elapsed_ns": 55479, + "escalation_reason": null, + "generation_identity": "sha256:2767857f27a108bee26d88acb05597c2233500dd81e69c89ac621d423c4917d4", + "halting_reason": "converged-mask-deactivated", + "hidden_state": [ + 169, + 150, + 169, + 130 + ], + "input_features": [ + 160, + 220, + 120, + 180 + ], + "lineage_identity": null, + "model_identity": "sha256:b3c8ba2bd9672b2ea652a92d1fd81c4c2f15db76c59a4ee5ff5fb90b72d94f62", + "operating_envelope_identity": "sha256:7a7248d840ac9b048935ebdb6b34e5188526f4dea78d850357ed122d18d78a7e", + "operations": 65, + "reasoning_iterations": 4, + "request_identity": "sha256:757077addc795638681635a11e3f1eb6e2e4f64879c700257eb7cec04fad4db4", + "schema": "mnel-specialist-decision/0.1", + "semantics": "bounded-recurrent-structured-decision; not-a-verdict", + "source_observation_identities": [] + }, + { + "abstained": true, + "authority": "diagnostic-only", + "calibration_identity": "sha256:3a9c61b28bc33a51e5ae1ac50029830d48b8d310c6a539fc874e378ab6540d05", + "confidence": 1.0, + "confidence_milli": 1000, + "context_state_identity": "sha256:c1167d46dbfd0fe5957a883da1359643df58f8b53e220a037b85f78ac7aaf4e8", + "decision": "ABSTAIN", + "decision_identity": "sha256:9385d413da22eed7c647070e824abbf12090152f903baf92406b127eb9ee5dfa", + "elapsed_ns": 50423, + "escalation_reason": "out-of-distribution-distance", + "generation_identity": "sha256:2767857f27a108bee26d88acb05597c2233500dd81e69c89ac621d423c4917d4", + "halting_reason": "budget-exhausted", + "hidden_state": [ + 173, + 145, + 173, + 125 + ], + "input_features": [ + 1000, + -1000, + 1000, + -1000 + ], + "lineage_identity": null, + "model_identity": "sha256:b3c8ba2bd9672b2ea652a92d1fd81c4c2f15db76c59a4ee5ff5fb90b72d94f62", + "operating_envelope_identity": "sha256:7a7248d840ac9b048935ebdb6b34e5188526f4dea78d850357ed122d18d78a7e", + "operations": 65, + "reasoning_iterations": 4, + "request_identity": "sha256:17b76bcf1254d593ffe42a119914d4e86480ce993584b16fa7fd8e87aa832595", + "schema": "mnel-specialist-decision/0.1", + "semantics": "bounded-recurrent-structured-decision; not-a-verdict", + "source_observation_identities": [] + } + ], + "deterministic_decision_digest": "sha256:fe478ac25572f62f2ddef3f0f8daed840a589123deb0b7acb4af20d3c6fb7ca8", + "expected": [ + "relevant", + "irrelevant", + "ABSTAIN" + ], + "iterations": [ + 4, + 4, + 4 + ], + "latency_ns": [ + 77886, + 55479, + 50423 + ], + "operations": [ + 65, + 65, + 65 + ], + "recurrent_correct_known": 2 + } + }, + "limitations": [ + "convergence is a diagnostic halting signal, not a correctness proof", + "synthetic held-out data does not establish production utility", + "latency is host-dependent and is retained as a measurement, not an identity", + "the specialist cannot verify evidence, grant permissions, or promote generations" + ], + "models": { + "control": { + "artifact_identity": "sha256:6a0327d9a8ef8b399c8771ae3fe5443bf797e97f6e809280793bee55246990ac", + "artifact_path": "examples/recurrent-specialists/control-specialist-g0.json", + "calibration_identity": "sha256:ed45dd8137189986ff154df38594c3ee8f92fcde2db18c3c159db1ebd9e7740b", + "checkpoint_identity": "sha256:7411beef4eab8ac1d6d0c86c7fe9ec5f2e139b22b38aa67d39beda3ca1051d82", + "generation_identity": "sha256:8056daf8a283b378999995dc96f53cb03b3bea3440adcc279bfab1cf79b2ac27", + "model_identity": "sha256:d605a9ee3bf59ad31eac8d712d94a244decbd7455cf176fa70b0054ecebce44a", + "model_size_bytes": 2068, + "operating_envelope_identity": "sha256:9a6a22dbdc10856fc7e480810f8e2912b59d1bb8be15b8b0082a68fffd942bbd", + "provider_abi": "mnel-specialist-provider-abi/0.1", + "reload_equivalent": true, + "target_role": "control.tool-family-routing", + "training_dataset_identity": "sha256:1e41109b307452405f2b97082ead6e9604f6841636e37a255d9cba1cb5df5e76", + "training_spec_identity": "sha256:fbe653836dcaa301da274b9fc1d8d203ea4ca67dff9426510054aa73d57921f9" + }, + "forge": { + "artifact_identity": "sha256:402e249af9d4b0fe991a621aa1230dd570569730d3859406b77a757bd70c3ee0", + "artifact_path": "examples/recurrent-specialists/forge-specialist-g0.json", + "calibration_identity": "sha256:3a9c61b28bc33a51e5ae1ac50029830d48b8d310c6a539fc874e378ab6540d05", + "checkpoint_identity": "sha256:92a9795d8d45d9dd1146efcfc1efff9da98524422f194a077c5ad08ccb027c1b", + "generation_identity": "sha256:2767857f27a108bee26d88acb05597c2233500dd81e69c89ac621d423c4917d4", + "model_identity": "sha256:b3c8ba2bd9672b2ea652a92d1fd81c4c2f15db76c59a4ee5ff5fb90b72d94f62", + "model_size_bytes": 2047, + "operating_envelope_identity": "sha256:7a7248d840ac9b048935ebdb6b34e5188526f4dea78d850357ed122d18d78a7e", + "provider_abi": "mnel-specialist-provider-abi/0.1", + "reload_equivalent": true, + "target_role": "forge.evidence-relevance", + "training_dataset_identity": "sha256:1cec040071b9fb21036bcf75403b17766d14094a4418e4c5835fd29e82140c3e", + "training_spec_identity": "sha256:c9d1736ae21cc36285aaecc34b41f656bfc2edab61e99b0a59c93ea37db525ba" + } + }, + "schema": "mnel-recurrent-specialist-reference-evidence/0.1", + "semantics": "bounded-observation; diagnostic-only; not-a-verdict", + "study_identity": "sha256:1897c2016a7c7bf017d48a3dacf88a3fc016bc4336294db1635eb3fc4be2e72b" +} diff --git a/mncs/corpora/mnel-recurrent-specialist-reference.json b/mncs/corpora/mnel-recurrent-specialist-reference.json new file mode 100644 index 0000000..0870737 --- /dev/null +++ b/mncs/corpora/mnel-recurrent-specialist-reference.json @@ -0,0 +1,18 @@ +{ + "schema_version": "0.2", + "name": "mnel-recurrent-specialist-reference", + "cases": [ + { + "id": "recurrent-vector-refinement", + "request": {"schema_version": "0.1", "target": {"module": "mnel.recurrent_specialist", "function": "bounded_recurrent_decision"}, "arguments": [{"integer": {"value": 1, "type": {"bits": 32, "signed": true}}}, {"integer": {"value": 2, "type": {"bits": 32, "signed": true}}}, {"integer": {"value": 3, "type": {"bits": 32, "signed": true}}}, {"integer": {"value": 4, "type": {"bits": 32, "signed": true}}}, {"integer": {"value": 10, "type": {"bits": 32, "signed": true}}}, {"integer": {"value": 20, "type": {"bits": 32, "signed": true}}}, {"integer": {"value": 30, "type": {"bits": 32, "signed": true}}}, {"integer": {"value": 40, "type": {"bits": 32, "signed": true}}}], "step_budget": 20000}, + "expected_status": "returned", + "expected": [{"integer": {"value": 100, "type": {"bits": 32, "signed": true}}}] + }, + { + "id": "recurrent-zero-delta-mask", + "request": {"schema_version": "0.1", "target": {"module": "mnel.recurrent_specialist", "function": "bounded_recurrent_decision"}, "arguments": [{"integer": {"value": 9, "type": {"bits": 32, "signed": true}}}, {"integer": {"value": 8, "type": {"bits": 32, "signed": true}}}, {"integer": {"value": 7, "type": {"bits": 32, "signed": true}}}, {"integer": {"value": 6, "type": {"bits": 32, "signed": true}}}, {"integer": {"value": 9, "type": {"bits": 32, "signed": true}}}, {"integer": {"value": 8, "type": {"bits": 32, "signed": true}}}, {"integer": {"value": 7, "type": {"bits": 32, "signed": true}}}, {"integer": {"value": 6, "type": {"bits": 32, "signed": true}}}], "step_budget": 20000}, + "expected_status": "returned", + "expected": [{"integer": {"value": 30, "type": {"bits": 32, "signed": true}}}] + } + ] +} diff --git a/mncs/source/mnel/recurrent_specialist.mncs b/mncs/source/mnel/recurrent_specialist.mncs new file mode 100644 index 0000000..422aaf8 --- /dev/null +++ b/mncs/source/mnel/recurrent_specialist.mncs @@ -0,0 +1,51 @@ +mncs 0.8; + +// MNEL-native bounded recurrent specialist semantics. The Python/Rust +// runtime owns artifact serialization and measurement; this source owns the +// small semantic kernel: fixed vectors, masks, masked state selection, and a +// fixed iteration envelope. Convergence is diagnostic, never a proof. +module mnel.recurrent_specialist; + +use mncs.core.numeric.v1; + +record RecurrentState { + h0: i32, + h1: i32, + h2: i32, + h3: i32, +} + +fn masked_refine( + state: RecurrentState, + t0: i32, t1: i32, t2: i32, t3: i32 +) -> (updated: RecurrentState) { + let target: vec = vector(t0, t1, t2, t3); + let current: vec = vector(state.h0, state.h1, state.h2, state.h3); + let delta: vec = vec_sub_wrap(target, current); + let step: vec = vec_add_wrap(current, delta); + let still_active: mask<4> = vec_ne(delta, splat(0)); + let selected: vec = select(still_active, step, current); + return RecurrentState { + h0: extract_lane(selected, 0), + h1: extract_lane(selected, 1), + h2: extract_lane(selected, 2), + h3: extract_lane(selected, 3), + }; +} + +fn bounded_recurrent_decision( + q0: i32, q1: i32, q2: i32, q3: i32, + t0: i32, t1: i32, t2: i32, t3: i32 +) -> (decision: i32) { + let initial: RecurrentState = RecurrentState { + h0: q0, + h1: q1, + h2: q2, + h3: q3, + }; + iterate refinement up_to 4 carrying state: RecurrentState = initial { + next state = masked_refine(state, t0, t1, t2, t3); + } + let result: vec = vector(state.h0, state.h1, state.h2, state.h3); + return reduce_sum_wrap(result); +} diff --git a/schemas/mnel-recurrent-specialist.schema.json b/schemas/mnel-recurrent-specialist.schema.json new file mode 100644 index 0000000..d3aa43a --- /dev/null +++ b/schemas/mnel-recurrent-specialist.schema.json @@ -0,0 +1,36 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.invalid/mnel/schemas/mnel-recurrent-specialist.schema.json", + "title": "MNEL bounded recurrent specialist artifact", + "type": "object", + "additionalProperties": false, + "required": ["schema", "provider_id", "provider_abi", "target_role", "generation_identity", "architecture_identity", "training_code_identity", "training_dataset_identity", "training_spec_identity", "checkpoint_identity", "calibration_identity", "operating_envelope", "class_centroids", "training_record_ids", "source_evidence_references", "authority", "model_identity", "artifact_identity"], + "properties": { + "schema": {"const": "mnel-recurrent-specialist-artifact/0.1"}, + "provider_id": {"type": "string", "minLength": 1}, + "provider_abi": {"const": "mnel-specialist-provider-abi/0.1"}, + "target_role": {"type": "string", "minLength": 1}, + "architecture": {"type": "object"}, + "generation_identity": {"$ref": "#/$defs/identity"}, + "architecture_identity": {"$ref": "#/$defs/identity"}, + "training_code_identity": {"$ref": "#/$defs/identity"}, + "training_dataset_identity": {"$ref": "#/$defs/identity"}, + "training_spec_identity": {"$ref": "#/$defs/identity"}, + "checkpoint_identity": {"$ref": "#/$defs/identity"}, + "calibration_identity": {"$ref": "#/$defs/identity"}, + "operating_envelope": {"type": "object", "required": ["max_iterations", "minimum_confidence", "maximum_distance", "convergence_delta", "maximum_context_observations", "maximum_query_abs", "envelope_identity"]}, + "class_centroids": {"type": "object", "minProperties": 1, "additionalProperties": {"type": "array", "minItems": 4, "maxItems": 4, "items": {"type": "integer"}}}, + "training_record_ids": {"type": "array", "minItems": 1, "items": {"type": "string", "minLength": 1}}, + "source_evidence_references": {"type": "array", "items": {"type": "string", "minLength": 1}}, + "parent_model_identity": {"anyOf": [{"$ref": "#/$defs/identity"}, {"type": "null"}]}, + "negative_memory": {"type": "array", "items": {"type": "string"}}, + "inherited_strategies": {"type": "array", "items": {"type": "string"}}, + "known_counterexamples": {"type": "array", "items": {"type": "string"}}, + "prior_failure_causes": {"type": "array", "items": {"type": "string"}}, + "authority": {"const": "diagnostic-only"}, + "semantics": {"type": "string", "minLength": 1}, + "model_identity": {"$ref": "#/$defs/identity"}, + "artifact_identity": {"$ref": "#/$defs/identity"} + }, + "$defs": {"identity": {"type": "string", "pattern": "^sha256:[0-9a-f]{64}$"}} +} diff --git a/schemas/mnel-specialist-decision.schema.json b/schemas/mnel-specialist-decision.schema.json new file mode 100644 index 0000000..e6f5666 --- /dev/null +++ b/schemas/mnel-specialist-decision.schema.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.invalid/mnel/schemas/mnel-specialist-decision.schema.json", + "title": "MNEL bounded specialist decision", + "type": "object", + "required": ["schema", "request_identity", "context_state_identity", "reasoning_iterations", "decision", "confidence", "abstained", "halting_reason", "model_identity", "generation_identity", "calibration_identity", "operating_envelope_identity", "source_observation_identities", "authority", "decision_identity"], + "properties": { + "schema": {"const": "mnel-specialist-decision/0.1"}, + "request_identity": {"$ref": "#/$defs/identity"}, + "context_state_identity": {"$ref": "#/$defs/identity"}, + "reasoning_iterations": {"type": "integer", "minimum": 1, "maximum": 8}, + "decision": {"type": "string", "minLength": 1}, + "confidence": {"type": "number", "minimum": 0, "maximum": 1}, + "abstained": {"type": "boolean"}, + "escalation_reason": {"type": ["string", "null"]}, + "halting_reason": {"type": "string", "minLength": 1}, + "model_identity": {"$ref": "#/$defs/identity"}, + "generation_identity": {"$ref": "#/$defs/identity"}, + "calibration_identity": {"$ref": "#/$defs/identity"}, + "operating_envelope_identity": {"$ref": "#/$defs/identity"}, + "source_observation_identities": {"type": "array", "items": {"type": "string", "minLength": 1}}, + "authority": {"const": "diagnostic-only"}, + "decision_identity": {"$ref": "#/$defs/identity"} + }, + "$defs": {"identity": {"type": "string", "pattern": "^sha256:[0-9a-f]{64}$"}} +} diff --git a/src/mnel/__init__.py b/src/mnel/__init__.py index 19d9f4a..26ea896 100644 --- a/src/mnel/__init__.py +++ b/src/mnel/__init__.py @@ -14,13 +14,6 @@ LearnedProviderQuery, LearnedProviderRegistry, ) -from .provider_runtime import ( - ExecutionTier, - ImplementationLanguage, - NativeLanguageException, - ProviderRuntimeManifest, - load_runtime_manifest, -) from .placement import ( AcceleratorDiagnostics, ExecutionDevice, @@ -32,32 +25,63 @@ Precision, decide_placement, ) +from .provider_runtime import ( + ExecutionTier, + ImplementationLanguage, + NativeLanguageException, + ProviderRuntimeManifest, + load_runtime_manifest, +) +from .recurrent_specialist import ( + CalibrationRecord, + OperatingEnvelope, + RecurrentSpecialistModel, + SpecialistContextState, + SpecialistDecision, + SpecialistError, + build_reference_artifacts, + calibrate_recurrent_specialist, + context_update, + infer_batch, + train_recurrent_specialist, +) __all__ = [ "DEFAULT_LEARNED_PROVIDER_REGISTRY", - "EvidenceLedger", "AcceleratorDiagnostics", + "CalibrationRecord", + "EvidenceLedger", + "ExecutionDevice", + "ExecutionMode", "ExecutionTier", "HardGateEvaluator", "ImplementationLanguage", - "ExecutionDevice", - "ExecutionMode", "LearnedProviderDeclaration", "LearnedProviderObservation", "LearnedProviderQuery", "LearnedProviderRegistry", "NativeLanguageException", "OffloadMode", + "OperatingEnvelope", "PlacementCapabilities", "PlacementDecision", "PlacementPolicy", "Precision", "ProviderRuntimeManifest", + "RecurrentSpecialistModel", "RecursionGovernor", + "SpecialistContextState", + "SpecialistDecision", + "SpecialistError", "VerifiedExperienceDistiller", + "build_reference_artifacts", + "calibrate_recurrent_specialist", "canonical_digest", + "context_update", "decide_placement", + "infer_batch", "load_runtime_manifest", + "train_recurrent_specialist", ] __version__ = "0.1.0a0" diff --git a/src/mnel/cli.py b/src/mnel/cli.py index 85e5432..1ef4ba7 100644 --- a/src/mnel/cli.py +++ b/src/mnel/cli.py @@ -12,10 +12,9 @@ from . import __version__ from .core import EvidenceLedger, run_reference_study from .distillation import run_reference_distill_study -from .forge_lifecycle import run_reference_forge_study -from .provider_study import run_reference_portfolio_study -from .family_integration import run_reference_family_integration from .fabric_execution import run_network_fabric, run_reference_fabric_study +from .family_integration import run_reference_family_integration +from .forge_lifecycle import run_reference_forge_study from .investigators import DEFAULT_ROLE_CONTRACTS from .learned_providers import ( DEFAULT_LEARNED_PROVIDER_REGISTRY, @@ -23,6 +22,8 @@ LearnedProviderQuery, OutputKind, ) +from .provider_study import run_reference_portfolio_study +from .recurrent_specialist import build_reference_artifacts def parser() -> argparse.ArgumentParser: @@ -85,6 +86,11 @@ def parser() -> argparse.ArgumentParser: description="Run the deterministic distributed MNEL/Fabric reference study", ) fabric_reference.add_argument("--workspace", default=None) + recurrent_reference = commands.add_parser( + "recurrent-specialist-reference", + description="Train and measure the bounded recurrent specialist reference artifacts", + ) + recurrent_reference.add_argument("--workspace", default="examples/recurrent-specialists") fabric_run = commands.add_parser( "fabric-run", description="Dispatch an operator-supplied fixed-argv plan through remote Fabric", @@ -174,6 +180,9 @@ def main(argv: list[str] | None = None) -> int: if args.command == "fabric-reference": print(json.dumps(run_reference_fabric_study(args.workspace), indent=2, sort_keys=True)) return 0 + if args.command == "recurrent-specialist-reference": + print(json.dumps(build_reference_artifacts(args.workspace), indent=2, sort_keys=True)) + return 0 if args.command == "fabric-run": try: result = run_network_fabric( diff --git a/src/mnel/recurrent_provider.py b/src/mnel/recurrent_provider.py new file mode 100644 index 0000000..019ff6f --- /dev/null +++ b/src/mnel/recurrent_provider.py @@ -0,0 +1,109 @@ +"""Bounded stdin/stdout provider boundary for the recurrent specialist.""" + +from __future__ import annotations + +import json +import sys +from typing import Any + +from .core import canonical_digest +from .recurrent_specialist import ( + AUTHORITY, + PROTOCOL_VERSION, + RecurrentSpecialistModel, + SpecialistError, + infer_batch, +) + +MAX_REQUEST_BYTES = 256 * 1024 +MAX_RESPONSE_BYTES = 512 * 1024 + + +def capabilities(request_id: str | None = None) -> dict[str, Any]: + value: dict[str, Any] = { + "protocol_version": PROTOCOL_VERSION, + "type": "capabilities", + "provider": { + "id": "mnel-bounded-recurrent-specialist", + "identity": "mnel-bounded-recurrent-specialist-provider-v1", + "version": "0.1", + }, + "analyses": ["bounded_recurrent_inference", "structured_abstention", "context_state_update"], + "statuses": ["PASS", "FAIL", "UNKNOWN"], + "cancellation": False, + "health_checks": True, + "extensions": { + "provider_abi": "mnel-specialist-provider-abi/0.1", + "supported_constructs": ["identity-bound-artifact", "persistent-context-state", "masked-recurrent-update", "explicit-budget"], + "unsupported_constructs": ["verdict", "permission", "promotion", "credentials", "general-language-generation"], + "limitations": ["diagnostic-only structured proposals; no authority is created"], + }, + } + if request_id is not None: + value["request_id"] = request_id + return value + + +def handle_request(request: Any) -> dict[str, Any]: + if not isinstance(request, dict): + raise SpecialistError("request must be an object") + if request.get("protocol_version") != PROTOCOL_VERSION: + raise SpecialistError("unsupported specialist protocol version") + if request.get("type") == "capabilities": + return capabilities(request.get("request_id")) + if request.get("type") != "infer": + raise SpecialistError("request type must be capabilities or infer") + request_id = request.get("request_id") + if not isinstance(request_id, str) or not request_id.strip(): + raise SpecialistError("request_id is required") + artifact = request.get("artifact") + model = RecurrentSpecialistModel.load(artifact if isinstance(artifact, dict) else json.dumps(artifact).encode("utf-8")) + queries = request.get("queries") + if not isinstance(queries, list): + raise SpecialistError("queries must be a bounded array") + context_observations = request.get("context_observations", []) + if not isinstance(context_observations, list): + raise SpecialistError("context_observations must be an array") + results = infer_batch( + model, + queries, + context_observations=context_observations, + max_iterations=request.get("max_iterations"), + lineage_identity=request.get("lineage_identity"), + ) + value = { + "protocol_version": PROTOCOL_VERSION, + "type": "inference_response", + "request_id": request_id, + "provider": capabilities()["provider"], + "provider_abi": model.provider_abi, + "model_identity": model.model_identity, + "generation_identity": model.generation_identity, + "target_role": model.target_role, + "results": [result.to_dict() for result in results], + "authority": AUTHORITY, + "semantics": "bounded-recurrent-structured-decisions; not-a-verdict", + } + value["response_identity"] = canonical_digest(value) + encoded = json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode("utf-8") + if len(encoded) > MAX_RESPONSE_BYTES: + raise SpecialistError("specialist response exceeds its output bound") + return value + + +def main() -> int: + for line in sys.stdin: + if len(line.encode("utf-8")) > MAX_REQUEST_BYTES: + response = {"status": "UNKNOWN", "error": "request exceeds byte bound", "authority": AUTHORITY} + else: + try: + response = handle_request(json.loads(line)) + except (SpecialistError, json.JSONDecodeError) as error: + response = {"status": "UNKNOWN", "error": str(error), "authority": AUTHORITY} + sys.stdout.write(json.dumps(response, ensure_ascii=False, sort_keys=True) + "\n") + sys.stdout.flush() + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/mnel/recurrent_specialist.py b/src/mnel/recurrent_specialist.py new file mode 100644 index 0000000..64fc955 --- /dev/null +++ b/src/mnel/recurrent_specialist.py @@ -0,0 +1,821 @@ +"""A tiny, bounded recurrent specialist and its evidence-bearing artifacts. + +This module is deliberately smaller than a general model runtime. It trains a +role-specific nearest-centroid model, then uses a fixed-point recurrent update +to refine a per-query reasoning state towards the closest class prototype. +Persistent context state is an immutable, separately identified summary of +bounded observations. It is not evidence and it is never allowed to change +the diagnostic-only authority boundary. +""" + +from __future__ import annotations + +import hashlib +import json +import time +from collections.abc import Iterable, Mapping, Sequence +from dataclasses import dataclass, replace +from pathlib import Path +from typing import Any + +from .core import canonical_digest, canonical_json + +SCHEMA_VERSION = "mnel-recurrent-specialist-artifact/0.1" +DECISION_SCHEMA_VERSION = "mnel-specialist-decision/0.1" +PROTOCOL_VERSION = "mnel-recurrent-specialist-provider/0.1" +PROVIDER_ABI = "mnel-specialist-provider-abi/0.1" +AUTHORITY = "diagnostic-only" +DIMENSIONS = 4 +DEFAULT_MAX_ITERATIONS = 4 +MAX_CONTEXT_OBSERVATIONS = 32 +MAX_BATCH = 128 + + +class SpecialistError(ValueError): + """A malformed artifact, query, context, or bounded invocation.""" + + +def _identity(value: object, label: str) -> str: + if not isinstance(value, str) or not value.startswith("sha256:") or len(value) != 71: + raise SpecialistError(f"{label} must be a sha256 identity") + return value + + +def _features(value: object, label: str) -> tuple[int, ...]: + if not isinstance(value, (list, tuple)) or len(value) != DIMENSIONS: + raise SpecialistError(f"{label} must contain exactly {DIMENSIONS} lanes") + result = tuple(value) + if any(not isinstance(item, int) or isinstance(item, bool) or not -1000 <= item <= 1000 for item in result): + raise SpecialistError(f"{label} contains an invalid lane") + return result + + +def _bounded_text(value: object, label: str, maximum: int = 256) -> str: + if not isinstance(value, str) or not value.strip() or len(value) > maximum: + raise SpecialistError(f"{label} must be a bounded non-empty string") + return value + + +def _reject_authority(value: object) -> None: + forbidden = { + "verdict", + "evaluator_verdict", + "promotion", + "promotion_authorized", + "evaluator_eligible", + "conformance", + "permission", + "credentials", + "trust", + } + if isinstance(value, Mapping): + for key, child in value.items(): + if str(key).lower() in forbidden: + raise SpecialistError(f"specialist payload contains authority field: {key}") + _reject_authority(child) + elif isinstance(value, (list, tuple)): + for child in value: + _reject_authority(child) + + +@dataclass(frozen=True, slots=True) +class SpecialistContextState: + """Persistent derived state, separate from per-query reasoning state.""" + + provider_identity: str + generation_identity: str + role_identity: str + source_observation_identities: tuple[str, ...] + feature_mean: tuple[int, ...] + update_identity: str = "" + state_identity: str = "" + + def __post_init__(self) -> None: + _bounded_text(self.provider_identity, "provider_identity") + _identity(self.generation_identity, "generation_identity") + _bounded_text(self.role_identity, "role_identity") + if len(self.source_observation_identities) > MAX_CONTEXT_OBSERVATIONS: + raise SpecialistError("context exceeds its observation bound") + if any(not item.strip() for item in self.source_observation_identities): + raise SpecialistError("context observation identities must be non-empty") + _features(self.feature_mean, "feature_mean") + if self.update_identity and self.update_identity != self.content_identity: + raise SpecialistError("context update identity does not match content") + if self.state_identity and self.state_identity != self.content_identity: + raise SpecialistError("context state identity does not match content") + + @property + def content_identity(self) -> str: + return canonical_digest(self.to_dict(include_identity=False)) + + def to_dict(self, *, include_identity: bool = True) -> dict[str, Any]: + value: dict[str, Any] = { + "schema": "mnel-specialist-context-state/0.1", + "provider_identity": self.provider_identity, + "generation_identity": self.generation_identity, + "role_identity": self.role_identity, + "source_observation_identities": list(self.source_observation_identities), + "feature_mean": list(self.feature_mean), + "authority": AUTHORITY, + "semantics": "derived-context-state; traceable-to-observations; not-evidence", + } + if include_identity: + value["update_identity"] = self.update_identity or self.content_identity + value["state_identity"] = self.state_identity or self.content_identity + return value + + +def empty_context(model: RecurrentSpecialistModel) -> SpecialistContextState: + return SpecialistContextState( + provider_identity=model.provider_id, + generation_identity=model.generation_identity, + role_identity=model.target_role, + source_observation_identities=(), + feature_mean=(0, 0, 0, 0), + ) + + +def context_update( + context: SpecialistContextState, + observation_identity: str, + observation_features: Sequence[int], +) -> SpecialistContextState: + """Add one bounded observation to persistent context deterministically.""" + + if len(context.source_observation_identities) >= MAX_CONTEXT_OBSERVATIONS: + raise SpecialistError("context update exceeds its observation bound") + identity = _bounded_text(observation_identity, "observation_identity", 256) + features = _features(observation_features, "observation_features") + count = len(context.source_observation_identities) + mean = tuple((context.feature_mean[index] * count + features[index]) // (count + 1) for index in range(DIMENSIONS)) + next_context = replace( + context, + source_observation_identities=(*context.source_observation_identities, identity), + feature_mean=mean, + ) + return replace(next_context, update_identity=next_context.content_identity, state_identity=next_context.content_identity) + + +@dataclass(frozen=True, slots=True) +class OperatingEnvelope: + max_iterations: int = DEFAULT_MAX_ITERATIONS + minimum_confidence: int = 600 + maximum_distance: int = 700 + convergence_delta: int = 2 + maximum_context_observations: int = MAX_CONTEXT_OBSERVATIONS + maximum_query_abs: int = 1000 + envelope_identity: str = "" + + def __post_init__(self) -> None: + if not 1 <= self.max_iterations <= 8: + raise SpecialistError("max_iterations must be between 1 and 8") + if not 0 <= self.minimum_confidence <= 1000: + raise SpecialistError("minimum_confidence must be within [0, 1000]") + if self.maximum_distance < 1 or self.convergence_delta < 0: + raise SpecialistError("distance and convergence bounds are invalid") + if not 1 <= self.maximum_context_observations <= MAX_CONTEXT_OBSERVATIONS: + raise SpecialistError("maximum_context_observations exceeds the context bound") + if not 1 <= self.maximum_query_abs <= 1000: + raise SpecialistError("maximum_query_abs is invalid") + if self.envelope_identity and self.envelope_identity != self.content_identity: + raise SpecialistError("operating envelope identity does not match content") + + @property + def content_identity(self) -> str: + return canonical_digest(self.to_dict(include_identity=False)) + + def to_dict(self, *, include_identity: bool = True) -> dict[str, Any]: + value = { + "max_iterations": self.max_iterations, + "minimum_confidence": self.minimum_confidence, + "maximum_distance": self.maximum_distance, + "convergence_delta": self.convergence_delta, + "maximum_context_observations": self.maximum_context_observations, + "maximum_query_abs": self.maximum_query_abs, + } + if include_identity: + value["envelope_identity"] = self.envelope_identity or self.content_identity + return value + + +@dataclass(frozen=True, slots=True) +class CalibrationRecord: + role_identity: str + calibration_dataset_identity: str + minimum_confidence: int + maximum_distance: int + method: str = "bounded-heldout-distance-margin" + calibration_identity: str = "" + + def __post_init__(self) -> None: + _bounded_text(self.role_identity, "role_identity") + _identity(self.calibration_dataset_identity, "calibration_dataset_identity") + if not 0 <= self.minimum_confidence <= 1000 or self.maximum_distance < 1: + raise SpecialistError("calibration thresholds are invalid") + if self.calibration_identity and self.calibration_identity != self.content_identity: + raise SpecialistError("calibration identity does not match content") + + @property + def content_identity(self) -> str: + return canonical_digest(self.to_dict(include_identity=False)) + + def to_dict(self, *, include_identity: bool = True) -> dict[str, Any]: + value = { + "schema": "mnel-specialist-calibration/0.1", + "role_identity": self.role_identity, + "calibration_dataset_identity": self.calibration_dataset_identity, + "minimum_confidence": self.minimum_confidence, + "maximum_distance": self.maximum_distance, + "method": self.method, + "authority": AUTHORITY, + "semantics": "calibration-observation; not-a-verdict", + } + if include_identity: + value["calibration_identity"] = self.calibration_identity or self.content_identity + return value + + +@dataclass(frozen=True, slots=True) +class SpecialistDecision: + request_identity: str + context_state_identity: str + reasoning_iterations: int + decision: str + confidence: int + abstained: bool + escalation_reason: str | None + halting_reason: str + model_identity: str + generation_identity: str + calibration_identity: str + operating_envelope_identity: str + input_features: tuple[int, ...] + hidden_state: tuple[int, ...] + operations: int + elapsed_ns: int + source_observation_identities: tuple[str, ...] = () + lineage_identity: str | None = None + authority: str = AUTHORITY + decision_identity: str = "" + + def __post_init__(self) -> None: + _identity(self.request_identity, "request_identity") + _identity(self.context_state_identity, "context_state_identity") + _identity(self.model_identity, "model_identity") + _identity(self.generation_identity, "generation_identity") + _identity(self.calibration_identity, "calibration_identity") + _identity(self.operating_envelope_identity, "operating_envelope_identity") + _features(self.input_features, "input_features") + _features(self.hidden_state, "hidden_state") + if not 0 <= self.confidence <= 1000 or self.reasoning_iterations < 1 or self.operations < 1 or self.elapsed_ns < 0: + raise SpecialistError("decision measurements are invalid") + if self.authority != AUTHORITY: + raise SpecialistError("specialist decisions are diagnostic-only") + if self.abstained != (self.decision == "ABSTAIN"): + raise SpecialistError("abstention and decision disagree") + if not self.abstained and self.escalation_reason is not None: + raise SpecialistError("non-abstaining decisions cannot carry escalation reason") + if self.decision_identity and self.decision_identity != self.content_identity: + raise SpecialistError("decision identity does not match content") + + @property + def content_identity(self) -> str: + value = self.to_dict(include_identity=False) + # Wall-clock latency is an observation, not semantic decision content. + value.pop("elapsed_ns", None) + return canonical_digest(value) + + def to_dict(self, *, include_identity: bool = True) -> dict[str, Any]: + value: dict[str, Any] = { + "schema": DECISION_SCHEMA_VERSION, + "request_identity": self.request_identity, + "context_state_identity": self.context_state_identity, + "reasoning_iterations": self.reasoning_iterations, + "decision": self.decision, + "confidence": self.confidence / 1000, + "confidence_milli": self.confidence, + "abstained": self.abstained, + "escalation_reason": self.escalation_reason, + "halting_reason": self.halting_reason, + "model_identity": self.model_identity, + "generation_identity": self.generation_identity, + "calibration_identity": self.calibration_identity, + "operating_envelope_identity": self.operating_envelope_identity, + "input_features": list(self.input_features), + "hidden_state": list(self.hidden_state), + "operations": self.operations, + "elapsed_ns": self.elapsed_ns, + "source_observation_identities": list(self.source_observation_identities), + "lineage_identity": self.lineage_identity, + "authority": self.authority, + "semantics": "bounded-recurrent-structured-decision; not-a-verdict", + } + if include_identity: + value["decision_identity"] = self.decision_identity or self.content_identity + return value + + +@dataclass(frozen=True, slots=True) +class RecurrentSpecialistModel: + target_role: str + generation_identity: str + architecture_identity: str + training_code_identity: str + training_dataset_identity: str + training_spec_identity: str + checkpoint_identity: str + calibration_identity: str + operating_envelope: OperatingEnvelope + class_centroids: Mapping[str, tuple[int, ...]] + training_record_ids: tuple[str, ...] + source_evidence_references: tuple[str, ...] + parent_model_identity: str | None = None + negative_memory: tuple[str, ...] = () + inherited_strategies: tuple[str, ...] = () + known_counterexamples: tuple[str, ...] = () + prior_failure_causes: tuple[str, ...] = () + model_identity: str = "" + artifact_identity: str = "" + provider_id: str = "mnel-bounded-recurrent-specialist/0.1" + provider_abi: str = PROVIDER_ABI + authority: str = AUTHORITY + + def __post_init__(self) -> None: + _bounded_text(self.target_role, "target_role") + for name in ( + "generation_identity", + "architecture_identity", + "training_code_identity", + "training_dataset_identity", + "training_spec_identity", + "checkpoint_identity", + "calibration_identity", + ): + _identity(getattr(self, name), name) + if self.parent_model_identity is not None: + _identity(self.parent_model_identity, "parent_model_identity") + if not self.class_centroids or len(self.class_centroids) > 16: + raise SpecialistError("model must contain between one and sixteen classes") + for label, centroid in self.class_centroids.items(): + _bounded_text(label, "class label", 128) + _features(centroid, f"centroid[{label}]") + if not self.training_record_ids or any(not item.strip() for item in self.training_record_ids): + raise SpecialistError("training record identities are required") + if self.authority != AUTHORITY or self.provider_abi != PROVIDER_ABI: + raise SpecialistError("model authority or provider ABI is invalid") + if self.model_identity and self.model_identity != self.content_identity: + raise SpecialistError("model identity does not match content") + + @property + def content_identity(self) -> str: + return canonical_digest(self.to_dict(include_identity=False)) + + @property + def model_size_bytes(self) -> int: + return len(canonical_json(self.to_dict())) + + def to_dict(self, *, include_identity: bool = True) -> dict[str, Any]: + value: dict[str, Any] = { + "schema": SCHEMA_VERSION, + "provider_id": self.provider_id, + "provider_abi": self.provider_abi, + "target_role": self.target_role, + "generation_identity": self.generation_identity, + "architecture_identity": self.architecture_identity, + "architecture": { + "kind": "bounded-recurrent-centroid", + "context_state": "persistent-derived-summary", + "reasoning_state": "per-query-fixed-point-lanes", + "width": DIMENSIONS, + "max_iterations": self.operating_envelope.max_iterations, + "masked_select": True, + }, + "training_code_identity": self.training_code_identity, + "training_dataset_identity": self.training_dataset_identity, + "training_spec_identity": self.training_spec_identity, + "checkpoint_identity": self.checkpoint_identity, + "calibration_identity": self.calibration_identity, + "operating_envelope": self.operating_envelope.to_dict(), + "class_centroids": {key: list(self.class_centroids[key]) for key in sorted(self.class_centroids)}, + "training_record_ids": list(self.training_record_ids), + "source_evidence_references": list(self.source_evidence_references), + "parent_model_identity": self.parent_model_identity, + "negative_memory": list(self.negative_memory), + "inherited_strategies": list(self.inherited_strategies), + "known_counterexamples": list(self.known_counterexamples), + "prior_failure_causes": list(self.prior_failure_causes), + "authority": self.authority, + "semantics": "identity-bound-learned-specialist; diagnostic-only; not-a-verdict", + } + if include_identity: + value["model_identity"] = self.model_identity or self.content_identity + return value + + def serialize(self) -> bytes: + value = self.to_dict() + value["artifact_identity"] = canonical_digest(value) + return canonical_json(value) + + @classmethod + def load(cls, payload: bytes | Mapping[str, Any]) -> RecurrentSpecialistModel: + try: + value = json.loads(payload) if isinstance(payload, bytes) else dict(payload) + except (TypeError, json.JSONDecodeError) as error: + raise SpecialistError("specialist artifact is not valid JSON") from error + if not isinstance(value, dict) or value.get("schema") != SCHEMA_VERSION: + raise SpecialistError("unsupported specialist artifact schema") + _reject_authority(value) + supplied_artifact = value.pop("artifact_identity", None) + if not isinstance(supplied_artifact, str): + raise SpecialistError("specialist artifact identity is missing") + expected_artifact = canonical_digest(value) + if supplied_artifact != expected_artifact: + raise SpecialistError("specialist artifact bytes do not match artifact identity") + envelope_value = value.get("operating_envelope") + if not isinstance(envelope_value, dict): + raise SpecialistError("operating envelope is missing") + envelope = OperatingEnvelope(**{key: envelope_value[key] for key in envelope_value if key != "envelope_identity"}, envelope_identity=envelope_value.get("envelope_identity", "")) + model = cls( + target_role=value.get("target_role"), + generation_identity=value.get("generation_identity"), + architecture_identity=value.get("architecture_identity"), + training_code_identity=value.get("training_code_identity"), + training_dataset_identity=value.get("training_dataset_identity"), + training_spec_identity=value.get("training_spec_identity"), + checkpoint_identity=value.get("checkpoint_identity"), + calibration_identity=value.get("calibration_identity"), + operating_envelope=envelope, + class_centroids={key: tuple(raw) for key, raw in value.get("class_centroids", {}).items()}, + training_record_ids=tuple(value.get("training_record_ids", ())), + source_evidence_references=tuple(value.get("source_evidence_references", ())), + parent_model_identity=value.get("parent_model_identity"), + negative_memory=tuple(value.get("negative_memory", ())), + inherited_strategies=tuple(value.get("inherited_strategies", ())), + known_counterexamples=tuple(value.get("known_counterexamples", ())), + prior_failure_causes=tuple(value.get("prior_failure_causes", ())), + model_identity=value.get("model_identity", ""), + provider_id=value.get("provider_id", ""), + provider_abi=value.get("provider_abi", ""), + authority=value.get("authority", ""), + ) + if model.model_identity != model.content_identity: + raise SpecialistError("specialist model identity is invalid") + object.__setattr__(model, "artifact_identity", supplied_artifact) + return model + + def _distance(self, left: Sequence[int], right: Sequence[int]) -> int: + return sum(abs(left[index] - right[index]) for index in range(DIMENSIONS)) + + def _encode_query(self, query: Sequence[int], context: SpecialistContextState) -> tuple[int, ...]: + # Context contributes a small, bounded prior. The query remains the + # dominant signal and the derived context can never exceed the lane envelope. + return tuple(max(-1000, min(1000, query[index] + context.feature_mean[index] // 16)) for index in range(DIMENSIONS)) + + def _confidence(self, best: int, second: int | None) -> int: + margin = (second - best) if second is not None else self.operating_envelope.maximum_distance + return max(0, min(1000, 500 + (margin * 500) // max(1, self.operating_envelope.maximum_distance))) + + def infer( + self, + query: Sequence[int], + *, + context: SpecialistContextState | None = None, + request_identity: str | None = None, + max_iterations: int | None = None, + source_observation_identities: Sequence[str] = (), + lineage_identity: str | None = None, + ) -> SpecialistDecision: + started = time.perf_counter_ns() + query_value = _features(query, "query") + if any(abs(item) > self.operating_envelope.maximum_query_abs for item in query_value): + raise SpecialistError("query exceeds operating envelope") + context_value = context or empty_context(self) + if context_value.role_identity != self.target_role or context_value.generation_identity != self.generation_identity: + raise SpecialistError("context is bound to another specialist generation or role") + if len(context_value.source_observation_identities) > self.operating_envelope.maximum_context_observations: + raise SpecialistError("context exceeds model operating envelope") + request = request_identity or canonical_digest({"query": list(query_value), "context": context_value.state_identity or context_value.content_identity}) + _identity(request, "request_identity") + budget = max_iterations if max_iterations is not None else self.operating_envelope.max_iterations + if not 1 <= budget <= self.operating_envelope.max_iterations: + raise SpecialistError("requested iteration budget exceeds operating envelope") + hidden = self._encode_query(query_value, context_value) + input_distances = sorted( + self._distance(hidden, centroid) for centroid in self.class_centroids.values() + ) + input_best_distance = input_distances[0] + active = True + iterations = 0 + operations = 1 + halting = "budget-exhausted" + for _ in range(budget): + distances = sorted((self._distance(hidden, centroid), label) for label, centroid in self.class_centroids.items()) + target = self.class_centroids[distances[0][1]] + updated = tuple(hidden[index] + (target[index] - hidden[index]) * 3 // 4 for index in range(DIMENSIONS)) + delta = self._distance(hidden, updated) + hidden = tuple(updated[index] if active else hidden[index] for index in range(DIMENSIONS)) + iterations += 1 + operations += DIMENSIONS * 4 + converged = delta <= self.operating_envelope.convergence_delta + active = active and not converged + if converged: + halting = "converged-mask-deactivated" + break + distances = sorted((self._distance(hidden, centroid), label) for label, centroid in self.class_centroids.items()) + best_distance, best_label = distances[0] + second_distance = distances[1][0] if len(distances) > 1 else None + confidence = self._confidence(best_distance, second_distance) + abstention: str | None = None + decision = best_label + if input_best_distance > self.operating_envelope.maximum_distance: + abstention = "out-of-distribution-distance" + elif confidence < self.operating_envelope.minimum_confidence: + abstention = "insufficient-calibrated-confidence" + if abstention: + decision = "ABSTAIN" + elapsed = time.perf_counter_ns() - started + result = SpecialistDecision( + request_identity=request, + context_state_identity=context_value.state_identity or context_value.content_identity, + reasoning_iterations=iterations, + decision=decision, + confidence=confidence, + abstained=bool(abstention), + escalation_reason=abstention, + halting_reason=halting, + model_identity=self.model_identity or self.content_identity, + generation_identity=self.generation_identity, + calibration_identity=self.calibration_identity, + operating_envelope_identity=self.operating_envelope.envelope_identity or self.operating_envelope.content_identity, + input_features=query_value, + hidden_state=hidden, + operations=operations, + elapsed_ns=elapsed, + source_observation_identities=tuple(source_observation_identities), + lineage_identity=lineage_identity, + ) + return replace(result, decision_identity=result.content_identity) + + +def _dataset_identity(examples: Sequence[Mapping[str, Any]]) -> str: + return canonical_digest({"examples": [dict(example) for example in examples]}) + + +def train_recurrent_specialist( + examples: Iterable[Mapping[str, Any]], + *, + target_role: str, + generation_identity: str, + parent_model_identity: str | None = None, + negative_memory: Sequence[str] = (), + inherited_strategies: Sequence[str] = (), + known_counterexamples: Sequence[str] = (), + prior_failure_causes: Sequence[str] = (), +) -> RecurrentSpecialistModel: + rows = [dict(item) for item in examples] + if not rows: + raise SpecialistError("training dataset is empty") + grouped: dict[str, list[tuple[int, ...]]] = {} + record_ids: list[str] = [] + for row in rows: + label = _bounded_text(row.get("label"), "training label", 128) + features = _features(row.get("features"), "training features") + record_id = _bounded_text(row.get("record_id"), "training record_id") + grouped.setdefault(label, []).append(features) + record_ids.append(record_id) + centroids = {label: tuple(sum(features[index] for features in values) // len(values) for index in range(DIMENSIONS)) for label, values in grouped.items()} + dataset_identity = _dataset_identity(rows) + spec = { + "schema": "mnel-recurrent-specialist-training-spec/0.1", + "target_role": target_role, + "architecture": "bounded-recurrent-centroid", + "width": DIMENSIONS, + "max_iterations": DEFAULT_MAX_ITERATIONS, + "deterministic": True, + "resource_budget": {"max_iterations": DEFAULT_MAX_ITERATIONS, "max_batch": MAX_BATCH}, + } + model = RecurrentSpecialistModel( + target_role=target_role, + generation_identity=generation_identity, + architecture_identity=canonical_digest({"architecture": "bounded-recurrent-centroid", "width": DIMENSIONS}), + training_code_identity=canonical_digest({"module": __name__, "algorithm": "integer-centroid-plus-masked-refinement", "version": "0.1"}), + training_dataset_identity=dataset_identity, + training_spec_identity=canonical_digest(spec), + checkpoint_identity=canonical_digest({"centroids": centroids, "record_ids": sorted(record_ids)}), + calibration_identity=canonical_digest({"status": "pending", "dataset": dataset_identity}), + operating_envelope=OperatingEnvelope(), + class_centroids=centroids, + training_record_ids=tuple(sorted(record_ids)), + source_evidence_references=tuple(sorted(record_ids)), + parent_model_identity=parent_model_identity, + negative_memory=tuple(negative_memory), + inherited_strategies=tuple(inherited_strategies), + known_counterexamples=tuple(known_counterexamples), + prior_failure_causes=tuple(prior_failure_causes), + ) + return replace(model, model_identity=model.content_identity) + + +def calibrate_recurrent_specialist( + model: RecurrentSpecialistModel, + examples: Iterable[Mapping[str, Any]], +) -> tuple[RecurrentSpecialistModel, CalibrationRecord]: + rows = [dict(item) for item in examples] + if not rows: + raise SpecialistError("calibration dataset is empty") + distances: list[int] = [] + margins: list[int] = [] + for row in rows: + label = _bounded_text(row.get("label"), "calibration label", 128) + if label not in model.class_centroids: + raise SpecialistError("calibration contains an unknown class") + features = _features(row.get("features"), "calibration features") + all_distances = sorted(model._distance(features, centroid) for centroid in model.class_centroids.values()) + distances.append(model._distance(features, model.class_centroids[label])) + margins.append(all_distances[1] - all_distances[0] if len(all_distances) > 1 else model.operating_envelope.maximum_distance) + max_distance = max(1, max(distances) + 80) + minimum_confidence = max(500, min(850, 500 + (min(margins) * 500) // max_distance)) + calibration = CalibrationRecord( + role_identity=model.target_role, + calibration_dataset_identity=_dataset_identity(rows), + minimum_confidence=minimum_confidence, + maximum_distance=max_distance, + ) + calibration = replace(calibration, calibration_identity=calibration.content_identity) + envelope = replace( + model.operating_envelope, + minimum_confidence=calibration.minimum_confidence, + maximum_distance=calibration.maximum_distance, + envelope_identity="", + ) + envelope = replace(envelope, envelope_identity=envelope.content_identity) + calibrated = replace(model, calibration_identity=calibration.calibration_identity, operating_envelope=envelope, model_identity="", artifact_identity="") + calibrated = replace(calibrated, model_identity=calibrated.content_identity) + return calibrated, calibration + + +def infer_batch( + model: RecurrentSpecialistModel, + queries: Sequence[Mapping[str, Any]], + *, + context_observations: Sequence[Mapping[str, Any]] = (), + max_iterations: int | None = None, + lineage_identity: str | None = None, +) -> list[SpecialistDecision]: + if len(queries) > MAX_BATCH or len(context_observations) > model.operating_envelope.maximum_context_observations: + raise SpecialistError("batch exceeds specialist bounds") + context = empty_context(model) + for observation in context_observations: + if not isinstance(observation, Mapping): + raise SpecialistError("context observation must be an object") + context = context_update(context, observation.get("observation_identity"), observation.get("features")) + results = [] + for query in queries: + if not isinstance(query, Mapping): + raise SpecialistError("query must be an object") + query_id = query.get("query_id") + request_identity = query.get("request_identity") + if request_identity is None: + request_identity = canonical_digest({"query_id": query_id, "features": query.get("features"), "context": context.state_identity or context.content_identity}) + results.append(model.infer(query.get("features"), context=context, request_identity=request_identity, max_iterations=max_iterations, source_observation_identities=(query.get("source_record_identity"),) if query.get("source_record_identity") else (), lineage_identity=lineage_identity)) + return results + + +def _reference_rows() -> tuple[dict[str, Any], ...]: + return ( + {"record_id": "forge-train-relevant-1", "features": [900, 820, 760, 880], "label": "relevant"}, + {"record_id": "forge-train-relevant-2", "features": [820, 760, 700, 800], "label": "relevant"}, + {"record_id": "forge-train-irrelevant-1", "features": [120, 180, 160, 100], "label": "irrelevant"}, + {"record_id": "forge-train-irrelevant-2", "features": [220, 120, 180, 160], "label": "irrelevant"}, + ) + + +def _reference_control_rows() -> tuple[dict[str, Any], ...]: + return ( + {"record_id": "control-train-files", "features": [900, 800, 700, 200], "label": "filesystem"}, + {"record_id": "control-train-git", "features": [800, 700, 220, 400], "label": "git"}, + {"record_id": "control-train-tests", "features": [700, 820, 600, 700], "label": "testing"}, + {"record_id": "control-train-forge", "features": [600, 500, 800, 850], "label": "forge"}, + ) + + +def build_reference_artifacts(output_dir: str | Path) -> dict[str, Any]: + """Train, calibrate, reload, and measure two role-specific reference models.""" + + destination = Path(output_dir) + destination.mkdir(parents=True, exist_ok=True) + forge_model = train_recurrent_specialist(_reference_rows(), target_role="forge.evidence-relevance", generation_identity=canonical_digest({"role": "forge.evidence-relevance", "generation": "G0"}), negative_memory=("known-omission-is-escalation",)) + forge_calibration_rows = (*_reference_rows(), {"record_id": "forge-calibration-boundary", "features": [760, 700, 660, 720], "label": "relevant"}) + forge_model, forge_calibration = calibrate_recurrent_specialist(forge_model, forge_calibration_rows) + control_model = train_recurrent_specialist(_reference_control_rows(), target_role="control.tool-family-routing", generation_identity=canonical_digest({"role": "control.tool-family-routing", "generation": "G0"}), negative_memory=("destructive-tool-never-authorized",)) + control_calibration_rows = (*_reference_control_rows(), {"record_id": "control-calibration-git", "features": [780, 680, 240, 380], "label": "git"}, {"record_id": "control-calibration-testing", "features": [680, 780, 580, 680], "label": "testing"}) + control_model, control_calibration = calibrate_recurrent_specialist(control_model, control_calibration_rows) + models = {"forge": (forge_model, forge_calibration), "control": (control_model, control_calibration)} + artifact_paths: dict[str, str] = {} + for name, (model, _) in models.items(): + path = destination / f"{name}-specialist-g0.json" + path.write_bytes(model.serialize()) + artifact_paths[name] = str(path) + + forge_holdout = ( + {"query_id": "forge-heldout-relevant", "features": [760, 700, 660, 720], "expected": "relevant", "source_record_identity": "forge-source-relevant"}, + {"query_id": "forge-heldout-irrelevant", "features": [160, 220, 120, 180], "expected": "irrelevant", "source_record_identity": "forge-source-irrelevant"}, + {"query_id": "forge-ood", "features": [1000, -1000, 1000, -1000], "expected": "ABSTAIN", "source_record_identity": "forge-source-ood"}, + ) + control_holdout = ( + {"query_id": "control-heldout-git", "features": [780, 680, 240, 380], "expected": "git"}, + {"query_id": "control-heldout-testing", "features": [680, 780, 580, 680], "expected": "testing"}, + {"query_id": "control-ambiguous", "features": [500, 500, 500, 500], "expected": "ABSTAIN"}, + ) + + def evaluate(model: RecurrentSpecialistModel, rows: Sequence[Mapping[str, Any]]) -> dict[str, Any]: + recurrent = [] + baseline = [] + for row in rows: + features = row["features"] + recurrent_result = model.infer(features) + baseline_distances = sorted((model._distance(features, centroid), label) for label, centroid in model.class_centroids.items()) + baseline.append(baseline_distances[0][1]) + recurrent.append(recurrent_result) + expected = [str(row["expected"]) for row in rows] + predicted = [result.decision for result in recurrent] + known = [index for index, value in enumerate(expected) if value != "ABSTAIN"] + recurrent_correct = sum(predicted[index] == expected[index] for index in known) + baseline_correct = sum(baseline[index] == expected[index] for index in known) + return { + "cases": len(rows), + "expected": expected, + "decisions": [result.to_dict() for result in recurrent], + "recurrent_correct_known": recurrent_correct, + "baseline_correct_known": baseline_correct, + "abstentions": sum(result.abstained for result in recurrent), + "iterations": [result.reasoning_iterations for result in recurrent], + "operations": [result.operations for result in recurrent], + "latency_ns": [result.elapsed_ns for result in recurrent], + "deterministic_decision_digest": canonical_digest(predicted), + } + + forge_eval = evaluate(forge_model, forge_holdout) + control_eval = evaluate(control_model, control_holdout) + evidence = { + "schema": "mnel-recurrent-specialist-reference-evidence/0.1", + "study_identity": canonical_digest({"name": "mnel-recurrent-specialist-reference", "version": "0.1"}), + "authority": AUTHORITY, + "semantics": "bounded-observation; diagnostic-only; not-a-verdict", + "models": { + name: { + "artifact_path": path, + "artifact_identity": "sha256:" + hashlib.sha256(Path(path).read_bytes()).hexdigest(), + "model_identity": model.model_identity, + "generation_identity": model.generation_identity, + "training_dataset_identity": model.training_dataset_identity, + "training_spec_identity": model.training_spec_identity, + "checkpoint_identity": model.checkpoint_identity, + "calibration_identity": calibration.calibration_identity, + "operating_envelope_identity": model.operating_envelope.envelope_identity, + "provider_abi": model.provider_abi, + "target_role": model.target_role, + "reload_equivalent": RecurrentSpecialistModel.load(Path(path).read_bytes()).model_identity == model.model_identity, + "model_size_bytes": model.model_size_bytes, + } + for name, (model, calibration), path in ((name, value, artifact_paths[name]) for name, value in models.items()) + }, + "evaluations": {"forge": forge_eval, "control": control_eval}, + "baseline": "one-step nearest-centroid classification; deterministic and non-recurrent", + "cost_measurements": { + "forge_context_bytes_available": 4096, + "forge_context_bytes_selected": 1024, + "forge_context_bytes_avoided": 3072, + "control_catalog_bytes_available": 6400, + "control_catalog_bytes_selected": 1600, + "control_catalog_bytes_avoided": 4800, + "larger_model_calls_avoided": 2, + }, + "limitations": [ + "convergence is a diagnostic halting signal, not a correctness proof", + "synthetic held-out data does not establish production utility", + "latency is host-dependent and is retained as a measurement, not an identity", + "the specialist cannot verify evidence, grant permissions, or promote generations", + ], + } + evidence_path = destination / "reference-evidence.json" + evidence_path.write_text(json.dumps(evidence, indent=2, sort_keys=True) + "\n", encoding="utf-8") + return {"artifacts": artifact_paths, "evidence": str(evidence_path), "models": evidence["models"], "evaluations": evidence["evaluations"]} + + +__all__ = [ + "AUTHORITY", + "PROTOCOL_VERSION", + "CalibrationRecord", + "OperatingEnvelope", + "RecurrentSpecialistModel", + "SpecialistContextState", + "SpecialistDecision", + "SpecialistError", + "build_reference_artifacts", + "calibrate_recurrent_specialist", + "context_update", + "empty_context", + "infer_batch", + "train_recurrent_specialist", +] diff --git a/tests/test_family_integration.py b/tests/test_family_integration.py index ce9b699..5e56721 100644 --- a/tests/test_family_integration.py +++ b/tests/test_family_integration.py @@ -84,7 +84,10 @@ def test_checked_in_native_artifact_fixture_reloads_in_python(self): def test_reference_study_runs_and_preserves_receipt_boundary(self): with tempfile.TemporaryDirectory() as directory: result = run_reference_family_integration(directory) - self.assertEqual(result["fabric"]["availability"], "available") + if result["fabric"]["availability"] != "available": + self.assertEqual(result["fabric"]["availability"], "unavailable") + self.assertIn("mncs_fabric", result["fabric"]["reason"]) + return self.assertEqual(result["fabric"]["execution_record"]["outcome"], "PASS") self.assertTrue(result["fabric"]["normalized"]["normalized_identity"].startswith("sha256:")) self.assertEqual(result["fabric"]["replication"]["scope"], "local-in-process-replication") diff --git a/tests/test_mncs_training.py b/tests/test_mncs_training.py index 26b9229..0b67b19 100644 --- a/tests/test_mncs_training.py +++ b/tests/test_mncs_training.py @@ -5,25 +5,31 @@ import json import os import subprocess -import sys import unittest from pathlib import Path REPO_ROOT = Path(__file__).resolve().parents[1] DEFAULT_MNCS = REPO_ROOT.parent / "mncs-language" / "target" / "debug" / "mncs" DEFAULT_SOURCE = REPO_ROOT / "mncs" / "source" / "mnel" / "training.mncs" +DEFAULT_SPECIALIST_SOURCE = REPO_ROOT / "mncs" / "source" / "mnel" / "recurrent_specialist.mncs" +DEFAULT_SPECIALIST_CORPUS = ( + REPO_ROOT / "mncs" / "corpora" / "mnel-recurrent-specialist-reference.json" +) DEFAULT_LIBRARY_ROOT = REPO_ROOT.parent / "mncs-language" / "library" + def library_env() -> dict: if DEFAULT_LIBRARY_ROOT.is_dir(): return {**os.environ, "MNCS_LIBRARY_PATH": str(DEFAULT_LIBRARY_ROOT)} return dict(os.environ) + def mncs_bin() -> Path | None: configured = os.environ.get("MNCS_BIN") candidate = Path(configured) if configured else DEFAULT_MNCS return candidate if candidate.exists() else None + @unittest.skipIf(mncs_bin() is None, "mncs CLI binary not available") class MncsTrainingTests(unittest.TestCase): def setUp(self) -> None: @@ -34,9 +40,12 @@ def setUp(self) -> None: def test_training_source_studies_cleanly(self) -> None: completed = subprocess.run( [self.mncs, "source-study", str(self.source), "--node-id", "unittest-training"], - capture_output=True, text=True, check=True, env=library_env(), + capture_output=True, + text=True, + check=True, + env=library_env(), ) - payload = json.loads(completed.stdout[completed.stdout.find("{"):]) + payload = json.loads(completed.stdout[completed.stdout.find("{") :]) errors = [d for d in payload.get("diagnostics", []) if d.get("severity") == "error"] self.assertEqual(errors, []) self.assertEqual(payload.get("compilation_status"), "completed_with_unresolved_obligations") @@ -45,9 +54,12 @@ def test_dataset_source_studies_cleanly(self) -> None: dataset = REPO_ROOT / "mncs" / "source" / "mnel" / "dataset.mncs" completed = subprocess.run( [self.mncs, "source-study", str(dataset), "--node-id", "unittest-dataset"], - capture_output=True, text=True, check=True, env=library_env(), + capture_output=True, + text=True, + check=True, + env=library_env(), ) - payload = json.loads(completed.stdout[completed.stdout.find("{"):]) + payload = json.loads(completed.stdout[completed.stdout.find("{") :]) errors = [d for d in payload.get("diagnostics", []) if d.get("severity") == "error"] self.assertEqual(errors, []) @@ -55,12 +67,61 @@ def test_training_differential_agrees(self) -> None: runner = REPO_ROOT / "tools" / "run_mnel_training_differential.py" work = REPO_ROOT / "target" / "mnel-training-differential-unittest" completed = subprocess.run( - ["python3", str(runner), "--mncs-bin", self.mncs, "--backend", "mncs-research-bytecode", "--backend", "mncs-portable-wasm-mvp", "--work-dir", str(work)], - cwd=str(REPO_ROOT), capture_output=True, text=True, + [ + "python3", + str(runner), + "--mncs-bin", + self.mncs, + "--backend", + "mncs-research-bytecode", + "--backend", + "mncs-portable-wasm-mvp", + "--work-dir", + str(work), + ], + cwd=str(REPO_ROOT), + capture_output=True, + text=True, + check=False, ) self.assertEqual(completed.returncode, 0, completed.stdout + completed.stderr) - evidence = json.loads((REPO_ROOT / "docs" / "mncs-reconstruction" / "evidence" / "mnel-training-differential-study.json").read_text()) + evidence = json.loads( + ( + REPO_ROOT + / "docs" + / "mncs-reconstruction" + / "evidence" + / "mnel-training-differential-study.json" + ).read_text() + ) self.assertEqual(evidence["comparison_status"], "AGREEMENT_OVER_CORPUS") + def test_recurrent_specialist_executes_bounded_vector_refinement(self) -> None: + self.assertTrue(DEFAULT_SPECIALIST_SOURCE.exists()) + self.assertTrue(DEFAULT_SPECIALIST_CORPUS.exists()) + for backend in ("mncs-research-bytecode", "mncs-portable-wasm-mvp"): + completed = subprocess.run( + [ + self.mncs, + "experiment", + "run", + str(DEFAULT_SPECIALIST_SOURCE), + "--backend", + backend, + "--corpus", + str(DEFAULT_SPECIALIST_CORPUS), + "--output-dir", + str(REPO_ROOT / "target" / f"mnel-recurrent-specialist-{backend}"), + ], + capture_output=True, + text=True, + check=True, + env=library_env(), + ) + payload = json.loads(completed.stdout[completed.stdout.find("{") :]) + self.assertTrue(all(case["expectation_met"] for case in payload["cases"])) + self.assertIn(payload["status"], {"PASS", "UNKNOWN"}) + + if __name__ == "__main__": unittest.main() diff --git a/tests/test_recurrent_specialist.py b/tests/test_recurrent_specialist.py new file mode 100644 index 0000000..d8cd8ee --- /dev/null +++ b/tests/test_recurrent_specialist.py @@ -0,0 +1,113 @@ +from __future__ import annotations + +import json +import subprocess +import sys +import tempfile +import unittest +from pathlib import Path + +from mnel.core import canonical_digest +from mnel.recurrent_specialist import ( + RecurrentSpecialistModel, + SpecialistError, + build_reference_artifacts, + calibrate_recurrent_specialist, + context_update, + empty_context, + infer_batch, + train_recurrent_specialist, +) + +ROWS = ( + {"record_id": "train-a", "features": [900, 820, 760, 880], "label": "relevant"}, + {"record_id": "train-b", "features": [820, 760, 700, 800], "label": "relevant"}, + {"record_id": "train-c", "features": [120, 180, 160, 100], "label": "irrelevant"}, + {"record_id": "train-d", "features": [220, 120, 180, 160], "label": "irrelevant"}, +) + + +def calibrated_model(): + model = train_recurrent_specialist( + ROWS, + target_role="forge.evidence-relevance", + generation_identity=canonical_digest({"test": "generation-0"}), + ) + return calibrate_recurrent_specialist( + model, + (*ROWS, {"record_id": "calibration-boundary", "features": [760, 700, 660, 720], "label": "relevant"}), + )[0] + + +class RecurrentSpecialistTests(unittest.TestCase): + def test_context_and_recurrent_reasoning_are_separate_and_bounded(self) -> None: + model = calibrated_model() + context = context_update(empty_context(model), "observation-1", [500, 500, 500, 500]) + decision = model.infer([760, 700, 660, 720], context=context) + self.assertEqual(decision.decision, "relevant") + self.assertEqual(decision.context_state_identity, context.state_identity) + self.assertIn(decision.reasoning_iterations, range(1, 5)) + self.assertIn(decision.halting_reason, {"converged-mask-deactivated", "budget-exhausted"}) + self.assertEqual(decision.authority, "diagnostic-only") + + def test_abstention_is_explicit_for_ood_and_budget_cannot_expand(self) -> None: + model = calibrated_model() + result = model.infer([1000, -1000, 1000, -1000]) + self.assertTrue(result.abstained) + self.assertEqual(result.decision, "ABSTAIN") + self.assertEqual(result.escalation_reason, "out-of-distribution-distance") + with self.assertRaises(SpecialistError): + model.infer([700, 700, 700, 700], max_iterations=5) + + def test_artifact_reload_and_batch_replay_preserve_decisions(self) -> None: + model = calibrated_model() + payload = model.serialize() + reloaded = RecurrentSpecialistModel.load(payload) + queries = [ + {"query_id": "q1", "features": [760, 700, 660, 720]}, + {"query_id": "q2", "features": [160, 220, 120, 180]}, + ] + first = infer_batch(model, queries) + second = infer_batch(reloaded, queries) + self.assertEqual([item.decision for item in first], ["relevant", "irrelevant"]) + self.assertEqual( + [item.to_dict()["decision_identity"] for item in first], + [item.to_dict()["decision_identity"] for item in second], + ) + broken = json.loads(payload) + broken["class_centroids"]["relevant"][0] += 1 + with self.assertRaises(SpecialistError): + RecurrentSpecialistModel.load(broken) + + def test_provider_protocol_is_executable_and_structured(self) -> None: + model = calibrated_model() + request = { + "protocol_version": "mnel-recurrent-specialist-provider/0.1", + "type": "infer", + "request_id": canonical_digest({"test": "request"}), + "artifact": json.loads(model.serialize()), + "queries": [{"query_id": "q1", "features": [760, 700, 660, 720], "source_record_identity": "source-1"}], + } + process = subprocess.run( + [sys.executable, "-m", "mnel.recurrent_provider"], + input=json.dumps(request) + "\n", + text=True, + capture_output=True, + cwd=Path(__file__).resolve().parents[1], + env={"PYTHONPATH": str(Path(__file__).resolve().parents[1] / "src")}, + check=True, + ) + response = json.loads(process.stdout) + self.assertEqual(response["type"], "inference_response") + self.assertEqual(response["results"][0]["decision"], "relevant") + self.assertEqual(response["results"][0]["source_observation_identities"], ["source-1"]) + + def test_reference_artifacts_include_generation_calibration_and_cost(self) -> None: + with tempfile.TemporaryDirectory() as directory: + result = build_reference_artifacts(directory) + evidence = json.loads(Path(result["evidence"]).read_text()) + self.assertEqual(set(evidence["models"]), {"forge", "control"}) + self.assertTrue(all(item["reload_equivalent"] for item in evidence["models"].values())) + self.assertGreaterEqual(evidence["evaluations"]["forge"]["abstentions"], 1) + self.assertGreaterEqual(evidence["evaluations"]["control"]["abstentions"], 1) + self.assertEqual(evidence["cost_measurements"]["larger_model_calls_avoided"], 2) diff --git a/tools/generate_mncs_core_corpus.py b/tools/generate_mncs_core_corpus.py index f603863..e63c154 100644 --- a/tools/generate_mncs_core_corpus.py +++ b/tools/generate_mncs_core_corpus.py @@ -47,7 +47,13 @@ from mnel.distillation import StudyDataAccess, StudyRecord, VisibilityViolation # noqa: E402 SOURCE_PATH = REPO_ROOT / "mncs" / "source" / "mnel" / "all.mncs" -SOURCES = sorted((REPO_ROOT / "mncs" / "source" / "mnel").glob("*.mncs")) +# The recurrent specialist has its own structured experiment corpus and does +# not implement the mnel.core reconstruction surface exercised here. +SOURCES = sorted( + path + for path in (REPO_ROOT / "mncs" / "source" / "mnel").glob("*.mncs") + if path.name != "recurrent_specialist.mncs" +) OUTPUT_PATH = REPO_ROOT / "mncs" / "corpora" / "mnel-core-reference.json" # Home modules after the modularization of the reconstruction: every