Goal, in your words: account data lives on the server; each device keeps its
own sense of where *you* were on *it*; drafts cross between them; and when you
come back you pick — carry on where this device was, or pick up what you left
on the other one.
---
Most of the plumbing is built. Worth knowing before scoping the rest.
_syncKeys / _syncPrefixes, [internal endpoint] | Working. Server publishes the key list so client and server can't drift. |kai.draft. | Working. Drafts already follow the account. |_autoDraftSave / _autoDraftRestore | Working. Excludes passwords and data-nodraft. |_leoMarks(), {page, at} per document | Working, device-local. |kai.lastView, oracle.lastChat | Fixed 2026-08-18 (v9.10.720) — was account-synced, which is why the Codex followed you between devices. |_kai_device_secret | Exists, but it is an auth secret. Not usable as an identity we display. |So the model you described is roughly half-implemented already. The missing
half is not sync. It is provenance and choice.
---
1. Nothing records which device wrote anything. Drafts and reading marks
carry no device id. So "continue the draft from your phone" cannot be built —
not because syncing is hard, but because nothing knows it came from a phone.
2. Drafts have no timestamp. saveDraft stores a bare string. Two devices
with two drafts cannot be ordered, so "which is newer" is unanswerable, and
last-write-wins silently eats the other.
3. Restore is silent and unconditional. _autoDraftRestore fills every
empty eligible input at 120ms and 700ms after load. It never asks. That is fine
when the draft is yours from this device and wrong when it came from elsewhere —
it is the same class of problem as the Codex jump: a decision taken for you.
4. There is no device list. Nothing to name, see or forget a device with.
---
Add kai.device.id (random, non-secret, device-local, never synced as a
preference) and kai.device.label, defaulted from the user agent — "Phone",
"Windows PC" — and user-editable. Registered to the account so other devices can
render a name instead of a hash. Not the existing _kai_device_secret;
reusing an auth secret as a display id would leak it into UI and logs.
Draft values become { v, at, dev } instead of a bare string, read
back-compatibly: a bare string is treated as {v, at:0, dev:null} so nothing
written by an older client is lost, and old clients keep working because the
server's allowlist is back-compat by design. Reading marks gain dev alongside
the at they already have.
kai.lastView, scroll, reading mark. Local, neversynced. This is the "carry on where I was on *this* machine" track.
This is the "pick up what I left elsewhere" track.
Both are kept. The point is that you choose, rather than one silently winning.
Replace the silent jump with an offer. On boot, compute candidates:
than this device's own
Then:
Dismiss is permanent for that item. A chip that reappears after you decline is a
nag, and a nag gets ignored, and an ignored control is the same as a broken one.
Same-device drafts keep filling the box silently — that behaviour is right and
people rely on it. A draft from *another* device stops overwriting and offers a
chip instead. That is the whole behavioural change.
---
{v, at, dev}; back-compat readers | none | low — additive, reversible |setView('codex') | the Codex stops grabbing you | low |Phase 1 is the only hard dependency. 2, 3 and 4 are independent after it and can
ship in any order or not at all.
---
accept both shapes forever, not for a transition window — old clients keep
writing bare strings for as long as one exists unrefreshed.
times where the sync round-trip already provides one.
not store raw user-agent strings; derive a label and keep the string local.
_kai_device_secret, and never sync kai.lastView again — thatregression is what this whole thread started from.
---
1. Should reading marks sync across devices? If yes you get "continue reading
from your phone" on the desktop; if no, each device remembers only its own
place. Your description implies yes, but it is the one place the two tracks
genuinely overlap.
2. Chip on every load, or only on conflict? Only-on-conflict is quieter and
is what I would build unless you say otherwise.
3. Same-device drafts — keep filling silently, or always ask? I would keep
silent; asking about your own draft on your own machine is friction with no
information in it.