Skip to main content
A Session’s workspace holds live files that the agent and its tools change. /agents/environments/{environment_id}/files lists one workspace directory and creates files in it. When a Turn completes, Core copies the files under the workspace’s outputs/ directory into immutable Artifacts, read through /agents/sessions/{session_id}/artifacts. Artifacts outlive the Environment; workspace files do not. The routes follow the SDK pinned in upstream.json: the Environment files resource, its list parameters, EnvironmentFile and TokenPage. They need the OpenAI-Beta: agents=v1 header.

Where files work

  • Environment files work on openai_hosted and self_hosted Environments. A none Environment returns 503 execution_unavailable.
  • Public paths start at /workspace, which stands for the Environment’s workspace directory wherever it is on the machine.
  • The Environment is looked up in the caller’s Project first; a missing or foreign ID returns 404.
  • On an openai_hosted Environment that is still pending, both operations return 400 the hosted environment is still provisioning; wait until it is connected before accessing files. This check runs after request validation and before any source File lookup. Any other state proceeds, and an unreachable Runtime returns 503.
  • Listing and writing never start a Turn or send input to the model.

List files

GET /agents/environments/{environment_id}/files lists the regular files directly in one directory. It does not recurse. Directories, symbolic links and other entries are left out. The response is {"object": "page", "data": [...], "next": …, "has_more": …}. next is null on the last page, and has_more is true exactly when next is set. Each file has environment_id, object: "agent.environment.file", the absolute path and size_bytes.
  • A path that does not exist, names a regular file, or passes through a symbolic link returns an empty page. Links are never followed.
  • Each page reads the directory again; there is no snapshot. If the directory’s regular files (their names or sizes) or the request’s parameters changed since the token was issued, the token is rejected. Unchanged names and sizes do not prove unchanged contents.
  • A directory with more than 1,024 entries of any kind returns 503 and no partial page. Permission errors, a missing workspace root and transport failures also return 503.
  • When the daemon has no local workspace binding, the Claude Code adapter answers the read instead: a missing path returns 404, and a regular file or symbolic link returns 503.
Query errors, all with type and code invalid_request_error and a null param unless noted: Unknown query keys are ignored. An explicit empty value is invalid for every key. Malformed query encoding, such as %GG or a ; separator, returns 400 invalid_request.

Create a file

POST /agents/environments/{environment_id}/files takes either form:
Empty inline data is valid and creates an empty file. It returns 201 with the four EnvironmentFile fields. file_id names a File of the same Project; Core reads its bytes before contacting the Runtime. Unless the table names a code, the 400 errors have type and code invalid_request_error and a null param. An existing destination is never replaced: the Runtime writes a temporary file and publishes it with a hard link that fails if the destination exists. Later tool writes can still change the created file.

Write ordering and uncertain outcomes

  • Before sending any bytes, Core records the write under the Session lock. While input is pending, a Turn is running or an earlier write is unsettled, a new write returns 409 turn_conflict. An unsettled write also makes new messages to the Session return 409.
  • The Runtime checks the complete body against its digest before creating anything, so incomplete input creates nothing. A write that fails later can leave newly created empty parent directories.
  • Only a definite receipt from the Runtime settles a write, as committed or rejected. A rejected write changes nothing and releases the Session. If the connection drops, the request times out or no receipt arrives, the request returns 503 and the write stays unsettled, across Core restarts. Core never resends it and has no automatic recovery, so the Session accepts no further writes or messages. Reads still work.
  • Deleting the source File after its bytes were read does not affect the copy.

Artifacts

Capture

When a Turn completes on an openai_hosted or self_hosted Environment, Core copies every regular file under /workspace/outputs/ and publishes the copies in the same transaction that completes the Turn. Each Artifact’s path is the file’s absolute workspace path, such as /workspace/outputs/report.md. Failed and cancelled Turns publish nothing. Without an outputs/ directory there is nothing to capture.
  • Symbolic links anywhere below outputs/ are skipped by their own type: never followed, opened or resolved, and never an Artifact. The remaining files are still captured.
  • A later Turn in the same Session publishes a path only when no Artifact remains for it in the Session, or when its bytes (SHA-256) differ from the newest remaining Artifact for that path. Newest follows the order of the producing Turns. An unchanged path keeps its existing Artifact and ID. Published Artifacts are never modified.
  • The capture fails, and the Turn fails with artifact_capture_failed (or ends cancelled when cancellation was requested), when outputs is not a directory (including a symbolic link to one), an entry is a FIFO, socket or device, a file changes size or modification time while it is copied, or a limit is exceeded: 4,096 entries, directory depth 64, a 4,096-byte path, 200 MiB per file or 500 MiB per Turn.

Read and delete Artifacts

  • Reads work whether or not the Environment still exists, including after it expires.
  • Deleting an Artifact leaves the workspace file alone. A content read already in progress can finish; later reads return 404. Removing a file from the workspace leaves its Artifacts in place.
  • Deleting the Session deletes its Artifacts.
List parameters: limit and cursor errors follow the shared list rules. The response is {"object": "list", "data": [...], "first_id", "last_id", "has_more"}; an empty page has null IDs. The Session is looked up first, so a missing or foreign Session returns 404 whatever the query.