Project-local Python extensions — trusted-context plug-ins.
Vis extensions are normally Clojure libraries baked into the binary at
build time. This namespace adds a second, fully dynamic authoring path:
drop a *.py file into
~/.vis/extensions/ (global — every project) <project>/.vis/extensions/ (project-local — this project only)
and it loads at startup (and on /reload) in BOTH the JVM and the
native image — the extension is Python all the way down, so nothing here
defines a class at runtime.
Each file is first evaluated in a TRUSTED gateway-wide registration namespace. When one of its callables runs for a gateway session, the extension is realized again in the session's separate trusted worker. The sandbox has its own process, interpreter and host-call connection. Extension imports and native-library state are shared only with other trusted extensions in that session. Another session owns another pair of processes.
The model can call an extension TOOL through the ordinary host wrapper, envelope-
checked like any tool, but cannot choose the trusted identity or evaluate code in
the extension namespace. The blockether.vis.extension API exposes host capabilities;
only sealed data crosses the boundary, never live Python or host objects.
Startup, reload and calls with no owning session use the shared registration worker.
A context is only a NAMESPACE in its owning embedded interpreter: opening one costs
a dict, and closing one drops the extension's Python with it. Calls into an extension
(tool, activation, prompt, slash, op hook) are serialized with locking on its
session name, the same proven pattern as the printer context.
The file's top-level vis.register(vis.Extension(...)) call registers through the
ordinary register-extension! — from the registry's perspective a
Python extension is indistinguishable from a Clojure one (activation,
prompt assembly, slash dispatch, vis-agent extension list all just work).
A file that fails to load becomes a load-failure warning (surfaced via
vis-agent doctor), never a crash.
Project-local Python extensions — trusted-context plug-ins. Vis extensions are normally Clojure libraries baked into the binary at build time. This namespace adds a second, fully dynamic authoring path: drop a `*.py` file into ~/.vis/extensions/ (global — every project) <project>/.vis/extensions/ (project-local — this project only) and it loads at startup (and on `/reload`) in BOTH the JVM and the native image — the extension is Python all the way down, so nothing here defines a class at runtime. Each file is first evaluated in a TRUSTED gateway-wide registration namespace. When one of its callables runs for a gateway session, the extension is realized again in the session's separate trusted worker. The sandbox has its own process, interpreter and host-call connection. Extension imports and native-library state are shared only with other trusted extensions in that session. Another session owns another pair of processes. The model can call an extension TOOL through the ordinary host wrapper, envelope- checked like any tool, but cannot choose the trusted identity or evaluate code in the extension namespace. The `blockether.vis.extension` API exposes host capabilities; only sealed data crosses the boundary, never live Python or host objects. Startup, reload and calls with no owning session use the shared registration worker. A context is only a NAMESPACE in its owning embedded interpreter: opening one costs a dict, and closing one drops the extension's Python with it. Calls into an extension (tool, activation, prompt, slash, op hook) are serialized with `locking` on its session name, the same proven pattern as the printer context. The file's top-level `vis.register(vis.Extension(...))` call registers through the ordinary `register-extension!` — from the registry's perspective a Python extension is indistinguishable from a Clojure one (activation, prompt assembly, slash dispatch, `vis-agent extension list` all just work). A file that fails to load becomes a load-failure warning (surfaced via `vis-agent doctor`), never a crash.
(add-change-listener! listener-id f)Subscribe f to Python-extension set changes. f receives
{:extensions [<validated ext map> ...] :removed [<ext-name> ...]}
after every (re)load that changed anything: :extensions is the full
freshly-registered set, :removed the names that no longer exist.
Re-registering the same listener-id replaces the old listener.
Returns listener-id.
Subscribe `f` to Python-extension set changes. `f` receives
`{:extensions [<validated ext map> ...] :removed [<ext-name> ...]}`
after every (re)load that changed anything: `:extensions` is the full
freshly-registered set, `:removed` the names that no longer exist.
Re-registering the same `listener-id` replaces the old listener.
Returns `listener-id`.(ensure-python-extensions-loaded!)(ensure-python-extensions-loaded! opts)Load the Python extension dirs only when this process has not loaded them yet, and NEVER pick an edit up.
The freshness contract: a running process serves exactly the extension bytes
its own start loaded, or the ones the last /reload loaded. Editing a .py
on disk changes nothing until a human reloads. Every implicit load path — a
session env cache miss, an env recycle — goes through
HERE rather than load-python-extensions!, whose content fingerprint would
otherwise re-execute an edited file's top level at the next cache miss, with
no human act anywhere in the chain.
Same return shape as load-python-extensions!.
Load the Python extension dirs only when this process has not loaded them yet, and NEVER pick an edit up. The freshness contract: a running process serves exactly the extension bytes its own start loaded, or the ones the last `/reload` loaded. Editing a `.py` on disk changes nothing until a human reloads. Every implicit load path — a session env cache miss, an env recycle — goes through HERE rather than `load-python-extensions!`, whose content fingerprint would otherwise re-execute an edited file's top level at the next cache miss, with no human act anywhere in the chain. Same return shape as `load-python-extensions!`.
(install-package! source
{:keys [trust subdirectory revision version directory]})Install an approved version, an explicit SHA or a local source project.
Install an approved version, an explicit SHA or a local source project.
(load-failures)Load failures from the latest scan. Each row names the file, error, retained extension, stale? status, loaded/requested source fingerprints and readiness changes.
Load failures from the latest scan. Each row names the file, error, retained extension, stale? status, loaded/requested source fingerprints and readiness changes.
(load-python-extensions!)(load-python-extensions! {:keys [dirs sync-projects?]})Scan the Python extension dirs (default: ~/.vis/extensions and
<cwd>/.vis/extensions) and (re)load every *.py file. Idempotent:
when no file changed since the last scan this is a cheap no-op. On any
change the whole set is torn down and rebuilt (contexts are ~40ms warm
on the shared engine) — deterministic ordering, no partial states.
Change is measured over each extension's WHOLE import root, never its entry
file alone — a package module the entry imports is part of the extension —
and every root is FROZEN at load (freeze-root!), one freeze per root per
pass.
A file that fails to load is recorded in load-failures (and surfaced
by vis-agent doctor) — it never crashes the host.
Returns {:loaded n :failed n :changed? bool}.
Scan the Python extension dirs (default: `~/.vis/extensions` and
`<cwd>/.vis/extensions`) and (re)load every `*.py` file. Idempotent:
when no file changed since the last scan this is a cheap no-op. On any
change the whole set is torn down and rebuilt (contexts are ~40ms warm
on the shared engine) — deterministic ordering, no partial states.
Change is measured over each extension's WHOLE import root, never its entry
file alone — a package module the entry imports is part of the extension —
and every root is FROZEN at load (`freeze-root!`), one freeze per root per
pass.
A file that fails to load is recorded in `load-failures` (and surfaced
by `vis-agent doctor`) — it never crashes the host.
Returns `{:loaded n :failed n :changed? bool}`.(loaded-python-extensions)Snapshot of the currently loaded Python extensions:
{<canonical-path> {:sha ... :ext-name ...}} (context handle elided).
Snapshot of the currently loaded Python extensions:
`{<canonical-path> {:sha ... :ext-name ...}} ` (context handle elided).(net-probe-report method target headers-json body)Guard-only egress probe for the in-sandbox network_probe(...) tool. Parses
target (an http(s) URL or a bare host[:port]), then runs the gateway's
Tier-1 host/port/SSRF gate + EVERY registered network filter over a SYNTHETIC
ctx via [[egress/probe]] — PURE: no socket, no egress, nothing is sent. Returns
a JSON string {scheme, ctx, tier1, filters} (or {error}) — the strings-only
boundary the sandbox glue json.loadses before merging its own local
network_filters and printing the verdict. method may be blank/nil.
headers-json is a JSON object string of request headers (or blank) and body
is the request body string (or blank); both are merged into the synthetic
HTTP-phase ctx so :headers/:body filter rules can be simulated.
Guard-only egress probe for the in-sandbox `network_probe(...)` tool. Parses
`target` (an http(s) URL or a bare `host[:port]`), then runs the gateway's
Tier-1 host/port/SSRF gate + EVERY registered network filter over a SYNTHETIC
ctx via [[egress/probe]] — PURE: no socket, no egress, nothing is sent. Returns
a JSON string `{scheme, ctx, tier1, filters}` (or `{error}`) — the strings-only
boundary the sandbox glue `json.loads`es before merging its own local
`network_filter`s and printing the verdict. `method` may be blank/nil.
`headers-json` is a JSON object string of request headers (or blank) and `body`
is the request body string (or blank); both are merged into the synthetic
HTTP-phase ctx so `:headers`/`:body` filter rules can be simulated.(package-versions source {:keys [subdirectory directory]})List approved releases and update availability for a GitHub repository slug or URL.
List approved releases and update availability for a GitHub repository slug or URL.
(reload-python-extensions!)(reload-python-extensions! opts)Force a full reload of every Python extension (even when no file
changed). Same return shape as load-python-extensions!. Live
sessions pick the new tool bindings up at the next turn boundary.
Force a full reload of every Python extension (even when no file changed). Same return shape as `load-python-extensions!`. Live sessions pick the new tool bindings up at the next turn boundary.
(remove-change-listener! listener-id)Remove a listener registered with add-change-listener!. Returns nil.
Remove a listener registered with [[add-change-listener!]]. Returns nil.
(rollback-package! source {:keys [trust subdirectory version directory]})Restore a previous pinned source or an explicitly selected older approved release.
Restore a previous pinned source or an explicitly selected older approved release.
(sync-packages! {:keys [trust refresh prune dry-run project global]})Explicitly reconcile YAML package scopes and prepare their uv environments. Never imports package entrypoints or reloads a live gateway. Dry-run is inert.
Explicitly reconcile YAML package scopes and prepare their uv environments. Never imports package entrypoints or reloads a live gateway. Dry-run is inert.
(update-package! source {:keys [trust subdirectory version directory]})Explicitly activate a newer approved source snapshot; dependencies prepare on reload.
Explicitly activate a newer approved source snapshot; dependencies prepare on reload.
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 |