How it works
A map of the machinery, for anyone who wants to know what the app is doing when they click.
One project, one folder
Every project is a folder on this Mac, inside the app’s sandbox container:
~/Library/Containers/app.syncretic.Syncretic/Data/Library/Application Support/Syncretic/
Models/
embeddinggemma-2-Q8_0.gguf the embedding model, shared by every project (310 MB, downloaded once)
Projects/<uuid>/
project.json name, the shape as drawn, and the shape the database currently has
graph.lbug the LadybugDB database: every node and relationship
layout.json where nodes sit on the canvas, which are pinned, which are on the canvas
history.json questions asked, the Cypher that answered them, routing decisions
embeddings.json + embeddings.vec passages and their vectors, for vector search
titles.* every node's title as a vector, always on, for duplicate spotting
previews/ link thumbnails and metadata
notes/, attachments the project's notes and their pictures, videos and drawings
conversations Build with AI conversations, one file each
publish.json where the project was shared, if it wasSettings › Storage › Show in Finder opens the Projects folder. Nothing in it is synced or uploaded.
The database
The graph is stored in LadybugDB, the maintained fork of the Kuzu embedded graph database: one in-process engine, one file, no server. It speaks the Kuzu dialect of Cypher, which is what every query in the app, written by you or by the AI, runs in.
The shape maps onto database tables directly:
| In the shape | In the database |
|---|---|
| A node type called Free energy principle | CREATE NODE TABLE FreeEnergyPrinciple(id STRING PRIMARY KEY, …) |
| A relationship type called relates to between Concept and Concept, and Concept and Source | CREATE REL TABLE RELATES_TO(FROM Concept TO Concept, FROM Concept TO Source, id STRING, …) |
| A Text or Link or Enum property Year published | a STRING column year_published |
| A Number property | a DOUBLE column |
| A Boolean property | a BOOLEAN column |
Every node and relationship gets an id: a UUID the app generates. What makes a string column a Link or an Enum lives in project.json, not in the database.
There is no separate “finalize” step. The database exists from the moment a project is created, and every change to the shape goes straight into it with ALTER TABLE, CREATE and DROP statements the app writes. Changes that would delete data ask first. See Shape › How the database follows the shape.
How names become identifiers
Display names are free-form; database names are derived from them, and the app shows you the display name everywhere.
| Display name | Identifier | Rule |
|---|---|---|
free energy principle, freeEnergy principle | FreeEnergyPrinciple | node tables are PascalCase; camelCase is split into words |
relates to | RELATES_TO | relationship tables are UPPER_SNAKE |
contains | CONTAINS_ (shown as CONTAINS) | a Cypher keyword gets a trailing underscore in the database only |
Year published | year_published | properties are snake_case |
2nd edition | _2ndEdition | a leading digit gets an underscore |
ārjavam | Arjavam | accents are folded; only ASCII letters and digits survive |
Two types whose names resolve to the same identifier are a validation issue (“resolve to the same name in the database”), and the shape waits until it’s fixed.
The AI route
Syncretic never holds an API key. It talks to the command-line tool of the subscription you already pay for:
- Claude through Claude Code, signed in to a Claude Pro or Max plan.
- ChatGPT through Codex, signed in to a ChatGPT Plus, Pro, Business or Enterprise plan.
Each call is one run of that tool with a structured-output request, so the answer comes back as JSON the app can check. The models offered are the ones the tool offers: Opus, Fable, Sonnet and Haiku for Claude (with efforts from Low to Max), and whatever Codex lists in its own model cache for ChatGPT. Two models are chosen in Settings: one that writes queries and builds, and one that routes questions in Auto search (a small call, so a quick model at low effort by default).
What goes out is deliberately small:
- For a question: the question, plus the shape rendered as text (node tables and their columns, relationship tables and the pairs they connect, which column is each type’s title), plus the dialect notes and rules the app always sends. Not a single node.
- For routing: the same, plus the list of embedded properties.
- For building: your message, the text of any links and files you attached (read on this Mac first), your pictures, the shape, and, only if you turn Existing nodes on, the names and types of nodes so Claude can connect to them.
Answers are checked before they touch anything. A query runs through a keyword guard, then inside a read-only transaction. A draft from the builder is checked against the shape, matched against existing nodes, and shown to you; nothing is written until you add it.
Searching by meaning
Mark a text property as embedded and its values are split into passages (about a paragraph each, sentence-packed to roughly 1,000 characters, with a one-sentence overlap) and turned into vectors by EmbeddingGemma 2, Google’s 270M-parameter text embedding model, in 8-bit, running through llama.cpp with Metal. The node’s title is embedded alongside each passage, as the model was trained.
The vectors live in a sidecar beside the database, not in it, so queries the AI writes never see chunk rows. A reconciler keeps the index in step with the graph: on open, after every edit, when the shape changes and when the model finishes downloading, it works out what’s missing or stale and a background pass fills it in, saving as it goes. The top bar shows “Embedding 12 of 340” while it runs; whatever was left when the app quit is finished on the next open. Search is brute-force cosine similarity over the vectors with Accelerate, which takes under a millisecond for thousands of passages.
The canvas
The canvas shows only what you’ve explored, never the whole graph: a node is on the canvas when it has a position there. Graphs of up to 300 nodes open with everything placed; larger ones start empty and offer a place to begin. Nodes are laid out by a force simulation (many-body repulsion, link springs, collision) that runs off the main thread; nodes you drag are pinned where you put them. Positions, pins, and what’s on the canvas are saved in layout.json.
The search bar’s answer has a canvas of its own, separate from the one you’ve explored, so an answer never scatters what you laid out. Each canvas also keeps its own Arrange lens.
Other AI apps
A small helper, syncretic-mcp, ships inside the app bundle. An AI app (the Claude app, Claude Code, Codex, Cursor…) starts it as an MCP server; it opens Syncretic if needed and passes messages to the running app over the Mac’s loopback interface. The tools it offers read the open project, run read-only Cypher, and propose changes that arrive in the Build panel as a draft for you to review. See Other AI apps.
The code, in outline
For the curious: the app is SwiftUI only (App/, Shape/, Graph/, Ask/, Build/, Notes/, Embedding/, MCP/, Canvas/, Links/, Library/, DesignSystem/), and everything with no UI lives in a Swift package, SyncreticKit: the schema model, the store and DDL builder, the Cypher guard, the AI client with its Claude Code and Codex transports, the query router, the graph builder and shape designer, the MCP protocol and tools, text chunking, the llama.cpp embedder, the vector index and reconciler, link metadata, project files, force layout and the arrangement geometry. The theme is called Lumen: ideas as points of light, on warm ink (dark) or parchment (light).