Tech Appendix For Platform Engineers 18 min read Updated May 2026

File I/O: Datasets In, Results Out

Your AI agent needs input data and produces output artifacts. Most production AI agent workloads end up bottlenecked here — not on the model, not on the compute, but on the plumbing of getting bytes in and results out.

This is the catalog. Four upload patterns (multipart, files API, scp/rsync, S3 pre-signed). Five output patterns (Slack, email, Linear, webhook, just-write-to-S3). Size limits across every layer of the stack. Data residency, encryption, and the common gotchas.

Plus a complete worked example: CSV uploaded by the operator, PDF report generated by the agent, posted to Slack with a download link, all under 8 minutes for $0.18. The whole pipeline.

Quick Answer

Four upload patterns by file size: files API (under 1 GB, simple), multipart upload (over 1 GB, resumable), scp/rsync (interactive / dev workflows), S3 pre-signed URLs (event-driven, the production default). For results, write to S3 and post the pre-signed URL to Slack — that pattern scales from 1 sandbox to 10,000. Sandbox disk default 30 GB; expand up to 16 TB. Storage costs $0.10/GB-month.

Why file I/O is the production bottleneck

The first AI agent workload most teams ship is "agent reads a thing, writes a thing, posts the result." It works fine in dev — the dataset is small, you scp it in, the agent runs, you scp the result out. Then it goes to production. Volumes scale. Files get bigger. Latency matters. The naive scp pattern breaks.

The four common production failures we see, in order of frequency:

  1. Sandbox disk fills up. A multi-day research agent accumulates intermediate files; one day there's no space for the next download. Disk monitoring + cleanup is mandatory at scale.
  2. Upload bottleneck blocks the agent. Operator uploads a 2 GB file; agent waits 8 minutes for upload to finish before starting. Asynchronous upload + event-trigger fixes this.
  3. Result file too large for the destination. Agent writes a 200 MB PDF; tries to attach to Slack (25 MB limit); fails silently. Results-via-S3-pre-signed-URL fixes this universally.
  4. Data leaves the residency boundary. EU-resident customer's data accidentally written to a us-east-1 S3 bucket. Region-pinning and bucket-policy enforcement matter.

This guide is the catalog of patterns that handle each of these failure modes. Read it before you ship a production data pipeline; refer back when an existing one starts hitting limits.

The four upload patterns

Pattern Best for File size Resumable Trigger style
Files API Simple uploads from operator UI; one-shot data transfer Up to 1 GB No Synchronous
Multipart upload Large files; flaky-network uploads Up to 5 TB Yes Synchronous (chunks parallel)
scp / rsync over SSH Developer workflows; interactive transfer; many small files Sandbox disk size Yes (rsync) Interactive
S3 pre-signed URLs Event-driven production pipelines; async; mass-volume Up to 5 TB per object Yes (multipart S3) Event-driven (S3 ObjectCreated)

Pick by file size, network reliability, and trigger style. For most production AI agent fleets, S3 pre-signed URLs are the right default — they decouple upload from processing, scale to any volume, and handle the data-residency story cleanly via bucket placement.

Pattern 1: The Files API (under 1 GB, synchronous)

Simplest pattern. The operator (or an upstream service) uploads a file directly to the sandbox's filesystem via a POST to /api/sandboxes/{id}/files. The file lands in the sandbox's /workspace directory. The agent reads it from there.

bash — files API upload
# Upload a CSV to the agent's workspace
curl -X POST "$SANDBOX_URL/api/sandboxes/sandbox_abc/files" \
  -H "Authorization: Bearer $SANDBOX_API_KEY" \
  -F "path=/workspace/sales-2026-q1.csv" \
  -F "file=@./local-sales.csv"

# Response:
# { "path": "/workspace/sales-2026-q1.csv", "bytes": 4823914, "sha256": "..." }

When to use: dev workflows, operator-initiated one-shots, files small enough to upload in under a minute. Best for files up to 100-200 MB; works up to 1 GB but starts to feel slow.

