Observation
KillSwitchMiddleware writes a full KillSwitchDecision struct (which embeds *KillSwitchScope and *KillSwitchEvent) into operation.Metadata["kill_switch_decision"]:
capability := defaultString(operation.ToolName, operation.Action)
decision := registry.DecisionFor(operation.AgentID, capability)
ensureOperationMetadata(operation)
operation.Metadata["kill_switch_decision"] = decision
The other middlewares that touch operation.Metadata mostly store small scalars or single-purpose values:
| Middleware |
Stored value |
KillSwitchMiddleware |
KillSwitchDecision (allowed + scope ptr + event ptr) |
PolicyEvaluationMiddleware |
PolicyDecision (string type alias) |
PromptDefenseMiddleware |
PromptDefenseResult (small struct) |
AuditTrailMiddleware |
string (the audit entry hash) |
(HTTP) agent_identity_error |
string |
KillSwitchDecision does JSON-marshal cleanly (all fields are exported and tagged), so this isn't a serialisation bug. The concern is consistency / consumer ergonomics: downstream consumers that read operation.Metadata["kill_switch_decision"] after encoding/json round-trip will get map[string]interface{} rather than the typed struct, since Metadata is map[string]interface{}.
Suggestion
If Metadata is intended to be a JSON-serialisable diagnostic bag, consider flattening the kill-switch entry to scalar keys, e.g.:
operation.Metadata[\"kill_switch_allowed\"] = decision.Allowed
if decision.Scope != nil {
operation.Metadata[\"kill_switch_scope\"] = decision.Scope.String()
}
if decision.Event != nil {
operation.Metadata[\"kill_switch_reason\"] = string(decision.Event.Reason)
}
Or, if richer state is intentional, keep the struct but document operation.Metadata as containing Go-side typed values (not just JSON scalars), so other middlewares can follow the same pattern.
I'm raising this as an issue rather than a PR because the answer is a maintainer judgment call: the current behaviour is functionally fine, the question is whether Metadata is a typed bag or a JSON bag.
Repro
registry := agentmesh.NewKillSwitchRegistry()
_, _ = registry.Activate(
agentmesh.AgentKillSwitchScope(\"agent-1\"),
agentmesh.KillSwitchReasonSecurityIncident,
\"test\",
)
stack, _ := agentmesh.CreateGovernanceMiddlewareStack(agentmesh.MiddlewareStackConfig{
Policy: agentmesh.NewPolicyEngine(nil),
KillSwitches: registry,
})
op := &agentmesh.GovernedOperation{AgentID: \"agent-1\", Action: \"tool.run\"}
_ = stack.Execute(op, func(*agentmesh.GovernedOperation) error { return nil })
fmt.Printf(\"%T\n\", op.Metadata[\"kill_switch_decision\"])
// agentmesh.KillSwitchDecision
Surfaced during independent audit conducted by @finnoybu (Ken Tannenbaum, AEGIS Initiative); [LOW, Go].
Observation
KillSwitchMiddlewarewrites a fullKillSwitchDecisionstruct (which embeds*KillSwitchScopeand*KillSwitchEvent) intooperation.Metadata["kill_switch_decision"]:The other middlewares that touch
operation.Metadatamostly store small scalars or single-purpose values:KillSwitchMiddlewareKillSwitchDecision(allowed + scope ptr + event ptr)PolicyEvaluationMiddlewarePolicyDecision(string type alias)PromptDefenseMiddlewarePromptDefenseResult(small struct)AuditTrailMiddlewarestring(the audit entry hash)agent_identity_errorstringKillSwitchDecisiondoes JSON-marshal cleanly (all fields are exported and tagged), so this isn't a serialisation bug. The concern is consistency / consumer ergonomics: downstream consumers that readoperation.Metadata["kill_switch_decision"]afterencoding/jsonround-trip will getmap[string]interface{}rather than the typed struct, sinceMetadataismap[string]interface{}.Suggestion
If
Metadatais intended to be a JSON-serialisable diagnostic bag, consider flattening the kill-switch entry to scalar keys, e.g.:Or, if richer state is intentional, keep the struct but document
operation.Metadataas containing Go-side typed values (not just JSON scalars), so other middlewares can follow the same pattern.I'm raising this as an issue rather than a PR because the answer is a maintainer judgment call: the current behaviour is functionally fine, the question is whether
Metadatais a typed bag or a JSON bag.Repro
Surfaced during independent audit conducted by @finnoybu (Ken Tannenbaum, AEGIS Initiative); [LOW, Go].