phone-laptop-roam
one human, two devices, one hum that follows the body
See sim/tests/phone_laptop_roam.rs for the executable form.
The setup
Trust tier T1 — own-devices. One operator with two humds:
- humd-L (laptop) — has the brood. Hosts a
claude-clinest, holds the live hum. - humd-P (phone) — quiet at first. No hum hosted locally; runs only
the daemon and a
vercel-ainestler embedded in a chat app.
Both humds share the same operator pubkey (T1 fingerprint), so their
HumdIds are siblings on the same identity chain. The in-memory
Transport wires them as peers from the start, but humd-P attaches no
nestler until the operator picks up the phone.
A single nestler on the laptop drives the hum during the first leg:
opencode pointing at hum’s openai-server over provider.hum, rich,
stateful. It speaks every chi the openai-shape preserves.
The happy path
- humd-L mints a hum, sigil resolved,
chi:"hello"exchanged with the OC nestler. Operator types the prompt. Bloom begins; petals flow. - Mid-bloom, operator walks away from the laptop. The nestler on humd-L stays attached; the brood keeps the worker warm; wane keeps ticking against the local sigil.
- Phone wakes. humd-P’s chat app opens; its
vercel-ainestler asks the local humdpeer-addstyle: “attach me to hum<sigil>.” humd-P doesn’t host the hum, so it routes the attach request to humd-L over the ensemble link, naming the target byHumdId. - humd-L recognises the sibling identity, grants attach, and begins
replicating its bloom: every fresh
chi:"chunk"is also routed to humd-P, which fans it out to the phone’s nestler. - The operator now sees the same petals on the phone, in order, with
no replay of the petals they already saw on the laptop (cursor
carried by
wane). - Operator types a follow-up on the phone. The nestler emits
chi:"prompt"into humd-P, which routes it to humd-L (the hum’s home). humd-L feeds the brood; new petals flow back to both humds. - Operator closes the laptop. humd-L’s local OC nestler detaches; the hum keeps living, now driven solely from humd-P’s tap.
The failure modes
- Identity mismatch. humd-P’s
HumdIdis forged or wrong; humd-L must refuse attach withRouteError-style rejection and never leak a petal. Test asserts zero tones reach humd-P after the bad handshake. - Late attach loses petals. Without wane the phone would either miss the chunks emitted between walk-away and attach, or get a flood of duplicates. Test must catch both: attach replays exactly the petals humd-P has not yet seen, and replays them in sigil-order.
- Divergent wane. If humd-L advances wane locally while humd-P’s
attach is in flight, the catch-up batch must close the gap; humd-P’s
WaneTracker::is_behindmust readfalseonce attach settles. - Stuck home. If humd-L drops mid-bloom (process death, not
network), humd-P’s tap must surface
chi:"error"with a peer-loss qualifier — not silence. Drone must stay quiet until the loss is surfaced.
The success criteria
- After step 4, humd-P’s nestler tap receives
chi:"hello"referencing the samesigilhumd-L holds, withinRTT + 50ms. - humd-P observes the full ordered sequence of
chi:"chunk"petals from prompt-start to current-tip; the concatenated text equals humd-L’s. No petal appears twice; no petal is missing. - After step 6, humd-L’s tap receives
chi:"prompt"originated by humd-P withinRTT + 50ms, withfromset to humd-P’sHumdId. - The final
chi:"finish"arrives on both taps withusage.output_tokens > 0and matching token counts. ensemble.peers()on humd-L lists humd-P throughout; on humd-P, lists humd-L. Neither registry shows stale entries after step 7.
What this scenario validates
hum_idis location-independent. A hum is named by its sigil, not by the humd that birthed it. Routing finds the hum wherever it lives.- peer-add wires connections. The ensemble’s
add_peer+Transport::connectpath is the same regardless of which humd initiates; T1 is identity-shortcut, not a different code path. - State propagates via wane. Mid-stream attach replays exactly the missing slice. No bespoke catch-up protocol — the wane cursor does the work.
- Routing surface.
Ensemble::routecarrieschi:"prompt"from humd-P to humd-L, andchi:"chunk"from humd-L to humd-P, both addressed byto: <HumdId>.