~ / freestyle-team ❯ The Best Way to Move Local Files Into a Sandbox
Getting local files into a sandbox sounds like a setup chore. It is actually one of the first architecture decisions in an agent product.
If the transfer path is awkward, every agent workflow inherits the awkwardness. A user uploads a folder, but symlinks disappear. A coding agent receives a ZIP, but nobody knows what changed. A browser agent starts from a copied checkout, but the next turn needs the user's latest local edits. A code interpreter gets one file at a time, but the task really needed a whole repo, test fixtures, lockfiles, dotfiles, and generated assets.
The right answer depends on the shape of the work. Git is best when the sandbox should preserve history, branches, diffs, and review. scp is best when a human needs to push a small thing quickly. rsync is best when the same directory needs to be synchronized repeatedly. Archives are best when you need one portable bundle. File APIs are best when your product is moving a few known files as part of a controlled workflow.
The important part is choosing deliberately. "Upload files to sandbox" is not one feature. It is several workflows wearing the same name.
Start with the sandbox
File transfer gets much easier when the destination behaves like a normal machine.
Freestyle VMs are full Linux virtual machines for long-running, complex tasks. The docs show VMs running shell commands with vm.exec(), reading and writing files through vm.fs, resizing CPU, memory, and storage, cloning Git repositories, accepting SSH, stopping and starting, and forking from the current running state. The lifecycle docs also describe VMs that can be stopped with disk preserved, started again later, or kept running with idleTimeoutSeconds: null when a workload should stay alive.
That matters for file transfer because the usual developer tools assume a real operating system on the other end. git clone, scp, rsync, tar, unzip, package managers, test runners, and editors all expect normal Linux paths, users, permissions, processes, and SSH behavior.
Freestyle VMs are the most powerful VMs for AI agents: they are hardware virtualized, they can run forever when configured to stay running, and they run real Linux instead of a narrow upload-and-exec sandbox. That gives you a boring but valuable property: the way you move files onto the machine can match the way developers already move files onto machines.
Option 1: Git for project state
Use Git when the local files are really a project.
That includes most AI coding agent workflows, app builders, eval harnesses with test fixtures, documentation generators, and any sandbox task where the answer should eventually become a branch, patch, pull request, or reviewed artifact. Git is not just a transport. It is the structure around the work.
The flow is straightforward:
git remote add sandbox <repo-url> git push sandbox main
Inside the sandbox:
git clone <repo-url> /workspace/project cd /workspace/project
For products, Freestyle Git is the natural companion to a VM. The Freestyle VM docs show creating a Git repository, granting an identity scoped access, minting a token, and cloning the repository inside a VM over native Git. The Freestyle Git docs show the same principle from the repository side: create a repository by API, grant read or write access to an identity, issue a token, and use normal git clone against git.freestyle.sh.
Git is the best answer when the sandbox must answer questions like:
- What changed?
- Which agent made the change?
- Can the user review it before accepting it?
- Can we branch before trying a risky edit?
- Can we roll back to the last known good state?
- Can multiple agents work from the same starting point?
Git is weaker when the input is not source-controlled, is too large for normal Git, or is meant to be a temporary blob rather than a durable workspace. For large binary data, build artifacts, cache directories, and one-off uploads, use a different transfer path and keep Git focused on source, manifests, lockfiles, prompts, scripts, and reviewable output.
Option 2: scp for quick copies
Use scp when a developer or operator needs the fastest possible manual path from a laptop to a sandbox.
Freestyle VMs accept SSH through vm-ssh.freestyle.sh. The SSH docs show connecting with a VM ID and a scoped access token, including formats for a default VM login and for a specific Linux user. Once SSH works, scp works the way people expect:
scp ./script.py <vm-id>:<token>@vm-ssh.freestyle.sh:/tmp/script.py scp -r ./fixtures <vm-id>:<token>@vm-ssh.freestyle.sh:/workspace/fixtures
This is excellent for debugging. A developer can copy a repro case into a VM, SSH in, run it, inspect the environment, and delete the VM later. It is also useful for support workflows where an engineer needs to put a diagnostic script, log bundle, or patch file onto a specific machine.
The problem with scp is that it does not give you much product structure. It copies bytes. It does not know whether a file is new, changed, deleted, reviewed, accepted, or stale. Recursive copies can also be surprisingly blunt: they may include local caches, ignored build outputs, credentials in dotfiles, or platform-specific junk unless you filter carefully before copying.
So the rule is simple: scp is a great human escape hatch and a weak source of truth. Keep it available for operational workflows, but do not make it the backbone of an agent workspace if the product needs history or review.
Option 3: rsync for repeated syncs
Use rsync when the same local directory needs to be mirrored into the sandbox more than once.
This is common when a user has a local project open and wants the sandbox to track ongoing edits. It is also useful for eval development: you change fixtures locally, sync them into a clean VM, run the suite, adjust, and sync again.
The command shape looks like this:
rsync -az --delete \ --exclude node_modules \ --exclude .git \ ./ \ <vm-id>:<token>@vm-ssh.freestyle.sh:/workspace/project/
rsync is more surgical than scp. It can avoid retransferring unchanged files, delete remote files that were deleted locally, preserve directory structure, and exclude obvious noise. For large projects where only a few files change between turns, that difference matters.
It also has a sharper edge. --delete is useful because it makes the remote directory match the local directory. It is dangerous for exactly the same reason. If the agent has created useful files inside the sandbox, a blind sync from the laptop can erase them. If your product supports two-way collaboration between a local user and an agent, plain rsync is not enough. You need a merge model, a branch model, or an explicit ownership boundary for generated files.
Use rsync for one-way synchronization into a sandbox. Use Git when both sides are going to make meaningful changes.
Option 4: tar, zip, and unzip for bundles
Archives are the most portable way to move a snapshot of local files into a sandbox.
They are useful when the input is a bundle, not a living workspace: a starter template, a benchmark fixture, an uploaded project, a dataset sample, or a package of files from a browser upload form. A web app can receive the archive, store it, copy it into the VM, and unpack it:
mkdir -p /workspace/import tar -xzf upload.tar.gz -C /workspace/import
or:
unzip upload.zip -d /workspace/import
Archives are also a good boundary for security review. You can inspect the file list before extraction, reject absolute paths, reject .. traversal, limit total uncompressed size, block suspicious symlink targets, and decide where the bundle is allowed to land.
The downside is that archives are snapshots. They do not naturally express future changes, review history, or a clean way for an agent to hand edits back. They are good for import. They are not a complete collaboration protocol.
If a user uploads a ZIP of a project and then expects an agent to edit it over time, import the ZIP into the VM, initialize or push it into Git, and continue from there. The archive gets the files across the boundary. Git gives the work a durable shape.
Option 5: file APIs for controlled inputs
Sometimes the best transfer method is not a developer tool at all.
The Freestyle VM docs show vm.fs.writeTextFile() and vm.fs.readTextFile() for working with files directly through the SDK. That is the right interface when your product controls the file shape. For example, a product might write a generated input.json, a single Python script, a prompt file, or a config file into a known path before calling vm.exec().
File APIs are clean when the number of files is small and the product owns the schema. They are awkward when users expect to move arbitrary trees. Once you are rebuilding rsync, Git, or archive extraction over a custom API, stop and use the native tool. The whole point of a real Linux VM is that you do not have to reinvent the operating system around the agent.
Pick by workflow, not taste
The practical decision tree is:
- Use Git for source code, branches, diffs, review, rollback, and multi-agent work.
- Use
scpfor quick manual copies into a VM. - Use
rsyncfor repeated one-way syncs from a local directory. - Use
taror ZIP for uploads, imports, templates, and fixtures. - Use file APIs for small product-owned inputs and outputs.
For AI agents, the most common mistake is starting with the easiest upload path and then discovering that the sandbox has become the source of truth. A copied folder becomes a hidden workspace. The hidden workspace accumulates fixes. The fixes need review. The review needs diffs. The diffs need history. At that point you are rebuilding Git badly.
A better architecture is to separate transfer from state. Use the simplest transfer method that fits the moment, but put long-lived project state somewhere reviewable. For most coding workflows, that means Git plus a VM: Git for the code and history, the VM for the running computer.
The bottom line
The best way to move local files into a sandbox is not one command. It is matching the transfer method to the job.
For a serious agent product, the destination should be a real Linux machine. That lets developers use Git, SSH, scp, rsync, archives, and normal shell tools instead of waiting for your product to invent a special case for every file movement. Freestyle VMs give you that machine, and the docs-backed lifecycle around it: commands, files, SSH, start and stop, resizing, forking, and long-running operation when the workload needs it.
Use Git when the files are the work. Use scp, rsync, archives, and file APIs when they are just the way the work arrives.

