Update, September 2026: images and voice now need a paid plan. The free plan no longer includes a cloud machine or panel chats — free is hub access from your own tools (The free plan becomes the hub).
You're looking at a stack trace in a screenshot. The agent is one keystroke away. And the only thing you can do is retype what you can already see.
That gap closes on September 1. Attach or paste an image and the agent looks at it. Hold the mic and talk instead of typing. Send a photo or a voice note from Telegram, Slack or WhatsApp and it lands in the same session your browser has open — and the answer can come back spoken.

Every AI product ships this. Almost none of them ship it well, and the ways it goes wrong are documented in public, in bug trackers, by name.
Nine ways attaching an image goes wrong
Before writing a line we went reading. These aren't hypotheticals. Each one is a filed issue with people in the thread, and four of them are about Claude Code images.
The image is re-sent on every turn until the session dies. One audit of 312 sessions found 171.5 MB — 33.8% of all transcript bytes — was image data, invisible in both /context and /usage (claude-code#86421). You pay for that screenshot again on turn 40.
One oversized image bricks the session permanently. Resume it and it fails forever; the only escapes are /compact, which burns tokens, or /clear, which loses the work (claude-code#55040, #59095).
The model can't see, so the image is silently stripped. Confirmed across Anthropic, Google and OpenAI provider paths: neither you nor the model is told anything was removed (pi#3429). You get a confident answer to a question the model never received.
Switch models mid-chat and the history poisons the next call. Old image blocks get re-sent to a text-only model, which 404s (cline#9819).
The error outlives the image. Cursor keeps failing with "model doesn't support images" after you remove the image, because the prior context is cached. A whole new chat is the only fix (forum thread).
Attach while the agent is busy and the text sends without the image (claude-code#86980, #83729).
Silent lossy recompression, no opt-out, keyed to file bytes rather than pixels — so a 4K PNG arrives blurry while a pre-compressed one arrives sharp (claude-code#56236, #58233).
Voice: no chance to fix the transcript before it sends. ChatGPT removed the preview step and users revolted — "voice recognition is not perfect… users should have the final say in what is sent" (community thread).
Text-to-speech reads markdown aloud — literal backticks and asterisks instead of "code block" (claude-code#69687, open-webui#6911).
Two products get a piece of this right and deserve the credit. Mistral emits an explicit (image omitted: model does not support images) rather than stripping in silence. LiteLLM and OpenRouter publish LLM vision capability up front — supports_vision, input_modalities — so a client can disable the attach button instead of discovering the problem after a paid call.
Each failure above became a constraint. Here's what came out of them.
Media travels as a reference, never as bytes
This is the one decision everything else follows from, and it wasn't taste — it was forced.
We persist every frame in a session verbatim into a mediumtext column, with no truncation anywhere in the write path. So a base64 image inside a frame is a base64 image in the database, on every replay, forever. That's the mechanism behind the 33.8% number, and it's not a bug anyone can patch out later. It's what happens when bytes are allowed into the transcript at all.
So they aren't. The upload goes to the control plane, the bytes land on the machine's own working disk, and what crosses the wire is an opaque ID:
{
"type": "message",
"session_id": "…",
"content": "why is this failing?",
"attachments": [
{
"id": "kQ8xR2mL9vTnP4wZ7hJ3bC6d",
"kind": "image",
"media_type": "image/png",
"name": "build-error.png",
"size": 291043
}
]
}Replaying a 200-turn session with 40 screenshots costs exactly what replaying a text one costs. The 33.8% failure cannot happen here by construction, which is a stronger guarantee than a size limit.

It also means the attachment is a real file. It lands in ~/.octomind/media/ on the machine, so the agent can open it, the shell can run identify or ffprobe over it, and Files can download it. It's machine data with a machine's lifetime — not a blob buried in a conversation you can't reach into.
The ID is 24 characters of opaque alphanumerics, and the agent validates that shape before it touches the filesystem. A crafted frame can't name a path.
Nothing is ever silently dropped
Two things can make an attachment unusable, and neither is allowed to be quiet.
The model can't see. Capability travels in the model catalog, so the panel knows before you do. The attach button is disabled on a text-only model and the tooltip names one that works. If you get past that — because you switched models with a slash command inside the machine — the turn is refused by name, before the machine wakes and before a paid call: "This model can't see images — switch models to attach one." The upload stays valid, so retrying after switching costs no second upload.

The agent is too old to understand attachments. This is the subtle one. The frame format is additive, and the deserialiser on the other end ignores fields it doesn't know. An older agent would accept a frame carrying an image, drop the media, and answer the text as though nothing were missing — the exact failure in pi#3429, reproduced by us on our own protocol.
So we never send and hope. The agent advertises message_attachments_v1 when it binds, and an attachment turn is refused unless it did. Not inferred from the image tag, either: a bring-your-own image follows no tag convention, and a long-running container can predate a catalog update. We gate on what the agent said, this run.
Unknown reads as unsupported, deliberately. Over-refusing costs you one restart. Under-refusing costs you an answer that was confidently wrong about a picture nobody looked at.
Voice in: you get the last word
Speak, and the words arrive in the composer as text you can still edit. Nothing sends until you press send.
That's the whole design, and it exists because of the ChatGPT thread above. Transcription is very good and not perfect, and the failure mode of auto-send is that a misheard word becomes an instruction. For voice coding that means: hold the mic, talk, let go, read it, fix the one word it got wrong, send.

Recording stops itself after 15 seconds of silence, and hard-stops at two minutes. A recording that transcribes to fewer than three words never wakes a machine — a stray tap costs nothing, because a machine wake plus a model call isn't what an accidental press should buy.
We keep the recording itself on disk beside the transcript, so a disputed transcript can be played back.
Voice out: only where a voice makes sense
Send a voice note from Telegram or WhatsApp and the answer can come back as a voice message you can play with your phone in your pocket. That's the feature: a walk becomes a working session.
Two things we didn't do.
We don't read markdown aloud. We transform a reply full of backticks, pipes and asterisks before speaking it — a fenced block is announced as "a 12-line code block", a table as "a table with 4 rows", a link as its text. If what remains isn't worth saying out loud, nothing is spoken and the text still arrives.
We don't speak in the panel. You're looking at the screen. The text is already there and it's faster to read. Spoken replies are delivered where the platform actually carries them, and they're off until you turn them on — Settings has one control, with summary and full, and the text always comes too. Never instead of it.

One session, every door
None of this is a separate mode. A screenshot pasted at your desk at 09:14 and a voice note sent from your phone at 12:40 are the same session, on the same machine, with the same files and the same branch. Open the tab again at six and all of it is still there.
That's the part no chatbot can copy, and it isn't because of the media work. It's because the machine was already yours and already persistent. Images and voice just gave you more ways to reach it.
What it costs
Images are on every plan, at no extra cost. There's no per-attachment charge and no storage charge — the disk is already reserved and hard-capped by your machine size. Images cap at 5 MB, audio at 8 MB, four attachments to a turn.
Voice is on paid plans, metered like everything else here: $0.010 a minute to listen and $0.025 a minute to speak, on top of the model. Speaking costs more than listening because generating audio genuinely costs more than transcribing it. Billing rounds to the second, with a 10-second floor on a clip, and it shows up in the same spend view as everything else.
If our speech provider is down or unconfigured, we tell you it's on us — not that you need a bigger plan. Those are two different sentences, and a paying customer should never see the wrong one.
Also new since the last update
Ask for a URL, get a URL. Tell your agent "make me a landing page and give me a link" and it comes back with one: it claims a public endpoint on its machine, starts the server detached so it outlives the chat, and hands you https://<token>.octomind.cloud. Endpoints live on the machine's panel page too — private by default (only your account's signed-in users), public when you mean the whole internet, with an off switch that keeps the URL and a release that retires it forever. Paid plans, 5 per machine, 5 / 20 / 50 per account on Pro / Max / Team.
The mechanism is worth spelling out, because it's the start of something: the cloud itself now runs an MCP server, and every machine ships with a built-in cloud capability wired to it. When the agent claims that endpoint, it's making an ordinary MCP tool call — through the same walls you see in the panel, never further. Endpoints are the first cloud tool on that path; machine control and routines are the obvious next ones. Nothing to configure — the capability loads itself when the task calls for it.
Credits unlock every model on Free. The subscription buys machines — models were never the privilege. Any credit balance on the free plan now opens the entire catalog, billed from credits. Run it dry and you're back on the included roster with nothing broken mid-session. The roster itself grew to five: qwen3.8-flash joins glm-5.3-flash, deepseek-v4-flash, gemma-4-31b and gpt-oss-20b.
Routines from your chat apps. /routines lists them, runs one now, pauses and resumes — from Telegram, Slack or WhatsApp. Completed runs deliver the full report into the chat rather than a bare "done", with long output clamped to fit the platform instead of truncated at a byte boundary. Times render in the routine's own timezone.
Routine notifications are yours to choose. Each routine now carries its own notification preference, so a noisy hourly check and a weekly report don't have to shout equally.
octomind 0.46 through 0.48. 0.46 is a deletion release: verifier recovery tracking and mutation outcome contracts in, a net −1,884 lines of supervisor surface out. 0.47 is the release that carries attachments on the wire — the one this post is about. And 0.48 collapses every model setting into one inherited [model] block and makes bare octomind the whole command. Config migrates itself on first run across all of it, so there's nothing for you to edit.
Fixed: a routine whose turn never started could settle early and report a run that hadn't happened; endpoint routing tightened its domain validation; and a failed model call now comes back as one clear sentence with a traceable reference code, never a raw dump of an upstream provider's internals.
Getting it
New machines are on 0.48.0 now. Existing ones pick it up on their next rebuild, with files, sessions and settings untouched — and until a machine is on it, image turns are refused by name rather than quietly degraded. Voice doesn't wait on any of that: transcription runs in the control plane, so the machine only ever receives text.
Open a session, paste a screenshot, and ask it what it sees.



