Attachments
Introduction
Files in Laraclaw flow in two directions: into the agent (a photo you sent on Telegram, a PDF emailed to the bot, a voice note in Slack) and out of the agent (a chart it generated, a TTS reply, a report it wrote).
We need a single, predictable place to keep both, and we need it to be safe — the agent's tools shouldn't be able to scribble all over the message-attachment lifecycle. That's what the attachments disk is for.
Let's break down how it works.
The Big Picture
Every file that flows through Laraclaw lives on a single Laravel disk: the attachments disk. Inbound files land in one folder, outbound files land in another. Both folders are scoped by the UUID of the message that triggered them.
There is no laraclaw_attachments table. The filesystem is the source of truth, and the message UUID is the primary key.
The Attachments Disk
LARACLAW_ATTACHMENTS_DISK=local
This is a Laravel filesystem disk name from config/filesystems.php. The default is local. Point it at S3, an NFS mount, or whatever you need — the only requirement is that every process touching Laraclaw (web nodes, queue workers, the imap:watch listener) can read and write the same disk.
!IMPORTANT If you run multiple nodes, this must be a shared disk. A connector worker on node A may write
inbound/{uuid}/photo.jpg, then a queue job on node B picks up the message and needs to read it. A local disk per node will silently lose files in that handoff.
Inbound Files
When a connector receives a message with files, it generates a UUID for the message and saves each file under:
{LARACLAW_INCOMING_ATTACHMENTS_PATH}/{uuid}/{filename}
The default incoming path is inbound, so a Telegram photo lands at inbound/01J9.../photo.jpg. The UUID is also written onto the IncomingMessage DTO, so any tool the agent runs in response to this message can find its inputs by asking the Attachments service for inbound($uuid).
Each connector handles its own download mechanics:
- Telegram uses the Telegram Bot API to fetch each photo size, voice note, and document.
- Slack generates the UUID before downloading so the file API call writes straight into the right folder.
- Email parses the IMAP message and writes each MIME attachment.
- API accepts uploaded files in the multipart request body.
- Terminal has no inbound attachments.
!NOTE Inbound folders are not cleaned up automatically. They stay on disk until you remove them. If you want a reaping policy, schedule something against the modified time — for example, "delete
inbound/*directories older than 30 days."
Outbound Files
Tools that produce files write them to:
{LARACLAW_OUTGOING_ATTACHMENTS_PATH}/{uuid}/{filename}
The default outgoing path is outbound. Tools never specify a UUID directly — they call $attachments->outbound($message->uuid)->set($filename, $contents), and the service computes the path from the current message's UUID. Pretty clean, right?
When the agent finishes its turn and the connector is ready to reply, it lists everything in outbound/{uuid}/, attaches it to the outgoing message, and (for connectors that delete after sending) clears the directory:
| Connector | Outbound cleanup |
|---|---|
| Telegram | Deletes outbound/{uuid}/ after the reply is sent |
| Slack | Deletes outbound/{uuid}/ after the reply is sent |
Deletes outbound/{uuid}/ after the reply is sent | |
| API | Does not delete — your client is responsible for fetching the files and cleaning them up |
| Terminal | Deletes outbound/{uuid}/ after rendering |
Protected Paths
Both inbound/ and outbound/ are added to BaseTool::isProtectedPath(). The agent cannot list, read, write, move, copy, or delete anything inside them through the File Manager tool — even though the same disk might be allowlisted by LARACLAW_ALLOWED_DISKS.
Tools that legitimately need to touch attachments (saving an inbound file, queuing an outbound one) do it through the dedicated Attachments service, not the File Manager. This separation keeps the agent from accidentally clobbering a delivery in flight.
This is the only protection. The rest of the disk is fair game for the File Manager. See File Manager for the details on locking down what else the agent can reach.
Configuration
LARACLAW_ATTACHMENTS_DISK=local
LARACLAW_INCOMING_ATTACHMENTS_PATH=inbound
LARACLAW_OUTGOING_ATTACHMENTS_PATH=outbound
The two path values are folder names within the attachments disk, not disk-relative URIs. Override them if inbound or outbound collides with something else you store on the same disk.
What's Next
- File Manager — the tool that reads and writes everywhere except these protected folders.
- Image Manager — generates outbound images.
- Text to Speech — generates outbound audio replies.
Until next time!