Workspaces are isolated by construction
Every function that touches content takes the owning account as its first positional argument, and every query filters on it. It is positional and required precisely so that a call site which forgot does not compile. An account id never arrives from a client. Server actions resolve the caller from their session cookie; MCP tools resolve them from the connection token, on every request. The practical consequence: there is no code path from one workspace to another. Not through the app, not through an assistant, not through an admin.What an admin can and cannot do
Can
Invite people, revoke invitations, change roles, suspend and reactivate accounts,
delete accounts, reset a member’s password, configure email and billing, change any
instance variable, read the audit log and the error stream, see per-workspace
counts.
Cannot
Read anyone’s brain, resumes, applications, companies, contacts or notes. See a
connection token. Reset another admin’s password, or the owner’s. Act on themselves.
admin_set_user_role over MCP, which checks only that an admin is acting
on a member — so an admin with a connection can promote one. It gains that admin nothing
they did not already have, but it does mean the admin set is not the owner’s to control
alone. Treat an admin account as trusted accordingly.
Every administrative action is written to the audit log with the acting admin’s name,
values included for everything that is not a secret. Secrets are recorded as having been
set, never as their value. Audit rows survive the deletion of the account they describe.
Connection tokens
A connection URL contains a secret token tied to one account. It cannot reach anyone else’s data. It can read and write all of that account’s data.- Treat it like a password. Never in a chat, a ticket, a screenshot or a repository.
- One per client. That is what makes revocation surgical rather than all-or-nothing.
list_connectionsnever returns tokens — they would sit in a transcript forever. The only place anyone needs to see one is the client they are pasting it into.rotate_connectionkills a URL and issues a fresh one on the same row, keeping its name and place in the list. The old address stops working the moment it returns.delete_connectionremoves it for good; a replacement is a new connection with a new URL.
Passwords and sessions
There are two ways in, and passwords always work. An instance with a Google OAuth client configured also shows a Continue with Google button; one without it never does. Matching is by address and an unverified Google email is refused, because accounts here are found by address and anyone can put any address on an unverified Google account. How that is set up, and exactly who it lets in → Passwords are scrypt hashes from the standard library — no native dependency, and the parameters are stored alongside each hash so they can be raised later. Someone who has only ever signed in with Google has no password at all, which is fine — an admin can generate one for them the same way they can for anyone. Sessions arehttpOnly cookies with sameSite: lax, and secure in production. Signing
out and every password reset end existing sessions, so an old browser stays logged out.
Keep me signed in on the sign-in page decides how long one lasts. Ticked, which is how
it arrives, the session runs for 30 days and survives the browser closing. Unticked, the
cookie has no expiry — it goes when the window does — and the row behind it expires after
12 hours regardless, which is what somebody on a borrowed machine is asking for.
One cookie is deliberately readable by scripts, and it is worth knowing what it is. When
Landing page is configured in Admin → Configuration and sits on the same domain as
the app, signing in also writes hired_signed_in=1 to that shared domain, so a static
marketing page can send somebody who is already signed in straight through to the app
instead of showing them the pitch. It is a flag and nothing more: no identity, no token, no
way back to a session. Forging one gets you a sign-in page. Leave the setting empty — the
default — and it is never written.
Sign-in attempts are counted two ways: 8 failures per account and 30 per address,
both in a rolling fifteen-minute window, with a fifteen-minute lockout. The code being
public is not the same as the door being open.
The three public pages
Only two of them are reachable without signing in, and unlisted links are the entire privacy model for both./r/<slug> — a published resume
/r/<slug> — a published resume
Readable by anyone holding the link. The slug is long and random so it cannot be
guessed or walked, the page tells search engines not to index it, and it is listed
nowhere.Your private notes on the resume are not on the page. If the resume has
showPhoto on, your face is — decide that before publishing.Withdrawing destroys the address rather than pausing it. Publishing the same resume
again mints a different URL, so a link you regret sending stays dead./print/<id> — the print page
/print/<id> — the print page
Auth-gated, unlike the other two. It is the US-Letter page your browser prints, and
the fallback where a host has no headless browser.
What connected assistants are told
Before an assistant sees a single tool, it receives a briefing generated for the account it is connected as. It carries the four areas, which tool to reach for first in each, the fact that connection URLs are credentials that must never be repeated where they will be stored — and the standing rules:- Never invent experience, employers, dates or metrics. Everything on a resume must trace back to something in the brain. If evidence is missing, say so and ask.
- Use
append_role_brain_dumprather thanupdate_rolefor new material about a job already on file. update_resumeandupdate_rolereplace what you send. Read first, modify, write back whole.- Prefer a tailored copy over editing a resume already attached to an application.
- A published resume is readable by anyone holding its link, and unpublishing destroys the link rather than pausing it. Say which resume you are publishing, and warn before withdrawing one that may already be out in the world.
- The profile photo is one picture the whole app shares. Only ever use a file or link the user gave you; never find them a picture.
What leaves your instance
Very little, and all of it is optional.
Nothing else. No analytics, no telemetry, no webfont on a rendered document.