hiphopsllm.faulttree

Fault trees: local failure logic, compositional synthesis, and analysis.

Three HiP-HOPS phases live here.

Annotate (failure) — every component is given an IF-FMEA table: each output deviation is a Boolean expression over the component’s input deviations and its own internal basic events. The library covers the agentic archetypes: LLM agent, tool/executor, router, aggregator, transform, boundary and feedback cut.

Synthesise (synthesis) — a tree is produced by traversing connections backwards from a system-level hazard and substituting each component’s local logic, memoised so shared sub-trees stay shared (HiP-HOPS transfer gates). Trees are never drawn by hand.

Analyse (analysis) — minimal cut sets by MOCUS with absorption, quantification by the minimal cut upper bound, Birnbaum and Fussell-Vesely importance, single points of failure, and a generated FMEA table.

The deviation notation is HiP-HOPS’ own, class-component.port: O-coder.out, VS-aggregator.out. The six failure classes are in FClass; keeping VC (coarse, detectable) apart from VS (subtle, plausible — the hallucination case) is the single most important modelling decision in the library, because they propagate identically but one is caught at the system boundary and the other is delivered to the user.

Failure logic

hiphopsllm.faulttree.failure — deviations, Boolean failure expressions and the annotation library (HiP-HOPS Phase 1: “annotate components with local failure logic”).

In HiP-HOPS every component carries an IF-FMEA table: for each output deviation of the component, a logical expression over input deviations and internal basic events that explains how that output deviation arises. Fault trees are never drawn by hand — they are synthesised by composing these local tables along the architecture’s connections.

Deviation notation follows the HiP-HOPS convention <class>-<component>.<port>:

O-coder.out      omission of the tool's output
VS-generator.out subtle (plausible but wrong) value deviation

Failure classes are the classical guideword set, specialised for LLM-based agents. The distinction that matters most for agentic systems is between VALUE_COARSE (wrong and detectable — malformed, unparsable, schema violation) and VALUE_SUBTLE (wrong but plausible — the hallucination case, which no downstream syntactic check will catch). They have very different propagation behaviour and very different consequences, so they are kept apart throughout.

class hiphopsllm.faulttree.failure.FClass[source]

Bases: str, Enum

Deviation guidewords used by the annotation library.

OMISSION = 'O'
COMMISSION = 'C'
VALUE_COARSE = 'VC'
VALUE_SUBTLE = 'VS'
EARLY = 'E'
LATE = 'L'
property title: str

Return a version of the string where each word is titlecased.

More specifically, words start with uppercased characters and all remaining cased characters have lower case.

__new__(value)
class hiphopsllm.faulttree.failure.Deviation[source]

Bases: object

A failure of class fclass observed at component.port.

component: str
port: str
fclass: FClass
property id: str
__init__(component, port, fclass)
Parameters:
Return type:

None

class hiphopsllm.faulttree.failure.Expr[source]

Bases: object

Base class of the local failure-logic expression language.

refs()[source]
Return type:

List[DevRef]

events()[source]
Return type:

List[str]

class hiphopsllm.faulttree.failure.BasicEventRef[source]

Bases: Expr

Reference to an internal basic event (a leaf of the fault tree).

event_id: str
events()[source]
Return type:

List[str]

__init__(event_id)
Parameters:

event_id (str)

Return type:

None

class hiphopsllm.faulttree.failure.DevRef[source]

Bases: Expr

Reference to a deviation at one of this component’s ports.

component: str
port: str
fclass: FClass
property deviation: Deviation
refs()[source]
Return type:

List[DevRef]

__init__(component, port, fclass)
Parameters:
Return type:

None

class hiphopsllm.faulttree.failure.And[source]

Bases: Expr

And(terms: ‘Tuple[Expr, …]’)

terms: Tuple[Expr, ...]
refs()[source]
Return type:

List[DevRef]

events()[source]
Return type:

List[str]

__init__(terms)
Parameters:

terms (Tuple[Expr, ...])

Return type:

None

class hiphopsllm.faulttree.failure.Or[source]

Bases: Expr

Or(terms: ‘Tuple[Expr, …]’)

terms: Tuple[Expr, ...]
refs()[source]
Return type:

List[DevRef]

events()[source]
Return type:

List[str]

__init__(terms)
Parameters:

terms (Tuple[Expr, ...])

