What Atrytone does with your code and your data — a plain note
DRAFT, unsigned. Written by the factory on 2026-09-09 from what the code actually does, and re-read against the code on 2026-09-24 (cockpit 325e64c4, worker 2795fea; §4 amended the same day against 720d34b0, and the support line to the operator's "within one working day"; §2's "Messages you write to us" added the same day, against the Write to us change, docs/spec-write-to-us.md); every claim names the mechanism that makes it true, so a reader can check it rather than trust it. Blanks filled 2026-10-04 from live reads and the operator's "Do it!" to the session's picks (docs/legal/blanks-prepared-2026-10-04.md); one remains: the provider's name and postal address. Amended 2026-10-07 against the GDPR specialist's reading (docs/readings/gdpr-2026-10-07.md, Appendix F): D1, D2, D4, D5, D6, D7, D8 and D9 now say honestly what is built and what is still only planned (gaps P0-2, P1-1, P1-2, P1-3, P0-4, P0-8); D3 was left as written, since GN-13 (go-live runbook) has not passed. A person signs it before any partner reads it, and a practitioner reviews the AI Act line before any paying customer (go-live L2).
This is the technical note behind Privacy (https://atrytone.ai/next/privacy), which is the plain version a customer reads and accepts. Here every claim names the mechanism that makes it true. It is not legal advice. Paths are in the cockpit's repository unless marked worker.
Who is responsible for your data: [OPERATOR: the provider's name and postal address]. Questions and requests: hello@atrytone.ai.
1. What we fetch, when, and what we keep from the model
Everything is read through your own GitHub App installation: the worker reads a customer's repository only with a token made for that installation, and reads nothing when there is none (worker src/sourceCredential.js).
- Before any signature. Once a draft epic exists in your project — because you asked for a plan, because a goal you signed plans its next step, or because a criterion you signed stopped holding on your main branch — the factory reads what the plan needs: the issue's text and comments, a bounded listing of the repository, and the files the issue names and the draft cites (
docs/spec-issue-to-draft.md). Your own models write and judge the draft, on your key (§2). And the worker downloads your repository at the commit a signature would pin and runs your own suite once, in a throwaway container, so a plan that cannot pass shows before you commit money (workersrc/basePrecheck.js). - When a run starts, the worker downloads your repository at one commit, as one archive, into a workspace on our server — the whole tree, up to 256 MB by default (
SOURCE_DISK_MAX_BYTES), because your own tests decide what they open. - What the model is shown is bounded separately: at most 1,200 files and 8 MB by default, 200 KB per file (
SOURCE_MAX_FILES,SOURCE_MAX_BYTES; workersrc/treeReading.js,src/workspace.js). A tree bigger than that is read source-first and the rest is listed rather than read — and the run SAYS what it did not read. - Credential-shaped files never reach the model. A path that looks like a secret (
.env, a key file, a credential) is withheld from the model by name: the path stays in the index, so the model knows the file exists, and its body is never put in a prompt (workersrc/secretPaths.js). Since 2026-09-18 such a file IS on the server's disk with the run's working copy, because your installer and your tests may need it, and that copy is deleted when the run ends (since 2026-10-07, gap P0-2 ofdocs/readings/gdpr-2026-10-07.md: workersrc/workspace.jsremoveRunWorkspace, at every final-status write). A daily sweep removes any copy left behind once it is 14 days old (workersrc/diskSweepTick.js). If your suite prints what it reads, the run log carries it, and the model judges read the run log. If a secret is committed inside ordinary source, we cannot know that — nothing here can. - Your code runs only in throwaway containers on our server. Your suite runs with no network; installing its dependencies has network, with install scripts off (worker
src/sandboxSplit.js). One step still runs with network: when a program's entry point needs a missing dependency, the dependency is installed and the program started in one container (workersrc/phases/execution.js). - After the work. Whenever your main branch moves, the cockpit re-reads the files your signed criteria name, to check they still hold. A break files a repair draft for you to sign (
src/routes/api/public/invariants.ts). - The night check (since 2026-10-01). Once a repository is connected, the cockpit reads it at its default branch's head — when it is first connected, then each night, and whenever you press "Check now" — as one archive read in memory; nothing is written to a disk. It looks for keys left in the code, tables strangers can read (worked out from your migrations; it never opens your live database) and parts with published flaws. It keeps only the findings, and a finding never carries a secret's value: one row per reading in the ledger, on your project, so it is kept as §3 says. It makes no model calls (
src/lib/conductor/app-check.server.ts,docs/spec-app-check.md). - What the App may do in the repositories you install it on: read metadata, write contents, write pull requests, write check runs, and nothing else — no administration, secrets, actions, workflows or members (
docs/spec-github-app.md§5). It pushes to its ownfactory/…branches and merges only under your merge rule. GitHub also tells us about pull requests in those repositories; it does not send us your other pushes.
2. Where it runs
- The cockpit and the worker run on one virtual server rented from netcup, in its data centre in Vienna, Austria (read from netcup's customer panel by the operator, 2026-10-04). One worker serves every workspace; there is no worker per customer. The same server also runs the operator's other product, Siire.
- The database is a hosted Supabase project in region eu-central-1 (Frankfurt; read from Supabase's own API on 2026-10-04). Siire's data lives in the same project, in a schema of its own.
- Files the factory keeps for you are stored in the same Supabase project, in one private bucket: a run's transcript, the files and screenshots its box made, a goal's reference picture, a new look's pieces, the parts of code you upload, and the packs of a hosted home. Nobody can list the bucket from outside; the cockpit hands your workspace's members a signed link to one file (probed 2026-09-29 for run and job files and 2026-09-30 for a goal's reference picture: another workspace is refused at all three). Code you upload instead of connecting GitHub is also kept as a git repository on our server's disk: a hosted home. A picture you take away from a job is no longer shown to it, but its file stays in the bucket until your workspace is erased; so does a picture whose upload started and was never finished. This changes: "Take away" will delete the file as well (decided 2026-10-04); until that ships, the file stays as described here.
- Model calls go to the provider YOU chose, and only to it. Your project names its own provider and its key lives in this deployment's vault. A run, and since 2026-09-22 a draft, leases it at start and keeps it in memory for that job only, and the ledger records the lease without the value (
docs/spec-credential-lease.md). Your code and your prompts go to that provider under that provider's terms, not ours. We never run your work on our own model subscription, and since 2026-09-22 that is a mechanism, not a practice: a signature is refused until every role and every fallback binds a provider of your own, and the worker refuses its own endpoints for any workspace but the operator's — at run start, at every call, and at the draft (cockpit14c8b2a2,986fea11; worker4e6743b). - Other services your data passes through: - GitHub: your repository, through the App. - osv.dev, the public database of published flaws in open-source parts (run by Google): the night check sends it the names and versions of the parts your lockfiles pin — never code, never a file's contents (
src/lib/conductor/app-check.server.ts). - Supabase: the database and sign-in. - Resend: sign-up confirmation and password-reset e-mails go out through Resend, the mail service Supabase's sign-in is set to use (read 2026-10-04). It receives your address and the link it carries. - Google: only if you sign in with Google, which is switched off for the alpha. - Cloudflare (Turnstile): the check on the sign-in page, once its site key is set. It is inert today (no key configured) and planned ON for launch (go-live runbook GN-12); once it is on it sees the signing-in visitor's network address and browser details. - Vercel: the name service for atrytone.ai. It answers address lookups for our names; no content passes through it. - Tailscale: GitHub's pull-request notices reach the server through Tailscale Funnel. - The operator's pager: Telegram, with ntfy.sh as the fallback. It receives short notices such as "a run parked" or "someone asked for an invitation" — where the request came in, never the requester's address (invitePagerLine,src/lib/conductor/invite-rate.ts) — and "a customer wrote" — your workspace's name (its short id when the name is an e-mail address), the message's length and when the answer is due, never the words or an address (pagerLine,src/lib/conductor/write-to-us.ts). Both are configured on the server (read 2026-10-04): a notice goes to Telegram, and to ntfy.sh only when the Telegram send fails (src/lib/conductor/notify.server.ts). Word-free since 2026-10-07 (gap P1-2,docs/readings/gdpr-2026-10-07.md§12):tools/factory-digest.mjs --notifypages a waiting question's run id and moment, never its text; when the worker cannot tell whose workspace a run is, it pages that it cannot, never the message (src/notify.js); and the worker's crash alert pages the error's kind only, while the stack goes to the server's own log (src/poller.js). - E-mail from Atrytone itself (notices about your goals) is switched off today. When it is switched on, it is sent through Resend, the service that already sends the sign-in mails. - Mail you send us: an @atrytone.ai address is received by ImprovMX, which forwards it to the operator's Gmail inbox, at Google (tested 2026-09-24 11:35Z: it accepts ops@ and hello@). - Messages you write to us ("Write to us", behind your avatar, since 2026-09-24): kept in our database, in one table (customer_messages), with your address and the page you wrote from. Your workspace's members can read your workspace's messages; the operator reads every workspace's, on their Desk, and a person answers within one working day, by e-mail to the address you signed in with. When e-mail from Atrytone is switched on, the message is also e-mailed, whole, through that sending service to the operator's own address, with your address as the one a reply goes to. Deleted on request: a person deletes the row by hand — there is no button — and it goes with your workspace when the workspace is deleted. The ledger keeps only that a message was written, its length and a 16-character hash of it, and, once it is answered, how many minutes the answer took and whether that was within the day — never the words or an address, so the ledger rows that cannot be deleted (§3) hold nothing of what you wrote. A hash cannot be turned back into the words; it can confirm a guess at a very short message. These ledger rows name your workspace and no project, so your Record page does not show them; your export carries them, and the messages. (src/lib/conductor/write-to-us.ts, migration20260924130000_customer_messages.sql.)
3. What the ledger keeps, and for how long
Every act the factory takes is a row: what was signed, what the policy ladder allowed or refused and why, what each model call cost, what the verifiers judged, what was merged. The ledger is append-only — a correction is a new row, never an edit — because a record that can be rewritten is not a record. That is enforced, not only promised: the database roles the application uses cannot update, delete or truncate a ledger row, and each row carries the hash of the row before it (migration 20260818120000_audit_append_only_chain.sql). The chain's head is published (§4), so a row removed later would show.
- Rows are kept for the life of your workspace; nothing we found deletes them on a schedule. The ledger's own rows are kept longer, as the next point says.
- What a deletion removes, and what it cannot. A person deletes your workspace on your request with one tool (
tools/erase-tenant.mjs); there is no button. The tool refuses until your workspace has been exported in the last 24 hours and nothing of yours is running. - In one transaction, it deletes: - the GitHub installation's link to your workspace; - the invitation that opened the workspace, which holds your address; - the lessons written from your runs; - your epics and projects, with every row that goes with them, including every run and its workspace snapshot (29 tables on the 2026-09-25 reading of the live schema); - your epics' budget reservations and signed ceiling extensions; - every version of your stored secrets; - the workspace itself.
It then counts again, and rolls all of it back unless every count is zero. - Your files, right after the rows. Every file in the bucket whose place starts with your workspace's id or one of your projects' ids is deleted through the storage service. The bucket is then read again, and one ledger row records how many files were planned and how many are left, never a file's name. A file left behind is deleted by running the tool again (--files-only). Tried end to end on 2026-09-30: two files, one a run's and one an upload's, and 0 left (tools/drill-erasure.mjs, 15 of 15). Before 2026-09-30 the tool deleted rows only, and the files stayed. The export now carries the files too, downloaded through the same storage service and written beside the rows, after them. A file that cannot be downloaded, or whose name is not safe to write locally, does not stop the others, but it is named — never silently dropped — and the export says by count and by name that it is INCOMPLETE, and exits failed. Tried end to end on 2026-09-30: the same two files, read back from the export byte for byte, before the erasure removed them (tools/drill-erasure.mjs, 16 of 16, 14:49Z). - Your hosted home, on the server. If you uploaded code instead of connecting GitHub, its git repository on our server's disk is deleted by a second tool, run on the server after the erasure (tools/erase-code-homes.mjs). It deletes a home only when the erasure's ledger row names that project AND the project no longer exists. It refuses a link or anything that is not a plain folder, and writes one ledger row per home deleted: its file and byte counts, never a file's name. Until it runs, the home stays. Tried on 2026-09-30 in a test folder on the server: the erased project's home was deleted, while a live project's home and two links were kept (ledger row 50150). - Your sign-in, if you ask. It also deletes each member's sign-in that belongs to no other workspace, with its mail settings and any request to be let in made from that address. - The ledger records the erasure as one row: ids, counts and moments, never a name or an address. - Tried end to end on 2026-09-25, against a drill workspace holding every one of those kinds of row. Nothing the drill made was left in any of 96 id columns across the database's own (public) tables (tools/drill-erasure.mjs). Since 2026-10-07 the erasure also deletes the sign-in service's own audit-log rows about a removed account (auth.audit_log_entries, by actor id or address; gap P1-1, tools/erasure-plan.mjs AUTH_AUDIT_DELETE_SQL). The drill reads that table too: run on 2026-10-07 11:26Z, 17 of 17 verdicts passed, 105 id columns, and 0 of the drill account's audit rows were left. - Before 2026-09-25 it could not be done for a workspace that had run an epic or stored a key: a budget reservation and its run each blocked the other's deletion, and the current version of a secret could not be deleted at all. Migration 20260925190000_erasure_doors.sql fixed that. Those rows now leave only through this tool's deliberate act; nothing deletes them as a side effect.
What stays: the ledger's rows about your work. The application cannot delete them, and removing them as the database's owner would break the chain whose head we publish. They record what was done — epic titles, criteria, file paths, amounts, verdicts and their reasons, the account id of each member who joined — and can quote whatever was written into a title or a criterion. The deletion itself adds rows: the database records each deleted project and rule with the values it held, as it did when they were set. On an erasure request these rows stay, for as long as the service runs. The basis is our legitimate interest in an intact record (GDPR Art. 6(1)(f)): the record is what lets you, us and anyone you show it to check what was done. What they hold is listed above; secrets are never written into them (next point). (Not yet reviewed by a lawyer.)
- Copies. The whole database is copied once a day to the same server, and the last fourteen copies are kept (read 2026-09-14; the timer lives on the server, not in the repository). Supabase keeps backups of its own: one a day for about the last seven days, on the Pro plan, with no point-in-time recovery (read 2026-10-04 15:42Z: 8 copies, 2026-09-27 to 2026-10-03). A deletion reaches a copy only when that copy ages out.
- Secrets are not in the ledger. Vault values are stored encrypted; the ledger records the NAME and the version of a secret that was used, never the value. A superseded version can be destroyed after 60 minutes by a tool a person runs (
tools/vault-retire-versions.mjs, which only reports unless told--destroy; the window isvault-retention.ts). Nothing runs it on a schedule. - Your code is kept as far as a run needs it, and today a little further. Each run's database row keeps a snapshot of its workspace — the files the model could read and the files the run wrote — so a stopped run can resume (
runs.saved_state; about 950 KB on the row measured 2026-09-09). The snapshot goes with the project. The worker's working directory for a run is temporary (since 2026-10-07, gap P0-2): it is deleted when the run ends, and a daily sweep removes any left behind once 14 days old (workersrc/workspace.js,src/diskSweepTick.js). That includes a run still paused when its workspace was erased: once the cockpit answers "no such run" for it, the sweep removes the copy at 14 days, at most five a day (worker 49285f8). First live sweep 2026-10-07 11:26-11:28Z: 709 removed, 10 more by hand (root-owned files from 09-05), 20 drill directories kept by name. What persists beyond that is the diff pushed to YOUR repository, plus the ledger's record of the act. - Logs on the server. The web edge keeps an access log — network addresses cut to their first three parts (/24, or /48 for IPv6), times, the pages asked for without the
t,codeandstatevalues, the browser's identification — for at most 30 days (deploy/Caddyfile,roll_keep_for 720h, read live asroll_keep_days: 30). Since the reload of 2026-10-07 10:09Z every line is masked (166 of 166 read at 11:40Z); lines written before it hold the full address and age out by 2026-11-06. The worker's log on the server holds run output, including the idea you typed and your answers to a job's questions in full (workerorchestrator.js,buildLoop.js). The server's system log, which holds the worker's and the cockpit's logs, is kept at most 30 days and at most 1 GB (deploy/journald-atrytone.conf, since 2026-10-07, gap P1-3).
4. What we publish
Nothing of yours. The public board on the site's front page (https://atrytone.ai/, fed by /api/open-ledger) and the /proof page publish acts only from the ONE workspace this deployment names (PUBLIC_LEDGER_TENANT, the operator's own), and only from projects switched on for it (public_ledger = true) that are not test drills; with either missing, nothing is served. Your projects live in your own workspace, so they cannot appear there. Every act on it goes through an allow-list (src/lib/conductor/public-ledger.ts): people, e-mail addresses, logins and anything shaped like a key are removed; project and repository names, epic titles, amounts and pull-request links are shown.
- Drills never. Since 2026-09-24 the door refuses a drill project whatever its switch says (
PUBLISHED_PROJECT, cockpit720d34b0, finding C66). - Public repositories only. Only projects on public repositories are switched on. The door itself does not read a repository's visibility. So that rule is kept by the person who switches a project on: they ask GitHub at that moment, and each switch is a ledger row saying why.
We also publish the ledger's head — a sequence number and a hash, nothing of any row's content — on the public board and in docs/ledger-anchors.md. The hash covers every row, yours included. It reveals nothing about them, and it is what makes a removed row visible.
5. Who can see your rows
- The database draws the line. Every table a signed-in person's browser can read has row policies that limit it to your workspace (and, for your own e-mail settings, to you). A live drill on 2026-09-21 — a throwaway second workspace reading all 34 tables open to the browser — saw no row of another workspace, and the site's public key alone reads no table at all.
- The pages' own queries add a filter of their own on most reads: read 2026-09-24 11:05Z (
tools/tenant-reads.mjs), 15 of the 21 reads of workspace tables carry one, and 6 rely on the row policies alone. - The machine API is gated by a key, scoped to the projects that key serves, and not reachable from the internet at all — only from the server itself and the operator's private network (closed 2026-09-15).
- A personal token or an AI client you connect (the MCP window) reads your workspace only, and what it reads goes wherever that client sends it.
- On our side, the operator — one person — and the tools the operator runs can read every row, through the database's service role, which row policies do not bind. The operator works with an AI coding assistant, Claude (Anthropic), which can read rows the same way when the operator directs it to an administrative task. Anthropic is a recipient on that basis. Open (gap P0-8,
docs/readings/gdpr-2026-10-07.md): whether Anthropic trains on what it reads this way depends on which Anthropic plan the operator uses for this, which is not yet confirmed in the tree — a commercial plan carries its own DPA and training off; a consumer plan does not, and would need the assistant kept out of customer rows by name instead. The privacy note states the answer once this is confirmed. - Each customer gets a workspace of their own, through a customer invitation, or by signing up directly once that is open to them. (Stale count removed 2026-10-07,
docs/readings/gdpr-2026-10-07.mdD7: the one-workspace count read 2026-09-22 stopped holding once open sign-up shipped; a re-count is a live read, not stated here.)
6. What we ask of you
- Connect a repository you are entitled to connect.
- Bring your own model key, or point us at your own endpoint — for every role, including the ones that write and judge drafts.
- Do not keep a secret where we cannot refuse to read it: if a credential is committed anywhere in your repository, assume our server holds a copy during a run, and that the model sees it if it sits inside ordinary source or your suite prints it.
7. What you can ask of us
- Stop: revoke the GitHub App installation, and no run or draft can read your repository again. The project is archived, not deleted.
- See it: the Record page shows your workspace's ledger, newest first, and downloads it as CSV or JSON, seven days at a time.
- Take it away:
tools/export-tenant.mjswrites YOUR workspace's rows and no one else's. It takes every table that a column places in your workspace, and the list comes from the schema itself, so a table added later is included too. Each file says how it was scoped: bytenant_id, or through the column that reaches your workspace, such as a run's project. The ledger has no workspace column. A ledger row is yours when its project, run or intent names one of your rows, or when an id in its payload does (your workspace, an epic, a sprint). Rows that name none of yours are left out, not guessed in. - Vault rows appear as name and version only. - A run's record goes without its workspace snapshot. - Columns that work as keys are withheld: a key's hash, the token in an unsubscribe link. The manifest names each withheld column. - Two tables are never exported, and the manifest says why: the deployment's own health checks, and requests to be let in, which are made before any workspace exists.
Run against this deployment on 2026-09-24 at 13:37Z, on the operator's workspace: 76,824 rows in 35 tables, 115 MB. The ledger part was 46,953 of its 47,421 rows.
- Delete: ask, and a person erases your workspace as §3 says: after an export, in one transaction, recorded on the ledger, and your sign-in too if you ask. None of it is one button. The ledger's rows stay, as §3 says.
- Ask why: every act has a row, and the receipt for an epic names the criterion, the judge, the price and the merge.
8. What we keep about you, and what your browser keeps
- Signing in: by e-mail and password (a confirmation link and a password-reset link come by e-mail). Sign-in with Google is switched off for the alpha (since 2026-10-04), and no other provider is on.
- Joining: by invitation only. Asking for one on the site stores your e-mail address, where you asked from, a hash of your network address and your browser's identification; the ledger records only a hash of the address. Both hashes are keyed (HMAC with a server secret,
src/lib/conductor/pii-hash.ts, since 2026-10-07; the unkeyed network hashes written before were nulled that day). An invitation request is deleted 12 months after the request; an account never confirmed after 30 days; an invitation never used after 12 months; a GitHub install link once it has expired and no installation names it (gap P0-4:tools/retention-tick.mjs, daily at 03:30 UTC, one counts-onlyretention.sweptledger row per run; first run 2026-10-07 11:10Z). - Your browser keeps your sign-in session, the project you last chose, and an unsent draft of a request, in local storage on your own device. We set no cookies of our own and run no analytics or tracking scripts.
- Visiting the site: since 2026-09-24 the pages load their typefaces from our own server, so no page asks Google Fonts for anything and no visitor's address goes to Google for a font. The edge's content policy lets a page reach only our own address and, once you are signed in, the database.
Open, and named rather than hidden: the AI Act review (L2, before any paying customer), and the classification itself, which is the operator's to state — the working posture since 2026-09-01 is a provisional reading the factory recorded, not a ruling by counsel: Atrytone is treated as a limited/minimal-risk AI system and as a deployer of general-purpose AI models, and no high-risk classification, conformity assessment or CE mark is claimed; the server's city (§2); a lawyer's review of what the ledger keeps after a deletion request, and on what basis (§3); erasure a customer can start themselves, rather than one a person runs on their request; the export's missing tables (§7); the ledger rows on this deployment that carry no project at all — 6,405 on the 2026-09-09 reading, not re-read — which since 2026-09-22 include a customer workspace's own creation and membership rows, so they can no longer all be called the factory's own acts; and support today is one person, answering within one working day, not a rota.