Explainability Guide¶
Use these capability snippets inside your application’s native runtime. Installation is through PyFly Agentic extras.
Copyright 2026 Firefly Software Foundation. Licensed under the Apache License 2.0.
The Explainability module records agent decisions, generates human-readable explanations, maintains an audit trail, and produces structured reports. Everything runs in-process; the host service decides how (or whether) to persist or expose the resulting records.
The public surface is small and stable:
from pyfly.agentic.explainability import (
AuditEntry,
AuditTrail,
DecisionRecord,
ExplainabilityReport,
ExplanationGenerator,
ReportBuilder,
ReportSection,
TraceRecorder,
default_trace_recorder,
)
Architecture¶
Application code records decision and audit events explicitly; an attached
ExplainabilityMiddleware can record agent-run events. These components are not
automatically invoked by every agent. ReportBuilder uses its explanation generator
when building a report from the collected records.
flowchart LR
AGENT[Agent Execution] --> TR[TraceRecorder]
TR -->|records| EG[ExplanationGenerator]
TR -->|records| RB[ReportBuilder]
EG -->|narrative| RB
RB --> REPORT[ExplainabilityReport]
REPORT --> MD["ReportBuilder.to_markdown()"]
REPORT --> JSON["ReportBuilder.to_json()"]
AGENT --> AT[AuditTrail]
AT --> AJSON["AuditTrail.export_json()"]
TraceRecorder feeds both the ExplanationGenerator and the ReportBuilder. The
AuditTrail is an independent append-only log used for compliance archival.
Trace Recorder¶
The TraceRecorder captures the decision points an agent reaches during execution:
LLM calls, tool invocations, reasoning steps, and delegations. Records are held in
memory and retrieved later for explanation generation or reporting.
from pyfly.agentic.explainability import TraceRecorder
recorder = TraceRecorder()
recorder.record(
"tool_invocation",
agent="writer",
input_summary="search the web for recent filings",
output_summary="3 documents retrieved",
detail={"tool": "search_tool", "top_k": 3},
)
record() is the only mutating call:
def record(
self,
category: str,
*,
agent: str = "",
detail: dict[str, Any] | None = None,
input_summary: str = "",
output_summary: str = "",
) -> DecisionRecord
It creates a DecisionRecord, appends it, and returns it. The category is a free-form
string; the convention used across the framework is one of "llm_call",
"tool_invocation", "reasoning_step", or "delegation".
A DecisionRecord has these fields:
| Field | Type | Description |
|---|---|---|
timestamp |
datetime |
UTC, auto-populated at creation |
category |
str |
decision kind (see convention above) |
agent |
str |
originating agent name (default "") |
detail |
dict[str, Any] |
arbitrary structured context |
input_summary |
str |
short summary of the input |
output_summary |
str |
short summary of the result |
Recorded decisions are read back through the records property (a copy of the internal
list), and the recorder supports len() and clear():
print(len(recorder)) # number of recorded decisions
for rec in recorder.records: # read-only copy of all DecisionRecords
print(rec.category, rec.agent)
recorder.clear() # drop all records
The module-level default_trace_recorder resolves the active runtime’s recorder (it is also the recorder used by
ExplainabilityMiddleware when none is supplied):
The agent middleware stack includes an opt-in
ExplainabilityMiddleware(pyfly.agentic.agents) that records an"llm_call"decision on each run. UnlikeObservabilityMiddleware, it is not auto-wired -- add it explicitly to an agent'smiddlewarelist. See the agents guide for details.
Explanation Generator¶
The ExplanationGenerator turns a sequence of DecisionRecord objects into a
chronological, multi-line narrative suitable for display or for inclusion in a report.
from pyfly.agentic.explainability import ExplanationGenerator
generator = ExplanationGenerator()
explanation = generator.generate(recorder.records)
print(explanation)
generate(records) walks the records in order, emitting one block per step with the
timestamp, category, agent, input/output summaries, and any detail entries. With no
records it returns "No decision records available to explain.".
Audit Trail¶
The AuditTrail is an append-only log intended for compliance archival. Once appended,
entries are not removed or modified through the API. It is a plain in-memory list -- there
is no hashing, chaining, or integrity-verification step.
from pyfly.agentic.explainability import AuditTrail
trail = AuditTrail()
trail.append(
"writer", # actor: agent name, user id, or system
"tool_execution", # action
resource="search_tool",
detail={"query": "recent filings"},
outcome="success", # "success" or "failure"
)
append() builds and stores an AuditEntry, returning it:
def append(
self,
actor: str,
action: str,
*,
resource: str = "",
detail: dict[str, Any] | None = None,
outcome: str = "success",
) -> AuditEntry
An AuditEntry has the fields timestamp (UTC, auto-populated), actor, action,
resource, detail, and outcome. The trail exposes the recorded entries through the
entries property (a read-only copy), supports len(), and serialises the full log to a
JSON string via export_json():
print(len(trail)) # entry count
for entry in trail.entries: # read-only copy of all AuditEntries
print(entry.actor, entry.action, entry.outcome)
json_text = trail.export_json() # JSON array of all entries
Persisting the exported JSON (to a file, object store, or database) is the host service's responsibility.
Report Builder¶
The ReportBuilder compiles decision records into a structured ExplainabilityReport. It
generates a narrative (via ExplanationGenerator) and a per-category statistics section.
The builder operates on decision records only -- audit entries are kept separate.
from pyfly.agentic.explainability import ReportBuilder
builder = ReportBuilder(title="Loan Decision Explainability")
report = builder.build(recorder.records)
markdown = ReportBuilder.to_markdown(report)
json_data = ReportBuilder.to_json(report)
build(records) returns an ExplainabilityReport:
| Field | Type | Description |
|---|---|---|
title |
str |
report title (from the builder) |
generated_at |
datetime |
UTC, auto-populated |
summary |
str |
e.g. "Report covering N decision points." |
sections |
list[ReportSection] |
Narrative Explanation and Statistics |
raw_records |
list[DecisionRecord] |
the source records |
Each ReportSection has a title and content. Serialisation is done with the static
methods ReportBuilder.to_markdown(report) and ReportBuilder.to_json(report), both of
which take an ExplainabilityReport.
Integration Example¶
A typical integration records decisions during agent execution, generates an explanation, builds a report, and archives the audit trail:
from pyfly.agentic.explainability import (
AuditTrail,
ExplanationGenerator,
ReportBuilder,
TraceRecorder,
)
recorder = TraceRecorder()
trail = AuditTrail()
# ... during agent execution, record decisions and audit actions ...
recorder.record(
"tool_invocation",
agent="writer",
input_summary="search the web",
output_summary="3 documents retrieved",
detail={"tool": "search_tool"},
)
trail.append("writer", "tool_execution", resource="search_tool", outcome="success")
# Generate a narrative explanation from the recorded decisions.
explanation = ExplanationGenerator().generate(recorder.records)
print(explanation)
# Build and render a structured report.
report = ReportBuilder(title="Agent Run Report").build(recorder.records)
print(ReportBuilder.to_markdown(report))
# Archive the audit trail (host decides where the JSON goes).
audit_json = trail.export_json()