Vision
How werbos sees images. /see slash captures a screenshot; --vision-model wires a vision-capable backend; the description gets injected as a SystemMessage so the text+tools model can reason over it.
werbos can take an image and let the model describe it back. Two moving parts:
- Capture — get bytes onto disk + into the next turn. The
/seeslash +werbos.vision.capturepackage do this. - Describe — run the bytes through a vision-capable model and
inject the description as a SystemMessage. The
VisionContextProviderdoes this; you wire it via--vision-model.
You can use either piece without the other. /see without
--vision-model still saves the image and attaches it to the next
turn; the text-only model just can’t see it. --vision-model
without /see works fine when the SPA’s image-upload composer
sends bytes directly.
Capture — the /see slash
Inside the chat REPL:
werbos> /see # interactive region select (drag)
werbos> /see region # same as above
werbos> /see full # silent full-screen capture (all displays)
werbos> /see window # click-to-pick window
werbos> /see clear # drop any pending images without sending
What happens:
- macOS
screencaptureruns with mode-specific flags (-i -xregion,-xfull,-W -xwindow). - The PNG lands at
~/.werbos/captures/cap-<mode>-<ts>-<rand>.png. - The bytes are base64-encoded and attached to the live
ChatSession’s pending-images buffer. - No turn is sent. The next message you type becomes the prompt that consumes the image.
ESC during interactive capture cancels cleanly (zero-byte file
detected and removed; CaptureCancelled raised).
werbos> /see
captured: /Users/you/.werbos/captures/cap-region-20260509-193045-a1b2c3.png (84 KB, region)
describe in your next message; type /see clear to drop it
you> what package is on the table?
werbos> [reads the image, replies]
If --vision-model isn’t set, the slash adds a hint:
captured: /Users/you/.werbos/captures/cap-full-20260509-193117-d4e5f6.png (412 KB, full)
(no --vision-model configured; image will attach but the text model can't see it)
describe in your next message; type /see clear to drop it
The image still attaches and ships on the next UserMessage — the
text-only base model silently ignores image bytes per
UserMessage.images’s pre-existing contract. You’re not losing the
file; you’re just not getting a description from it.
Multiple images per turn
Each /see appends to the buffer. Send multiple captures in a
single turn:
werbos> /see # captures image A
werbos> /see # captures image B
you> compare these two
The buffer drains in order onto the next UserMessage’s images
tuple. The buffer survives until the next AI turn fires — not
until the next text turn. Slash commands don’t drain it.
Describe — VisionContextProvider
The description side wraps a separate vision-capable Backend
(typically BellmanBackend pointed at a vision model) and runs a
one-shot inference. The result is injected as a SystemMessage in
the conversation history; the text+tools model then runs the
user-facing turn against that augmented history.
werbos chat \
--backend ollama \
--model llama3.1:8b \
--vision-model llama3.2-vision:11b
# vision model on a different host (e.g. SR8):
werbos chat \
--backend ollama \
--model llama3.1:8b \
--vision-model llama3.2-vision:11b \
--vision-host http://192.168.1.50:11434
The same flags work on werbos serve so the SPA’s image upload
composer routes through the same path:
werbos serve --vision-model llama3.2-vision:11b
Why a separate provider, not a Backend swap?
The originally-proposed approach was per-turn model swap inside
BellmanBackend. The actual shipped design wraps a separate
backend in VisionContextProvider. Three reasons:
- Tools still work on image turns. Vision-capable Ollama models
reject the
toolsfield; if you swapped the model wholesale for image turns, you’d lose tool use. The provider runs vision as a sidecar and returns text; the main model keeps its tools. - Cache by image hash. ChatSession holds a per-session memo on
sha256(images || user_text)so re-asking about the same image doesn’t re-call the vision model. - Local-first as DATA, not compute. The pixels live on your box; only the description travels to whichever inference backend fits. The vision model and the text model can run on different hosts.
The default vision prompt
You are a vision module describing an image for another AI assistant.
Be precise, factual, and concise (1-3 sentences). Describe what is
visible. Do not speculate, do not interpret intent, do not answer
the user's question. The downstream model will handle that with the
description you provide.
Override with --vision-prompt "..." if you want different framing.
Phase events
When a vision interlude runs, ChatSession emits PhaseEvents that
the SPA renders inline:
vision— “Looking at image…” while the description runsvision_failed— if the vision backend errors (90s timeout, OOM, model not found); the user-facing turn proceeds without vision context (degraded but not broken)thinking— the regular agent loop streams; the SPA hides the vision label as soon as the first token of the user-facing reply arrives
The CLI’s streaming wrapper renders the same events as [Looking at image...] lines.
Audit shape
Every vision interlude appends a KIND_VISION_OBSERVATION event to
the chat’s PFM recorder chain with actor ACTOR_VISION and a
payload containing the image hash + the resulting description. The
description survives across turns via a post-agent history filter
that preserves SystemMessages prefixed with the
_VISION_CONTEXT_PREFIX marker.
This means a multi-turn coherence path: image landed turn 3, user asked a follow-up turn 5, the vision context from turn 3 is still in scope. No re-describe per turn.
Platform support
macOS-only at v1. The capture function raises
CaptureUnsupportedError on non-Darwin platforms with a message
naming Linux/Windows backends as tracked but not shipped. The
public surface (capture_screen, the dataclass, the error types) is
OS-agnostic so a Linux gnome-screenshot or grim implementation
can land later without changing callers.
Pair with the html persona
werbos chat \
--persona-name html \
--vision-model llama3.2-vision:11b
Take a screenshot of a page you want to imitate; describe what you want changed; werbos generates the corresponding single-file HTML informed by the visual context. The HTML persona’s style discipline
- the vision interlude’s description = a sharp single-file deliverable from a screenshot prompt.
See also
- Chat surfaces — the full slash card
- Personas —
--persona-name htmlfor HTML expert mode - Install — pulling a vision model in Ollama