Return type:

None

class hiphopsllm.faulttree.failure.Const[source]

Bases: Expr

Const(value: ‘bool’)

value: bool
__init__(value)
Parameters:

value (bool)

Return type:

None

hiphopsllm.faulttree.failure.AND(*terms)[source]

Build a simplified conjunction (flattens, drops TRUE, dedups).

Return type:

Expr

Parameters:

terms (Expr | None)

hiphopsllm.faulttree.failure.OR(*terms)[source]

Build a simplified disjunction (flattens, drops FALSE, dedups).

Return type:

Expr

Parameters:

terms (Expr | None)

class hiphopsllm.faulttree.failure.BasicEvent[source]

Bases: object

A leaf failure: an internal fault of one component, or a shared cause.

prob is the point estimate used for quantification. prob_interval optionally carries an imprecise (lower, upper) pair — useful when the estimate comes from a small sample, which is the normal situation for LLM failure rates.

id: str
component: str
label: str
fclass: FClass
prob: float = 0.05
prob_interval: Optional[Tuple[float, float]] = None
baseline_prob: Optional[float] = None

The probability this event had before any calibration touched it, set once by the calibrator. Calibration splits a component’s measured probability over its events in proportion to their priors; without a stable baseline the second calibration would use the first one’s output as weights and quietly move numbers that were already measured.

kind: str = 'internal'
rationale: str = ''
mitigation: str = ''
evidence: str = 'engineering judgement (placeholder replace with measurement)'
property interval: Tuple[float, float]
__init__(id, component, label, fclass, prob=0.05, prob_interval=None, baseline_prob=None, kind='internal', rationale='', mitigation='', evidence='engineering judgement (placeholder replace with measurement)')
Parameters:
Return type:

None

class hiphopsllm.faulttree.failure.ComponentFailureLogic[source]

Bases: object

The IF-FMEA table of one component.

component: str
role: Role
logic: Dict[Deviation, Expr]

output deviation -> Boolean expression over input deviations + basic events

events: Dict[str, BasicEvent]
notes: List[str]
set(port, fclass, expr)[source]
Return type:

None

Parameters:
add_event(event)[source]
Return type:

BasicEventRef

Parameters:

event (BasicEvent)

table()[source]
Return type:

List[Dict[str, str]]

__init__(component, role, logic=<factory>, events=<factory>, notes=<factory>)
Parameters:
Return type:

None

class hiphopsllm.faulttree.failure.FailureModel[source]

Bases: object

The annotated system: architecture + per-component failure logic.

system: SystemModel
logic: Dict[str, ComponentFailureLogic]
events: Dict[str, BasicEvent]
ccf_groups: Dict[str, List[str]]
connection_events: Dict[str, List[str]]
notes: List[str]
expression(dev)[source]
Return type:

Optional[Expr]

Parameters:

dev (Deviation)

event(eid)[source]
Return type:

BasicEvent

Parameters:

eid (str)

__init__(system, logic=<factory>, events=<factory>, ccf_groups=<factory>, connection_events=<factory>, notes=<factory>)
Parameters:
Return type:

None

hiphopsllm.faulttree.failure.annotate_system(system, probability_overrides=None, entropy_by_component=None, extra_logic=None)[source]

Annotate every component with its local failure logic (HiP-HOPS Phase 1).

Parameters:
Return type:

FailureModel

hiphopsllm.faulttree.failure.entropy_to_fail_prob(entropy_value, ent_mid=0.9, slope=3.0, p_min=0.01, p_max=0.95)[source]

Map semantic-cluster entropy to a failure probability.

Identical calibration to the Bayesian-network cell of the source notebook, so the fault tree and the BN are quantified on the same scale. Entropy is computed by HFSemanticUncertainty over K resamples of one prompt: high entropy means the agent’s answer is unstable, which we read as an elevated probability of a subtle value deviation.

Return type:

float

Parameters:

Synthesis

hiphopsllm.faulttree.synthesis — fault tree synthesis (HiP-HOPS Phase 2).

HiP-HOPS does not ask the analyst to draw fault trees. It synthesises them: starting from a system-level hazard expressed as a deviation at the system boundary, it walks the architecture backwards, and at each component substitutes the local failure expression for the deviation being explained. Input deviations are resolved across connections into output deviations of the upstream component, and the traversal continues until only basic events remain.

