Skip to content

Add a shared filesystem for Daytona and Modal - #37

Merged
Nabhag8848 merged 12 commits into
mainfrom
feat/sandbox-filesystem
Nov 3, 2026
Merged

Nabhag8848 merged 12 commits into
mainfrom
feat/sandbox-filesystem

Conversation

@Nabhag8848

@Nabhag8848 Nabhag8848 commented Nov 1, 2026 •

Copy link
Copy Markdown
Member

Summary

  • Daytona and Modal share one filesystem on the sandbox handle. Lifecycle stays on the handle. The eight calls are read, write, copy in, copy out, make a directory, list one directory, stat, and remove.
  • A sandbox path must be absolute. A relative path fails in Kit before either provider is contacted. Read and write move bytes. Copy uses a path on the caller's disk.
  • Go WriteFile normalizes only a nil buffer to an empty slice before the provider call. A non-nil empty slice is left alone.
  • parents defaults to true, so creating a directory also creates missing parents. Set it to false when the parent must already exist. Daytona rejects that value. Modal requires the parent and returns an error if the new directory is already there.
  • TypeScript Daytona read, write, and copy pass a timeout as a whole number of seconds. 2000 is two seconds. 1500 is rejected, and the field is timeout. An omitted timeout on those four calls is passed as 0, so Daytona adds no transfer limit and the abort signal carries the client deadline. Directory, list, metadata, and delete calls use the abort signal, so 1500 ends the call at 1.5 seconds. Every Go file call uses the context as the deadline. Modal file calls use the context or the abort signal.
  • A missing file on copy from the local machine is not found. A local directory is an invalid argument. The field is local_path. Daytona and Modal, in Go and TypeScript, make that decision before the provider call. Copy onto the machine creates a missing local parent directory on every provider. A local destination that is already a directory is an invalid argument with the same field. Go Daytona creates that parent in Kit because Daytona's DownloadFile only calls os.WriteFile.
  • Go Daytona stat and list read the toolbox file record. The result keeps path, permissions, owner, and group when Daytona sends them. modifiedAt keeps a fractional timestamp. An empty field stays unset, and an unreadable modifiedAt is omitted. TypeScript Daytona already mapped those fields.
  • Each call has its own Go and TypeScript example. Those examples open a sandbox that already exists and perform that one call.
  • go.work.sum keeps only the checksums written by loading the workspace packages. go work sync and go list -m all walk the full google.golang.org/grpc requirement graph and download go.mod files for modules the build never imports, including plot, PDF, font, and example modules named by gRPC test requirements. sandbox-kit test go rebuilds that sum from package loads and fails when the committed file drifts. Update a module's dependencies inside that module with GOWORK=off (go get, then go mod tidy).

How a call moves

flowchart LR
  caller[Caller]
  files[Filesystem on the handle]
  path[Absolute path check]
  gate[One call at a time on this handle]
  adapter[Daytona or Modal adapter]
  native[Native SDK]

  caller --> files --> path --> gate --> adapter --> native
Loading

A relative path, a blank path, or a path that contains a NUL byte stops at the path check. The provider is not called. An omitted timeout uses the client deadline. Explicit zero adds no Kit deadline.

TypeScript Daytona read, write, and copy pass the timeout to Daytona as a whole number of seconds. 2000 milliseconds is 2 seconds. 1500 milliseconds is 1.5 seconds, so those calls reject it. The field is timeout. An omitted timeout, and an explicit 0, are passed as 0. Daytona adds no transfer limit. The abort signal carries the client deadline. A read with that 0 uses Daytona's multi-file download for the one path, because downloadFile(path, 0) drops the 0 and keeps Daytona's 30-minute default. 2000 still uses downloadFile(path, 2). makeDirectory, listDirectory, stat, and remove use the abort signal, so 1500 ends the call at 1.5 seconds. Every Go file call uses the context as the deadline, so 1500 * time.Millisecond ends the call at 1.5 seconds. Modal file calls use the context or the abort signal.

One method, two native calls

flowchart TB
  subgraph kit [Sandbox Kit]
    readFile[ReadFile]
    writeFile[WriteFile]
    copyIn[CopyFromLocal]
    copyOut[CopyToLocal]
    mkdir[MakeDirectory]
    listDir[ListDirectory]
    stat[Stat]
    remove[Remove]
  end

  subgraph daytona [Daytona SDK]
    download[DownloadFile]
    upload[UploadFile]
    folder[CreateFolder]
    listFiles[ListFiles]
    details[GetFileInfo]
    deleteFile[DeleteFile]
  end

  subgraph modal [Modal SDK]
    readBytes[ReadBytes]
    writeBytes[WriteBytes]
    fromLocal[CopyFromLocal]
    toLocal[CopyToLocal]
    makeDir[MakeDirectory]
    listFilesM[ListFiles]
    statM[Stat]
    removeM[Remove]
  end

  readFile --> download
  readFile --> readBytes
  writeFile --> upload
  writeFile --> writeBytes
  copyIn --> upload
  copyIn --> fromLocal
  copyOut --> download
  copyOut --> toLocal
  mkdir --> folder
  mkdir --> makeDir
  listDir --> listFiles
  listDir --> listFilesM
  stat --> details
  stat --> statM
  remove --> deleteFile
  remove --> removeM
Loading

Go Daytona list and stat call the toolbox ListFiles and GetFileInfo directly. The high-level Daytona file type omits path on stat, and it omits permissions, owner, group, and a fractional modifiedAt on both calls.

Creating a directory

parents defaults to true. An omitted flag and an explicit true both create missing parents.

