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
57 changes: 57 additions & 0 deletions packages/agent-governance-dotnet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace AgentGovernance.Hypervisor;

/// <summary>
/// Reason an agent was terminated via the kill switch.
/// </summary>
public enum KillReason
{
/// <summary>Agent violated a governance policy.</summary>
PolicyViolation,

/// <summary>Agent's trust score dropped below the acceptable threshold.</summary>
TrustThreshold,

/// <summary>A human operator manually triggered the kill.</summary>
ManualOverride,

/// <summary>Anomalous behaviour was detected by the monitoring system.</summary>
AnomalyDetected,

/// <summary>Agent exceeded resource consumption limits.</summary>
ResourceExhaustion
}

/// <summary>
/// Immutable record of a single kill switch activation.
/// </summary>
/// <param name="AgentId">DID of the terminated agent.</param>
/// <param name="Reason">Why the agent was killed.</param>
/// <param name="Detail">Human-readable detail message.</param>
/// <param name="Timestamp">UTC time the kill occurred.</param>
public record KillEvent(string AgentId, KillReason Reason, string Detail, DateTimeOffset Timestamp);

/// <summary>
/// Agent kill switch that can be armed/disarmed.
/// When armed, calling <see cref="Kill"/> terminates the target agent,
/// records the event, and notifies subscribers.
/// </summary>
public class KillSwitch
{
private readonly List<KillEvent> _history = new();
private readonly object _lock = new();

/// <summary>
/// Whether the kill switch is currently armed. Only armed switches can kill agents.
/// </summary>
public bool IsArmed { get; private set; }

/// <summary>
/// Immutable snapshot of all recorded kill events.
/// </summary>
public IReadOnlyList<KillEvent> History
{
get
{
lock (_lock)
{
return _history.ToList().AsReadOnly();
}
}
}

/// <summary>
/// Raised immediately after an agent is killed.
/// </summary>
public event EventHandler<KillEvent>? OnKill;

/// <summary>
/// Arms the kill switch, enabling <see cref="Kill"/> to terminate agents.
/// </summary>
public void Arm()
{
IsArmed = true;
}

/// <summary>
/// Disarms the kill switch. Subsequent calls to <see cref="Kill"/> will throw.
/// </summary>
public void Disarm()
{
IsArmed = false;
}

/// <summary>
/// Terminates the specified agent, records the event, and raises <see cref="OnKill"/>.
/// </summary>
/// <param name="agentId">DID of the agent to terminate.</param>
/// <param name="reason">Reason for the kill.</param>
/// <param name="detail">Human-readable detail message.</param>
/// <returns>The recorded <see cref="KillEvent"/>.</returns>
/// <exception cref="InvalidOperationException">Thrown when the switch is not armed.</exception>
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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace AgentGovernance.Lifecycle;

/// <summary>
/// Eight-state lifecycle model for governed agents.
/// </summary>
public enum LifecycleState
{
/// <summary>Agent is being provisioned and is not yet ready.</summary>
Provisioning,

/// <summary>Agent is running and accepting work.</summary>
Active,

/// <summary>Agent is temporarily paused (e.g., policy hold).</summary>
Suspended,

/// <summary>Agent credentials or keys are being rotated.</summary>
Rotating,

/// <summary>Agent is running in a degraded capacity.</summary>
Degraded,

/// <summary>Agent is isolated due to a security or trust concern.</summary>
Quarantined,

/// <summary>Agent shutdown is in progress.</summary>
Decommissioning,

/// <summary>Agent has been fully decommissioned.</summary>
Decommissioned
}

/// <summary>
/// Immutable record of a lifecycle state transition.
/// </summary>
/// <param name="AgentId">DID of the agent.</param>
/// <param name="FromState">Previous lifecycle state.</param>
/// <param name="ToState">New lifecycle state.</param>
/// <param name="Reason">Human-readable reason for the transition.</param>
/// <param name="Timestamp">UTC time of the transition.</param>
/// <param name="InitiatedBy">Identifier of the actor that initiated the transition.</param>
public record LifecycleEvent(
string AgentId,
LifecycleState FromState,
LifecycleState ToState,
string Reason,
DateTimeOffset Timestamp,
string InitiatedBy);

/// <summary>
/// Manages the lifecycle of a single governed agent using an eight-state
/// machine with validated transitions.
/// </summary>
public class LifecycleManager
{
private static readonly Dictionary<LifecycleState, HashSet<LifecycleState>> 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<LifecycleEvent> _events = new();
private readonly object _lock = new();

/// <summary>
/// Creates a new <see cref="LifecycleManager"/> for the given agent.
/// The agent starts in <see cref="LifecycleState.Provisioning"/>.
/// </summary>
/// <param name="agentId">DID of the agent to manage.</param>
public LifecycleManager(string agentId)
{
_agentId = agentId;
State = LifecycleState.Provisioning;
}

/// <summary>
/// Current lifecycle state of the agent.
/// </summary>
public LifecycleState State { get; private set; }

/// <summary>
/// Immutable snapshot of all recorded lifecycle events.
/// </summary>
public IReadOnlyList<LifecycleEvent> Events
{
get
{
lock (_lock)
{
return _events.ToList().AsReadOnly();
}
}
}

/// <summary>
/// Returns whether a transition from the current state to <paramref name="toState"/> is valid.
/// </summary>
public bool CanTransition(LifecycleState toState)
{
return ValidTransitions.TryGetValue(State, out var targets) && targets.Contains(toState);
}

/// <summary>
/// Transitions the agent to a new state if the transition is valid.
/// </summary>
/// <param name="toState">The target state.</param>
/// <param name="reason">Human-readable reason for the transition.</param>
/// <param name="initiatedBy">Identifier of the actor initiating the transition.</param>
/// <returns>The recorded <see cref="LifecycleEvent"/>.</returns>
/// <exception cref="InvalidOperationException">Thrown when the transition is not allowed.</exception>
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 ──────────────────────────────────────

/// <summary>
/// Transitions the agent to <see cref="LifecycleState.Active"/>.
/// </summary>
public LifecycleEvent Activate(string reason = "Ready")
=> Transition(LifecycleState.Active, reason, "system");

/// <summary>
/// Transitions the agent to <see cref="LifecycleState.Suspended"/>.
/// </summary>
public LifecycleEvent Suspend(string reason)
=> Transition(LifecycleState.Suspended, reason, "system");

/// <summary>
/// Transitions the agent to <see cref="LifecycleState.Quarantined"/>.
/// </summary>
public LifecycleEvent Quarantine(string reason)
=> Transition(LifecycleState.Quarantined, reason, "system");

/// <summary>
/// Transitions the agent to <see cref="LifecycleState.Decommissioning"/>.
/// </summary>
public LifecycleEvent Decommission(string reason)
=> Transition(LifecycleState.Decommissioning, reason, "system");
}
Loading
Loading