Liking cljdoc? Tell your friends :D

com.blockether.vis.internal.foundation.doctor

Foundation's contribution to the host vis doctor aggregator. ONE fn (doctor-fn) returns the full message stream from two logical sections, each stamping its own :check-id so the formatter groups them under the same banner the original four-checks-vec shape produced (plan §1 Q18 / §10):

::agents-md AGENTS.md presence / source / size; one :info line when found, one :warn line when neither AGENTS.md nor CLAUDE.md exists (rules silently absent is worth flagging even though it isn't an error per se).

These section fns are pure data -> message-seq; they don't mutate anything and don't depend on the runtime environment beyond what's needed to read the existing scanners. Activation contract per plan: every registered extension's :ext/doctor-fn runs regardless of :ext/activation-fn, so the section fns must NOT assume :db-info or other env keys are present.

Foundation's contribution to the host `vis doctor` aggregator. ONE fn
(`doctor-fn`) returns the full message stream from two logical
sections, each stamping its own `:check-id` so the formatter
groups them under the same banner the original four-checks-vec
shape produced (plan §1 Q18 / §10):

  ::agents-md         AGENTS.md presence / source / size; one
                       :info line when found, one :warn line
                       when neither AGENTS.md nor CLAUDE.md exists
                       (rules silently absent is worth flagging
                       even though it isn't an error per se).

These section fns are pure data -> message-seq; they don't mutate
anything and don't depend on the runtime environment beyond
what's needed to read the existing scanners. Activation
contract per plan: every registered extension's `:ext/doctor-fn`
runs regardless of `:ext/activation-fn`, so the section fns must
NOT assume `:db-info` or other env keys are present.
raw docstring

com.blockether.vis.internal.foundation.editing.core

Filesystem tools exposed as bare symbols in the Python sandbox.

Two layers:

  1. Structured helpers for read / tree / search:

    (cat path) ; -> {:path :anchors {<N:hash> text…} :next-offset N? :truncated? B} (cat path n) ; first n lines from line 1 (cat path offset n) ; n lines starting at line offset (1-based) (cat path :tail) ; last 400 lines (tail) (cat path :tail n) ; last n lines (ls path) ; -> nested dict tree (name/path/type/size/children) (ls path opts) ; opts keys: depth / is_hidden / is_respect_gitignore (rg query) ; -> content hits; query = a term or list of terms (OR), ; smart-case substring. Opts: paths/include/context/is_files_only

  2. Cwd-safe wrappers over the babashka.fs file API. patch is the canonical text edit surface:

    (cat path) (patch [edit-map]) ; keys: path / from_anchor / replace (create-dirs path) (copy src dest) (move src dest) (delete path) (delete-if-exists path) (exists? path)

Hard guard: every path must stay inside the session's working directory (fs/cwd); .. traversal is rejected before any I/O.

Filesystem tools exposed as bare symbols in the Python sandbox.

Two layers:

1. Structured helpers for read / tree / search:

     (cat path)            ; -> {:path :anchors {<N:hash> text…} :next-offset N? :truncated? B}
     (cat path n)          ; first n lines from line 1
     (cat path offset n)   ; n lines starting at line `offset` (1-based)
     (cat path :tail)      ; last 400 lines (tail)
     (cat path :tail n)    ; last n lines
     (ls path)             ; -> nested dict tree (name/path/type/size/children)
     (ls path opts)        ; opts keys: depth / is_hidden / is_respect_gitignore
     (rg query)           ; -> content hits; query = a term or list of terms (OR),
                            ; smart-case substring. Opts: paths/include/context/is_files_only

2. Cwd-safe wrappers over the babashka.fs file API. `patch` is
   the canonical text edit surface:

     (cat path)
     (patch [edit-map])    ; keys: path / from_anchor / replace
     (create-dirs path)
     (copy src dest)
     (move src dest)
     (delete path)
     (delete-if-exists path)
     (exists? path)

Hard guard: every path must stay inside the session's working
directory (`fs/cwd`); `..` traversal is rejected before any I/O.
raw docstring

com.blockether.vis.internal.foundation.editing.outline

Structural outline: a high-level, line-ranged skeleton of a source file produced via tree-sitter (com.blockether/tree-sitter-language-pack, which sources Clojure from our own grammar fork).

Every item carries FULL anchors — the same <lineno>:<hash> anchors cat emits and patch consumes — for its first and last line, so you can replace a whole definition straight from the index with one patch([{from_anchor start, to_anchor end, replace …}]), no intermediate cat. Each line is:

<kind> <name> <signature> @<start-anchor>..<end-anchor>

e.g.

class Greeter @3:a1b..7:c2d function hello @6:1b2..7:c2d function main @9:3c4..10:5d6

Read it BEFORE cat: a cheap map of a file so you jump straight to the right range (and its anchors) instead of reading the whole file.

Requiring this namespace also requires the native resolver, which selects the right per-platform FFI library at runtime.

Structural outline: a high-level, line-ranged skeleton of a source file
produced via tree-sitter (com.blockether/tree-sitter-language-pack, which
sources Clojure from our own grammar fork).

Every item carries FULL anchors — the same `<lineno>:<hash>` anchors `cat`
emits and `patch` consumes — for its first and last line, so you can replace
a whole definition straight from the index with one
`patch([{from_anchor start, to_anchor end, replace …}])`, no intermediate
`cat`. Each line is:

  <kind> <name>  <signature>  @<start-anchor>..<end-anchor>

e.g.

  class Greeter  @3:a1b..7:c2d
    function hello  @6:1b2..7:c2d
  function main  @9:3c4..10:5d6

Read it BEFORE `cat`: a cheap map of a file so you jump straight to the right
range (and its anchors) instead of reading the whole file.

Requiring this namespace also requires the native resolver, which selects the
right per-platform FFI library at runtime.
raw docstring

com.blockether.vis.internal.foundation.editing.patch

Fuzzy line-based matching toolkit used by patch exact-replace.

This namespace USED to also carry the Codex apply_patch envelope parser, but envelope mode was retired — we consolidated to a single per-intent mutation surface (patch exact-replace + write / move / delete). The fuzzy matcher stays because it is what lets multi-line :search blocks tolerate whitespace and typographic drift before they fall through to :no-match.

Public surface (all pure): seek-sequence lines pattern start eof? -> start-index or nil seek-sequence-with-pass lines pattern start eof? -> {:start :pass :indent-delta?} or nil split-content-lines string -> vec of lines (no trailing \n element) char-offset-at-line content line-idx -> char offset apply-indent-delta delta lines -> re-indented lines

It also owns the reusable HASHLINE layer (content-addressed editing): line-hash / lines->anchors text -> 6-hex anchor / {ln hash} render-hashline-block / -range-block tuples -> <hash>| text gutter indices-matching-hash / resolve-anchor-edit self-locating range replace

Fuzzy line-based matching toolkit used by `patch` exact-replace.

This namespace USED to also carry the Codex `apply_patch` envelope
parser, but envelope mode was retired — we consolidated to a single
per-intent mutation surface (`patch` exact-replace + `write` /
`move` / `delete`). The fuzzy matcher stays because it is what
lets multi-line `:search` blocks tolerate whitespace and typographic
drift before they fall through to `:no-match`.

Public surface (all pure):
  seek-sequence            lines pattern start eof? -> start-index or nil
  seek-sequence-with-pass  lines pattern start eof?
                           -> {:start :pass :indent-delta?} or nil
  split-content-lines      string -> vec of lines (no trailing \n element)
  char-offset-at-line      content line-idx -> char offset
  apply-indent-delta       delta lines -> re-indented lines

It also owns the reusable HASHLINE layer (content-addressed editing):
  line-hash / lines->anchors              text -> 6-hex anchor / {ln hash}
  render-hashline-block / -range-block   tuples -> `<hash>| text` gutter
  indices-matching-hash / resolve-anchor-edit  self-locating range replace
raw docstring

com.blockether.vis.internal.foundation.editing.structural

Thin Clojure adapter over the pack's Java structural-edit engine (dev.kreuzberg.treesitterlanguagepack.StructuralApi). All the work — locate the definition by name from the tree-sitter outline, splice its line span, and re-parse to refuse syntax-breaking edits — lives in Java so it is language-neutral, reusable from any JVM consumer, and native-image clean. This namespace only maps vis op keywords onto the Java API.

Thin Clojure adapter over the pack's Java structural-edit engine
(`dev.kreuzberg.treesitterlanguagepack.StructuralApi`). All the work —
locate the definition by name from the tree-sitter outline, splice its line
span, and re-parse to refuse syntax-breaking edits — lives in Java so it is
language-neutral, reusable from any JVM consumer, and native-image clean.
This namespace only maps vis op keywords onto the Java API.
raw docstring

com.blockether.vis.internal.foundation.editing.zipper

Language-neutral STRUCTURAL ZIPPER over the tree-sitter pack (306+ langs) — the unified cursor the name-based structural ops were missing.

A node's location is a STATELESS PATH: a vector of NAMED-child indices from the root (e.g. [2 0] = first named child of the third named child of the file). Stateless means it round-trips cleanly through async tool calls — no live native cursor to keep between calls. Relative moves (down/up/next/prev) are pure path arithmetic on top, so the model navigates like a rewrite-clj zipper but over EVERY language tree-sitter understands.

Edits splice the target node's UTF-8 byte range and RE-PARSE, refusing a result that introduces a syntax error the original didn't have — the same safety contract as structural. Pairs with the name-based ops: locate a def by name, then walk into it by path.

All native handles (Parser/Tree/Node) are opened and closed inside each call; only plain Clojure data escapes.

Language-neutral STRUCTURAL ZIPPER over the tree-sitter pack (306+ langs) —
the unified cursor the name-based `structural` ops were missing.

A node's location is a STATELESS PATH: a vector of NAMED-child indices from
the root (e.g. `[2 0]` = first named child of the third named child of the
file). Stateless means it round-trips cleanly through async tool calls — no
live native cursor to keep between calls. Relative moves (down/up/next/prev)
are pure path arithmetic on top, so the model navigates like a rewrite-clj
zipper but over EVERY language tree-sitter understands.

Edits splice the target node's UTF-8 byte range and RE-PARSE, refusing a
result that introduces a syntax error the original didn't have — the same
safety contract as `structural`. Pairs with the name-based ops: locate a def
by name, then walk into it by path.

All native handles (Parser/Tree/Node) are opened and closed inside each call;
only plain Clojure data escapes.
raw docstring

com.blockether.vis.internal.foundation.environment.agents

Thin re-export shim — project-guidance discovery moved to com.blockether.vis.internal.agents. AGENTS.md / CLAUDE.md handling is core functionality (drives system prompt + ctx digest), so it now lives outside the foundation extension.

This shim keeps the legacy main-agent-instructions tool + any downstream (:project ctx) :guidance consumers compiling while they migrate to the internal namespace.

Thin re-export shim — project-guidance discovery moved to
`com.blockether.vis.internal.agents`. AGENTS.md / CLAUDE.md
handling is core functionality (drives system prompt + ctx
digest), so it now lives outside the foundation extension.

This shim keeps the legacy `main-agent-instructions` tool +
any downstream `(:project ctx) :guidance` consumers compiling
while they migrate to the internal namespace.
raw docstring

com.blockether.vis.internal.foundation.environment.core

vis-foundation — the agent's environment-awareness layer.

Owns the environment facts: cwd, user, platform, shell, plus:

  • git repository facts via the git binary (root, branch, dirty status, submodules, worktree),
  • a bounded language scan over the working tree (top languages by total bytes, primary language),
  • monorepo / multi-package shape detection (polylith, workspace, submodules) by counting per-ecosystem manifests.

Model-facing VCS/workspace truth lives in :session/workspace CTX. Remaining helpers cover coarse project shape (languages, monorepo, repositories) and cache invalidation (refresh!).

Runtime facts are computed lazily on first access and cached per working-directory. The cache is invalidated automatically when cwd changes between calls and explicitly by (refresh!).

vis-foundation — the agent's environment-awareness layer.

Owns the environment facts: cwd, user, platform, shell, plus:

  * git repository facts via the git binary (root, branch, dirty status,
    submodules, worktree),
  * a bounded language scan over the working tree (top languages
    by total bytes, primary language),
  * monorepo / multi-package shape detection (polylith, workspace,
    submodules) by counting per-ecosystem manifests.

Model-facing VCS/workspace truth lives in `:session/workspace` CTX.
Remaining helpers cover coarse project shape (`languages`,
`monorepo`, `repositories`) and cache invalidation (`refresh!`).

Runtime facts are computed lazily on first access and cached per
working-directory. The cache is invalidated automatically when
`cwd` changes between calls and explicitly by `(refresh!)`.
raw docstring

com.blockether.vis.internal.foundation.environment.git

Git introspection for the environment block, backed by the native git binary (via internal.git).

Returns a snapshot map for the repository that contains start (typically the JVM working directory). nil when start is not inside any git repository. Never throws — every git call is guarded; on any failure we degrade gracefully to nil or a reduced-shape map.

The expensive call is git status (working-tree walk). When it fails or is suppressed, the snapshot drops the dirty-status fields instead of stalling the system-prompt build.

Git introspection for the environment block, backed by the native `git`
binary (via `internal.git`).

Returns a snapshot map for the repository that contains `start`
(typically the JVM working directory). nil when `start` is not inside any
git repository. Never throws — every git call is guarded; on any failure we
degrade gracefully to nil or a reduced-shape map.

The expensive call is `git status` (working-tree walk). When it fails or is
suppressed, the snapshot drops the dirty-status fields instead of stalling
the system-prompt build.
raw docstring

com.blockether.vis.internal.foundation.environment.host

Host-side facts read from JDK system properties and process environment variables.

No I/O, no shell-out, no third-party deps. Cheap to compute, safe to call from any thread, never throws.

Host-side facts read from JDK system properties and process
environment variables.

No I/O, no shell-out, no third-party deps. Cheap to compute, safe
to call from any thread, never throws.
raw docstring

com.blockether.vis.internal.foundation.environment.languages

Bounded language scan over a directory tree.

Walks the tree with Files/walkFileTree, skipping common non-source subdirectories (.git, node_modules, target, ...) via FileVisitResult/SKIP_SUBTREE. Counts files and bytes per language using a small extension-to-language map.

The walk has TWO hard guards: a max-file count and a wall-time deadline. Either one stops the walk via TERMINATE. Callers get a possibly-partial result; on a small repo the result is exact.

No third-party deps. Reflection-clean.

Bounded language scan over a directory tree.

Walks the tree with `Files/walkFileTree`, skipping common
non-source subdirectories (`.git`, `node_modules`, `target`, ...)
via `FileVisitResult/SKIP_SUBTREE`. Counts files and bytes per
language using a small extension-to-language map.

The walk has TWO hard guards: a max-file count and a wall-time
deadline. Either one stops the walk via `TERMINATE`. Callers get
a possibly-partial result; on a small repo the result is exact.

No third-party deps. Reflection-clean.
raw docstring

com.blockether.vis.internal.foundation.environment.monorepo

Monorepo / multi-package detection.

Walks the tree once (bounded), counting per-language manifest files at any depth below the root. >=2 manifests of the same kind in distinct subdirectories signals a multi-package workspace; we report the kind, the count, and a best-guess shape label (e.g. "polylith", "workspace", "submodules").

Reflection-clean. Honors the same skip-directory list as the language scanner.

Monorepo / multi-package detection.

Walks the tree once (bounded), counting per-language manifest
files at any depth below the root. >=2 manifests of the same
kind in distinct subdirectories signals a multi-package
workspace; we report the kind, the count, and a best-guess
shape label (e.g. "polylith", "workspace", "submodules").

Reflection-clean. Honors the same skip-directory list as the
language scanner.
raw docstring

com.blockether.vis.internal.foundation.environment.render

Build compact foundation environment data for ctx. No prompt labels.

Build compact foundation environment data for `ctx`. No prompt labels.
raw docstring

com.blockether.vis.internal.foundation.environment.repositories

Bounded discovery of multiple Git repositories below the current project root. This catches multirepo workspaces where the user's cwd is a parent directory or a primary repo that vendors sibling/nested repos outside .gitmodules.

Returns compact per-repo Git summaries for the system prompt. Full status walks are bounded per repo by git/snapshot; the repository scan itself is bounded by max files, max repos, and a wall-clock deadline. Never throws.

Bounded discovery of multiple Git repositories below the current
project root. This catches multirepo workspaces where the user's cwd
is a parent directory or a primary repo that vendors sibling/nested
repos outside `.gitmodules`.

Returns compact per-repo Git summaries for the system prompt. Full
status walks are bounded per repo by `git/snapshot`; the repository
scan itself is bounded by max files, max repos, and a wall-clock
deadline. Never throws.
raw docstring

com.blockether.vis.internal.foundation.git-tool

The single git tool — a thin, honest proxy to the host git binary.

ONE built-in Python function, git, runs git <args…> in the active workspace root and returns a lean, string-keyed result the model reads directly: {"cmd", "args", "stdout", "duration_ms"} plus "exit" (when the process finished), "stderr" (when non-empty), and "timed_out" (when it blew the timeout). A non-zero exit is DATA, not a tool failure — the model reads it like it would in a terminal.

This REPLACES the old JGit-backed git_* surface (foundation-git): no embedded git implementation, no SSH/BouncyCastle stack — the only git is the one already on the user's PATH, so behaviour matches their shell exactly. Read-only workspace facts (branch/dirty/ahead-behind for the footer, env block, file picker) still flow through com.blockether.vis.internal.git; this namespace is purely the model- facing command tool.

Built-in (bare git in the sandbox, next to cat/rg), gated to activate only when the workspace sits inside a repository.

The single `git` tool — a thin, honest proxy to the host `git` binary.

ONE built-in Python function, `git`, runs `git <args…>` in the active
workspace root and returns a lean, string-keyed result the model reads
directly: `{"cmd", "args", "stdout", "duration_ms"}` plus `"exit"`
(when the process finished), `"stderr"` (when non-empty), and
`"timed_out"` (when it blew the timeout). A non-zero exit is DATA, not a
tool failure — the model reads it like it would in a terminal.

This REPLACES the old JGit-backed `git_*` surface (foundation-git): no
embedded git implementation, no SSH/BouncyCastle stack — the only git is
the one already on the user's PATH, so behaviour matches their shell
exactly. Read-only workspace facts (branch/dirty/ahead-behind for the
footer, env block, file picker) still flow through
`com.blockether.vis.internal.git`; this namespace is purely the model-
facing command tool.

Built-in (bare `git` in the sandbox, next to `cat`/`rg`), gated to
activate only when the workspace sits inside a repository.
raw docstring

com.blockether.vis.internal.foundation.introspection

Programmatic introspection of the agent's own state from inside :code. The public state surface is deliberately small:

  • (session-state [session-id]) -> data map, including raw LLM diagnostics
  • (session-report-md [session-id]) -> Markdown rendered from that data

Everything else in this namespace is implementation detail. The agent gets the data once, manipulates it via plain Clojure (get-in, filter, map, etc.), and renders the same data to Markdown only when presentation is needed.

Every function is a pure read off the same DB tables the projection layer reads from (or a classpath read for the doc accessors). Failures return nil/[], never throw, so a misbehaving introspection call cannot break iteration execution.

Opt-in: not auto-loaded by default. Add this jar to the classpath to enable.

Programmatic introspection of the agent's own state from inside
`:code`. The public state surface is deliberately small:

- `(session-state [session-id])` -> data map, including raw LLM diagnostics
- `(session-report-md [session-id])` -> Markdown rendered from that data

Everything else in this namespace is implementation detail. The agent
gets the data once, manipulates it via plain Clojure (`get-in`,
`filter`, `map`, etc.), and renders the same data to Markdown only
when presentation is needed.

Every function is a pure read off the same DB tables the projection
layer reads from (or a classpath read for the doc accessors).
Failures return nil/[], never throw, so a misbehaving introspection
call cannot break iteration execution.

Opt-in: not auto-loaded by default. Add this jar to the classpath
to enable.
raw docstring

com.blockether.vis.internal.foundation.language-surface

Language-neutral FORMAT / TEST / REPL_EVAL / START_REPL dispatch.

Language extensions register handlers under :ext/language-tools; this foundation surface exposes stable bare tool names and dispatches to the active handler for the requested/current language. REPL lifecycle is resource backed: repl_start creates a language-owned session resource and repl_stop stops one by id. Live REPLs also surface in the ctx resources block.

Language-neutral FORMAT / TEST / REPL_EVAL / START_REPL dispatch.

Language extensions register handlers under `:ext/language-tools`; this
foundation surface exposes stable bare tool names and dispatches to the
active handler for the requested/current language. REPL lifecycle is resource
backed: `repl_start` creates a language-owned session resource and `repl_stop`
stops one by id. Live REPLs also surface in the ctx `resources` block.
raw docstring

com.blockether.vis.internal.foundation.mpl-capture

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.
raw docstring

com.blockether.vis.internal.foundation.pty

Pure-Java pseudo-terminal for shell_bg — NO JNA, NO extracted native helper, NO external tmux. Everything is a java.lang.foreign (Panama FFM) downcall into the platform libc, so it survives GraalVM native-image the same way the rest of vis's FFM surface (fff / rift / ruff / tree-sitter) does.

Why FFM and not pty4j:

  • pty4j drags in JNA and ships its own compiled libpty that it extracts to a temp dir at runtime — two native-image headaches (reflection metadata for JNA + a resource-extraction dance for the .dylib/.so).
  • The obvious pure-FFM shortcut — call libc forkpty then execve in the child — does NOT work from a JVM: invoking an FFM downcall MethodHandle after fork() is not async-signal-safe and SIGBUSes the child. (Verified.)
  • posix_spawn sidesteps that entirely: libc does the fork+exec ATOMICALLY in native code, so vis only ever issues ONE parent-side downcall and never runs any JVM code in the child. Paired with openpty (master/slave fds) + a dup2 of the slave onto the child's 0/1/2, the child gets a real TTY: isatty() is true, $TERM is honoured, stdin is writable.

Public surface — spawn! returns a PLAIN MAP (not a java.lang.Process; a runtime proxy/gen-class would break native-image), shaped for the internal.foundation.shell background pump:

{:pid <long> OS pid (a genuine child — ProcessHandle/of works, unlike a pty4j process) :in <java.io.InputStream> master-fd reader (a real piped stream) :send (fn [^bytes b]) write bytes to the master (the stdin channel) :wait (fn [] <int>) block until exit, reap, return the exit code :alive? (fn [] <bool>) :destroy (fn [force?]) SIGTERM (false) / SIGKILL (true) the child}

Pure-Java pseudo-terminal for `shell_bg` — NO JNA, NO extracted native helper,
NO external `tmux`. Everything is a `java.lang.foreign` (Panama FFM) downcall
into the platform libc, so it survives GraalVM native-image the same way the
rest of vis's FFM surface (fff / rift / ruff / tree-sitter) does.

Why FFM and not pty4j:
- pty4j drags in JNA *and* ships its own compiled `libpty` that it extracts to
  a temp dir at runtime — two native-image headaches (reflection metadata for
  JNA + a resource-extraction dance for the .dylib/.so).
- The obvious pure-FFM shortcut — call libc `forkpty` then `execve` in the
  child — does NOT work from a JVM: invoking an FFM downcall MethodHandle after
  `fork()` is not async-signal-safe and SIGBUSes the child. (Verified.)
- `posix_spawn` sidesteps that entirely: libc does the fork+exec ATOMICALLY in
  native code, so vis only ever issues ONE parent-side downcall and never runs
  any JVM code in the child. Paired with `openpty` (master/slave fds) + a
  `dup2` of the slave onto the child's 0/1/2, the child gets a real TTY:
  `isatty()` is true, `$TERM` is honoured, stdin is writable.

Public surface — `spawn!` returns a PLAIN MAP (not a `java.lang.Process`; a
runtime `proxy`/`gen-class` would break native-image), shaped for the
`internal.foundation.shell` background pump:

  {:pid      <long>              OS pid (a genuine child — `ProcessHandle/of`
                                 works, unlike a pty4j process)
   :in       <java.io.InputStream>   master-fd reader (a real piped stream)
   :send     (fn [^bytes b])     write bytes to the master (the stdin channel)
   :wait     (fn [] <int>)       block until exit, reap, return the exit code
   :alive?   (fn [] <bool>)
   :destroy  (fn [force?])       SIGTERM (false) / SIGKILL (true) the child}
raw docstring

com.blockether.vis.internal.foundation.pty-bridge

Passthrough bridge on top of the FFM pseudo-terminal (internal.foundation.pty).

The problem it solves: a shell_bg child runs INSIDE the vis process (the FFM PTY master fd + reader thread live in vis's heap). That's great for the agent (shell_send / shell_logs) but a HUMAN can't jump into the live terminal to finish a step the agent can't — click through a browser OAuth, answer a prompt only a person can. tmux gets that for free because its server is a separate daemon you can tmux attach to; the FFM child is not.

This namespace restores that capability WITHOUT tmux: each background PTY optionally exposes a per-shell UNIX-DOMAIN SOCKET. vis is the server (it holds the master fd); vis ext shell attach <id> is a thin client the human runs in their OWN Terminal.app. On connect the server (a) tees live master output to the socket and (b) forwards the socket's bytes to the master (stdin) — a genuine bidirectional passthrough. Multiple humans can attach at once; detaching just drops the socket and leaves the child running (exactly like tmux detach).

Everything here is stdlib: java.nio.channels AF_UNIX sockets (JDK 16+, already in vis's native-image reachability metadata) on the server side, and stty for raw mode on the client side (the human's interactive shell always has it). No JNA, no new dep, native-image clean.

Passthrough bridge on top of the FFM pseudo-terminal (internal.foundation.pty).

The problem it solves: a `shell_bg` child runs INSIDE the vis process (the FFM
PTY master fd + reader thread live in vis's heap). That's great for the agent
(`shell_send` / `shell_logs`) but a HUMAN can't jump into the live terminal to
finish a step the agent can't — click through a browser OAuth, answer a prompt
only a person can. tmux gets that for free because its server is a separate
daemon you can `tmux attach` to; the FFM child is not.

This namespace restores that capability WITHOUT tmux: each background PTY
optionally exposes a per-shell UNIX-DOMAIN SOCKET. vis is the server (it holds
the master fd); `vis ext shell attach <id>` is a thin client the human runs in
their OWN Terminal.app. On connect the server (a) tees live master output to the
socket and (b) forwards the socket's bytes to the master (stdin) — a genuine
bidirectional passthrough. Multiple humans can attach at once; detaching just
drops the socket and leaves the child running (exactly like `tmux detach`).

Everything here is stdlib: `java.nio.channels` AF_UNIX sockets (JDK 16+,
already in vis's native-image reachability metadata) on the server side, and
`stty` for raw mode on the client side (the human's interactive shell always
has it). No JNA, no new dep, native-image clean.
raw docstring

com.blockether.vis.internal.foundation.self-docs

Vis self-documentation lookup — the vis_docs sandbox tool.

Vis ships its documentation as embedded markdown pages under vis-docs/ on the classpath (the same corpus the website and the gateway /docs site render, discovered via each artifact's vis-docs/vis-docs.edn manifest — so extensions' doc pages are lookup-able too). This namespace exposes that corpus to the MODEL through one observation tool so vis can answer questions about ITSELF — features, configuration, how to write an extension — from its real docs instead of guessing.

Progressive disclosure: a short always-on prompt fragment says the docs exist and when to reach for them; page content is only paid for when actually fetched.

Vis self-documentation lookup — the `vis_docs` sandbox tool.

Vis ships its documentation as embedded markdown pages under
`vis-docs/` on the classpath (the same corpus the website and the
gateway `/docs` site render, discovered via each artifact's
`vis-docs/vis-docs.edn` manifest — so extensions' doc pages are
lookup-able too). This namespace exposes that corpus to the MODEL
through one observation tool so vis can answer questions about
ITSELF — features, configuration, how to write an extension — from
its real docs instead of guessing.

Progressive disclosure: a short always-on prompt fragment says the
docs exist and when to reach for them; page content is only paid
for when actually fetched.
raw docstring

com.blockether.vis.internal.foundation.session-slashes

Declarative session-level slash commands shared by every channel.

These are channel-agnostic: the engine dispatches them for the TUI, the web, and Telegram alike through the same slash/dispatch path, and each handler mutates state via the gateway so the change fans out everywhere.

/rename <new title> set this session's title

/rename routes through titling/set-title-with-broadcast! — the single title mutation point.

Declarative session-level slash commands shared by every channel.

These are channel-agnostic: the engine dispatches them for the TUI, the
web, and Telegram alike through the same `slash/dispatch` path, and each
handler mutates state via the gateway so the change fans out everywhere.

  /rename <new title>   set this session's title

`/rename` routes through `titling/set-title-with-broadcast!` — the single
title mutation point.
raw docstring

com.blockether.vis.internal.foundation.shell

shell/ compatibility extension — a DROPPABLE classpath plug-in (drop the jar, drop the feature), gated behind the user-owned :shell/enabled toggle (OFF by default; every call short-circuits into a refusal envelope until the user flips it in settings).

Three model-facing bindings under alias shell (the flat Python sandbox renders alias/name as <alias>_<name>, so the calls are shell_run / shell_bg / shell_logs — same shape as git_status, clj_eval, search_web):

  1. SYNC shell_run(cmd) / shell_run(cmd, opts)bash -lc in the workspace root, waits up to a timeout, and returns a LEAN payload with string keys cmd/stdout/duration_ms + conditional keys (exit when finished; timed_out/timeout_secs on timeout; stderr when non-empty; truncation flags when true; cwd when narrowed) — results ride every later prompt as frozen <results> pins, so dead fields never ship. Output is bounded at read time to a head+tail budget per stream — only the MIDDLE of a huge stream is dropped, never its start or end (memory can't balloon on a chatty-then-killed command). A non-zero exit is DATA the model reads, not a tool error.

  2. BACKGROUND shell_bg(id, cmd) — spawns the process, pumps its merged output into a bounded ring buffer, and registers it as a session RESOURCE in internal.resources: it shows up in the footer count, the F4 dialog, and the resources ctx block, and the ONE stop path is resource_stop(id) (model) / the footer dialog (user) — both land on resources/stop!, which runs our :stop-fn (process-tree kill + buffer drop). An exited process is NOT auto-pruned (its :alive-fn reports true while the buffer entry exists) so shell_logs(id) can still read its output + exit code until the resource is stopped.

  3. shell_logs(id) / shell_logs(id, n) — tail of a background shell's captured output as [seq, line] tuples plus status/exit/uptime.

The :shell/enabled toggle is registered HERE, extension-owned under the extension's own namespace.

`shell/` compatibility extension — a DROPPABLE classpath plug-in (drop the
jar, drop the feature), gated behind the user-owned `:shell/enabled` toggle
(OFF by default; every call short-circuits into a refusal envelope until the
user flips it in settings).

Three model-facing bindings under alias `shell` (the flat Python sandbox
renders `alias/name` as `<alias>_<name>`, so the calls are
`shell_run` / `shell_bg` / `shell_logs` — same shape as `git_status`,
`clj_eval`, `search_web`):

1. SYNC `shell_run(cmd)` / `shell_run(cmd, opts)` — `bash -lc` in the
   workspace root, waits up to a timeout, and returns a LEAN payload with
   string keys cmd/stdout/duration_ms + conditional keys (exit when finished;
   timed_out/timeout_secs on timeout; stderr when non-empty; truncation
   flags when true; cwd when narrowed) — results ride every later prompt
   as frozen <results> pins, so dead fields never ship. Output is bounded
   at read time to a head+tail budget per stream — only the MIDDLE of a huge
   stream is dropped, never its start or end (memory can't balloon on a
   chatty-then-killed command). A non-zero exit is DATA the
   model reads, not a tool error.

2. BACKGROUND `shell_bg(id, cmd)` — spawns the process, pumps its merged
   output into a bounded ring buffer, and registers it as a session
   RESOURCE in `internal.resources`: it shows up in the footer count, the
   F4 dialog, and the `resources` ctx block, and the ONE stop path
   is `resource_stop(id)` (model) / the footer dialog (user) — both land
   on `resources/stop!`, which runs our `:stop-fn` (process-tree kill +
   buffer drop). An exited process is NOT auto-pruned (its `:alive-fn`
   reports true while the buffer entry exists) so `shell_logs(id)` can
   still read its output + exit code until the resource is stopped.

3. `shell_logs(id)` / `shell_logs(id, n)` — tail of a background shell's
   captured output as `[seq, line]` tuples plus status/exit/uptime.

The `:shell/enabled` toggle is registered HERE, extension-owned under the
extension's own namespace.
raw docstring

com.blockether.vis.internal.foundation.shim-attach

Built-in sandbox SHIM: vis_attach / vis_attach_bytes — the GENERIC producer twin of the matplotlib capture. A tool running in python_execution writes any artifact (a PNG it rendered, a CSV/JSON/PDF/wav it built, whatever) and hands it to vis_attach(path) (or vis_attach_bytes(data, name)); the engine then OWNS the bytes as a durable session_iteration_attachment row, exactly like a matplotlib figure — surviving a web/TUI restart and (for image media-types) replayable to a vision model cross-turn.

No parsing, no round-trip through the model-facing stdout: we control the whole boundary. The Python side reads the file through the sandbox's OWN confined open (so filesystem-root confinement is enforced for free — a path outside the roots raises the normal sandbox error), sniffs the media-type (magic bytes then extension then utf-8 probe), base64-encodes, and calls the tiny host bridge __vis_record_attachment__, which appends the attachment map to the per-block *image-sink* (mpl-capture/record-attachment!). run-python-block drains that sink into the block outcome's :attachments; the loop stamps each with the producing block's tool-call-id and hands them to db-store-iteration!'s :attachments.

Registered unconditionally as a foundation shim (like shim-yaml / shim-matplotlib): its :ext/sandbox-shims entry autoloads vis_attach into every sandbox (main + every sub_loop fork).

Built-in sandbox SHIM: `vis_attach` / `vis_attach_bytes` — the GENERIC
producer twin of the matplotlib capture. A tool running in `python_execution`
writes any artifact (a PNG it rendered, a CSV/JSON/PDF/wav it built, whatever)
and hands it to `vis_attach(path)` (or `vis_attach_bytes(data, name)`); the
engine then OWNS the bytes as a durable `session_iteration_attachment` row,
exactly like a matplotlib figure — surviving a web/TUI restart and (for image
media-types) replayable to a vision model cross-turn.

No parsing, no round-trip through the model-facing stdout: we control the whole
boundary. The Python side reads the file through the sandbox's OWN confined
`open` (so filesystem-root confinement is enforced for free — a path outside
the roots raises the normal sandbox error), sniffs the media-type (magic bytes
then extension then utf-8 probe), base64-encodes, and calls the tiny host
bridge `__vis_record_attachment__`, which appends the attachment map to the
per-block `*image-sink*` (`mpl-capture/record-attachment!`). `run-python-block`
drains that sink into the block outcome's `:attachments`; the loop stamps each with
the producing block's tool-call-id and hands them to `db-store-iteration!`'s
`:attachments`.

Registered unconditionally as a foundation shim (like shim-yaml /
shim-matplotlib): its `:ext/sandbox-shims` entry autoloads `vis_attach` into
every sandbox (main + every `sub_loop` fork).
raw docstring

com.blockether.vis.internal.foundation.shim-matplotlib

Built-in sandbox SHIM: a minimal matplotlib.pyplot-compatible module for the model's Python sandbox, backed by a pure-JVM Java2D renderer. The agent sandbox ships no CPython matplotlib wheel (it needs numpy's native core + freetype, all blocked by the deny-by-default Context); this extension instead contributes a :ext/sandbox-shims entry whose Python preamble accumulates the familiar pyplot artists (plot/scatter/bar/hist/fill_between/step/ pie/axhline/axvline/title/xlabel/… plus the OO subplots/Axes API) and whose savefig DELEGATES the whole figure spec across the boundary to the host callable __vis_mpl_render__, which draws it with java.awt/ ImageIO and returns a base64 PNG. The Python side base64-decodes and writes it (to a path, confined to the sandbox roots, or to any file-like buffer).

It is a SUBSET, not real matplotlib: line/scatter/bar/hist/fill/step/pie with title, axis labels, grid, legend, dashed line styles, markers, log scales and text annotations — enough for the model to visualize data. Rendering runs entirely on the JVM (no pip, no native wheels), and any render failure surfaces to Python as a catchable exception (never crashes the sandbox).

Together with shim-yaml this demonstrates the sandbox-shim mechanism: an extension turns a host / JVM capability into a real importable Python module while env-python stays completely generic about which shims exist.

Built-in sandbox SHIM: a minimal `matplotlib.pyplot`-compatible module for the
model's Python sandbox, backed by a pure-JVM Java2D renderer. The agent
sandbox ships no CPython matplotlib wheel (it needs numpy's native core +
freetype, all blocked by the deny-by-default Context); this extension instead
contributes a `:ext/sandbox-shims` entry whose Python preamble accumulates the
familiar pyplot artists (`plot`/`scatter`/`bar`/`hist`/`fill_between`/`step`/
`pie`/`axhline`/`axvline`/`title`/`xlabel`/… plus the OO `subplots`/`Axes`
API) and whose `savefig` DELEGATES the whole figure spec across the boundary
to the host callable `__vis_mpl_render__`, which draws it with `java.awt`/
`ImageIO` and returns a base64 PNG. The Python side base64-decodes and writes
it (to a path, confined to the sandbox roots, or to any file-like buffer).

It is a SUBSET, not real matplotlib: line/scatter/bar/hist/fill/step/pie with
title, axis labels, grid, legend, dashed line styles, markers, log scales and
text annotations — enough for the model to visualize data. Rendering runs
entirely on the JVM (no pip, no native wheels), and any render failure
surfaces to Python as a catchable exception (never crashes the sandbox).

Together with `shim-yaml` this demonstrates the sandbox-shim mechanism: an
extension turns a host / JVM capability into a real importable Python module
while `env-python` stays completely generic about which shims exist.
raw docstring

com.blockether.vis.internal.foundation.shim-pytest

Built-in sandbox SHIM: a pytest-compatible module for the model's Python sandbox, implemented PURELY in Python on the stdlib (ast, inspect, linecache, traceback, warnings, tempfile) — NO host/JVM bridge, NOT a line of Clojure or babashka. pytest is a third-party wheel that does not ship in GraalPy, so agents writing full Python extensions and wanting to TEST them inline would otherwise hit ModuleNotFoundError; this extension contributes a :ext/sandbox-shims entry that env-python/build-agent-context installs into every sandbox Context (main + every sub_loop fork).

It is NOT real pytest: there is no pluggy/plugin system, no conftest.py file discovery, no CLI, and no import-time assertion rewrite. Instead it implements the subset that matters in an inline sandbox where the model writes tests + pytest.main() in ONE block:

  • collection of test_* functions and Test* classes (scoped to the CURRENT block via __vis_src__, so leftovers from earlier blocks in the shared globals are NOT swept in),
  • RUNTIME assert introspection (assert 2 == 3 reconstructed with operand values) done by registering __vis_src__ into linecache and walking the failing frame's AST — the same UX as pytest's rewrite, via a different mechanism,
  • pytest.raises / warns / approx / fail / skip / xfail / importorskip, @pytest.fixture (function/module/session scope, yield-teardown, autouse, recursive injection), @pytest.mark.parametrize / skip / skipif / xfail (+ arbitrary marks), pytest.param, builtin fixtures request / monkeypatch / capsys / capfd / tmp_path, and a pytest.main() runner that prints progress + failure reports + a summary line and returns an exit code.

Unlike shim-yaml/shim-matplotlib there are NO :shim/bindings: the shim is a self-contained Python preamble with zero host callables. It publishes a pytest module into sys.modules (so import pytest works) and staples it onto builtins (so pytest.raises(...) works with NO import, like json/os/requests).

Built-in sandbox SHIM: a `pytest`-compatible module for the model's Python
sandbox, implemented PURELY in Python on the stdlib (`ast`, `inspect`,
`linecache`, `traceback`, `warnings`, `tempfile`) — NO host/JVM bridge, NOT a
line of Clojure or babashka. `pytest` is a third-party wheel that does not
ship in GraalPy, so agents writing full Python extensions and wanting to
TEST them inline would otherwise hit ModuleNotFoundError; this extension
contributes a `:ext/sandbox-shims` entry that
`env-python/build-agent-context` installs into every sandbox Context (main +
every `sub_loop` fork).

It is NOT real pytest: there is no pluggy/plugin system, no `conftest.py`
file discovery, no CLI, and no import-time assertion rewrite. Instead it
implements the subset that matters in an inline sandbox where the model
writes tests + `pytest.main()` in ONE block:

  - collection of `test_*` functions and `Test*` classes (scoped to the
    CURRENT block via `__vis_src__`, so leftovers from earlier blocks in the
    shared globals are NOT swept in),
  - RUNTIME assert introspection (`assert 2 == 3` reconstructed with operand
    values) done by registering `__vis_src__` into `linecache` and walking
    the failing frame's AST — the same UX as pytest's rewrite, via a
    different mechanism,
  - `pytest.raises` / `warns` / `approx` / `fail` / `skip` / `xfail` /
    `importorskip`, `@pytest.fixture` (function/module/session scope,
    yield-teardown, autouse, recursive injection), `@pytest.mark.parametrize`
    / `skip` / `skipif` / `xfail` (+ arbitrary marks), `pytest.param`,
    builtin fixtures `request` / `monkeypatch` / `capsys` / `capfd` /
    `tmp_path`, and a `pytest.main()` runner that prints progress + failure
    reports + a summary line and returns an exit code.

Unlike `shim-yaml`/`shim-matplotlib` there are NO `:shim/bindings`: the shim
is a self-contained Python preamble with zero host callables. It publishes a
`pytest` module into `sys.modules` (so `import pytest` works) and staples it
onto builtins (so `pytest.raises(...)` works with NO import, like
json/os/requests).
raw docstring

com.blockether.vis.internal.foundation.shim-requests

Built-in sandbox SHIM: a requests-compatible module for the model's Python sandbox, backed PURELY by the stdlib urllib.request — NO host/JVM bridge, NOT a line of Clojure or babashka. requests is a third-party wheel that does not ship in GraalPy, so agents that reach for import requests out of habit would otherwise hit ModuleNotFoundError; this extension contributes a :ext/sandbox-shims entry that env-python/build-agent-context installs into every sandbox Context (main + every sub_loop fork).

Because every call travels through the sandbox's OWN socket (urllib -> http.client -> socket), it automatically honours the network toggle (allowHostSocketAccess) AND the allow/deny + anti-SSRF network-guard-python — a JVM babashka.http-client bridge would open an egress path OUTSIDE the sandbox and disarm all of that, which is exactly why this stays 100% Python.

Unlike shim-yaml/shim-matplotlib there are NO :shim/bindings: the shim is a self-contained Python preamble with zero host callables. It publishes a requests module into sys.modules (so import requests works) and staples it onto builtins (so requests.get(...) works with NO import, like json/os).

Built-in sandbox SHIM: a `requests`-compatible module for the model's Python
sandbox, backed PURELY by the stdlib `urllib.request` — NO host/JVM bridge,
NOT a line of Clojure or babashka. `requests` is a third-party wheel that does
not ship in GraalPy, so agents that reach for `import requests` out of habit
would otherwise hit ModuleNotFoundError; this extension contributes a
`:ext/sandbox-shims` entry that `env-python/build-agent-context` installs into
every sandbox Context (main + every `sub_loop` fork).

Because every call travels through the sandbox's OWN socket (urllib ->
http.client -> socket), it automatically honours the network toggle
(`allowHostSocketAccess`) AND the allow/deny + anti-SSRF `network-guard-python`
— a JVM `babashka.http-client` bridge would open an egress path OUTSIDE the
sandbox and disarm all of that, which is exactly why this stays 100% Python.

Unlike `shim-yaml`/`shim-matplotlib` there are NO `:shim/bindings`: the shim is
a self-contained Python preamble with zero host callables. It publishes a
`requests` module into `sys.modules` (so `import requests` works) and staples
it onto builtins (so `requests.get(...)` works with NO import, like json/os).
raw docstring

com.blockether.vis.internal.foundation.shim-yaml

Built-in sandbox SHIM: a PyYAML-compatible yaml module for the model's Python sandbox, backed by the pure-Clojure YAMLStar loader (org.yamlstar/yamlstar). No CPython PyYAML wheel ships in the sandbox; this extension contributes a :ext/sandbox-shims entry that env-python/build-agent-context installs into every sandbox Context (main + every sub_loop fork): the host bridge callables are wired onto the globals, then the Python preamble publishes a yaml module into sys.modules (so import yaml works) and staples it onto builtins (so yaml.safe_load(...) works with NO import).

This is the reference example of the sandbox-shim mechanism: a host / JVM capability surfaced to sandbox Python as a real importable module, with the engine staying completely generic about which shims exist.

Built-in sandbox SHIM: a PyYAML-compatible `yaml` module for the model's
Python sandbox, backed by the pure-Clojure YAMLStar loader
(`org.yamlstar/yamlstar`). No CPython PyYAML wheel ships in the sandbox; this
extension contributes a `:ext/sandbox-shims` entry that
`env-python/build-agent-context` installs into every sandbox Context (main +
every `sub_loop` fork): the host bridge callables are wired onto the globals,
then the Python preamble publishes a `yaml` module into `sys.modules` (so
`import yaml` works) and staples it onto builtins (so `yaml.safe_load(...)`
works with NO import).

This is the reference example of the sandbox-shim mechanism: a host / JVM
capability surfaced to sandbox Python as a real importable module, with the
engine staying completely generic about which shims exist.
raw docstring

com.blockether.vis.internal.foundation.transcript

Full session transcript - DATA first, presentation second.

transcript returns one canonical Clojure map with every turn, every iteration, every executed block plus the LLM-side context (system prompt, message envelope, reasoning trace, top-level provider error, per-iteration vars, answer-form pointer, returned-empty-blocks flag) and the per-block forensic detail (code, comment, result, error, duration, timeout?, repaired?). Pure data. The agent can pattern-match on it; the CLI renders Markdown on top; a future TUI screen, JSON exporter, or analytics extension consumes the same shape.

Lives in foundation because it's an introspection surface, not host plumbing. The sandbox-visible public surface is (session-state) for data and (session-report-md) for Markdown; this namespace owns the transcript portion behind that deeper interface.

Public Clojure surface:

(transcript db-info session-id) -> transcript data map (transcript->md data) -> Markdown string (transcript-md db-info session-id) -> DB lookup + Markdown string

Canonical data shape:

{:session {:id :title :channel :model :provider :created-at} :totals {:turns N :iterations N :tokens {:input :output :reasoning :cached} :cost-usd D} :dialog [{:role :turn-id :content}] :calls [{:kind :ref :parent-ref :turn-id :iteration-id :op :tool :var :code :status :duration-ms :command :target :result :result-summary :info}] :timeline [{:kind :ref :turn-id :iteration-id :content :code :status :duration-ms :result-summary}] :turns [{:id :user-request :status :prior-outcome :provider :model :iteration-count :failure-count :tokens :cost-usd :answer :iterations [{:id :position :status :duration-ms :provider :model :thinking :error :tokens :cost-usd :answer-position :returned-empty-blocks? :vars [{:name :code :value :version}] :blocks [{:position :code :comment :result :error :duration-ms :timeout? :repaired?}]}]}]}

The Markdown renderer renders thinking, iteration-level errors, vars, per-block forensic previews, and final answer text. Large fields are bounded so reports stay safe to open.

Full session transcript - DATA first, presentation second.

`transcript` returns one canonical Clojure map with every turn,
every iteration, every executed block plus the LLM-side context
(system prompt, message envelope, reasoning trace, top-level
provider error, per-iteration vars, answer-form pointer,
returned-empty-blocks flag) and the per-block forensic detail
(code, comment, result, error, duration, timeout?, repaired?).
Pure data. The agent can pattern-match on it; the CLI
renders Markdown on top; a future TUI screen, JSON exporter, or
analytics extension consumes the same shape.

Lives in foundation because it's an introspection surface, not host
plumbing. The sandbox-visible public surface is `(session-state)`
for data and `(session-report-md)` for Markdown; this namespace
owns the transcript portion behind that deeper interface.

Public Clojure surface:

  `(transcript      db-info session-id)`  -> transcript data map
  `(transcript->md  data)`             -> Markdown string
  `(transcript-md   db-info session-id)`  -> DB lookup + Markdown string

Canonical data shape:

  {:session {:id :title :channel :model :provider :created-at}
   :totals       {:turns N :iterations N
                  :tokens {:input :output :reasoning :cached}
                  :cost-usd D}
   :dialog      [{:role :turn-id :content}]
   :calls       [{:kind :ref :parent-ref :turn-id :iteration-id :op :tool
                  :var :code :status :duration-ms :command :target
                  :result :result-summary :info}]
   :timeline    [{:kind :ref :turn-id :iteration-id :content :code
                  :status :duration-ms :result-summary}]
   :turns
    [{:id :user-request :status :prior-outcome :provider :model
      :iteration-count :failure-count
      :tokens :cost-usd :answer
      :iterations
       [{:id :position :status :duration-ms
         :provider :model :thinking :error
         :tokens :cost-usd
         :answer-position :returned-empty-blocks?
         :vars
         [{:name :code :value :version}]
         :blocks
         [{:position :code :comment :result :error
           :duration-ms :timeout? :repaired?}]}]}]}

The Markdown renderer renders thinking, iteration-level errors,
vars, per-block forensic previews, and final answer text. Large
fields are bounded so reports stay safe to open.
raw docstring

com.blockether.vis.internal.foundation.workspace-ctx

Pre-turn "session_workspace" CTX block (STRING-KEYED — crosses the Python boundary as session["workspace"]).

Sessions may work directly in trunk or inside an isolated backend workspace. That distinction is reported on "sandbox", NOT as a VCS. "vcs_kind" reports the underlying repository VCS ("git" when the root is inside a git repo, else "none") so it matches the git/ extension surface, which activates on the same predicate. The model reads the workspace block to know the active root and what it has changed since the fork. The block is stamped once per turn at engine start; ctx_renderer serialises it verbatim.

Pre-turn `"session_workspace"` CTX block (STRING-KEYED — crosses the
Python boundary as `session["workspace"]`).

Sessions may work directly in trunk or inside an isolated backend
workspace. That distinction is reported on `"sandbox"`, NOT
as a VCS. `"vcs_kind"` reports the underlying repository VCS (`"git"`
when the root is inside a git repo, else `"none"`) so it matches the
`git/` extension surface, which activates on the same predicate. The
model reads the workspace block to know the active root and what it has
changed since the fork. The block is stamped once per turn at engine
start; ctx_renderer serialises it verbatim.
raw docstring

com.blockether.vis.internal.foundation.workspace-slashes

Declarative /draft … slash tree.

Drafts are OPT-IN. By default a session works directly in the user's real cwd (trunk). /draft new <label> clones cwd into an isolated draft (an isolated workspace named <label>) and enters it; /draft apply lands the draft's changes into cwd and leaves the draft; /draft abandon discards it and leaves. The header shows <label> (DRAFT) while you're in one.

/draft show whether you're on trunk or in a draft /draft new <label> clone cwd into a draft named <label>, enter it /draft apply land the draft's changes into cwd, leave the draft /draft abandon [why] discard the draft, leave it

Filesystem permissions (/fs, /root) — session-scoped, every channel:

/root [path] show / CHANGE the session's filesystem root /fs list the session's filesystem permissions /fs root <path> same as /root <path> /fs add <path> also let the session operate under <path> /fs remove <path> drop an added directory /fs create <path> mkdir + add it

Vis owns no git lifecycle — apply copies the changed files into the user's real cwd, uncommitted. Handlers are PURE w.r.t. the channel.

Declarative `/draft …` slash tree.

Drafts are OPT-IN. By default a session works directly in the user's
real cwd (trunk). `/draft new <label>` clones cwd into an isolated
draft (an isolated workspace named `<label>`) and enters it; `/draft apply`
lands the draft's changes into cwd and leaves the draft; `/draft
abandon` discards it and leaves. The header shows `<label> (DRAFT)`
while you're in one.

  /draft                show whether you're on trunk or in a draft
  /draft new <label>    clone cwd into a draft named <label>, enter it
  /draft apply          land the draft's changes into cwd, leave the draft
  /draft abandon [why]  discard the draft, leave it

Filesystem permissions (`/fs`, `/root`) — session-scoped, every channel:

  /root [path]          show / CHANGE the session's filesystem root
  /fs                   list the session's filesystem permissions
  /fs root <path>       same as /root <path>
  /fs add <path>        also let the session operate under <path>
  /fs remove <path>     drop an added directory
  /fs create <path>     mkdir + add it

Vis owns no git lifecycle — `apply` copies the changed files into the
user's real cwd, uncommitted. Handlers are PURE w.r.t. the channel.
raw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close