Vision / multimodal tasks
Loop workers can forward image attachments to vision-capable models. Images are read from disk and sent as OpenAI-style imageurl content parts (base64 data URLs). The router automatically excludes...
Loop workers can forward image attachments to vision-capable models. Images are read from disk and
sent as OpenAI-style image_url content parts (base64 data URLs). The router automatically excludes
text-only workers when a task carries images, so a vision task is never accidentally dispatched to a
model that cannot see it.
cyberine run --image <path> (ke9kx)
The one-command path takes images as a repeatable flag; png/jpg/gif/webp, the file must exist:
cyberine run -p --image ./shot.png "Which button is highlighted in this screenshot?"
cyberine run --image ./a.png --image ./b.png "What changed between these two screenshots?"
cyberine run --bg --image ./mockup.png "Describe the layout of this mockup" # persisted under <runDir>/attachments/What it does, measured 2026-09-07: with no --fleet, the build-fleet auto-router stands down and
the run is pinned to the vision-capable build workers available on this machine (muse here --
WORKER_PROFILES[id].multimodal); the run then takes the single-step compare path, and
orchestrate() drops every worker whose AgentInfo.acceptsImages is false (text-only loop
workers, CLI workers without a native image flag) with a NOTE naming them, so a text-only worker
can never answer "without the image" and look like a pass. A neutral filename PNG (left half red,
right half blue) through muse came back "The left half is red and the right half is blue."
--image refuses a remote server (CYBERINE_SERVER_URL).
Known Limitations and Deferred Work: the implement-fleet shape (--fleet/CYBERINE_BUILD_FLEET
inside a git repo) reads CYBERINE_TASK and does not carry Task.images, so an image task never
gets per-worker worktrees or DiffJudge -- it always runs the single-step compare path and says so.
PDF, audio and video have no adapter support and are not accepted by the flag. Native SDK adapters
(Anthropic, Bedrock, Vertex) leave acceptsImages undefined and are kept in the pool unfiltered.
Mark a task with image attachments by passing images: [{ path: 'relative/path.png' }] on the Task
object, or use the CYBERINE_IMAGES env var with npm run cli:
# One image
CYBERINE_IMAGES=./docs/screenshot.png \
CYBERINE_BUILD_FLEET=loop-openai-build \
OPENAI_API_KEY=sk-... \
npm run cli -- "describe the UI element in this screenshot"
# Multiple images (comma-separated)
CYBERINE_IMAGES=./docs/a.png,./docs/b.png \
CYBERINE_BUILD_FLEET=loop-gemini-build \
GEMINI_API_KEY=... \
npm run cli -- "what changed between these two screenshots?"Vision-capable build-fleet workers (all LoopAgentAdapter-based) include:
| Worker | Default model |
|---|---|
loop-openai-build | gpt-4o |
loop-gemini-build | gemini-2.5-pro |
loop-xai-build | grok-3 |
loop-azure-build | gpt-4o |
loop-copilot-build | gpt-4o |
loop-kimi-build | kimi-k2.7-code |
loop-claude-build | anthropic/claude (via LiteLLM gateway) |
loop-gpt-build | openai/gpt (via LiteLLM gateway) |
CYBERINE_BUILD_FLEET is required because the default compare fleet is text-only (DeepSeek/OpenRouter
etc.). The router also works on Task objects directly:
import { routeTask } from './src/core/router.ts';
import { availableBuildWorkerIds } from './src/orchestrator/buildAdapters.ts';
const decision = routeTask(
{ prompt: 'describe this screenshot', images: [{ path: './screenshot.png' }] },
availableBuildWorkerIds(),
);
// decision.workers contains only vision-capable ids; text-only workers are excluded.When a loop worker is flagged as multimodal it forwards the images; otherwise it omits them and
appends a short note so the model knows attachments were dropped. Set detail: 'low' or 'high' on
a TaskImage to control resolution ('auto' is the default).
DeepSeek vision (T680). DeepSeek's only vision-capable id is deepseek-v4-flash-vision-exp
(shipped 2026-08-21) — deepseek-v4-pro and deepseek-v4-flash both return HTTP 400 "This model
does not support image". Pinning DEEPSEEK_MODEL=deepseek-v4-flash-vision-exp (bare form for
DeepSeek-direct, or deepseek/deepseek-v4-flash-vision-exp when routed through OpenRouter/
opencode) makes loop-deepseek-direct and loop-deepseek-build resolve multimodal: true and
forward images normally:
CYBERINE_IMAGES=./docs/screenshot.png \
DEEPSEEK_MODEL=deepseek-v4-flash-vision-exp \
DEEPSEEK_API_KEY=sk-... \
npm run cli -- "describe the UI element in this screenshot"Discoverable in the catalog (T687). deepseek-v4-flash-vision-exp is now a real entry in
src/core/modelCatalog.ts, so cyberine models deepseek lists it and
cyberine model use deepseek deepseek-v4-flash-vision-exp no longer prints the dim
not in catalog warning. It is listed LAST on purpose — deepseek-v4-pro stays the provider
default.
Vision cost (T683). deepseek-v4-flash-vision-exp is priced in src/core/modelPricing.ts
identically to deepseek-v4-flash on every row — $0.22/$0.66 per 1M input(cache-miss)/output
off-peak, $0.44/$1.32 peak, same 2500 concurrency limit — so there is no vision surcharge and the
TUI cost chip, the oracle tool, and the daily spend cap all bill a vision dispatch correctly.
Images need no separate accounting: DeepSeek converts them to tokens by dimension and bills them
as ordinary input tokens alongside the text (live-confirmed — a real dispatch's usage carries no
image-token field of any kind). Verify with:
npm run selftest:deepseek-peak-pricing # sections 10-11 cover the vision row + the pinned live usage objectKnown limitation / deferred work: deepseek-v4-flash-vision-exp itself still has no verified
cache-HIT rate in modelPricing.ts (k92fe added the cachedInputCostPerMTok field to the table
and filled it for the live deepseek-flash / deepseek-v4-pro rows this id used to mirror, but
the id is dealiased server-side — see the T680/k1k86 notes above — and was left unpriced for cache
rather than guessed), so a heavily-cached vision session still bills every input token at the
cache-miss rate. Also unmodelled: FIM Completion (Beta) is "Not supported" on the vision
model (flash and pro both support it in non-thinking mode) — recorded in
docs/providers/deepseek.md but not enforced anywhere in code, so pinning the vision id and
asking for an FIM completion fails at the provider, not at a Cyberine guard.
Known limitation / deferred work: the multimodal flag is a construction-time flag only —
DEEPSEEK_MODEL must be pinned to the vision id BEFORE dispatch. loop-deepseek-build's WorkerProfile entry
deliberately carries no static multimodal: true (that would let an image task route to it even
when the router resolves its reasoning/flash tier variant, deepseek-v4-pro /
deepseek-v4-flash, neither of which accepts images). So DeepSeek is NOT yet in the "Vision-capable
build-fleet workers" table above and the router's automatic multimodal gate (routeTask) does not
route to it — an image task with no other vision worker available will not auto-select DeepSeek even
when the vision id is pinned. That routing gap is the separate, user-gated T689.
MiniMax mmx — text worker + image/speech tools
The MiniMax mmx CLI is two capabilities in Cyberine: a TEXT worker (mmx-text-build) and two multimedia IMPLEMENTER tools (mmximagegenerate, mmxspeechsynthesize). Auth is flexible — EITHER export...
DeepSeek request isolation — user_id
Every DeepSeek-direct dispatch now carries DeepSeek's userid isolation field, which buys content-safety, KVCache, and scheduling isolation for sub-identities under one API account. On an account...