Rumor
Information propagating through a graph, hop by hop. Nodes are NPCs, users, settlements, or agents; directed edges carry delay and fidelity. An injected fact spreads on a schedule, mutates as fidelity decays, is forgotten after a TTL, and can be chased by a correction through the same edges. Query what any node currently believes, or trace who knows what, in which form, via which path.
When to reach for it: living-world news that travels at travel speed, guards two towns over hearing a mutated version of your crime, viral-spread modeling over customer graphs, correction-strategy testing, engine-managed asymmetric information for social deduction games.
When not to: instant delivery to subscribers with no graph, delay, or distortion is PubSub; recording what actually happened in an ordered feed is Timeline.
Concepts
- A graph is a world. Its edge defaults (latency, fidelity, bandwidth), distortion model, forgetting TTL, and optional deterministic seed are set at creation and govern everything spreading inside it.
- Facts vs beliefs. A fact is the injected ground truth plus its
append-only propagation history. A belief is one node's copy: a possibly
mutated
form,fidelity_remaining,confidence, the provenance path inreceived_via, and an expiry. - Propagation is scheduled, not instant. Each edge delays delivery by
its latency; a background scheduler (1 s resolution by default) delivers
hops as they come due.
tickdrains due hops on demand. - Distortion is fidelity-driven. Fidelity multiplies down each edge; below 0.6 the payload mutates. The full model - mutation table, trust weighting, saturation, forgetting - is on the propagation model page.
- Writes are platform-admin operations. Creating graphs, adding
topology, injecting, correcting, quarantining, and ticking require a
platform admin token - other callers get
403 insufficient_permissions. Reads (graph get/list,beliefs,trace) andsubscribework for any authenticated user.
Build a graph
Requires a platform admin token:
snug rumor graph create --graph-id docs4-gossip --seed 42 \
--edge-latency 1 --edge-fidelity 0.7 --ttl 3600
snug rumor nodes add docs4-gossip --file nodes.json
snug rumor edges add docs4-gossip --from docs4-page --to docs4-innkeeper
snug rumor edges add docs4-gossip --from docs4-innkeeper --to docs4-guardnodes.json is a bare array - here
[{"id": "docs4-page"}, {"id": "docs4-innkeeper"}, {"id": "docs4-guard"}] -
and each node takes an optional entity_ref (bind to a real user, agent,
or ECS entity) and trust_source (a Reputation index for trust weighting).
Edges are directed - add the reverse edge for two-way gossip. Omitted
per-edge properties fall back to the graph defaults (latency 3600 s,
fidelity 0.85, bandwidth 4; an edge with bandwidth 0 is inert). An edge to
a missing node fails with 404 edge_target_not_found; re-creating an
existing graph fails with 409 graph_already_exists. The HTTP nodes and
edges endpoints take bulk arrays; the CLI adds one edge per call.
snug rumor graph list paginates and sorts by created_at, node_count,
or edge_count.
Inject a fact
snug rumor inject docs4-gossip --topic king_health \
--payload fact.json --origin docs4-page --truth--truth is a bare flag: present marks the fact true, omitted injects a
false rumor. Over HTTP, the enveloped response, captured live:
curl -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"topic":"king_health","payload":{"claim":"the king is wounded","severity":2},
"truth_value":true,"origin_node":"docs4-page"}' \
http://localhost:4000/api/v1/rumor/graphs/docs4-gossip/inject{
"status": 201,
"msg": "Created",
"data": {
"fact_id": "fact_CstRfMjbmVzupqdMRuZE",
"graph_id": "docs4-gossip",
"topic": "king_health",
"origin_node": "docs4-page",
"injected_at": "2026-08-28T14:16:11.110710Z",
"beliefs_recorded": 1,
"first_hops": [
{ "to_node": "docs4-innkeeper", "scheduled_at": "2026-08-28T14:16:12.110710Z" }
]
}
}The origin believes immediately at full fidelity and confidence; each
first hop is scheduled one edge latency in the future. Injecting at a
missing node fails with 404 node_not_found.
Query beliefs and trace spread
snug rumor beliefs docs4-gossip --node docs4-guard --topic king_healthTwo hops from the origin (0.7 x 0.7 = 0.49 fidelity, below the 0.6 mutation threshold) the guard believes a distorted form - captured live:
{
"node_id": "docs4-guard",
"beliefs": [
{
"node_id": "docs4-guard",
"fact_id": "fact_CstRfMjbmVzupqdMRuZE",
"topic": "king_health",
"form": { "claim": "the king is slain", "severity": 2 },
"fidelity_remaining": 0.49,
"confidence": 0.49,
"received_via": ["docs4-page", "docs4-innkeeper", "docs4-guard"],
"received_at": "2026-08-28T14:16:13.840118Z",
"expires_at": "2026-08-28T15:16:13.840118Z",
"corrected": false,
"truth_value": true
}
],
"count": 1
}Expired beliefs are purged on read - after the graph's TTL a node has
genuinely forgotten. snug rumor trace <graph> <fact> returns the
epidemiological view: every node that knows the fact and the form it
holds, the hop-by-hop propagation history, and a prevalence summary -
for the fact above,
{"true_form": 2, "distorted": 1, "corrected": 0, "total_known": 3}.
Countermeasures
snug rumor correct docs4-gossip --fact fact_CstRfMjbmVzupqdMRuZE \
--payload correction.json --origin docs4-page
snug rumor quarantine docs4-gossip --node docs4-guard
snug rumor quarantine docs4-gossip --node docs4-guard --releaseA correction reports original_footprint (3 nodes knew the rumor), then
chases it through the same edges at edge speed, propagating verbatim -
corrections never mutate - and overwriting what it finds. After two hops
the guard held the corrected payload with "corrected": true, and
prevalence read
{"true_form": 0, "distorted": 0, "corrected": 3, "total_known": 3}.
An incoming hop to a quarantined node is dropped: no belief recorded, no onward hops scheduled. Verified live - with the guard quarantined, a new fact reached the innkeeper while the guard's belief count stayed 0. Dropped hops are consumed, not parked: releasing the node later does not replay rumors it missed.
Belief-change events
Any authenticated user can provision fanout, optionally filtered to one node and/or one topic, with an optional webhook sink:
snug rumor subscribe docs4-gossip --topic king_health
snug pubsub listen -c rumor.docs4-gossip.beliefssubscribe returns a subscription_id and the graph's PubSub channel
(rumor.docs4-gossip.beliefs). Every belief change matching a
subscription - origin write, hop delivery, correction - is published to
that channel as the full belief document; verified live end to end. But
nothing is published unless some subscription matches: a bare pubsub listen stays silent until one exists (reproduced with a fact on an
unsubscribed topic). Webhook deliveries are signed - Snug-Signature
(t=...,v1=<HMAC>), X-Webhook-Timestamp, and
X-Rumor-Event: belief.changed headers, verified against a local
listener. Subscriptions persist for the life of the graph; there is no
unsubscribe endpoint.
Limits and configuration
By default: 10,000 graphs, 100,000 nodes per graph, 1,000 edges per node,
32 hops per fact, and a 1 s scheduler tick - all tunable via the RUMOR_*
variables in the
Rumor CONFIG reference.
Reference
- Rumor API - every endpoint, callable
- The propagation model - fidelity, distortion, trust, saturation, forgetting, determinism
- Related: Reputation for trust-weighted confidence, PubSub for the event channel, ECS for entities a node can bind to