flowchart TB
  omitted[parents omitted or true]
  strict[parents false]
  daytonaOk[Daytona creates missing parents]
  modalOk[Modal creates missing parents and accepts an existing directory]
  rejected[Daytona rejects the call. Field is parents]
  modalStrict[Modal requires the parent. An existing directory returns an error]

  omitted --> daytonaOk
  omitted --> modalOk
  strict --> rejected
  strict --> modalStrict
Loading

Modal accepts a directory that already exists when parents is true. Daytona returns the provider error when it already exists. Remove deletes a file or an empty directory unless recursive is set. Stat on a symlink describes the symlink. Modal can report a symlink. Daytona reports a file or a directory. permissions, owner, and group are set when the provider supplies them. modifiedAt is set when the provider supplies a time.

Copying a local file

A missing file passed to copy from the local machine is not found before the provider reads it. A directory at that path is an invalid argument. The field is local_path. Daytona and Modal, in Go and TypeScript, do this before the native copy call.

Copy onto the machine creates a missing local parent directory for Daytona and Modal in both languages. A local destination that is already a directory is an invalid argument with the same field. Go Daytona creates the parent in Kit before DownloadFile, because that SDK write uses os.WriteFile only.

Where it sits

flowchart TB
  client[Client]
  handle[Sandbox handle]
  life[Lifecycle: start, stop, terminate, delete]
  fs[Filesystem]

  client --> handle
  handle --> life
  handle --> fs
Loading

Closing the client or the handle does not delete the files or the sandbox.

Test plan

  • GOWORK=off go test in sdks/go/sandbox, sdks/go/providers/daytona, and sdks/go/providers/modal
  • npm test in sdks/typescript/sandbox, sdks/typescript/providers/daytona, and sdks/typescript/providers/modal
  • Compile the new Go and TypeScript filesystem examples
  • Confirm a relative sandbox path fails before the provider is called
  • Confirm Daytona rejects parents: false before the provider is called, and Modal requires the parent to exist
  • Confirm TypeScript Daytona read, write, and copy reject 1500 milliseconds and pass 2000 as 2 seconds
  • Confirm an omitted TypeScript Daytona read passes 0 through the multi-file download, and write and copy pass 0
  • Confirm a Go file call and a TypeScript list call accept 1500 milliseconds as the deadline
  • Confirm a missing local file is not found with field local_path, and a local directory is an invalid argument with the same field
  • Confirm Go and TypeScript Modal, and Daytona, reject a local directory on copy before the provider call
  • Confirm copy onto a missing local parent creates that parent for Go Daytona, TypeScript Daytona, and Modal
  • Confirm Go Daytona stat and list keep path, permissions, owner, group, and a fractional modifiedAt when Daytona sends them
  • Confirm Go WriteFile turns only a nil buffer into []byte{} before the provider call
  • Confirm sandbox-kit test go fails when go.work.sum contains checksums that loading the workspace packages does not write
  • Live Daytona and Modal runs are still manual: each example needs credentials and an existing sandbox id

Nabhag8848 and others added 12 commits November 1, 2026 22:42
Callers read, write, copy, list, and remove files through one handle, and a bad sandbox path fails before either provider is contacted.

Co-authored-by: Cursor <cursoragent@cursor.com>
Entire-Checkpoint: 01M6C1K9KW5FNR21W2BZS6BWZ3
Callers set parents to false when the parent must already exist. Daytona rejects that value because it always creates parents.

Co-authored-by: Cursor <cursoragent@cursor.com>
Entire-Checkpoint: 01M6C2XQHGRKY18E31FB5F53EK
…seconds, and classify local copy failures as local path errors.

Co-authored-by: Cursor <cursoragent@cursor.com>
Entire-Checkpoint: 01M6C55VEFDKRAEPJEGEGXQ4BH
…er limit, and classify a local copy path before the provider call.

Co-authored-by: Cursor <cursoragent@cursor.com>
Entire-Checkpoint: 01M6DBNWVAXVAMBM4EC9SS52CW
Co-authored-by: Cursor <cursoragent@cursor.com>
Entire-Checkpoint: 01M6DBV2MFRNGSQPGPKD9BEDCN
Co-authored-by: Cursor <cursoragent@cursor.com>
Entire-Checkpoint: 01M6E8MGMP5YKH6CDDZFVZ6R8P
…e, and errors.

Co-authored-by: Cursor <cursoragent@cursor.com>
Entire-Checkpoint: 01M6E9DP0J5EETXYBSZZF2QRGR
go work sync and go list -m all walk the full grpc requirement graph and record go.mod checksums for modules the build never imports; sandbox-kit test go now rejects that drift.

Co-authored-by: Cursor <cursoragent@cursor.com>
Entire-Checkpoint: 01M6EDB2EZ9MBSYEEPPZPTBVMD
Keep PathError mapping on copy only, and align naming and website path docs with the filesystem surface.

Co-authored-by: Cursor <cursoragent@cursor.com>
Entire-Checkpoint: 01M6EQHY2KBN7Z420A2Q52VP2H
Daytona's DownloadFile only calls os.WriteFile, so Kit creates the parent first and matches Modal and TypeScript Daytona.

Co-authored-by: Cursor <cursoragent@cursor.com>
Entire-Checkpoint: 01M6ERM5E74H2FH29EDWMSCBDA
Co-authored-by: Cursor <cursoragent@cursor.com>
Entire-Checkpoint: 01M6G0HDXNMWEXE4XHEAM8D589
Keep guides, godoc/JSDoc, and Daytona TypeScript copy-out parent creation
consistent with the eight file operations both providers implement.

Co-authored-by: Cursor <cursoragent@cursor.com>
Entire-Checkpoint: 01M6G1G8268BH20WQZFPK2TTE9
@Nabhag8848
Nabhag8848 merged commit 0f8946f into main Nov 3, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant