> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cycls.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Files and the canvas

> Every user gets a workspace of files. The canvas renders spreadsheets, documents, slides, PDFs, images and HTML without leaving the chat.

Each user of an agent gets a file tree on the volume mounted at `/workspace`. The
`Editor` and `Bash` tools read and write it, the files panel lists it, and the
canvas renders one file at a time beside the conversation.

## Where files live

```
/workspace/<user or workspace scope>/
  report.md              files the user keeps
  data/sales.csv
  apps/burnup/           apps the agent built
  skills/<name>/         skills the user created
  .tmp/                  scratch, hidden and cleaned up
  .db/                   chat store, not readable from the sandbox
```

Paths in tools are workspace relative. `~/report.md` and `/workspace/report.md`
both resolve to the same file, traversal outside the workspace is refused, and
`/tmp` raises an explicit error because every bash command gets its own `/tmp`
that no other tool can see.

Scratch belongs in `.tmp/`. It is hidden from the files panel and cleaned up, so
intermediate downloads and working data do not clutter what the user sees.

## Opening a file on the canvas

The `Canvas` tool opens one finished file in the side panel.

```python theme={null}
llm = cycls.LLM().allowed_tools(["Bash", "Editor", "Canvas"])
```

From a custom loop, or from your own code, the same action is a `ui` event:

```python theme={null}
yield {"type": "ui", "action": "open_canvas", "path": "report.xlsx"}
```

## What the canvas renders

| File type                                                        | Rendered as                                             | Where the work happens |
| ---------------------------------------------------------------- | ------------------------------------------------------- | ---------------------- |
| `csv`, `tsv`, `xls`, `xlsx`, `xlsm`, `ods`                       | interactive grid with sheet tabs                        | in the browser         |
| `docx`                                                           | formatted document with pages, fonts, tables and images | in the browser         |
| `ppt`, `pptx`, `odp`                                             | slide viewer with a thumbnail rail                      | office render service  |
| `doc`, `odt`, `rtf`, `epub`                                      | read-only PDF                                           | office render service  |
| `pdf`                                                            | PDF viewer                                              | in the browser         |
| `md`, `html`, images, audio, video, `glb`, `gltf`, code and text | native viewer                                           | in the browser         |
| anything else                                                    | download card                                           |                        |

Rendering is read-only. Any failure, including an unreachable service or an
unparseable file, falls back to the download card rather than an error page.

## Configuring office rendering

Spreadsheets and `.docx` render from raw bytes in the browser and need no
service. Presentations and the PDF fallback call a shared render service,
configured with two environment variables:

```bash theme={null}
OFFICE_RENDER_URL=https://office-render.cycls.ai
OFFICE_RENDER_SECRET=your_shared_secret
```

With either unset, those two paths return a download card and everything else
keeps working.

LibreOffice is about 1GB and is used by a small fraction of turns, which is why
it runs in one shared service rather than inside every agent image. Rendered
output is cached under a hidden `.cache/office/` directory in the workspace, so
reopening a deck does not repay the conversion.

## Uploads

Users attach files in the composer. The loop ingests them before the model call:
images and PDFs go to the model as media when `.vision(True)`, and every
attachment is written into the workspace so tools can open it.

```python theme={null}
web = cycls.Web().max_upload(256)    # MB per file, default 512
```

For text-only models, set `.vision(False)`. The attachment stays in the
workspace and the model receives a note naming the file, which it can then read
with a tool.

## The HTTP API

Files are also a REST surface, which is what the files panel and mobile clients
use. All routes require auth.

| Method   | Path                 | Purpose                                            |
| -------- | -------------------- | -------------------------------------------------- |
| `GET`    | `/files?path=subdir` | list a directory                                   |
| `GET`    | `/files/{path}`      | download, add `?download` for an attachment header |
| `PUT`    | `/files/{path}`      | upload, multipart                                  |
| `PATCH`  | `/files/{path}`      | rename, body `{"to": "new/path"}`                  |
| `POST`   | `/files/{path}`      | create a directory                                 |
| `DELETE` | `/files/{path}`      | delete, which moves the file to trash              |

Deleting moves a file to trash and keeps it restorable for thirty days through
`GET /trash`, `POST /trash/{id}/restore` and `DELETE /trash/{id}`.

## Sharing

```
POST   /share                  create a share link for a chat
GET    /share                  list share links
DELETE /share/{token}          revoke
GET    /shared/{user}/{token}  the public page
POST   /shared/{user}/{token}/fork   copy the conversation into the viewer's workspace
```

A shared chat carries the files it references, so a recipient can open the
artifact without an account. Forking lands in the forker's active workspace.

## Next

<Card title="Connectors and MCP" icon="plug" href="/agents/connectors">
  Let a user connect an account, then give tools the credential.
</Card>