When NOT to use: bulk pipelines (10+ files), large datasets, anything operator-facing where the operator shouldn't have to wait. The synchronous nature blocks both operator and sandbox.

Pattern 2: Multipart upload (1 GB - 5 TB, resumable)

For larger files, the multipart upload API splits the file into 5 MB - 5 GB chunks, uploads them in parallel, and assembles them on the receiver. Each chunk gets its own request; failed chunks retry without restarting the whole upload.

python — multipart upload
import requests, hashlib, os

API = os.environ["SANDBOX_URL"]
KEY = os.environ["SANDBOX_API_KEY"]
HDRS = {"Authorization": f"Bearer {KEY}"}
CHUNK_SIZE = 8 * 1024 * 1024   # 8 MB chunks

def upload_multipart(sandbox_id, local_path, remote_path):
    file_size = os.path.getsize(local_path)
    # 1. Initiate multipart upload
    init = requests.post(
        f"{API}/api/sandboxes/{sandbox_id}/files/multipart/init",
        headers=HDRS,
        json={"path": remote_path, "size": file_size},
    ).json()
    upload_id = init["upload_id"]

    # 2. Upload chunks (parallelize for speed)
    parts = []
    with open(local_path, "rb") as f:
        part_num = 0
        while chunk := f.read(CHUNK_SIZE):
            part_num += 1
            etag = requests.put(
                f"{API}/api/sandboxes/{sandbox_id}/files/multipart/{upload_id}/{part_num}",
                headers=HDRS,
                data=chunk,
            ).json()["etag"]
            parts.append({"part_num": part_num, "etag": etag})

    # 3. Complete (server assembles)
    requests.post(
        f"{API}/api/sandboxes/{sandbox_id}/files/multipart/{upload_id}/complete",
        headers=HDRS,
        json={"parts": parts},
    )

upload_multipart("sandbox_abc", "large-dataset.parquet", "/workspace/data.parquet")

When to use: files over 1 GB, slow or unreliable networks, anything where you can benefit from parallel chunk upload (5-10× speedup over single-stream).

Gotcha: if the upload fails partway through and you don't call abort, the chunks accumulate in storage and incur cost. The platform auto-cleans incomplete multipart uploads after 24 hours; configure shorter if you want.

Pattern 3: scp / rsync over SSH (developer workflow)

Once you've SSH'd into the sandbox (per the SSH guide), scp and rsync work as you'd expect. Best for developer workflows, debugging, and many-small-files transfers.

bash — scp and rsync patterns
# Copy a local file to the sandbox
scp -i ~/Downloads/my.pem ./report.csv ec2-user@$SANDBOX_IP:/workspace/

# Copy a directory tree, preserving structure
scp -r -i ~/Downloads/my.pem ./datasets/ ec2-user@$SANDBOX_IP:/workspace/

# rsync — resumable, only transfers diffs, ideal for many small files
rsync -avz --progress \
  -e "ssh -i ~/Downloads/my.pem" \
  ./datasets/ ec2-user@$SANDBOX_IP:/workspace/datasets/

# Pull a result file back
scp -i ~/Downloads/my.pem ec2-user@$SANDBOX_IP:/workspace/output.pdf ./

# Or rsync the whole results directory back
rsync -avz \
  -e "ssh -i ~/Downloads/my.pem" \
  ec2-user@$SANDBOX_IP:/workspace/results/ ./local-results/

