Workspace files
Use Fleet's bound tools instead of inventing host paths. The Turn context reports whether durable workspace tools are available.
Projects (durable deliverables)
Finished deliverables the user should keep belong in a browsable Project on
the shared Volume at projects/<slug>/, not under
sessions/<uuid>/workspace/. You name the slug explicitly: pick a short,
repo/task-derived value (for example fleet-rlm) matching
^[a-z0-9][a-z0-9._-]{0,63}$; the backend sanitizes only and never invents a
slug for you. Reserved Volume roots (sessions, files, artifacts,
attachments, memory) cannot be slugs. Project paths are canonical
POSIX-relative paths whose first segment is the slug; projects/ itself is
implicit and nested subdirectories are allowed. Project writes are immediate
private state; they survive a failed or cancelled Run and are not published as
Artifacts.
listing = list_project_files(path="fleet-rlm", limit=100)
saved = write_project_text(path="fleet-rlm/reports/review.md", content=report, overwrite=False)
page = read_project_text(path="fleet-rlm/reports/review.md", max_chars=10000)
assert saved["ok"] is True
write_project_text requires overwrite=True to replace an existing file;
the read page contract (max_chars 1 through 10,000 characters, next_cursor
until eof), list cursors, and byte-size checks match the Session Workspace
tools below. There is no append Project Tool; replace larger stretches with
write_project_text(..., overwrite=True). For a small, exactly-located
change, call edit_project_text(path, old, new): it fails unless old
occurs exactly once, so keep the fragment short and unique. Call
delete_project_path(path) to remove one file or one empty directory;
non-empty directories are always refused (there is no force/recursive
delete), and each of these calls accepts an optional expected_sha256
checksum precondition.
Session Workspace (scratch)
Session Workspace paths are canonical POSIX-relative paths rooted at .. Session Workspace is tool-only: it is not visible to Python open(), os, or pathlib, and a sandbox-local file never satisfies a Session Workspace request. List or inspect before writing, use overwrite=true only when replacement is intended, and handle a tool error before trying a different operation. Session Workspace supports full file lifecycle: in addition to write/append, edit_workspace_text(path, old, new) replaces one exactly-located fragment (it fails when old is absent or ambiguous) and delete_workspace_path(path) removes one file or one empty directory (non-empty directories are refused; there is no recursive delete). Both accept an optional expected_sha256 checksum precondition; pass the checksum_sha256 from a prior REST stat or re-read when guarding against concurrent change.
listing = list_workspace_files(path=".", limit=100)
page = read_workspace_text(path="notes/analysis.md", max_chars=10000)
pages = read_workspace_text_batch(
requests=[
{"path": "notes/analysis.md", "max_chars": 5000},
{"path": "notes/summary.md", "max_chars": 5000},
]
)
saved = write_workspace_text(path="notes/analysis.md", content=updated_text, overwrite=True)
assert page["ok"] is True
read_workspace_text accepts max_chars from 1 through 10,000 characters and returns one
UTF-8 page with content, byte_size, next_cursor, and eof. Continue with
the opaque next_cursor until eof for large documents; never invent or edit a
cursor. list_workspace_files is immediate-child and can continue with its
next_cursor. Keep only the requested page in memory.
For several independently selected files, use read_workspace_text_batch rather than a serial loop of
read_workspace_text calls. A batch accepts 1–32 {path, cursor?, max_chars?} requests with a 32,000-character
aggregate request bound and returns ordered per-item results. List or stat first; do not use it to crawl a Workspace.
For exact write-size confirmation, compare metadata with
len(content.encode("utf-8")) rather than character count.
Use append_workspace_text for incremental generation. It writes only the new
content, enforces the Workspace size bound, and returns bounded metadata. Use
write_workspace_text(..., overwrite=True) when replacement is intended.
For an existing Workspace document that should be downloadable, call
publish_workspace_artifact with its relative path and kind. The host copies
the validated bytes into a private Artifact Candidate; do not resend the body
through create_artifact. Turn Commit remains the only publication boundary.
Workspace writes are immediate private Session state. They survive a failed or cancelled Run and are not published as Artifacts.
Never report a workspace or project operation as successful unless its tool call succeeds and the applicable page iteration, append receipt, or metadata confirmation completes. Do not retry a deterministic tool error unchanged.
Attachments and Artifacts
Read only Attachment IDs supplied to the Turn:
source = read_attachment(attachment_id=attachment_id)
The result contains UTF-8 text or base64-encoded binary content; check its encoding field before using the body.
To return a new downloadable result, call create_artifact with text,
markdown, or json content. This stages an Artifact Candidate; only a
successful Turn Commit publishes it. A successful tool result is not proof
that publication completed.
candidate = create_artifact(kind="markdown", content=report, title="Analysis")
Writing a workspace file, project file, or sandbox file directly never creates a public Artifact.
Read references/filesystem-contract.md only when exact durability, layout, or path-boundary details are needed.