4.2 KiB
tines — opportunistic shared memory for mostly-offline robots
Small robots doing their work collect facts about the world. They are mostly offline, so when two of them happen to meet in range they swap what they know so that each robot's picture of the world improves over time. No robot depends on receiving anything — it can do its job on its own observations — but there is no reason not to share.
tines is the library that does this: a store of timestamped-by-age
observations, a belief layer derived from them, and a delay-tolerant exchange
protocol that runs over whatever datagram link the robot has. The name is from
Vinge's A Fire Upon the Deep: the Tines only form a mind when close enough to
hear each other.
Architecture
Three layers, each a separate concern, top to bottom:
┌──────────────────────────────────────────┐
│ memory — "my priors": beliefs per │ derived, local, never synced
│ topic, computed from │
│ storage under a policy │
├──────────────────────────────────────────┤
│ storage — append-only log of raw │ synced by set-union
│ observations; local │
│ trimming policy │
├──────────────────────────────────────────┤
│ comms — contact-driven exchange │ PHY-agnostic; transport
│ of observations │ trait + adapters
└──────────────────────────────────────────┘
Only observations cross the wire. Memory is a pure function of storage plus a
policy and is never transmitted; storage is synced by set-union (order- and
path-independent); comms is a contact-driven exchange of observations over a
Transport trait.
Usage
tines-core exposes a single application-facing facade, Node<S, T>, generic
over any Store and Transport. Persist Node::next_seq() across reboots
(boot_count << 32 | counter) and pass it back as the initial first_seq; a
repeated seq after reboot collides in the content-id hash and dedupes silently.
use tines_core::{LatestWins, MemStore, Node, NodeId};
let me: NodeId = [7; 16];
let boot_count = 0u64;
let counter = 3u64;
let store = MemStore::new();
let transport = /* any Transport implementation */;
// first_seq must survive reboot: boot_count << 32 | counter
let mut node: Node<MemStore, _> = Node::new(
me,
boot_count << 32 | counter,
store,
transport,
);
// Record something you saw. observed_at is an optional absolute clock
// (ms since epoch); `now` is a local monotonic Tick on every call.
let id = node
.observe(b"door/3", b"open", Some(1_700_000_000_000), 0)
.expect("observation fits the size limits");
// Drive the exchange in your loop. Returns how many new observations were
// stored this round.
let stored = node.poll(500);
// Ask what you believe about a topic prefix, under a belief policy
// (e.g. the youngest observation wins).
let belief = node.belief(b"door", &LatestWins, 500);
Crate layout
tines/
tines-core/ no_std core: Observation, Store / Transport / BeliefPolicy
traits, the exchange state machine, and the `Node` facade.
`std` is optional; `tines-core` builds for `thumbv7em-none-eabihf`
with `--no-default-features`.
tines-sim/ std: the simulator, contact traces, and the convergence /
order-independence property tests.
CI
scripts/ci.sh runs the full gate — formatting, clippy, the test suite, and the
no_std build — and is safe to drop into a CI config:
sh scripts/ci.sh
Where to read more
The spec (authoritative) and the implementation plan that argues from it:
- Spec:
docs/superpowers/specs/2026-10-16-tines-design.md - Plan:
docs/superpowers/plans/2026-10-16-tines-core-and-sim.md
