diff --git a/packages/agent-governance-dotnet/README.md b/packages/agent-governance-dotnet/README.md
index d95c127d..ef99654f 100644
--- a/packages/agent-governance-dotnet/README.md
+++ b/packages/agent-governance-dotnet/README.md
@@ -134,6 +134,63 @@ var limits = enforcer.GetLimits(ring);
When enabled via `GovernanceOptions.EnableRings`, ring checks are automatically enforced in the middleware pipeline.
+### Kill Switch
+
+Terminate rogue agents immediately with an arm/disarm safety mechanism, event history, and subscriber notifications:
+
+```csharp
+using AgentGovernance.Hypervisor;
+
+var ks = new KillSwitch();
+ks.Arm();
+
+// Subscribe to kill events
+ks.OnKill += (_, evt) =>
+ Console.WriteLine($"Killed {evt.AgentId}: {evt.Reason} — {evt.Detail}");
+
+// Terminate an agent
+var killEvent = ks.Kill("did:mesh:rogue-agent", KillReason.PolicyViolation, "exceeded scope");
+
+// Review history
+foreach (var e in ks.History)
+ Console.WriteLine($"{e.Timestamp}: {e.AgentId} — {e.Reason}");
+
+ks.Disarm(); // Prevents further kills until re-armed
+```
+
+| Reason | Description |
+|--------|-------------|
+| `PolicyViolation` | Agent violated a governance policy |
+| `TrustThreshold` | Trust score dropped below threshold |
+| `ManualOverride` | Human operator triggered the kill |
+| `AnomalyDetected` | Anomalous behaviour detected |
+| `ResourceExhaustion` | Resource consumption limits exceeded |
+
+### Lifecycle Management
+
+Eight-state lifecycle machine with validated transitions, event logging, and convenience methods:
+
+```csharp
+using AgentGovernance.Lifecycle;
+
+var mgr = new LifecycleManager("did:mesh:agent-007");
+
+mgr.Activate(); // Provisioning → Active
+mgr.Suspend("scheduled maintenance"); // Active → Suspended
+mgr.Transition(LifecycleState.Active, "maintenance done", "ops");
+mgr.Quarantine("trust breach detected"); // Active → Quarantined
+mgr.Decommission("end of life"); // Quarantined → Decommissioning
+
+// Check transition validity
+bool canActivate = mgr.CanTransition(LifecycleState.Active); // false
+
+// Review full event log
+foreach (var evt in mgr.Events)
+ Console.WriteLine($"{evt.Timestamp}: {evt.FromState} → {evt.ToState} ({evt.Reason})");
+```
+
+**Lifecycle states:** Provisioning → Active ↔ Suspended / Rotating / Degraded / Quarantined → Decommissioning → Decommissioned
+
### Saga Orchestrator
Multi-step transaction governance with automatic compensation on failure:
diff --git a/packages/agent-governance-dotnet/src/AgentGovernance/Hypervisor/KillSwitch.cs b/packages/agent-governance-dotnet/src/AgentGovernance/Hypervisor/KillSwitch.cs
new file mode 100644
index 00000000..76a23e44
--- /dev/null
+++ b/packages/agent-governance-dotnet/src/AgentGovernance/Hypervisor/KillSwitch.cs
@@ -0,0 +1,111 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace AgentGovernance.Hypervisor;
+
+///
+/// Reason an agent was terminated via the kill switch.
+///
+public enum KillReason
+{
+ /// Agent violated a governance policy.
+ PolicyViolation,
+
+ /// Agent's trust score dropped below the acceptable threshold.
+ TrustThreshold,
+
+ /// A human operator manually triggered the kill.
+ ManualOverride,
+
+ /// Anomalous behaviour was detected by the monitoring system.
+ AnomalyDetected,
+
+ /// Agent exceeded resource consumption limits.
+ ResourceExhaustion
+}
+
+///
+/// Immutable record of a single kill switch activation.
+///
+/// DID of the terminated agent.
+/// Why the agent was killed.
+/// Human-readable detail message.
+/// UTC time the kill occurred.
+public record KillEvent(string AgentId, KillReason Reason, string Detail, DateTimeOffset Timestamp);
+
+///
+/// Agent kill switch that can be armed/disarmed.
+/// When armed, calling terminates the target agent,
+/// records the event, and notifies subscribers.
+///
+public class KillSwitch
+{
+ private readonly List _history = new();
+ private readonly object _lock = new();
+
+ ///
+ /// Whether the kill switch is currently armed. Only armed switches can kill agents.
+ ///
+ public bool IsArmed { get; private set; }
+
+ ///
+ /// Immutable snapshot of all recorded kill events.
+ ///
+ public IReadOnlyList History
+ {
+ get
+ {
+ lock (_lock)
+ {
+ return _history.ToList().AsReadOnly();
+ }
+ }
+ }
+
+ ///
+ /// Raised immediately after an agent is killed.
+ ///
+ public event EventHandler? OnKill;
+
+ ///
+ /// Arms the kill switch, enabling to terminate agents.
+ ///
+ public void Arm()
+ {
+ IsArmed = true;
+ }
+
+ ///
+ /// Disarms the kill switch. Subsequent calls to will throw.
+ ///
+ public void Disarm()
+ {
+ IsArmed = false;
+ }
+
+ ///
+ /// Terminates the specified agent, records the event, and raises .
+ ///
+ /// DID of the agent to terminate.
+ /// Reason for the kill.
+ /// Human-readable detail message.
+ /// The recorded .
+ /// Thrown when the switch is not armed.
+ public KillEvent Kill(string agentId, KillReason reason, string detail)
+ {
+ if (!IsArmed)
+ {
+ throw new InvalidOperationException("Kill switch is not armed.");
+ }
+
+ var killEvent = new KillEvent(agentId, reason, detail, DateTimeOffset.UtcNow);
+
+ lock (_lock)
+ {
+ _history.Add(killEvent);
+ }
+
+ OnKill?.Invoke(this, killEvent);
+ return killEvent;
+ }
+}
diff --git a/packages/agent-governance-dotnet/src/AgentGovernance/Lifecycle/LifecycleManager.cs b/packages/agent-governance-dotnet/src/AgentGovernance/Lifecycle/LifecycleManager.cs
new file mode 100644
index 00000000..ccf1bee1
--- /dev/null
+++ b/packages/agent-governance-dotnet/src/AgentGovernance/Lifecycle/LifecycleManager.cs
@@ -0,0 +1,200 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace AgentGovernance.Lifecycle;
+
+///
+/// Eight-state lifecycle model for governed agents.
+///
+public enum LifecycleState
+{
+ /// Agent is being provisioned and is not yet ready.
+ Provisioning,
+
+ /// Agent is running and accepting work.
+ Active,
+
+ /// Agent is temporarily paused (e.g., policy hold).
+ Suspended,
+
+ /// Agent credentials or keys are being rotated.
+ Rotating,
+
+ /// Agent is running in a degraded capacity.
+ Degraded,
+
+ /// Agent is isolated due to a security or trust concern.
+ Quarantined,
+
+ /// Agent shutdown is in progress.
+ Decommissioning,
+
+ /// Agent has been fully decommissioned.
+ Decommissioned
+}
+
+///
+/// Immutable record of a lifecycle state transition.
+///
+/// DID of the agent.
+/// Previous lifecycle state.
+/// New lifecycle state.
+/// Human-readable reason for the transition.
+/// UTC time of the transition.
+/// Identifier of the actor that initiated the transition.
+public record LifecycleEvent(
+ string AgentId,
+ LifecycleState FromState,
+ LifecycleState ToState,
+ string Reason,
+ DateTimeOffset Timestamp,
+ string InitiatedBy);
+
+///
+/// Manages the lifecycle of a single governed agent using an eight-state
+/// machine with validated transitions.
+///
+public class LifecycleManager
+{
+ private static readonly Dictionary> ValidTransitions = new()
+ {
+ [LifecycleState.Provisioning] = new()
+ {
+ LifecycleState.Active,
+ LifecycleState.Decommissioning
+ },
+ [LifecycleState.Active] = new()
+ {
+ LifecycleState.Suspended,
+ LifecycleState.Rotating,
+ LifecycleState.Degraded,
+ LifecycleState.Quarantined,
+ LifecycleState.Decommissioning
+ },
+ [LifecycleState.Suspended] = new()
+ {
+ LifecycleState.Active,
+ LifecycleState.Quarantined,
+ LifecycleState.Decommissioning
+ },
+ [LifecycleState.Rotating] = new()
+ {
+ LifecycleState.Active,
+ LifecycleState.Degraded,
+ LifecycleState.Decommissioning
+ },
+ [LifecycleState.Degraded] = new()
+ {
+ LifecycleState.Active,
+ LifecycleState.Quarantined,
+ LifecycleState.Decommissioning
+ },
+ [LifecycleState.Quarantined] = new()
+ {
+ LifecycleState.Active,
+ LifecycleState.Decommissioning
+ },
+ [LifecycleState.Decommissioning] = new()
+ {
+ LifecycleState.Decommissioned
+ },
+ [LifecycleState.Decommissioned] = new()
+ };
+
+ private readonly string _agentId;
+ private readonly List _events = new();
+ private readonly object _lock = new();
+
+ ///
+ /// Creates a new for the given agent.
+ /// The agent starts in .
+ ///
+ /// DID of the agent to manage.
+ public LifecycleManager(string agentId)
+ {
+ _agentId = agentId;
+ State = LifecycleState.Provisioning;
+ }
+
+ ///
+ /// Current lifecycle state of the agent.
+ ///
+ public LifecycleState State { get; private set; }
+
+ ///
+ /// Immutable snapshot of all recorded lifecycle events.
+ ///
+ public IReadOnlyList Events
+ {
+ get
+ {
+ lock (_lock)
+ {
+ return _events.ToList().AsReadOnly();
+ }
+ }
+ }
+
+ ///
+ /// Returns whether a transition from the current state to is valid.
+ ///
+ public bool CanTransition(LifecycleState toState)
+ {
+ return ValidTransitions.TryGetValue(State, out var targets) && targets.Contains(toState);
+ }
+
+ ///
+ /// Transitions the agent to a new state if the transition is valid.
+ ///
+ /// The target state.
+ /// Human-readable reason for the transition.
+ /// Identifier of the actor initiating the transition.
+ /// The recorded .
+ /// Thrown when the transition is not allowed.
+ public LifecycleEvent Transition(LifecycleState toState, string reason, string initiatedBy)
+ {
+ if (!CanTransition(toState))
+ {
+ throw new InvalidOperationException(
+ $"Cannot transition from {State} to {toState}.");
+ }
+
+ var fromState = State;
+ State = toState;
+
+ var evt = new LifecycleEvent(_agentId, fromState, toState, reason, DateTimeOffset.UtcNow, initiatedBy);
+
+ lock (_lock)
+ {
+ _events.Add(evt);
+ }
+
+ return evt;
+ }
+
+ // ── Convenience methods ──────────────────────────────────────
+
+ ///
+ /// Transitions the agent to .
+ ///
+ public LifecycleEvent Activate(string reason = "Ready")
+ => Transition(LifecycleState.Active, reason, "system");
+
+ ///
+ /// Transitions the agent to .
+ ///
+ public LifecycleEvent Suspend(string reason)
+ => Transition(LifecycleState.Suspended, reason, "system");
+
+ ///
+ /// Transitions the agent to .
+ ///
+ public LifecycleEvent Quarantine(string reason)
+ => Transition(LifecycleState.Quarantined, reason, "system");
+
+ ///
+ /// Transitions the agent to .
+ ///
+ public LifecycleEvent Decommission(string reason)
+ => Transition(LifecycleState.Decommissioning, reason, "system");
+}
diff --git a/packages/agent-governance-dotnet/tests/AgentGovernance.Tests/KillSwitchTests.cs b/packages/agent-governance-dotnet/tests/AgentGovernance.Tests/KillSwitchTests.cs
new file mode 100644
index 00000000..5f95e5cb
--- /dev/null
+++ b/packages/agent-governance-dotnet/tests/AgentGovernance.Tests/KillSwitchTests.cs
@@ -0,0 +1,128 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using AgentGovernance.Hypervisor;
+using Xunit;
+
+namespace AgentGovernance.Tests;
+
+public class KillSwitchTests
+{
+ [Fact]
+ public void NewKillSwitch_IsNotArmed()
+ {
+ var ks = new KillSwitch();
+
+ Assert.False(ks.IsArmed);
+ }
+
+ [Fact]
+ public void Arm_SetsIsArmedTrue()
+ {
+ var ks = new KillSwitch();
+ ks.Arm();
+
+ Assert.True(ks.IsArmed);
+ }
+
+ [Fact]
+ public void Disarm_SetsIsArmedFalse()
+ {
+ var ks = new KillSwitch();
+ ks.Arm();
+ ks.Disarm();
+
+ Assert.False(ks.IsArmed);
+ }
+
+ [Fact]
+ public void Kill_WhenDisarmed_Throws()
+ {
+ var ks = new KillSwitch();
+
+ Assert.Throws(
+ () => ks.Kill("agent-1", KillReason.ManualOverride, "test"));
+ }
+
+ [Fact]
+ public void Kill_WhenArmed_ReturnsEvent()
+ {
+ var ks = new KillSwitch();
+ ks.Arm();
+
+ var evt = ks.Kill("agent-1", KillReason.PolicyViolation, "exceeded scope");
+
+ Assert.Equal("agent-1", evt.AgentId);
+ Assert.Equal(KillReason.PolicyViolation, evt.Reason);
+ Assert.Equal("exceeded scope", evt.Detail);
+ Assert.True(evt.Timestamp <= DateTimeOffset.UtcNow);
+ }
+
+ [Fact]
+ public void Kill_FiresOnKillEvent()
+ {
+ var ks = new KillSwitch();
+ ks.Arm();
+
+ KillEvent? received = null;
+ ks.OnKill += (_, e) => received = e;
+
+ ks.Kill("agent-1", KillReason.AnomalyDetected, "drift");
+
+ Assert.NotNull(received);
+ Assert.Equal("agent-1", received!.AgentId);
+ Assert.Equal(KillReason.AnomalyDetected, received.Reason);
+ }
+
+ [Fact]
+ public void History_TracksAllKills()
+ {
+ var ks = new KillSwitch();
+ ks.Arm();
+
+ ks.Kill("agent-1", KillReason.PolicyViolation, "v1");
+ ks.Kill("agent-2", KillReason.TrustThreshold, "v2");
+ ks.Kill("agent-3", KillReason.ResourceExhaustion, "v3");
+
+ Assert.Equal(3, ks.History.Count);
+ Assert.Equal("agent-1", ks.History[0].AgentId);
+ Assert.Equal("agent-2", ks.History[1].AgentId);
+ Assert.Equal("agent-3", ks.History[2].AgentId);
+ }
+
+ [Fact]
+ public void History_IsEmpty_WhenNoKills()
+ {
+ var ks = new KillSwitch();
+
+ Assert.Empty(ks.History);
+ }
+
+ [Fact]
+ public void ArmDisarmArm_AllowsKillAfterRearm()
+ {
+ var ks = new KillSwitch();
+ ks.Arm();
+ ks.Disarm();
+ ks.Arm();
+
+ var evt = ks.Kill("agent-1", KillReason.ManualOverride, "re-armed");
+
+ Assert.Equal("agent-1", evt.AgentId);
+ }
+
+ [Fact]
+ public void Kill_AllReasons_Accepted()
+ {
+ var ks = new KillSwitch();
+ ks.Arm();
+
+ foreach (var reason in Enum.GetValues())
+ {
+ var evt = ks.Kill($"agent-{reason}", reason, reason.ToString());
+ Assert.Equal(reason, evt.Reason);
+ }
+
+ Assert.Equal(Enum.GetValues().Length, ks.History.Count);
+ }
+}
diff --git a/packages/agent-governance-dotnet/tests/AgentGovernance.Tests/LifecycleManagerTests.cs b/packages/agent-governance-dotnet/tests/AgentGovernance.Tests/LifecycleManagerTests.cs
new file mode 100644
index 00000000..dc89d030
--- /dev/null
+++ b/packages/agent-governance-dotnet/tests/AgentGovernance.Tests/LifecycleManagerTests.cs
@@ -0,0 +1,199 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using AgentGovernance.Lifecycle;
+using Xunit;
+
+namespace AgentGovernance.Tests;
+
+public class LifecycleManagerTests
+{
+ private const string AgentId = "did:mesh:lifecycle-test";
+
+ [Fact]
+ public void NewManager_StartsInProvisioning()
+ {
+ var mgr = new LifecycleManager(AgentId);
+
+ Assert.Equal(LifecycleState.Provisioning, mgr.State);
+ Assert.Empty(mgr.Events);
+ }
+
+ [Fact]
+ public void Activate_FromProvisioning_Succeeds()
+ {
+ var mgr = new LifecycleManager(AgentId);
+
+ var evt = mgr.Activate();
+
+ Assert.Equal(LifecycleState.Active, mgr.State);
+ Assert.Equal(LifecycleState.Provisioning, evt.FromState);
+ Assert.Equal(LifecycleState.Active, evt.ToState);
+ Assert.Equal("Ready", evt.Reason);
+ Assert.Equal(AgentId, evt.AgentId);
+ }
+
+ [Fact]
+ public void Suspend_FromActive_Succeeds()
+ {
+ var mgr = new LifecycleManager(AgentId);
+ mgr.Activate();
+
+ var evt = mgr.Suspend("policy hold");
+
+ Assert.Equal(LifecycleState.Suspended, mgr.State);
+ Assert.Equal("policy hold", evt.Reason);
+ }
+
+ [Fact]
+ public void Quarantine_FromActive_Succeeds()
+ {
+ var mgr = new LifecycleManager(AgentId);
+ mgr.Activate();
+
+ var evt = mgr.Quarantine("trust breach");
+
+ Assert.Equal(LifecycleState.Quarantined, mgr.State);
+ Assert.Equal("trust breach", evt.Reason);
+ }
+
+ [Fact]
+ public void Decommission_FromActive_Succeeds()
+ {
+ var mgr = new LifecycleManager(AgentId);
+ mgr.Activate();
+
+ var evt = mgr.Decommission("end of life");
+
+ Assert.Equal(LifecycleState.Decommissioning, mgr.State);
+ Assert.Equal("end of life", evt.Reason);
+ }
+
+ [Fact]
+ public void FullLifecycle_ProvisionToDecommissioned()
+ {
+ var mgr = new LifecycleManager(AgentId);
+
+ mgr.Activate();
+ mgr.Suspend("maintenance");
+ mgr.Transition(LifecycleState.Active, "maintenance complete", "ops");
+ mgr.Decommission("retiring");
+ mgr.Transition(LifecycleState.Decommissioned, "done", "ops");
+
+ Assert.Equal(LifecycleState.Decommissioned, mgr.State);
+ Assert.Equal(5, mgr.Events.Count);
+ }
+
+ [Fact]
+ public void InvalidTransition_Throws()
+ {
+ var mgr = new LifecycleManager(AgentId);
+
+ // Provisioning → Quarantined is not allowed
+ Assert.Throws(
+ () => mgr.Quarantine("should fail"));
+ }
+
+ [Fact]
+ public void Decommissioned_CannotTransition()
+ {
+ var mgr = new LifecycleManager(AgentId);
+ mgr.Activate();
+ mgr.Decommission("retiring");
+ mgr.Transition(LifecycleState.Decommissioned, "done", "ops");
+
+ Assert.Throws(
+ () => mgr.Activate("should fail"));
+ }
+
+ [Fact]
+ public void CanTransition_ReturnsCorrectly()
+ {
+ var mgr = new LifecycleManager(AgentId);
+
+ Assert.True(mgr.CanTransition(LifecycleState.Active));
+ Assert.True(mgr.CanTransition(LifecycleState.Decommissioning));
+ Assert.False(mgr.CanTransition(LifecycleState.Suspended));
+ Assert.False(mgr.CanTransition(LifecycleState.Quarantined));
+ }
+
+ [Fact]
+ public void Transition_RecordsInitiatedBy()
+ {
+ var mgr = new LifecycleManager(AgentId);
+
+ var evt = mgr.Transition(LifecycleState.Active, "init", "admin-user");
+
+ Assert.Equal("admin-user", evt.InitiatedBy);
+ }
+
+ [Fact]
+ public void ConvenienceMethods_UseSystemAsInitiator()
+ {
+ var mgr = new LifecycleManager(AgentId);
+ var evt = mgr.Activate();
+
+ Assert.Equal("system", evt.InitiatedBy);
+ }
+
+ [Fact]
+ public void Events_AreImmutableSnapshot()
+ {
+ var mgr = new LifecycleManager(AgentId);
+ mgr.Activate();
+
+ var snapshot = mgr.Events;
+ mgr.Suspend("pause");
+
+ // Snapshot should not reflect the new event
+ Assert.Single(snapshot);
+ Assert.Equal(2, mgr.Events.Count);
+ }
+
+ [Fact]
+ public void Rotating_FromActive_Succeeds()
+ {
+ var mgr = new LifecycleManager(AgentId);
+ mgr.Activate();
+
+ var evt = mgr.Transition(LifecycleState.Rotating, "key rotation", "security");
+
+ Assert.Equal(LifecycleState.Rotating, mgr.State);
+ Assert.Equal("key rotation", evt.Reason);
+ }
+
+ [Fact]
+ public void Degraded_FromActive_Succeeds()
+ {
+ var mgr = new LifecycleManager(AgentId);
+ mgr.Activate();
+
+ var evt = mgr.Transition(LifecycleState.Degraded, "partial failure", "monitor");
+
+ Assert.Equal(LifecycleState.Degraded, mgr.State);
+ }
+
+ [Fact]
+ public void Quarantine_FromDegraded_Succeeds()
+ {
+ var mgr = new LifecycleManager(AgentId);
+ mgr.Activate();
+ mgr.Transition(LifecycleState.Degraded, "failing", "monitor");
+
+ mgr.Quarantine("escalation");
+
+ Assert.Equal(LifecycleState.Quarantined, mgr.State);
+ }
+
+ [Fact]
+ public void Active_FromQuarantined_Succeeds()
+ {
+ var mgr = new LifecycleManager(AgentId);
+ mgr.Activate();
+ mgr.Quarantine("incident");
+
+ mgr.Transition(LifecycleState.Active, "cleared", "ops");
+
+ Assert.Equal(LifecycleState.Active, mgr.State);
+ }
+}
diff --git a/packages/agent-mesh/sdks/rust/agentmesh/README.md b/packages/agent-mesh/sdks/rust/agentmesh/README.md
index b916cbba..2e1a07ea 100644
--- a/packages/agent-mesh/sdks/rust/agentmesh/README.md
+++ b/packages/agent-mesh/sdks/rust/agentmesh/README.md
@@ -195,6 +195,70 @@ policies:
window: "60s"
```
+### Execution Rings (`rings.rs`)
+
+Four-level privilege model inspired by hardware protection rings.
+
+| Function / Method | Description |
+|---|---|
+| `RingEnforcer::new()` | Create a new enforcer with no assignments |
+| `enforcer.assign(agent_id, ring)` | Assign an agent to a ring |
+| `enforcer.get_ring(agent_id)` | Get assigned ring (if any) |
+| `enforcer.check_access(agent_id, action)` | Check if action is permitted |
+| `enforcer.set_ring_permissions(ring, actions)` | Configure allowed actions for a ring |
+
+Ring levels:
+
+| Ring | Level | Access |
+|------|-------|--------|
+| `Admin` | 0 | All actions allowed |
+| `Standard` | 1 | Configurable actions |
+| `Restricted` | 2 | Configurable actions |
+| `Sandboxed` | 3 | All actions denied |
+
+```rust
+use agentmesh::{RingEnforcer, Ring};
+
+let mut enforcer = RingEnforcer::new();
+enforcer.set_ring_permissions(Ring::Standard, vec!["data.read".into(), "data.write".into()]);
+enforcer.assign("my-agent", Ring::Standard);
+
+assert!(enforcer.check_access("my-agent", "data.read"));
+assert!(!enforcer.check_access("my-agent", "shell:rm"));
+```
+
+### Agent Lifecycle (`lifecycle.rs`)
+
+Eight-state lifecycle model tracking an agent from provisioning through decommissioning.
+
+| Function / Method | Description |
+|---|---|
+| `LifecycleManager::new(agent_id)` | Create a new manager (starts in `Provisioning`) |
+| `manager.state()` | Get current lifecycle state |
+| `manager.events()` | Get recorded transition events |
+| `manager.transition(to, reason, initiated_by)` | Transition to a new state |
+| `manager.can_transition(to)` | Check if a transition is valid |
+| `manager.activate(reason)` | Convenience: transition to `Active` |
+| `manager.suspend(reason)` | Convenience: transition to `Suspended` |
+| `manager.quarantine(reason)` | Convenience: transition to `Quarantined` |
+| `manager.decommission(reason)` | Convenience: transition to `Decommissioning` |
+
+Lifecycle states: `Provisioning` -> `Active` <-> `Suspended` / `Rotating` / `Degraded` -> `Quarantined` -> `Decommissioning` -> `Decommissioned`
+
+```rust
+use agentmesh::{LifecycleManager, LifecycleState};
+
+let mut mgr = LifecycleManager::new("my-agent");
+mgr.activate("initial boot").unwrap();
+assert_eq!(mgr.state(), LifecycleState::Active);
+
+mgr.suspend("maintenance window").unwrap();
+assert_eq!(mgr.state(), LifecycleState::Suspended);
+
+mgr.activate("maintenance complete").unwrap();
+assert_eq!(mgr.events().len(), 3);
+```
+
## License
See repository root [LICENSE](../../../../LICENSE).
diff --git a/packages/agent-mesh/sdks/rust/agentmesh/src/lib.rs b/packages/agent-mesh/sdks/rust/agentmesh/src/lib.rs
index af1f5c36..22ea34ee 100644
--- a/packages/agent-mesh/sdks/rust/agentmesh/src/lib.rs
+++ b/packages/agent-mesh/sdks/rust/agentmesh/src/lib.rs
@@ -20,15 +20,19 @@
pub mod audit;
pub mod identity;
+pub mod lifecycle;
pub mod mcp;
pub mod policy;
+pub mod rings;
pub mod trust;
pub mod types;
pub use audit::AuditLogger;
pub use identity::{AgentIdentity, PublicIdentity};
+pub use lifecycle::{LifecycleEvent, LifecycleManager, LifecycleState};
pub use mcp::*;
pub use policy::{PolicyEngine, PolicyError};
+pub use rings::{Ring, RingEnforcer};
pub use trust::{TrustConfig, TrustManager};
pub use types::{
AuditEntry, AuditFilter, CandidateDecision, ConflictResolutionStrategy, GovernanceResult,
diff --git a/packages/agent-mesh/sdks/rust/agentmesh/src/lifecycle.rs b/packages/agent-mesh/sdks/rust/agentmesh/src/lifecycle.rs
new file mode 100644
index 00000000..a40bffee
--- /dev/null
+++ b/packages/agent-mesh/sdks/rust/agentmesh/src/lifecycle.rs
@@ -0,0 +1,350 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+//! Agent lifecycle management -- an eight-state model tracking an agent from
+//! provisioning through decommissioning.
+
+use serde::{Deserialize, Serialize};
+use std::time::{SystemTime, UNIX_EPOCH};
+
+/// The eight lifecycle states an agent can occupy.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
+pub enum LifecycleState {
+ /// Agent is being provisioned (initial state).
+ Provisioning,
+ /// Agent is fully operational.
+ Active,
+ /// Agent is temporarily suspended.
+ Suspended,
+ /// Agent credentials are being rotated.
+ Rotating,
+ /// Agent is running in a degraded mode.
+ Degraded,
+ /// Agent has been quarantined due to policy violations or anomalies.
+ Quarantined,
+ /// Agent is in the process of being decommissioned.
+ Decommissioning,
+ /// Agent has been permanently decommissioned (terminal state).
+ Decommissioned,
+}
+
+/// A recorded lifecycle transition event.
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct LifecycleEvent {
+ /// State before the transition.
+ pub from: LifecycleState,
+ /// State after the transition.
+ pub to: LifecycleState,
+ /// Human-readable reason for the transition.
+ pub reason: String,
+ /// Who or what initiated the transition.
+ pub initiated_by: String,
+ /// Unix timestamp (seconds) when the transition occurred.
+ pub timestamp: u64,
+}
+
+/// Manages the lifecycle of a single agent.
+pub struct LifecycleManager {
+ agent_id: String,
+ state: LifecycleState,
+ events: Vec,
+}
+
+impl LifecycleManager {
+ /// Create a new lifecycle manager for the given agent.
+ ///
+ /// The initial state is [`LifecycleState::Provisioning`].
+ pub fn new(agent_id: &str) -> Self {
+ Self {
+ agent_id: agent_id.to_string(),
+ state: LifecycleState::Provisioning,
+ events: Vec::new(),
+ }
+ }
+
+ /// Return the current lifecycle state.
+ pub fn state(&self) -> LifecycleState {
+ self.state
+ }
+
+ /// Return the agent identifier.
+ pub fn agent_id(&self) -> &str {
+ &self.agent_id
+ }
+
+ /// Return all recorded lifecycle events.
+ pub fn events(&self) -> &[LifecycleEvent] {
+ &self.events
+ }
+
+ /// Attempt to transition the agent to `to`.
+ ///
+ /// Returns the resulting [`LifecycleEvent`] on success, or an error
+ /// message describing why the transition is not allowed.
+ pub fn transition(
+ &mut self,
+ to: LifecycleState,
+ reason: &str,
+ initiated_by: &str,
+ ) -> Result<&LifecycleEvent, String> {
+ if !self.can_transition(to) {
+ return Err(format!(
+ "invalid transition from {:?} to {:?}",
+ self.state, to
+ ));
+ }
+
+ let event = LifecycleEvent {
+ from: self.state,
+ to,
+ reason: reason.to_string(),
+ initiated_by: initiated_by.to_string(),
+ timestamp: epoch_now(),
+ };
+ self.state = to;
+ self.events.push(event);
+ Ok(self.events.last().expect("just pushed"))
+ }
+
+ /// Check whether transitioning from the current state to `to` is valid.
+ pub fn can_transition(&self, to: LifecycleState) -> bool {
+ allowed_transitions(self.state).contains(&to)
+ }
+
+ /// Convenience: transition to [`LifecycleState::Active`].
+ pub fn activate(&mut self, reason: &str) -> Result<&LifecycleEvent, String> {
+ self.transition(LifecycleState::Active, reason, "system")
+ }
+
+ /// Convenience: transition to [`LifecycleState::Suspended`].
+ pub fn suspend(&mut self, reason: &str) -> Result<&LifecycleEvent, String> {
+ self.transition(LifecycleState::Suspended, reason, "system")
+ }
+
+ /// Convenience: transition to [`LifecycleState::Quarantined`].
+ pub fn quarantine(&mut self, reason: &str) -> Result<&LifecycleEvent, String> {
+ self.transition(LifecycleState::Quarantined, reason, "system")
+ }
+
+ /// Convenience: transition to [`LifecycleState::Decommissioning`].
+ pub fn decommission(&mut self, reason: &str) -> Result<&LifecycleEvent, String> {
+ self.transition(LifecycleState::Decommissioning, reason, "system")
+ }
+}
+
+/// Return the set of states reachable from `from`.
+fn allowed_transitions(from: LifecycleState) -> &'static [LifecycleState] {
+ use LifecycleState::*;
+ match from {
+ Provisioning => &[Active],
+ Active => &[Suspended, Rotating, Degraded, Decommissioning],
+ Suspended => &[Active, Decommissioning],
+ Rotating => &[Active],
+ Degraded => &[Active, Quarantined, Decommissioning],
+ Quarantined => &[Active, Decommissioning],
+ Decommissioning => &[Decommissioned],
+ Decommissioned => &[],
+ }
+}
+
+fn epoch_now() -> u64 {
+ SystemTime::now()
+ .duration_since(UNIX_EPOCH)
+ .unwrap_or_default()
+ .as_secs()
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_initial_state_is_provisioning() {
+ let mgr = LifecycleManager::new("agent-1");
+ assert_eq!(mgr.state(), LifecycleState::Provisioning);
+ assert_eq!(mgr.agent_id(), "agent-1");
+ assert!(mgr.events().is_empty());
+ }
+
+ #[test]
+ fn test_activate_from_provisioning() {
+ let mut mgr = LifecycleManager::new("agent-1");
+ let event = mgr.activate("initial activation").unwrap();
+ assert_eq!(event.from, LifecycleState::Provisioning);
+ assert_eq!(event.to, LifecycleState::Active);
+ assert_eq!(event.reason, "initial activation");
+ assert_eq!(mgr.state(), LifecycleState::Active);
+ }
+
+ #[test]
+ fn test_suspend_from_active() {
+ let mut mgr = LifecycleManager::new("agent-1");
+ mgr.activate("boot").unwrap();
+ let event = mgr.suspend("maintenance window").unwrap();
+ assert_eq!(event.from, LifecycleState::Active);
+ assert_eq!(event.to, LifecycleState::Suspended);
+ assert_eq!(mgr.state(), LifecycleState::Suspended);
+ }
+
+ #[test]
+ fn test_reactivate_from_suspended() {
+ let mut mgr = LifecycleManager::new("agent-1");
+ mgr.activate("boot").unwrap();
+ mgr.suspend("pause").unwrap();
+ let event = mgr.activate("resume").unwrap();
+ assert_eq!(event.from, LifecycleState::Suspended);
+ assert_eq!(event.to, LifecycleState::Active);
+ }
+
+ #[test]
+ fn test_quarantine_from_degraded() {
+ let mut mgr = LifecycleManager::new("agent-1");
+ mgr.activate("boot").unwrap();
+ mgr.transition(LifecycleState::Degraded, "high error rate", "monitor")
+ .unwrap();
+ let event = mgr.quarantine("policy violation detected").unwrap();
+ assert_eq!(event.from, LifecycleState::Degraded);
+ assert_eq!(event.to, LifecycleState::Quarantined);
+ }
+
+ #[test]
+ fn test_decommission_flow() {
+ let mut mgr = LifecycleManager::new("agent-1");
+ mgr.activate("boot").unwrap();
+ mgr.decommission("end of life").unwrap();
+ assert_eq!(mgr.state(), LifecycleState::Decommissioning);
+
+ mgr.transition(LifecycleState::Decommissioned, "cleanup done", "system")
+ .unwrap();
+ assert_eq!(mgr.state(), LifecycleState::Decommissioned);
+ }
+
+ #[test]
+ fn test_decommissioned_is_terminal() {
+ let mut mgr = LifecycleManager::new("agent-1");
+ mgr.activate("boot").unwrap();
+ mgr.decommission("bye").unwrap();
+ mgr.transition(LifecycleState::Decommissioned, "done", "system")
+ .unwrap();
+
+ let result = mgr.activate("try again");
+ assert!(result.is_err());
+ assert!(result
+ .unwrap_err()
+ .contains("invalid transition from Decommissioned"));
+ }
+
+ #[test]
+ fn test_invalid_transition_from_provisioning() {
+ let mut mgr = LifecycleManager::new("agent-1");
+ let result = mgr.suspend("not allowed");
+ assert!(result.is_err());
+ }
+
+ #[test]
+ fn test_invalid_transition_returns_descriptive_error() {
+ let mut mgr = LifecycleManager::new("agent-1");
+ let err = mgr.suspend("nope").unwrap_err();
+ assert!(err.contains("Provisioning"));
+ assert!(err.contains("Suspended"));
+ }
+
+ #[test]
+ fn test_can_transition_returns_true_for_valid() {
+ let mut mgr = LifecycleManager::new("agent-1");
+ assert!(mgr.can_transition(LifecycleState::Active));
+ assert!(!mgr.can_transition(LifecycleState::Suspended));
+
+ mgr.activate("boot").unwrap();
+ assert!(mgr.can_transition(LifecycleState::Suspended));
+ assert!(mgr.can_transition(LifecycleState::Rotating));
+ assert!(mgr.can_transition(LifecycleState::Degraded));
+ assert!(mgr.can_transition(LifecycleState::Decommissioning));
+ assert!(!mgr.can_transition(LifecycleState::Quarantined));
+ }
+
+ #[test]
+ fn test_rotating_returns_to_active() {
+ let mut mgr = LifecycleManager::new("agent-1");
+ mgr.activate("boot").unwrap();
+ mgr.transition(LifecycleState::Rotating, "key rotation", "security")
+ .unwrap();
+ assert_eq!(mgr.state(), LifecycleState::Rotating);
+
+ mgr.activate("rotation complete").unwrap();
+ assert_eq!(mgr.state(), LifecycleState::Active);
+ }
+
+ #[test]
+ fn test_event_history_records_all_transitions() {
+ let mut mgr = LifecycleManager::new("agent-1");
+ mgr.activate("boot").unwrap();
+ mgr.suspend("pause").unwrap();
+ mgr.activate("resume").unwrap();
+
+ let events = mgr.events();
+ assert_eq!(events.len(), 3);
+ assert_eq!(events[0].to, LifecycleState::Active);
+ assert_eq!(events[1].to, LifecycleState::Suspended);
+ assert_eq!(events[2].to, LifecycleState::Active);
+ }
+
+ #[test]
+ fn test_event_timestamps_are_monotonic() {
+ let mut mgr = LifecycleManager::new("agent-1");
+ mgr.activate("boot").unwrap();
+ mgr.suspend("pause").unwrap();
+ mgr.activate("resume").unwrap();
+
+ let events = mgr.events();
+ for window in events.windows(2) {
+ assert!(window[1].timestamp >= window[0].timestamp);
+ }
+ }
+
+ #[test]
+ fn test_lifecycle_state_serde_roundtrip() {
+ let state = LifecycleState::Quarantined;
+ let json = serde_json::to_string(&state).unwrap();
+ let deserialized: LifecycleState = serde_json::from_str(&json).unwrap();
+ assert_eq!(state, deserialized);
+ }
+
+ #[test]
+ fn test_lifecycle_event_serde_roundtrip() {
+ let event = LifecycleEvent {
+ from: LifecycleState::Active,
+ to: LifecycleState::Suspended,
+ reason: "maintenance".to_string(),
+ initiated_by: "admin".to_string(),
+ timestamp: 1700000000,
+ };
+ let json = serde_json::to_string(&event).unwrap();
+ let deserialized: LifecycleEvent = serde_json::from_str(&json).unwrap();
+ assert_eq!(deserialized.from, event.from);
+ assert_eq!(deserialized.to, event.to);
+ assert_eq!(deserialized.reason, event.reason);
+ }
+
+ #[test]
+ fn test_quarantined_can_reactivate() {
+ let mut mgr = LifecycleManager::new("agent-1");
+ mgr.activate("boot").unwrap();
+ mgr.transition(LifecycleState::Degraded, "issues", "monitor")
+ .unwrap();
+ mgr.quarantine("violation").unwrap();
+ mgr.activate("cleared").unwrap();
+ assert_eq!(mgr.state(), LifecycleState::Active);
+ }
+
+ #[test]
+ fn test_quarantined_can_decommission() {
+ let mut mgr = LifecycleManager::new("agent-1");
+ mgr.activate("boot").unwrap();
+ mgr.transition(LifecycleState::Degraded, "issues", "monitor")
+ .unwrap();
+ mgr.quarantine("violation").unwrap();
+ mgr.decommission("permanent removal").unwrap();
+ assert_eq!(mgr.state(), LifecycleState::Decommissioning);
+ }
+}
diff --git a/packages/agent-mesh/sdks/rust/agentmesh/src/rings.rs b/packages/agent-mesh/sdks/rust/agentmesh/src/rings.rs
new file mode 100644
index 00000000..8fa6d8cf
--- /dev/null
+++ b/packages/agent-mesh/sdks/rust/agentmesh/src/rings.rs
@@ -0,0 +1,232 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+//! Execution privilege rings — a four-level access-control model inspired by
+//! hardware protection rings.
+//!
+//! | Ring | Level | Description |
+//! |------|-------|-------------|
+//! | `Admin` | 0 | Full tool access |
+//! | `Standard` | 1 | Scoped tool access (configurable) |
+//! | `Restricted` | 2 | Read-only + approved writes (configurable) |
+//! | `Sandboxed` | 3 | No external access |
+
+use serde::{Deserialize, Serialize};
+use std::collections::HashMap;
+
+/// Execution privilege ring.
+///
+/// Lower numeric values imply higher privilege — matching the classic
+/// ring-0 / ring-3 convention used in OS kernels.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
+pub enum Ring {
+ /// Ring 0 — full tool access.
+ Admin = 0,
+ /// Ring 1 — scoped tool access (actions are configurable).
+ Standard = 1,
+ /// Ring 2 — read-only plus approved writes (actions are configurable).
+ Restricted = 2,
+ /// Ring 3 — no external access.
+ Sandboxed = 3,
+}
+
+/// Manages per-agent ring assignments and per-ring action permissions.
+///
+/// # Access semantics
+///
+/// * **Ring 0 (`Admin`)** — every action is implicitly allowed.
+/// * **Ring 3 (`Sandboxed`)** — every action is implicitly denied.
+/// * **Ring 1 / Ring 2** — allowed only if the action appears in the
+/// ring's configured permission set.
+/// * Unknown agents (not yet assigned) are denied by default.
+pub struct RingEnforcer {
+ assignments: HashMap,
+ permissions: HashMap>,
+}
+
+impl RingEnforcer {
+ /// Create a new enforcer with no assignments and no custom permissions.
+ pub fn new() -> Self {
+ Self {
+ assignments: HashMap::new(),
+ permissions: HashMap::new(),
+ }
+ }
+
+ /// Assign an agent to a specific ring.
+ pub fn assign(&mut self, agent_id: &str, ring: Ring) {
+ self.assignments.insert(agent_id.to_string(), ring);
+ }
+
+ /// Return the ring currently assigned to the agent, if any.
+ pub fn get_ring(&self, agent_id: &str) -> Option {
+ self.assignments.get(agent_id).copied()
+ }
+
+ /// Check whether `agent_id` is allowed to perform `action`.
+ ///
+ /// Returns `false` for unknown agents.
+ pub fn check_access(&self, agent_id: &str, action: &str) -> bool {
+ match self.get_ring(agent_id) {
+ Some(Ring::Admin) => true,
+ Some(Ring::Sandboxed) => false,
+ Some(ring) => self
+ .permissions
+ .get(&ring)
+ .map_or(false, |allowed| allowed.iter().any(|a| a == action)),
+ None => false,
+ }
+ }
+
+ /// Configure the set of allowed actions for a given ring.
+ ///
+ /// Only meaningful for `Standard` and `Restricted` rings — `Admin`
+ /// always allows and `Sandboxed` always denies regardless of this
+ /// setting.
+ pub fn set_ring_permissions(&mut self, ring: Ring, allowed_actions: Vec) {
+ self.permissions.insert(ring, allowed_actions);
+ }
+}
+
+impl Default for RingEnforcer {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_admin_ring_allows_everything() {
+ let mut enforcer = RingEnforcer::new();
+ enforcer.assign("root-agent", Ring::Admin);
+ assert!(enforcer.check_access("root-agent", "any.action"));
+ assert!(enforcer.check_access("root-agent", "shell:rm"));
+ assert!(enforcer.check_access("root-agent", "deploy.prod"));
+ }
+
+ #[test]
+ fn test_sandboxed_ring_denies_everything() {
+ let mut enforcer = RingEnforcer::new();
+ enforcer.assign("sandbox-agent", Ring::Sandboxed);
+ assert!(!enforcer.check_access("sandbox-agent", "data.read"));
+ assert!(!enforcer.check_access("sandbox-agent", "any.action"));
+ }
+
+ #[test]
+ fn test_standard_ring_with_permissions() {
+ let mut enforcer = RingEnforcer::new();
+ enforcer.assign("agent-1", Ring::Standard);
+ enforcer.set_ring_permissions(
+ Ring::Standard,
+ vec!["data.read".to_string(), "data.write".to_string()],
+ );
+
+ assert!(enforcer.check_access("agent-1", "data.read"));
+ assert!(enforcer.check_access("agent-1", "data.write"));
+ assert!(!enforcer.check_access("agent-1", "shell:rm"));
+ }
+
+ #[test]
+ fn test_restricted_ring_with_permissions() {
+ let mut enforcer = RingEnforcer::new();
+ enforcer.assign("agent-2", Ring::Restricted);
+ enforcer.set_ring_permissions(Ring::Restricted, vec!["data.read".to_string()]);
+
+ assert!(enforcer.check_access("agent-2", "data.read"));
+ assert!(!enforcer.check_access("agent-2", "data.write"));
+ }
+
+ #[test]
+ fn test_unknown_agent_denied() {
+ let enforcer = RingEnforcer::new();
+ assert!(!enforcer.check_access("unknown-agent", "data.read"));
+ }
+
+ #[test]
+ fn test_get_ring_returns_none_for_unknown() {
+ let enforcer = RingEnforcer::new();
+ assert_eq!(enforcer.get_ring("unknown"), None);
+ }
+
+ #[test]
+ fn test_get_ring_returns_assigned() {
+ let mut enforcer = RingEnforcer::new();
+ enforcer.assign("admin-agent", Ring::Admin);
+ enforcer.assign("sandbox-agent", Ring::Sandboxed);
+ assert_eq!(enforcer.get_ring("admin-agent"), Some(Ring::Admin));
+ assert_eq!(enforcer.get_ring("sandbox-agent"), Some(Ring::Sandboxed));
+ }
+
+ #[test]
+ fn test_reassign_ring_overrides() {
+ let mut enforcer = RingEnforcer::new();
+ enforcer.assign("agent", Ring::Admin);
+ assert!(enforcer.check_access("agent", "anything"));
+
+ enforcer.assign("agent", Ring::Sandboxed);
+ assert!(!enforcer.check_access("agent", "anything"));
+ }
+
+ #[test]
+ fn test_standard_ring_no_permissions_denies() {
+ let mut enforcer = RingEnforcer::new();
+ enforcer.assign("agent", Ring::Standard);
+ // No permissions configured for Standard → deny
+ assert!(!enforcer.check_access("agent", "data.read"));
+ }
+
+ #[test]
+ fn test_ring_ordering() {
+ assert!(Ring::Admin < Ring::Standard);
+ assert!(Ring::Standard < Ring::Restricted);
+ assert!(Ring::Restricted < Ring::Sandboxed);
+ }
+
+ #[test]
+ fn test_ring_serde_roundtrip() {
+ let ring = Ring::Restricted;
+ let json = serde_json::to_string(&ring).unwrap();
+ let deserialized: Ring = serde_json::from_str(&json).unwrap();
+ assert_eq!(ring, deserialized);
+ }
+
+ #[test]
+ fn test_multiple_agents_different_rings() {
+ let mut enforcer = RingEnforcer::new();
+ enforcer.assign("admin", Ring::Admin);
+ enforcer.assign("standard", Ring::Standard);
+ enforcer.assign("restricted", Ring::Restricted);
+ enforcer.assign("sandboxed", Ring::Sandboxed);
+
+ enforcer.set_ring_permissions(Ring::Standard, vec!["data.read".to_string()]);
+ enforcer.set_ring_permissions(Ring::Restricted, vec!["data.read".to_string()]);
+
+ assert!(enforcer.check_access("admin", "shell:rm"));
+ assert!(enforcer.check_access("standard", "data.read"));
+ assert!(!enforcer.check_access("standard", "shell:rm"));
+ assert!(enforcer.check_access("restricted", "data.read"));
+ assert!(!enforcer.check_access("restricted", "shell:rm"));
+ assert!(!enforcer.check_access("sandboxed", "data.read"));
+ }
+
+ #[test]
+ fn test_set_ring_permissions_replaces_previous() {
+ let mut enforcer = RingEnforcer::new();
+ enforcer.assign("agent", Ring::Standard);
+ enforcer.set_ring_permissions(Ring::Standard, vec!["data.read".to_string()]);
+ assert!(enforcer.check_access("agent", "data.read"));
+
+ enforcer.set_ring_permissions(Ring::Standard, vec!["data.write".to_string()]);
+ assert!(!enforcer.check_access("agent", "data.read"));
+ assert!(enforcer.check_access("agent", "data.write"));
+ }
+
+ #[test]
+ fn test_default_impl() {
+ let enforcer = RingEnforcer::default();
+ assert_eq!(enforcer.get_ring("any"), None);
+ }
+}