Kotoba

Where to start

What Kotoba isWho she is and where she runs, in one page
InstallingOne package, two commands
First runA key, a model, her first words
The approval gateHow she asks before she acts
The two voice modesLocal voice, or the agent tunnel
The soul fileChange who she is
RoadmapWhat grows next, and what was cut on purpose

Or a section

↑↓ move openesc close124 pages
All pages

Vision1 of 5

What she can see

About 6 minutes to read

On this page

Kotoba has vision. An image reaches her in one of two ways: you hand her one, or a tool she runs gives one back. Both end up in the same place — an input_image part on the request that goes to your model provider — and both are subject to caps that are enforced in code, not suggested.

Nothing here has an OCR step or a local vision model. She sees an image because the provider's model sees it. That is worth knowing before you send it anything.

Handing her an image

WhereHowWhat it accepts
BrowserThe paperclip in the chat composerAny image/*, plus PDF and plain-text files
Terminal/attach ~/Downloads/shot.png, or ctrl-v.png .jpg .jpeg .webp .gif .pdf ride the turn; any other text file is copied into her working folder for read_file instead. ctrl-v takes PNG, JPEG, GIF or RIFF bytes off the system clipboard
DiscordAttach it to your messageAnything whose content type starts with image/, plus PDF

The browser sends the file to POST /api/session/{session_id}/attachment as a base64 data URL. The terminal and the Discord bridge call the same in-process store directly. In all three the part waits in memory until the next turn goes out, and that turn carries it.

The terminal is the odd one out for a reason a Wayland session forces: a paste hands a terminal application text and only text, so the CLI shells out to wl-paste, xclip or pngpaste and reads the clipboard itself. With none of the three installed, ctrl-v says so instead of doing nothing.

The caps, and what happens at each one

LimitValueWhat you get when you hit it
Files in one message4The browser gets HTTP 409 and the sentence "I can only carry 4 files in one message — send this one and I'll take the next lot after it." The terminal prints the same sentence and draws no sent row. Nothing is stored.
Size, browser14,000,000 bytes of data URLHTTP 413, attachment too large. Base64 costs a third, so the real ceiling on a picture is about 10.5 MB.
Size, terminal14,000,000 bytes, checked twiceOnce against the file, once against the data URL — so again about 10.5 MB. She says the file is too big and tells you to leave it in her working folder, where read_file can reach it.
Size, Discord8,000,000 bytes of the attachmentThe name goes on a refused list she is told about, so she says out loud she could not take it.
PDFs, browser5 pagesThe page count is guessed in the browser before the upload; a longer PDF is refused there.

The per-message cap bounds one message, not the session. Send the first four, then send the rest.

After the turn

Two stores hold what you shared, and they answer different questions.

  • The pending queue is drained by the turn that uses it. One shot, gone.
  • The shared list keeps the last 3 images of the session, up to 40,000,000 bytes across all sessions, so the model has a handle it can quote back later. It is shown base64 it can never repeat, so without this list "save that picture I sent" had nothing to point at. The newest image is never the one evicted.

In local voice mode the pending queue is drained only by a typed turn. A spoken turn, and the silence turns that fire when you say nothing, leave it alone — otherwise a sentinel turn would eat the upload before the message it belonged to went out. The browser sends an attachment as a typed message, so this is invisible in normal use.

Both stores live in the backend's memory. Restart the backend and both are gone. In the browser and on Discord, an image you attach is never written to disk unless she saves it herself with remember_image. The terminal is different: /attach and ctrl-v also copy the picture into her file library under shared/, which is what lets her open it again days later.

Images she gets herself

Nothing captures on a timer or in the background. An image arrives only as the result of a tool call inside a turn, and today that means MCP tools: the browser server's screenshot, or any MCP tool whose result carries an image block. No built-in tool takes a picture of anything.

Two caps apply at that door:

  • at most 4 images out of one tool call;
  • an image over 10,000,000 base64 characters (about 7.5 MB) is dropped, and the text of the result is kept.

A dropped or capped image is silent to you: the tool result is still delivered, just without it.

Whatever survives is filed into her Files library and logged as a session capture.

Two tools return images without being captures: view_capture and recall_image re-open a picture that already exists. The loop excludes both by name from the filing step, or every recall would duplicate the image in Files.

What the model actually receives

A tool result carrying images is not sent as a string. It becomes a content-part list: one input_text part with the result text, then one input_image part per image at detail: "auto". An image with no text of its own still leads with a placeholder line, so no part is empty.

That request goes to whichever provider you configured — OpenAI or xAI today, or any endpoint that speaks the same Responses API. The image leaves your machine. See Privacy and deletion.

Elision: images do not stay in view

Within a single turn the loop rewrites its own history to keep the token cost down.

Every browser view — a snapshot, a screenshot, a run_code or evaluate page inspection — is replaced by this stub as soon as a newer one arrives:

[Earlier browser view elided to save context — take a fresh browser_snapshot for current refs.]

Only the latest browser view survives, and the stub replaces the whole result, image parts included. Ordinary browser calls like navigate and click are small and are left alone.

Across turns, nothing survives at all. Conversation history is reloaded from the turns table, whose content column is text; the words she wrote about a picture come back, the picture does not. The pending attachment queue was drained by the turn that used it.

So the state a follow-up question meets is this:

What was in front of herStill there next turn?What brings it back
An image you attachedNoremember_image(source="attachment") still reaches the bytes through the shared list. At the terminal, view_capture reaches it too
A screenshot from a toolNoview_capture(file) — it is on disk in Files
A keepsake she savedNorecall_image(query)

This is the entire reason the two stores in the next two pages exist. Without them, "what was in that post you just read?" has one honest answer: send it again.

Next: Session captures.