Skip to content

Read results

After a turn that solves there are three things to read: the execution record, the files the run wrote, and the snapshot it froze.

listExecutions returns newest-first, so index 0 is the most recent run.

const executions = await aura.listExecutions(project.id);
const [latest] = executions;
console.log(latest.status); // e.g. "completed"
console.log(latest.execution_time_ms);
console.log(latest.output_summary); // the solver's own summary
console.log(latest.error_message); // set when the run failed

Fetch one by id with getExecution / get_execution — useful when you kept the id from an earlier poll rather than re-listing.

Every file operation names the container it targets, so the same four verbs — listFiles, readFile, uploadFile, deleteFile — reach all three:

Container Holds Mutable
deployment The published model and data a project starts from Yes
workspace One project’s live files — the deployment’s, plus whatever the agent wrote Yes
snapshot One run’s captured state No — writes are rejected with a 409
{ source: "deployment", deploymentName }
{ source: "workspace", projectId }
{ source: "snapshot", projectId, snapshotId }

This mirrors the server’s FileContainerRef, which requires exactly the identifiers a source needs and rejects anything else. Modelling it as a union rather than a source string means an invalid combination — a workspace carrying a snapshotId, say — fails to type-check instead of returning a 422.

Which container you want turns on when: the workspace is live and the next run overwrites it, a snapshot is frozen, which is what keeps an old run’s solution retrievable.

const workspace = { source: "workspace", projectId: project.id } as const;
// everything under run/output/
const outputs = await aura.listFiles(workspace, "run/output/");
for (const entry of outputs) console.log(entry.path, entry.size);
const solution = await aura.readFile(workspace, "run/output/solution.json");
const parsed = JSON.parse(solution.text ?? "{}");

A file read returns text for textual content and base64 for binary, with content_type naming which. Check content_type rather than assuming text is populated.

The agent runs the solver as part of a turn, so the usual shape is chat, then read.

const reply = await aura.chat(project.id, "Solve with the current inputs.", {
timeoutMs: 15 * 60_000,
});
const [execution] = await aura.listExecutions(project.id);
if (execution.status !== "completed") {
throw new Error(`Solve ${execution.status}: ${execution.error_message ?? "no detail"}`);
}
const solution = await aura.readFile(
{ source: "workspace", projectId: project.id },
"run/output/solution.json",
);

Every completed solve freezes a snapshot, so comparing runs means comparing snapshots rather than racing the live workspace.

const snapshots = await aura.listSnapshots(project.id); // newest first
const [current, previous] = snapshots;
console.log(current.solution_summary, previous.solution_summary);
// each snapshot's own frozen copy, at the path that run recorded
const [a, b] = await Promise.all(
[current, previous].map((snapshot) =>
aura.readFile(
{ source: "snapshot", projectId: project.id, snapshotId: snapshot.id },
snapshot.solution_path ?? "solutions/out.json",
),
),
);

Reading through the snapshot container rather than the workspace is what makes this a comparison rather than a race: the live workspace holds only the most recent run’s output, so the older solution exists nowhere else.

solution_path is the path the solver recorded for that run. A solution is JSON by convention only — a solver may write any format — so treat the extension as data, not a guarantee.

Snapshots and tags covers naming the good ones and loading one back.