Version files
The history repo is a plain Git repo. Each version is one commit. Each flow is one folder of small text files, written the same way every time, so a one-word prompt edit is a one-line diff. Where the repo is: History settings. A copy in GitHub has the same files. How to use history: Version history.
Do not edit the repo while Cuttlely is running. Cuttlely writes it and reads it back.
Repo layout
Section titled “Repo layout”versions/ README.md flows/ bakery-helper--1f2e3d4c/ flow.json edges.json layout.json nodes/ chatOpenAI_0.json chatOpenAI_0.definition.json conversationChain_0.json conversationChain_0.definition.json prompts/ conversationChain_0.systemMessagePrompt.mdThe branch is main. Cuttlely adds no remote.
A flow’s folder is named <slug>--<first 8 characters of the flow id, without dashes>. The slug is the flow’s name in lowercase, with each run of other characters replaced by -, up to 40 characters, or flow when nothing is left. If another flow already has that folder, the folder uses the whole flow id. Cuttlely finds a flow’s folder by the id in its flow.json, so renaming a flow renames its folder and keeps its history.
All JSON is written with sorted keys, a two-space indent, and a final newline.
| File | Contents |
|---|---|
flow.json |
formatVersion (1), the flow’s id, name, type, and category, the node list in canvas order (id and file), the credentials it names, the chat models it uses (node, component, model), hidden when secret-looking text was hidden (Markers), and extra for any other top-level keys of the flow. |
nodes/<node id>.json |
One node without its position, size, or selection state. Its data keeps only id, name, label, version, type, category, inputs, outputs, and credential. |
nodes/<node id>.definition.json |
The rest of the node’s data: the input and output definitions the canvas draws from. Written when there is any. |
prompts/<node id>.<input>.md |
A multi-line text input, such as a prompt or instructions, as plain text. |
edges.json |
The connections, sorted by id. |
layout.json |
Each node’s position, positionAbsolute, width, and height, and the canvas viewport. |
Selection and drag state is not written.
Markers
Section titled “Markers”Some values in a node file are written as a one-key object:
| Marker | Meaning |
|---|---|
{ "$text": "prompts/<node id>.<input>.md" } |
The text is in that file. |
{ "$credential": { "name": "OpenAI main", "credentialName": "openAIApi" } } |
A credential, by its name and type. { "$credential": null } is a credential Cuttlely could not name, such as one that was deleted. |
{ "$secret": true } |
A value that looked secret and was not written. It comes back from the flow as it is now. |
{ "$json": … } |
JSON text that held a credential or a secret. The text is written as JSON with those values marked. |
{ "$literal": … } |
A value of your own that looked like one of these markers, kept as it was. |
Inputs that are never written at all: password, file, and folder fields, and known fields that hold a path on the server’s disk. On read, each of those comes back from the node with the same id in the flow as it is now. What counts as secret is in What is stored, and what is not.
Text in prompts/ and other text values are written as typed, except secret-looking values, which are replaced with [secret hidden]. What counts is in Keys typed into text fields. On read and restore the placeholder stays. A value that already holds the placeholder, such as a restored version that still has one, counts as hidden again.
When anything was hidden, flow.json has a hidden list, one entry per input:
"hidden": [ { "count": 1, "input": "systemMessagePrompt", "inputLabel": "System Message", "label": "Conversation Chain", "node": "conversationChain_0" }]count is how many values were hidden in that input. A flow.json without hidden has nothing hidden.
Before each commit, Cuttlely also scans the files for key-shaped text: sk- and xai- keys, GitHub tokens, AWS access key ids, Postgres URLs with a password, and private keys. This catches text that is not an input, such as the flow’s name. A hit stops that commit. The message names the file and the pattern, never the text.
Commits
Section titled “Commits”Changed System Message on Conversation Chain
Version-Flow: 4b6f0c2e-…Version-Kind: saveVersion-Name: Bakery helper- The subject is the version name, at most 72 characters.
- The body has more lines about the change when there are any.
- The trailers are
Version-Flow(the flow id),Version-Kind(create,save,restore,publish,rollback,promote, ordelete), andVersion-Name(the flow’s name). Apromoteversion also hasVersion-From, the full id of the version from Dev it copies.Version-Hiddenis how many secret-looking values the version holds as[secret hidden], written only when there are any; the body then says so, for example1 secret-looking value was hidden from history. - The author is the person who saved it: their display name and
<user id>@users.versions.local. An API key or system save isCuttlely <system@versions.local>. - The committer is always
Cuttlely <system@versions.local>.
A delete commit removes the flow’s folder. Earlier commits still have it.
Cuttlely runs the system git with its own settings. It does not read your global or system Git config, signs nothing, and skips hooks.
Format version
Section titled “Format version”flow.json carries formatVersion. This Cuttlely writes and reads version 1. A file with another version is not read: flow.json has format version <n>. This Cuttlely reads version 1.