You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
v0.3.0 — one daemon, many nodes; services by address; the family LAN
One daemon, and the nodes that use it
One vox daemon per data root (VOX_DATA_DIR). It holds the machine's one port and runs every node attached to it. A node is one identity: you, and one for each agent working beside you.
vox node create | attach | detach | list.vox node attach starts the daemon if none is running.
--node <name> (or VOX_NODE), given after the verb, says which node a command acts as. With one node attached, that one is used.
Verbs that hold a session start the daemon and attach their node: serve, connect, up, forward, lan up. The one-shot verbs (room, status, trust, share, service, app) act only as an attached node, and when it is not attached they say so: vox node attach <name>.
Two commands that start the daemon at the same moment both succeed: the one that loses the race waits for the winner's socket.
What attaching a node says reaches your terminal. Skipped anchors-file lines, and that the node is carrying on with no anchor, are printed by the verb that attached it, by vox node attach and in the TUI's notice line, as well as in the daemon's log.
A room the daemon does not reopen is named in its log, with the reason.
vox node (the anchor) is a daemon with its anchor node attached, not a process of its own kind.
The terminal client is a client of the daemon. It has no lock: a node takes its passphrase once, when it attaches, and stops only when it is detached. In the client, :attach attaches your node, :node <name> acts as another, and :link shows a room's link.
Rooms
vox room link <room> prints the room's link, and how to send its passphrase: another way than the link (in person, a call, a different app). A member joins with the link and the passphrase.
vox room leave <room>: the others are told, and the room is removed from your node. Joining again later works.
vox room end <room> ends a room for everyone. Only its creator, or an admin the creator named with vox room admin add <room> <member>, may. An end, or a retention change, made by an admin at the same moment as that admin is taken back does not stand.
vox room retention <room> <duration> (1h, 1w, 1m for a month, seconds, or forever) sets how long the room keeps messages, for everything already in it.
vox room read shows a structured post in words: its kind, the work item and the body, or "file offered: NAME (N bytes)". --json gives the envelope.
A host sees a refused join: in the terminal client's notice line, on the daemon's stderr and in its log, without offering the passphrase.
A join in progress is quiet. For a joiner it has just let in, vox serve says nothing about a sync or a dial that fails while the joiner is still settling, until the joiner's first clean session or 60 s. A failure after that is said, and vox status --json counts them all.
vox trust add says why your key for the member waits: the room has not synced since you joined it, or the member is not yet admitted. It is sent as soon as that changes.
A room is forward-only: a member reads what is posted after you trust it.
Services by address, UDP, sharing
A service is reached as <service>.<node>.<room>.vox, and only that way.<node> and <room> are your own names for them (or the node's fingerprint and the room's id). vox serve ssh=22 names the service; a bare port is refused.
vox serve names who can reach the service: the members you trust, by name, and the members of the room who cannot, by fingerprint. It says it again when someone joins.
A forward into a room not yet synced says it is waiting for the room's first sync, and after its patience is refused with that reason; it never guesses what is shared there.
UDP over Vox: vox serve dns=53/udp, and vox forward carries a UDP service.
vox share <room> <path> serves a file or a folder to the members you trust until --count fetches or --for a time. They fetch it with vox room get. An offer that has ended is said to be gone; an offer withdrawn while it is being collected is said so, naming who stopped sharing it; a sharer that cannot be reached is said so.
The family LAN (macOS)
vox lan up <room> puts a room's members on one virtual LAN, each on its own interface, with discovery (mDNS, broadcast) flowing between members who trust each other. Nothing on your machine is reachable over it unless its port is listed with --allow.
Making the interface needs root, and only a small helper has it: sudo vox lan helper in one terminal, vox lan up <room> --allow <ports> as yourself in another. vox lan up checks that the helper answers as one before it does anything, and refuses a socket that is not one at once, naming it.
Every LAN interface reaches its room's LAN, with two or more nodes on one Mac: the helper adds a route scoped to each interface, checks it is there, and reports one it could not add.
Agents
Each agent is its own node, never a person's. vox agent plugin claude|codex|opencode --node <name> prints the integration for that node, and the hook acts only as the --node it is given.
No character reaches a reader or an agent unseen. Zero-width and other invisible characters, the tag characters that can spell text invisibly, bidi controls and other control characters are all shown as ⟨U+XXXX⟩ in the terminal client, in vox room read and in what an agent's hook gives it. Emoji built with joiners (👨👩👧, 🏳️🌈) and the subdivision flags are shown whole.
An agent's hook never hangs its harness. A hook whose node is detaching is refused after 8 s, and its registration is bounded at 10 s.
Proved with real agents. Optional live proofs, each run sandboxed, drive real Claude Code, Codex and OpenCode turns against a room, each through its node's hook; all were green on a live run.
Words
Help and messages speak of nodes, rooms, room links and trust, and name no design document.
Errors are said in plain words. A reply a command did not expect is said as a sentence, never as a debug dump; a node detached while a command waits says "node was detached from the vox daemon, so this stopped". The same goes for the app's notices, claim outcomes and a ping's reach.
A peer's reason for closing a connection is shown, also when the close is larger than the path MTU.
Reliability
A crash costs you neither a room nor a member. A daemon killed during vox trust add keeps the posts made after its restart readable to the member just trusted: where the decision stands in the room's order is saved before the trust itself. A daemon killed in the middle of a join leaves the room either absent or one that reopens; a room your node holds closed can be joined again with its passphrase.
A host going offline while a joiner reads the room is said so and retried, like a room not yet on the board. A failed join prints each of its steps once.
A vox connect that is stopped lets go of the room at once: a client that hangs up mid-request is noticed straight away, so its node's goodbye does not wait on the request.
A member opens sync sessions only with members. Anchors' boards are read, so a member that restarted learns where the others are and can release keys to them.
vox status counts a flow on a retired connection: datagrams on a peer's connection that a better path replaced are reported.
Two members dialling each other through a relay at the same moment both get through and keep the same connections at both ends; a circuit that is not kept is closed.
A first relayed connection asks for its circuit at once, with the request for a direct dial-back running beside it.
A dial at an address heard on the local network is one attempt among others. If the socket cannot use the address (an IPv4 address heard on an IPv6-only node), the member is still reached through the anchor at once.
Documentation
The user manual, docs/manual/, describes v0.3.0 command by command.
ADR-017: trusting a member is the operator's act, made by any process running as the node's user, person or agent, behind the 30-minute passphrase window.
ADR-012 N-49–N-58: network-change detection, gateway discovery beyond Linux and the PCP mapping lifecycle. These are planned for v0.3.1; v0.3.0 does not have them.
The v0.4.0 UX research (docs/ux/): the research, the interview answers and the review notes.
Work items delivered
#18 R15: Addressing uses fingerprints on the wire and shows keyring names
#19 R17: The filer can hard-lock a claim; a silent holder can be taken over after a window
#52 R1: A room has no lifetime message limit; reopen, restart and cold catch-up work at any size