alexandria.

what it does, and what it sends

Setting up your system puts a folder of plain files on your computer, ~/alexandria, and adds a few small programs, called hooks, to the coding tools you already use. After each conversation, the hooks save a copy of it in that folder, with anything shaped like a password or key blanked out, so your model can learn from what you said before. That copy stays on your computer. Nothing leaves it unless you turn on a connection yourself, each with its own yes, and you can turn each one off again.

You don’t have to trust us for any of this. We can’t receive your private files, because our server has no address that accepts them and nothing installed on your computer sends them. We can’t change your computer after setup, because nothing there updates itself, and a new version runs only when you say yes and only if it carries the maintainer’s signature, made with a key nobody else holds. And we can’t hide anything, because anyone with an invite can read every line of the code, and your own model reads it before running any of it.

One command takes it all back out of your tools and leaves your files where they are. The rest of this page is the detail your model checks against, and each question below goes straight to its answer.

find an answer

what setup puts on your computer

Your system is a method your own model runs, not an agent or a service. Setup puts it in two folders and adds two skills. Your model can write the first folder and not the second, so nothing it is tricked into writing can replace the next hook or the mark that proves it was checked, grant a connection that carries your data, or make a hook run code or git settings from your folder. What runs is plain bash, Python, and Node where it is installed. Setup is one bash script, and so are the hook program and the small starter the session hooks call, shim.sh. Everything else is Markdown.

Together they make one loop on your computer. Ordinary sessions use your files and keep what is worth keeping, the first reply of each new task offers an active session, /a works through what built up, and the close, a., keeps what changed, while local capture and Git keep the history. Only your model’s finished reply counts as showing that offer, never a hook message or a hidden instruction, and touch ~/alexandria/system/hooks/visible-cue.off turns off the whole automatic return path.

your folder, ~/alexandria

~/alexandria/ holds plain Markdown and small JSON files, all readable, kept as a local Git repository (~/alexandria/.git/), so every change you keep is a commit you can see and undo. The repository is yours to push to any Git host; GitHub is the default only if you have signed in with gh auth login.

  • AGENTS.md is your guide, which every tool reads first. It holds your own rules for how any model works with you, where everything else you keep lives, and one marked block that setup keeps current.
  • files/constitution/ holds your beliefs, personality, and working style. You write these.
  • files/vault/ holds raw input, such as notes, voice memos, and your session transcripts, which the hooks keep in vault/transcripts/ with a readable copy of each conversation in vault/sessions/. Every capture you save is in vault/captures/, and new ones land in vault/captures/new/, which stays on this computer unless it is linked to the capture folder in your own iCloud Drive.
  • files/marginalia/ holds thoughts still forming, between raw input and settled belief, both yours and the ones your model proposes, and empties over time as they settle.
  • files/works/ holds what you made, each piece you finish (an essay, a letter, a post) and each draft you mean to come back to, plus the records of your protected positions and of how your thinking shifted.
  • files/core/ is your model’s working memory (machine.md, notepad.md, feedback.md, and checklist.md), plus life.md, facts about your life that you tell your model and that have no other home you named, with health, money, and identity details only where you said they go.
  • files/library/ holds what you might publish, one folder per way in (public, member, paid, invite, and market), and filter.md, your publishing rule, drawn from your own files, which your model checks before a draft becomes final. Once you set up your Mirror, your model keeps a draft of each folder here on its own, and the alexandria skill shows you what the draft would publish, as one list for your yes. A source file may stay anywhere in your own folders, with a link to it under the exact folder it is for. You can add exact groups inside a folder when you need them (invite/friends, paid/course), and a folder never inherits from its parent or its neighbours. A piece can need more than one way in at once: the gated folders nest in the order member, invite, paid (member/paid/course is a group members can buy, invite/paid holds pieces only people you invited can buy), a reader must pass every one, and a layer’s name is never a group’s. Each paid or invite piece straight in its folder opens alone, to whoever bought it or was invited to it, and a group opens as one. A file leaves only when Library sync is separately granted and your protected approval list names its exact scope, name, and the SHA-256 of its current bytes. Setup or the next session start moves an older authors/ or members/ folder, the member folder’s names before 26 September and 9 October 2026, into member/, only real folders and plain files and never over a file of the same name, and approvals made under an old name still count.
  • files/library/market/ is the one folder no reader can reach, and setup or the next session start creates it empty. What you approve there is read only by you and through Alexandria’s matching credential, which Alexandria’s operator can use, and every matching read shows in your access log (what our server holds). The market for answers, which is on, also reads it, only if you chose to be asked or answered for, and only for a buyer switched on. It was called alexandria/ until 9 October 2026: setup or the next session start moves an older alexandria/ folder’s files into market/, only real folders and plain files, never over a file of the same name, which stays where it is and is named each session until you settle it, and approvals made under the old name still count.
  • system/canon/ holds the method files your model follows, signed copies cached once and never rewritten automatically afterwards. Two come by default, foundation.md and change-closure.md, and with the hooks, each session, and the Git history, they make up the whole local loop. Eight are recommended (axioms.md, methodology.md, editor.md, mercury.md, publisher.md, the Mirror’s filter.md, host-memory.md, and root-stewardship.md), and moving one into system/canon/disabled/ turns it off, which setup and update notices respect. Personal modules and the Connector’s files (the required tier, which only joining needs) are not in the base install. Connecting brings the connector and Library files, asking brings the others, and each stays dormant until you approve it exactly. MODULES.md maps them all. A file being here does not switch on its feature, and a signed update is only a notice you pull or ignore.
  • system/modules.json is the signed map of the four tiers, required (the Connector, which only joining needs), default, recommended, and personal, with everything past the loop marked local or outward, and each outward one needs its own yes to its exact scope. Your model reads it on your computer, with no account or server read. What is actually on is decided by your local files, your protected grants, and their switches, never by this map.
  • system/skills/ holds your own skills, one Markdown file each, which your model saves when you build something you’d reuse. A skill runs nothing and grants nothing by being there, and any command, network call, or write in it keeps its normal gate. Sharing one is a separate yes to its exact bytes, which the connector then publishes on your Library profile and in the marketplace under your name, and the copy you approved stays in modules/shared/ (factory/skills/publish.md).
  • system/.api_key, once you connect, is the read key (mode 0600), which the server accepts only for looking people up. The full key is in the protected folder below. A computer connected before the key split keeps its one full key here until its next verified setup run moves it.
  • system/.protocol_status.json, once you connect, holds exactly two values, whether your membership is active and the name your invite link carries, which /a reads to open with your invite link. Only the session-start hook writes it, from the answer it keeps in ~/.local/share/alexandria/.membership and refreshes at most once a day, so an edit here lasts until the next session start and grants nothing. Without a key, and at uninstall, it goes.
  • system/.block holds the one-time setup instructions, cached on your computer, and system/.optional is the menu of add-ons, saying what each one does, what it touches, and how to turn it off.
  • The other system/.* files are short-lived state (session markers, sync logs, the error log, and when upkeep last ran), all readable, and none of them leave your computer.

the protected folder, ~/.local/share/alexandria

~/.local/share/alexandria/ is the part your model cannot change, because supported coding tools do not add it to the folders your model may write. It holds only the signed programs the hooks run, the signed wording they show your model, the checks that prove both are genuine, the uninstaller, and permissions/, the grants and exact Library approvals you recorded with scripts/permission.sh, a step that works only outside your model’s sandbox. Once you connect, it also holds the full account key (.api_key, mode 0600) and the last membership answer (.membership), and, when messages are on, the inbox keys and the sealed messages session start collected, still unopened. While the Drive bridge is on, it keeps here the copy of your Drive folder it compares against (drive_shadow/), so nothing written in ~/alexandria can aim its sync at another folder. While whereabouts is on, it keeps here the small app it builds from signed source so macOS can tell it which city your Mac is in (whereabouts/), the last reading it applied, and whether a city is showing (whereabouts.json). Nothing else of yours is kept here, and no transcripts. bash ~/.local/share/alexandria/scripts/permission.sh status shows which connections are on.

  • hooks/shim.sh is the small starter the session hooks call. It runs the pinned, verified hook program, and checks for signed updates only if you separately turn that on.
  • .hooks_payload is the pinned hook program. It runs only after its signature checks against the keys in allowed_signers.
  • .payload_verified_sha records the hash of the verified hook program, which is the pin. If the program changes without a new check, the starter refuses to run it.
  • .canon_manifest is the newest signed manifest this computer verified, which update checks move forward, while .installed_manifest keeps the one the installed release came with. Every method file is checked against a verified manifest before it is written, so a compromised GitHub repository cannot push poisoned method files either.
  • .factory_version is the highest signed release this computer has accepted. An older valid manifest is refused rather than replayed as a downgrade.
  • .owned_integrations holds exact path-and-hash receipts for the skills, rules, and Cursor hook files setup created, and for the scheduled jobs of the Drive bridge, the public watch, and whereabouts. Re-runs and the uninstaller rely on this receipt instead of trusting a file name or a copied sentence. When a re-run installs the close skill in a better place than an earlier run used, it removes the earlier copy only while its bytes still match the receipt, so a copy you edited stays.
  • allowed_signers holds the three public keys that may sign a release (who can sign a release), against which every hook program and manifest is checked.
  • scripts/verify-fetch.sh is the only way an update gets in later. It checks the signed manifest, refuses an older release, checks the requested file’s hash, and only then hands over or runs those exact bytes.
  • scripts/ also holds the signed helpers capture_resolver.py, capture_state.py, host_memory.py, change_closure.py, and statusline.sh, and the uninstaller, uninstall.py, all kept outside your folder. capture_state.py only reads. It gives the status line a current count and lets /a freeze and prove the exact batch of captures that existed when the session began.
  • host_memory.py, which /a runs, only reads other tools’ own memory on this computer (folders named memory, memories, or memory-bank where tools keep their settings, such as Claude Code’s and Codex’s), and only for a tool you said yes to. It lists what is new since the last time, so /a can draw it into your files. It makes no network call itself, though the lines it hands /a are read by the model running that session, so that model’s provider sees them. It never writes to a tool, prints anything shaped like a key as [REDACTED] (by pattern, so a secret with no recognisable shape can still show), and records only paths, hashes, and your yes or no for each tool in system/.host_memory_taken.json. Asking a tool whose memory lives in its account happens only when you start the ask. Moving system/canon/host-memory.md into disabled/, or touch ~/alexandria/system/hooks/host-memory.off, stops it.
  • change_closure.py, which only your model runs, reads the files your upkeep receipts name to list every output whose source changed since it was last checked. It writes nothing but those checks’ stamps in system/change-closure/ and makes no network call.

two skills

~/.claude/skills/a/SKILL.md is the /a skill, which opens a session. An /alexandria copy that an earlier release installed is kept current, and no new one is made. ~/.claude/skills/a./SKILL.md is the /a. skill, which closes one, saving everything to your files and asking what you now think that you didn’t before, so you say it in your words and it files them. Cursor gets the same under ~/.cursor/skills/, and Codex under ~/.agents/skills/. They are plain Markdown you can read.

If a skill named a or a. already belongs to something else, setup leaves it alone. Where the name a. is taken, or on Windows, where a folder name cannot end in a dot, the close skill goes in an alexandria-close or close-alexandria folder instead, and the loop still counts as complete. Where the name a is taken, setup reports the loop incomplete, and a first install keeps every hook off while an update puts the previous version back, because /a would otherwise open the wrong skill.

backup and commit signing

Setup reads no SSH key and writes no signing setting, and your folder’s first commit is unsigned. Signing is its own yes inside the backup add-on, which is also the only step that creates the private alexandria-private repository on your own GitHub account and pushes to it. It signs commits in ~/alexandria/ with the one SSH public key you pick, in that repository’s own settings (your global git settings and other repositories are untouched), and uploads only that public key to your GitHub, so commits show as verified there. The add-on does this through your own gh sign-in on your computer, never through Alexandria’s server, and the private key never leaves your computer.

Installs from earlier releases, which signed with the first key setup found and added it to ~/.config/git/allowed_signers, keep that setting, and the uninstaller removes the one line setup recorded adding there. If a key cannot sign when a session hook commits, the hook commits unsigned rather than stop the backup. With signing on, GitHub shows a green Verified badge on each signed commit, and on your computer git -C ~/alexandria log --show-signature and git -C ~/alexandria verify-commit HEAD check them too, once the key is listed in the file that gpg.ssh.allowedSignersFile names.

what it changes in your tools

One verified setup wires every tool on your computer, with nothing to install for each one, no plugin, and no marketplace. It adds the entries below to your tools’ settings, keeps everything else in them as it was, and the uninstaller takes those entries back out while keeping your files.

Claude Code

~/.claude/settings.json gets five hook entries, two at session start (the starter, and the capture resolver that turns links you saved into readable captures, in every network call, which hands anything that waits on the network to one background run of itself so session start never waits for it), one at session end, one when a helper agent starts, and one with each message you send. They run the starter at the start and end of each session. The one with each message shows your model the few rules every reply follows, the footer and the lines under ## Every reply in your own ~/alexandria/AGENTS.md, beside the message it is answering; it discards what Claude Code sends it, your message included, without reading it, and nothing leaves your computer. Creating ~/alexandria/system/hooks/every-reply.off turns it off. Claude Desktop’s Code tab is Claude Code running on your computer and reads the same file, so the same entries cover it with nothing extra to install. Its normal chat tab cannot run these local hooks, though it can still use a folder you approved and load its _start guidance on request.

Setup also adds ~/alexandria to permissions.additionalDirectories, so ordinary sessions in any project can read and write your folder without a prompt. That includes a session misled by a hostile file in some other project, which could write into the record that later sessions read as yours. The record is plain files under Git, so any such write shows in its history and can be undone, and once you connect, the only key such a session could read there is the read key. Setup adds one permissions.deny rule, Read(~/.local/share/alexandria/.api_key), which keeps Claude Code’s file tools and sandbox from reading the full account key, so the acts that need it run only through a command you approve outside the sandbox. It sets the status line to Alexandria’s cue only when you have no status line of your own, and system/hooks/visible-cue.off removes it.

Claude Code skips a whole settings file that holds any value it rejects, hooks included. So setup then starts the installed claude once, in a throwaway folder, on a copy of this file whose hook commands only write a marker and whose env values are blank, with no login, its prompt blocked, and its network pointed at a closed local port, so nothing leaves your computer. A sign-in helper the file names (such as apiKeyHelper) runs once, as on any Claude Code start. If that run fires a hook but the marker never appears, setup names the file and says Claude Code is skipping it instead of reporting Claude Code healthy, and if Claude Code cannot be started, the check records that it does not know. The check never changes your file.

Cursor