When to use: debugging, dev workflows, ad-hoc transfers, many small files (rsync's diff-only behavior shines here), interactive sessions where you're already SSH'd in.

When NOT to use: production pipelines (the human-in-the-loop is wrong), event-driven flows, anything you want to automate without SSH key management on the operator's machine.

Pattern 4: S3 pre-signed URLs (production default)

The pattern that scales. Operator uploads to a pre-signed S3 URL (no AWS credentials on the operator's side); S3 ObjectCreated event triggers the agent; agent reads from S3; agent writes results back to S3; agent posts a pre-signed download URL to Slack. End to end, no inline file transfer through the sandbox API.

python — generate pre-signed upload URL (your app)
import boto3, uuid

s3 = boto3.client("s3")
BUCKET = "agent-uploads"

def issue_upload_url(filename, content_type="application/octet-stream"):
    """Operator's app calls this; gets back a URL the operator can PUT to."""
    key = f"incoming/{uuid.uuid4()}/{filename}"
    url = s3.generate_presigned_url(
        ClientMethod="put_object",
        Params={"Bucket": BUCKET, "Key": key, "ContentType": content_type},
        ExpiresIn=3600,   # 1 hour to upload
    )
    return {"upload_url": url, "key": key}
bash — operator uploads
# Operator (or their app) does a simple PUT — no AWS creds needed
curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: text/csv" \
  --upload-file ./sales-2026-q1.csv

# S3 receives the file. ObjectCreated event fires automatically.
python — agent processes (triggered by S3 event)
import boto3, os
s3 = boto3.client("s3")
BUCKET = os.environ["AGENT_BUCKET"]

def handle_new_file(event):
    """Triggered by S3 ObjectCreated event via SQS."""
    for record in event["Records"]:
        key = record["s3"]["object"]["key"]
        local_path = f"/workspace/{key.split('/')[-1]}"

        # Stream the file from S3 to local disk (no full memory load)
        s3.download_file(BUCKET, key, local_path)

        # Agent does its work
        result_path = process_file(local_path)

        # Write result back to S3
        result_key = f"results/{key.split('/')[-2]}/output.pdf"
        s3.upload_file(result_path, BUCKET, result_key)

        # Generate a 24-hour download URL and post to Slack
        download_url = s3.generate_presigned_url(
            ClientMethod="get_object",
            Params={"Bucket": BUCKET, "Key": result_key},
            ExpiresIn=86400,
        )
        post_to_slack(f"Result ready: {download_url}")

Why this is the production default:

This is what every mature AI agent fleet eventually converges on. Adopt it from the start if you can.

The five output patterns

Once the agent has produced a result, where does it go? Five common destinations:

DestinationBest forSize limitsHow
SlackHigh-visibility one-off results, executive digests25 MB attachment, 1 GB via APIFiles API or pre-signed URL link
EmailLow-attention reports, scheduled summaries10-25 MB attachmentSES / Postmark / Sendgrid with attachment or link
Linear / Jira / Notion / ConfluenceTracked work items, results that need assignmentPer-platform; Notion 5 MB per uploadAPI write with file attachment
Webhook to your appProgrammatic results that drive downstream automationPer your endpointHTTP POST with payload (link, not file)
S3 + pre-signed URL linkProduction default; everything else above is a special case5 TBWrite to S3; post link to wherever the user sees it

For most workloads, "write to S3 and post a pre-signed URL to Slack" handles every case. The other patterns are special cases for specific destinations.

Slack attachment vs Slack link

Two ways to get a result file into Slack:

Attachment (under 25 MB)

  • File appears inline in Slack with preview
  • Searchable via Slack's file search
  • Counts against your Slack storage quota
  • Permanent (until manually deleted)
  • Best for: small reports, screenshots, summary CSVs

Pre-signed URL link (any size)

  • Posted as a regular message with a clickable link
  • File lives in your S3, not Slack's
  • Time-bounded (e.g. 24 hour expiry)
  • Searchable only in S3, not Slack
  • Best for: anything large, anything time-bounded, anything with strict residency

Most production fleets use the link pattern even for small files — keeps Slack storage clean, handles the residency story, gives a uniform API.

Size limits across the stack

Plan around the smallest constraint in your pipeline. Common ones:

LayerLimitWorkaround when you hit it
Sandbox disk (default)30 GBConfigure up to 16 TB; or mount S3 instead of copying
EC2 user-data16 KBDon't put data in user-data; use S3 or files API instead
Files API single upload1 GBSwitch to multipart upload
Multipart upload chunk5 GB max, 5 MB minStay in 8-100 MB range for best parallelism
Multipart total file5 TBSplit file across multiple S3 keys
Slack file attachment1 GB API, 25 MB practicalUse pre-signed URL instead
Email attachment25 MB at most providers; 10 MB saferPre-signed URL link
SQS message body256 KBUse S3 pointer pattern: store in S3, send the key in SQS
SNS message body256 KBSame as SQS
Lambda response (sync)6 MBAsync invocation; or write to S3 and return a URL
Lambda response (async)256 KBSame workaround
API Gateway request body10 MBPre-signed URL for direct upload
API Gateway response10 MBReturn a URL; client downloads
HTTP request via Anthropic / OpenAI~25-32 MBFor larger files, use the Files API of the provider
Notion file upload5 MBExternal link instead

The pattern: when in doubt, use S3 + a URL. Almost every layer of the stack passes URLs cheaply; few pass large bytes cheaply.

Worked example: CSV in, PDF out

The full pipeline. An operator uploads a sales CSV; the agent processes it, builds a quarterly variance PDF, posts to Slack with a download link. Walk through the whole thing.

The setup (one-time, by your IT person)

  1. S3 bucket agent-uploads in your region with SSE-KMS enabled and 30-day lifecycle on incoming/, 90-day on results/.
  2. S3 ObjectCreated event configured to push to SQS queue agent-events.
  3. Sandbox Platform receiver pulls from agent-events and routes events to the agent's workspace.
  4. Slack app installed; agent has chat:write scope.

The flow

Operator UI → PUT → S3 (incoming/) → event → SQS → pull → Agent → PUT → S3 (results/) → URL → Slack 8 minutes end-to-end, $0.18 total

Step 1: Operator uploads (UI)

javascript — operator's web app
// 1. Operator clicks Upload in the operator app
async function uploadCsv(file) {
  // 2. App requests a pre-signed URL from your backend
  const { upload_url, key } = await fetch("/api/issue-upload-url", {
    method: "POST",
    body: JSON.stringify({ filename: file.name, content_type: file.type }),
  }).then(r => r.json());

  // 3. App uploads directly to S3 — no AWS creds in the browser
  await fetch(upload_url, {
    method: "PUT",
    headers: { "Content-Type": file.type },
    body: file,
  });

  // 4. Show progress: "Upload complete; agent will start in <30 sec"
  showStatus("Upload complete. Result will be posted to #ap-summary.");
}

Step 2: S3 event triggers the agent

S3 ObjectCreated event fires automatically. Routed via SQS to the agent's event receiver. Agent's sandbox auto-resumes if it was idle (8-15 sec). Agent reads the event from its inbox.

Step 3: Agent processes

python — agent's processing logic
import boto3, pandas as pd, os
from reportlab.lib.pagesizes import letter
from reportlab.platypus import SimpleDocTemplate, Table, Paragraph
from slack_sdk import WebClient

s3 = boto3.client("s3")
slack = WebClient(token=os.environ["SLACK_TOKEN"])
BUCKET = os.environ["AGENT_BUCKET"]

def handle_event(event):
    key = event["s3"]["object"]["key"]
    run_id = key.split("/")[1]

    # Download the CSV
    local = f"/workspace/{run_id}/input.csv"
    os.makedirs(os.path.dirname(local), exist_ok=True)
    s3.download_file(BUCKET, key, local)

    # Process — pandas + your domain logic
    df = pd.read_csv(local)
    variance = df.groupby("region").agg({"actual": "sum", "budget": "sum"})
    variance["variance"] = variance["actual"] - variance["budget"]

    # Build PDF
    pdf_path = f"/workspace/{run_id}/q1-variance.pdf"
    doc = SimpleDocTemplate(pdf_path, pagesize=letter)
    elements = [
        Paragraph("Q1 2026 Sales Variance Report", styles["Title"]),
        Table([variance.columns.tolist()] + variance.values.tolist()),
    ]
    doc.build(elements)

    # Upload result to S3
    result_key = f"results/{run_id}/q1-variance.pdf"
    s3.upload_file(pdf_path, BUCKET, result_key,
                    ExtraArgs={"ContentType": "application/pdf",
                                "ServerSideEncryption": "aws:kms"})

    # Generate 24-hour pre-signed download URL
    download_url = s3.generate_presigned_url(
        "get_object",
        Params={"Bucket": BUCKET, "Key": result_key},
        ExpiresIn=86400,
    )

    # Post to Slack
    slack.chat_postMessage(
        channel="#ap-summary",
        text=f"Q1 variance report ready: {download_url}",
        unfurl_links=False,
    )

    # Clean up local files; sandbox returns to idle
    os.system(f"rm -rf /workspace/{run_id}")

Step 4: Operator clicks the link

Slack message posts in #ap-summary. Operator clicks the link; downloads the PDF directly from S3 (the pre-signed URL bypasses any auth). PDF lands; operator reviews; ready for the executive meeting.

Cost breakdown

ComponentCost per run
S3 upload (8 MB CSV) + 30-day storage$0.0002
SQS message + Lambda receiver$0.0001
Sandbox compute (ab0t.medium, ~6 min active)$0.004
Model API (Claude Sonnet, ~15K tokens)$0.045
S3 result upload (200 KB PDF) + 90-day storage$0.00005
Slack message$0
KMS encryption operations$0.0001
Total~$0.05 per run

Wait — the lead said $0.18. The $0.18 is the higher-end with Claude Opus for higher-stakes runs and a larger CSV (50 MB). The $0.05 is the typical case with mid-tier models. Both are dramatic compared to the human alternative (1-2 hours of analyst time at $80/hour fully loaded = $80-160).

When not to copy: mount S3 directly

For datasets larger than 5 GB or for read-mostly workloads, copying the whole file is wasteful — the agent reads 100 MB and you transferred 50 GB. Mount the S3 bucket as a filesystem instead.

bash — mountpoint-s3 (AWS-recommended)
# Install mountpoint-s3 (one-time, in your custom Docker image)
sudo yum install -y mount-s3

# Mount the bucket read-only at /mnt/datasets
mkdir -p /mnt/datasets
mount-s3 your-datasets-bucket /mnt/datasets --read-only --region us-west-2

# Agent reads files as if local — only the bytes accessed are pulled
ls /mnt/datasets/q1-2026/      # lists S3 objects
head /mnt/datasets/q1-2026/sales.csv   # downloads first ~64 KB

# Unmount when done
umount /mnt/datasets

mountpoint-s3 is AWS's official tool. s3fs is the older alternative (POSIX-compatible but slower). Pick mountpoint-s3 for read-mostly workloads; s3fs only when you need POSIX semantics (random write, file locks).

When to mount vs copy:

Encryption: three layers

LayerWhat it coversHow to enable
Sandbox disk (EBS)Files on the sandbox's local disk; survives stopDefault on; configurable to customer-managed KMS for stricter compliance
S3 bucketsFiles in your buckets — uploaded, results, mounted datasetsSSE-S3 default; SSE-KMS for compliance regimes; bucket policy enforces it
In-transitHTTPS for all uploads/downloads; TLS for S3 accessDefault; bucket policy can require aws:SecureTransport=true
Application-levelPer-file encryption with a key your app holdsCustom; rare unless required by regulation

For HIPAA / financial / GDPR-strict deployments, layers 1+2+3 with customer-managed KMS keys is the standard. Document in the BAA / DPA. The platform supports this configuration; ask sales for the compliance addendum.

Data residency

If your customers have data-residency requirements (EU GDPR, AU local-first, US-government-only), the rule is: data goes where the bucket is. Constrain everything to the right region:

Document the data-flow diagram for compliance audits. The platform's audit log captures the region of every operation — useful evidence for a regulator.

Cleanup: don't accumulate cruft

Most production AI agent fleets have one of two cleanup failures: disks fill up, or S3 buckets accumulate stale data. Both are preventable.

Sandbox disk cleanup

S3 lifecycle policies

terraform — S3 lifecycle
resource "aws_s3_bucket_lifecycle_configuration" "agent_uploads" {
  bucket = aws_s3_bucket.agent_uploads.id

  # Delete incoming/ files after 30 days (operator already got the result)
  rule {
    id     = "clean-incoming"
    status = "Enabled"
    filter { prefix = "incoming/" }
    expiration { days = 30 }
  }

  # Move results/ to cheaper storage after 90 days; delete at 365
  rule {
    id     = "archive-results"
    status = "Enabled"
    filter { prefix = "results/" }
    transition {
      days          = 90
      storage_class = "STANDARD_IA"
    }
    expiration { days = 365 }
  }

  # Auto-abort failed multipart uploads after 24 hours
  rule {
    id     = "abort-incomplete-multipart"
    status = "Enabled"
    abort_incomplete_multipart_upload {
      days_after_initiation = 1
    }
  }
}

Cost math at scale

ComponentPer run1K runs / month100K runs / month
S3 PUT (upload)$0.000005$0.005$0.50
S3 storage (8 MB × 30 days)$0.000005$0.005$0.50
SQS message$0.0000004$0.0004$0.04
Sandbox compute (6 min ab0t.medium)$0.004$4$400
Model API (15K tokens Sonnet)$0.045$45$4,500
S3 PUT (result)$0.000005$0.005$0.50
S3 storage (200 KB × 90 days)$0.0000005$0.0005$0.05
S3 GET (operator downloads)$0.0000004$0.0004$0.04
Slack post$0$0$0
Total~$0.05~$50~$5,000

The model dominates; the file plumbing is essentially free. Optimize the model first (cheaper model, smaller prompts, prompt caching) before optimizing the file plumbing.

Real-world patterns from the field

Pattern 1: Daily competitive intel snapshot

An agency. Cron fires at 6am. Agent fetches each competitor's pricing page, screenshots, runs visual diff. Each screenshot lands in S3 at s3://comp-intel/{date}/{competitor}/screenshot.png. Diffs land at s3://comp-intel/{date}/{competitor}/diff.png. Slack message at end-of-run links to a summary HTML page. Total daily storage: ~50 MB; lifecycle deletes after 90 days.

Pattern 2: Invoice-processing fleet

An accounting firm. Vendor invoices arrive via email at ap@firm.com. Email gateway extracts attachments to S3 at s3://invoices/{date}/{invoice-id}.pdf. S3 event triggers the AP-clerk agent. Agent reads the PDF, extracts line items, posts to NetSuite, writes processed metadata to S3 at s3://invoices/{date}/{invoice-id}.json. Originals retained 7 years (financial compliance); processed metadata 1 year.

Pattern 3: ML training data pipeline

An ML team. Operator drops training data (multi-GB Parquet files) to s3://training-data/raw/. ML preprocessing agent reads via mountpoint-s3, transforms in chunks, writes processed data to s3://training-data/processed/. GPU sandboxes (ab0t.gpu) read from processed/, train models, write checkpoints to s3://training-data/checkpoints/. The whole pipeline is event-driven; each stage triggers the next via S3 events.

Pattern 4: Customer-facing report generator

A SaaS startup. End user clicks "generate report" in the app. App writes the request to s3://app-requests/{user_id}/{request_id}/input.json. Agent processes, writes the PDF report to s3://app-results/{user_id}/{request_id}/report.pdf. App polls the result location; once present, generates a pre-signed download URL with the user's session as audience binding (only that user's session can access). User clicks; gets the report. Mean time end-to-end: 45 seconds.

Pattern 5: Audit log export

A compliance team. Quarterly cron fires. Agent queries audit log API, exports to a date-range tar.gz, signs it with the firm's GPG key, uploads to s3://audit-exports/{quarter}/audit-log.tar.gz.gpg. Posts download link + GPG signature to #compliance Slack. Compliance officer downloads, verifies signature, archives in long-term storage. The whole pipeline is one agent, one cron, one S3 bucket.

Anti-patterns: what not to do

How this compares to other AI sandbox platforms

PlatformFile primitivesBest for
Sandbox PlatformFiles API, multipart, scp/rsync (via SSH), S3 mount, S3 pre-signedMaximum flexibility; bring your own storage
E2BFilesystem API, env upload, no native S3 mountCode execution; smaller payloads
ModalVolumes (persistent), network filesystems, S3 mountML workloads with persistent state
DaytonaWorkspace persistence; standard SSH/scpDev environments; developer-centric
BrowserbaseBrowser file downloads to S3; no general filesystemBrowser-only workloads
Fly MachinesVolumes; S3-compatible Tigris storeEdge-compute workloads

For AI agent workloads specifically, the patterns in this guide work across most of these platforms — the S3-pre-signed-URL pattern is platform-agnostic. Sandbox Platform's differentiation is the breadth of primitives (you can use any of the four upload patterns) and the auto-stop / persistence story (files survive stop without paying for compute).

Frequently asked questions

What's the largest file I can upload to a sandbox?

Via the multipart upload API: 5 TB per file in 5 GB chunks. Via the simple files API: 1 GB per request. Via scp/rsync over SSH: bounded only by sandbox disk size (default 30 GB; configurable). Via S3 pre-signed URL: 5 TB per object. For files over 1 GB, prefer S3 pre-signed URLs or multipart — they don't block sandbox memory and resume gracefully on network blips.

How do I avoid copying a 50 GB dataset into the sandbox just to read 100 MB?

Don't copy. Mount it. The sandbox supports S3 mount via mountpoint-s3 or s3fs — the agent reads from S3 paths as if they were local files; only the bytes the agent actually reads are pulled. For datasets over 5 GB or for read-mostly workloads, mounting beats copying every time. Reserve full copies for write-heavy or random-access workloads.

What's the right way to send results from the agent back to the operator?

Five patterns by destination. (1) Slack: agent posts a message with the file attached (under 25 MB) or a link to S3 (over). (2) Email: SES or Postmark with the file attached or a download link. (3) Linear / Jira / Notion: API write with file attachment. (4) Webhook back to your application: agent POSTs to your callback URL with the result payload. (5) Just write to S3 and notify: results land in s3://your-bucket/results/{date}/{run_id}/, agent posts the URL to Slack. Pattern 5 scales the best and is what most production fleets use.

Do files persist across sandbox stop/start?

Yes — anything on the sandbox's disk survives. Stopped sandboxes don't accrue compute charges; storage continues at $0.10/GB-month. Files in /workspace and /data and /home/ec2-user all survive. Files in /tmp do not — that's RAM-backed in some configurations. Use /data for state that should survive; /tmp for scratch. See Persistent Workspaces for the full survives-stop inventory.

How do I encrypt files at rest?

Three layers. (1) Sandbox disk: EBS volumes use AWS-managed KMS encryption by default; configurable to customer-managed KMS for stricter compliance. (2) S3 buckets the agent writes to: enable SSE-KMS or SSE-S3 on the bucket; the agent's writes inherit the encryption. (3) Application-level: the agent can encrypt with a per-file key before writing (rare; usually overkill if (1) and (2) are in place). HIPAA / financial deployments require all three layers documented in the BAA.

How does data residency work for cross-region requirements?

Sandboxes deploy to specific AWS regions (us-east-1, us-west-2, eu-west-1, ap-southeast-2). Data written to a sandbox's local disk stays in that region. Data written to S3 stays in the bucket's region. For EU-residency requirements, deploy sandboxes to eu-* regions and use eu-* S3 buckets — no data crosses an Atlantic boundary. The platform's workspace settings let you constrain new sandboxes to a region whitelist.

Can I stream large file uploads instead of buffering them?

Yes — the multipart upload API supports streaming. Each 5 MB chunk is uploaded as it's read; the receiver assembles the file. This is the right pattern for files over a few hundred MB or for slow-network uploads. The Python SDK wraps it; raw HTTP works too. For S3 destinations, boto3's TransferManager handles multipart automatically for files over 8 MB.

What if the agent's task takes longer than the upload?

Decouple. Don't upload-then-process inline. Pattern: (1) operator uploads file to S3 with a known prefix; (2) S3 ObjectCreated event fires; (3) event triggers the agent (via the platform's event integration); (4) agent processes the file from S3; (5) agent writes result. The agent never blocks on upload. For very large files (50+ GB), the agent might process while the upload continues — file is partially available; agent reads what's there.

How do I let an end user download a file the agent generated?

S3 pre-signed URL is the standard pattern. The agent writes the result to a private S3 bucket, then generates a pre-signed URL with a 24-hour expiry, then posts the URL to wherever the user can see it (Slack, email, your app's UI). The user clicks; downloads directly from S3 without authenticating. Pre-signed URLs are time-bounded so a leaked link only stays valid for the expiry window.

What size limits should I plan for?

Sandbox disk default: 30 GB (configurable up to 16 TB). EC2 user-data: 16 KB hard cap (this is why you don't put datasets in user-data). Multipart upload: 5 TB max file, 5 GB max chunk. Slack file attachment: 1 GB max via API, 25 MB practical for most channels. Email attachment: 25 MB at most providers; 10 MB safer. SQS message body: 256 KB (use a pointer to S3 for larger payloads). Lambda response: 6 MB sync, 256 KB async. Plan around the smallest constraint in your pipeline.

How do I handle compressed datasets?

Three options. (1) Decompress before upload — operator does it; the sandbox sees raw files. Best when the operator already has decompressed data on hand. (2) Upload compressed; decompress in the sandbox — faster upload, more sandbox CPU. Best for big text data (logs, CSVs) where compression ratios are 5-10×. (3) Stream-decompress while reading — the agent reads the compressed file directly using gzip or zstd modules. Best for read-once large datasets where you don't need the decompressed form on disk.

What about binary files like images, video, PDFs?

Treat them like any other file — upload via the same patterns. The agent's harness usually handles binary natively (Claude Code's tools include image and PDF reading). For very large media, use S3 mount to avoid copying full files; for batch processing, tar.gz the directory and upload once. Image inputs to vision models are typically encoded inline (base64) but the original file lives on disk for reuse.

Can multiple agents share a workspace's files?

Yes — within a workspace. Mount a shared EBS volume read-write across agents (with file locking discipline) or use a shared NFS mount. For coordination, agents can use sqlite with WAL mode or post lock notes to a shared file. For larger fleets, S3 + event-driven coordination is more reliable than filesystem-shared.

How long do pre-signed URLs stay valid?

Configurable up to 7 days (S3's hard limit). 24 hours is the typical default — long enough for human review, short enough to bound leaked-link exposure. For programmatic access (your app downloading on behalf of a user), shorter (15-60 minutes) is better. For "send a result to a customer they'll review later," 24-72 hours.

What happens to files when I delete a sandbox?

Files on the sandbox's disk are deleted with the sandbox. Files in S3 (and any other external stores) persist independently. To delete the S3 files too, configure lifecycle rules or use the platform's "purge" API which sweeps both sandbox-local and configured-S3-prefix files. For compliance regimes that require certified deletion, the platform supports cryptographic erasure (delete the KMS key the data was encrypted with).

What's next

Wire your first file pipeline

The platform's S3 integration is configurable in the dashboard. The patterns here are reproducible.

Open Dashboard