The same happens here, over the acyclic projection of the agent graph:

hazard  H2  "wrong answer delivered, undetected"
  = VS-__end__.in
  = VS-aggregator.out                                  (across the connection)
  = BE-aggregator-SELECT AND (VS-react.out OR VS-cot.out)
    OR (VS-react.out AND VS-cot.out)
    OR CCF-LLM-...                                     (shared model snapshot)
  = ... until every leaf is a basic event

Termination is structural, not heuristic: the architecture is acyclic before synthesis starts, expansions only ever move upstream, and every deviation is expanded at most once (the result is memoised and shared, exactly as a transfer gate is shared in a hand-drawn tree). A defensive path check remains, so a malformed annotation produces a clearly-labelled circular reference undeveloped event instead of an infinite recursion.

class hiphopsllm.faulttree.synthesis.FTNode[source]

Bases: object

One node of the synthesised tree.

ntype top | intermediate | basic | undeveloped | house gate AND | OR | None (a single-cause pass-through)

id: str
ntype: str
label: str
gate: Optional[str] = None
children: List[str]
deviation: Optional[str] = None
event_id: Optional[str] = None
component: Optional[str] = None
port_kind: Optional[str] = None

“in” for an input-port deviation, “out” for an output-port one

repeat_of: Optional[str] = None

set on a duplicate produced by expand_to_tree() — the id it copies

transfer_ref: Optional[str] = None

transfer tag (“A”, “B”, …) linking a transfer symbol to its subtree

detail: str = ''
property is_leaf: bool
__init__(id, ntype, label, gate=None, children=<factory>, deviation=None, event_id=None, component=None, port_kind=None, repeat_of=None, transfer_ref=None, detail='')
Parameters:
  • id (str)

  • ntype (str)

  • label (str)

  • gate (str | None)

  • children (List[str])

  • deviation (str | None)

  • event_id (str | None)

  • component (str | None)

  • port_kind (str | None)

  • repeat_of (str | None)

  • transfer_ref (str | None)

  • detail (str)

Return type:

None

class hiphopsllm.faulttree.synthesis.FaultTree[source]

Bases: object

A synthesised static fault tree (a rooted DAG with shared sub-trees).

id: str
name: str
root: str
nodes: Dict[str, FTNode]
hazard: Optional[Hazard] = None
events: Dict[str, BasicEvent]
warnings: List[str]
notes: List[str]
node(nid)[source]
Return type:

FTNode

Parameters:

nid (str)

basic_event_ids()[source]
Return type:

List[str]

leaves()[source]
Return type:

List[FTNode]

size()[source]
Return type:

Dict[str, int]

depth()[source]
Return type:

int

parent_count()[source]
Return type:

Dict[str, int]

simplified(**kwargs)[source]

Return a structurally reduced copy (see simplify_tree()).

Return type:

FaultTree

shared_nodes()[source]

Nodes referenced by more than one parent (rendered as transfer gates).

Return type:

List[str]

verify_acyclic()[source]

True when no node is its own ancestor. A fault tree must satisfy this.

Return type:

bool

__init__(id, name, root, nodes=<factory>, hazard=None, events=<factory>, warnings=<factory>, notes=<factory>)
Parameters:
Return type:

None

class hiphopsllm.faulttree.synthesis.Hazard[source]

Bases: object

A system-level effect to be analysed, anchored to a boundary deviation.

id: str
name: str
deviations: List[Deviation]
severity: str = 'major'
description: str = ''
detection: str = ''
property label: str
__init__(id, name, deviations, severity='major', description='', detection='')
Parameters:
Return type:

None

hiphopsllm.faulttree.synthesis.default_hazards(model)[source]

The standard hazard list for an agentic workflow.

Anchored at the system boundary (the sink’s input ports), plus one hazard per component that executes model-authored code, because that effect is not observable at the output port at all.

Return type:

List[Hazard]

Parameters:

model (SystemModel)

hiphopsllm.faulttree.synthesis.synthesise_fault_tree(fmodel, hazard, simplify=True)[source]

Synthesise the fault tree for one hazard.

Return type:

FaultTree

Parameters:
hiphopsllm.faulttree.synthesis.synthesise_all(fmodel, hazards=None, simplify=True)[source]

Synthesise one fault tree per hazard, keyed by hazard id.

Return type:

Dict[str, FaultTree]

Parameters:
hiphopsllm.faulttree.synthesis.simplify_tree(tree, collapse_single_input=True, flatten_gates=True, flatten_ports=True, dedup_inputs=True)[source]

Reduce a synthesised tree to its informative structure.

Synthesis is deliberately literal: it emits one intermediate event per deviation, so a chain of components produces a chain of one-input gates. That is faithful but tedious to read, and a one-input OR is not a gate at all. Three reductions are applied to a fixed point:

Return type:

FaultTree

Parameters:
collapse_single_input

A gate with a single input is that input. OR(BE-coder-PARSE) becomes BE-coder-PARSE and the intervening event box disappears.

flatten_gates

OR(a, OR(b, c)) becomes OR(a, b, c) for the anonymous combination gates.

flatten_ports

An input-port deviation is absorbed into the output deviation it causes: “omission at coder.in” under “omission of coder.out” is one step, not two. One node per component output survives, so the propagation between components stays visible while the port-level bookkeeping goes. Set False to keep every port explicitly.

dedup_inputs

The same input listed twice under one gate is listed once.

The Boolean function is unchanged: every reduction is an identity of Boolean algebra, so the minimal cut sets before and after are identical. test_synthesis_internals.py checks exactly that. The unreduced tree remains available (report.raw_trees) when the full propagation chain is wanted.

hiphopsllm.faulttree.synthesis.describe_deviation(system, dev)[source]
Return type:

str

Parameters:

Analysis

hiphopsllm.faulttree.analysis — minimal cut sets, quantification and FMEA (HiP-HOPS Phase 3: “analyse the synthesised trees”).

Three products are derived from each synthesised tree:

Minimal cut sets. The smallest combinations of basic events that are together sufficient to cause the top event. Order-1 cut sets are single points of failure: one fault, one hazard, no redundancy in between. For agentic systems this is the number that matters — an architecture drawn with two “independent” agents and a judge looks redundant, and the cut sets say whether it actually is.

Quantification. Point probabilities per cut set, and a top-event estimate by the minimal-cut upper bound. Where a basic event carries an imprecise prob_interval the bound is evaluated at both ends, so the result is reported as an interval rather than false precision. All defaults are placeholders and are labelled as such; the value of the analysis is in the structure and the ranking, which are unaffected by the absolute numbers.

FMEA. The inverse view: for each component failure mode, which hazards it causes, whether it causes them alone, and how much it contributes.

class hiphopsllm.faulttree.analysis.CutSetResult[source]

Bases: object

CutSetResult(sets: ‘List[CutSet]’ = <factory>, symbols: ‘Dict[str, BasicEvent]’ = <factory>, truncated: ‘bool’ = False, max_order: ‘int’ = 0, notes: ‘List[str]’ = <factory>)

sets: List[FrozenSet[str]]
symbols: Dict[str, BasicEvent]
truncated: bool = False
max_order: int = 0
notes: List[str]
by_order()[source]
Return type:

Dict[int, List[FrozenSet[str]]]

order_1()[source]
Return type:

List[str]

containing(event_id)[source]
Return type:

List[FrozenSet[str]]

Parameters:

event_id (str)

__init__(sets=<factory>, symbols=<factory>, truncated=False, max_order=0, notes=<factory>)
Parameters:
Return type:

None

class hiphopsllm.faulttree.analysis.Quantification[source]

Bases: object

Quantification(top_probability: ‘float’ = 0.0, top_interval: ‘Tuple[float, float]’ = (0.0, 0.0), rare_event_sum: ‘float’ = 0.0, cut_set_probability: ‘Dict[CutSet, float]’ = <factory>, method: ‘str’ = ‘minimal-cut upper bound (MCUB)’, imprecise: ‘bool’ = False, notes: ‘List[str]’ = <factory>)

top_probability: float = 0.0
top_interval: Tuple[float, float] = (0.0, 0.0)
rare_event_sum: float = 0.0
cut_set_probability: Dict[FrozenSet[str], float]
method: str = 'minimal-cut upper bound (MCUB)'
imprecise: bool = False
notes: List[str]
__init__(top_probability=0.0, top_interval=(0.0, 0.0), rare_event_sum=0.0, cut_set_probability=<factory>, method='minimal-cut upper bound (MCUB)', imprecise=False, notes=<factory>)
Parameters:
Return type:

None

hiphopsllm.faulttree.analysis.cut_sets(tree, fmodel=None, max_order=6, max_sets=20000)[source]

Compute the minimal cut sets of a synthesised tree (MOCUS, bottom-up).

max_order and max_sets bound the combinatorial expansion; truncation is always reported rather than silently applied, because an unreported truncation reads as “there is nothing else”, which would be false.

Return type:

CutSetResult

Parameters:
hiphopsllm.faulttree.analysis.quantify(result)[source]

Top-event probability by the minimal-cut upper bound, with intervals.

Return type:

Quantification

Parameters:

result (CutSetResult)

hiphopsllm.faulttree.analysis.importance(result, quant)[source]

Rank basic events by contribution to the top event.

Fussell-Vesely is the share of the (rare-event) top probability carried by cut sets containing the event; Birnbaum is the sensitivity of the top event to that event; risk reduction worth is the factor by which the top event would fall if the event were eliminated.

Return type:

List[ImportanceRow]

Parameters:
hiphopsllm.faulttree.analysis.fmea_table(analyses)[source]

Derive the FMEA from the synthesised trees (the HiP-HOPS inversion step).

‘Direct effect’ means the failure mode causes the hazard on its own (an order-1 cut set); ‘further effect’ means it does so in combination with other failures. This is the classical HiP-HOPS FMEA, which is generated from the trees rather than elicited separately, so the two views cannot disagree.

Return type:

List[FMEARow]

Parameters:

analyses (Dict[str, TreeAnalysis])

hiphopsllm.faulttree.analysis.single_points_of_failure(analyses)[source]

Every (hazard, basic event) pair where one failure alone causes the hazard.

Return type:

List[Dict[str, str]]

Parameters:

analyses (Dict[str, TreeAnalysis])

hiphopsllm.faulttree.analysis.analyse_tree(tree, fmodel=None, max_order=6, max_sets=20000)[source]
Return type:

TreeAnalysis

Parameters:
class hiphopsllm.faulttree.analysis.TreeAnalysis[source]

Bases: object

TreeAnalysis(tree: ‘FaultTree’, cuts: ‘CutSetResult’, quant: ‘Quantification’, importance: ‘List[ImportanceRow]’)

tree: FaultTree
cuts: CutSetResult
quant: Quantification
importance: List[ImportanceRow]
property single_points: List[str]
__init__(tree, cuts, quant, importance)
Parameters:
Return type:

None

Export

Export of synthesised fault trees and reports.

to_mermaid

Fault tree as a mermaid flowchart — renders in a notebook the same way the LangGraph diagram does.

to_dot

Graphviz DOT with conventional fault-tree shapes.

to_json

Machine-readable tree, for diffing across releases.

to_openpsa_xml

Open-PSA MEF, so the tree can be opened in an external fault-tree tool (XFTA, SCRAM) rather than trusted blindly.

markdown_report

The full safety-analysis document, including the loop handling and the common-cause notes.

hiphopsllm.faulttree.export.to_mermaid(tree, direction='TB', show_gate=True)[source]

Render the fault tree as mermaid text.

Gates are drawn as their own nodes between an event and its causes, which is as close to the conventional AND/OR symbols as mermaid gets; the matplotlib renderer (hiphopsllm.viz.plots) draws the real symbols. Shared sub-trees (the equivalent of a transfer gate) appear once and are referenced by several parents, outlined so a shared cause is visible as such.

Return type:

str

Parameters:
hiphopsllm.faulttree.export.to_dot(tree)[source]

Graphviz DOT using conventional fault-tree shapes.

Return type:

str

Parameters:

tree (FaultTree)

hiphopsllm.faulttree.export.to_json(tree, analysis=None, indent=2)[source]
Return type:

str

Parameters:
hiphopsllm.faulttree.export.to_openpsa_xml(tree, name=None)[source]

Export in Open-PSA Model Exchange Format for an external FT engine.

Being able to re-run the cut sets in an independent tool is the cheapest available check on this implementation, so the export is part of the normal output rather than an extra.

Return type:

str

Parameters:
hiphopsllm.faulttree.export.markdown_report(system, fmodel, analyses, cycle_report=None, title=None, include_trees=True)[source]

The full HiP-HOPS-style analysis document.

Return type:

str

Parameters: