Liking cljdoc? Tell your friends :D

com.blockether.vis.internal.env-python

Embedded-GraalPy sandbox machinery — the agent's action substrate. The agent writes Python; this ns embeds a GraalPy org.graalvm.polyglot.Context, marshals values across the Clojure↔Python boundary, wires the Clojure tool fns into the Python globals as ProxyExecutables (so cat("x") in Python runs the Clojure cat), and runs the model's code block as ONE whole-block coroutine.

Public surface used by the loop:

create-python-context / set-python-binding! / bind-and-bump! / bind-and-bump-with-doc! / push-eval-result! / push-eval-error! / reset-eval-bindings! / count-top-level-forms / validate-non-empty-block! / validate-no-banned-defs! / restore-sandbox! / SYSTEM_VAR_NAMES / system-var-sym? / lru-atom / current-turn-position / fresh-lru-atom / run-python-block / map-polyglot-error / bind-ctx! / ctx->python-str

The :python-context slot holds the GraalPy Context; the Python top scope is context.getBindings("python"). GraalPy ships in the default deps (runs on Oracle GraalVM 25 → Truffle gets the Graal JIT).

Embedded-GraalPy sandbox machinery — the agent's action substrate. The agent
writes **Python**; this ns embeds a GraalPy `org.graalvm.polyglot.Context`,
marshals values across the Clojure↔Python boundary, wires the Clojure tool
fns into the Python globals as `ProxyExecutable`s (so `cat("x")` in Python
runs the Clojure `cat`), and runs the model's code block as ONE whole-block
coroutine.

Public surface used by the loop:

  create-python-context / set-python-binding! / bind-and-bump! /
  bind-and-bump-with-doc! / push-eval-result! / push-eval-error! /
  reset-eval-bindings! / count-top-level-forms / validate-non-empty-block! /
  validate-no-banned-defs! / restore-sandbox! / SYSTEM_VAR_NAMES /
  system-var-sym? / *lru-atom* / *current-turn-position* / fresh-lru-atom /
  run-python-block / map-polyglot-error / bind-ctx! / ctx->python-str

The `:python-context` slot holds the GraalPy `Context`; the Python top scope is
`context.getBindings("python")`. GraalPy ships in the default deps (runs on
Oracle GraalVM 25 → Truffle gets the Graal JIT).
raw docstring

*auto-repair-brackets?*clj

When true, a bracket-balance syntax hint ALSO appends repair-bracket-balance's single-candidate suggested fix. OFF by default: the walker only DIAGNOSES; the auto-fix stays gated behind this flag until proven safe in the wild.

When true, a bracket-balance syntax hint ALSO appends `repair-bracket-balance`'s
single-candidate suggested fix. OFF by default: the walker only DIAGNOSES; the
auto-fix stays gated behind this flag until proven safe in the wild.
sourceraw docstring

*current-turn-position*clj

source

*lru-atom*clj

source

->cljclj

(->clj v)

Polyglot Value (a Python value) -> Clojure data. STRINGS-ONLY boundary: dicts -> maps with VERBATIM STRING keys (exactly what Python held — no keywordizing, no regex key-shape sniffing), lists/tuples -> vectors, host objects (Java values that crossed the boundary, e.g. UUIDs) -> their underlying Java value via asHostObject, callables/opaque objects -> the raw Value. A non-string Python dict key (int, tuple, ...) stringifies via its Clojure conversion so the map stays string-keyed and total.

Polyglot `Value` (a Python value) -> Clojure data. STRINGS-ONLY boundary:
dicts -> maps with VERBATIM STRING keys (exactly what Python held — no
keywordizing, no regex key-shape sniffing), lists/tuples -> vectors, host
objects (Java values that crossed the boundary, e.g. UUIDs) -> their
underlying Java value via `asHostObject`, callables/opaque objects -> the
raw `Value`. A non-string Python dict key (int, tuple, ...) stringifies via
its Clojure conversion so the map stays string-keyed and total.
sourceraw docstring

->pyclj

(->py x)

Clojure value -> something GraalPy accepts as a Python value. STRINGS-ONLY boundary: map keys must be strings and no keyword/symbol may appear at any depth — a violation throws boundary-violation! naming the key path. Primitives and Strings pass through (the Context auto-converts Java boxed types); collections become polyglot proxies so Python sees dict/list; leaves convert via leaf->py (UUID/Temporal/Date -> ISO strings).

Clojure value -> something GraalPy accepts as a Python value. STRINGS-ONLY
boundary: map keys must be strings and no keyword/symbol may appear at any
depth — a violation throws `boundary-violation!` naming the key path.
Primitives and Strings pass through (the Context auto-converts Java boxed
types); collections become polyglot proxies so Python sees dict/list;
leaves convert via `leaf->py` (UUID/Temporal/Date -> ISO strings).
sourceraw docstring

BANNED_DEF_HEADSclj

Python constructs refused pre-eval — belt-and-suspenders against the obvious sandbox-escape footguns on top of the Context restrictions.

Python constructs refused pre-eval — belt-and-suspenders against the obvious
sandbox-escape footguns on top of the Context restrictions.
sourceraw docstring

bind-and-bump!clj

(bind-and-bump! env sym val)

Set sym -> val in the env's Python sandbox.

Set `sym` -> `val` in the env's Python sandbox.
sourceraw docstring

bind-and-bump-with-doc!clj

(bind-and-bump-with-doc! env sym doc val)

Like bind-and-bump! but also records doc in the side __vis_docs__ dict so a future live-vars view can surface name + doc (Python has no var metadata channel for doc text).

Like `bind-and-bump!` but also records `doc` in the side `__vis_docs__` dict
so a future live-vars view can surface name + doc (Python has no var
metadata channel for doc text).
sourceraw docstring

bind-ctx!clj

(bind-ctx! python-context data)

Bind the standing context in the sandbox as an ordered polyglot dict (->pyProxyHashMap, which GraalPy treats as a NATIVE dict: session["k"], .get, .items(), comprehensions and {**ctx} all work) built from the SAME projection the renderer prints — so the live dict and the wire's session["a"]["b"] = … structural deltas agree. Bound under session only — decoupled from r, no legacy context alias. No JSON round-trip.

Bind the standing context in the sandbox as an ordered polyglot dict (`->py`
→ `ProxyHashMap`, which GraalPy treats as a NATIVE `dict`: `session["k"]`, `.get`,
`.items()`, comprehensions and `{**ctx}` all work) built from the SAME
projection the renderer prints — so the live dict and the wire's
`session["a"]["b"] = …` structural deltas agree. Bound under `session` only —
decoupled from `r`, no legacy `context` alias. No JSON round-trip.
sourceraw docstring

boundary-viewclj

(boundary-view x)
(boundary-view x path)

What a plain-data Clojure value LOOKS LIKE after the GraalPy round trip — the mechanical composition of ->py then ->clj without a Python context. STRINGS-ONLY: string map keys pass VERBATIM, sets/seqs -> vectors, UUID/Temporal/Date leaves -> ISO strings. A keyword/symbol anywhere (key or value, any depth) throws boundary-violation! exactly like the real boundary — fix the producer fixture, never catch it. Idempotent.

Every tool result the model sees in production (serialized structurally by ctx-renderer/render-form-value) has already crossed this boundary, so assertions about what the model reads MUST be written against THIS shape. Tests feed (boundary-view raw-result) to pin that contract without booting GraalPy.

What a plain-data Clojure value LOOKS LIKE after the GraalPy round trip —
the mechanical composition of `->py` then `->clj` without a Python context.
STRINGS-ONLY: string map keys pass VERBATIM, sets/seqs -> vectors,
UUID/Temporal/Date leaves -> ISO strings. A keyword/symbol anywhere (key or
value, any depth) throws `boundary-violation!` exactly like the real
boundary — fix the producer fixture, never catch it. Idempotent.

Every tool result the model sees in production (serialized structurally
by `ctx-renderer/render-form-value`) has already crossed this boundary,
so assertions about what the model reads MUST be written against THIS
shape. Tests feed `(boundary-view raw-result)` to pin that contract
without booting GraalPy.
sourceraw docstring

boundary-violation!clj

(boundary-violation! kind x path)

Throw on a keyword/symbol trying to cross the Clojure->Python boundary. The boundary is STRINGS-ONLY: every map that crosses (tool results, ctx, verb payloads) is built with string keys and string enum values at the SOURCE — there is no silent keyword->string conversion, so a keyword here means a producer bug, not data. path is the key path down from the value handed to ->py, so the offending producer field is nameable.

Throw on a keyword/symbol trying to cross the Clojure->Python boundary.
The boundary is STRINGS-ONLY: every map that crosses (tool results, ctx,
verb payloads) is built with string keys and string enum values at the
SOURCE — there is no silent keyword->string conversion, so a keyword here
means a producer bug, not data. `path` is the key path down from the value
handed to `->py`, so the offending producer field is nameable.
sourceraw docstring

count-top-level-formsclj

(count-top-level-forms code)

Number of top-level Python statements in code. Comment-/whitespace-only blocks return 0. Raises the underlying PolyglotException on a syntax error — that's a syntax issue, not a multi-statement issue.

Number of top-level Python statements in `code`. Comment-/whitespace-only
blocks return 0. Raises the underlying `PolyglotException` on a syntax
error — that's a syntax issue, not a multi-statement issue.
sourceraw docstring

create-python-contextclj

(create-python-context custom-bindings)
(create-python-context custom-bindings roots-fn)
(create-python-context custom-bindings roots-fn network-opts)

Create the embedded-GraalPy sandbox context with all available bindings.

custom-bindings — map of symbol->value (tool fns + engine values). Fns are wired as Python callables; values are marshalled. Returns:

