Skip to content

Snapshots and tags

Every completed solve freezes the project workspace into an immutable snapshot, so the state that produced a given result stays available after the next turn changes things.

Newest first, and each row carries what it came from.

const snapshots = await aura.listSnapshots(project.id);
for (const snapshot of snapshots) {
console.log(snapshot.id, snapshot.kind, snapshot.label ?? "", snapshot.solution_summary);
}

kind is "solve" for the automatic ones and "manual" for a snapshot someone captured on demand. solution_path names the file inside the snapshot that holds the solution; execution_id links back to the execution that produced it. Exactly one row has is_current set: the snapshot the live workspace sits on, which is the newest one after a solve and whichever you checked out otherwise.

Filters are applied server-side and stack, so “give me the solutions” is one request rather than a full history you sift through.

const solutions = await aura.listSnapshots(project.id, { kind: "solve", hasSolution: true });
const pinned = await aura.listSnapshots(project.id, { hasLabel: true });
const fromThread = await aura.listSnapshots(project.id, { conversationId: conversation.id });

hasSolution / has_solution and hasLabel / has_label are tri-state. Leaving one out does not filter; passing false asks for the rows where the field is unset — the solves that produced no solution file, or the snapshots nobody has named.

getSnapshot takes either — a raw id, or a tag name defined in the same project.

const byId = await aura.getSnapshot(project.id, "3f1c…");
const byTag = await aura.getSnapshot(project.id, "best-known");
console.log(byTag.file_count, byTag.total_size_bytes);
for (const file of byTag.files) console.log(file.path, file.size);

The detail response includes files; the list response does not — list is a summary.

A tag is a movable name for a snapshot inside a project — its address, accepted anywhere a snapshot id is. Point it at a new snapshot and the name follows. (A snapshot’s label is only its display title; to fetch a snapshot by name, tag it.)

const [latest] = await aura.listSnapshots(project.id);
await aura.setTag(project.id, "best-known", latest.id);
const tags = await aura.listTags(project.id);

A nightly job can re-solve, compare, and move best-known only when it improves; everything downstream asks for the name rather than tracking ids.

A label is different: a free-text display title, not a ref — it’s what a person sees in the Run tab’s snapshot list, not something you fetch a snapshot by. Set or clear one with renameSnapshot / rename_snapshot:

await aura.renameSnapshot(project.id, latest.id, "before the capacity change");
// clear it
await aura.renameSnapshot(project.id, latest.id, null);

Checkout replaces a project’s live workspace with the files from a source. source can be:

source Resolves to
3f1c… A snapshot id in this project
best-known A tag in this project
acme/vrp A deployment handle — reset to the deployed starting point
acme/vrp-demo/best-known A tag on another project in your company
// go back to the best run so far
const basis = await aura.checkout(project.id, "best-known", conversationId);
console.log(basis.kind, basis.file_count); // "snapshot", 42
// or start over from the deployment
await aura.checkout(project.id, "acme/vrp", conversationId);

Pass the conversation id to attribute the checkout to that conversation, so the agent sees the workspace change in context.

A checkout clears the workspace before it copies, so the server runs one per project and refuses the rest with a 409 — a second checkout, and a snapshot taken while one is running, both wait their turn rather than freezing or copying a half-written tree. A checkout is also refused while an agent turn is in flight.

Read the workspace to see whether one is running before you start another, or after a request whose answer you never got:

const workspace = await aura.getWorkspace(project.id);
if (!workspace.checkout_in_flight) {
await aura.checkout(project.id, "best-known");
}

checkout_in_flight is read from the running checkout itself, so it answers for every caller — your other tab, a teammate, a script — not just this client. It is a check, not a reservation: two callers that read it at the same moment can still both try, and the loser gets the 409.

Capturing a snapshot on demand, and reading or deleting a tag by name, are REST endpoints you can call directly.