mind-body: chat images reach the Persona session as image blocks (ChatDelivery v2 image field) #11

Merged
eureka merged 3 commits from eureka/chat-image-consumer into main 2026-11-08 08:12:44 +00:00
Owner

Cut B of Minds reading chat images (follow-up gamecult-chat:follow_up:chat-mind-20261107T195444303Z-t20). Do not merge or deploy until Bifrost cut A emits v2 and a store exists.

Wire (for Bifrost). Pending kind gamecult.persona.chat_pending.v2. A delivery is a 12-element tuple: index 11 is image, nil or [path: str, mime: str, size: u64, sha256: str]. path is relative to the Persona's image store and exactly <lowercase hex sha256 of the room id>/<lowercase hex sha256 of the bytes>[.<1-5 alnum ext>]. mime is exactly image/png|jpeg|gif|webp and must equal what the magic bytes say. size is the byte length, at most 5 MiB. Golden fixtures: tests/fixtures/persona-chat-v3/. v1 pending answers and 11-element records still decode (image: None), and the client accepts both kinds.

Consumer. chat_image::load is the only place a reference becomes bytes; it refuses any path not of that shape or not for this delivery's room, anything whose canonical path differs (symlinks), non-regular files (checked before open, so a FIFO cannot block), over 5 MiB (declared and on disk), a size or sha256 differing from the declaration, non-image magic bytes, and a mime differing from the sniffed type. Only then base64. persona.rs puts a text block then one image block per image into the stream-json user message (at most 4 per line) and adds an attachment role=organ ... status=attached image=N | status=not_shown reason="..." line per image-bearing delivery, with fixed reasons. The echo reader treats a replayed line with blocks as our echo (else the session machinery would lose track of what she was shown). Store root: MIND_PERSONA_IMAGE_STORE (absolute); unset shows no image. No bytes are logged or in an error.

Verification. unshare -Ur cargo test -p mind-body lib, chat_rudp, public_surface: green (760 lib tests; the control and persona_bin integration tests fail here for environment reasons that predate this change). Guards hand-mutated one at a time: every guard's removal fails a test except two recorded as equivalent: take(MAX+1) on the read (the metadata length check already bounds it; only a file growing mid-load differs) and symlink_metadata vs metadata (the canonical-path check already refuses symlinks). The CLI accepted the image-block line shape in a probe, but no model read was observed (no lease in the Self run).

Open. The Persona unit needs read access to the store and the env var set. Images are untrusted input to the model; the Persona instructions should say so (not changed here).

🤖 Generated with Claude Code

Cut B of Minds reading chat images (follow-up `gamecult-chat:follow_up:chat-mind-20261107T195444303Z-t20`). Do not merge or deploy until Bifrost cut A emits v2 and a store exists. **Wire (for Bifrost).** Pending kind `gamecult.persona.chat_pending.v2`. A delivery is a 12-element tuple: index 11 is `image`, nil or `[path: str, mime: str, size: u64, sha256: str]`. `path` is relative to the Persona's image store and exactly `<lowercase hex sha256 of the room id>/<lowercase hex sha256 of the bytes>[.<1-5 alnum ext>]`. `mime` is exactly `image/png|jpeg|gif|webp` and must equal what the magic bytes say. `size` is the byte length, at most 5 MiB. Golden fixtures: `tests/fixtures/persona-chat-v3/`. v1 pending answers and 11-element records still decode (`image: None`), and the client accepts both kinds. **Consumer.** `chat_image::load` is the only place a reference becomes bytes; it refuses any path not of that shape or not for this delivery's room, anything whose canonical path differs (symlinks), non-regular files (checked before open, so a FIFO cannot block), over 5 MiB (declared and on disk), a size or sha256 differing from the declaration, non-image magic bytes, and a mime differing from the sniffed type. Only then base64. `persona.rs` puts a text block then one image block per image into the stream-json user message (at most 4 per line) and adds an `attachment role=organ ... status=attached image=N | status=not_shown reason="..."` line per image-bearing delivery, with fixed reasons. The echo reader treats a replayed line with blocks as our echo (else the session machinery would lose track of what she was shown). Store root: `MIND_PERSONA_IMAGE_STORE` (absolute); unset shows no image. No bytes are logged or in an error. **Verification.** `unshare -Ur cargo test -p mind-body` lib, chat_rudp, public_surface: green (760 lib tests; the control and persona_bin integration tests fail here for environment reasons that predate this change). Guards hand-mutated one at a time: every guard's removal fails a test except two recorded as equivalent: `take(MAX+1)` on the read (the metadata length check already bounds it; only a file growing mid-load differs) and `symlink_metadata` vs `metadata` (the canonical-path check already refuses symlinks). The CLI accepted the image-block line shape in a probe, but no model read was observed (no lease in the Self run). **Open.** The Persona unit needs read access to the store and the env var set. Images are untrusted input to the model; the Persona instructions should say so (not changed here). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
ChatDelivery gains tuple index 11, `image: Option<ChatImage>` ([path, mime,
size, sha256], a reference into her per-room image store, never bytes); the
pending kind is gamecult.persona.chat_pending.v2, and v1 answers and records
still decode with no image. chat_image::load is the one place a reference
becomes bytes: exact `<hex sha256 of room id>/<sha256>[.ext]` path with no
traversal or symlink, regular file, 5 MiB declared and actual, declared size
and sha256, png/jpeg/gif/webp magic bytes matching the declared mime. The
persona line carries the image as a base64 image block after its text and an
`attachment role=organ` line saying attached or not shown and why. The
stream-json echo of a line with blocks still counts as our echo. The store is
MIND_PERSONA_IMAGE_STORE; unset, every image is not shown.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
eureka merged commit c1c3e7d6fe into main 2026-11-08 08:12:44 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
GameCult/Huginn!11
No description provided.