Decision Explainability
Available on platform v7.1.0+.
Every policy decision AxonFlow makes can be explained after the fact. Given a decision_id, the platform returns the matched policies, rule detail, risk level, override availability, and a rolling 24-hour count of times the same caller hit the same rule.
This closes the "why was this blocked?" gap. A plugin block stops being a dead end — the user or downstream tooling can pull the full context and decide whether to override, escalate, or accept.
The shape
The response payload is frozen per ADR-043. Additive fields may appear in future versions (extra fields are tolerated forward), but existing field names, types, and semantics are stable across minor versions.
{
"decision_id": "dec_wf123_step4",
"timestamp": "2026-04-17T12:00:00Z",
"decision": "deny",
"reason": "SQL injection patterns detected in query",
"risk_level": "high",
"policy_matches": [
{
"policy_id": "pol-sqli-detector",
"policy_name": "SQL Injection Detector",
"action": "deny",
"risk_level": "high",
"allow_override": true,
"policy_description": "Blocks SQL injection patterns using keyword + regex detection"
}
],
"matched_rules": [
{
"policy_id": "pol-sqli-detector",
"rule_id": "sqli-union-select",
"rule_text": "Contains UNION SELECT keyword combination",
"matched_on": "query.sql"
}
],
"override_available": true,
"override_existing_id": "ov-f3a81c...",
"historical_hit_count_session": 3,
"policy_source_link": "https://policies.axonflow/sqli-detector",
"tool_signature": "Bash"
}
Every field except decision_id, timestamp, decision, reason, and policy_matches is optional. Consumers that don't find an expected field should treat the absence as "context not available from this platform version" and continue.
API
curl -X GET https://your-platform/api/v1/decisions/{decision_id}/explain \
-u "$CLIENT_ID:$CLIENT_SECRET"
See Decisions API for the full endpoint reference.
SDK usage
Naming is locked across all 4 SDKs (ADR-043):
Go (v5.4.0+):
exp, err := client.ExplainDecision(ctx, "dec_wf123_step4")
if err != nil { return err }
if exp.OverrideAvailable {
// offer the user a governed override action
}
Python (v6.4.0+):
exp = await client.explain_decision("dec_wf123_step4")
if exp.override_available:
# offer a governed override
pass
TypeScript (v5.4.0+):
const exp = await client.explainDecision("dec_wf123_step4");
if (exp.overrideAvailable) {
// offer a governed override
}
Java (v5.4.0+):
DecisionExplanation exp = axonflow.explainDecision("dec_wf123_step4");
if (exp.isOverrideAvailable()) {
// offer a governed override
}
MCP tool parity
The explain_decision MCP tool is served by the agent's MCP server at /api/v1/mcp-server, not by any individual plugin. Every plugin (OpenClaw, Claude Code, Cursor, Codex) points its MCP client at the same agent endpoint, so a single registration on the platform makes the tool available everywhere. Verify with tools/list against the agent:
curl -s -X POST https://your-platform/api/v1/mcp-server \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":"1","method":"tools/list"}' \
| jq '.result.tools[] | .name'
Expected output includes explain_decision, create_override, delete_override, list_overrides alongside the 6 pre-existing governance tools.
Arguments: a single decision_id string. Response: the DecisionExplanation shape above, marshaled as a JSON string inside the MCP tools/call result content. See /docs/governance/overrides for the override-creation tools that pair with this one.
Authorization
The caller must either own the decision (the user_email matches) or belong to the same tenant as the decision's originator. Cross-tenant explanation requests return 403.
Admin-level cross-tenant explanation is out of scope for this endpoint — use the audit search API with appropriate admin credentials for that.
Retention
Explanation is bounded by the tier's audit retention:
| Tier | Retention | Scope |
|---|---|---|
| Community | 7 days | Own decisions only |
| Evaluation | 30 days | Own decisions only |
| Enterprise | 365 days | Any decision in org |
| Enterprise+ | Custom | Any decision in org |
Beyond retention, the endpoint returns 404 — no synthesized "we don't know why anymore" placeholder.
Historical hit count
historical_hit_count_session counts how many times the same (policy_id, user_email) combination appeared in audit logs within a rolling 24-hour window from the decision's timestamp.
This is intended for at-a-glance "I keep hitting this rule" awareness. Full cross-decision queries go through audit search.
See also
- Decisions API — full endpoint reference
- Session Overrides — the action
override_available: trueunlocks - Audit Logging — cross-reference the full lifecycle