Only if Cursor is on your computer.

  • ~/.cursor/hooks.json gets four hook entries pointing at the small Python files below, for session start, session end, and each prompt and response. An earlier release’s stop hook, for a retired self-check, is removed.
  • ~/.cursor/hooks/alexandria-{session-start,session-end,transcript}.py are three small Python files that call the same starter or write the local transcript. The session-start one also starts the signed capture resolver in the background, as Claude Code and Codex run it as a second session-start hook, so what you saved from your phone is readable when you open the alexandria skill. The session-end copy is Cursor’s own full record of the chat, dictated prompts included, and the transcript the prompt and response hooks write on the side is the fallback for Cursor versions that keep no full record.
  • ~/.cursor/rules/alexandria.mdc is a plain Markdown rule. If you delete it, setup records that in ~/.local/share/alexandria/.declined_integrations and leaves it out from then on, and removing that line brings it back.
  • ~/.alexandria/ is a side folder Cursor’s hooks use instead of your project folder, for transcript staging, an optional addition to session start that is read into context (~/.alexandria/inject/session-start.md), and logs of what each session start added and whether each transcript reached the vault.

Codex

Only if Codex is on your computer.

  • ~/.codex/hooks.json keeps any hooks it already has and adds entries for session start, session end (within Codex’s three-second limit), a helper agent starting, each message you send (the same rules for every reply as in Claude Code, your message never read), and the capture resolver. Session end saves the transcript and a receipt inside that limit, and the next session start finishes the git work and copies any transcript too long to save in two seconds. Codex makes you trust each new or changed entry in /hooks before it runs, and setup stays visibly unfinished until trusted hooks have actually run at the start and end of a session.
  • ~/.codex/config.toml gets ~/alexandria in [sandbox_workspace_write] writable_roots, so Codex can write your folder, and [features] hooks = true, so the hooks can run. Everything else in the file stays.
  • ~/.codex/AGENTS.md keeps its instructions and gains one small marked block, unless the full instructions are already there, in which case nothing is written. The older instructions.md is never touched.
  • ~/.agents/skills/a/ and a./ hold the start and close skills. A skill already there under either name is kept. Where the name a. is taken, the close skill goes in alexandria-close or close-alexandria, and because everything starts at /a, a taken name a leaves setup visibly unfinished rather than pointing at the other skill.

So Claude Code, Cursor, and Codex each run the whole loop over one signed hook program and your one folder, Codex once you have trusted its hooks.

Grok Bot, Cowork, and chat apps

Grok Bot is not a Mac install. Neither its public documents nor this repository confirm a folder on your computer from which it loads skills, so setup does not invent one. You save factory/skills/grok-bot.md in Grok Bot’s own workflow library as /a and /alexandria, and factory/skills/aclose.md as the close skill (a.), and it can run /a whenever its connection to your computer reaches ~/alexandria.

Setup installs no Cowork plugin and no chat-app extension. Connecting a chat app to your account is a server connection (chat apps), and it reaches none of your files. A chat app or a closed surface like Cowork uses the files themselves, in two steps, and neither routes your files through a server.

  1. After the loop works on your computer, your model offers the short block in ~/alexandria/system/.account-instructions.md for your chat app’s saved instructions, below whatever is already there. It is recorded as loaded only when the saved field reads back and a brand-new chat summarises it accurately. That proves the app loads it, not that it can reach your files, and an app where it can’t be checked stays marked unverified without holding up the loop.
  2. Give the app the exact folder that holds your system, ~/alexandria. Ordinary work then uses it, and deliberate work uses the app’s own alexandria skill or the plain request start the alexandria skill. New access, sharing, publishing, and deleting keep their own approvals.

what it leaves alone

Setup does not touch your shell startup files (.zshrc, .bashrc, .profile), the system PATH, sudoers, system services, launchd, cron, or cloud storage. The one exception is a removal: an update stops and removes the parked daily texts job (io.alexandria.nudge) only where its own path-and-hash receipt proves an earlier release scheduled it, and leaves its settings and lines in your folder. Beyond its own two folders, it changes only the tool folders listed above (~/.claude/, ~/.cursor/, ~/.codex/, and ~/.agents/skills/) and Cursor’s side folder, and it sets git settings inside ~/alexandria/ only, never your global ones. It leaves each tool’s own memory alone too. Claude Code’s memory, Codex’s memories, and a chat app’s account memory stay on and keep deciding for themselves what to remember, and the installed instructions tell your model to save there whenever the tool’s own rules would, beside your folder, never instead of it.

Setup connects nothing to the cloud, pushes to no remote, creates no repository, uploads no key, and schedules nothing new. Where the Drive bridge is already on, it reloads that job with the updated scripts. It starts two background processes. One moves transcripts that earlier releases saved into vault/transcripts/, blanks out key-shaped strings in them, and removes a copy only when a longer copy holds every byte of it, then records that it finished, running again only when a release changes that helper. The other writes the readable copies in vault/sessions/.

During setup, on a Mac with iCloud Drive, your model links the capture folder in your own iCloud Drive into your folder as part of the system you asked for, and offers Git backup and update checks with one yes each. Personal modules wait until you ask for them. Every other add-on is a connection that needs its own yes. Those are recovering current files from iCloud, the Google Drive copy for chat apps, Airlock, fetching saved links, the public-profile watch, showing other members the city you are in now, Library publishing, marketplace signals, messages through the connector, and a live mirror. ~/alexandria/system/.optional says what each add-on touches, what leaves the computer, and its off switch, and the signed plm.md describes the live mirror. Scheduled jobs exist only inside add-ons, each installed only on its own yes and each with a one-line off switch listed there. Git is the history you own, and GitHub is only the default place to push it.

when the hooks turn on

The hooks turn on only after setup proves the whole local loop works. The hooks are in place, the exact /a and /a. skills are safe, the cue shows both its start state and its close state for each session, Codex is either trusted or visibly waiting for you to trust it, and a Claude Code that skips its settings file is named rather than reported healthy. Every hook needs the .setup_complete mark that setup writes after those checks, and if a core check fails, the hooks stay off. A failed download stops setup before anything changes. If an optional file cannot be put in place while the core passes, the local loop stays on with the files it has, and setup reports an incomplete refresh, exits with an error, and keeps the earlier completed-install manifest, so the mark proves the core is ready, not that every optional file is from one release. A first install that stops halfway keeps its partial files for you to inspect and repair, with every hook off until a verified rerun succeeds.

Skill and rule file names are shared with everything else on your computer, so setup replaces one only on an exact path-and-hash receipt or exact earlier signed bytes. A file setup wrote that you edited since still counts as its own when the receipt names that exact path and, for the start and close skills, the skill still carries the name setup gave it. Allowlisted Cursor hook files are also recognised by a matching Cursor hook: header. These are ways of recognising files, not cryptographic proof of who owns them, so a file someone else put at one of those paths can collide. Any other file is kept and that tool is reported incomplete, and another healthy tool cannot hide that failure. The uninstaller removes only exact receipted or signed bytes.

a tool you are only trying

Don’t hand a tool you are only trying your Apple login, your Drive account, your normal GitHub account, or your own repository. To connect one, open the model you trust on your computer and type Airlock this new tool: <name or URL>, or paste the request from the door for untrusted tools at /loop. The steps it follows are the Airlock block in ~/alexandria/system/.optional, and it hands you one message for the new tool with its repository’s address filled in.

Airlock uses a separate GitHub account that belongs to no organizations and may reach only your own declared Airlock repositories, one private repository per tool, each named for it, with each tool’s access limited to its own wherever the tool allows that. A tool that demands access to a whole account signs in as the Airlock account itself: it can then read, change or share every room, including private copies you approve for other tools later, and only an instruction asks it to write to its own; it never gets a further account, since GitHub allows each person one free personal account and one machine account. The repository holds a copy you deliberately selected, plus inbox/ files and airlock-capture issues for what comes back. A copy of only public Library pieces may refresh on its own. Any other copy needs your approval of its plan, and session start keeps it current only once you have recorded that approval yourself outside the model's sandbox (permission.sh grant <room>): then a Library piece you approved for another audience, members or invited people, follows the version you approve for that audience, while every other file stays at the exact bytes you approved, and the copy stays frozen as a whole until changed private bytes, or a changed list of files, are approved again. Before any network action, the controller checks the account, its credential, the remote, its visibility, its organizations, the complete list of repositories it can reach, the manifest, and the exported bytes. Everything that comes back becomes a capture marked trust: untrusted, or, from a tool on your team, a note in your team folder marked the same way, with no automatic authority to change files, run tools, or become one of your method files, and an issue is closed only once its capture is kept, written on your computer, and in your backup while backup is on. Giving a repository to a different tool means importing its last return, revoking the old app, and deleting or rebuilding that repository; retiring a tool that signed in as the Airlock account itself also means signing it out, removing any app, token or key it added and anything it left on any room, and changing the account's password if it ever held it. Creating a GitHub account, signing in, and authorising another app stay steps for you.

what the hooks save

Everything here stays on your computer.

  • Transcripts. The session hooks keep transcripts in ~/alexandria/files/vault/transcripts/ when the tool exposes them. For Cursor that is its own full record of the chat, and its prompt and response hooks build a local transcript only as the fallback. Each session keeps one file named by its end time and session id, and when a resumed session ends, its new copy replaces the earlier one only if it contains every byte of it, and otherwise both stay. On the way in, strings shaped like credentials (API keys, access tokens, connection codes, and private key blocks) become [REDACTED]. That is pattern matching, so a secret with no recognisable shape can still reach the vault. Without python3 and the signed helper that does it, no transcript is saved, and the warning count at session start says why. A transcript path the tool supplies is copied only when it is a regular file you own under a supported tool folder (~/.claude/, ~/.codex/, ~/.cursor/, or ~/.alexandria/transcripts/), with no link or path that climbs out of it. If a tool supplies no transcript, the loop says so rather than claiming it saved one.
  • Older transcripts. Setup brings transcripts that earlier releases saved up to the same standard, in the background, once and again whenever a release changes the helper that blanks them. It moves the ones saved at the top of the vault into transcripts/ without replacing any file, blanks them in place, and then removes a copy only when a longer copy begins with every byte of it. Nothing else in the vault moves, apart from a one-time move of older capture folders into vault/captures/.
  • Readable copies. After each session, in the background, the hooks also keep a readable copy of each conversation in ~/alexandria/files/vault/sessions/, one file per conversation with at least two of your messages. It holds your messages and the replies, with no tool calls or tool output, and the same credential shapes blanked out, and when a release adds a shape, the next run blanks it out of the copies already written too. The hooks build these from the transcripts above and from the records Claude Code, Cowork, and Cursor keep on this computer, including conversations from before setup that those tools still hold, which setup writes once, in the background. touch ~/alexandria/system/hooks/session-capture.off stops it, and lines in ~/alexandria/system/session-capture.conf leave out every conversation whose working folder matches a pattern (skip-cwd: <pattern>) or any single message that does (drop-text: <pattern>).
  • Where it goes. By default the vault goes nowhere. It is your record, kept on your computer until you delete it. If you separately turn on backup, tracked vault files go only to the exact private Git remote you approved, and Alexandria has no access to that repository. A file over 100 MB stays on this computer, out of the backup, because GitHub refuses files that large and one would stop every later push, and session start names it.
  • Captures. Working through captures happens on your computer. /a freezes the exact batch that was there when it started, opens the conversation at once, and proves that batch in the background without mistaking later arrivals for unfinished work. The Apple Shortcut that sends captures from your phone is described step by step in factory/systems/shortcut.md, and the Shortcut itself runs only on Apple devices.

what your model cannot switch on

The hooks run outside your model’s sandbox, and your model can write anything in ~/alexandria. So nothing written there can turn on work that leaves your computer, and nothing there runs as a hook’s code.

  • Two halves. Library publishing, Marketplace reporting, backup, fetching saved links, collecting messages, and collecting from a post box on your own website each run only while a grant exists in the protected folder, ~/.local/share/alexandria/permissions/, and its switch exists in ~/alexandria/system/permissions/. Library approvals, each file’s SHA-256 with its exact scope and name, live in the same protected folder, never beside the file.
  • Only you grant. ~/.local/share/alexandria/scripts/permission.sh writes grants and approvals, and it works only outside your model’s sandbox, so you run it in your own terminal or approve your tool’s prompt to run that one command outside the sandbox. It checks the file, manifest, or remote against the hash or address you were shown, and refuses if it changed.
  • Anyone can switch off. Deleting a switch stops that connection at once, and the next session start deletes its grant. Your model can end a permission, but it cannot begin or restore one.
  • A folder that publishes on its own, only if you turn it on. permission.sh grant library-auto-<layer> lets one Library folder publish without your yes to each change. Session start approves each plain file in that folder at its current bytes when the floor check (scripts/mirror_check.py) flags nothing, and records it in permissions/library-auto.log, which permission.sh status shows. A flagged file, a floor check that cannot run, or a deleted switch turns the folder off, and only you turn it on again, and a link to a file elsewhere never ships this way. It is the one place a file your model wrote can leave without your yes to its exact bytes, so it stays off until you choose it, because a model tricked by text it read could publish into that folder, and a published copy cannot be taken back.
  • No key from ~/alexandria. The hooks take the full account key only from the protected folder. The key your model can read and write in ~/alexandria/system/.api_key is the read key, and a key written there, even a working full key of another account, never reaches a hook’s call.
  • No code or git settings from ~/alexandria. The checker that guards your protected positions (root_integrity.py) runs only from a private copy whose bytes match a signed release this computer verified. Every git call a hook makes forces repository hooks and the file-system monitor off. Backup pauses, and names the setting, while the repository’s own git settings hold anything that could start a program or redirect data, such as a hooks path, a filter or merge driver, a credential helper, a signing program, a proxy, a URL rewrite, or an ssh command other than the one recorded with the grant. It pushes only to origin by name, and only while every fetch and push address of origin equals the approved one.
  • No file becomes an instruction. No hook passes a file’s contents to your model as its next instruction, and Cursor gets no stop hook asking for another turn.
  • Existing installs. The first setup run of this release carries forward, once, exactly what the earlier hook would already have acted on, meaning switches that are there, a backup remote that still matches, and Library approvals whose bytes still match. It then records a receipt, and nothing written in ~/alexandria afterwards becomes a grant.

A few switches stay writable by your model on purpose, because none of them can send anything private. system/hooks/auto-update turns on update checks, which fetch only signed release files, and system/hooks/visible-cue.off and system/hooks/every-reply.off only silence. The ## Every reply section of your AGENTS.md is read by the hooks only to show your model, a few thousand characters at most, and never through a link. The host-memory record’s yes and added-store lists (system/.host_memory_taken.json) can widen only which other tools’ memory folders on this computer the session’s model reads, never send anything elsewhere, and an added store must sit where tools keep their settings. The readable-copy switch and settings (system/hooks/session-capture.off and system/session-capture.conf) can only stop or narrow what is written into files/vault/sessions/ or change the label on your turns, and a settings line the hook cannot read stops it writing anything. The people-context marker governs a helper your model itself runs during a task, never a hook.

every network call

This inventory lists every call over the network that setup, the hooks, the signed helpers, and the optional website handler make, and nothing is left out. “Full key” means the account key from the protected folder, and the read key appears only in the rows for looking people up.

