SINK for artifacts the sandbox PRODUCES — matplotlib figures (plt.show() /
plt.savefig()), anything a tool hands to vis_attach, AND anything written
into the per-context OUTBOX directory ($VIS_OUTBOX) — so the engine OWNS the
bytes (a session_iteration_attachment row) captured AT THE SOURCE, with NO
re-parsing of the model-facing stdout fence.
The old flow rendered each figure to a $TMPDIR/vis-mpl temp file, printed a
vis-image fence carrying just that PATH, then at persist time re-parsed the
fence out of stdout and re-read the (possibly already-gone) file. We control the
whole boundary, so that round-trip is gone: a producer renders/reads the bytes
HOST-side (matplotlib's __vis_mpl_render_file__ Java2D backend; vis_attach's
sandbox-confined open; the outbox filesystem tap in sandbox-fs) and, right
where it already holds the bytes, calls record-attachment! (or record-file!).
run-python-block binds *attachment-sink* to a fresh collector around each
block's eval and drains it into the block outcome's :attachments; the loop
stamps each with the block's tool-call-id and hands them to db-store-iteration!'s
:attachments. The stdout fence now serves ONLY the inline TUI/web display +
ASCII fallback — never persistence.
Deliberately dependency-free (no AWT, no vis.core): safe to require from BOTH
a render shim and the hot engine loop without dragging Java2D or a require
cycle.
SINK for artifacts the sandbox PRODUCES — matplotlib figures (`plt.show()` / `plt.savefig()`), anything a tool hands to `vis_attach`, AND anything written into the per-context OUTBOX directory (`$VIS_OUTBOX`) — so the engine OWNS the bytes (a `session_iteration_attachment` row) captured AT THE SOURCE, with NO re-parsing of the model-facing stdout fence. The old flow rendered each figure to a `$TMPDIR/vis-mpl` temp file, printed a `vis-image` fence carrying just that PATH, then at persist time re-parsed the fence out of stdout and re-read the (possibly already-gone) file. We control the whole boundary, so that round-trip is gone: a producer renders/reads the bytes HOST-side (matplotlib's `__vis_mpl_render_file__` Java2D backend; `vis_attach`'s sandbox-confined `open`; the outbox filesystem tap in `sandbox-fs`) and, right where it already holds the bytes, calls `record-attachment!` (or `record-file!`). `run-python-block` binds `*attachment-sink*` to a fresh collector around each block's eval and drains it into the block outcome's `:attachments`; the loop stamps each with the block's tool-call-id and hands them to `db-store-iteration!`'s `:attachments`. The stdout fence now serves ONLY the inline TUI/web display + ASCII fallback — never persistence. Deliberately dependency-free (no AWT, no `vis.core`): safe to require from BOTH a render shim and the hot engine loop without dragging Java2D or a require cycle.
Per-block READ-BACK accessor for artifacts already persisted in THIS session,
bound by run-python-code around one block's eval (else nil). A map
{:list (fn [] [{:id :filename :media-type :kind :size :position :tool-call-id :iteration-id} …]) :read (fn [attachment-id] {:id :base64 :media-type …}|nil)}
closing over the session's db-info + id. Lets the vis_attachments /
vis_read_attachment sandbox shims re-fetch an artifact a tool (or an earlier
turn) produced. Nil outside a driven block ⇒ the shim surfaces a clear
RuntimeError instead of silently returning nothing.
Per-block READ-BACK accessor for artifacts already persisted in THIS session,
bound by `run-python-code` around one block's eval (else nil). A map
`{:list (fn [] [{:id :filename :media-type :kind :size :position :tool-call-id
:iteration-id} …]) :read (fn [attachment-id] {:id :base64 :media-type …}|nil)}`
closing over the session's db-info + id. Lets the `vis_attachments` /
`vis_read_attachment` sandbox shims re-fetch an artifact a tool (or an earlier
turn) produced. Nil outside a driven block ⇒ the shim surfaces a clear
`RuntimeError` instead of silently returning nothing.Per-block queue of persisted image attachments deliberately reintroduced to the
NEXT provider request. Bound by run-python-code; vis_reinspect_attachment
appends hydrated session-owned images here. Unlike *attachment-sink*, these
are ephemeral: the loop consumes them once and never stores duplicate bytes.
Per-block queue of persisted image attachments deliberately reintroduced to the NEXT provider request. Bound by `run-python-code`; `vis_reinspect_attachment` appends hydrated session-owned images here. Unlike `*attachment-sink*`, these are ephemeral: the loop consumes them once and never stores duplicate bytes.
Per-block artifact collector: an atom holding a vector of attachment maps, bound
by run-python-block around ONE block's eval (else nil). Producers append into
it via record-attachment!; the block drains @*attachment-sink* into its
:attachments.
Per-block artifact collector: an atom holding a vector of attachment maps, bound by `run-python-block` around ONE block's eval (else nil). Producers append into it via `record-attachment!`; the block drains `@*attachment-sink*` into its `:attachments`.
Per-block set of canonical outbox paths ALREADY captured by the filesystem tap,
bound (an atom #{}) alongside *attachment-sink* so a file re-closed in the
same block is not recorded twice. Nil outside a driven block.
Per-block set of canonical outbox paths ALREADY captured by the filesystem tap,
bound (an atom `#{}`) alongside `*attachment-sink*` so a file re-closed in the
same block is not recorded twice. Nil outside a driven block.(display-cache-file prefix ext bs)Durable, content-addressed host file backing ONE inline vis-image display
fence: ~/.vis/cache/display/<prefix><sha256-16>.<ext>.
The fence a shim prints carries a HOST PATH, and that path is persisted with
the iteration output — a TUI re-rendering history repaints the picture from
it. An OS temp file (the old home) is swept by the system days later, so a
restored bubble then pointed at a dead path while the same artifact still
rendered fine in the companion app (which fetches DB bytes). This cache is
the TUI-side equivalent of that durability. DISPLAY ONLY — the bytes stay
DB-owned via record-attachment!.
Content-addressed: the same figure written twice reuses one file, the name is stable across restarts, and an existing file is never rewritten.
Durable, content-addressed host file backing ONE inline `vis-image` display fence: `~/.vis/cache/display/<prefix><sha256-16>.<ext>`. The fence a shim prints carries a HOST PATH, and that path is persisted with the iteration output — a TUI re-rendering history repaints the picture from it. An OS temp file (the old home) is swept by the system days later, so a restored bubble then pointed at a dead path while the same artifact still rendered fine in the companion app (which fetches DB bytes). This cache is the TUI-side equivalent of that durability. DISPLAY ONLY — the bytes stay DB-owned via `record-attachment!`. Content-addressed: the same figure written twice reuses one file, the name is stable across restarts, and an existing file is never rewritten.
(drain sink)The attachments collected in sink (an atom vector) as a plain vector, or nil
when empty — the value run-python-block folds into a block outcome's
:attachments.
The attachments collected in `sink` (an atom vector) as a plain vector, or nil when empty — the value `run-python-block` folds into a block outcome's `:attachments`.
(drain-reinspections sink)Queued one-request image re-inspections for sink, or nil when none.
Queued one-request image re-inspections for `sink`, or nil when none.
THE single per-artifact byte cap (32 MiB) — the one source of truth every
producer path shares. The filesystem outbox/temp tap (record-file!) SKIPS a
larger file silently (an incidental write must not throw); the explicit
vis_attach/vis_attach_bytes shim REJECTS one with a clear error (a
deliberate attach deserves a signal, not a silent drop). Either way a huge
write can't OOM the engine or bloat the DB.
THE single per-artifact byte cap (32 MiB) — the one source of truth every producer path shares. The filesystem outbox/temp tap (`record-file!`) SKIPS a larger file silently (an incidental write must not throw); the explicit `vis_attach`/`vis_attach_bytes` shim REJECTS one with a clear error (a deliberate attach deserves a signal, not a silent drop). Either way a huge write can't OOM the engine or bloat the DB.
(queue-reinspection! attachment)Queue one hydrated, session-owned image for exactly one provider request. A silent no-op outside a driven block; callers validate ownership and media type.
Queue one hydrated, session-owned image for exactly one provider request. A silent no-op outside a driven block; callers validate ownership and media type.
(record-attachment! m)Append ONE produced-artifact attachment map to the active per-block
*attachment-sink* (a silent no-op when unbound — e.g. a call outside a driven
block). Shape mirrors ONE element of db-store-iteration!'s :attachments,
minus :tool-call-id which the loop stamps from the block that produced it:
{:kind <"image"|"file"|…> :media-type <mime> :base64 <b64> :size <bytes> :filename <name> :dims <"WxH", images only>}. NEVER throws — capture must not
break a turn.
Append ONE produced-artifact attachment map to the active per-block
`*attachment-sink*` (a silent no-op when unbound — e.g. a call outside a driven
block). Shape mirrors ONE element of `db-store-iteration!`'s `:attachments`,
minus `:tool-call-id` which the loop stamps from the block that produced it:
`{:kind <"image"|"file"|…> :media-type <mime> :base64 <b64> :size <bytes>
:filename <name> :dims <"WxH", images only>}`. NEVER throws — capture must not
break a turn.(record-file! path)Read the bytes of a just-written file at path (a java.nio.file.Path), sniff
its media-type, base64-encode, and record-attachment! it — the host side of
the filesystem OUTBOX tap. De-dups per block via *outbox-seen* (a file
re-closed in the same block records once). Skips a file larger than
max-capture-bytes, an EMPTY (0-byte) file, one whose extension is in
noisy-capture-exts, a directory, or one already seen. NEVER throws — an
outbox write must not break a turn.
Read the bytes of a just-written file at `path` (a `java.nio.file.Path`), sniff its media-type, base64-encode, and `record-attachment!` it — the host side of the filesystem OUTBOX tap. De-dups per block via `*outbox-seen*` (a file re-closed in the same block records once). Skips a file larger than `max-capture-bytes`, an EMPTY (0-byte) file, one whose extension is in `noisy-capture-exts`, a directory, or one already seen. NEVER throws — an outbox write must not break a turn.
(sniff-media-type data filename)Best-effort media-type for produced bytes: magic-byte signatures first, then a
filename-extension fallback, then a utf-8 probe (text/plain), else
application/octet-stream. NEVER throws.
Best-effort media-type for produced bytes: magic-byte signatures first, then a filename-extension fallback, then a utf-8 probe (`text/plain`), else `application/octet-stream`. NEVER throws.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |