Extension Spec
Auto-Discovery
Extensions are auto-discovered from META-INF/vis/extensions.edn on
the classpath. See Overview — Auto-Discovery
for the full convention.
extension — build and validate
(ext/extension spec) → validated extension map
| Key | Required | Default | Description |
|---|---|---|---|
:ext/namespace | ✓ | — | Fully qualified symbol, e.g. 'com.blockether.vis.ext.editing, 'com.acme.ext.git |
:ext/doc | ✓ | — | Extension-level description |
:ext/group | ✓ | — | Top-level prompt group, e.g. "knowledge" |
:ext/subgroup | ✗ | same as :ext/group | Finer-grained grouping within the group |
:ext/activation-fn | ✗ | (constantly true) | (fn [env] → bool) — when falsy, all symbols are unbound and nudge-fn is skipped |
:ext/prompt | ✓ | — | String or (fn [env] → string) — LLM-facing docs in system prompt |
:ext/nudge-fn | ✗ | — | (fn [ctx] → string|nil) — per-iteration nudge composer (see Nudge System) |
:ext/requires | ✗ | [] | Vector of extension namespace symbols that must be registered first, e.g. ['com.blockether.vis.ext.editing] |
:ext/version | ✗ | — | Semver version string, e.g. "1.0.0", "0.3.1-SNAPSHOT" |
:ext/author | ✗ | — | Author name or org, e.g. "Blockether" |
:ext/license | ✗ | — | SPDX license identifier, e.g. "MIT", "Apache-2.0", "Apache-2.0" |
:ext/symbols | ✓ | — | Vector of symbol entries (from symbol / value) |
:ext/classes | ✗ | {} | {fq-symbol → Class} — Java classes exposed in sandbox |
:ext/imports | ✗ | {} | {short-symbol → fq-symbol} — short-name imports |
:ext/ns-alias | ✓ | — | {:ns 'vis.ext.fs :alias 'fs} — required. Creates a dedicated SCI namespace with alias. Symbols are bound only into this namespace, never into sandbox directly. The alias is auto-required in the sandbox. The LLM must use (fs/read-file ...) — bare (read-file ...) does not resolve. |
symbol — function binding
(ext/symbol sym-name f opts) → validated fn symbol entry
| Opt | Required | Default | Description |
|---|---|---|---|
:doc | ✓ | — | One-liner shown in the sandbox var’s docstring |
:arglists | ✓ | — | Argument signatures, e.g. '([query] [query opts]) |
:examples | ✗ | derived from :arglists | Usage examples injected into system prompt |
:before-fn | ✗ | — | (fn [env f args] → map) — pre-call hook |
:after-fn | ✗ | — | (fn [env f args result] → map) — post-call hook |
:on-error-fn | ✗ | — | (fn [err env f args] → map) — error handler |
value — constant binding
(ext/value sym-name val opts) → validated value symbol entry
| Opt | Required | Description |
|---|---|---|
:doc | ✓ | One-liner description |
wrap-extension — bind into SCI
(ext/wrap-extension ext env) → {sym → fn-or-value}
Wraps every function symbol through invoke-symbol-wrapper
(before → fn → after, with on-error recovery). Value symbols
are returned as {sym → value}. Each wrapped fn closes over
the extension, symbol entry, and environment.
validate! — standalone validation
(ext/validate! ext) → normalized ext (or throws)
Normalizes :ext/prompt (string → fn) then checks the spec.
Called internally by extension; safe to call standalone.
Note:
:ext/promptacceptsstringorfn?. Bothextensionandvalidate!normalize strings to(constantly s)before validation.
Full Example
(ns com.blockether.vis.ext.documents
(:require [c.b.vis.loop.runtime.conversation.environment.extension :as ext]))
(defn- search-fn [query] ...)
(defn- search-with-opts [query opts] ...)
(def search-sym
(ext/symbol 'search search-fn
{:doc "Full-text search across ingested documents."
:arglists '([query] [query opts])
:examples ["(docs/search \"neural\")"
"(docs/search \"attention\" {:limit 5})"]
:before-fn (fn [env f args]
{:args (update args 0 str/lower-case)})
:after-fn (fn [env f args result]
{:result (take 10 result)})}))
(def max-results-sym
(ext/value 'max-results 50
{:doc "Maximum number of search results returned."}))
(def docs-ext
(ext/extension
{:ext/namespace 'com.blockether.vis.ext.documents
:ext/doc "Document search and retrieval"
:ext/version "1.0.0"
:ext/author "Blockether"
:ext/license "Apache-2.0"
:ext/group "knowledge"
:ext/subgroup "documents"
:ext/ns-alias {:ns 'vis.ext.docs :alias 'docs}
:ext/requires ['com.blockether.vis.ext.editing]
:ext/prompt "Document search (use docs/ prefix):
- (docs/search query) — full-text search across documents
- docs/max-results — max results constant (default 50)"
:ext/activation-fn (fn [env] (seq (list-docs (:db-info env))))
:ext/nudge-fn (fn [{:keys [environment iteration prev-expressions]}]
(when (and (> iteration 5)
(some :error prev-expressions))
"[system_nudge] Document searches are failing."))
:ext/symbols [search-sym max-results-sym]
:ext/classes {'java.time.LocalDate java.time.LocalDate}
:ext/imports {'LocalDate 'java.time.LocalDate}}))
;; Self-register at load time
(ext/register-global! docs-ext)
The LLM sees in the system prompt:
[namespace: docs → vis.ext.docs]
Document search (use docs/ prefix):
- (docs/search query) — full-text search across documents
- docs/max-results — max results constant (default 50)
And calls (docs/search "neural") from :code blocks.
Bare (search "neural") does not resolve.