Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ __pycache__/
*.egg-info/
.venv/
build/
target/
workspace/
dist/
.pytest_cache/
.ruff_cache/
Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# Changelog

## 0.2.0a0 — unreleased

- Add backend-neutral CPU, full-CUDA, and sequential CPU offload placement policy with
reserve/cap/workspace math, real accelerator probe requirements, precision checks, and
bounded AUTO OOM recovery.
- Extend runtime manifests with backward-compatible placement policy and capability
metadata without changing C ABI v1.
- Add optional Torch/Accelerate adapter with observed sequential-offload verification.
- Add process-local Rust host lifecycle, identity-bound snapshot reuse, bounded host-owned
result buffers, normalized diagnostic statuses, measurements, unload, and quarantine.
- Add executable deterministic Rust HMM provider baseline and provider-host benchmark.
- Add bounded investigator context packing, explicit workspace access, candidate transaction,
identity envelope, quarantine, and morning-report contracts.

## 0.1.0a0 — unreleased

- Define Machine-Native Experimental Learning and Verified Experience Distillation.
Expand Down
31 changes: 31 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[workspace]
members = [
"crates/mnel-provider-api",
"crates/mnel-provider-classical",
"crates/mnel-provider-sdk",
"crates/mnel-provider-host",
]
Expand Down
47 changes: 37 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,14 @@ experience, negative memory, causal attribution, transfer-gated principles, reus
strategies, and append-only candidate lineage rather than relying exclusively on
conventional neural-weight training.

> **Current status:** functional `0.1.0a0` foundation. The repository implements the
> core local lifecycle, deterministic evidence ledger, hard-gate evaluator, recursion
> governor, investigator contracts, a diagnostic-only learned micro-provider registry,
> and a testable Rust-first provider runtime contract. It does not yet train or execute
> learned micro-providers, provide unattended model execution, distributed scheduling,
> protected final custody, formal MNCS/MNCDS conformance, or automatic RAVEL promotion.
> **Current status:** functional `0.2.0a0` iteration. The repository now includes a
> backend-neutral accelerator placement policy, optional Torch/Accelerate adapter,
> process-local persistent Rust host, reusable identity-bound snapshots, bounded and
> normalized diagnostic results, failure quarantine, an executable Rust HMM baseline,
> deterministic runtime measurements, and bounded investigator context/workspace
> contracts. It still does not provide dynamic library loading, process isolation,
> unattended model execution, distributed scheduling, protected final custody, formal
> MNCS/MNCDS conformance, or automatic RAVEL promotion.

## Core rule

Expand Down Expand Up @@ -86,6 +88,14 @@ copy their authority or silently create substitute implementations.
- accepted Rust-first runtime architecture decision and versioned C ABI;
- safe Rust provider SDK, host admission policy, reusable snapshot cache, and runtime
manifest validation;
- backend-neutral CPU/full-CUDA/sequential-CPU-offload policy with reserve/cap/workspace
accounting, real-probe requirements, precision checks, and bounded AUTO OOM recovery;
- explicit distinction between persistent provider lifetime and physical weight placement;
- process-local Rust host lifecycle, snapshot reuse, output-buffer limits, normalized
status handling, measurement collection, and deterministic quarantine;
- executable `mnel-provider-classical` HMM diagnostic provider and host integration tests;
- eligible-context packing, read-only/proposal workspace models, identity envelopes,
candidate transactions, quarantine queues, and deterministic morning-report records;
- deterministic reference workflow, JSON schemas, mutation-oriented tests, and CI.

