0.15.0¶
Released 2026-08-20.
The release that answers. Two operator agents worked a full day of infrastructure incidents without opening Toise once. A third used it heavily and concluded that a host had been renamed, a database deleted and a service removed by hand — three human operations that never happened, asserted at high confidence, inferred from entities disappearing.
The graph held the right answers the whole time. What failed was everything around the data: what an answer looks like, what it admits, and what an agent knows before asking. That is what this release fixes.
No wire-contract break, no data migration. Everything is additive.
The day that produced this release¶
Two production incidents cost roughly two hours of investigation. The root cause
was a WireGuard netdev being created across the fleet, which reconfigured the
public interface on each host and dropped its DHCP lease. It was found by reading
journalctl on machine after machine.
Toise had it. One graph_diff bounded to the incident window returns the whole
rollout — eleven hosts gaining the same interface and its address, in sequence,
with timestamps. That is the view a per-machine log structurally cannot produce:
not this host changed, but eleven hosts changed the same way in six minutes.
Nobody reached it, and the naive path actively misled: asking recent_changes
for a five-hour window returned the most recent slice, which contained nothing of
interest. The honest reading of that day is not that consumers used the tool
badly. It is that a graph which holds the answer and returns "nothing" to the
natural question is, from where the user stands, indistinguishable from a graph
that does not know.
Guidance where an agent actually looks¶
The MCP instructions field — the one guidance channel delivered to every client
at initialize, without being asked — was empty. Everything we had written lived in
a resource that must be deliberately fetched and in prompts a human invokes, so an
autonomous agent saw none of it.
It now carries only what changes an answer:
- Start from
describe_schema. Guessing type and attribute names is the most common way to conclude "Toise does not know that" about something it holds. - Bound an incident window with
graph_diff(orrecent_changeswithfrom/to) — fleet-wide, in one call. - What a disappearance does not mean.
- Ids are per-replica and re-minted after the resurrection window, so they must never be carried between investigations.
- An address is two hops away, not an attribute of its host.
- Absence is not evidence of absence — the graph is exactly as complete as its producers.
Disappearances say what they mean¶
delete_source has always answered who authored this disappearance. It is a
three-value enum sitting next to a field name that reads like a cause, with its
semantics living in a contract document — and it was read as a cause, repeatedly.
Every change on the feed now carries a disappearance sentence alongside the
code, and each one states the negative explicitly:
delete_source |
What the payload now says |
|---|---|
producer |
the producer reported this gone — an observation about the world, not an operator action |
liveness_expiry |
no news: the producer went silent, and the thing may well still be running |
cascade |
collateral: something this edge touched died — a consequence, not a cause |
None of the three means a human deleted anything. Never report an operator action, a rename or a manual removal from a disappearance alone.
A window you can trust¶
recent_changes gains from / to, so the tool an agent reaches for first can
answer about the past — the ten minutes before an alert — instead of only about
now. Previously the sole way to reach further back was a wider window, and the
limit then kept the newest changes: an event two hours old was silently
absent from a five-hour answer.
Every answer now names the window it actually read (window_from, window_to).
When the limit truncates, covered says so in words:
PARTIAL: 200 of 800 matching changes returned, covering 15:12:03Z to 16:28:38Z —
NOT the whole window you asked for (11:28:38Z to 16:28:38Z). 600 older changes in
the window are not shown. To see them, ask again with from/to bounding the
sub-window you care about (that is how you investigate the minutes before an
incident), or narrow with kind/change_type.
This is the "no silent caps" rule applied to time rather than to pages: a partial answer that reads as a complete one is worse than an error.
Heartbeats leave the GraphQL history¶
entityHistory and recentChanges now take includeHeartbeats (default
false), matching what MCP has always done. A live window is dominated by
entity.unchanged heartbeats; returning them by default meant a client asking
what changed received mostly noise.
Behavior change: a client relying on heartbeats appearing must now pass
includeHeartbeats: true. Counts drop sharply — that is the point.
Producers: the four-key endpoint helper¶
pkg/emit v0.10.0 adds DependsOnLocalEndpoint, which builds the identity of a
host-local network.endpoint so producers stop hand-rolling the place the
contract is most often violated. Forget the fourth key on a loopback address and
every machine's local endpoint collapses into one entity; add it on an RFC1918
peer and you fork an identity nothing else will ever join.
The helper classifies the address itself — exactly 127.0.0.0/8, ::1,
169.254.0.0/16 and fe80::/10 gain the observing host's id, everything else
falls back to the three-key edge — and applies the frozen canonicalization on the
way through, so ::ffff:127.0.0.1 and 127.0.0.1 derive one identity rather
than two.
Also in this release¶
- The debug UI names its tenant on every page, with a zero-JS switcher where tenant selection is client-controlled. An empty page that does not say what it is empty for was once diagnosed as "the producer stopped".
- The HA docs state what replicas agree on — and what they legitimately do not, which had been diagnosed twice as a defect.
- The ingestion guide documents the 15-minute resurrection window, which had shipped since 0.7.0 undocumented, along with its two consequences: never persist a logical id, and ids are per-replica.
- The 1.0 freeze audit walked the whole MCP surface; two conventions are now stated once in the stability policy rather than repeated per field.