Agents APIWorkspace
Outputs
Name the files you want from a session, and download them with links that keep working after it ends.
A session's files live on its own disk, which is deleted when the session ends. Outputs are the files you name when you create the session. LatentStack copies them out at the end of each run and keeps them after the session ends, with download links that need no API key. You can collect them later, or hand the links to someone else.
Name your outputs
const session = await sessions.create({
agent: { model: "anthropic/claude-sonnet-5.5" },
input: "Sort the names in uploads/names.txt and save them as out/sorted.txt.",
files: { "uploads/names.txt": "Mira\nArjun\nZoe\nLena\n" }, // in the workspace before the first message
outputs: ["out/*.txt"], // copied out when the run ends, and kept after the session
end_after_run: true, // stop once the run has ended
})outputslist of pathsoptionalout/report.pdf. In a name, * matches any characters and ? one character, within one folder: out/*.json is every JSON file directly in out/, not in the folders below it. reports/*/summary.md works too. ** isn't supported.filesobjectoptionalend_after_runbooleanoptionaldefault falsePaths follow the same rules as file calls: relative to the workspace, no .., and nothing inside .git. The agent is already told to leave what you ask for in out/, so naming the file in your message is enough.
When they're copied
- At the end of every run, whether it completed or failed. The copy is made before the run's webhook is sent, so
run.completedalready lists the files with their links. - Once more when the session stops while its sandbox is still up: when you delete it, or after its run with
end_after_run. A session stopped while paused has no sandbox to copy from, so the copies made before are what you get. - When it pauses with its sandbox up, including at a spend limit, where its run never reaches an end. The
session.pausedwebhook then lists the files with their links. - A file that hasn't changed since its last copy (the same size and modification time) isn't copied again.
- Each path keeps only its newest copy.
When something is missing
The run's run.completed or run.failed webhook says what this copy couldn't do:
| Field | Meaning |
|---|---|
| outputs_missing | Patterns that matched no file this time. |
| outputs_errors | Files or patterns that couldn't be copied, each with a message. |
| outputs_truncated | A pattern looks through at most 1,000 files under its folder, subfolders included. true when a folder held more. |
| outputs_error | The copy failed as a whole. The outputs already kept are still listed. |
List and download
await sessions.wait(session.id, { until: ["stopped", "failed"] })
const listing = await sessions.outputs(session.id)
for (const output of listing.data) {
// The link needs no API key and works for 24 hours.
const response = await fetch(output.url)
await writeFile(basename(output.path), Buffer.from(await response.arrayBuffer()))
console.log(output.path, output.size, "bytes")
}A listing is { object: "list", data, patterns }: the outputs in path order, and the patterns the session asked for.
{
"id": "out_5e0a91c2d4b7e8f1a2c3d4e5",
"object": "agent.output",
"path": "out/sorted.txt",
"size": 20,
"sha256": "9f2c…",
"content_type": "text/plain",
"run_seq": 14,
"captured_at": "2026-10-14T06:12:40.118000+00:00",
"url": "https://latentstack.dev/v1/agents/outputs/out_5e0a91c2d4b7e8f1a2c3d4e5/content?token=…",
"url_expires_at": "2026-10-15T06:13:02.540000+00:00"
}| Field | Meaning |
|---|---|
| path · size · sha256 · content_type | The file, as it was copied. |
| run_seq | The event number of the run end (or the stop) that copied it. |
| captured_at | When it was copied. |
| url · url_expires_at | A download link, and when it stops working. |
Links
- A link downloads the file without an API key until
url_expires_at: 24 hours after you list. List again for fresh links. - Links inside a
run.completedorrun.failedwebhook last 4 days, longer than the webhook's retries. - A link always gives the newest copy of its path.
- A browser saves the file rather than opening it.
- An expired link, or one for a deleted output, answers 404.
Keep and delete
Outputs stay after the session stops or is deleted, until you delete them. List them with the session's id as before. The rest of the session's disk is still deleted when the session ends.
await sessions.deleteOutput(session.id, output.id)Its links stop working at once. If the session runs again, the next run's end copies the file again under a new id.
Send files with create
files on create puts files in the workspace before the first message runs, so input can use them at once. You don't have to wait for the sandbox and call uploadFile().
- It maps a workspace path to the file's content. In both SDKs a string is the file's text, stored as UTF-8, and bytes (
Uint8Arrayin TypeScript) are its content. The SDK sends them as base64; over HTTP, send base64 yourself. - The files travel inside the create request, which is at most 50 MB in all (
request_too_largeotherwise). For larger files, create the session withoutinput, upload them withuploadFile()once it is running (any size), then send the message. - If a file can't be put in place, the session fails with an
errorof typefiles_errorthat names the file.
End after the run
With end_after_run: true, the session stops as soon as a run ends. First its patch is saved, its outputs are copied and the run's webhook is sent. Then it stops with stop_reason run_finished.
- Use it for one-off jobs, such as CI or a webhook that starts a task. Waiting for
stoppedorfailed, as the example does, then tells you the work is done. - Such a session has nothing to come back to, so where another session would pause, it stops: when idle (
stop_reasonidle_timeout) or when its sandbox reaches its time limit (hard_limit). If it pauses anyway, at a spend limit for example, it waits 7 days for you to raise the limit and continue, then stops. See Pause and resume. - Schedules turn it on for their sessions unless you say otherwise.
Over HTTP
| Request | Does |
|---|---|
| GET /v1/agents/sessions/{id}/outputs | Lists the outputs, each with a fresh link. |
| DELETE /v1/agents/sessions/{id}/outputs/{output_id} | Deletes one. Answers 204. |
| GET <url> | Downloads the file. No API key: the token in the link is the permission. |
On POST /v1/agents/sessions, files is an object of path to base64 content, outputs a list of paths, and end_after_run a boolean. The session carries its outputs and end_after_run back.
outputs on create is refused with invalid_request and the outputs requests answer 404.