Only one request to your account repeats on its own. On a connected computer, session start asks the connector at most once a day whether your membership is active and the name your invite link carries, sending only the full key and the installed client version.

Beyond that, the hooks make no standing account request unless you turned on messages, Library publishing, or Marketplace reporting. For messages, session start sends only the full key, this computer’s key id, the installed client version, and the ids of messages it collected, nothing else, plus, once you delete the switch, the calls that turn messages off. If your post box is on your own website and you approved that exact address, then only when the connector says a letter waits there, it also asks the connector for a five-minute pass for that website and what the connector vouches for about the letters there, and collects with the pass, sending your website only the pass, this computer’s key id, and the ids it is done with.

Looking people up happens only during relevant ordinary work while its marker exists, never in a hook. Each connected computer has its own account key, so signing in or connecting on another computer never breaks a working connection here. Transcripts appear in none of these requests.

GET api.alexandria-library.com/source/<invite>/…

When
Your coding agent, once before the first install
Sends
your invite code
Gets back
one exact commit and the source to review

GET api.alexandria-library.com/source/<invite>/<verified-commit>/factory/…

When
Setup, pinned to the commit your agent reviewed
Sends
your invite code
Gets back
the signed manifest and the factory files

GET api.alexandria-library.com/source/<invite>/commit

When
Before an update, an update check, a pull, pinning a newly installed hook program, or any signed helper run through the installed verifier
Sends
your invite code
Gets back
the one commit of the release your invite code is on (releases in stages), so the manifest, its signature, and the files all come from the same release

GET api.alexandria-library.com/source/<invite>/<commit>/factory/<file>

When
Each run of the installed verifier, whether applying an update (setup.sh and its classifier) or running a signed helper on your yes (connecting, adding or sharing a module, publishing your page, connecting another computer, turning on Drive or Airlock)
Sends
your invite code
Gets back
files accepted only if the whole-factory manifest’s signature, version, and file hash all pass

GET api.alexandria-library.com/source/<invite>/<commit>/factory/manifest.txt(.sig)

When
Install and update, an explicit pull, every run of the installed verifier, checking a newly pinned hook program, or session start once you turn on update checks
Sends
your invite code
Gets back
the signed manifest and its signature

GET api.alexandria-library.com/source/<invite>/<commit>/factory/changes.md

When
Session start once you turn on update checks, only when an update notice for the hook program shows (at most once a day)
Sends
your invite code
Gets back
the release’s list of what changed, shown only if it matches its hash in the verified manifest