{:python-context <org.graalvm.polyglot.Context> :sandbox-ns :python ; placeholder (Python has one top scope) :initial-ns-keys #{...baseline globals...}}

roots-fn (optional) — a 0-arg fn returning the current allowed root path strings; when supplied the sandbox gets REAL filesystem access confined to them. Omitted ⇒ no Python filesystem (IO-NONE).

Create the embedded-GraalPy sandbox context with all available bindings.

`custom-bindings` — map of symbol->value (tool fns + engine values). Fns are
wired as Python callables; values are marshalled. Returns:

  {:python-context          <org.graalvm.polyglot.Context>
   :sandbox-ns       :python          ; placeholder (Python has one top scope)
   :initial-ns-keys  #{...baseline globals...}}

`roots-fn` (optional) — a 0-arg fn returning the current allowed root path
strings; when supplied the sandbox gets REAL filesystem access confined to
them. Omitted ⇒ no Python filesystem (IO-NONE).
sourceraw docstring

ctx->python-strclj

(ctx->python-str data)

Render a Clojure value as the canonical Python-literal string — produced by Python (__vis_pp__) inside GraalPy, so it matches repr-style Python and the live ctx dict the agent reads. The value is marshalled to a polyglot object via ->py (an ordered ProxyHashMap/ProxyArray that GraalPy treats as a native dict/list) and pretty-printed DIRECTLY — no JSON round-trip.

Render a Clojure value as the canonical Python-literal string — produced by
Python (`__vis_pp__`) inside GraalPy, so it matches `repr`-style Python and
the live `ctx` dict the agent reads. The value is marshalled to a polyglot
object via `->py` (an ordered `ProxyHashMap`/`ProxyArray` that GraalPy treats
as a native `dict`/`list`) and pretty-printed DIRECTLY — no JSON round-trip.
sourceraw docstring

eval-blockclj

(eval-block python-context code)

Evaluate a whole Python code block in python-context. Returns {:source code :result <clj>} on success; throws the PolyglotException on failure (caller maps it to the engine error shape). Globals (defs/imports/ state) persist across calls in the same context.

Evaluate a whole Python `code` block in `python-context`. Returns
`{:source code :result <clj>}` on success; throws the PolyglotException on
failure (caller maps it to the engine error shape). Globals (defs/imports/
state) persist across calls in the same context.
sourceraw docstring

fork-context!clj

(fork-context! custom-bindings)
(fork-context! custom-bindings roots-fn)
(fork-context! custom-bindings roots-fn network-opts)

Fork a CHILD agent Context for a sub_loop — same deny-by-default sandbox as the main context, built ON the shared Engine so it is SAFE to create even while the parent's eval is running (GraalVM-verified: no Truffle deadlock). custom-bindings wires the child's tool/verb fns, which close over the CHILD's env (its own ctx-atom). Returns the same {:python-context :sandbox-ns :initial-ns-keys} shape as create-python-context. The caller owns the child Context's lifecycle (close it when the sub_loop ends). roots-fn (optional) confines the child's Python filesystem to the current filesystem roots, same as the parent.

Fork a CHILD agent Context for a `sub_loop` — same deny-by-default sandbox as
the main context, built ON the shared `Engine` so it is SAFE to create even
while the parent's eval is running (GraalVM-verified: no Truffle deadlock).
`custom-bindings` wires the child's tool/verb fns, which close over the CHILD's
env (its own ctx-atom). Returns the same
`{:python-context :sandbox-ns :initial-ns-keys}` shape as
`create-python-context`. The caller owns the child Context's lifecycle (close
it when the sub_loop ends). `roots-fn` (optional) confines the child's Python
filesystem to the current filesystem roots, same as the parent.
sourceraw docstring

fresh-lru-atomclj

(fresh-lru-atom)
source

map-polyglot-errorclj

(map-polyglot-error e code)

Map a GraalPy PolyglotException into the engine's op-error shape. :phase is :python/syntax for parse errors, else :python/runtime; :line/:column come from the Python source location when present. A host (Clojure-tool) exception is unwrapped so its real message surfaces. Recurring syntax-failure classes get an actionable hint prepended: a NON-ASCII char in code position (em-dash, x, curly quote - CPython's invalid character, precise wherever it lands), a PROSE-leading reply (see prose-leading-syntax-hint, first-line only), and - via parse-diagnose - an unbalanced double-quote or an unbalanced (), [], {} bracket pinpointed to its line/col.

