---
title: "Filesystem"
url: "https://control.actana.ai/docs/architecture/filesystem"
description: "A Project's files are the directory on the Core; file bytes cross HTTPS routes, never the core link."
updated: 2026-09-01T09:02:53+00:00
---

A Project's files are the directory on the Core's machine. There is no file table, no per-file id, no cached tree. A path plus a Project root is the whole address space. `readdir`, `stat`, and `open` are the query engine. Anything the Panel remembered about that tree would be stale the moment a harness wrote.

The address of a file is `(projectId, relative path)`. Project-relative, POSIX-shaped, `/`-separated. `""` and `"."` both mean the Project root. Two consecutive requests naming the same path are two independent questions.

Exactly one machine validates paths: the one that owns the disk. The Panel forwards `..`, an absolute path, and a symlink escape exactly as written and lets the Core refuse them. A confinement check on the Core is an accident guard, not a sandbox — whoever can call this surface can also open a VM Shell Session.

## Bytes cross HTTPS, not the core link

File bytes travel on the Core's **HTTPS origin** — the same `https.Server`, port, client certificate, and bearer as the [core link](/docs/architecture/core-link). Not one byte of a transfer goes through a core-link frame. Chunking a multi-gigabyte upload into JSON would stutter every terminal sharing that socket, and base64 would cost a third of the wire.

mTLS alone is not the gate. The client certificate says a client was paired at some point; the bearer says the pairing is still current. Both are required, the same as the core link.

A Core that serves these routes announces `files: { version: 1 }` on `ready`. Absent means no file surface — withheld, not broken, never "needs update".

| Route | Does |
| --- | --- |
| `GET /v1/projects/:id/files?path=` | a file's bytes, or a directory as streamed `application/x-tar` |
| `PUT /v1/projects/:id/files?path=` | write a file, or unpack a tar at that path |
| `GET /v1/projects/:id/files/list?path=` | the tree as chunked NDJSON |

A folder crosses as one streamed tar. One write transfer per Project (`409 transfer-in-progress`); reads are unrestricted and concurrent. Type against `project.files.list` / `.upload` / `.download` on [`@actana/sdk`](/docs/reference/sdk), not the routes.

## The Panel is a dumb pipe

The Panel streams the browser's body through to the Core and the Core's answer straight back. It buffers nothing, unpacks nothing, and validates no path. It holds the mTLS credentials because no browser can present a client certificate.

## See also

- [Architecture](/docs/architecture) — where this surface sits.
- [Core link](/docs/architecture/core-link) — control plane; no file bytes.
- [HTTP surfaces](/docs/reference/http-surfaces) — `/v1/...` on the mTLS server.
- [SDK](/docs/reference/sdk) — `project.files.*`.