GET api.alexandria-library.com/source/<invite>/<commit>/factory/canon/*.md

When
Install, an explicit pull, connecting your account (the connector and Library files, where missing), or adding a listed module
Sends
your invite code
Gets back
signed method files

GET api.alexandria-library.com/source/<invite>/<commit>/factory/{skills,hooks/cursor,templates,scripts}/...

When
Install (session start compares your files against the last verified manifest on your computer, with no fetch)
Sends
your invite code
Gets back
factory files to install

POST api.alexandria-library.com/account/connect/exchange

When
Once, only after you say the exact word connect
Sends
the short-lived one-use code, the installed public client version, the current full key when one already works, and a request for this computer’s read key
Gets back
one full key in its exact format, or one exact flag that a key already exists, each with one exact read key; any other answer fails closed and no server text is shown

POST api.alexandria-library.com/account/read-key

When
Each verified setup run you start on a connected computer
Sends
the full key
Gets back
one exact read key, which replaces this computer’s previous one; anything else leaves the keys as they were

HEAD api.alexandria-library.com/account/connect/current

When
Each setup run on a connected computer
Sends
the full key
Gets back
a status code only

POST api.alexandria-library.com/account/connect/handoff

When
Only when you ask to connect another computer, through your tool’s approval prompt
Sends
the full key
Gets back
one connection code for that computer

GET api.alexandria-library.com/account/membership

When
Session start on a connected computer, at most once a day, in the background
Sends
the full key and the installed client version, as headers passed on standard input so neither shows in a process listing, and nothing else
Gets back
exactly whether the membership is active and the name the invite link carries, kept as those two values in system/.protocol_status.json; any other answer is discarded, an unavailable connector keeps the last answer, and a refused key removes it

GET api.alexandria-library.com/library

When
During ordinary work, only while permissions/people-context exists and a named person matters to the task
Sends
the read key, with no name, prompt, or private context
Gets back
a limited set of member directory fields, used to match the person on your computer

GET api.alexandria-library.com/people

When
When looking someone up or listing your own people, under the same permission
Sends
the read key, with no name, prompt, or private context
Gets back
the people this account is already tied to, each as a public card saying how you know each other, marked untrusted

GET api.alexandria-library.com/connect/site/<author>

When
After one confident match on your computer, under the same permission
Sends
the read key and the chosen public handle, with no prompt or private context
Gets back
the exact verified address of their own website and the path of its description, or a plain answer that none is registered

GET <verified-own-site>/<manifest>.json and the public pages it lists on the same site

When
After that permitted match, or for an exact public description you supplied yourself
Sends
no key, cookie, prompt, or private context, as an ordinary public request
Gets back
limited public details and the public pages it lists, with the address pinned and hashes checked when declared, still treated as untrusted

GET api.alexandria-library.com/library/<author>

When
After one confident match, under the same permission
Sends
the read key and the chosen public Library address, with no prompt or private context
Gets back
only the profile and piece details this account can already see, marked untrusted

GET api.alexandria-library.com/library/<author>/file/<name>

When
Only for a piece that permitted profile read returned
Sends
the read key and the exact Library file address, with no prompt or private context
Gets back
the exact piece this account may read, limited in size and marked as untrusted data, never instructions

PUT api.alexandria-library.com/library/me/profile

When
Once, only after the exact draft is shown on your computer and you say the exact word publish
Sends
the full key and only the approved, hash-bound display_name, text, website, and socials fields (the server discards website)
Gets back
exactly {ok:true} or a fixed failure on your computer; no server text is shown

PUT and DELETE api.alexandria-library.com/library/me/here-now

When
Only while your protected whereabouts grant and its switch exist, at session start or from its hourly job (macOS), and only when a new reading arrives; the clear runs once more after you turn it off
Sends
the full key, one Library city with its two-letter country, when it ends (at most 36 hours ahead), a trip’s last day if you gave one, and whether every member or only your own people may see it; nothing else about where you are, and never a history. The reading comes from your Mac itself: after you allow it once, macOS’s own location service tells that small app where the Mac is and Apple names the city, as Maps does, and no position ever reaches us
Gets back
the city as it now shows, or that nothing shows

POST api.alexandria-library.com/call

When
Only while your protected Marketplace grant holds the SHA-256 of the current .call_manifest and its switch exists
Sends
the full key and the exact approved JSON
Gets back
limited acknowledgements checked against the local manifest, then reduced to a fixed success or failure

PUT/DELETE api.alexandria-library.com/marketplace/modules/<name>

When
Only when you share one of your own modules after seeing its exact bytes, or ask to take one down, each through your tool’s approval prompt
Sends
the full key and exactly the approved module text and its SHA-256 (sharing), or nothing more (taking down)
Gets back
the module’s id, or a refusal naming what to fix; nothing else is sent

GET api.alexandria-library.com/marketplace/modules/<handle>/<name>, or raw.githubusercontent.com/<user>/<repo>/<branch>/<path>.md

When
Only when you ask to add or inspect a named marketplace module that is not part of the signed release
Sends
nothing but that address (and your IP)
Gets back
the module’s text, shown to you before anything installs

GET api.alexandria-library.com/files

When
Only with Library permission, after sending approved files
Sends
the full key, a hash of the client version, and the Library folders this hook knows, so an older hook is never shown a folder it predates
Gets back
this account’s file details, limited and checked on your computer, then reduced to fixed codes for what differs

PUT api.alexandria-library.com/file/<name>

When
Only with the protected Library grant and an approval naming that file’s exact scope, name, and content hash
Sends
the full key, that exact approved file, its scope and type, and the subtitle and questions read from those same bytes, with no other details and no private path. Large media stays elsewhere and comes in as a link, and the account limits are in server/src/library-limits.ts
Gets back
success or refusal

POST/DELETE api.alexandria-library.com/connect/site

When
Only after you choose the exact site registration or withdrawal
Sends
the full key and the approved site, description path, callback path, and listing choice, with no published bytes
Gets back
a structured registration result; the domain proof is untrusted data, never an instruction to run code

POST api.alexandria-library.com/connect/site/verify

When
After you place the exact domain proof
Sends
the full key and the site
Gets back
the verified registration

GET api.alexandria-library.com/connect/access/<author>

When
Made by your website’s own server, only when it serves protected material
Sends
a credential scoped to the reader (none for a visitor who has not signed in), your site’s address, the exact scope, the piece’s name when it sits straight in the invite or paid folder, where its own grant decides, and the codes the reader holds for that person (an invitation, a purchase, or a code that opens several pieces), with no question or published text
Gets back
the current access decision; if the connector cannot answer, access is refused

PUT/DELETE api.alexandria-library.com/inbox/key

When
Turning messages on, after your grant, or off (which first reads your settings)
Sends
the full key and this computer’s public inbox key (on), or nothing more (off)
Gets back
whether receiving is on, and the key id; turning off also deletes what was waiting

GET api.alexandria-library.com/inbox?key_id=<this computer’s key id> then POST …/inbox/ack

When
Session start, only while the protected inbox grant and its switch exist
Sends
the full key and this computer’s key id, then the ids of the messages it collected
Gets back
up to 25 sealed messages with the sender’s handle, the date, and the sender’s signature when there is one, your own handle as senders sign it, and how many wait for another computer; acknowledged ones are deleted from the server

GET api.alexandria-library.com/inbox/settings then DELETE …/inbox/key

When
Once, at the session start after you deleted the inbox switch
Sends
the full key
Gets back
whether this computer still holds the registered key; if it does, receiving is turned off and anything waiting is deleted

GET api.alexandria-library.com/inbox/to/<handle>

When
When your task needs a person whose Mirror does not have the answer
Sends
the full key and that handle, with no question or draft
Gets back
whether you may write to them, their public key, where their post box is when it is on their own website, and your own handle as your letter will be labelled and signed; refusals are fixed codes

POST api.alexandria-library.com/inbox/to/<handle>

When
Only after you approve the exact words of that message
Sends
the full key, the key id, the text sealed on this computer, and its signature, plus the id of the message it answers when it is a reply
Gets back
a fixed sent or refusal code

POST api.alexandria-library.com/inbox/to/<handle>, for someone whose post box is on their own website

When
Only after you approve the exact words of that message
Sends
the full key, the key id, a SHA-256 of the text sealed on this computer, and its signature, plus the id of the message it answers when it is a reply
Gets back
a five-minute pass for this one letter and their website’s address, or a fixed refusal code

POST <their website>/…/inbox

When
Right after that pass
Sends
the pass and the text sealed on this computer (and your IP, as when their Mirror is read), never the account key
Gets back
a fixed sent or refusal code

POST api.alexandria-library.com/inbox/pass, then GET <your website>/…/inbox?key_id=<this computer’s key id>, POST api.alexandria-library.com/inbox/attest, POST …/inbox/ack, and POST <your website>/…/inbox/take

When
Session start while your post box is on your own website and the connector says a letter waits there, only at the exact address your protected post-box grant names and while its switch, the inbox grant, and its switch exist; also inbox.mjs box on, box off, and off
Sends
the full key and the post box path, then to the connector the ids the website listed, and to your website only the pass, this computer’s key id, and then the ids it is done with
Gets back
a five-minute pass, up to 25 sealed messages, and for each the connector’s sender, key, date, fingerprint, and the sender’s signature, and whether to keep it; finished ones are removed from your website

GET …/inbox/settings then PUT api.alexandria-library.com/inbox/settings with {"box":null}

When
Once, at the session start after you deleted the post-box switch, on the computer that collected from that website, while it still holds the inbox
Sends
the full key
Gets back
where messages wait, back on the connector

POST api.alexandria-library.com/inbox/pass/verify

When
Made by your website’s own server when someone brings a letter or your computer collects; anyone holding a pass can make it too
Sends
the pass, your handle and website address, and for a letter the SHA-256 of the sealed text it received, with no text and no key
Gets back
whether the pass is good, and for a letter its id, the sender’s handle, the key id, and the dates; the same answer whether or not you blocked the sender

GET/PUT api.alexandria-library.com/inbox/settings

When
When you ask to see or change who may write, to block someone, or where messages wait
Sends
the full key and one setting
Gets back
whether receiving is on, who may write, blocked handles, your verified website, and where messages wait

POST api.alexandria-library.com/account/feedback

When
Only after you approve the exact words of a note to the Alexandria team, through your tool’s approval prompt
Sends
the full key, those words, and the fixed label session, nothing else
Gets back
an id for the note, or a fixed refusal; the words wait in the team’s private feedback queue until they are dealt with

GET api.alexandria-library.com/file/<name>?scope=<scope>

When
Only with Library permission, after that exact approved file was sent
Sends
the full key and the exact local scope
Gets back
this account’s stored bytes, hashed on your computer to check they match, and never shown to your model

DELETE api.alexandria-library.com/file/<name>

When
No signed script sends it. It is the route an unpublish uses, only after you directly ask to unpublish that exact piece and separately approve the deletion, never from standing sync
Sends
the full key
Gets back
success or refusal

git push and git pull --rebase against your own alexandria-private GitHub repository

When
Session start (commit and push, then pull and rebase) and session end (push), only while your protected backup grant names every fetch and push address of origin exactly and its switch exists. A remote that was already there or has changed does nothing, and repository git settings that could start a program pause it
Sends
the tracked contents of ~/alexandria/, leaving out what git ignores (system/hooks/, system/modules.json, system/permissions/, system/.*, and, in a repository setup created, files/library/ and node_modules/)
Gets back
git’s own reference data

gh ssh-key add and gh repo create

When
Never at install. Only when you turn on the backup add-on, on your explicit yes
Sends
your own gh sign-in token, never your Alexandria key
Gets back
success or failure

git fetch and git push to your Airlock repository, and gh reading and closing its open airlock-capture issues

When
When you set Airlock up, and at session start while a room you set up is on
Sends
the room’s approved copy, through the separate Airlock account’s own credentials
Gets back
the room’s returns, kept as untrusted captures, or as untrusted notes in your team folder

rclone to your own Google Drive

When
Every ten minutes, only while the Drive bridge you turned on runs; turning it on installs rclone with Homebrew and signs in to Google through rclone
Sends
your guide, the method files it points to, and your constitution’s positions, as Google Docs (at most once a day, or sooner when one changed); the sign-in stays in rclone’s own settings
Gets back
new writing in the Drive copy, kept as captures, or in your team folder when a helper wrote it under vault/team/

GET api.fxtwitter.com/i/status/<id> (and its photos on X’s image hosts)

When
Session start, only while your protected grant for fetching saved links and its switch exist, and an X link is in files/vault/captures/new/
Sends
the post id saved there (and your IP, as with any fetch)
Gets back
the post’s text and media, written into files/vault/captures/ on your computer

GET www.youtube.com/oembed?...

When
The same permission, plus a saved YouTube link
Sends
the video address you saved
Gets back
its title and author, kept on your computer

GET <a URL you saved>

When
The same permission, plus a saved link or .url file
Sends
the exact address you saved (and your IP)
Gets back
the page’s title, written on your computer for you to review

GET api.fxtwitter.com/2/profile/<handle>/statuses and api.fxtwitter.com/<handle> (and X’s photo hosts), www.instagram.com/<handle>/, www.linkedin.com/in/<handle>/, www.pinterest.com/<handle>/feed.rss, www.youtube.com/feeds/videos.xml (and the channel page once), and api.github.com/users/<handle> with its repositories

When
Every six hours, only while the public-watch job you turned on for exact profiles of your own runs (macOS); its schedule names those profiles, and public_watch.py off removes it
Sends
each approved profile’s public handle (and your IP); Instagram and LinkedIn are asked the way a link preview asks, with no account, cookie, or sign-in
Gets back
new posts and profile details, written into files/vault/captures/ on your computer

POST api.alexandria-library.com/connect/token

When
Made by your website’s own server, when a reader comes back from signing in through it
Sends
the one-use code, its verifier, your handle, and your site’s address
Gets back
the reader’s scoped credential, kept in a cookie on your site

Most of these calls also carry an X-Alexandria-Client header naming the installed client (a hash of the signed hook program or its release number, or the fixed word publish when a module is shared), so a broken client can be spotted on the server. It identifies the software, not you, since your account is already on the request. Setup’s key check and read-key request, page publishing, and the code for another computer send none.

That is all. There are no telemetry pings, install reports, automatic feedback, error reporters, analytics kits, account-status reads beyond the two-value membership check and setup’s key check, or Library imports at session start. General public pages stay in your browser or a properly isolated reader. The narrow exceptions are looking up a named person, which sends no private query and keeps no cache, and a message you choose to open, which arrives only from a member allowed to write to you. Text returned by either may shape only the answer shown to you, never a tool action, and no server text enters through connecting or session start. Everything the saved-link and public-watch rows fetch lands on your disk, not ours. The saved-link rows need both a separate permission and a link in your own capture inbox, and the public-watch row exists only after you approve the exact profiles and turn the job on.

Fetching saved links sends only the exact address or post id found in the capture inbox, plus, for a post, requests for its photos on X’s image hosts, and stays off without its grant. While it is on, it fetches any link in that inbox, including one a model session wrote there. It still refuses private, loopback, link-local, reserved, multicast, and metadata addresses, checks every redirect again, pins the address it looked up, allows only https, and caps the size of what comes back.

To check the surface yourself, run grep -nE 'curl|https?://|fetch\(|urlopen|git (push|pull|fetch)|\bgh |rclone' ~/.local/share/alexandria/.hooks_payload ~/.local/share/alexandria/hooks/shim.sh ~/.local/share/alexandria/scripts/*. The helpers that run only through the verifier (connecting, publishing, adding a module, connecting another computer) are read in the signed source.

what our server holds

No address on our server accepts your private files, and nothing installed on your computer reads them into a request. Library sync sends only the files you approve to publish. Two routes take words. Feedback takes only the words you approve for the Alexandria team, and messages take only text sealed on the sender’s computer, which we cannot read, except a reply you send to @alexandria, which is sealed to the market’s own key. Once a member moves their post box to their own website, we hold none of the messages sent to them after that.

If you give an email on the start page or the join page, we keep it so we can send your setup, a few gentle reminders with the same request until a setup starts with your code, and occasional useful notes, until you unsubscribe. The reminders come the same day, two days later, a week later, two weeks later, and then monthly for six months, and then they stop. Giving an email creates no account and no install record. The table below is the full list of what the server keeps.

The server is a Cloudflare Worker that keeps none of your private material, and what it does keep sits in Cloudflare’s three stores, KV, D1, and R2.

Your email and your name here (your GitHub login and id, if your account was made with GitHub), your Stripe customer id and your subscription and payout-account references, your member number, and when you joined, connected, and last signed in, all in one encrypted account record. Between signing in and joining it can also hold the hash of the invite code your browser used, removed once the join is counted, and from then on it keeps only which release circle that code was in and its kind (a door, a member’s link, or one made for a person), and any membership terms you were put on by hand

Where
KV (AES-256-GCM at rest)
Why
Your account, sign-in, and billing

For email sign-in, a keyed hash of the address and which account it opens; while you sign in, the code as a keyed hash and the address encrypted, deleted within the hour; and the names given here

Where
D1 (email_accounts, auth_signins, account_handles)
Why
Signing in without GitHub, one account per address, and one person per name

The email you give on /start or the join page, the route you chose (and whose link you came through, if any), an unsubscribe token, the invite code it came with (encrypted) and its hash, and how many setup reminders were sent

Where
D1 (waitlist)
Why
Your setup email, reminders with the same request until your code is read, and occasional notes, and counting emails per invite code

For 30 days after a setup email, the address encrypted under a keyed hash of a token only that browser holds

Where
KV (AES-256-GCM at rest)
Why
Ending reminders to a mistyped address when you correct it on /start

For 30 days after an announcement or update email, a keyed hash of the address and which email it was

Where
KV
Why
Never sending the same email twice

For each invite code, as its hash, the code itself (encrypted), who it was made for or which link made it, and its release stage

Where
KV
Why
Opening the source only to valid codes, and releasing in stages

For each invite code, as its hash, when the source was first read with it, when a setup or update check last started with it, and how many people it brought have joined

Where
D1 (invite_funnel)
Why
Seeing where invited people stop, with nothing about what they read or who read it

Each connected computer’s full key and read key, as SHA-256 hashes only, with which full key made each read key and when

Where
KV
Why
Checking keys, and refusing a read key everywhere but the lookups

The account connection code, as a SHA-256 hash, with which account it is for and when it expires (one hour or first use)

Where
D1
Why
One deliberate connection exchange, without putting a lasting key in a web page or an email

For each chat app you connect, its registration (name and return addresses), your approval (which account, what it may do, when, and whether you removed it), and SHA-256 hashes of its access and refresh credentials

Where
D1 (mcp_oauth_*)
Why
Letting that app use the connector until you remove it

For each key you make for an app, the app name you typed, when you made it, and the key’s SHA-256 hash, erased when you remove the key

Where
D1 (mcp_oauth_*)
Why
Letting that app use the connector until you remove the key

A change a chat app prepared to your page, encrypted, for fifteen minutes or until you confirm it, then erased

Where
D1 (mcp_profile_drafts)
Why
Showing you the exact change before anything is published

Whether each account is a member, its handle, and when that was last confirmed

Where
D1 (member_standing)
Why
Listing members on a directory page without asking Stripe about each person every time

How many requests each network address, each account or name, and each key, browser session, or sign-in address (as a hash) made in the current window and the two before it, each window a minute to a day long

Where
D1 (request_rate_limits)
Why
Stopping floods and runaway clients; older counts are deleted as new ones arrive

A log of the routes you deliberately use, with times and light request details, and, for a module report without the client’s version header or an anonymous call to a retired route, also the network address, browser, and country

Where
KV (deleted after two days)
Why
Fixing problems, and spotting abuse

Library files you deliberately publish

Where
R2
Why
Your published Library pieces

Library file details (name, exact scope, title, short description, price, visibility, content type, content hash, and when it changed)

Where
D1
Why
Finding and listing pieces, with their permissions

Files you approve into market/, stored like the rest and counted in the same limits. No reader, grant, invite code, mirror, website, cover, directory, or handoff reaches them, nor your mirror preview, nor any key, your own included. Your own page lists them to you alone, signed in in your own browser, and you list, read back, and delete them with your own key. Two things read them, and each read is logged as a read of your file. One is a matching route only the operator can use, behind its own secret, which returns only active members’ files in this folder. The other is the market for answers, only if you chose to be asked or answered for, and only for a buyer switched on. A folder you make inside it for one buyer (market/<buyer>), or for every buyer of one category (market/lab, market/fund, market/company or market/person), answers that buyer instead of the whole folder: its own first, then its category’s

Where
R2 and D1
Why
Choosing whose questions reach you, and answering fixed ones for you if you chose that

Who opened each of your Library files, or was refused, and when, with which of your codes for someone not signed in; each code made or revoked; and each question put to your mirror, with who asked (or which of your codes they asked with) and which folders the answer could draw on, never the question or the answer

Where
The KV log (30 days); file views and codes also in a permanent hash-chained archive in R2, which deleting an account does not remove; and a row for each answered mirror question, with who asked, the question and answer lengths, and a hash of the context sent, in D1 (access_log)
Why
Your own access log (GET /library/{you}/access-log, the last 30 days), spotting abuse, and a record nobody can quietly change

Marketplace calls you separately approve, with the module id, account id, time, and exact optional notes in the approved manifest

Where
D1 (protocol_calls)
Why
The marketplace listing

What you give back, as numbers per billing period. How many different people opened your invite link, made an account through it, and are members now; how many modules you shared and how many other members ran them; how many pieces others can open you published or changed, and how many signed-in people opened them; how many signed-in people your mirror answered; how many times your own model used the connector (lookups counted per day, messages read from their labels); what each bill charged for membership; and which policy decided that period and how it was chosen. Behind them, each open of your invite link as a hash of the network it came from, keyed to you, one a day per visitor for 70 days; each reader of your pieces as a hash keyed to you, one a day; and whose link each new account came through. Never who opened a link, who read or was looked up, or what was read

Where
D1 (contributions, contribution_periods, contribution_days, referrals); KV (invite opens, 70 days)
Why
Deciding what a month costs, and seeing what members give back

Modules you choose to share, with the exact text you approved, its name, its SHA-256, and when you shared it, public until you take it down

Where
D1 (shared_modules)
Why
Your Library profile and the marketplace, and anyone’s model reading a module before adding it

Your Library profile, with your name here, short note, settings, location, and links; every account has one from its first sign-in

Where
D1 (authors)
Why
Your page and the directory

If whereabouts is on, the one Library city you are in now, when that ends (at most 36 hours after the last reading), a trip’s last day if you gave one, and whether only your own people may see it. Each reading replaces the last, so there is never a history, a reading at home clears it, and it is erased within a day of ending

Where
D1 (authors)
Why
Showing every member, or only the people you are connected with if you chose that, and their models, where you are this week. Nobody else sees it

Codes you make for your pages, and the one each purchase gives its buyer, in plain text with their label and scope, any other pieces you chose for the same code to open, whether each is for one person and, if so, which account took it, which sale a purchase's code came from and when it was collected, for a purchase made signed out a keyed digest of the buyer's email (so signing in with that address puts it on their account, never the address itself), and which signed-in accounts a code or purchase was put on

Where
D1 (access_codes, access_code_targets, access_grants)
Why
Opening your invited pieces to whoever holds a code you gave, signed in or not, paid pieces to the people who bought them, and whatever else you chose for a code to open

A website you register, with its address, paths, whether it is listed, and a hash of its DNS proof; and, for five minutes, a reader’s sign-in for it, encrypted

Where
D1 (visitor_connector_sites, visitor_connector_codes)
Why
Verified website addresses and shared reader sign-in

If you connect a Mirror, its address and shared secret and any access credentials it needs, encrypted; and how your page orders, groups, and subtitles your files, with the questions shown beside each

Where
KV
Why
Asking your Mirror on a reader’s behalf, and your page

Each browser sign-in, under a SHA-256 hash of its token, for 30 days, so a copy of the store signs no one in; and for five minutes the steps between signing in and the welcome page, which hold the new sign-in until your browser collects it, once, and a connection code

Where
KV
Why
Keeping you signed in on the website

After a purchase, which account bought which piece, for 7 to 30 days

Where
KV
Why
Opening what you bought right after paying

A feedback note you approve, encrypted until it is passed to the team’s private feedback repository on GitHub, where it stays

Where
KV, and a private GitHub repository
Why
The team reading what you sent

Stripe event ids and markers of which emails were sent, some named by account, for 90 days; and small markers (a keyed hash of an address that used the public start form, for a day; whether you were reminded about the Mirror; a billing warning, for 60 days; and the index of each account’s unsubscribe token)

Where
D1 (stripe_webhook_events); KV
Why
Handling each Stripe event and email once, limits, and one-time notices

If messages are on, your inbox’s public key, who may write to you, and the handles you blocked

Where
D1 (inbox_keys, inbox_blocks)
Why
Routing, and your own controls

Messages to you, sealed on the sender’s computer to your key, until your computer collects them or for 30 days; none if your post box is on your own website

Where
D1 (inbox_messages)
Why
Delivery, though we cannot open them. The one exception is a reply you send to @alexandria, which is sealed to the market’s own key so the market can count it

If your post box is on your own website, that website and the box’s path

Where
D1 (inbox_boxes)
Why
Sending letters there

Each post box pass, usable for five minutes and deleted within a day, with its hash, the website it is for, and, for a letter, its id, sender, key id, and a SHA-256 of the sealed text, never the text

Where
D1 (inbox_passes)
Why
Letting your website check a letter, or your computer collect, without holding a key

For each letter left on your website, for 30 days, its sender, the key it was sealed to, and a SHA-256 of the sealed text, never the text

Where
D1 (inbox_letters)
Why
Your computer keeps only letters whose sender and contents match what was sent

Each letter’s signature, made on its sender’s computer, until the letter is collected, its 30 days run out, or an unused pass for it expires

Where
D1 (inbox_signatures)
Why
Letting the recipient’s computer check who wrote it, which we cannot fake

A label for each message for 30 days, with sender, recipient, time, whether it was collected or answered, and a price field that stays empty until paid questions exist

Where
D1 (inbox_labels)
Why
Daily limits, replies, and a record of delivery

Each paid piece you buy or sell, with which account paid which (a purchase made signed out has no account on it), the piece, the price and fee, whether it went on a bill or was paid by card, where it stands (on a bill, paid, credited, or refunded), and its Stripe references; each credit for your answers to a company, with the company, the round, the amount, and where it stands, which a company taking its payment back never takes back from you; and each payout of your credit to your bank. Never your card or bank details, which only Stripe holds

Where
D1 (ledger)
Why
Crediting a seller only once the buyer has paid, closing a piece whose payment comes back, and your own record at /account

Your rules for paid questions, in order, each saying which buyers, categories of buyer, and uses it covers, whether you are off (where everyone starts), asked, or answered for automatically, the least an answer must pay you, the fewest people you will be counted among, whether a writing model may read your folder, how much of you your answers carry (inside totals only, one round’s answers together with no name, or with your name), and whether your Library page tells the buyers it covers that you are open to being asked (off unless you choose it); and a hash of the exact words you agreed to for each; and when you were last asked whether you want to add to your market folder, for no longer than the weekly limit on those letters needs. Only you change your rules, signed in in your own browser, never with a key your model holds

Where
D1 (market_choices)
Why
Asking you only what you agreed to

While a buyer’s round runs, whether you are in it, which of its questions your answers counted for and how each was made (by the decision model, the writing model, or you), the screening answers that let you in, what reading for you cost, whether you pay for membership by card, how old your account is, and your name only if a rule of yours gives it; never what you answered; deleted once you are paid

Where
D1 (market_people)
Why
Paying you for exactly the answers the buyer received, and quality signals a buyer sees only as figures across everyone counted

If a rule of yours lets your answers leave as one row, your answers to that round together with the screening answers that let you in, with no name, or with your name if you chose that. A row with no name leaves only when at least as many people as the round’s crowd allowed the same, a row is cut to the questions that were released, and a row with your name is deleted with your account

Where
D1 (market_rows)
Why
What the buyer paid for, from people who chose to give it

Each round’s totals per question across everyone who answered, and the open answers people approved, with no name. A question fewer than 100 people answered is refused and its answers deleted, and nothing is released before the buyer pays

Where
D1 (market_tallies, market_texts)
Why
What the buyer paid for

A round’s questions as a letter from @alexandria, a handle no member can hold, sealed to your inbox key and signed with the market’s own key like any letter, held here or, if your post box is on your own website, posted there with a pass for that one letter as a friend’s is (if your website does not take it, you are told once by email and the round counts you as unreachable), with one short email saying it came that names only the kind of asker; your reply, sealed on your computer to the market’s key, which this server opens as you send it, to check it answers what it should and hand it straight back if not, and again to count it, until it is counted; and when the round closes, one letter saying what it paid you

Where
D1 (inbox_messages, market_replies)
Why
Asking you, and receiving only the words you approved

Each buyer, a company or one person asking for themselves, made by the founder or by any account for itself, with its name, use, the category Alexandria gave it when it switched it on (lab, fund, company or person), invoice email, the account that made it, a SHA-256 of its key, its Stripe customer, whether it is switched on (every buyer starts off), any terms of its own deal, and its rounds’ questions, prices, state, and what reading for each cost; and the market’s two letter keys, encrypted; and, for each buyer or round waiting for the maintainer, a SHA-256 of the one-time link in the email he decides it from

Where
D1 (market_buyers, market_rounds, market_approvals); KV (AES-256-GCM at rest)
Why
Running rounds only for a buyer switched on

If your page shows that you are open to being asked and a buyer your rule covers adds you to a round it is still drafting, that you are one of that draft’s examples, until the round is written or your account is deleted. The buyer’s own model drafts the round’s questions from what anyone can read on your page; being named never puts you in a round its questions would not, and the buyer never learns whether you were asked or answered

Where
D1 (market_examples)
Why
Letting a buyer say who it is looking for, from public pages only

If your folder did not say whether a round is for you and your rules let its buyer ask you, that you could be written to about it, until its letters are picked (at most a set number a round, at random, and one a week to you), then deleted. That letter asks you who the round is for as well as its questions; the buyer never learns you were asked

Where
D1 (market_silent)
Why
Asking people whose folders do not say, a few at a time

Every round you were in: which round, whether you were asked or answered for, and in the end whether it paid you and how much, or could not reach you; never your answers. It shows only to you, on your account page, and is deleted with your account

Where
D1 (market_history)
Why
Your own history of paid questions

For a round a buyer wrote on the website, what it is for and who it wants to hear from, in the buyer’s own words; and which email about each step of its rounds the buyer was sent. Nothing about a person who answers

Where
D1 (market_round_lines, market_notices)
Why
Showing the buyer its own round, and telling it each step once

The market for answers is on. Every rule starts off, so it touches nobody who has not chosen it on their own account page. Any account can ask to be a buyer (a company, or one person asking the crowd); the maintainer approves each one and gives it its category, and reviews each of its rounds before anyone is asked. For a buyer switched on, the market reads the market/ folder of people who chose ask me or automatic, through Cloudflare’s decision model on Workers AI in our own Cloudflare account, which Cloudflare says neither stores nor trains on what it reads. It uses the folder to choose who is asked and, for people who chose automatic, to answer that buyer’s fixed questions where the folder clearly says. A model of Anthropic’s reads each round’s questions before it runs; until it has a key of its own, the maintainer reviews each round himself. A writing model of Anthropic’s reads a person’s folder only if a rule of theirs allows it and the market’s own switch for it is on, which today it is not, and it then drafts answers for someone who answers themselves, or answers for someone on automatic what the decision model cannot, and Anthropic’s own terms then cover what it reads. A buyer receives only totals and open answers that at least 100 people stand behind, never who answered, and only after it pays. Each person is credited on their own bill and can see every round they were in on /account, never their answers, which are not kept. Nothing on your computer changes. A buyer’s letter is opened only when you choose, as data, never as instructions, and your reply goes through the same approval as any message. For anyone whose rules are all off, which is everyone until they choose otherwise, none of it reads or sends anything; and the market has one switch of its own that stops all of it (server/src/market.ts, with every number and word, that switch included, in server/src/market-terms.ts).

A nightly copy. Every night a copy of what the server holds (the database, the stored records, and the published files, in the same form as on the server, so what is encrypted there stays encrypted) is kept on the maintainer’s own computer, never in git or a cloud drive, so the service can be rebuilt if Cloudflare loses it. It leaves out only the two short event logs, whose file views and invite codes are already in the permanent archive it copies. The last seven nights are kept, and older ones are deleted.

Never stored anywhere we control. Your constitution, vault, marginalia, transcripts, machine.md, notepad, raw account key, and model-provider keys (Anthropic’s, OpenAI’s, and the rest) never reach us. Nothing else of yours does either, apart from the files you approve to publish from files/library/, the only folder the session sync ever sends, and the modules you approve to share, each shown to you whole. A mirror model answering for you receives only the exact published scopes that reader may see, plus the piece open in front of them and the current conversation. Its adapter runs somewhere you choose and, to meet Alexandria’s terms, has no access to your files, no hidden memory, no live web, and no Alexandria credential, and it accepts your material only from the server’s authenticated request.

What a complete breach of the server yields. Account emails; setup emails with the invite code each came with (encrypted, and hashed with the same server key, so neither gives the code back without that key); each invite code’s label and release stage; per-code first and last read times and join counts; GitHub user ids and account numbers; keyed hashes of sign-in addresses and of codes still in use; hashed keys, which cannot be reversed; the two-day event log and 30 days of who opened Library files and asked a mirror; the full history of approved marketplace calls (each module’s totals, without who called, are already public in the marketplace listing); what each account gave back per billing period, the policy that decided it, and the release circle it joined through, and whose link brought whom; published Library pieces and shared modules, both public by your choice; inbox public keys; message labels (who wrote to whom and when); which paid pieces each account bought or sold and for how much; post box addresses, passes, and letter fingerprints; senders’ signatures on letters not yet collected (each names the sender’s public signing key); which chat apps are connected to which accounts; messages not yet collected, sealed so only their recipients’ keys open them; each person’s rules for companies’ questions; which questions of a running round each person’s answers counted for, until they are paid; rows of answers from people who chose to give them (some with their names); replies to the market not yet counted (which the server’s own key opens); the market’s letter keys; browser sign-ins still within their 30 days (each works as a sign-in); Library profiles, with the city a member who turned whereabouts on is in now; Library invite codes and who each let in; registered websites and Mirror connection secrets; Stripe subscription and payout-account references; feedback notes from before they were relayed; network addresses in the request counts; the permanent file-view archive; and Cloudflare’s own access logs (addresses and times). It does not yield your private record, unpublished files, or model-provider keys, because those never reach the server.

your account and its keys

The loop on your computer needs no account, and joining never authorizes setting anything up there. Connecting a computer to your account stores two keys and one permission you can remove (system/permissions/people-context), and on a computer with the loop it adds the signed connector and Library method files to system/canon/ where they are missing.

  • The read key sits where your model reads, in ~/alexandria/system/.api_key, and the server accepts it only for looking people up, meaning the member directory, a member’s profile and the files this account may already open, a member’s verified website address, and the list of people this account is already tied to. Every other route refuses it before any handler runs (server/src/read-key.ts), so it can never send, publish, list, register, make a key, or connect a computer, and it stops working when its full key does. A leaked read key exposes only what other people already shared with this account, and whom it is tied to here.
  • The full key sits in the protected folder, ~/.local/share/alexandria/.api_key. The session-start hook uses it, and so do the commands you approve outside your model’s sandbox, which are turning messages on, checking and sending one, sending feedback, publishing a profile or a listing, registering a website, and connecting another computer. In Claude Code, the loop’s setup adds a rule that keeps your model’s file tools and sandbox from reading that file, so each of those acts needs your approval of the exact command. The connector-only client, whose full key sits in ~/.local/share/alexandria-connector/, adds the same rule for that file wherever Claude Code keeps settings on the computer. Other tools, Codex and Cursor among them, get no such rule, a risk named under what it does not protect against.

Connecting publishes nothing, syncs nothing, makes no model, and drafts no hosted profile unless you asked for that page. A computer without the loop can connect too, so someone who skips the rest of the blueprint can still connect. Every new session on a connected computer shows one short note, printed by the signed session-start hook from local files, saying it is connected, where each key is, and how looking up a named person works. Without the loop, the same marked note sits in each coding tool’s own instructions file, where you can read or delete it. Once a day that hook also asks whether your membership is active (every network call), so a direct question about joining is answered from current state, and neither of the two values it keeps is ever printed into a session.

connecting a computer

When your setup request asked for the Connector, joining is offered first thing after your go to the plan, in the same sitting, while the build carries on, and nothing of yours depends on it. The Connector never waits for a part you skipped. After you join, the page copies a short first-person request around a random alex_connect_… code, built in your browser from a fixed template in the same shape as the setup request. The server returns only the code, never prose. The signed instructions on your computer treat the code as opaque data, explain the narrow change, and wait for the exact word connect. The request may ask for the mirror and the alexandria skill afterwards, and anything public still needs the exact word publish for the exact bytes shown.

After your yes, the verifier checks the signed factory/scripts/connect-account.sh before it runs. On a computer with the loop it refuses unless the loop’s verified setup is complete, and on a computer with only the connector it refuses unless that client was installed from a reviewed, signed release. Neither waits for setup to finish, reads any of your private files, or writes anything but the client’s two keys (the read key in its state folder, the full key in its protected folder), the removable permissions/people-context marker it told you about first, and, on a computer with the loop, the signed connector.md and library.md method files in system/canon/ where they are missing. Because the full key is saved in the protected folder, the connector runs through your tool’s approval prompt, outside the sandbox, and inside the sandbox it stops before the one-use code is spent.

The browser carries a one-hour, one-use code, never the lasting key. The server’s database uses up the code in a single step, and the server checks live membership before making a separate key for this computer and its read key. The connector never prints a server response or stores account status. A parser on your computer accepts only the exact key shapes (a full alex_ key and an alex_read_ read key) or the exact flag that a key already exists, and any failure becomes fixed text plus a status code. Signing in never replaces a working key, and when a computer connected stays separate from when it installed (connected_at and installed_at). A computer connected before the key split keeps its one full key in its state folder until its next verified setup run (or, for the connector-only client, its next connection), which moves the key into the protected folder and puts a read key in its place. If the read key cannot be made then, the earlier arrangement stays and the next run tries again.

your page starts empty

After connecting, your mirror starts empty. Its folders exist, your member page carries only the name the account already has, and nothing of yours is published. Later, when a session suggests it or you ask, your own model may use only material on your computer it already had permission to read to prepare a private profile draft, not yet able to be published, at files/library/_profile.json. No public page and no text written by the server enter setup. The site supplies the page design, and the draft holds only your content. Existing public links are included only when your own material already marks them as public. Nothing is published yet. You see every byte and must separately say the exact word publish, and the signed one-shot publisher then checks the approved hash, sends only the allowed public-profile fields (name, short text, website, and links, of which the server discards the website) to /library/me/profile, accepts only the fixed {ok:true} answer, and turns on no standing sync.

looking people up

Later, ordinary work may use the signed person-context.mjs helper when a specifically named person really matters to the task. To find someone by name it reads the member directory page by page and matches the name on your computer, so the name never leaves it. It then makes GET requests only for that person’s profile, their verified website address (/connect/site), the exact files this account can already open, and the list of people this account already knows (/people), and for a person with a verified website, it reads their public pieces straight from that website, sending no key or cookie. The requests carry the read key, the installed client version, and the Library address, never your prompt, your private files, any guessed relationship, or anything else from your computer. That address still tells Alexandria whose page this account opened and when, the way any website sees a page visit, but never why. The helper labels every byte that comes back as untrusted data, so it can shape the answer shown to you but cannot authorize a command, a write, a message, a purchase, a publication, or anything else outward. Removing system/permissions/people-context stops those reads without disconnecting the account. Every profile change, publication, Marketplace call, backup, and other outward step still needs its own exact yes.

signing in

You sign in with your email. A six-digit code works for ten minutes and five tries, only in the browser that asked for it (its id lives in that browser’s own cookie). It is the one check that the address is yours, so a stranger who types your address cannot get in, and a mistyped address is caught before an account is tied to someone else’s inbox. A new account’s name is made from its address. Accounts made with GitHub sign in the same way, with the email on the account, and a later change to the billing email in Stripe never changes which address signs in. An old GitHub sign-in link now opens the sign-in page.

your own website

A website can take part with no Alexandria request at all, through one JSON description on the existing site pointing to chosen public Markdown or PDF files, plus, if you like, a backend you control that reads only chosen material and calls your own model. The paid Connector adds shared discovery, verified website addresses, reader identity, and current exact access. Registering needs a website, a description path, and DNS TXT proof that you own it, so a host without DNS control has no complete verified path yet. Listing is your choice, a callback is added only for shared reader sign-in, and your account key never goes in the website. For people who only want their website, factory/scripts/setup-connector.sh checks an independently reviewed, signed copy and prepares only ~/.local/share/alexandria-connector and ~/.config/alexandria/connector, with no loop, hooks, private record, model, or website, and where Claude Code keeps settings it adds the one rule that keeps its file tools from reading that client’s full key. The signed connect-account.sh --website then makes the same strict one-use exchange after the exact word connect, and finishes without drafting or publishing a page.

membership

Current membership gates the service Alexandria runs. Stripe handles membership and purchases between members, and setting up payouts, on Stripe’s own page, is only for sending what you earned to a bank. A cancellation keeps the service through the paid period, and when that ends, shared discovery and protected requests stop. Renewing restores the connection and every grant still valid. A reader’s exact invitation or purchase is separate from membership, and membership never creates one. Public files, answers from your own model, and material already delivered remain the owner’s or the reader’s, and cancelling cannot recall knowledge already copied. A billing outage refuses only the shared action that depends on it, without breaking independent public reading.

why your keys are safe

  • The server keeps each key only as a SHA-256 hash, never the key itself.
  • The account record is encrypted at rest with AES-256-GCM.
  • The full key is handed over once, to the connecting computer’s signed helper, in exchange for a one-use connection code, and never appears in a browser, an email, or anyone else’s records.
  • Stripe knows your account by its name here, not by a key.
  • DELETE /account with your full key cancels any Stripe subscription (billing anything you bought on it first) and removes your account and both keys of every computer, your setup email, your Library profile, published files, invite codes and the reader grants you gave or received, your registered website, connected chat apps, your sign-in address and names, your purchase and sale records, module-call records, shared modules, inbox key, post box, and every sealed message and label to or from you, your rules for paid questions, any draft round naming you, and any answers kept with your name, what you gave back, whose link brought whom, the invite opens counted for you, and your membership as last confirmed. Logged route events expire within two days and Library access events within 30, the archive of file views stays (it is the record nobody can quietly change), and the maintainer’s nightly copies drop it within seven nights.

messages

Messages are the other half of the Connector. What a person’s Mirror cannot answer can reach that person. Your own tools read their Mirror first, so nobody’s model is asked and nobody else’s tokens are spent.

Turning messages on is a grant only your own step outside the sandbox can record (permission.sh grant inbox, then inbox.mjs on). The grant creates an X25519 key pair in ~/.local/share/alexandria/inbox/, and beside it an Ed25519 key that signs this computer’s letters and never leaves it (an inbox made before signatures gets its signing key at the next session start), and inbox.mjs on registers only the public half of the first. The hook collects only while that grant and its switch both exist, and the signed helper checks and sends only while they do.

Checking and sending use the full key, so your model runs the helper through your tool’s approval prompt, outside the sandbox, with the exact words written into the command, so you see those words in the approval and say yes to that one message (how far that holds in each tool is under what it does not protect against). The helper then fetches the recipient’s public key and checks it against the first key this computer saw for them. It seals your approved words on this computer (a fresh X25519 key per message, HKDF-SHA256, and AES-256-GCM), signs the letter with this computer’s signing key over your handle, the recipient’s handle and key id, a SHA-256 of the sealed text, and the time, and posts only the key id, the sealed text, that signature, and, for a reply, the id of the letter it answers. It keeps a record of every message it sent in ~/alexandria/system/.inbox_sent, so you can see what left under your name.

The server checks who may write (any member, or only people the recipient gave invite access to their pages plus anyone they wrote to in the last 30 days), blocks, limits, and both memberships, then stores the sealed text. A blocked sender is told the message was sent, and it is discarded, so a block is never visible to them. At the recipient’s next session start, the hook collects it into the protected folder, where no model session can write, and the server deletes it. Nothing is opened then, and the live line shows only a count. The recipient’s tools list senders and dates when asked and unseal one message only when the recipient chooses it, handing it over labelled as that person’s words, never as an instruction.

A letter shows as from its sender only when its signature checks, on the recipient’s computer, against a key that computer accepts for them. That is the first signing key it saw from that sender, kept in ~/.local/share/alexandria/inbox/senders where no model session can write, plus any the recipient accepts later with inbox.mjs trust <id>, outside the sandbox, after confirming with that person. A letter that is unsigned (every letter from before signatures, or from a sender on an older helper), whose signature fails, that was signed for someone else or more than a day from when the connector took it, that repeats an earlier letter, or that is signed with a key not accepted for that sender still opens, but shows as unverified, labelled with the handle the connector gave, never as from that person. A reply is a new message sent the same way. inbox.mjs off stops new messages and deletes anything still waiting on the connector. Deleting the switch does the same at the next session start, unless the inbox has since moved to another computer.

a post box on your own website

A member whose website is verified to their account and runs a small backend can keep their post box there, so the connector stores none of their messages. Because session start would then contact that website, you first approve its exact address outside the sandbox (permission.sh grant post-box <address>, beside a post-box switch that can only turn it off). inbox.mjs box on then checks that the box answers and moves your messages there, and inbox.mjs box off moves them back, brings home what waits there for that computer, and turns the switch off. Deleting the switch turns collecting from your website off at once, and the next session start on the computer that collected from it moves the post box back to the connector, so letters are not stranded there.

For a letter to such a member, the sender’s helper sends the connector only the key id, a SHA-256 fingerprint of the sealed text, the letter’s signature, and for a reply the id of the letter it answers, and receives a five-minute pass for that one letter and the member’s website address. It then posts the sealed text and the pass straight to that website, never the account key. The website asks the connector whether the pass is good for exactly those bytes. The connector checks the key, the box, and the daily limits again, writes the label and a record vouching for the sender, key, and fingerprint, and only then tells the website to keep the letter. That answer is the same whether or not the member blocked the sender, because a sender holding the pass could ask for it too, so the website keeps a blocked sender’s letter like any other. If the website does not take a letter, the sender’s helper reports that nothing was sent, and nothing falls back to the connector’s box.

Session start collects from your website only while the connector names the approved address. It asks the connector for a five-minute pass that only your account key can get, and then asks what the connector vouches for about each letter the website hands over, with the sender’s signature. It keeps only letters whose sealed text matches the fingerprint the connector vouched for, takes their sender and date from the connector and never from the website, and drops letters from anyone you have blocked, then or since. It marks the kept letters collected and then removes everything the connector answered for from the website. The website holds no account key and answers only in fixed codes.

chat apps

A chat app can connect to the same account without anything running on your computer, wherever that app lets you add a custom connector on your plan. You add the address https://api.alexandria-library.com/mcp in the app’s own connector settings (or https://alexandria.place/chat copies a request that walks the app through it), sign in with your email, and approve a page on alexandria.place that names the app, where it returns to, and what it may do. You can connect before joining. The tools that need membership check it each time they run, exactly as the website does, so finding people, the directory, your people, reading members-only pieces, and changing your page start working once you join.

The app holds its own narrow sign-in, never your computer’s key, and nothing is pasted into the chat or saved in your folder. The server keeps the app’s registration, your approval, and SHA-256 hashes of a one-hour access credential and a 30-day refresh credential that changes each time it is used. Reusing an old refresh credential revokes that connection, and removing it at https://alexandria.place/chat/connections or deleting your account stops access at once, while cancelling stops the tools that need membership once your membership ends. These credentials work only at the connector’s own address, never at any other route, even beside a signed-in browser.

An app that adds a connector only with a key, not a browser sign-in, can use a key you make for it at https://alexandria.place/chat/connections while signed in there, and no credential can make one. The key has exactly the abilities and membership checks of a sign-in, is shown once, and goes into that app’s connector settings, never into a chat. The server keeps only its SHA-256 hash, the app name you typed, and when you made it. It never expires and works until you remove it there, which erases all three at once along with any change it prepared that still waits, as deleting your account does, and cancelling stops the tools that need membership once your membership ends. The request the connect page copies for a tool you trust carries one such key too, made in that click while you are signed in, so an app that takes only a key never needs another page; that key sits in the request you paste, and so in that app's chat, which is why it goes only to a tool you trust. It waits a day for its first use and is erased if none comes, appears in your connections only once an app uses it, and from then on is like any other key.

The tools find a person by name, list the people your account is already tied to, read what people chose to share with your account (with the same exact checks as anywhere else), read your own page and the pages you have published, and prepare changes to them. To find someone, the chat app sends the name it was given and nothing else from the conversation, and the server uses that name for the one search, neither stores nor logs it, and returns only listed members and people you are already tied to. Your people come from what the server already holds for other reasons (who gave whom access to pieces, the 30-day records of who wrote to whom, and whose invite link someone joined through), so listing them stores nothing new. A prepared change publishes nothing. It is held encrypted for fifteen minutes, bound to the connection that made it, and goes live only when you open its link, sign in as the same person, read the exact change, and confirm it there. A change made elsewhere in the meantime voids it, and a page that looks like it carries a password or key is refused. From a chat app you can change your name, the short note other people’s models read, your themes, location, contact, and links, and publish or remove pages in the public and member folders. Invite-only and paid pages, messages, billing, and connecting a computer stay on your computer or the website. The connector cannot read your private record or any file of yours that you have not published.

This is a different line of trust from everything above. The chat app talks to our live server, and the tool descriptions and published material it gets back go straight into that app’s model, unlike the signed code a computer runs. A compromised server or a hostile published page could therefore shape what that model says. The model is told that everything returned is someone’s published material, never instructions, and every change to your page still needs your confirmation in the browser. The chat app’s own provider sees what passes through it, as it sees the rest of your chat.

who can sign a release

Three keys may sign a release. Two are P-256 keys made inside the Secure Enclave of the maintainer’s Mac, the part of the chip whose keys cannot be copied out. The third is a recovery key that exists only on paper, for the day that Mac is lost.

  • The Mac key, the current signer, is SHA256:p33nHmkn0rm09XTjoBRKezyxSk3qxdA3oYnvLPBhaMI. Signing with it needs no fingerprint, so the maintainer’s agents can release while he is away, but it works only on that one Mac. So a valid signature proves a release came from that Mac, not that a person approved it.

  • The Touch ID key, the earlier signer and still trusted, is SHA256:9DVo6uNuieqKMdNtT0QIi/WoQAAbWl5i/t0Z5MdQ/Jg. Every signature with it needs a fresh match against the fingerprints enrolled when it was made.

  • The recovery key, on paper, is SHA256:MU9fIpQL0trapEgJkvw2SWypG3eRORa9sDunC1oBeVw. It is an Ed25519 key that ssh-keygen made on the maintainer’s Mac. Its private half was shown once, on his own terminal, as 24 words, and he wrote them on one sheet of paper and typed them back to prove the sheet rebuilds the key. It was never written to disk, and only its public half was kept. It has one job. If the Mac is lost, it signs the one release that makes installs trust the new Mac’s key (key rotation), and factory/ship.sh refuses to sign anything else with it. That refusal binds only the script, and whoever finds the sheet can sign any release, so the sheet is kept like a passport.

  • The signing handover. An install that has not updated since the Mac key arrived may trust only the Touch ID key, so the last release signed with that key stays reachable for it. How an older install moves across is under key rotation.

  • An independent witness. GitHub publishes all three keys as the maintainer’s signing keys at https://api.github.com/users/benmowinckel/ssh_signing_keys. GitHub serves that record, not Alexandria’s source route, so a compromised Alexandria server cannot change it. Work out the SHA-256 fingerprint of each key there and compare it with the ones above.

  • The public keys, exactly as installed in ~/.local/share/alexandria/allowed_signers.

    alexandria-payload-signing ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBETzcr+XjCojo7y6s+JU8UwqkOtzIv3h9kEQI/ef9/nuGolyXvLF8WXkoEDwFc3zkXxTbZ+TVWI5Uq0fgMxHvjM= alexandria-touchid
    alexandria-payload-signing ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBBnntuFCVzj3xypcrpVPv7nToNOL/i4Kb/9SWKc6p2azYGjYhFokZj+wou53Nhnf4Bc11ZIDoaukrDuKnqrLYVQ= alexandria-mac
    alexandria-payload-signing ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILT3KGAuFErhe7BWi/pfSfryQtgxnNdraOupZUMQ0WL9 alexandria-paper
    
  • The private keys. The two Mac keys are hardware that cannot export them, with no passphrase, cached copy, CI secret, or backup that can sign. The recovery key’s only copy is the sheet, which is on no computer, in no sync service, and not in this repository. So a stolen GitHub login, a compromised server, or anyone without that Mac or that sheet cannot sign.

what is signed

A single manifest, factory/manifest.txt, lists a signed release time stamp that only ever goes up and the SHA-256 of every tracked file under factory/. factory/ship.sh takes that set from Git itself, so there is no hand-kept list that could forget a new script, prompt, template, hook, or setup file. Only the generated manifest and its signature are left out. The version people see, such as 0.22.0, is written under each release's time stamp in the signed change list, factory/changes.md; the time stamp, never the version, is what every check compares.

On a first install, every fetch is pinned to the reviewed commit through the invite-gated source route. Setup then checks the manifest before fetching any factory file, and refuses any file missing from the manifest or not matching its signed hash. On every later update, the verifier already installed works out the one exact commit of the release your invite code is on (releases in stages) and checks setup.sh from that commit before running it. The website and the API are never trusted to say what code runs.

<sha256>  factory/hooks/payload.sh
<sha256>  factory/hooks/shim.sh
<sha256>  factory/setup.sh
<sha256>  factory/canon/foundation.md
<sha256>  factory/canon/axioms.md
<sha256>  factory/canon/methodology.md
...
<sha256>  factory/skills/claudecode.md
<sha256>  factory/scripts/install.sh
<sha256>  factory/scripts/verify-fetch.sh

The manifest is signed inside the Secure Enclave of the maintainer’s Mac or, for a recovery release, by ssh-keygen with the paper key (factory/manifest.txt.sig), in the namespace alexandria with the identity alexandria-payload-signing. The result is a standard SSH signature, so anyone can check it with ssh-keygen, and the Apple-only signing tool exists only on the publishing side. Each accepted release version becomes a floor on your computer. An older valid manifest is refused, so going back is shipped as a new release signed going forward, never by replaying old bytes.

how updates reach you

This is the most important property to understand. The hook program that runs is the one pinned on your disk, nothing updates itself, and no code runs before it is checked.

The starter at ~/.local/share/alexandria/hooks/shim.sh is installed by setup.sh outside your folder and refreshed only by a verified update you choose. Sessions never fetch it again. At every session start (Claude Code and Claude Desktop’s Code tab reach it through their settings entries, Codex through its hooks, and Cursor through its Python files), the starter does two things.

  1. It runs only the hook program pinned on your disk (~/.local/share/alexandria/.hooks_payload), and only if that exact file has passed its check. The program, the accepted manifest, the version floor, the mark that setup completed, and the recorded hash all stay outside ~/alexandria, the only Alexandria folder your model may write. When the program is new or changed (a fresh install, or an update you applied), the starter fetches factory/manifest.txt and its .sig over HTTPS, checks the signature with ssh-keygen -Y verify against ~/.local/share/alexandria/allowed_signers (the three public keys, which every verified setup run writes), and compares the program’s SHA-256 with the manifest’s entry. If they pass, the hash is recorded beside the program and it runs. If they fail, the starter refuses to run it, with a loud warning in your model’s context, an entry in ~/alexandria/system/.alexandria_errors, and a bare session (your constitution only, and no calls out).
  2. It checks for updates only if you turn that on, and only to tell you. Setup leaves hooks/auto-update absent. If you separately create it, the starter fetches the current manifest and checks its signature. Session start compares only signed hashes with the files on your computer and writes fixed wording, on your computer, naming a module the signed manifest lists. It never downloads new method files or writes a remote difference into your model’s context. The one piece of release text it shows is in the notice for the hook program, naming the version installed here and what changed since, under the version each change came in, taken from that release’s signed change list (factory/changes.md), used only when the file matches its hash in the verified manifest, and cut to the first sentence of at most eight changes, with control characters removed. Nothing is applied.

So the code that handles your sessions is exactly what you approved, the hook program pinned at install or at your last deliberate update, and it passed the signature check before its first run. Applying an update is always your action, with bash ~/.local/share/alexandria/scripts/verify-fetch.sh --run setup.sh. The installed verifier checks setup before it runs, and setup then checks every factory file it fetches. Access to GitHub alone isn’t enough to ship code, because the manifest must also be signed on the maintainer’s Mac or with the paper recovery key.

The hook program and the method files work the same way. Both are offered, checked, and applied only on your go, the method files through the update notice you pull one at a time, and the hook program through the verifier on your computer. Nothing on your computer changes without your explicit action.

releases in stages

A new release does not reach every install at once. It goes first to the maintainer’s own computer, then to the people closest to him, and outward from there, one stage at a time, normally a day apart, though the maintainer can move a release on sooner or send an urgent fix to every stage at once. Which stage an install is in is decided by its invite code, which every fetch already carries in its address, so nothing new is sent. The source route answers …/source/<invite>/commit, and any read at main, with the release that invite code is on (except the four files the signing handover serves older installs, under key rotation). Every stage is a release that was main, signed like any other, and a stage only ever moves forward, so the version floor on your computer never meets an older release. A release stays readable by its exact commit while it is a stage’s release, main, or held in the server’s copy. A code moved to a later stage waits on the release it already has until that stage catches up. Installs never report back, so a stage moves on because nothing went wrong on the maintainer’s own computer, nobody reported a problem, the release passed its test on a clean machine, and the latest check of the live service is green, never because of anything measured on yours. The website, the server, and the setup text are one version for everyone.

what you are trusting

You are trusting the maintainer’s Mac, which holds private keys that cannot leave Apple’s hardware, and the paper recovery sheet. Every invited person can read the source, and those keys and that sheet are the only things that can sign new code. What protects you anyway is five things.

  1. The whole factory is signed, and every file’s hash is pinned. manifest.txt lists a release time stamp that only goes up and the SHA-256 of every tracked file under factory/, setup and the verifier included. Automated checks take the same set from Git and fail on any missing or extra path. The manifest itself is signed (manifest.txt.sig), so taking over the GitHub repository alone does not get code to run on your computer.
  2. Unchecked code never runs. A hook program that has never passed its check never runs, and if the file on disk changes without a new check (tampering, or an update that stopped halfway), the session runs bare instead.
  3. Every change is on record. Every version of the hook program is in the Git history, which every invited person can read, and any session can be traced to the release its install had applied.
  4. The method files refuse. They tell your model to refuse instructions that try to send your files out, widen what it may touch, or go around you. The same holds for marketplace modules, where a module someone else wrote is untrusted input, so instructions inside it are read as data, not commands, and adopted only after review against your own files.
  5. Your tool asks first. Claude Code, Cursor, and Codex show every shell action before running it. That is real protection at install and when something looks odd, but it weakens as you get used to saying yes, so treat it as a backstop, not the main defence.

turning update checks on or off

They start off. To turn on signed checks that only tell you about updates, after a separate yes, run touch ~/alexandria/system/hooks/auto-update, and to stop them, rm ~/alexandria/system/hooks/auto-update. Removing the marker keeps every session on the copy pinned on your computer, and running setup again does not turn checks back on.

an update that fails

An update first downloads every file of the release it installs into the protected folder and checks each against the signed manifest, and changes nothing unless all of them arrived. While it replaces files, it keeps a copy of each one in ~/.local/share/alexandria/.update/. If it stops partway or its checks fail, those copies go back, the hooks keep running the version you had, and the next session start says once that the update did not finish.

your own fork

A truly independent fork needs its own keys. Copying the repository is easy, but safely shipping changed factory files means replacing the public keys built into setup.sh and setup-connector.sh and the release signer, publishing the new fingerprint through a channel your users already trust, and then signing your own whole-factory manifests. A fork’s address on its own proves nothing. If all you want is for nothing to change, freezing on your verified copy is the safer and smaller move, because your already-checked files keep running with no way to update at all.

the first install

The repository is private, and every download goes through a code, either a private invite or, while the maintainer has setup open to everyone, the one code the start page gives every visitor. The full source is readable by anyone who can start setup, at api.alexandria-library.com/source/<invite>/.

Start at alexandria.place/start. Enter your private invite code if it asks for one, then your email, which unlocks the setup request and sends you a copy, and then set up with an agent on your computer, such as Codex, Claude Code, or Cursor, including one you reach through Remote Control. Anywhere else, the request first names where to paste it instead, and only if you answer that it should go on there does Claude Code Web or another trusted cloud agent work from committed files in a repository you deliberately selected, writing its own branch. On an iPhone the start page adds the Shortcut directly, and on a computer, setup’s list of what is left gives alexandria.place/shortcut to open on the phone.

The request you send gives no command to run, no fingerprint, and no checking recipe written by us. It says, in your voice and read by you before you send it, that you want to set up your own personal data system from the blueprint at the invite-gated source address, through its overview, https://api.alexandria-library.com/source/<invite>/main/README.md. It asks the agent first to show you the ideal system for you, what you already have, the gaps, and the plan, and to wait for your go, which then covers the whole plan. It names the lasting effects (hooks in your coding tools, copies of sessions kept in ~/alexandria, and write access to that folder) and exactly what needs no check-back after that go (reading the project, looking at how your computer is set up, creating what the system needs, adding instructions and skills to your tools, and running the setup script once the agent has read it). It keeps everything you use working, keeps your material private, and tells the agent to stop if anything looks unsafe. It also asks, in your own message rather than by the repository’s authority, for the connector and the exact join link. Your message is the authority and the go-ahead, every Alexandria file stays reference material to evaluate, and the agent chooses its own way of checking.

A careful agent works in two phases. First it reviews, reading only. It turns …/source/<invite>/commit into one full commit hash and reviews the source at …/source/<invite>/<hash>/ without running code or creating anything on your computer. Only when that review finds nothing unsafe does it act on your go-ahead. It fetches that same revision’s factory/setup.sh and factory/scripts/classify_install.sh from …/source/<invite>/<hash>/ into a folder with the same layout, and runs that setup.sh with ALEXANDRIA_SOURCE_COMMIT set to the hash and ALEXANDRIA_INVITE set to the invite code. The whole revision is also at …/source/<invite>/<hash>.tar.gz, over 20 MB, most of it the website’s images. Setup fetches every other file through the same route, pinned to that hash, and checks the signed manifest before installing anything. The route serves only released revisions, never an unmerged branch.

On the computer route, if the agent judges the project safe, it shows you a short plan, and your go to that plan is its go-ahead to fit the blueprint around your existing setup, keeping everything you use working. Claude Desktop’s Code tab set to Local runs on your Mac. Claude on the web or your phone can reach those same files only when it is controlling a Claude Code session already running on your computer, where the screen is remote but the files and everything that runs stay on the computer.

On the cloud route, the agent first evaluates only the invite-gated source, and then separately asks permission for the exact private sources and destination. Only then may an approved provider use the repository you selected for it. It can read committed files and write only to its own branch, and it cannot see files on your Mac that aren’t committed, use tools only your Mac has, or make its branch live by itself. Your trusted agent on your computer brings its work back and closes it. If you do not trust a provider with your own repository, give it an Airlock copy instead (a tool you are only trying).

A regular chat can use an approved folder it can really write and read back. After you give permission for the named personal sources and the destination, it stores the reviewed _start guidance beside your private record and runs the loop it can by hand when hooks are missing. It reads its writes back, keeps the exact location of the record for later chats, and labels anything it cannot confirm, such as whether instructions loaded or the record can be found, as unverified. A failed save is reported as a failure. The private loop does not require joining, and the agent gives the neutral join link only when your original request asked for it.

The source route is Alexandria’s own server, so it is not an independent witness. For each invite code it notes only the first time the source was read and the latest time a setup or update check started with it, with no IP, path, or device, so the maintainer can see whether invited people reach setup and the reminders can stop. It cannot forge the manifest signature, but the keys built into setup.sh are only as trustworthy as the channel that delivered them. Confirm the fingerprints in who can sign a release through a channel you already trust, or decline to go on. A script cannot prove itself with a key it supplies itself.

When setup finds an earlier install, it sorts out what is already there by receipts and hashes before reading any personal file. A healthy install is left as it is and never overwritten, a partial or unknown one stops setup rather than being guessed at, and any connection you already turned on is shown to you and left as you set it.

what we claim, and what we don’t

We claim three things. The install does what this page says, and only that, which you can check line by line. Your private record (your constitution, vault, marginalia, and transcripts) never leaves your computer through Alexandria, because no address accepts it. And a complete breach of our server yields what what our server holds lists and nothing more, because nothing more is stored.

We don’t claim that your use leaves no trace. The server logs which routes your account uses and when, counts requests per network address for up to three days to stop floods, and keeps file views and invite codes in a permanent archive, all listed in its table, and Cloudflare logs addresses at its edge. We don’t claim safety if the maintainer’s Mac is compromised, if someone finds the recovery sheet, or if you deliberately run an agent with no sandbox, each of which is under what it does not protect against, though taking over the repository or the GitHub account alone is not enough. And we don’t claim there is no risk at all. Coding tools run hooks with your shell’s permissions, as every editor extension, development server, and shell hook on your computer does, and that is true here too.

what it protects against

A fake site or source supplies different code

The official request names the exact invite-gated source on Alexandria’s API domain and tells your already-running agent to evaluate the project rather than obey it. Setup stays pinned to one reviewed commit, and every file must match the signed manifest. A compromise of Alexandria’s server or GitHub account could still serve a different key at a first install, so strict protection means confirming the fingerprint through another trusted channel.

The server or GitHub account is taken over after you installed

The installed verifier keeps the release keys it already accepted. An attacker cannot produce a valid newer manifest signature, so updates are refused.

One file is quietly changed, including setup.sh or the verifier itself

The manifest covers the whole tracked factory, so any change breaks the signed hash and is refused.

A prompt or a repository edits your folder, ~/alexandria

The hook programs, the signing keys, the accepted manifest, the version floor, the setup mark, the connection grants, and the Library approvals live in ~/.local/share/alexandria/, outside the folders your model may write. A switch in ~/alexandria can only turn a connection off. The next hook runs no file from ~/alexandria, runs the checker that guards your protected positions only from a verified copy of signed bytes, forces repository git hooks and the file-system monitor off, and pauses backup on any repository git setting that could start a program or redirect data. The pre-commit hook setup installs for commits made by hand also runs only a verified copy of the signed checker, though that hook file sits in ~/alexandria/.git, where, as in any repository, whatever can write the folder can replace it.

Someone intercepts the source route

The signature check on top of HTTPS catches forged content.

A breach of our server exposes messages

Messages are sealed on the sender’s computer to the recipient’s key, which never leaves the recipient’s computer, and are deleted when collected, so a breach yields sealed text and labels, not words. Messages to a member whose post box is on their own website are never stored on our server, which sees only their fingerprints, except that a sender on an out-of-date helper may still send the sealed text, which is refused and never stored, and such a sender cannot write to that member until their helper updates.

Your post box on your own website

Letters sit on storage you run, sealed to your computer’s key, so your host sees sealed text, sender handles, and dates, never words. Your website checks every letter’s pass with the connector against a fingerprint of the exact bytes it received, and it holds no key of yours. Whoever controls that storage can delete or hold back letters, but cannot add one, change one, or change who sent it, because your computer keeps only letters whose sender and fingerprint the connector vouches for. Letters from someone you blocked wait there like any other until your computer drops them unopened. If your website is down, people are told their message was not sent. Session start contacts only the exact address you approved outside the sandbox, so a file your model can write, even a swapped account key, cannot point it at another website. A compromised connector could still issue passes and vouch for letters, so it could let through someone you blocked, or deliver a letter it wrote itself, which shows as unverified unless it is the first signed letter your computer sees under that handle (the first-contact risk below).

Writing to someone whose post box is on their website

Your computer contacts their website directly, so that website sees your network address and when you wrote, as it does when your agent reads their Mirror. It receives only the sealed text and a pass for that one letter, never your account key, and the helper looks up its address once, refuses addresses that aren’t public and any redirect, and shows you only fixed words for its answer.

A stranger flooding someone’s inbox

Only active members can write. The recipient chooses any member, or only people they gave invite access to their pages plus anyone they wrote to in the last 30 days. Blocks are silent, daily, per-recipient, waiting, and size limits apply (server/src/inbox.ts), and a sender must have their own messages on, so every message can be answered.

The maintainer’s Mac is lost or destroyed

The paper recovery key signs one release that trusts the new Mac’s key and drops the lost Mac’s keys, so an install that has applied any release since the recovery key arrived moves across with an ordinary update its owner applies. factory/ship.sh --recovery rebuilds the key from the typed words only for that signature and holds it in a private ssh-agent started for it and stopped right after, so it never reaches the disk, and it refuses a release that changes anything but the lists of trusted keys.

An old but valid signed release is replayed

The signed release time stamp only moves forward, and each computer stores the highest one it has checked and refuses anything lower.

A program that can write a shared temporary folder, a sandboxed model among them, swaps a file between its check and its run

Setup, an update, and the connector client keep every file they check and then run in the protected folder, which your model cannot write, so the bytes checked are the bytes that run. Their inline programs, and the ones the session hooks run with the account key, reach Python and Node through a pipe, never a here-doc, which macOS’s bash 3.2 writes to a shared folder first.

what it does not protect against

The maintainer’s Mac is compromised

The private keys still cannot be exported, so an attacker must act from that Mac. Code running there as the maintainer, including a misled agent, could sign a release with the Mac key without any prompt. This is accepted deliberately, because a fingerprint prompt only ever guarded against code already on that Mac, and routine releases get approved without reading every line. The automated tests on GitHub are no barrier here, because code acting from that Mac can change the tests a release is judged by or push main directly. What still holds is that an install applies only a signed release, and only when its owner says so. If this gap matters to you, freeze on your verified copy. That Mac also keeps the nightly copy of the server’s data (what our server holds), so whoever controls it also gets what a complete server breach yields.

The maintainer’s Mac is lost before an install trusts the recovery key

An install that has not applied a release since the recovery key arrived knows only the lost Mac’s keys. It keeps running its pinned version, and moves across only through the independent first-install process, accepting the announced new fingerprint, because the old software cannot quietly switch to a new key.

Someone finds or reads the recovery sheet

The 24 words are the whole key, so whoever has them can sign a release every install accepts, from any computer and with no prompt. The defence is physical. There is one sheet, kept offline like a passport, never photographed, copied, or stored, and typed again only on the new Mac that needs it. The key was made on the maintainer’s Mac, so code running there as him at that moment could have read it, as such code could use the Mac key anyway. If the sheet is lost or seen, a release drops that key and a new sheet replaces it, and each install keeps trusting the old key until its owner applies that release.

The maintainer ships harmful code on purpose

Anyone who can start setup can read every line before anything runs, and every release is signed with a hardware key on one Mac or, once, with the paper recovery key. His reputation and the law are the structural deterrent, the same as for every maintainer of a command-line tool.

Alexandria’s server is compromised

It can offer an older signed release instead of the newest one, and since an install refuses anything older than a release it has already accepted, the effect is a delayed update, never a rollback or unsigned code. It can reject an outward action, lie through a fixed success or failure status, tie a key to the wrong account or disable it, mishandle bytes you chose to publish, or return false or hostile Library content. It can also give a false membership answer, so /a shows or hides the invite wrongly or carries another name in the link, but that answer is only a true or false and a name of lowercase letters, digits, and hyphens. Connecting and session start still accept no server prose. The people-context helper sends no prompt or private context, uses GET only, checks the API’s address and the shape of every answer, and labels every byte that comes back untrusted. The honest remaining risk is that hostile published writing may distort the answer you see. It never has authority over a tool action or anything kept.

You bypass the official way in and run code from an impersonator

No shell script can prove itself once it has started. The official way asks your existing agent to inspect first and to stop if anything looks unsafe, with your own message as the only go-ahead, but a person can still deliberately step around that, as with any phishing attempt.

The agent is deliberately run without a sandbox, or runs commands outside it without asking

Every file in the protected folder is owned by the same account on your computer, so a truly unsandboxed agent, or one whose tool approves commands outside the sandbox automatically, could change them and write its own grants. The separation protects the supported setup, where the tool asks before a command leaves the sandbox, and it is not a boundary between accounts on your computer.

Your model can read a key and reach Alexandria’s API directly

The key in ~/alexandria/system/.api_key is the read key, which the server refuses for everything but looking people up, so an agent holding it can read only what other members already shared with this account, and cannot send or turn on messages, publish, list, register a website, make a key, or connect a computer. The full key sits in the protected folder. In Claude Code, the rule the loop’s setup adds keeps your model’s file tools and sandbox from reading it, so sending, publishing, and every other act that needs it happen only through a command you approve outside the sandbox, and the connector’s instructions put a message’s words in the command so that approval shows them. That protection comes from the tool, not the file, and only the loop’s setup and the connector-only client’s add it, each for its own key and only in Claude Code. Other tools, Codex and Cursor among them, get none and let a sandboxed command read files outside the project, and an agent run without a sandbox reads everything. There, an agent that goes looking can read the full key and act as the account, for example turning receiving on and messaging any member who accepts messages with anything it can read, and the helper’s word-for-word approval, grant check, and local record hold only when the agent follows its instructions. Nothing in a hook uses a key your model can write.

A chat app connected to Alexandria hears from our live server

Its tool descriptions and the published material it reads arrive from the server into that app’s model, with no signed code in between, so a compromised server or a hostile page can shape what that model says to you. It cannot change your page without your confirmation in the browser, and it reaches nothing of yours you have not published. Remove the connection at alexandria.place/chat/connections and in the app.

A key you made for an app leaks

A key in use does not expire, and the one in a trusted-tool request also sits in that app's chat history, so whoever holds it can use the connector as you until you remove it, reading what your account can read and preparing changes, which still need your confirmation in the browser. It works nowhere but the connector, and removing it at alexandria.place/chat/connections stops it at once.

Alexandria’s server hands a sender its own key instead of the recipient’s

It could then read what that sender writes next. The helper remembers the first key it saw for each person and stops before sending to a changed one, which also happens when the recipient moves their inbox to another computer, so the agent asks the sender first. That record sits in the sender’s own folder, so it guards against a compromised server, not against an agent that ignores its instructions. A server compromised before someone’s first message to a person is not caught.

Alexandria’s server forges or relabels who sent a letter

Each letter is signed on its sender’s computer, with a key that never leaves it, over their handle, yours, your key, the sealed text, and the time. Your computer shows it as theirs only when that signature checks against a key it accepts for them, either the first it saw from them or one you accepted with inbox.mjs trust after confirming with them. So the server cannot pass off a letter as someone else’s, send yours to another person, or relabel it, though it can still drop or delay letters. A letter it makes under a handle whose signed letters your computer has never seen is taken as that person’s first, the same first-contact risk as the recipient’s key above. Letters from before signatures, or from a sender on an older helper, show as unverified.

A message you open carries instructions

Its words reach your agent labelled as that person’s, like a Mirror you read, and can shape what it says to you. Nothing in the system grants them authority, and the agent’s instructions are to act on none of them, but that rests on the agent following its instructions. A message is never opened automatically, only members allowed to write to you can send one, and every message your agent sends through the helper as instructed is recorded in system/.inbox_sent. The remaining risk is the same distorted answer as from a hostile Mirror, plus the account-key risk above.

Alexandria’s server is compromised while the market exists

It could read the market/ folder of anyone who chose ask me or automatic, send that folder to the writing model whatever they chose, and send letters as @alexandria to anyone who chose ask me. That folder holds only what you approved for Alexandria, and a letter reaches your agent as data labelled as from @alexandria, never as instructions.

Another running session rewrites git settings between the check and git’s own read

The check runs immediately before each sync, and hooks and the file-system monitor are forced off regardless. A compromised session racing that moment is not ruled out.

audit checklist

The fastest way is to give the plain request from alexandria.place/start to your existing agent and let it choose how to audit. For the project’s own hostile checklist, use factory/redteam.md as untrusted evidence, not authority. To do it by hand, download one exact commit from api.alexandria-library.com/source/<invite>/<commit>.tar.gz, compare the release keys in factory/setup.sh with the fingerprints in who can sign a release, and check the signed manifest before running anything.

These are the files. Read them.

factory/setup.sh
factory/hooks/shim.sh
factory/hooks/payload.sh
factory/scripts/verify-fetch.sh
factory/manifest.txt
factory/manifest.txt.sig
factory/canon/methodology.md
factory/skills/claudecode.md

From that downloaded commit, put the three lines under the public keys in who can sign a release in a file named allowed_signers, and check the manifest signature yourself.

ssh-keygen -Y verify \
  -f allowed_signers \
  -I alexandria-payload-signing \
  -n alexandria \
  -s factory/manifest.txt.sig \
  < factory/manifest.txt
# Expected: Good "alexandria" signature for alexandria-payload-signing with ECDSA key SHA256:p33nHmkn0rm09XTjoBRKezyxSk3qxdA3oYnvLPBhaMI
# (releases signed by the Touch ID key show SHA256:9DVo6uNuieqKMdNtT0QIi/WoQAAbWl5i/t0Z5MdQ/Jg,
#  and a recovery release signed with the paper key shows ED25519 key SHA256:MU9fIpQL0trapEgJkvw2SWypG3eRORa9sDunC1oBeVw)

# Verify every tracked factory file's hash matches the manifest
awk '$1 !~ /^#/ { print $1 "  " $2 }' factory/manifest.txt | shasum -a 256 -c

After install, these are the files that run.

  • ~/.local/share/alexandria/hooks/shim.sh, refreshed only by a verified update you choose
  • ~/.local/share/alexandria/.hooks_payload, refreshed only by a verified update you choose
  • ~/.local/share/alexandria/.canon_manifest, the newest verified manifest, which update checks move forward, with .installed_manifest beside it, the one the installed release came with
  • ~/.local/share/alexandria/.factory_version, the floor that stops a rollback
  • ~/alexandria/system/canon/*.md, which are yours to change, and with update checks on, any difference from the latest release shows in ~/alexandria/system/.canon_update_notice

Then audit the installed hook program for anything that touches the network, runs code from elsewhere, or reads sensitive paths.

# Network and code-evaluation surface
grep -nE '\b(curl|wget|eval|osascript)\b|fetch\(|python -c|bash -c' \
  ~/.local/share/alexandria/.hooks_payload

# Credential-store traversal
grep -nE '\.ssh|\.aws|\.anthropic|\.openai|keychain|gnome-keyring' \
  ~/.local/share/alexandria/.hooks_payload

The first should match only the curl and Node fetch( calls listed in every network call. The second should return nothing. The same checks against factory/setup.sh show its fetches at install through the source route and, on a connected computer, its key check and read-key request, all of them listed there too.

removing it

To hide only the /a cue and leave the loop running, run this.

touch ~/alexandria/system/hooks/visible-cue.off

If messages are on, stop them first, while this computer still holds the full account key that turning them off needs; the remover changes nothing on the server, so otherwise the connector keeps taking letters for you, each deleted after 30 days. Stopping them also deletes anything still waiting on the connector, and first brings home what waits for this computer on your own website if your post box is there and this computer approved it.

node ~/.local/share/alexandria/scripts/inbox.mjs off

Deleting your Alexandria account is separate, because it changes what the server holds. It needs the full key, which the uninstaller removes, so delete the account first if you want both.

# Revoke server-side with the full key (~/.local/share/alexandria/.api_key;
# removes the account record, both keys of every computer, marketplace calls,
# published files, what you gave back, inbox key, sealed messages and labels to or from you,
# and any Stripe subscription, and the rest listed under "why your keys are safe";
# endpoint events expire within two days, access events within 30; messages you separately
# chose to send Alexandria are correspondence and are not part of the local loop)
curl -X DELETE -H "Authorization: Bearer $YOUR_KEY" https://api.alexandria-library.com/account

One scoped remover then takes out every hook, instruction block, writable-folder entry, key rule, skill, and optional background job that Alexandria owns, deletes the full account key and every grant from the protected folder, so a later install starts with every connection off, messages and the post box included, and turns Codex’s hooks switch back off when setup’s receipt shows setup turned it on. It checks generic names, such as a and a., before removing them, so a skill that was already yours is left alone. By default it disconnects the loop and deletes none of your files. It leaves ~/alexandria/ (your files, permission markers, and local Git history), ~/.alexandria/ (Cursor’s side folder), the rest of ~/.local/share/alexandria/ (its receipts and trust keys, the record of what Library sync shipped on its own, and, if messages were ever on, inbox/ with the inbox keys and any collected sealed messages, because those keys are never replaced and letters sealed to them would otherwise become unreadable), any iCloud capture folder, the daily iCloud mirror job if you set one up (it is your own backup), any private Git remote or Drive copy, and every skill or hook that isn’t Alexandria’s.

python3 ~/.local/share/alexandria/scripts/uninstall.py

To remove your files too, use the explicit destructive form. It refuses if ~/alexandria is a link and never deletes a remote backup. If the daily iCloud mirror is on, turn it off first, or it will make the iCloud copy match whatever is left in ~/alexandria/files.

python3 ~/.local/share/alexandria/scripts/uninstall.py --delete-files

The same remover takes away the connector-only client, meaning its signed files, both its keys, its people-context switch, its rule in Claude Code’s settings, and the connector note in ~/.claude/CLAUDE.md and ~/.codex/AGENTS.md wherever that note is exactly the signed one (a note anywhere else, such as Cursor’s user rules, you remove yourself). Without the loop, fetch it through the client’s own verifier.

bash ~/.local/share/alexandria-connector/scripts/verify-fetch.sh scripts/uninstall.py | python3 -I -

key rotation

If the maintainer’s Mac is lost, or its keys stop working, the maintainer will do five things.

  1. On a new Mac, create a new key in its Secure Enclave, by building factory/signing/alexandria-sign.swift with swiftc and running alexandria-sign init ~/.alexandria-signing/mac.keyref ~/.alexandria-signing/mac.pub.
  2. Publish its public key on GitHub as one of his signing keys, the independent witness above.
  3. Prepare one release that adds it to the trusted-key lists in factory/setup.sh and factory/scripts/setup-connector.sh, drops the lost Mac’s keys, and changes no other factory file but the fingerprints named in factory/redteam.md and the release’s change list, with the new fingerprint on this page and in the release checks that pin the trusted keys (.github/workflows/structural-release.yml and server/test/stranger.sh).
  4. Sign that release with the recovery key, bash factory/ship.sh --recovery "<what changed>". It asks for the 24 words on that terminal only, rebuilds the key just for this signature, refuses a release that changes anything but those lists, adds no key, or drops the recovery key itself, and keeps nothing.
  5. Announce the new fingerprint on the project website and in the repository.

An install that has applied a release since the recovery key arrived checks this release like any other update, applied by its owner as always, and trusts the new Mac from then on. An install older than that does not know the recovery key. It keeps running its pinned version, and its owner moves across by repeating the independent verification from /start and accepting the announced fingerprint, because the old verifier on the computer cannot quietly grant trust to a replacement key.

If a key is suspected compromised rather than lost, the same release drops it, but whoever holds that key can still sign releases that an install accepts until its owner applies the one that drops it. If the recovery sheet is lost or seen, an ordinary release drops its key, and a new sheet is made with scripts/paper-key.py generate.

Adding a key while the current one is still trusted needs no recovery. A release signed by the Touch ID key added the Mac key, so an install that applied it trusts both, and a release signed by the Mac key added the recovery key the same way. Applying either was your own explicit update, as always.

Verifiers installed before the signing handover read every update from a moving main, and those not updated since the Mac key arrived trust only the Touch ID key. Current verifiers read one exact commit instead. So a moving main is left to older installs, and the source route answers them there with the signing handover. For the four files an older verifier reads on its way to updating (factory/manifest.txt, its signature, factory/setup.sh, and factory/scripts/classify_install.sh), it serves the last release signed with the Touch ID key, named in server/src/invites.ts. Every other path, and every exact commit, is served as it is. The older verifier checks that release as it checks any update. Its setup.sh then works out the one commit of the release the invite code is on, checks that release’s manifest signature against the two keys it carries, refuses anything older than the computer has already accepted, checks the hash of the current setup.sh, and runs it. So a single update, applied by its owner as always, reaches the current release, and from then on the install trusts all three keys and reads exact commits. Until then an older install keeps running its pinned version, and if its update checks are on, it sees the usual update notice. The handover stays in place while older installs still use it, and the server counts its manifest reads without the invite code or anything else about who made them. factory/ship.sh refuses a release signed with any key other than the Touch ID key unless the named handover release exists and checks out with that key, so a later release cannot strand these installs by mistake. The oldest verifiers, from before updates came through the invite-gated source, read GitHub directly, which no longer serves the private repository, and an install that trusts only the Ed25519 key that came before the Touch ID key can verify no current release. Each keeps running its pinned version until its owner sets it up again from /start.

how to think about this

The trust here is visible, not zero, and it has limits you can see.

  • The full source is readable by anyone who can start setup, and every change to the hook program is in the Git history.
  • The signing keys are Apple hardware that cannot export them, plus one recovery key that exists only on paper. Nobody with only the source or the GitHub account can ship factory code, because a release also has to be signed on the maintainer’s Mac or with that sheet. That is a real concentration of trust, and we are not pretending otherwise. How a key is replaced is in key rotation.
  • You can freeze forever on your verified copy, and a changed fork must set up and publish its own signing keys rather than inheriting ours.
  • You can audit again at any time, by comparing your installed hook program with the same file in the invite source (diff) and checking the manifest signature with ssh-keygen -Y verify, as shown above.

What we are claiming is not that no trust is needed. We are claiming you can read every line of the trust you are extending, change the relationship any time, and walk away cleanly with all your files intact.

reporting a problem

Write to benmowinckel@gmail.com about a key you think is compromised, a signature that looks wrong, or any question about this page.