For the reader who does not believe marketing copy.
Everything on the rest of this site, restated as mechanism — names, curves, sizes, thresholds, and the places where a limit exists, including the ones that are not flattering. If you find something here the code does not do, we would rather hear about it than not.
This page is published in English only, in every language the rest of the site is offered in. A translated description of a cryptographic construction that has drifted from the code is worse than an exact English one.
The signed history
Every change is an action in a directed acyclic graph. An action carries its parent hashes, the author node, a per-author sequence number, a UTC timestamp and the payload. Its identity is BLAKE3-256 over those fields concatenated in a fixed order — parents, author, sequence, timestamp, payload — with the signature deliberately excluded, so signing never changes the hash. The signature is Ed25519 over the 32 raw digest bytes, not over the preimage and not over its hex rendering. The byte layout is published as a spec, aingle-dag-action-v1, so you can rebuild the preimage yourself; a test pins the published spec against the code that actually hashes, so the two cannot drift apart in silence.
Removal is a retraction, not a delete
Deleting a note does not erase anything. It appends a signed deletion action naming what it retracts, so the earlier state stays reachable and provable and time-travel survives. One exception, stated because you would find it: a prune operation does physically remove old actions under an explicit retention policy. It never prunes the tips, and it writes a checkpoint recording what it removed — but it is a real delete, and where it is exposed to AI tooling it is marked destructive rather than hidden among the read-only calls.
What the lock proves — and what it does not
A verified lock means two things at once: the Ed25519 signature on the anchoring action verified, and the note on disk still hashes to exactly what that signature attests. Either one failing yields no lock, and every error path fails closed — no key, no graph, a malformed hash or a missing action all produce "unverified" rather than the benefit of the doubt. "Signed, then edited" is reported as its own state instead of collapsing to unsigned. What it does not prove: the key is your vault's own, generated locally, so this is a self-attestation, not an identity — there is no certificate authority and no binding to a person. The timestamp is your machine's clock, sealed inside the signed bytes; there is no timestamping authority, so anyone holding the seed could back-date. And the signing seed is stored unencrypted next to the database. It proves these bytes were ingested and signed here and have not changed since. That is a smaller claim than "authentic", and it is the one we make.
Encryption at the sync target
One random 32-byte master key per target, generated on the device. Three subkeys derived from it with BLAKE3 derive_key under distinct context strings: one for content, one for blob names, one for a public target id. Content is XChaCha20-Poly1305 with a fresh random 24-byte nonce per write and a 16-byte tag. The master key is reachable two ways, both held only by you: a passphrase wrapped with Argon2id — the argon2 crate defaults, 19 MiB, 2 iterations, 1 lane, with a 16-byte random per-target salt — stored in a keybox on the target itself so any machine that can see the folder can unlock it; or a one-time recovery key, which is the master key rendered as 56 Crockford base32 characters with a checksum. Nothing is escrowed. We hold no copy and there is no reset, which is the same thing as saying we could not read your files even if we wanted to.
What actually lands in the folder
A blob's name is a keyed BLAKE3 hash of its vault path, Crockford base32 encoded — always 52 characters, so the length of a path leaks nothing either. The real path travels inside the sealed payload, so decrypting one blob is enough to place it: there is no manifest and no name table that could be lost or corrupted. The authentication tag covers the path, so a blob renamed or moved by anything other than the app is detected rather than silently accepted. Padding is applied to the plaintext before sealing: the padded length is the next multiple of 4 KiB up to 64 KiB, and the next multiple of 64 KiB above that. On disk a blob is that padded length plus exactly 40 bytes — a 24-byte nonce and a 16-byte authentication tag.
What an observer of that folder can still see
Stated here because you would find it anyway, and because a limitation you discover for yourself is worth far more doubt than one we hand you. Someone who can read the synced folder learns: that it is an Altretta target, because the header file is plaintext on purpose so a second machine can find the salt; the number of notes, since there is one blob each; and each file's size to within its padding bucket. Because a blob's name is a deterministic function of its path, every note keeps the same name for its whole life — a stable pseudonym. That determinism is what lets two machines agree where a note lives without a shared index, and it is also what lets an observer watch how often you edit any single note, note by note, from timestamps alone — without ever learning which note it is. Deletion is the one place this was worse and was fixed rather than explained away: a deleted blob is moved into a trash directory instead of being unlinked, and until 0.9.1 it kept its name and gained the millisecond it was removed, which handed a single snapshot of the folder both an exact deletion time and the pseudonym the note wore while it was alive. It is now filed under a fresh random id and the real path is read back out of the sealed payload, so what remains readable is the number of deletions and nothing else. The public target id is a stable fingerprint linking two folders to the same key. None of it discloses a title, a path, or a word of content.
What makes the assistant refuse
Retrieval embeds your question, over-fetches from the memory index, keeps only chunk entries and re-ranks them by pure cosine similarity — deliberately discarding the composite recency-and-importance score the memory layer would otherwise apply, because relevance to the question is the only thing that should decide a citation. A verdict of "grounded" requires the best match to clear the high threshold and at least 2 chunks to clear it: corroboration, not one lucky passage. With the neural embedder the product ships, those thresholds are 0.80 and 0.77, calibrated against a measurement that unrelated pairs top out near 0.76. Between the two is "weak"; below both is "ungrounded". The answerability gate is then: at least one visible source after your folder exclusions have been applied, and — if you asked for grounded answers only — a verdict of exactly "grounded". When that gate fails, no model call is made at all. Answering anyway is a separate flag that is never inferred on your behalf, and when you set it the weak passages are withheld from the model so it cannot cite what did not qualify.
The symbol graph
Twenty languages have symbol extractors, each a tree-sitter parse plus a query: Rust, TypeScript, JavaScript, Python, Go, Swift, Java, C, C++, C#, Kotlin, PHP, Ruby, Dart, Scala, Objective-C, Bash, Lua, R and SQL. Symbols get canonical identifiers and are emitted as signed triples through the same path as everything else in the graph. Edges are typed — defines, imports, references, calls — and carry a confidence tier: a call resolving to exactly one candidate is high confidence, while a call with homonyms and every bare reference are marked uncertain rather than asserted as fact. Files over 2 MiB are recorded as skipped rather than quietly ignored — the skip is itself a node in the graph you can query — and a project is budgeted at 750,000 symbols. Drift compares a note's last-written time against the newest signed change to the symbol it documents. The limitation, which the source states plainly: that time is the filesystem mtime, so a checkout, a restore or an rsync that rewrites mtimes will change the verdict.
Reading documents, and what gets dropped
Word, PowerPoint and their OpenDocument equivalents are parsed in pure Rust — no rasterising, no OCR, no native dependency. Spreadsheets go through calamine. PDFs use the pdf.js text layer; rasterising with OCR is a separate, opt-in tier that runs only on pages with no usable text, and pages left unread are recorded as debt rather than silently dropped. Extraction emits citation anchors — a page marker per PDF page, a heading per slide, a sheet-and-row anchor per spreadsheet block, and a section anchor for numbered sections in a regulation, keeping the numeral in the heading because it is the only stable name that section has. Slides are ordered numerically, so slide 10 follows slide 9 rather than slide 1. Caps are enforced by bounded reads instead of trusting a declared size: 32 MiB per XML part, 128 MiB per archive, 16 MiB of emitted text, 5,000 slides, and 50,000 rows in a spreadsheet — a table inside a document is capped far lower, at 4,096 rows by 256 columns. A gap worth naming: spreadsheets report truncation as a warning and the other converters do not, so a very large document can be trimmed quietly. The extracted text is written as an ordinary companion Markdown file beside the source, so the normal ingest signs it like any other note — there is no extraction database, the companion file's recorded source hash is the idempotency record.
The team vault, and the thing it does not hide
A team vault is a second, encrypted store beside the plaintext one. Note contents are sealed with XChaCha20-Poly1305 under a vault key that is handed out through an owner-signed roster: each member holds an X25519 keypair and the key is wrapped to their public key, so it is never transmitted in the clear, and removing someone rotates it. The limit is stated in the source under a heading that calls it a documented metadata leak, and it is repeated here for the same reason: filenames and directory structure stay in plaintext. The encrypted store mirrors the plaintext tree path for path, so anyone holding it learns the shape of the vault and the title of every note, and only the contents are unreadable. This is the one place where the personal sync target is strictly stronger than the team vault — there, the names are hashed too. Encrypting team filenames is deferred, not done.
Where everything lives on disk
Your notes are Markdown files in your folder. The only thing the app adds inside it is a hidden control directory holding the vault format stamp and, for a team vault, the keyring. Everything derived — the graph database, the search index, the snapshot store, the signing seed — lives outside the vault in application data, and the app refuses to start if you point the database inside the vault, because its own writes would trigger a re-ingest loop. That separation is why removing the app leaves your folder untouched, and why the derived index is always safe to delete and rebuild. It is also, honestly, why version history does not travel with a synced folder today. Entitlement is decided offline: a signed token verified on your machine against a key compiled into the app, against your machine's clock, with no server in the path — and the app additionally asks the licence server once per launch whether anything changed, so a renewal or a cancellation shows up promptly. That call is best-effort in one direction only: a transport failure never denies, an expired licence keeps working offline for fourteen days, and past that it drops to Free rather than locking. If we disappeared tomorrow, the app keeps opening, reading, writing, searching and answering.
How to check all of this yourself
The engine is open source. github.com/ApiliumCode/aingle holds the signing, hashing, graph and grounded-retrieval implementation, under Apache-2.0 for individuals, academia and organisations under $1M revenue, with a commercial licence above that. The application itself is not open source, and we would rather tell you that than imply otherwise.
- Reproduce a content hash: run any BLAKE3 tool over a note and compare it with the hash the app shows you. There is a pinned test vector in the public source to check your tooling against first.
- Rebuild an action hash and verify its signature independently: the byte layout is published as a spec, and the public test suite exercises tamper-rejection and the fact that signing does not change the hash.
- Confirm there is no telemetry: grep the engine source for the usual analytics and crash-reporting names, then list every URL literal in it. What comes back is your own AI provider, your own connectors, our licence, publishing and plugin-hub endpoints, the identity providers you can sign in with, and the update manifest — nothing else.
- Watch the network: open a vault with a monitor running. Nothing should be contacted until you configure a model or deliberately click an account action.
- Check a published page without trusting us: a published site carries its own verifier, which rebuilds the action preimage in your browser and checks the signature offline. Break one byte of the page and watch it refuse.
- Confirm no reset exists: forget the passphrase and lose the recovery key on a scratch target. Nothing in the app, and nothing we hold, can open it again.