## Install
Expand Down Expand Up @@ -132,8 +142,15 @@ mnel learned-provider match \
--limit 4
```

These commands inspect declarations and matching only. They do not download, train, or
execute any model.
The catalog commands do not download or train models. The Rust reference provider can be
measured locally with:

```bash
cargo run -p mnel-provider-host --example provider-benchmark
```

The benchmark reports one local observation of host admission and warm invocation timing;
hardware-independent placement policy tests use fake accelerator diagnostics.

Run the deterministic reference lifecycle:

Expand Down Expand Up @@ -201,6 +218,15 @@ path.
See [ADR 0001](docs/decisions/0001-rust-provider-runtime.md) and the
[learned-provider runtime contract](docs/LEARNED_PROVIDER_RUNTIME.md).

### Placement and residency

Provider admission is persistent, but GPU residency is a separate policy decision.
`resident-on-admission` means the provider is loaded and reusable; it does not mean all
weights must remain permanently on a GPU. With sequential CPU offload, weights remain in
system RAM while individual modules execute temporarily on CUDA, trading VRAM for host
memory and transfer overhead. Explicit CPU/CUDA/offload choices are honored or rejected;
only `auto` may use bounded full-CUDA → sequential-offload → CPU recovery.

## Investigator roles

- **Investigator** — proposes falsifiable hypotheses and bounded interventions.
Expand Down Expand Up @@ -239,9 +265,10 @@ state, and failure modes.
src/mnel/ Python control plane and executable foundation
crates/mnel-provider-api/ versioned provider ABI vocabulary
crates/mnel-provider-sdk/ safe Rust provider authoring surface
crates/mnel-provider-host/ admission policy and reusable snapshot storage
crates/mnel-provider-host/ persistent process-local host, policy, and snapshots
crates/mnel-provider-classical/ executable deterministic HMM diagnostic baseline
include/ language-neutral provider ABI header
schemas/ machine-readable record vocabulary
schemas/ machine-readable record and runtime vocabulary
docs/ architecture, decisions, method, boundaries, roadmap
examples/reference-study/ deterministic lifecycle example
examples/learned-providers/ architecture catalog and runtime manifest example
Expand Down
15 changes: 15 additions & 0 deletions crates/mnel-provider-classical/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "mnel-provider-classical"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
repository.workspace = true
description = "Deterministic Rust classical provider baseline for MNEL"

[dependencies]
mnel-provider-api = { path = "../mnel-provider-api" }
mnel-provider-sdk = { path = "../mnel-provider-sdk" }

[lints]
workspace = true
137 changes: 137 additions & 0 deletions crates/mnel-provider-classical/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
//! A small executable HMM-style diagnostic provider.
//!
//! The provider is intentionally boring: it consumes a compact state-sequence snapshot,
//! computes a bounded negative log likelihood against a fixed transition model, and
//! returns an anomaly observation. It has no evaluator or promotion authority.

use mnel_provider_api::OUTPUT_ANOMALY_SCORE;
use mnel_provider_sdk::{DiagnosticResult, Invocation, LearnedProvider, ProviderError};

const STATE_COUNT: usize = 4;
const TRANSITION_PROBABILITIES: [[f64; STATE_COUNT]; STATE_COUNT] = [
[0.70, 0.20, 0.08, 0.02],
[0.05, 0.75, 0.15, 0.05],
[0.10, 0.10, 0.70, 0.10],
[0.20, 0.10, 0.10, 0.60],
];

#[derive(Clone, Copy, Debug, Default)]
pub struct HiddenMarkovProvider;

impl HiddenMarkovProvider {
pub const fn new() -> Self {
Self
}
}

impl LearnedProvider for HiddenMarkovProvider {
fn infer(&self, invocation: &Invocation<'_>) -> Result<DiagnosticResult, ProviderError> {
let snapshot = invocation
.snapshots()
.first()
.ok_or(ProviderError::MissingSnapshots)?;
let sequence = snapshot.payload;
let operation_limit = invocation.budget().operation_limit as usize;
if sequence.len() > operation_limit {
return Err(ProviderError::BudgetExceeded);
}
if sequence
.iter()
.any(|state| usize::from(*state) >= STATE_COUNT)
{
return Err(ProviderError::OutOfDistribution);
}

let mut negative_log_likelihood = 0.0_f64;
let mut transitions = 0_usize;
for pair in sequence.windows(2) {
let probability = TRANSITION_PROBABILITIES[usize::from(pair[0])][usize::from(pair[1])];
negative_log_likelihood -= probability.ln();
transitions += 1;
}
let score = if transitions == 0 {
0.0
} else {
negative_log_likelihood / transitions as f64
};
let calibration_band = if score < 0.6 {
0
} else if score < 1.5 {
1
} else {
2
};
let payload = format!("transitions={transitions};mean_negative_log_likelihood={score:.6}")
.into_bytes();
DiagnosticResult {
output_kind: OUTPUT_ANOMALY_SCORE,
value: score,
calibration_band,
out_of_distribution: false,
payload,
}
.validate()
}
}

#[cfg(test)]
mod tests {
use super::*;
use mnel_provider_api::Digest32;
use mnel_provider_sdk::{InvocationIdentity, ResourceBudget, SnapshotRef};

fn digest(value: u8) -> Digest32 {
Digest32 { bytes: [value; 32] }
}

fn invocation(payload: &[u8], limit: u64) -> Invocation<'_> {
let result = Invocation::new(
InvocationIdentity {
declaration: digest(1),
model: digest(2),
calibration: digest(3),
query: digest(4),
},
ResourceBudget {
wall_time_ns: 1_000_000,
operation_limit: limit,
memory_bytes: 1024,
},
vec![SnapshotRef {
schema_version: 1,
identity: digest(5),
feature_extractor_identity: digest(6),
payload,
}],
);
match result {
Ok(invocation) => invocation,
Err(_) => panic!("test invocation should be bounded and non-empty"),
}
}

#[test]
fn produces_a_bounded_diagnostic_score() {
let result = match HiddenMarkovProvider::new().infer(&invocation(&[0, 0, 1, 1, 2], 10)) {
Ok(result) => result,
Err(_) => panic!("provider should infer"),
};
assert_eq!(result.output_kind, OUTPUT_ANOMALY_SCORE);
assert!(result.value.is_finite());
assert!(!result.payload.is_empty());
}

#[test]
fn respects_operation_budget_and_ood_states() {
let budget_error = match HiddenMarkovProvider::new().infer(&invocation(&[0, 1, 2], 2)) {
Ok(_) => panic!("operation budget should be enforced"),
Err(error) => error,
};
assert_eq!(budget_error, ProviderError::BudgetExceeded);
let ood_error = match HiddenMarkovProvider::new().infer(&invocation(&[0, 9], 10)) {
Ok(_) => panic!("unknown state should abstain as OOD"),
Err(error) => error,
};
assert_eq!(ood_error, ProviderError::OutOfDistribution);
}
}
4 changes: 4 additions & 0 deletions crates/mnel-provider-host/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ description = "Persistent policy host for MNEL learned micro-providers"

[dependencies]
mnel-provider-api = { path = "../mnel-provider-api" }
mnel-provider-sdk = { path = "../mnel-provider-sdk" }

[dev-dependencies]
mnel-provider-classical = { path = "../mnel-provider-classical" }

[lints]
workspace = true
77 changes: 77 additions & 0 deletions crates/mnel-provider-host/examples/provider-benchmark.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use std::sync::Arc;
use std::time::Instant;

use mnel_provider_api::{Digest32, ABI_VERSION_V1};
use mnel_provider_classical::HiddenMarkovProvider;
use mnel_provider_host::placement::{PlacementCapabilities, PlacementPolicy};
use mnel_provider_host::{
CachedSnapshot, ExecutionTier, ImplementationLanguage, ProviderHost, ProviderManifest,
};
use mnel_provider_sdk::{InvocationIdentity, ResourceBudget};

fn digest(value: u8) -> Digest32 {
Digest32 { bytes: [value; 32] }
}

fn main() {
let manifest = ProviderManifest {
provider_id: "state.hidden-markov-model".to_owned(),
provider_version: "0.1.0".to_owned(),
declaration_identity: digest(1),
artifact_identity: digest(2),
language: ImplementationLanguage::Rust,
tier: ExecutionTier::NativeTrusted,
abi_version: ABI_VERSION_V1,
persistent_host: true,
language_exception: None,
placement_policy: PlacementPolicy::default(),
placement_capabilities: PlacementCapabilities::default(),
};
let mut host = ProviderHost::new(3, 1024);
let cold_started = Instant::now();
if let Err(error) = host.admit(manifest, Arc::new(HiddenMarkovProvider::new())) {
panic!("benchmark provider admission should succeed: {error:?}");
}
let cold_admission_ns = cold_started.elapsed().as_nanos();
let snapshot_id = digest(9);
host.register_snapshot(CachedSnapshot {
identity: snapshot_id,
feature_extractor_identity: digest(10),
payload: Arc::from([0_u8, 0, 1, 1, 2, 2, 3, 3]),
});
let identities = InvocationIdentity {
declaration: digest(1),
model: digest(2),
calibration: digest(3),
query: digest(4),
};
let budget = ResourceBudget {
wall_time_ns: 1_000_000,
operation_limit: 100,
memory_bytes: 1024,
};
let mut samples = Vec::new();
let mut process_rss_bytes = None;
for _ in 0..32 {
let result = match host.invoke(
"state.hidden-markov-model",
identities,
&[snapshot_id],
budget,
) {
Ok(result) => result,
Err(error) => panic!("benchmark invocation should succeed: {error:?}"),
};
samples.push(result.measurement.elapsed_ns);
process_rss_bytes = result.measurement.process_rss_bytes;
}
samples.sort_unstable();
let p50 = samples[samples.len() / 2];
let p95 = samples[(samples.len() * 95 / 100).min(samples.len() - 1)];
let p99 = samples[(samples.len() * 99 / 100).min(samples.len() - 1)];
println!(
"{{\"provider\":\"state.hidden-markov-model\",\"cold_admission_ns\":{cold_admission_ns},\"warm_p50_ns\":{p50},\"warm_p95_ns\":{p95},\"warm_p99_ns\":{p99},\"samples\":{},\"snapshot_reuse\":true,\"bytes_copied_per_invocation\":0,\"process_rss_bytes\":{rss},\"execution_mode\":\"cpu\",\"precision\":\"float32\",\"placement_reason\":\"native Rust CPU backend; no accelerator adapter attached\"}}",
samples.len(),
rss = process_rss_bytes.map_or_else(|| "null".to_owned(), |value| value.to_string())
);
}
Loading
Loading