Full agent governance in Rust — policy evaluation, trust scoring, hash-chain
audit logging, and Ed25519 agent identity. The agentmesh crate provides the
same governance primitives as the Python, TypeScript, and .NET SDKs with Rust's
zero-cost abstractions and memory safety guarantees.
Target runtime: Rust 1.75+ (2021 edition) Crate:
agentmeshv0.1.0 Dependencies:serde,serde_yaml,sha2,ed25519-dalek,thiserror
| Section | Topic |
|---|---|
| Quick Start | Evaluate a policy in 5 lines of Rust |
| AgentMeshClient | Unified governance pipeline — identity + trust + policy + audit |
| PolicyEngine | YAML rules, capability/approval/rate-limit types, conflict resolution |
| TrustManager | 0–1000 trust scoring, tiers, decay, persistence |
| AuditLogger | Hash-chain audit logging and verification |
| AgentIdentity | Ed25519 key pairs, DIDs, signing, JSON export |
| Loading Policies from YAML | File-based policy configuration |
| Full Governance Pipeline | End-to-end example |
| Cross-Reference | Equivalent Python and TypeScript tutorials |
| Next Steps | Where to go from here |
- Rust 1.75+ with Cargo
- Familiarity with
cargoandCargo.toml - Recommended: read Tutorial 01 — Policy Engine for governance concepts
Add the crate to your project:
cargo add agentmeshOr add it to your Cargo.toml directly:
[dependencies]
agentmesh = "0.1"Five lines to evaluate your first policy:
use agentmesh::AgentMeshClient;
fn main() {
let client = AgentMeshClient::new("my-agent")
.expect("failed to create client");
let result = client.execute_with_governance("data.read", None);
println!("Allowed: {}", result.allowed); // true
println!("Decision: {:?}", result.decision); // Allow
}When no policy is loaded, the engine defaults to allow — load a YAML policy to enforce governance rules.
AgentMeshClient is the recommended entry point. It wires together identity,
trust, policy, and audit into a single governance-aware pipeline.
use agentmesh::{AgentMeshClient, ClientOptions};
// Default client — generates identity, empty policy (allow-all)
let client = AgentMeshClient::new("analyst-001")?;
// Client with options
let opts = ClientOptions {
capabilities: vec!["data.read".into(), "data.write".into()],
trust_config: None, // use defaults
policy_yaml: None, // load later
};
let client = AgentMeshClient::with_options("analyst-001", opts)?;Each subsystem is accessible as a public field:
// Identity (Ed25519 DID)
println!("DID: {}", client.identity.did);
println!("Capabilities: {:?}", client.identity.capabilities);
// Trust scores
let score = client.trust.get_trust_score(&client.identity.did);
println!("Trust: {} (tier: {:?})", score.score, score.tier);
// Audit trail
println!("Audit chain valid: {}", client.audit.verify());
println!("Entries: {}", client.audit.entries().len());execute_with_governance() runs the full pipeline: evaluate → log → trust
update.
use std::collections::HashMap;
let result = client.execute_with_governance("data.read", None);
// The result contains everything
println!("Allowed: {}", result.allowed);
println!("Decision: {:?}", result.decision);
println!("Trust: {} ({:?})", result.trust_score.score, result.trust_score.tier);
println!("Audit hash: {}", result.audit_entry.hash);The PolicyEngine evaluates actions against YAML-defined rules. It supports
four decision types: allow, deny, requires-approval, and
rate-limit.
use agentmesh::PolicyEngine;
// Empty engine — allows everything
let engine = PolicyEngine::new();
assert!(!engine.is_loaded());
// Load from a YAML string
let yaml = r#"
version: "1.0"
agent: my-agent
policies:
- name: capability-gate
type: capability
allowed_actions:
- "data.read"
- "data.write"
denied_actions:
- "shell:*"
"#;
engine.load_from_yaml(yaml)?;
assert!(engine.is_loaded());use agentmesh::types::PolicyDecision;
let decision = engine.evaluate("data.read", None);
assert_eq!(decision, PolicyDecision::Allow);
let decision = engine.evaluate("shell:rm", None);
assert!(matches!(decision, PolicyDecision::Deny(_)));
// Actions outside the rule's scope fall through to Allow
let decision = engine.evaluate("admin.delete", None);
assert_eq!(decision, PolicyDecision::Allow);# policies/governance.yaml
version: "1.0"
agent: multi-decision-demo
policies:
# 1. Capability — allow/deny by action pattern
- name: data-gate
type: capability
allowed_actions:
- "data.*"
denied_actions:
- "shell:*"
# 2. Approval — require human sign-off
- name: deploy-gate
type: approval
actions:
- "deploy.*"
min_approvals: 2
# 3. Rate limit — cap call frequency
- name: api-throttle
type: rate_limit
actions:
- "api.*"
max_calls: 10
window: "60s"let engine = PolicyEngine::new();
engine.load_from_file("policies/governance.yaml")?;
// Capability — allowed
assert_eq!(engine.evaluate("data.read", None), PolicyDecision::Allow);
// Capability — denied
assert!(matches!(engine.evaluate("shell:rm", None), PolicyDecision::Deny(_)));
// Approval required
assert!(matches!(
engine.evaluate("deploy.prod", None),
PolicyDecision::RequiresApproval(_)
));
// Rate limiting
for _ in 0..10 {
assert_eq!(engine.evaluate("api.call", None), PolicyDecision::Allow);
}
assert!(matches!(
engine.evaluate("api.call", None),
PolicyDecision::RateLimited { .. }
));Rules can include conditions that are matched against a context map:
version: "1.0"
agent: conditional-demo
policies:
- name: prod-gate
type: capability
denied_actions:
- "deploy.*"
conditions:
environment: "production"use std::collections::HashMap;
use serde_yaml::Value;
let engine = PolicyEngine::new();
engine.load_from_yaml(yaml)?;
// Without context — rule is skipped, action allowed
assert_eq!(engine.evaluate("deploy.app", None), PolicyDecision::Allow);
// With matching context — denied
let mut ctx = HashMap::new();
ctx.insert("environment".into(), Value::String("production".into()));
assert!(matches!(
engine.evaluate("deploy.app", Some(&ctx)),
PolicyDecision::Deny(_)
));When multiple policy candidates conflict, the engine resolves them using one of four strategies:
use agentmesh::types::{CandidateDecision, ConflictResolutionStrategy, PolicyScope};
let engine = PolicyEngine::with_strategy(ConflictResolutionStrategy::DenyOverrides);
let candidates = vec![
CandidateDecision {
decision: PolicyDecision::Allow,
priority: 10,
scope: PolicyScope::Global,
rule_name: "allow-rule".into(),
},
CandidateDecision {
decision: PolicyDecision::Deny("blocked".into()),
priority: 5,
scope: PolicyScope::Global,
rule_name: "deny-rule".into(),
},
];
let result = engine.resolve_conflicts(&candidates);
assert!(matches!(result.winning_decision, PolicyDecision::Deny(_)));
assert!(result.conflict_detected);| Strategy | Behaviour |
|---|---|
DenyOverrides |
Any deny wins, regardless of priority |
AllowOverrides |
Any allow wins, regardless of priority |
PriorityFirstMatch |
Highest priority wins (default) |
MostSpecificWins |
Agent > Tenant > Global scope, then priority |
The TrustManager tracks per-agent trust scores on a 0–1000 scale across
five tiers, with time-based decay and optional JSON persistence.
| Tier | Score Range | Description |
|---|---|---|
Untrusted |
0–199 | Agent has failed validation or behaved maliciously |
Provisional |
200–399 | New or recovering agent, limited access |
Neutral |
400–599 | Default starting point |
Trusted |
600–799 | Established track record |
FullyTrusted |
800–1000 | Highest confidence level |
use agentmesh::TrustManager;
let tm = TrustManager::with_defaults();
// New agent starts at 500 (Neutral)
let score = tm.get_trust_score("agent-x");
assert_eq!(score.score, 500);
assert_eq!(score.tier, TrustTier::Neutral);
// Record successes — trust increases
tm.record_success("agent-x");
tm.record_success("agent-x");
let score = tm.get_trust_score("agent-x");
assert!(score.score > 500);
// Record failures — trust decreases (asymmetric: penalty > reward)
tm.record_failure("agent-x");
let score = tm.get_trust_score("agent-x");
println!("After failure: {} ({:?})", score.score, score.tier);use agentmesh::TrustConfig;
let config = TrustConfig {
initial_score: 800,
threshold: 700,
reward: 20,
penalty: 100,
persist_path: None,
decay_rate: 0.95,
};
let tm = TrustManager::new(config);
let score = tm.get_trust_score("high-trust-agent");
assert_eq!(score.score, 800);
assert_eq!(score.tier, TrustTier::Trusted);Trust scores decay over time if no interactions occur. The decay_rate
multiplier is applied per hour since the last update:
decayed_score = score × decay_rate ^ hours_elapsed
Set decay_rate closer to 1.0 for slower decay, or lower (e.g., 0.90) for
aggressive decay requiring frequent re-validation.
Enable JSON persistence to survive process restarts:
let config = TrustConfig {
persist_path: Some("trust-scores.json".into()),
..Default::default()
};
let tm = TrustManager::new(config);
// Scores are saved on every update and loaded on construction
tm.record_success("agent-x");
// trust-scores.json now contains the scoreThe AuditLogger provides an append-only, hash-chain-linked audit trail. Each
entry's SHA-256 hash incorporates the previous entry's hash, creating a
tamper-evident chain.
use agentmesh::AuditLogger;
let logger = AuditLogger::new();
let entry = logger.log("agent-001", "data.read", "allow");
println!("Hash: {}", entry.hash);
println!("Prev: {}", entry.previous_hash); // empty for genesis entry
println!("Seq: {}", entry.seq); // 0let logger = AuditLogger::new();
logger.log("agent-1", "data.read", "allow");
logger.log("agent-1", "data.write", "deny");
logger.log("agent-2", "report.send", "allow");
// Verify the entire chain
assert!(logger.verify());
// Each entry links to the previous
let entries = logger.entries();
assert_eq!(entries.len(), 3);
assert_eq!(entries[1].previous_hash, entries[0].hash);
assert_eq!(entries[2].previous_hash, entries[1].hash);How the chain works:
Entry 0 Entry 1 Entry 2
┌──────────┐ ┌──────────┐ ┌──────────┐
│ hash: A │──────▶│ prev: A │──────▶│ prev: B │
│ prev: "" │ │ hash: B │ │ hash: C │
│ seq: 0 │ │ seq: 1 │ │ seq: 2 │
└──────────┘ └──────────┘ └──────────┘
If any entry is tampered with, verify() returns false because the hash chain
breaks.
use agentmesh::types::AuditFilter;
let filter = AuditFilter {
agent_id: Some("agent-1".into()),
action: None,
decision: None,
start_time: None,
end_time: None,
};
let filtered = logger.get_entries(&filter);
println!("Agent-1 entries: {}", filtered.len());The AgentIdentity provides Ed25519-based cryptographic identity with DID
identifiers, signing, and JSON serialisation.
use agentmesh::AgentIdentity;
let identity = AgentIdentity::generate(
"researcher-agent",
vec!["data.read".into(), "search".into()],
)?;
println!("DID: {}", identity.did); // did:agentmesh:researcher-agent
println!("Capabilities: {:?}", identity.capabilities);
println!("Public key: {} bytes", identity.public_key.len());let data = b"important message";
// Sign
let signature = identity.sign(data)?;
println!("Signature: {} bytes", signature.len()); // 64 bytes
// Verify with the same identity
assert!(identity.verify(data, &signature));
// Tampered data fails verification
assert!(!identity.verify(b"wrong message", &signature));Export the public portion of an identity for sharing:
let json = identity.to_json()?;
println!("{}", json);
// {"did":"did:agentmesh:researcher-agent","public_key":"...","capabilities":["data.read","search"]}
// Reconstruct from JSON
let imported = AgentIdentity::from_json(&json)?;
assert_eq!(imported.did, identity.did);Share a PublicIdentity when you need verification without the private key:
use agentmesh::PublicIdentity;
let pub_id = identity.public_identity();
assert!(pub_id.verify(b"important message", &signature));The recommended pattern is to keep policies in versioned YAML files:
# policies/security.yaml
version: "1.0"
agent: production-agent
policies:
- name: data-access
type: capability
allowed_actions:
- "data.read"
- "data.write"
denied_actions:
- "shell:*"
- "admin.*"
- name: deploy-approval
type: approval
actions:
- "deploy.*"
min_approvals: 2
- name: api-rate-limit
type: rate_limit
actions:
- "api.*"
max_calls: 100
window: "1m"use agentmesh::{AgentMeshClient, ClientOptions};
// Load policy at client creation
let yaml = std::fs::read_to_string("policies/security.yaml")?;
let client = AgentMeshClient::with_options("prod-agent", ClientOptions {
policy_yaml: Some(yaml),
..Default::default()
})?;
// Or load into an existing engine
client.policy.load_from_file("policies/additional.yaml")?;End-to-end example combining all subsystems:
use agentmesh::{AgentMeshClient, ClientOptions, TrustConfig};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 1. Configure the client
let policy_yaml = r#"
version: "1.0"
agent: research-agent
policies:
- name: data-gate
type: capability
allowed_actions:
- "data.read"
- "search.*"
denied_actions:
- "data.delete"
- "shell:*"
- name: api-throttle
type: rate_limit
actions:
- "search.*"
max_calls: 5
window: "60s"
"#;
let client = AgentMeshClient::with_options("research-agent", ClientOptions {
capabilities: vec!["data.read".into(), "search.web".into()],
trust_config: Some(TrustConfig {
initial_score: 500,
reward: 15,
penalty: 75,
..Default::default()
}),
policy_yaml: Some(policy_yaml.into()),
})?;
println!("Agent DID: {}", client.identity.did);
// 2. Execute governed actions
let actions = ["data.read", "search.web", "data.delete", "shell:ls"];
for action in &actions {
let result = client.execute_with_governance(action, None);
println!(
" {} → {} (trust: {}, tier: {:?})",
action,
if result.allowed { "✅ allowed" } else { "❌ denied" },
result.trust_score.score,
result.trust_score.tier,
);
}
// 3. Verify audit chain
let entries = client.audit.entries();
println!("\nAudit trail: {} entries", entries.len());
println!("Chain valid: {}", client.audit.verify());
for entry in &entries {
println!(
" [{}] {} → {} (hash: {}...)",
entry.seq,
entry.action,
entry.decision,
&entry.hash[..12],
);
}
Ok(())
}Expected output:
Agent DID: did:agentmesh:research-agent
data.read → ✅ allowed (trust: 510, tier: Trusted)
search.web → ✅ allowed (trust: 520, tier: Trusted)
data.delete → ❌ denied (trust: 445, tier: Neutral)
shell:ls → ❌ denied (trust: 370, tier: Provisional)
Audit trail: 4 entries
Chain valid: true
[0] data.read → allow (hash: 3a7f2b8c91e4...)
[1] search.web → allow (hash: f1d9a2c38b71...)
[2] data.delete → deny (hash: 8e4c1a7d02f3...)
[3] shell:ls → deny (hash: b5e7d3f9c128...)
| Rust SDK Feature | Python Equivalent | Tutorial |
|---|---|---|
PolicyEngine |
agent_os.policy |
Tutorial 01 — Policy Engine |
TrustManager |
agent_os.trust |
Tutorial 02 — Trust & Identity |
AuditLogger |
agent_os.audit |
Tutorial 04 — Audit & Compliance |
AgentIdentity |
agent_os.identity |
Tutorial 02 — Trust & Identity |
AgentMeshClient |
AgentMeshClient |
Tutorial 20 — TypeScript SDK |
Note: The Rust SDK wraps all governance features into a single
agentmeshcrate, while the Python implementation splits them across separateagent_os.*modules. Policy YAML files work identically across all SDKs.
| Component | Location |
|---|---|
| Main exports | packages/agent-mesh/sdks/rust/agentmesh/src/lib.rs |
| Type definitions | packages/agent-mesh/sdks/rust/agentmesh/src/types.rs |
PolicyEngine |
packages/agent-mesh/sdks/rust/agentmesh/src/policy.rs |
TrustManager |
packages/agent-mesh/sdks/rust/agentmesh/src/trust.rs |
AuditLogger |
packages/agent-mesh/sdks/rust/agentmesh/src/audit.rs |
AgentIdentity |
packages/agent-mesh/sdks/rust/agentmesh/src/identity.rs |
| Tests | packages/agent-mesh/sdks/rust/agentmesh/tests/ |
| Package config | packages/agent-mesh/sdks/rust/agentmesh/Cargo.toml |
- Run the tests to see the SDK in action:
cd packages/agent-mesh/sdks/rust/agentmesh cargo test
- Load a YAML policy from the repository's
policies/directory and evaluate it against your agent - Enable trust persistence with
persist_pathto retain trust scores across process restarts - Verify audit chains in your CI/CD pipeline — call
audit.verify()as a post-deployment check - Explore the TypeScript SDK tutorial (Tutorial 20) for equivalent patterns in TypeScript