You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
An agent whose model has no image input support cannot run on a store that contains images. Compiles ship the live window's image blocks (and compression prompts ship the chunk's) to the provider, which rejects the whole request — every turn and every compression, deterministically, until the images age out of the window. There is no configuration surface that says "this model is text-only."
Field repro (2026-09-16, connectome-host main + #108, NanoGPT xiaomi/mimo-v2.5-pro:thinking): a resident with ~3 idle weeks — so images from an older era still sat in the recent window — hard-failed every activation and every hierarchical compression with 400 image_input_not_supported. The carrier-degrade retry path kicked in for one compression lane; nothing else had a fallback. The agent was unrunnable on that model while being perfectly runnable, with the same store, on a multimodal one.
This matters beyond one gateway: the entire local-model world this stack just gained access to (host #108 — Ollama, vLLM, llama.cpp servers) is full of text-only models.
A hardcoded capability list (the NO_TEMPERATURE_MODELS pattern in membrane). That precedent is for a sampling parameter; here the list would silently drop content, which collides with the strategy's own invariant that nothing is dropped silently (fatal coverage invariant; budget-stripped images become loud placeholders). The temperature list's own NB comment documents how one wrong entry silently discarded a valid parameter on the most-used model — a wrong entry here would silently blind a model that can see. Capability lists also cannot keep up with gateway catalogs (NanoGPT alone fronts hundreds of models).
Strip in membrane. Membrane's contract is explicit that its size check is "a true invariant, never a silent editor." Editing content belongs to the context compiler.
Proposed direction
An explicit, recipe-declared capability, enforced at compile time in context-manager, with the store untouched:
context-manager: a strategy option — working name imagePolicy: 'live' | 'placeholders' (default 'live', today's behavior) — that renders every image block as the existing loud text placeholder, in the compiled live window and in compression/merge prompts. A new option rather than the existing knobs because those can't express "none": maxLiveImages: 0 means unlimited, and imageStripDepthTokens is depth-based, so an image in the newest message always leaks through.
connectome-host (companion): agent.vision: false in the recipe (default true), validated at load, mapped to imagePolicy: 'placeholders'. Recipes fail loudly on typos, per the existing validation posture.
The store keeps the images. Chronicle is model-agnostic and append-only; stripping is a render decision. Switching the same resident back to a multimodal model restores sight over the same memories — nothing was lost, it just wasn't rendered. This is also why the ingestion surfaces (e.g. discord-mcpl attachment inlining) need no changes: adapters shouldn't know what model is running.
Acceptance evidence
A store containing image blocks (live window and un-compressed chunks) compiles and compresses under imagePolicy: 'placeholders' with zero image blocks in any outbound request; placeholders are visibly marked, not empty.
An image arriving in the triggering message is placeholder-rendered in that same turn (the depth-based knob's leak case).
Round-trip: the same store compiled under 'live' again renders the original images (store untouched).
Existing image policies (maxLiveImages, imageStripDepthTokens, maxLiveImageBytes, maxCompressionImageBytes) are unchanged under 'live' and moot under 'placeholders'.
Host: vision: false maps through; vision with a non-boolean is rejected at recipe load.
Out of scope
Auto-detection of model capabilities (list- or error-driven) — deliberately, per above.
Sending stripped images to a vision sidecar for text description (the pattern some Discord deployments use); composes on top of this later, doesn't belong in the compiler.
Happy to send both PRs (CM option first, host companion after) if the direction lands.
Problem
An agent whose model has no image input support cannot run on a store that contains images. Compiles ship the live window's image blocks (and compression prompts ship the chunk's) to the provider, which rejects the whole request — every turn and every compression, deterministically, until the images age out of the window. There is no configuration surface that says "this model is text-only."
Field repro (2026-09-16, connectome-host
main+ #108, NanoGPTxiaomi/mimo-v2.5-pro:thinking): a resident with ~3 idle weeks — so images from an older era still sat in the recent window — hard-failed every activation and every hierarchical compression with400 image_input_not_supported. The carrier-degrade retry path kicked in for one compression lane; nothing else had a fallback. The agent was unrunnable on that model while being perfectly runnable, with the same store, on a multimodal one.This matters beyond one gateway: the entire local-model world this stack just gained access to (host #108 — Ollama, vLLM, llama.cpp servers) is full of text-only models.
Why not the obvious alternatives
image_input_not_supportedis NanoGPT's spelling, not an OpenAI-standard code), and this repo has already paid for string-matching provider errors once (membrane fix: MessageStore.get returns slot index as sequence — silently corrupts branchAt #17/fix(kv-control): boundary-cut groups are foldable — protected leaves exempt, not vetoes #48). Also retry-after-strip means the failing bytes were already sent — wasted call, and the "fix" is invisible in config.NO_TEMPERATURE_MODELSpattern in membrane). That precedent is for a sampling parameter; here the list would silently drop content, which collides with the strategy's own invariant that nothing is dropped silently (fatal coverage invariant; budget-stripped images become loud placeholders). The temperature list's own NB comment documents how one wrong entry silently discarded a valid parameter on the most-used model — a wrong entry here would silently blind a model that can see. Capability lists also cannot keep up with gateway catalogs (NanoGPT alone fronts hundreds of models).Proposed direction
An explicit, recipe-declared capability, enforced at compile time in context-manager, with the store untouched:
imagePolicy: 'live' | 'placeholders'(default'live', today's behavior) — that renders every image block as the existing loud text placeholder, in the compiled live window and in compression/merge prompts. A new option rather than the existing knobs because those can't express "none":maxLiveImages: 0means unlimited, andimageStripDepthTokensis depth-based, so an image in the newest message always leaks through.agent.vision: falsein the recipe (defaulttrue), validated at load, mapped toimagePolicy: 'placeholders'. Recipes fail loudly on typos, per the existing validation posture.Acceptance evidence
imagePolicy: 'placeholders'with zero image blocks in any outbound request; placeholders are visibly marked, not empty.'live'again renders the original images (store untouched).maxLiveImages,imageStripDepthTokens,maxLiveImageBytes,maxCompressionImageBytes) are unchanged under'live'and moot under'placeholders'.vision: falsemaps through;visionwith a non-boolean is rejected at recipe load.Out of scope
Happy to send both PRs (CM option first, host companion after) if the direction lands.
🤖 Filed via Claude Code