Extension-owned durable sidecar API.
Public ext-* helpers are for code running inside an extension callback. They never accept :extension-id from callers; the currently executing extension identity is supplied by com.blockether.vis.internal.extension.
The db-* persistence facade remains the privileged/admin surface for inspecting rows across extensions.
Extension-owned durable sidecar API. Public ext-* helpers are for code running inside an extension callback. They never accept :extension-id from callers; the currently executing extension identity is supplied by com.blockether.vis.internal.extension. The db-* persistence facade remains the privileged/admin surface for inspecting rows across extensions.
What THIS machine can actually run, asked once and answered as data.
A pack that REGISTERED exists in this process. Whether the machine behind it works is a different question, asked at a different time: landing a pack costs an atom write and happens in manifest order for every session, while a capability costs whatever its probe costs and is paid by the FIRST caller that needs it — so a session that never speaks never loads sherpa's 8-13 MB of natives, and a session that never speaks never waits for them either.
No heuristic decides this. maxMemory, an OS name and an arch say nothing
about a glibc version, a missing libstdc++, or a CPU without the instructions
a wheel was built for: they refuse machines that would have worked and admit
machines that die inside the linker. The probe asks the real question by doing
the real thing, once.
A verdict is remembered only when asking again cannot answer differently:
:ready — it worked, and nothing in this process will unwork it.:unavailable :kind :terminal — the JVM already froze this answer. A class
whose static initializer failed can NEVER load again in the same process, so
probing again can only re-download a library and reprint a linker error
nobody can act on. That state is the reported "voice only works after
restarting Vis", which is why the verdict, not the caller, is what says so.:unavailable :kind :transient — no network, a missing file, a full disk.
NOT remembered: the next ask retries.The state atom starts EMPTY and stays empty at load. graal-build-time
initializes every Clojure namespace inside the BUILDER, so a value computed by
a top-level form is baked into the image heap of every installed binary: a
probed verdict would ship the BUILD machine's answer to every user. That cost
is already paid for once — sherpa/default-native-dir is a function for this
reason, and TTYDeviceControl is the single --initialize-at-run-time in
build.clj after a build-time SUPPORTED=true SIGSEGV'd v0.1.33-35.
What THIS machine can actually run, asked once and answered as data. A pack that REGISTERED exists in this process. Whether the machine behind it works is a different question, asked at a different time: landing a pack costs an atom write and happens in manifest order for every session, while a capability costs whatever its probe costs and is paid by the FIRST caller that needs it — so a session that never speaks never loads sherpa's 8-13 MB of natives, and a session that never speaks never waits for them either. No heuristic decides this. `maxMemory`, an OS name and an arch say nothing about a glibc version, a missing `libstdc++`, or a CPU without the instructions a wheel was built for: they refuse machines that would have worked and admit machines that die inside the linker. The probe asks the real question by doing the real thing, once. A verdict is remembered only when asking again cannot answer differently: - `:ready` — it worked, and nothing in this process will unwork it. - `:unavailable` `:kind :terminal` — the JVM already froze this answer. A class whose static initializer failed can NEVER load again in the same process, so probing again can only re-download a library and reprint a linker error nobody can act on. That state is the reported "voice only works after restarting Vis", which is why the verdict, not the caller, is what says so. - `:unavailable` `:kind :transient` — no network, a missing file, a full disk. NOT remembered: the next ask retries. The state atom starts EMPTY and stays empty at load. `graal-build-time` initializes every Clojure namespace inside the BUILDER, so a value computed by a top-level form is baked into the image heap of every installed binary: a probed verdict would ship the BUILD machine's answer to every user. That cost is already paid for once — `sherpa/default-native-dir` is a function for this reason, and `TTYDeviceControl` is the single `--initialize-at-run-time` in `build.clj` after a build-time `SUPPORTED=true` SIGSEGV'd v0.1.33-35.
Extension subsystem: spec, builders, hook execution, the global registry, and parse-error rescue.
An extension is the SINGLE entry point for everything a third-party
bundle contributes to vis. Whatever surfaces it populates - Python
sandbox symbols, CLI commands, channels, providers, persistence
backends - it does so by listing them in the matching :ext/<surface>
slot, and register-extension! dispatches each slot to its concrete
sub-registry. The same data feeds:
:ext.engine/symbols:ext/hooks checksChannel and provider registries live in internal.registry; the persistence
backend table lives in internal.persistance. The one ordered distribution manifest invokes
each extension's explicit registration function.
Extension subsystem: spec, builders, hook execution, the global registry, and parse-error rescue. An extension is the SINGLE entry point for everything a third-party bundle contributes to vis. Whatever surfaces it populates - Python sandbox symbols, CLI commands, channels, providers, persistence backends - it does so by listing them in the matching `:ext/<surface>` slot, and `register-extension!` dispatches each slot to its concrete sub-registry. The same data feeds: - the active-extensions list every iteration consults - the system-prompt block rendered from `:ext.engine/symbols` - the per-iteration `:ext/hooks` checks - the parse-error rescue chain Channel and provider registries live in `internal.registry`; the persistence backend table lives in `internal.persistance`. The one ordered distribution manifest invokes each extension's explicit registration function.
The ordered registration list for Vis' built-in modules, not a user extension loader.
META-INF/vis/manifest.edn has one key, :initialization. Each entry is a
qualified registration symbol, or {:register qualified.ns/register! :apropos "META-INF/vis/apropos/docs.edn"} when the module owns static docs.
Keeping documents beside their owner preserves discovery order.
Every built-in is required. Registration runs once in dependency order; a failure
stops initialization rather than leaving a partial engine. Successful entries are
remembered so a retry does not register them again. User extensions are Python
files, loaded separately by internal.python.extensions.
Nothing scans the classpath and there is no alternate manifest format.
The ordered registration list for Vis' built-in modules, not a user extension loader.
`META-INF/vis/manifest.edn` has one key, `:initialization`. Each entry is a
qualified registration symbol, or `{:register qualified.ns/register!
:apropos "META-INF/vis/apropos/docs.edn"}` when the module owns static docs.
Keeping documents beside their owner preserves discovery order.
Every built-in is required. Registration runs once in dependency order; a failure
stops initialization rather than leaving a partial engine. Successful entries are
remembered so a retry does not register them again. User extensions are Python
files, loaded separately by `internal.python.extensions`.
Nothing scans the classpath and there is no alternate manifest format.Build-time reachability for everything the engine loads DYNAMICALLY.
The manifest resolves an entrypoint with requiring-resolve, a pack reaches
its own optional half the same way (voice's ASR), and a
native image contains only what the BUILDER saw. A namespace nobody loads
while the image is built therefore has no class in the binary, and the first
command that wants it dies with "Could not locate ...__init.class on
classpath" - on a binary whose JVM run is perfectly healthy. Loading them
here puts them in clojure.lang.RT's namespace map, which IS part of the
image heap, so the same resolve at run time finds them already loaded.
NOTHING requires this namespace. Vis' native-image.properties names its
__init class in --initialize-at-build-time, so the builder loads it outside
any other namespace's load: an entrypoint requiring the engine back would
otherwise create a cycle. A JVM run never
loads this file, so manifest registration stays lazy.
The engine namespace list is derived from the manifest's entrypoints and the compiled namespace tree. Formatter and analyzer dependencies are required here: JVM registration defers them, but the native image must retain their code.
Build-time reachability for everything the engine loads DYNAMICALLY. The manifest resolves an entrypoint with `requiring-resolve`, a pack reaches its own optional half the same way (voice's ASR), and a native image contains only what the BUILDER saw. A namespace nobody loads while the image is built therefore has no class in the binary, and the first command that wants it dies with "Could not locate ...__init.class on classpath" - on a binary whose JVM run is perfectly healthy. Loading them here puts them in `clojure.lang.RT`'s namespace map, which IS part of the image heap, so the same resolve at run time finds them already loaded. NOTHING requires this namespace. Vis' `native-image.properties` names its `__init` class in `--initialize-at-build-time`, so the builder loads it outside any other namespace's load: an entrypoint requiring the engine back would otherwise create a cycle. A JVM run never loads this file, so manifest registration stays lazy. The engine namespace list is derived from the manifest's entrypoints and the compiled namespace tree. Formatter and analyzer dependencies are required here: JVM registration defers them, but the native image must retain their code.
Three global registries in one place: channels, providers, commands.
Each descriptor is checked by a local predicate before entering its process registry.
Channel registry (:channel/id keyword):
channel build + validate a descriptor
register-channel! register, idempotent on :channel/id
deregister-channel! remove by id
registered-channels all entries, vec
channel-by-id lookup by id
by-cmd lookup by :channel/cmd
Provider registry (:provider/id keyword):
provider build + validate a descriptor
register-provider! register, idempotent on :provider/id
deregister-provider! remove by id
registered-providers all entries, vec
provider-by-id lookup by id
Command registry ([:cmd/parent :cmd/name] tuple key):
command build + validate a descriptor
resolve-subcommands static vec or dynamic 0-arg fn -> vec
register-cmd! register, idempotent on [parent name]
deregister-cmd! remove by [parent name]
registered-commands all entries, vec (registration order)
registered-under filter by parent path
Channel mounting:
channel-subcommands compose vis-agent channels subcommand vec
from the channel registry + any commands
registered with :cmd/parent ["channels"].
Loading this ns also registers the
vis-agent channels parent itself.
Parsing / help rendering / dispatch utilities live in
com.blockether.vis.internal.commandline. The closed initialization manifest
lives in com.blockether.vis.internal.extension.manifest.
Three global registries in one place: channels, providers, commands.
Each descriptor is checked by a local predicate before entering its process registry.
Channel registry (`:channel/id` keyword):
channel build + validate a descriptor
register-channel! register, idempotent on :channel/id
deregister-channel! remove by id
registered-channels all entries, vec
channel-by-id lookup by id
by-cmd lookup by :channel/cmd
Provider registry (`:provider/id` keyword):
provider build + validate a descriptor
register-provider! register, idempotent on :provider/id
deregister-provider! remove by id
registered-providers all entries, vec
provider-by-id lookup by id
Command registry (`[:cmd/parent :cmd/name]` tuple key):
command build + validate a descriptor
resolve-subcommands static vec or dynamic 0-arg fn -> vec
register-cmd! register, idempotent on [parent name]
deregister-cmd! remove by [parent name]
registered-commands all entries, vec (registration order)
registered-under filter by parent path
Channel mounting:
channel-subcommands compose `vis-agent channels` subcommand vec
from the channel registry + any commands
registered with `:cmd/parent ["channels"]`.
Loading this ns also registers the
`vis-agent channels` parent itself.
Parsing / help rendering / dispatch utilities live in
`com.blockether.vis.internal.commandline`. The closed initialization manifest
lives in `com.blockether.vis.internal.extension.manifest`.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 |