Map a GraalPy `PolyglotException` into the engine's op-error shape. `:phase`
is `:python/syntax` for parse errors, else `:python/runtime`; `:line`/`:column`
come from the Python
source location when present. A host (Clojure-tool) exception is unwrapped so
its real message surfaces. Recurring syntax-failure classes get an actionable
hint prepended: a NON-ASCII char in code position (em-dash, x, curly quote -
CPython's `invalid character`, precise wherever it lands), a PROSE-leading
reply (see `prose-leading-syntax-hint`, first-line only), and - via
`parse-diagnose` - an unbalanced double-quote or an unbalanced (), [], {}
bracket pinpointed to its line/col.
sourceraw docstring

normalize-dict-keyclj

(normalize-dict-key s)

Model-input hygiene at the ONE inbound conversion: a dict key spelled ":from_anchor" is still a STRING (the model drifting into colon spelling while reading keyword-heavy source), so strip the single leading colon when an identifier char follows and the call just works — no lecture, no failure. Data keys are untouched: anchors ("44:f14") start with a digit, paths with a letter or /, neither with :. Produces strings, never keywords.

Model-input hygiene at the ONE inbound conversion: a dict key spelled
`":from_anchor"` is still a STRING (the model drifting into colon
spelling while reading keyword-heavy source), so strip the single leading
colon when an identifier char follows and the call just works — no
lecture, no failure. Data keys are untouched: anchors (`"44:f14"`) start
with a digit, paths with a letter or `/`, neither with `:`. Produces
strings, never keywords.
sourceraw docstring

polyglot-noise-silencedclj

source

push-eval-error!clj

(push-eval-error! env throwable)

Park the most recent uncaught error in the sandbox _e slot. The _1/_2/_3 value stack does NOT advance on error.

Park the most recent uncaught error in the sandbox `_e` slot. The `_1/_2/_3`
value stack does NOT advance on error.
sourceraw docstring

push-eval-result!clj

(push-eval-result! env value)

REPL-style stack push for the sandbox _1 _2 _3 recovery slots. Python convention is _, but we use _1/_2/_3 to match the engine's three-deep history.

REPL-style stack push for the sandbox `_1 _2 _3` recovery slots. Python
convention is `_`, but we use `_1/_2/_3` to match the engine's three-deep
history.
sourceraw docstring

remove-python-binding!clj

(remove-python-binding! python-context sym)

Remove sym from the Python sandbox globals ENTIRELY — the member key disappears, so apropos/dir no longer list it and calling it raises a plain NameError. This is how a deactivated tool must vanish: putMember nil only parks a None under the name, which apropos still lists and which calls as 'NoneType is not callable'.

Remove `sym` from the Python sandbox globals ENTIRELY — the member key
disappears, so `apropos`/`dir` no longer list it and calling it raises
a plain NameError. This is how a deactivated tool must vanish:
`putMember nil` only parks a None under the name, which `apropos`
still lists and which calls as 'NoneType is not callable'.
sourceraw docstring

reset-eval-bindings!clj

(reset-eval-bindings! env)

Clear _1 _2 _3 _e at turn start so a follow-up turn doesn't see leftovers.

Clear `_1 _2 _3 _e` at turn start so a follow-up turn doesn't see leftovers.
sourceraw docstring

restore-sandbox!clj

(restore-sandbox! _python-context _db-info _session-id)

NOOP. The session has ONE persistent interpreter; globals (defs/imports/vars) persist NATURALLY across turns, so there is nothing to restore.

NOOP. The session has ONE persistent interpreter; globals (defs/imports/vars)
persist NATURALLY across turns, so there is nothing to restore.
sourceraw docstring

run-python-blockclj

(run-python-block python-context code & [_opts])

Evaluate one Python code block in python-context as ONE WHOLE-BLOCK coroutine, returning the FLAT sum-typed outcome:

{:stdout <printed>} ; SUCCESS — python_execution (what it print()ed) {:result <value>} ; SUCCESS — a native tool value (nothing printed) {:error <op-error>} ; FAILURE — the raised error IS the result

__vis_run_async__ AST-wraps the block in an async def, AUTO-SETTLES every bare top-level tool call (so cat(x) without await still runs), drives it as a single coroutine, and maps any raised exception against the WHOLE source. The program runs exactly as the model wrote it — Python's own halt-on-exception decides what ran. A pre-eval protected-rebind violation short-circuits to an :error instead.

Evaluate one Python `code` block in `python-context` as ONE WHOLE-BLOCK
coroutine, returning the FLAT sum-typed outcome:

  {:stdout <printed>}   ; SUCCESS — python_execution (what it print()ed)
  {:result <value>}     ; SUCCESS — a native tool value (nothing printed)
  {:error  <op-error>}  ; FAILURE — the raised error IS the result

`__vis_run_async__` AST-wraps the block in an `async def`, AUTO-SETTLES every
bare top-level tool call (so `cat(x)` without `await` still runs), drives it
as a single coroutine, and maps any raised exception against the WHOLE source.
The program runs exactly as the model wrote it — Python's own
halt-on-exception decides what ran. A pre-eval protected-rebind violation
short-circuits to an `:error` instead.
sourceraw docstring

set-python-binding!clj

(set-python-binding! python-context sym val)

Bind sym -> val in the Python sandbox globals. Clojure fns are wired as callables; everything else is marshalled.

ASYNC-BY-DEFAULT: a tool fn bound here is also DEFERRED (wrapped by __vis_deferred__, same as build-agent-context's defer step) so await tool(...) / gather(tool(...)) work. This matters because extension and foundation tools are (re)installed via this fn AFTER the context's own defer pass — without deferring here they'd stay raw/synchronous and the await the prompt teaches would fail. The compaction verbs (session_fold/session_drop/__vis_par__) are bound via create-python-context, not here, so they stay direct. No-op when the async preamble isn't installed (the printer/parser helper contexts never bind tools).

Bind `sym` -> `val` in the Python sandbox globals. Clojure fns are wired as
callables; everything else is marshalled.

ASYNC-BY-DEFAULT: a tool fn bound here is also DEFERRED (wrapped by
`__vis_deferred__`, same as `build-agent-context`'s defer step) so
`await tool(...)` / `gather(tool(...))` work. This matters because extension
and foundation tools are (re)installed via this fn AFTER the context's own
defer pass — without deferring here they'd stay raw/synchronous and the
`await` the prompt teaches would fail. The compaction verbs
(`session_fold`/`session_drop`/`__vis_par__`) are bound via `create-python-context`, not
here, so they stay direct. No-op when the async preamble isn't installed
(the printer/parser helper contexts never bind tools).
sourceraw docstring

shared-engineclj

source

sym->py-nameclj

(sym->py-name sym)

Clojure tool/binding symbol -> a Python-LEGAL global name. Purely mechanical: / and - fold to _ (alias fold + kebab->snake); a trailing ! (mutation marker) is dropped; a trailing ? (predicate) becomes an is_ prefix. So git/status -> git_status, git/commit! -> git_commit, search/web -> search_web, file-exists -> file_exists. FULL SNAKE: this is how the agent reaches the tools — git_status() calls git/status.

A tiny compatibility alias layer may additionally expose selected historical short names (currently find for find_files), but the snake name remains canonical.

Clojure tool/binding symbol -> a Python-LEGAL global name. Purely mechanical:
`/` and `-` fold to `_` (alias fold + kebab->snake); a trailing `!` (mutation
marker) is dropped; a trailing `?` (predicate) becomes an `is_` prefix. So
`git/status` -> `git_status`, `git/commit!` -> `git_commit`, `search/web` ->
`search_web`, `file-exists` -> `file_exists`. FULL SNAKE:
this is how the agent reaches the tools — `git_status()` calls `git/status`.

A tiny compatibility alias layer may additionally expose selected historical
short names (currently `find` for `find_files`), but the snake name remains
canonical.
sourceraw docstring

system-var-sym?clj

(system-var-sym? sym)
source

SYSTEM_VAR_NAMESclj

Engine-owned symbols hidden from user live-var listings.

Engine-owned symbols hidden from user live-var listings.
sourceraw docstring

validate-no-banned-defs!clj

(validate-no-banned-defs! code)

Throws :vis/banned-def-head when code references a banned construct (BANNED_DEF_HEADS). Parse failures are silent — the eval that follows surfaces a clean syntax error with line/column.

Throws `:vis/banned-def-head` when `code` references a banned construct
(`BANNED_DEF_HEADS`). Parse failures are silent — the eval that follows
surfaces a clean syntax error with line/column.
sourceraw docstring

validate-non-empty-block!clj

(validate-non-empty-block! code)

Throws :vis/empty-block when code parses to zero top-level statements (comment-only blocks). Iterations that produce no evidence are rejected at the model boundary.

Throws `:vis/empty-block` when `code` parses to zero top-level statements
(comment-only blocks). Iterations that produce no evidence are rejected at
the model boundary.
sourceraw 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