Extending the package¶
Seven things people actually want to add, each with the files to touch and the test that proves it worked. Every one of them ends the same way: a small graph you can check by hand, because a cut set you cannot verify on paper is a cut set you cannot trust from a machine.
A new component archetype¶
You have a kind of node the library does not model - a retriever, a guardrail, a human-in-the-loop approval step.
Add the role.
Roleinarchitecture/model.py. The value is what appears in reports and drawings, so it is prose ("llm_agent", not"LLMAGENT").Teach
classify_roleto recognise it. It reads node source, then names, then falls back onhas_conditional_out. Prefer a signal that survives refactoring - a resource, a decorator - over a name substring.Write the annotation builder in
faulttree/failure.py, next toannotate_llm_agentandannotate_tool. For each outputDeviationthe component can produce, give the Boolean expression over its input deviations and its own basic events.Give every basic event a
rationaleand amitigation. The FMEA is generated from these, so an event without them produces an FMEA row that tells the reader nothing. Write them for the class: what does aVSfrom this archetype actually look like?Add defaults to
DEFAULT_P, and sayplaceholderin theevidencestring. A number nobody measured must never look measured.Register it in the dispatch that
annotate_systemuses.
The decision that matters is transparency. Ask: if a subtle value deviation
arrives at this component’s input, can it detect it? An LLM agent cannot, so its
logic passes VS straight through; a schema validator can, so it converts VC
to O and stops it. Get this wrong and the tree will show a check where there is
none.
# the test to write: a two-node graph you can reason about on paper
spec = {"name": "t", "nodes": {...}, "edges": [...]}
report = analyse_langgraph(spec)
assert report.cut_sets("H2") == [["BE-guard-MISS"], ...] # by hand, not by eye
A new hazard¶
Hazards are data, not code. Pass them in:
from hiphopsllm import AgenticReliabilityStudy, Hazard, FClass
study = AgenticReliabilityStudy(
graph,
hazards=[Hazard(id="HX", name="PII leaves the system", severity="critical",
component="__end__", port="in", fclass=FClass.COMMISSION)],
)
A hazard is a deviation at a named port of a named component. If the tree comes back empty, that is a result and not a bug: nothing in this architecture can cause that deviation at that port. Check you picked the right port before concluding the system is safe.
To make a hazard appear automatically for a whole class of system, extend
default_hazards in faulttree/synthesis.py. N8nWorkflow.hazards shows the
other half of the pattern: it takes the defaults and rewords H5-<tool>, the
unsafe-execution hazard, into “unsolicited outward action” for the tools that act
on the world, and drops it for the ones that cannot.
A new gate type¶
Write the table constructor in
bayes/cpt.pybesidek_of_n_cpt, and validate its fan-in through_check_fan_in.Add it to
GateTypeand toCPTBuilder._gate_table.Check monotonicity.
ImpreciseBayesianNetworkbuilds a lower and an upper twin network, and that is only valid because the top event is monotone in every basic event. If your gate is not monotone, say so in its docstring and append tocpts.notes, the waysoft_gatesdoes. Silently breaking the interval arithmetic is the worst failure mode this codebase has.Compare against a hand-computed table in
tests/unit/test_cpt.py. Not against another implementation - against arithmetic you did yourself.
A new export format¶
faulttree/export.py takes a FaultTree and returns a string. Add the function,
export it from faulttree/__init__.py and the top-level __init__.py, and wire
it into SafetyReport.save if it should be written by default.
Two properties to test: the output parses in its target format, and it
contains every basic event id in tree.basic_event_ids(). An exporter that
quietly drops shared sub-trees is the classic bug here - to_openpsa_xml handles
them by reference, to_mermaid by repetition, and the tests say which.
A new stratifier¶
A stratifier is any callable from an input item to a label. Nothing to register:
from hiphopsllm import stratify
labels = stratify(items, lambda r: "long" if len(r["q"].split()) > 40 else "short",
profile=profile)
Pass profile= so an unexpected label raises instead of silently dropping those
items from every downstream estimate. If you want to ship one, put it beside
decomposition_stratum in reliability/profile.py.
The property a good stratifier has: failure probability varies between strata more than within them. If it does not, the stratification buys nothing and you have only made the intervals wider.
A new n8n node rule¶
RULES in io/n8n.py is an ordered tuple; the first rule that matches wins, so
order is part of the meaning - gmailTool must be read as a tool before it is
read as a Gmail node.
Rule(
"vector store",
_endswith("vectorstorepinecone", "vectorstoreqdrant"),
kind="component", # or "resource", or "excluded"
role=Role.TOOL,
why="A vector store is a retrieval step the agent depends on: it can be "
"unreachable (omission) or return the wrong neighbours (a plausible, "
"undetectable value deviation).",
)
The why string is not a comment. It reaches the ledger the analyst reads, so
write it for them: what this node is in failure terms, and why it is modelled
this way rather than the obvious alternative. kind="resource" also needs
resource_kind - that is how a model sub-node becomes resources["llm"] and
therefore how two agents on one model become a common-cause group.
Test it on a workflow dict, the way tests/unit/test_n8n.py does, and assert on
the ledger as well as the tree - the reason is as much a product here as the
number.
A new plot¶
viz/plots.py. Take an optional ax, return the ax, use the TOKENS palette,
and never let a status colour be the only signal - pair it with a text tag, as
plot_importance does with ◆ single point.
Test what breaks rather than what it looks like: every node is drawn, degenerate
input produces a figure or a clear error rather than a traceback from inside
matplotlib, and long labels do not escape their shapes. tests/unit/test_viz_plots.py
reads ax.texts and ax.lines to assert those.
Things that will bite you¶
Do not edit src/HIPLLM/ or src/hip_llm/. They are byte copies of
koo-ec/HIP_LLM. Fix bugs upstream and
re-vendor - see Vendoring. They are excluded from ruff for
this reason.
pyAgrum is optional. Anything in bayes/ must also work through
engine="exact". CI runs the whole suite once with pyAgrum uninstalled.
Nothing fails silently. If your code cannot do what was asked, raise with a
message naming what was wrong and what the valid options are. If a value is a
placeholder, its evidence string says so. This is the house rule the package
exists to embody - a Bayesian network running on default priors looks exactly
like one running on evidence.
Provenance travels with numbers. Every BasicEvent has an evidence string,
every OperationalProfile a provenance, every ComponentEvidence its
method, n and interval. A number that reaches a report without them is a bug.