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

# File references

> Reference files, folders, and media inline with @path, and how the model receives them.

Type `@path` anywhere in a message to hand Ziro a file or folder. Ziro reads it
for the model before its first step, so you do not have to wait for a tool call.
Example: `explain @src/app.py and @docs/shot.png`.

## Syntax

* `@path` or `@"path with spaces"`.
* The `@` must be at the start of the input or after whitespace, so an email
  address or `user@host` stays plain text.
* A path that does not exist stays literal text. Nothing is attached and nothing
  is reported. The exception is a quoted path with no closing quote (the picker
  leaves `@"my dir/` open while you drill down): it stays text with a notice.
* Several references per message are fine, up to `max_refs_per_turn` (default
  20\). Extra references stay as text and a notice says so.
* `deny_paths` entries (secrets, policy files, `.git`) and gitignored paths are
  refused with a notice.
* A path outside the project (absolute, `~/...` or `../...`) works for media
  files: `@~/Pictures/shot.png`, `@C:\Users\me\Desktop\shot.png`. Text files and
  folders outside the project are refused with a notice, because the agent can
  only read inside the project. A project file that is a symlink to somewhere
  outside is refused too. Network paths (`\\server\share\...`) are always
  refused, without contacting the server. This works where you type the message yourself: the
  TUI, the plain REPL, and `-p "<message>"`. Piped `-p` input, queued jobs and
  the Python SDK stay limited to the project, because their text may come from
  someone else.

## Picker

Typing `@` in the [TUI](https://docs.ziro-agent.com/interfaces/tui.md) opens a fuzzy picker over the project's
files and folders. It reads `git ls-files` (falling back to a directory walk) and
never lists gitignored or denied entries.

| Key | Action |
| - | - |
| `Tab` or `Enter` | Insert the highlighted entry, replacing only the `@token` |
| `Enter` on a folder | Insert `@dir/` and keep the picker open to drill down |
| `Esc` | Close the picker |

While the picker is open, `Enter` selects instead of sending. The plain REPL and
`-p` parse `@` references but have no picker.

Dropping a file onto the terminal window inserts it as an `@` reference
(project-relative inside the project, the full path outside it). Pasted text
that is not a list of existing file paths is inserted as is. Paths that VS Code
formats for PowerShell (`& 'C:\...'`), Git Bash (`/c/...`) or WSL (`/mnt/c/...`)
are recognized too.

To paste a screenshot straight from the clipboard, press **Ctrl+V** (or
**Alt+V**, if your terminal keeps Ctrl+V for itself). The image is saved as a
PNG in a private `ziro-paste` folder (your temp directory on Windows, `~/.cache` on Linux and macOS) and inserted as an
`@` reference; files copied in Explorer or Finder are inserted as their paths.
Saved images older than a day are removed on the next paste. On Linux this
needs `wl-paste` (Wayland) or `xclip` (X11).

## What the model gets

| Reference | Delivered as |
| - | - |
| Text file | Pre-read through `read_file` as a synthetic tool round, with no approval prompt |
| Folder | Listed through `glob_files`, skipping ignored directories such as `node_modules` and `.venv` |
| Image (png, jpg, jpeg, gif, webp) | Native image content |
| Audio (wav, mp3) | Native audio content |
| Video (mp4, webm, mov) | Native video content |
| PDF | Native file content |

If the agent has no filesystem tools, the content is inlined into the message
instead. If a `deny` permission covers `read_file`, the reference is refused with a
notice.

The model receives media only if it accepts that kind. Otherwise it gets a text
stub naming the file, and for PDF and Office files the skill to use. Switching
models mid-thread re-filters history, so images come back when you move to a
vision model.

The agent's own `read_file` on a media file returns the media too. On Office and
other binary files it returns a stub naming the skill to use.

Media bytes are stored per thread under the data directory (`media/<thread>/`)
and deleted with the thread.

## Provider support

| Provider | Accepted input |
| - | - |
| OpenRouter | Whatever the model's catalog entry advertises |
| Anthropic | Image and PDF |
| OpenAI | `gpt-4o`, `gpt-4.1`, `gpt-5`, `o3`, `o4` families: image and PDF. Model ids containing `audio` also take audio |
| Custom providers | Text only, unless `input_modalities` is set |

When the capability is unknown, Ziro sends the media anyway. If the provider
rejects it (HTTP 400 or 422 that is not a context overflow), the turn is retried
with the media as text, with the notice `model rejected media; sent as text`. That
model and kind is remembered for the rest of the thread.

> [!WARNING]
>
> Not yet verified on providers that strictly validate tool-call history, such as
> Gemini 3 models that require a thought signature on every tool call. The
> synthetic `read_file` round for a text or folder reference carries no
> signature, so such a provider may reject the whole request with HTTP 400. If
> that happens, reference media only, or paste the text into the message.

To declare what a custom provider accepts, set `input_modalities` in
`~/.ziro/custom_providers.json` (`$ZIRO_HOME/custom_providers.json` when `ZIRO_HOME` is set):

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "my-llava": {
    "style": "openai",
    "base_url": "http://localhost:8000/v1",
    "models": ["llava"],
    "input_modalities": ["text", "image"]
  }
}
```

## Files outside the project

Reference outside media directly with `@`, as above, at any point in the
conversation. You can also drag a file onto the terminal window: the TUI turns
the dropped path into an `@` reference.

`--attach <path>` does the same from the command line, in an interactive session
or with `-p`. It is repeatable and applies to the first message.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
ziro --attach ~/Downloads/diagram.png
ziro -p "describe this" --attach ~/Downloads/diagram.png
```

Outside the project only media files are accepted. Inside the project the
gitignore and deny rules apply as for `@`. The old `/img` command is gone; typing
it prints a notice that points you to `@path`.

## Notices

Anything Ziro skips, refuses, or downgrades produces a notice:

| Surface | Where it appears |
| - | - |
| TUI | Dim lines in the transcript |
| `-p` | stderr, as `[ziro] <notice>` |
| `--json` | A `notices` array in the output; queued jobs store `notices` too |

## Configuration

`attachment_policy.yaml` (the `attachments` section):

| Field | Default | Meaning |
| - | - | - |
| `enabled` | `true` | When false, media (image, audio, video, PDF) is not attached, whether referenced with `@`, `--attach`, or read by the agent. Text files and folders still work |
| `max_refs_per_turn` | `20` | References expanded per message |
| `max_bytes` | image `4000000`, audio `10000000`, video `20000000`, file `20000000` | Raw byte cap per kind. A partial map merges over the defaults |

The legacy `max_image_bytes` field is still read at runtime and sets the image cap; `ziro config doctor` flags it and asks you to move it to `max_bytes.image`. The other pre-`@` fields (`allowed_image_types` and the `*_notice` texts) are ignored. `allowed_image_types` no longer narrows anything: every image type Ziro recognises can be attached. Any other unknown key is ignored with a warning.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.