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

# Files

> Receive, pass, persist, serve, and delete files produced by agents.

Use `druks.files` for an agent file that must outlive its sandbox. Druks pulls
the file from the sandbox. It stores immutable bytes under a stable ID and
returns a `File` handle. Apps declare files and decide when to delete them.
Apps do not implement transport or file routes.

## Receive a file from an agent

Annotate the file in the agent output contract. Every nested object must also
inherit `AgentOutput`. The harness needs a strict schema at every object node.

```python theme={null}
from druks.agents import AgentOutput
from druks.files import File


class SurveyShot(AgentOutput):
    screen: str
    viewport: str
    image: File


class SurveyOutput(AgentOutput):
    findings: list[str]
    shots: list[SurveyShot]
```

The harness sees each `File` field as a string path. The agent writes the file
inside its workspace and reports that path. Druks accepts only a regular file
whose resolved path stays inside the workspace. One file can contain at most
10 GiB. A missing path, a symlink escape, or an oversized transfer fails the
agent call.

The returned value has a stable identity and metadata:

```python theme={null}
shot = result.shots[0]
shot.image.id
shot.image.name
shot.image.size
shot.image.content_type
shot.image.url
content = await shot.image.open()
```

`url` is `/api/files/{id}`. The route uses the normal Druks identity gate. It
shows images, PDF, and plain text inline. It downloads all other content. The
route sends `X-Content-Type-Options: nosniff` and validates the file with its
SHA-256 ETag.

## Pass a file to another agent

Pass the `File` as ordinary agent context:

```python theme={null}
analysis = await NightWatch.analyze(image=shot.image)
```

Before the agent starts, Druks copies each stored file into the call directory
in the sandbox. The prompt context receives the sandbox path. A deleted file or
missing stored content stops the agent call before it starts.

## Keep a file on an app row

Use `FileField` for one file reference. It stores a real foreign key to the
platform `files` table and does not copy the bytes.

```python theme={null}
from sqlalchemy.orm import Mapped, mapped_column

from druks.db import Base
from druks.files import File, FileField


class Shot(Base):
    __tablename__ = "night_watch_shots"

    id: Mapped[int] = mapped_column(primary_key=True)
    image: Mapped[File] = FileField()
```

A read of the app row loads the handle metadata in the same query. Access to
`image.id` or `image.url` does not start another query. Use one app row for each
file in a collection.

## Delete a file

If app policy says that the file is no longer usable, delete it:

```python theme={null}
await shot.image.delete()
```

Deletion is immediate for reads and file routes. Druks keeps the row as a tombstone
so an app foreign key remains valid. An hourly task removes only the stored
bytes after a 24-hour grace period.

An interrupted durable operation can leave a file without a reference after an
agent retry. A worker failure can also leave canonical bytes without a row. This
can occur between the rename and the step commit. This is the same retry
boundary as other side effects. Druks does not remove this rare crash data in
version 1.

Files do not cover app-generated bytes, browser uploads, remote storage,
or list-valued fields.
