brainmesh
v0.0.1
Published
One encrypted, append-only brain across your devices
Maintainers
Readme
brainmesh
one brain across your devices. append only, end to end encrypted, no required central server.
The target is a laptop, always-on machine, phone and wearable, each holding only what it needs. The CLI and desktop-to-desktop path exist today. Native iOS and Android thin clients exist for grant-scoped search and capture; native phone replicas and watch clients do not. Sync uploads no plaintext. Optional peers or object storage may hold signed ciphertext, while an explicitly authorized hosted model sees only the plaintext slice its grant returns.
Losing one device does not lose history when another enrolled device and a data copy remain. If every device is lost, a separate encrypted recovery kit plus its 24-word paper phrase restores the keys without cloning an old device.
the promise, in plain words
- it works offline. write on a train, it shows up everywhere later.
- storage cannot read it. not a bucket, relay, ISP, or sync operator. A hosted model can read only the slice you deliberately return through a grant.
- nothing is ever silently changed. every entry is signed and chained to the one before it, so a rewrite is detectable, not a matter of trust.
- you can delete the app and keep the format. ciphertext, signed history and keys are ordinary files with an open spec. Preserve an enrolled key separately from data backups.
documentation
| | | | --- | --- | | docs/PRD.md | what this is, and what it is not | | docs/SPEC.md | the formats, normative, reimplementable from the text | | docs/CONSTRAINTS.md | invariants, threat model, stated limits | | docs/EDGE-CASES.md | 393 cases, each with what it drives | | docs/DECISIONS.md | choices made and rejected, with reasons | | docs/ROADMAP.md | built, next, and known gaps |
the six ideas
- a device is a keypair. there is no account and no login.
- a record is a signed, sealed fact. immutable.
- each device writes only its own durable log, so logs never merge. Two edits to one projected file become an explicit conflict event instead of data loss.
- big things are stored by their hash, fetched from whichever device has them.
- permission is a signed note saying who may read what, until when.
- search and everything you see is a cache. delete it, it rebuilds.
status
early. the core is real and tested, the product around it is not written yet.
| piece | state |
|---|---|
| record format, signing, sealing | done |
| append only log with hash chain | done |
| sync between devices | done, offline and resume covered |
| chunked blobs, 4gb files, resume, dedup, immutable transfer packs | done |
| self-contained offline bundle, signed history plus encrypted file bytes | done |
| fork detection, torn tail repair, single writer | done |
| enrollment, one command over ssh | done, works between real machines |
| cli | done, including path-preserving workspace scan/restore/verify/watch |
| transport, stdio over ssh | done |
| files, any size, index first then bytes on demand | done, verified between machines |
| grants, expiry, attenuation, revocation, read receipts | done |
| workspace subtree grants (company/ without personal/) | done |
| owner authority separated from shared decryption keys | done for new brains and spaces |
| signed company roots, role compartments, keyless limited admins, scoped co-owners, and rotation | done at CLI layer |
| remote MCP OAuth, resource binding, refresh rotation, Origin validation | done |
| offline 24-word recovery kit for personal, space and organization keys | done; no device identity is cloned |
| hosted or company escrow and recovery policy | not built; offline kit is user-held |
| personal and shared-space future-access rotation | done; removed devices keep old plaintext but never receive later epoch keys |
| the app a non technical person opens | not started |
The test suite is mostly failure cases, not happy paths: writing on both sides of a partition, resuming a transfer that died at chunk three of four, a restored backup forking a log, a power cut mid append, two processes racing one log, a relay holding data it cannot read, a peer claiming a 4gb record, path traversal, a stale workspace scan, mass deletion, and divergent offline edits.
running it for real
The npm preview builds the audited Rust CLI locally. It requires Node 18+, Rust
1.75+, and the same operating-system build dependencies as a Cargo build. The
install step compiles only the packaged source; it does not open ~/.brain,
create an identity, or start a service.
npm install --global brainmesh
brainmesh --helpBuilds use stable Rust. Ubuntu and Debian also need the Secret Service build headers even when a headless node chooses file-backed key storage:
sudo apt-get install build-essential pkg-config libssl-dev libdbus-1-dev
cargo build --releasebrainmesh start # on the always on machine
brainmesh join mini # on every other machine
brainmesh sync # no arguments, it remembers peersjoin records the peer, so nothing after that needs a hostname. background
sync is store-scoped and uses the native user scheduler on macOS, Linux and
Windows:
brainmesh service install --interval 300
brainmesh service statusInstallation first runs a real sync --require-any. It refuses an empty route
list or a setup where no configured peer completes the protocol, rather than
installing a timer that moves no data. The generated launch agent, systemd user
timer or Windows scheduled task uses the canonical absolute binary and store
paths, never a shell. Its private, bounded manifest is inside the store;
service-manager files are deterministically derived from that store so a
tampered manifest cannot redirect uninstall at another file.
An ordinary interactive sync still tolerates unreachable machines because
laptops are closed most of the day. A scheduled sync uses --require-any, so
service status reports the last run as unhealthy when every route is down.
It also hashes the installed executable and regenerates the expected native
scheduler configuration, so replacing the binary or changing the job is shown
as unhealthy instead of silently trusting registration alone.
An optional independently held rollback witness can be ratcheted only after a
successful required sync:
brainmesh service install --interval 300 --witness /Volumes/stick/brain.headsThe witness must already exist and must be outside the store. If it is absent, unwritable, stale relative to the local store, or concurrently changed, that scheduled run exits nonzero and service health exposes the failure. Uninstall never removes the external witness. Windows runs the task only while that user is logged in. A Linux user service requires a working per-user systemd manager; an always-on logged-out server also needs administrator-configured user lingering.
Peer routes are local configuration, not replicated authority. The peers
file is bounded, refuses symlinks and control-character injection, and is
created or repaired at mode 0600 on Unix because a custom remote command can
reveal private topology or arguments.
brainmesh service uninstall removes only that store's scheduler and private
manifest. It preserves the brain and macOS sync logs. Removing the data itself
is deliberately a separate, manual decision.
using it
brainmesh start # first machine
brainmesh join mini # every other machine, one command
brainmesh write "a thought"
brainmesh sync mini # pull and push in one round trip
brainmesh read
brainmesh statusthree machines need six directions and nobody configures six directions by hand. so a machine announces itself once, into the log, and every other device learns it on the next sync:
brainmesh peer announce [email protected] /Users/louis/.local/bin/brainmeshannouncements are records, so they travel like everything else, sealed like everything else. a device skips its own, and a machine that announces more than one way to reach it becomes one entry with fallbacks, tried in order.
some machines cannot use ssh for this. windows openssh does not forward piped stdin, which was measured rather than assumed: five bytes through a file redirect came back answered, the same five through a pipe never arrived. so there is a socket transport too, and both sides prove they hold the brain key before a byte of anything else moves:
brainmesh listen 100.89.216.79:7778 # on the always on machine
brainmesh peer announce tcp://100.89.216.79:7778nothing new to hand out: the proof is the brain key every member already has, and a stranger is turned away at the greeting.
The direct listener bounds work before that greeting: at most 64 peer sockets are active, at most eight come from one observed source, and one source may start at most 120 admitted sessions per minute. Its source-window table is capped at 4,096 entries, over-limit sockets close without a protocol response, and every admitted stream has 30-second read/write inactivity timeouts. Outbound dialing spends one total 30-second budget across the addresses already returned by the OS resolver. These are process-local resource controls, not a public DDoS service; a proxy collapses its clients into one observed source and must enforce the real-source boundary itself.
When neither device accepts inbound connections, a tiny blind rendezvous can pair their outbound sockets. The public machine runs only the relay command; it does not have a brain directory or any key:
# public machine
brainmesh relay listen 0.0.0.0:7779
# always-on brain node; generate the token once with `brainmesh relay token`
brainmesh relay expose relay://relay.example.com:7779/<64-hex-token>
# another enrolled device, after the exposure is waiting
brainmesh sync relay://relay.example.com:7779/<64-hex-token>The random token is an unlisted room name, not storage authority. After pairing, the existing mutual brain-key proof runs end to end, before any heads or sealed records move. The relay stores no vault bytes and parses none of the wire stream. It can still observe IP addresses, timing and byte counts, steal a room to cause a failed authentication, drop traffic or go away. Rotate the token to unlink later sessions. This TCP relay gives private desktop peers a route; it does not carry HTTP credentials or plaintext. A separate TLS-passthrough route below makes the grant-scoped endpoint public without changing that boundary.
join is the whole pairing flow. it asks the primary machine over ssh to wrap
the brain key and authority public key for this device. The stable authority
signing seed is deliberately not cloned to routine devices; otherwise a lost
device could authorize its own return after rotation. no key is ever shown to
you, typed, or pasted, which is the failure this design exists to avoid.
The device introduction is itself signed: v2 : device signing key : X25519
agreement key : signature. This prevents a relay or clipboard tool from
substituting its own encryption key while leaving the visible device id alone.
Unsigned legacy introductions are refused; rerun brainmesh id on the joining
device.
Personal-device removal is an explicit data-key epoch, not grant theatre:
brainmesh epoch status
brainmesh epoch rotate --keep-self --keep '<signed introduction>'The root-signed transition pins the exact final head of every omitted device, delivers the next key only to retained devices, and makes all new writes and blobs use that epoch. Sync and bundle import quarantine the complete incoming record transaction, fold every signed transition it carries, and reject a removed author's post-cutoff tail before any author is committed. Old history remains readable to devices that already held it; no cryptography can recall a copy. If an offline node stored a tail before learning the revocation, the signed cutoff later makes that tail logically quarantined: its immutable bytes remain as evidence, but runtime reads, sync advertisements, served pages and authorized bundle exports stop at the cutoff. Create a new recovery kit after every rotation because the old kit is then intentionally stale.
Pre-epoch owner stores migrate once. The authority signs the already-present authors as historical evidence before enforcing membership; a later signed device introduction upgrades that evidence. This preserves existing valid history without silently treating every future unknown author as trusted.
there is no daemon, no port, no certificate and no discovery. sync spawns
brainmesh serve on the other machine over ssh and talks to its stdin and
stdout, the way git has worked for twenty years. over a tailnet that means it
already works everywhere you can already ssh.
where the keys live
in a 0600 file, the same place ssh, gpg and age keep theirs. brainmesh
status says so, because a security property you cannot see is one you cannot
trust. On Unix, Brainmesh creates the file at mode 0600 before writing any
secret byte, opens with no-symlink semantics, checks the effective mode on the
opened handle, and refuses an unsafe key before reading it. Creation and
replacement first flush a complete private sibling and only then publish it:
no-clobber for a new key, atomic replacement for an existing key, plus a parent
directory sync on Unix and write-through publication on Windows. A crash can
therefore expose the complete old key or the complete new key, never a
truncated identity. A filesystem that cannot enforce private modes is an error,
not a silently weaker key store. Windows uses the user-profile ACL boundary.
the os keystore is available with BRAINMESH_KEYSTORE=1 and is off by
default. that is a reversal, and the reason is worth knowing: a keychain entry
is bound to the code signature of the program that wrote it. rebuild the
binary and it is a different program, so the read prompts. a prompt is a
dialog, a dialog needs a human, and there is no human at a five minute timer.
we found this the way you would expect, by replacing the binary and watching
every command hang at zero cpu behind an invisible dialog.
a key left in the keystore by an older version is still read, once, and written back out to a file. every keystore call is bounded at five seconds, and one that does not answer raises rather than reporting the key absent, because absent is what makes a device decide it should make a new identity.
the honest limit: neither a file nor a keystore protects against something running as you. only a hardware backed non extractable key does, and both the apple secure enclave and the windows tpm hold p-256 only, never ed25519. that is why every record carries a key type byte: without it, hardware backed identity would be foreclosed forever.
full disk encryption is the control that actually matters here, and it covers swap and backups too, which no keystore does.
owners, members, and why those are different keys
A decryption key answers "may this device open the space?" It must not also answer "may this device give access to somebody else?" New brains and spaces therefore have a random owner authority key that is unrelated to the brain key.
- the primary/recovery identity holds the authority secret; routine devices
receive the data key and authority public key through
join space invitegives a member the space key and only the authority public key- a member can contribute to that shared space, but cannot issue root grants or invite another member
- an owner node rejects a grant manufactured from the shared space key, even if its signer has a valid member log
Old stores remain readable in legacy mode. Before using one as a company boundary, make the migration explicit:
brainmesh authority status
brainmesh authority harden
brainmesh space harden teamHardening deliberately invalidates existing grants. Accepting them afterward
would preserve the exact capability being removed. Existing devices must also
receive and trust the new authority before they enforce the new boundary; do
not interpret one machine's harden output as a fleet-wide migration.
This solves who can grant. It does not make one shared space confidential between its members: anyone holding that space key can read that whole space.
ending shared access without rewriting the past
An owner can move future writes to a fresh key generation and hand that key only to retained devices:
brainmesh space rotate team \
--keep '<remaining-device:introduction>' --days 7
# retained device:
brainmesh space join '<signed-rotation-bundle>'The handoff is signed by the previous space authority, addressed to one device,
and carries a conforming-client deadline; copied offline ciphertext is not
erased by time. A retained device accepts it only when it already holds the
exact previous generation and trusts that authority. An omitted device keeps
the old history but cannot decrypt or authenticate future generations. A
member with the decryption key but without the owner key cannot rotate. Use
--keep-none to make an intentionally empty retained set; an accidentally
missing --keep is refused. Bundle delivery and new peer setup are still
manual.
companies are key compartments, not folder ACLs
An organization has a durable signing identity, and each role or project is a separately encrypted space with its own key and owner authority. An engineer invited only to engineering never receives the finance key—or even finance role metadata—in the first place. A founder can receive both compartments.
brainmesh org new Acme
brainmesh org role Acme engineering
brainmesh org role Acme finance
brainmesh org invite Acme engineering <employee-introduction> --days 7
# employee:
brainmesh org join <signed-bundle>
# org join prints a device-signed receipt; owner records it:
brainmesh org accept <signed-receipt>
# if the receipt was lost, the employee can prove the installed keys again:
brainmesh org receipt <same-signed-bundle>
brainmesh org roles Acme
brainmesh org members Acme
# root: make another device a full co-owner of engineering@g1
brainmesh org delegate-owner Acme engineering <manager-introduction> --days 2
# manager installs the addressed handoff:
brainmesh org join <role-owner-bundle>
# manager may now issue an employee invitation without the organization root:
brainmesh org invite Acme engineering <employee-introduction> --days 7
# the employee returns both its receipt and the delegated invitation to root:
brainmesh org accept <signed-receipt> <delegated-invitation>
brainmesh org co-owners Acme
# recommended when a manager may nominate people but root must approve first:
brainmesh org delegate-admin Acme engineering <manager-introduction> --days 30
# manager installs the public, keyless certificate:
brainmesh org join <administrator-certificate>
# manager creates a proposal containing no role key:
brainmesh org propose Acme engineering <employee-introduction> --days 2
# employee acknowledges it and seals the keyless request to the company root:
brainmesh org request <access-proposal> \
--to https://root.example/v1/org/inbox
# a transport delivers the opaque line; root verifies and queues without keys:
brainmesh org receive <access-envelope>
brainmesh org inbox Acme
# root reviews, then releases the first key-bearing bundle by queued request id:
brainmesh org approve <request-id-prefix> --days 7
# or root records a terminal, signed no-key decision:
brainmesh org deny <request-id-prefix>
# employee polls the same trusted inbox; the signed result decrypts locally:
brainmesh org poll <request-id-prefix> https://root.example/v1/org/inbox
# an approval's first line is installed normally, then its receipt is returned:
brainmesh org join <approved-first-line>
brainmesh org accept <signed-receipt>
brainmesh org admins Acme
# root may stop an exact proposal certificate before its deadline:
brainmesh org revoke-admin Acme <manager-device-or-certificate-prefix>
# one-time upgrade for organizations created before role-state certificates:
brainmesh org migrate Acme
# remove future engineering access by omitting that device from --keep:
brainmesh org rotate Acme engineering \
--keep '<remaining-device:introduction>' --days 7The bundle is signed by the organization root, addressed to one device, and has a root-confirmation deadline. A conforming joining client refuses it after that deadline. Because the bundle is offline ciphertext already encrypted to that device, the bytes cannot be made undecryptable on a hostile modified client; actual future-key revocation requires rotation. The joining device pins the public organization identity and never receives the root secret. Each encrypted role invitation is checked against the space id before any key is persisted. Changing a role, recipient, deadline, space, or encrypted invitation breaks the organization signature.
New role creation and every rotation also carry a root-signed role-state
certificate binding organization, role, generation, new compartment and its
predecessor. On a member device, the exact root-signed invitation bundle is the
role-state proof. Once either signed form exists for a role, an unsigned JSON
summary—even one claiming generation 99—cannot move it forward. Preexisting
unsigned roles remain a visible migration boundary until their next signed
rotation or an explicit org migrate. Migration keeps the current compartment
keys, but root-signs them only after the complete generation-one-to-current
lineage has one space per generation, every predecessor matches, and this
device proves it owns every current role. All role certificates append as one
batch; one missing key or damaged lineage leaves every role untouched. Running
the command again is a no-op.
org receipt is a safe, idempotent recovery path while the invitation remains
valid: it checks that this is the addressed device and that every installed
role key derives the signed space id before producing a fresh acceptance.
First contact still needs a trusted comparison of the organization id, just as SSH needs a host-key check: a valid signature proves continuity with that key, not that a stranger's newly created key really belongs to "Acme".
Agents use the exact same grant path inside a compartment:
brainmesh grant <agent-device> --space 'Acme/engineering@<org-id>' --days 30
brainmesh mcp --as <grant-id> --space 'Acme/engineering@<org-id>'The authoritative membership chain ends in organization-root confirmation.
For a direct invitation, root signs the exact invitation. After installing only
those role keys, the employee device signs an acceptance bound to it, and root
confirms both before the deadline. org members reconstructs confirmed current
membership only from that signed invitation → device receipt → root
confirmation chain.
Unsigned summary records are explicitly non-authoritative once signed role
state exists. The alternate paths below either add a root-authorized co-owner
signature or require root approval before the key-bearing invitation exists.
The organization root can also sign a role-owner handoff addressed to one
device and exact role generations. This is deliberately called co-ownership,
not temporary admin access: the device receives each role's data key and
separate owner signing seed, so it can read, grant, and invite within those
compartments. It receives neither the organization root nor sibling-role keys.
--days is a conforming-client installation window for the encrypted handoff,
not cryptographic erasure. A recipient already holding ciphertext addressed to
its own device can modify its client and decrypt later. Once the seed is
copied, there is no honest wall-clock expiry; rotating the role to a new
generation is what ends its authority over future data.
A co-owner's employee invitation is signed by that device and embeds the root-signed role-owner bundle. The employee pins the root, pins each exact role authority, installs ordinary member keys, and signs the normal receipt. The organization root must import that delegated invitation with the receipt and confirm that every role generation is still current. Confirmation is a signed membership and inventory fact, not a key-release gate: the addressed role key was already delivered. An offline co-owner may not yet know a rotation happened and can still disclose the old key—past access cannot be recalled—but root confirmation rejects the superseded membership and the co-owner never receives the new generation.
For strict approval before access, use a limited administrator instead of a co-owner. Its root-signed certificate contains only device identity and exact role-generation references—no data key, owner seed, or organization root. The administrator signs a keyless proposal, the addressed employee signs a keyless request, and root checks administrator expiry plus current role state before creating the first encrypted role invitation. The root-signed approved wrapper binds that request to the key-bearing invitation. A stale proposal after rotation is rejected before the employee receives a key, and repeating approval returns the exact prior bundle. Root may revoke one exact administrator certificate early; approval checks that signed revocation before creating or reprinting a key-bearing bundle.
New administrator certificates also bind a deterministic organization inbox
public key. org request seals the complete signed request to that key under a
protocol-specific X25519/XChaCha20-Poly1305 domain before printing it. A future
mailbox or an existing chat/file transport sees only organization and root
pseudonyms, request id, ciphertext size and timing—not the employee, roles,
proposal or signatures inside. Any root restored from the same organization
authority derives the same inbox secret and can open it. Root still accepts
legacy plaintext requests for compatibility; new clients emit envelopes.
org receive is the durable trust boundary for delivery. It opens and verifies
the envelope while the request is live, then stores the still-opaque envelope
as an encrypted append-only record without approving it. Exact retries are a
no-op. org inbox decrypts only on the root and renders request id, employee,
administrator, exact role generations, and one of pending, approved, denied,
expired, revoked, stale, or conflicting. org approve accepts a unique
lowercase request-id prefix, reopens the stored envelope, and repeats every
expiry, revocation and current-generation check before key release. org deny
signs and durably stores a terminal decision for the exact request without
creating any invitation; repeating it returns the same receipt, while a later
approval fails. org inspect-denial lets the employee or an auditor verify the
root signature and render the request. A scoped listener makes this boundary
networkable:
# root node; loopback by default
brainmesh org listen-inbox Acme 127.0.0.1:7781
# employee or native client retries only the opaque envelope
brainmesh org submit - http://127.0.0.1:7781/v1/org/inbox \
< access-envelope.txtThe listener accepts POST /v1/org/inbox, GET /health, and employee polling
at GET /v1/org/decision/<request-id>. It refuses all browser Origin
headers, caps bodies at 8 MiB and concurrent connections at 32, verifies the
held organization root, and calls the same idempotent durable receive path. It
is plain HTTP because public TLS must terminate on the trusted node; expose it
through the existing SNI TLS passthrough or another node-local terminator,
never by letting a hosted proxy decrypt the connection. Arrival and polling
still cannot approve. A one-minute in-process limiter permits 120 submissions
and 600 polls per observed source, tracks at most 4,096 source/traffic buckets,
returns 429 beyond budget, and exempts health. This contains one-source
mistakes and abuse; distributed limits and original-client address recovery
still belong at trusted ingress. Notifications remain unbuilt.
org request --to stores and prints the envelope before submitting it, so a
network failure never destroys the request. org submit is the explicit retry
path, and - reads a bounded envelope from stdin so it need not enter the
brainmesh process arguments. Both use curl's HTTPS and system-trust support,
stream ciphertext into curl over stdin, require an HTTP(S) URL ending in
/v1/org/inbox, and verify that the JSON acknowledgement names the exact
request id plus a boolean queue state. A generic 2xx from the wrong service is
not delivery proof.
This boundary is cryptographic rather than cooperative: possession of the limited-admin certificate and signing key cannot decrypt a role or manufacture its member invitation. It can only ask root. The direct and co-owner commands remain available for personal teams that prefer offline convenience over mandatory approval.
Rotation creates a new role generation and key, then emits signed invitations
only for the explicit --keep devices. Omitted members show as historical and
never receive the new key; retained members remain confirmation-pending until
their new receipt is root-confirmed. They keep old history. --keep-none is
required to rotate everyone out, so an empty retained set is never an
accidental command-line omission.
Administrator revocation is authority over future root approvals, not retroactive erasure: it immediately makes the root refuse pending requests from that certificate, but cannot recall a role key released by an earlier approval.
Likewise, approval and denial are first-decision-wins on converged root state.
A denial receipt is brainmesh-org-access-denial-v1: and binds the complete
employee request, exact request id, organization root and decision time under
the root signature. If separately recovered roots decide differently while
partitioned, inbox state reports a conflict and refuses further release; it
cannot recall a key an isolated root already delivered.
An approval or denial is returned as
brainmesh-org-access-decision-envelope-v1:. Root seals the complete signed
outcome to the X25519 agreement key in the employee introduction; a distinct
KDF domain prevents request, decision, enrollment, epoch and brain-key
ciphertexts from being substituted. org poll resolves a locally signed
request, fetches only its full 256-bit id, decrypts on the employee device, and
stores the verified plaintext outcome inside that employee's encrypted brain.
org decision reopens it later without the root being online. An observer sees
the stable request pseudonym, pending/ready timing and ciphertext size, but not
approval versus denial, roles, employee identity or wrapped role invitations.
The service may still delay, drop or falsely remain pending; signatures prevent
forged decisions, not denial of service.
Receipt, owner-handoff and rotation delivery are still manual. Role co-owners are full cryptographic owners of selected generations; limited administrators provide approval-gated key release without read access and support root-signed early revocation. Quorums, policy automation and automatic rotation-state delivery are not built. Old plaintext can never be made unread.
versions
nothing is ever overwritten, so every version you have written is already on disk. what was missing was a way to ask for one:
brainmesh add plan.txt # three times, as it changes
brainmesh files # plan.txt 21 bytes here 3 versions
brainmesh history plan.txt # each one, dated, latest marked
brainmesh get plan.txt out.txt --version 1an older version comes back whole, not reconstructed from diffs, because it was never turned into a diff. two machines that add the same name while apart arrive at the same history rather than two unrelated files, since the identity is derived from the name and needs no coordination.
archiving a live screenpipe database without a permanent second local copy
Plaintext SQLite and encrypted ciphertext are different bytes, so a hardlink, reflink or APFS clone cannot safely make them occupy one object. Brainmesh uses a proof-gated cache instead: screenpipe keeps its live database, an archive node keeps the encrypted chunks, and the source keeps only the signed manifest after the archive proves every exact chunk is retained.
# once, on the always-on archive node
brainmesh blobs pin all
# on the machine running screenpipe; metadata is the default
brainmesh blobs pin metadata
brainmesh screenpipe archive mini
# disaster-recovery proof into a separate file, never over the live database
brainmesh get screenpipe.sqlite /tmp/restored-screenpipe.sqlite mini
sqlite3 /tmp/restored-screenpipe.sqlite 'pragma integrity_check;'screenpipe archive first requires an authenticated peer whose local policy is
all, then pre-syncs existing signed metadata. It obtains the local API token
in memory, asks the loopback-only authenticated screenpipe backup endpoint for
a transactional VACUUM INTO snapshot, records every run as the next version
of screenpipe.sqlite, uploads only missing encrypted chunks, and reproves the
complete manifest. Only then does it remove loose source ciphertext and the
temporary plaintext snapshot. It never copies db.sqlite plus its live WAL.
Steady state therefore has no second local database-sized copy on the
screenpipe machine. Version one still needs temporary free space for one
consistent plaintext snapshot plus one encrypted cache while the proof runs;
the command checks for the snapshot size plus 512 MiB before encryption. A
failure preserves the exact snapshot for inspection or retry and never evicts
the encrypted cache before remote proof. Packed chunks already shared with
other manifests remain until pack-aware garbage collection exists. Use
--port for a non-default local screenpipe port and --staging for a private
staging volume.
replacing git for a second brain
The durable log is not a filesystem. A workspace is a safe projection of one: full relative paths point at immutable blob versions, while parent event ids make concurrent offline edits become explicit conflicts instead of last-write wins.
brainmesh start # once, for a new local store
brainmesh workspace status brain ~/Documents/brain # read-only preview
brainmesh workspace scan brain ~/Documents/brain # append adds/changes/moves
brainmesh workspace checkpoint brain ~/Documents/brain
# timer-safe; defers live files and never deletes
brainmesh workspace verify brain ~/Documents/brain # hash every current file
brainmesh workspace restore brain /tmp/empty-brain --from mini
# fetch missing blobs once
brainmesh workspace track brain ~/Documents/brain # pin one exact local view
brainmesh workspace pull brain ~/Documents/brain --from mini
# apply remote heads safely
brainmesh workspace watch brain ~/Documents/brain --peer mini
# local edits wake it; poll every 5m as a backstop
# and run a full byte audit at least daily
brainmesh workspace watch-status brain ~/Documents/brain
brainmesh workspace conflicts brain # divergent heads, if any
brainmesh workspace resolve brain <object> <event> # explicitly choose one.git and .brainmesh directories and symlinks are skipped. A root
.brainmeshignore can list literal files or directories, one per line; it
deliberately does not guess at gitignore glob semantics. Obvious credential
containers such as .env, .mcp.json, private-key extensions and credential
files are blocked and must be inspected; --skip-secret-files explicitly
continues without them. Deletions are
only proposed until --allow-deletes is present. Removing ten or more files
and over ten percent of the workspace additionally requires
--allow-mass-delete. An interactive scan is all-or-nothing: a file changed
after preview is named and the scan is refused. A checkpoint instead appends
every stable file, names files that changed during capture, and retries them on
the next run. Tombstones and every prior blob remain in history.
For the personal feedback loop, run checkpoint from a timer. It appends adds,
edits and renames, while reporting deletions and leaving their previous state
active. Files under active writers are also reported and left for the next
checkpoint; one growing log cannot block the rest of a vault. Review deletions
with workspace status, then cross the explicit
scan --allow-deletes boundary yourself. Automation may preserve knowledge; it
does not get permission to erase it.
Bidirectional projection starts only after workspace track proves that the
folder exactly matches the stored view. The per-folder cursor is local and
crash-safe. Without it, an old local copy and a new remote version are
indistinguishable from a deliberate local edit. workspace pull replaces a
path only when its current bytes still match that cursor. If both sides edited
it, the local file is preserved and the conflict is reported. A remote
tombstone also stays pending until the local file is explicitly removed.
Installs use same-filesystem .brainmesh/staging; either rename boundary can be
replayed safely after a crash.
workspace watch performs sync, safe pull, checkpoint, then a second sync when
it appended records. Native filesystem events wake local changes after a
bounded debounce. Exact existing-file hints hash only the named paths; a coarse
platform event first compares bounded path metadata, and a missing path,
possible rename, ignore-rule edit or ambiguous event falls back to the complete
planner. The watcher separately observes encrypted store-log changes, so a
peer-delivered head can apply only changed projection objects immediately. It
never watches its own projection metadata, which would make a self-wake loop.
Large projection cursors are buffered while decoding.
--interval remains the remote-sync and lost-event backstop. If the platform
watcher cannot start, it falls back to that polling interval. Every event is a
hint. Idle backstop polls hash sorted path, size and modification time plus the
store's signed log heads, and --full-every 86400 still forces a content-hash
audit at least daily, including edits that preserve metadata. Watch health uses
two alternating 0600 records and distinguishes the last attempt, last success
and last full audit. Pending deletion, a live file deferred during capture, a
materialization conflict, or a failed peer leaves the cycle unhealthy and
eligible for retry.
This is deliberately the first migration loop, not an automatic cutover: preview, scan, restore into an empty directory, compare, then decide when git stops being the primary copy.
A real local pass on 2026-08-16 exercised 16,244 eligible files and 5.39 GB. Preview took 4.82 seconds. The first checkpoint committed 16,242 stable files in 109.1 seconds and explicitly deferred two files under active writers. Restore took 35.1 seconds; all 16,242 files matched their stored hashes, and an independent checksum pass found byte equality. One add, one update, and one pending deletion over that full vault each took 6.36-6.64 seconds. Two isolated folders then exercised remote add, update, rename, concurrent edit refusal, adoption and a pending tombstone; both ended at 16,242 exact files. An idle watcher full audit took 19.46 seconds and the next metadata-only poll took 3.34 seconds.
On 2026-08-17 the same disposable projection contained the original 16,242
files plus three harmless probes. Before the incremental path, one native edit
took about 235 seconds to checkpoint because it hashed every file, then the Mac
mini waited for its 300-second backstop; end to end was about 487 seconds. After
exact-path checkpointing, encrypted-log wakeups and buffered cursor reads, a
new MacBook-to-mini file materialized in about nine seconds by filesystem
timestamps and the reverse mini-to-MacBook probe completed in 8.3 seconds from
the pre-copy timestamp. Both watcher records said quick, not full, and both
folders then verified 16,245 matching files with zero missing, changed, extra
or conflicted entries.
The same encrypted store was then restored onto a physical Mac mini through a
relayed tailnet path. One resumed command fetched 18,011 chunks needed by
14,405 files and restored all 16,242 represented files in 2,951.64 seconds.
Both internal verification and an independent checksum comparison found zero
file-content differences. Timestamps differed, and three empty local
directories were absent because workspaces currently represent files rather
than empty directories. A real add and update crossed the machines; concurrent
offline edits surfaced as the same two heads on both, and an explicit merge
event selected one without last-writer-wins. A tombstone was refused by the
unattended watcher, refused again without --allow-deletes, and remained
pending on the laptop until its local copy was moved aside. Both machines
finished with 16,242 matching files, zero missing, changed, extra or conflicted
files. This is physical cross-device and interruption evidence, not yet a
long-run soak or complete disaster-recovery claim.
The full store then passed an identity-loss drill. Its original Mac mini
identity was moved completely outside the store, a 216-byte recovery kit and
separately held 24-word phrase produced a new device author, and the recovered
owner authority exactly matched the original. Without copying or rebuilding
the ciphertext, workspace verify again reported all 16,242 files matching.
The recovered device signed two new records, pushed them to the laptop, and the
laptop decrypted the ordinary note. This proves loss of every local identity
file while encrypted data survives.
A separate full ciphertext-loss drill then started with an empty destination, restored the fresh identity from its kit and imported an authorized encrypted bundle. It rebuilt 16,264 signed records and 20,116 encrypted chunks (5,291,099,261 bytes), restored all 16,242 files with zero hash differences, signed and decrypted a new record under the recovered identity, rejected the used kit as stale and verified its successor. Repeating both loss drills over time remains part of the dogfood gate. The built-in non-destructive drill later repeated the complete reconstruction from newly generated artifacts in 32.07 seconds: exact heads for the same 16,264 records, all 20,116 ciphertext chunks, 16,027 historical manifests and 5,290,294,621 authenticated plaintext bytes; the live store and healthy watcher remained unchanged.
agents
an ai client talks to a brain through one grant, and cannot exceed it.
brainmesh ships a reusable operating skill for Claude, Codex, Hermes, Pi and
screenpipe under skills/brainmesh. Install it into every agent root on the
current device with:
bash skills/brainmesh/scripts/install-agent-skill.shOn Windows, run
powershell -ExecutionPolicy Bypass -File skills/brainmesh/scripts/install-agent-skill.ps1.
The skill teaches the agents how to discover stores, verify sync, use narrow
grants and avoid destructive recovery mistakes. Installing it grants no data
access; each agent still needs an explicit brainmesh grant.
brainmesh grant $(brainmesh id | cut -d: -f2) \
--kinds note,file --sensitivity personal --days 365 --write
brainmesh grants # copy the id
brainmesh mcp --as <grant id> # json-rpc on stdin and stdoutpoint claude desktop at it by adding this to
~/Library/Application Support/Claude/claude_desktop_config.json:
{ "mcpServers": { "brainmesh": {
"command": "/Users/you/.local/bin/brainmesh",
"args": ["mcp", "--as", "<grant id>"] } } }eight tools: overview, request_access, access_status, search, recent,
record, remember, files. The standing grant remains the ordinary
permission model. request_access does not widen it: it queues an owner-visible
request for one exact protected search, recent, or files call.
- reads return only what the scope covers, and tell the model how many records it was not allowed to see, so it does not answer as though it read everything
- writes are checked against the same scope, so an agent holding a slice for work notes cannot file a medical one
- every read appends a receipt naming the grant, never the query text; every grant-authorized write records the exact effective grant and its declared grantee alongside the new record
- a receipt that overlaps a same-device CLI write waits through a bounded writer-lock retry; sustained contention returns an error rather than dropping the receipt or racing the author sequence
brainmesh auditlists both grant reads and grant-authorized writes without trusting stored identifiers to be long, printable or well formedbrainmesh revoke <id>resolves and revokes the signed grant blob rather than trusting its descriptive JSON id, then cuts it off mid conversation- search returns bounded one-line excerpts under a whole-response budget;
recordreopens one exact result by its source-backed ref and checks the grant and revocation again, so models do not ingest every full note up front - when more matches exist, search and recent return a filter-bound
next_cursor; pass it back with the same semantic filters to walk older records without repeating the previous page. Relative date windows are frozen at page one, page size may change, and every page rechecks the grant, expiry and revocation because a cursor is never an access capability
one-time access from an AI chat
When a standing grant withholds something needed for the current question, the
AI can explain why and call request_access with the target tool and its exact
arguments. The request is not authority. It expires after 15 minutes and is
stored only in the endpoint's private state. access_status lets the same chat
observe pending, approved, denied, consumed, or expired state without learning
the owner credential.
The installed PWA has a separate owner approval credential. It renders the
AI-provided reason, exact query/date/tag/kind boundary and maximum sensitivity.
"Allow one read" makes the trusted node root-sign a read-only grant to the same
agent subject for at most two minutes. That grant is bound to the displayed
tool arguments and is atomically consumed before the query executes. Changed
arguments do not consume it; concurrent or repeated exact calls cannot use it
twice. Approval does not authorize record, writes, later searches, another
agent, or a wider sensitivity.
Web Push is only the wake-up channel. Its encrypted payload contains an opaque
request id, not the reason, query, scope, token, or memory. The phone fetches
the request from the trusted node after the owner opens the PWA. Push
subscriptions, request events, the VAPID private key and approval.token are
bounded private endpoint files and mode 0600 on Unix. The relay transports
TLS and push services receive encrypted Web Push bytes; the hosted model still
sees plaintext returned by the one approved read.
On iOS/iPadOS, first open /mobile/, use Share → Add to Home Screen, launch the
installed app, open Requests, paste the separate owner approval code, and
tap Enable permission notifications. Notification permission must be
requested from that owner tap. Android and desktop installed browsers use the
same flow. A browser stores this owner code in origin-scoped app storage, so
this is still a bearer boundary; the native clients should hardware-bind this
credential before claiming Keychain/Keystore-grade approval security.
A long-lived MCP/HTTP node keeps a bounded process-local decrypted snapshot so repeated agent searches do not reopen the whole log. It is never written to disk, is capped at 64 MiB/250,000 readable records, zeroizes plaintext on drop, and is reused only while exact verified heads and the complete runtime-key view match. External writes, sync, revocation or key changes invalidate it; exact local receipts and captures advance it, and oversized brains use the ordinary streaming scan. This cache lives on the trusted node, not on the phone.
A hierarchy can be granted without exposing its sibling:
brainmesh grant <agent-device> --workspace brain --paths company --days 30Workspace records carry ancestor tags, so that grant covers company/ and
everything below it but not personal/. The signed permission format remains
the same, and the check still happens at the node holding the encryption key.
A phone or hosted model does not decrypt the bucket. It becomes a client of a machine that already holds the key. That trusted node opens records, applies the grant, returns only the permitted plaintext, and appends a read receipt:
encrypted bucket/peers -> your trusted node -> grant filter -> Claude/ChatGPTThat is the mobile architecture today: no brain key or encrypted store is uploaded to an AI vendor. A native phone client can hold only an OAuth token bound to one grant. Claude and ChatGPT are different: their hosted connector holds that grant-bound token and necessarily sees the permitted plaintext your node returns. It still cannot decrypt anything else. Use a narrow, expiring grant and treat the returned slice as disclosed to that model provider.
For a hosted connector, public DNS points at a relay, but the certificate and TLS private key remain on the trusted brain node. Create one no-clobber 0600 route capability, copy it to that node, and allowlist the same file on the relay:
# public relay; brain.example resolves here
brainmesh tunnel route brain.example.route brain.example
brainmesh tunnel listen 0.0.0.0:443 0.0.0.0:7780 \
--route brain.example.route
# trusted node: a local Caddy/nginx on 127.0.0.1:8443 holds the public
# certificate and proxies decrypted HTTP only to 127.0.0.1:7777
brainmesh serve-http --as <grant-id> --bind 127.0.0.1:7777 \
--url https://brain.example
brainmesh tunnel expose relay.example:7780 brain.example.route \
--to 127.0.0.1:8443 --pool 4Use a local TLS terminator configured with a public certificate for the route; DNS-based certificate issuance works without giving the relay that key. The relay reads the visible ClientHello SNI only, then forwards the original TLS records to one waiting outbound connection. Registration proves the route-file token with a fresh challenge, so the token itself never crosses the control socket. Stealing the route can at most hijack availability: without the TLS private key, a replacement endpoint cannot complete a valid handshake.
The relay still sees the hostname, client and node IPs, timing, connections and
byte counts. It can drop traffic, and a route with no waiting pool closes. The
current core is deliberately HTTP-version agnostic because it carries TLS
bytes. A real TLS 1.3 certificate/hostname check, authenticated MCP search,
unauthenticated 401, exact granted result and durable read receipt all passed
through the relay. A public domain, public-CA certificate and real
Claude/ChatGPT connector pass are still deployment work.
brainmesh serve-http --as <grant id> --url https://your-node.ts.net
tailscale funnel 127.0.0.1:7777 # tls terminates on YOUR machineOpen https://your-node.ts.net/mobile/ for the first usable handset client.
It can search, show recent records and an overview, and append a scoped note.
Its Requests inbox can also receive privacy-minimized Web Push notifications
and approve or deny one exact hosted-agent read.
Pairing is OAuth authorization-code flow with S256 PKCE; access tokens last one
hour, refresh tokens rotate, and Disconnect durably revokes both tokens. The
app shell can remain installed while the node is offline and preserves its
revocable credential for retry, but it never caches memory content and cannot
pretend an offline query succeeded. This web step stores the refresh token in
browser storage and therefore remains an ordinary bearer client. The iOS shell
in mobile/ios keeps credentials in device-only Keychain storage, holds a
non-exportable Secure Enclave P-256 key, and sender-constrains access and
refresh credentials with RFC 9449 DPoP. It adds a text/URL share target and
passes its Keychain, DPoP, hostile-callback and pre-network capture-limit tests
in the iOS simulator. A native integration test also completes dynamic
registration, S256 pairing, scoped search, capture, read-back and durable
logout against a live node. Physical-device signing and an on-phone
share-sheet/Secure Enclave pass remain. The Android shell in mobile/android
uses separate non-exportable Android Keystore AES-GCM and P-256 signing keys:
the first protects its no-backup credential envelope and the second
sender-constrains every native token and protected request with DPoP. Its build
and lint pass; prior API-35 emulator coverage exercised pairing, search,
capture, read-back and logout, while the new hardware-key DPoP test still needs
an attached emulator or phone. Physical Android hardware remains untested.
None of these designs puts a brain data key on the phone.
Mobile privacy also fails closed around OS snapshots and backups. The iOS app replaces its content with an opaque lock shield whenever its scene is inactive or backgrounded; it has no memory database or file cache, and its credential and Secure Enclave representation are non-synchronizing this-device-only Keychain items. Android disables application backup, cloud extraction and device transfer across every data domain, keeps its encrypted envelope in no-backup storage, and marks both main and share windows secure so Android refuses screenshots, screen recording and recent-task thumbnails. These are platform controls, not a claim that a compromised OS cannot observe plaintext.
clients that will not take a header run the oauth flow instead: registration,
an approval page, pkce with S256 required rather than optional, single use
codes, exact resource binding, one-hour access tokens and rotating refresh
tokens. browser Origins are refused unless explicitly allowed with
--allow-origins. approving means pasting the pairing token, which is a file
only you can read. there is one user here and inventing an account system for
them would be inventing something to lose.
Authentication state is namespaced under the signed grant id. The static
pairing token remains a raw private 256-bit file because the owner must be able
to display and paste it. serve-http displays that value only on an interactive
terminal; redirected or background output names the private file without
copying the credential into logs. OAuth access and refresh values are different:
the node persists only domain-separated BLAKE3 fingerprints, never the raw
credentials returned to the client. Existing plaintext logs migrate to the
fingerprinted format under one exclusive server lease and are atomically
replaced without signing existing clients out. The logs are bounded, durable,
symlink-refusing and mode 0600 on Unix; an incomplete final record is removed
while complete corruption stops startup. An endpoint restarted under a
different grant gets different credentials, so an old phone token can never
silently widen with the server configuration. Web and hosted clients remain
bearer clients. The first-party iOS and Android clients instead bind the
authorization code, rotating refresh credentials, access credentials and every
protected request to one non-exportable P-256 key with RFC 9449 DPoP. Copying a
native token without that key is insufficient; expiry, durable logout and grant
revocation remain the administrative controls.
The trusted-node HTTP listener admits at most 64 active sockets before it
creates request threads. Every admitted socket has a 30-second read/write
deadline; overflow gets a best-effort 503 and is closed without blocking the
accept loop. The separate organization inbox uses 32 slots. These are local
resource-exhaustion bounds, not edge DDoS protection; a public deployment still
needs connection and request limits at the node-owned TLS terminator.
Before authentication, OAuth mutation work has separate 60-second budgets per
observed TCP peer: 60 dynamic registrations, 30 pairing approvals and 120 code
or refresh exchanges. The endpoint keeps at most 4,096 source/traffic buckets,
returns 429 plus Retry-After before JSON, pairing-state or DPoP work, and
does not trust a caller-supplied forwarding header. Health, discovery, the
approval page and logout remain available. If a node-owned TLS proxy connects
over loopback, every proxied client intentionally shares that loopback budget;
the proxy must enforce real-source limits before forwarding. These counters
also reset on process restart and are not distributed DDoS protection.
After authentication, protected MCP and capture work is additionally limited
to 120 cost units per credential identity and 600 per signed grant in each
60-second window. A DPoP client's identity is its stable public-key thumbprint,
so rotating its access token cannot reset the budget; bearer and pairing
credentials are represented only by domain-separated fingerprints. Every
JSON-RPC batch member costs one unit, and an oversized batch is rejected with
429 plus Retry-After before any member runs. Logout is exempt so a user can
always disconnect. The bounded table and counters are process memory and reset
on restart, so this is a fair-use and accidental-loop guard, not a substitute
for node-owned edge protection against distributed abuse. Give independently
budgeted agents separate grants and endpoints.
the phone holds nothing, so losing it is not losing a key. the token names a
grant and nothing more, and brainmesh revoke ends it.
over a tailnet you need no certificate at all: wireguard already encrypts and
authenticates it, so --bind <tailnet ip> is reachable from your own phone and
involves nobody. the caveat is who dials: claude and chatgpt call remote
connectors from their servers rather than from the handset, so those two
need public reach. anything connecting from the device itself works privately
today.
do not put anything in front of this that terminates tls away from your node. the whole claim is that nobody but you can read it. Tailscale can terminate on your own machine; the public brainmesh tunnel passes TLS through to your own local terminator. A provider-managed decrypting proxy withdraws the claim.
capture from a phone needs no app either. one iOS Shortcut in the share sheet,
pointed at /capture, files whatever you share:
curl -X POST https://your-node/capture -H "Authorization: Bearer <token>" \
-d 'a thought worth keeping'the grant decides, exactly as it does everywhere else: a read only grant is refused rather than quietly obliged.
want to see exactly what an agent sees, before you hand it over:
brainmesh ask <grant id> "pricing" # the same path, on your terminal
brainmesh audit # what a grant has read or writtenany cloud, as a peer
a bucket cannot host the live log, because there are no locks and no appends and a log needs both. but it is a near perfect home for what the log produces: segments and blobs are immutable, content addressed ciphertext, which is the one thing object storage is unambiguously good at.
so a bucket is a peer that never talks back. push what it lacks, pull what you lack. two machines that are never awake at the same time converge through it.
brainmesh remote https://<account>.r2.cloudflarestorage.com auto mybucket brain/ <key> <secret>
brainmesh push
brainmesh pullaws, r2, b2, minio, wasabi, garage: one dialect, any cloud. tls is curl's job, the same bargain already made with ssh.
it holds noise. records are sealed with the brain key and signed, so the provider stores bytes it cannot read and cannot forge. and nothing pulled from a bucket is trusted: segments are decoded and applied through the same path a peer's records take, so a bucket serving something rewritten is refused. a store that trusts its backup is a store with no signatures.
Bucket keys have a closed grammar and are never joined as provider-controlled filesystem paths. Loose chunks must match the ciphertext hash in their key; packs must match their whole-object hash, canonical table and every member hash; and an entire log object must decode and verify before any record is applied. Listing counts, response sizes and each object class are bounded. Local repair markers and forensic packs are never uploaded. On repeated pull, held chunks and exact local packs are skipped before a GET, so a large archive does not repeatedly pay to download immutable bytes it already has. A real local HTTP/curl drill confirms that the second pull performs LIST but no blob GET.
Packs are file-backed end to end. Upload hashes BLAKE3 and SHA-256 in one
bounded-buffer pass, then curl --upload-file reads the file directly. A pack
download uses one private same-filesystem part and lock per content hash. Curl
continues an interrupted prefix with HTTP Range, including on the next pull;
an endpoint without Range support safely restarts that object from zero. The
completed file is flushed, whole-pack and member hashes are validated from
disk, and only then is it renamed live. Oversized or malformed completed parts
are deleted; bounded interrupted ciphertext is retained only for resume. A
128 MiB pack is never a 128 MiB Rust allocation.
it does not depend on tailscale
nothing in the crate knows what a network is. sync shells out to ssh, and
tailscale only ever enters at name resolution. three ways round it:
several addresses per peer, tried in order. the same machine found over a tailnet from a cafe and over the lan at home, without choosing.
brainmesh peer add "mini.tailnet.ts.net,mini.local,192.168.1.50"any command that carries stdin and stdout. an address starting with ! is
run instead of ssh, so a jump host, kubectl exec, adb shell or a serial
link all work unchanged.
brainmesh peer add '!kubectl exec -i pod -- brainmesh serve'no network at all.
brainmesh bundle /Volumes/stick/brain.bundle
brainmesh import /Volumes/stick/brain.bundlethe bundle is self-contained: it carries signed history and all encrypted file chunks held by this machine. every record still has to verify and fit its author chain, and every chunk must match its ciphertext hash, so a stick someone hands you can add valid history but never rewrite it or smuggle bytes under a false identity. importing twice transfers nothing the second time. version-one record-only bundles remain readable. On an enrolled key-holding device, the entire record section is quarantined and checked against every included root-signed epoch transition before any author log changes.
The bundle alone is data transport, not key recovery. The importing device must already be enrolled, receive an invite wrapped to it, or restore keys from a separate recovery kit and phrase. The bundle never contains a brain key.
recovery without a back door
brainmesh recovery create /Volumes/stick/brain.keys
# write the 24 words it shows once on paper; do not put them beside the kit
brainmesh recovery verify /Volumes/stick/brain.keys
# retype the paper phrase; this decrypts in memory and writes no keys
brainmesh recovery status /Volumes/stick/brain.keys
# fails if a data-key epoch, space, role generation, or organization key changed
# for a headless drill, keep the phrase out of stdout and shell history:
brainmesh recovery create brain.keys --phrase-output /Volumes/other/brain.phrase
brainmesh recovery verify brain.keys --phrase-file /Volumes/other/brain.phrase
brainmesh bundle /Volumes/stick/personal.bundle
brainmesh bundle /Volumes/stick/shared.bundle --space shared
brainmesh recovery drill brain.keys /Volumes/stick/personal.bundle \
--space-bundle shared /Volumes/stick/shared.bundle \
--phrase-file /Volumes/other/brain.phrase
# after a forced termination or power loss, or before inspecting leftovers:
brainmesh recovery cleanup
# after losing every device, on a fresh machine:
brainmesh recovery restore /Volumes/stick/brain.keys
brainmesh import /Volumes/stick/brain.bundle
brainmesh witness verify /Volumes/stick/brain.headsThe kit contains every held personal data-key epoch and its signed transition chain, every separately encrypted space held by this device, and organization authority material it holds. It contains no records, blobs, device signing key or device agreement key. Restore therefore creates a fresh device author rather
