(answer-markdown answer)Extract the raw Markdown source from a final-answer value.
Canonical shapes:
{:answer string} -> the string (the plain prose answer){:vis/answer-mode :needs-input :answer/text string} -> :answer/text[:ir {…} …] canonical IR AST -> rendered to flat Markdown. loop.clj
hands back an IR AST (NOT a Markdown map) for the provider-error /
fatal-iteration fallbacks. The LIVE wire carries it as :answer_ir,
but session_turn_state.answer_markdown is the only persisted answer
channel — so without rendering it here a failed turn persisted a NULL
answer and reopened as a bare "(error)" with the real message lost.Returns nil for anything else.
Extract the raw Markdown source from a final-answer value.
Canonical shapes:
- `{:answer string}` -> the string (the plain prose answer)
- `{:vis/answer-mode :needs-input :answer/text string}` -> `:answer/text`
- `[:ir {…} …]` canonical IR AST -> rendered to flat Markdown. loop.clj
hands back an IR AST (NOT a Markdown map) for the provider-error /
fatal-iteration fallbacks. The LIVE wire carries it as `:answer_ir`,
but `session_turn_state.answer_markdown` is the only persisted answer
channel — so without rendering it here a failed turn persisted a NULL
answer and reopened as a bare "(error)" with the real message lost.
Returns nil for anything else.(ask-code! opts)One-shot routed svar/ask-code! against the global router.
Plain-text completion + Markdown-code-block extraction — returns the
svar map {:blocks :raw :reasoning :tokens :cost :duration-ms :assistant-message :provider-state}. :blocks is a vec of
{:lang :source} (one entry per Markdown code block); concatenate
yourself with svar.internal.codes/concat-sources if you need a
single string. ask! (JSON-spec) is gone; every Vis caller uses
ask-code!.
One-shot routed `svar/ask-code!` against the global router.
Plain-text completion + Markdown-code-block extraction — returns the
svar map `{:blocks :raw :reasoning :tokens :cost :duration-ms
:assistant-message :provider-state}`. `:blocks` is a vec of
`{:lang :source}` (one entry per Markdown code block); concatenate
yourself with `svar.internal.codes/concat-sources` if you need a
single string. `ask!` (JSON-spec) is gone; every Vis caller uses
`ask-code!`.(auto-archive-hot-symbols! _environment)Deprecated NOOP. Cross-turn def survival was removed when the
definition_* sidecar tables were dropped; the Python sandbox starts
fresh each turn, so there is nothing to archive.
Deprecated NOOP. Cross-turn def survival was removed when the `definition_*` sidecar tables were dropped; the Python sandbox starts fresh each turn, so there is nothing to archive.
(by-id id)Return the session record (UUID :id) or nil.
Return the session record (UUID `:id`) or nil.
In-process env cache.
Keyed by java.util.UUID session-soul-id. Under the 1:1 session ↔
workspace invariant this key is isomorphic to (:workspace/id env)
— one cache entry = one session = one workspace = one Python sandbox
lineage. Lookups normalize incoming strings to UUID via cache-key
so string-id callers keep working alongside the UUID key.
In-process env cache. Keyed by `java.util.UUID` session-soul-id. Under the 1:1 session ↔ workspace invariant this key is isomorphic to `(:workspace/id env)` — one cache entry = one session = one workspace = one Python sandbox lineage. Lookups normalize incoming strings to UUID via `cache-key` so string-id callers keep working alongside the UUID key.
(cache-env! session-id env)Insert env into the cache under session-id (UUID, or string
normalized via cache-key). Returns {:id <UUID> :environment env}.
Insert `env` into the cache under `session-id` (UUID, or string
normalized via `cache-key`). Returns `{:id <UUID> :environment env}`.Toggles a sub_loop child has ON by DEFAULT, whatever the global state — so
a dispatched agent can always run shell commands and reach the harness
skills/agents (the user's rule: sub-agents get both compat layers enabled).
Soft keyword coupling only (no load dependency on those extensions), the same
convention as the posix shim's tool-name coupling. Bound around the child
turn; binding-conveyance carries it into parallel futures.
Toggles a `sub_loop` child has ON by DEFAULT, whatever the global state — so a dispatched agent can always run shell commands and reach the harness skills/agents (the user's rule: sub-agents get both compat layers enabled). Soft keyword coupling only (no load dependency on those extensions), the same convention as the posix shim's tool-name coupling. Bound around the child turn; binding-conveyance carries it into `parallel` futures.
(child-workspace! db-info parent-ws)Spawn the child's workspace. An isolation backend available for the parent's
root → a CoW clone of the parent's workspace (workspace/create! {:from parent-ws}): isolated writes, and workspace/apply! later lands the
since-fork diff back into the parent root. Else (Windows / non-POSIX, or no
backend) → a trunk row at the parent's root (create-trunk-at!): SHARED
files, no clone (safety = disjoint :files). Returns the workspace row.
Spawn the child's workspace. An isolation backend available for the parent's
root → a CoW clone of the parent's workspace (`workspace/create! {:from
parent-ws}`): isolated writes, and `workspace/apply!` later lands the
since-fork diff back into the parent root. Else (Windows / non-POSIX, or no
backend) → a trunk row at the parent's root (`create-trunk-at!`): SHARED
files, no clone (safety = disjoint `:files`). Returns the workspace row.(create! channel)(create! channel {:keys [title external-id workspace-id]})Create a brand-new session.
Opts (all optional): :title display title :external-id channel-specific external id :workspace-id pre-spawned workspace to pin the new session to. When omitted, a trunk workspace is auto-minted in create-environment.
Create a brand-new session.
Opts (all optional):
:title display title
:external-id channel-specific external id
:workspace-id pre-spawned workspace to pin the new session to.
When omitted, a trunk workspace is auto-minted in
create-environment.(create-environment router
{:keys [db session channel external-id title workspace-id
child]})Creates a vis environment (component) for session lifecycle and querying.
The environment holds:
Params:
router - Required. Result of llm/make-router.
opts - Map with :db and optional :session,
:channel, :external-id, :title.
:db accepted forms:
nil - no DB (sandbox-only execution)
:memory - ephemeral in-process SQLite DB
path string - persistent SQLite DB at path
{:path p} - persistent SQLite DB at path
{:datasource ds} - caller-owned DataSource (not closed on dispose)
Returns the vis environment map.
Creates a vis environment (component) for session lifecycle and
querying.
The environment holds:
- Python sandbox context with custom bindings + bindings cache
- DB connection (or shared-mem datasource)
- Router (LLM provider config)
- Extension registry atom
Params:
`router` - Required. Result of `llm/make-router`.
`opts` - Map with `:db` and optional `:session`,
`:channel`, `:external-id`, `:title`.
`:db` accepted forms:
nil - no DB (sandbox-only execution)
:memory - ephemeral in-process SQLite DB
path string - persistent SQLite DB at path
{:path p} - persistent SQLite DB at path
{:datasource ds} - caller-owned DataSource (not closed on dispose)
Returns the vis environment map.(custom-bindings env)Current custom sandbox bindings {sym -> value}.
Current custom sandbox bindings {sym -> value}.
(db-info)Return the process-wide shared DB connection bound to
(config/resolve-db-spec). Thin wrapper over
persistance.core/db-shared-connection! that fills in the default db-spec
so frontend callers stay clear of config resolution.
Return the process-wide shared DB connection bound to `(config/resolve-db-spec)`. Thin wrapper over `persistance.core/db-shared-connection!` that fills in the default db-spec so frontend callers stay clear of config resolution.
(db-sweep-orphaned-running-turns!)(db-sweep-orphaned-running-turns! db)Mark every :running turn as :interrupted. Run at process start
to clean up turns that crashed or were killed mid-write so the next
turn's handover digest renders the right outcome instead of guessing.
Returns the number of turns swept.
Mark every `:running` turn as `:interrupted`. Run at process start to clean up turns that crashed or were killed mid-write so the next turn's handover digest renders the right outcome instead of guessing. Returns the number of turns swept.
(dispose-environment! environment)Disposes a vis environment and releases resources. For persistent DBs
(created with :path), data is preserved. For disposable DBs, all
data is deleted.
A sub_loop CHILD env BORROWS the parent's DB connection (:owns-db? false) —
disposing the child must NOT close it, or the parent loses its DB mid-turn.
Disposes a vis environment and releases resources. For persistent DBs (created with `:path`), data is preserved. For disposable DBs, all data is deleted. A sub_loop CHILD env BORROWS the parent's DB connection (`:owns-db?` false) — disposing the child must NOT close it, or the parent loses its DB mid-turn.
(final-answer-gate-error environment iteration blocks)(final-answer-gate-error environment iteration blocks answer-value)(final-answer-gate-error environment
iteration
blocks
answer-value
active-extensions)(final-answer-gate-error environment
iteration
blocks
answer-value
active-extensions
extra-ctx)Dispatch :turn.answer/validate extension hooks against the
candidate answer. Returns nil when every hook accepts,
otherwise a single string surfaced as the rejected answer's
validation error.
A final answer is plain prose with no tool calls, so it inherently
never shares an iteration with extension/tool calls: the model uses
one iteration to observe tool output, then a later iteration replies
with the answer. Extensions that need an additional veto (e.g.
user-facing safety / format gates) still get their
:turn.answer/validate hook fired here.
active-extensions is passed by the turn loop so activation is
computed once per turn; direct callers may omit it and provide
:extensions on the environment.
Dispatch `:turn.answer/validate` extension hooks against the candidate answer. Returns nil when every hook accepts, otherwise a single string surfaced as the rejected answer's validation error. A final answer is plain prose with no tool calls, so it inherently never shares an iteration with extension/tool calls: the model uses one iteration to observe tool output, then a later iteration replies with the answer. Extensions that need an additional veto (e.g. user-facing safety / format gates) still get their `:turn.answer/validate` hook fired here. `active-extensions` is passed by the turn loop so activation is computed once per turn; direct callers may omit it and provide `:extensions` on the environment.
(get-locals _environment)User-defined sandbox vars surface. Live-vars introspection is cosmetic-off for the Python engine (the agent uses its own Python scope + stdlib), so this returns an empty map. Kept as a stable seam for the trailer/renderer callers.
User-defined sandbox vars surface. Live-vars introspection is cosmetic-off for the Python engine (the agent uses its own Python scope + stdlib), so this returns an empty map. Kept as a stable seam for the trailer/renderer callers.
(get-router)Get or create the shared LLM router.
Honors :router opts from ~/.vis/config.edn (:rate-limit,
:network, :budget, ...). Without that block svar's built-in
defaults apply. See config/router-opts for the supported keys.
Get or create the shared LLM router. Honors `:router` opts from `~/.vis/config.edn` (`:rate-limit`, `:network`, `:budget`, ...). Without that block svar's built-in defaults apply. See `config/router-opts` for the supported keys.
(handle-iteration-exception! e ctx)Error path for the main-loop try/catch around run-iteration.
Infrastructure failures are terminal for the turn; model/format/code
failures still return {::iteration-error ...} for RLM self-correction.
Error path for the main-loop try/catch around `run-iteration`.
Infrastructure failures are terminal for the turn; model/format/code
failures still return `{::iteration-error ...}` for RLM self-correction.(install-extension! environment ext)Register a validated extension into environment (per-env registration,
distinct from the global-registry register-extension! defined earlier
in this file).
Checks :ext/requires - if the extension declares dependencies, all
listed extension namespaces must already be registered. Throws on
missing dependencies.
If an extension with the same :ext/name is already registered,
it is replaced (not duplicated). Enables hot-swap via
reload-extension! (removed for GraalVM native-image compatibility).
Returns environment for chaining.
Register a validated extension into `environment` (per-env registration, distinct from the global-registry `register-extension!` defined earlier in this file). Checks `:ext/requires` - if the extension declares dependencies, all listed extension namespaces must already be registered. Throws on missing dependencies. If an extension with the same `:ext/name` is already registered, it is replaced (not duplicated). Enables hot-swap via `reload-extension!` (removed for GraalVM native-image compatibility). Returns `environment` for chaining.
(iteration-loop environment
user-request
{:keys [system-prompt session-turn-id max-context-tokens hooks
cancel-atom cancel-token reasoning-default routing
extra-body turn-features allow-copilot-claude-deep?
workspace-overrides]})The core iteration loop. Runs assemble -> ask LLM -> execute -> persist
until the model emits :answer or the user cancels.
The core iteration loop. Runs assemble -> ask LLM -> execute -> persist until the model emits `:answer` or the user cancels.
(llm-text! {:keys [messages system prompt reasoning temperature routing]
:as opts})Fast helper LLM call for extensions.
Uses svar routing (:routing {:optimize :cost}) instead of Vis-side model
name heuristics. The call still goes through svar/ask-code! because Vis no
longer uses the retired ask! structured-output path; :lang "text",
:reasoning :off, and :code-tail-pointer? true make the return a plain
text string under :text. Callers may pass either :messages or :system +
:prompt.
Fast helper LLM call for extensions.
Uses svar routing (`:routing {:optimize :cost}`) instead of Vis-side model
name heuristics. The call still goes through `svar/ask-code!` because Vis no
longer uses the retired `ask!` structured-output path; `:lang "text"`,
`:reasoning :off`, and `:code-tail-pointer? true` make the return a plain
text string under :text. Callers may pass either :messages or :system +
:prompt.(markdown-answer? v)True for the canonical final-answer VALUE: {:answer string}.
The answer is the plain prose the model replies with; answer-fn wraps that
string into this {:answer string} shape. The only other accepted value is
the needs-input-answer? map.
True for the canonical final-answer VALUE: `{:answer string}`.
The answer is the plain prose the model replies with; `answer-fn` wraps that
string into this `{:answer string}` shape. The only other accepted value is
the `needs-input-answer?` map.(model-routing-status displayed-provider displayed-model)(model-routing-status router displayed-provider displayed-model)Live routing health for the model a channel is DISPLAYING (displayed-provider
displayed-model — the per-session pick or the config default the picker
shows).svar opens a circuit breaker on a provider after repeated transient failures
(5xx / 'Overloaded' 529 / dropped streams) and routes turns to the next
AVAILABLE provider so work keeps flowing. The displayed model is computed
from config ORDER and is NOT breaker-aware, so during an outage the picker
says opus while turns actually land on zai. This reconciles the two: when
the displayed provider's breaker is open/half-open, it reports what svar is
actually serving so the channel can surface
⚠ <displayed> overloaded — routing to <serving>.
Returns nil when the displayed provider is healthy, else
{:overloaded-provider <kw> :overloaded-model <str> :serving-provider <kw> :serving-model <str>}. serving-* is nil if every
provider is down.
Live routing health for the model a channel is DISPLAYING (`displayed-provider`
+ `displayed-model` — the per-session pick or the config default the picker
shows).
svar opens a circuit breaker on a provider after repeated transient failures
(5xx / 'Overloaded' 529 / dropped streams) and routes turns to the next
AVAILABLE provider so work keeps flowing. The displayed model is computed
from config ORDER and is NOT breaker-aware, so during an outage the picker
says `opus` while turns actually land on `zai`. This reconciles the two: when
the displayed provider's breaker is open/half-open, it reports what svar is
actually serving so the channel can surface
`⚠ <displayed> overloaded — routing to <serving>`.
Returns nil when the displayed provider is healthy, else
`{:overloaded-provider <kw> :overloaded-model <str>
:serving-provider <kw> :serving-model <str>}`. `serving-*` is nil if every
provider is down.(needs-input-answer? v)True for explicit clarification/needs-input answer payloads.
Foundation exposes this through (needs-input ...); the loop
keeps the predicate data-shaped instead of depending on foundation
namespaces so the core runtime has no extension cycle.
True for explicit clarification/needs-input answer payloads. Foundation exposes this through `(needs-input ...)`; the loop keeps the predicate data-shaped instead of depending on foundation namespaces so the core runtime has no extension cycle.
(parallel-sub-loops! parent-env specs)Run several sub-loop!s CONCURRENTLY on Clojure futures, bounded by
MAX-PARALLEL-SUBLOOPS (a Semaphore), and return their results as a vector in
INPUT ORDER. specs is a seq of {:prompt :subctx :models} maps (the model
passes a list of dicts; keys arrive keyword-snake at the GraalPy boundary).
All children share the parent's ONE db-info + depth-cap; the fast rift clone /
merge-back steps serialize on workspace-mutation-lock while the expensive
child LLM turns overlap. A child that throws does NOT sink the batch — its slot
becomes a {:status "failed" :error …} result so the coordinator can see the
failure and merge the rest. The sandbox denies Python threads, so concurrency
lives Clojure-side on the shared GraalVM Engine (forks are safe mid-eval).
Run several `sub-loop!`s CONCURRENTLY on Clojure futures, bounded by
`MAX-PARALLEL-SUBLOOPS` (a Semaphore), and return their results as a vector in
INPUT ORDER. `specs` is a seq of `{:prompt :subctx :models}` maps (the model
passes a list of dicts; keys arrive keyword-snake at the GraalPy boundary).
All children share the parent's ONE db-info + depth-cap; the fast rift clone /
merge-back steps serialize on `workspace-mutation-lock` while the expensive
child LLM turns overlap. A child that throws does NOT sink the batch — its slot
becomes a `{:status "failed" :error …}` result so the coordinator can see the
failure and merge the rest. The sandbox denies Python threads, so concurrency
lives Clojure-side on the shared GraalVM Engine (forks are safe mid-eval).(rebuild-router! config)Rebuild the router from the given config. Used when provider settings change.
Forwards :router opts so live config edits (e.g. tuning
:same-provider-delays-ms) take effect on the next set-provider!
without restarting the JVM.
Rebuild the router from the given config. Used when provider settings change. Forwards `:router` opts so live config edits (e.g. tuning `:same-provider-delays-ms`) take effect on the next `set-provider!` without restarting the JVM.
(refresh-cached-routers! router)Reseat :router on every cached env's environment map.
create-environment snapshots the router into
(:router env) at construction time, and the iteration loop calls
(svar/ask-code! (:router environment) ...) - not the global
router-atom. So when a frontend changes provider
config and rebuilds the global router, every long-lived env in the
cache (TUI keeps one for the whole session) keeps talking to the
previous model until disposed.
Call this immediately after rebuild-router! so the
next send! on any cached session picks up the new router.
Reseat `:router` on every cached env's environment map. `create-environment` snapshots the router into `(:router env)` at construction time, and the iteration loop calls `(svar/ask-code! (:router environment) ...)` - not the global `router-atom`. So when a frontend changes provider config and rebuilds the global router, every long-lived env in the cache (TUI keeps one for the whole session) keeps talking to the *previous* model until disposed. Call this immediately after `rebuild-router!` so the next `send!` on any cached session picks up the new router.
(resolve-effective-model router)(resolve-effective-model router _routing-overrides)Best-effort root model descriptor from router config.
The returned map carries :name (model id, e.g. "gpt-4o") AND
:provider (provider id keyword, e.g. :openai) so every caller
can persist BOTH alongside the model. Earlier versions returned
just the model map and the provider id was silently dropped on
the way to the DB - leaving the meta layer with no way to render
provider/model.
Best-effort root model descriptor from router config. The returned map carries `:name` (model id, e.g. "gpt-4o") AND `:provider` (provider id keyword, e.g. `:openai`) so every caller can persist BOTH alongside the model. Earlier versions returned just the model map and the provider id was silently dropped on the way to the DB - leaving the meta layer with no way to render `provider/model`.
(retry-sub-loop! parent-env spec n)DECORATOR (not a composite): re-run the SAME spec until its child SUCCEEDS,
up to n total attempts (default 2). Returns the first successful result,
else the last failure — stamped with the :attempts made. (Contrast
selector-sub-loops!, which tries DIFFERENT alternatives.)
DECORATOR (not a composite): re-run the SAME `spec` until its child SUCCEEDS, up to `n` total attempts (default 2). Returns the first successful result, else the last failure — stamped with the `:attempts` made. (Contrast `selector-sub-loops!`, which tries DIFFERENT alternatives.)
(router-for-model router prefs)Return a router variant whose provider/model ORDER reflects a model PREFERENCE,
so svar's router picks + falls back accordingly — WE don't pick one model, we
express the preference and let the inner router decide (no svar change: it
already routes by the router's order). prefs is a model name OR an ORDERED
coll of names; matching models are hoisted to the front in preference order
(within each provider AND across providers), and the rest of the router follows
UNCHANGED as fallback. Blank/unknown prefs → the router as-is (child inherits the
parent's order). Coordinator: sub_loop(prompt, subctx, {"models": ["haiku", "sonnet"]}) (or a single "model") — try the cheap one first, fall back.
Return a router variant whose provider/model ORDER reflects a model PREFERENCE,
so svar's router picks + falls back accordingly — WE don't pick one model, we
express the preference and let the inner router decide (no svar change: it
already routes by the router's order). `prefs` is a model name OR an ORDERED
coll of names; matching models are hoisted to the front in preference order
(within each provider AND across providers), and the rest of the router follows
UNCHANGED as fallback. Blank/unknown prefs → the router as-is (child inherits the
parent's order). Coordinator: `sub_loop(prompt, subctx, {"models": ["haiku",
"sonnet"]})` (or a single `"model"`) — try the cheap one first, fall back.(router-initialized?)True once the shared router has been built (via get-router/rebuild-router!).
Lets a frontend defer the FIRST build to lazy first-use instead of forcing it
at startup — so OAuth token fetches (Copilot/Codex) never run at TUI boot.
True once the shared router has been built (via `get-router`/`rebuild-router!`). Lets a frontend defer the FIRST build to lazy first-use instead of forcing it at startup — so OAuth token fetches (Copilot/Codex) never run at TUI boot.
(run-iteration environment
messages
&
[{:keys [routing iteration reasoning-level resolved-model
on-chunk extra-body llm-headers active-extensions
answer-validation-context]}])Runs a single RLM iteration: ask! -> check final -> execute code. Returns map with :thinking :blocks :final-result :api-usage etc.
Runs a single RLM iteration: ask! -> check final -> execute code. Returns map with :thinking :blocks :final-result :api-usage etc.
(run-turn! env user-request loop-opts)Store turn -> iteration-loop -> update turn -> return result.
Derives :prior-outcome (one of :complete, :cancelled, :error)
from the loop result and
persists it on the session_turn_state row. The next turn's
<system_state> digest reads it.
BEFORE the LLM round-trip, every turn is passed through
slash/dispatch. When the user-message resolves
to a registered slash, the turn is fully handled by a synthetic
iteration (tag :user-slash) and the LLM is never called. The
transcript still shows the user message + the slash envelope.
A slash NO extension claims (:reason :unknown) gets one more
chance as a PROMPT TEMPLATE (.vis/prompts/*.md, ~/.vis/prompts,
provider-contributed templates like /skill:<name>): when a
template matches, the expanded text runs as a NORMAL LLM turn.
Registered slashes always win over templates.
Store turn -> iteration-loop -> update turn -> return result. Derives `:prior-outcome` (one of `:complete`, `:cancelled`, `:error`) from the loop result and persists it on the `session_turn_state` row. The next turn's `<system_state>` digest reads it. BEFORE the LLM round-trip, every turn is passed through `slash/dispatch`. When the user-message resolves to a registered slash, the turn is fully handled by a synthetic iteration (`tag :user-slash`) and the LLM is never called. The transcript still shows the user message + the slash envelope. A slash NO extension claims (`:reason :unknown`) gets one more chance as a PROMPT TEMPLATE (`.vis/prompts/*.md`, `~/.vis/prompts`, provider-contributed templates like `/skill:<name>`): when a template matches, the expanded text runs as a NORMAL LLM turn. Registered slashes always win over templates.
(selector-sub-loops! parent-env specs):selector composite (a.k.a. fallback) — try specs IN ORDER until one
child SUCCEEDS, then STOP. Serial. Returns the vector of results tried, in
order: the failed alternatives followed by the first success (the last
result), or — if every alternative failed — all of them (all failures).
Mirrors the BT selector: any-succeed. (Unlike retry, the alternatives are
DIFFERENT specs.)
`:selector` composite (a.k.a. fallback) — try `specs` IN ORDER until one child SUCCEEDS, then STOP. Serial. Returns the vector of results tried, in order: the failed alternatives followed by the first success (the last result), or — if every alternative failed — all of them (all failures). Mirrors the BT selector: any-succeed. (Unlike `retry`, the alternatives are DIFFERENT specs.)
(sequence-sub-loops! parent-env specs):sequence composite — run specs IN ORDER, each only after the prior
SUCCEEDS, SHORT-CIRCUITING on the first failure. Serial by nature (each child
may depend on the last). Returns the vector of results ACTUALLY RUN, in order:
all of them when every child succeeded, or up to and INCLUDING the first
failure when it stopped early (that last result carries the failure). Mirrors
the BT sequence: all-succeed, fail-fast.
`:sequence` composite — run `specs` IN ORDER, each only after the prior SUCCEEDS, SHORT-CIRCUITING on the first failure. Serial by nature (each child may depend on the last). Returns the vector of results ACTUALLY RUN, in order: all of them when every child succeeded, or up to and INCLUDING the first failure when it stopped early (that last result carries the failure). Mirrors the BT sequence: all-succeed, fail-fast.
(set-provider! provider)Set the single active provider config. Persists to disk, updates
in-memory state, rebuilds the global router, and reseats cached
session envs. provider is a svar-native provider map
{:id :base-url :api-key :models [...]}. Replaces an existing
provider with the same :id or appends a new entry.
Set the single active provider config. Persists to disk, updates
in-memory state, rebuilds the global router, and reseats cached
session envs. `provider` is a svar-native provider map
`{:id :base-url :api-key :models [...]}`. Replaces an existing
provider with the same `:id` or appends a new entry.(set-title! id title)Host-driven title change. Resolves the live env (if any) so the
in-memory atom + listener fan-out stay in sync; falls back to a
plain DB write when no env is live for this session (e.g.
vis sessions rename ops).
Host-driven title change. Resolves the live env (if any) so the in-memory atom + listener fan-out stay in sync; falls back to a plain DB write when no env is live for this session (e.g. `vis sessions` rename ops).
(sub-loop! parent-env {:keys [prompt subctx models system-prompt]})Run a CHILD agentic loop for prompt over subctx (the model-supplied focused
slice; see subctx->seed-ctx). Forks a child session env (own ctx-atom seeded
from subctx, own forked Context on the shared Engine, own workspace per
the parent root's isolation backend, reusing the parent's SINGLE DB
connection + depth-cap),
optionally on a cheaper PROPOSED model preference list models
(router-for-model — always a vector, svar falls back). Runs run-turn!,
merges the child's workspace diff back (rift path), then ALWAYS tears the child
down — env disposed, rift clone trashed (both via guard, failures logged) so
nothing leaks across parallel/retry. Returns:
{:task_id <focus> :status <string> :answer :changed_files}
Throws :vis/subloop-depth-exceeded past MAX-SUBLOOP-DEPTH.
Run a CHILD agentic loop for `prompt` over `subctx` (the model-supplied focused
slice; see `subctx->seed-ctx`). Forks a child session env (own ctx-atom seeded
from subctx, own forked Context on the shared Engine, own workspace per
the parent root's isolation backend, reusing the parent's SINGLE DB
connection + depth-cap),
optionally on a cheaper PROPOSED model preference list `models`
(`router-for-model` — always a vector, svar falls back). Runs `run-turn!`,
merges the child's workspace diff back (rift path), then ALWAYS tears the child
down — env disposed, rift clone trashed (both via `guard`, failures logged) so
nothing leaks across `parallel`/`retry`. Returns:
{:task_id <focus> :status <string> :answer :changed_files}
Throws `:vis/subloop-depth-exceeded` past `MAX-SUBLOOP-DEPTH`.(subctx->seed-ctx _subctx)Seed ctx for a sub_loop child's ctx-atom from the model-supplied subctx.
Child contexts start from an empty engine ctx. PURE. Kept as the named seed site.
Seed ctx for a sub_loop child's ctx-atom from the model-supplied `subctx`. Child contexts start from an empty engine ctx. PURE. Kept as the named seed site.
(sync-active-extension-symbols! environment)(sync-active-extension-symbols! environment active-extensions)Make the Python sandbox's callable globals match active extension state.
install-extension! keeps every extension row in :extensions, but only
active extensions contribute callable symbols. Called after per-env
installation and again at turn start so :ext/activation-fn changes become
real tool availability, not just prompt visibility.
The Python sandbox is FLAT globals (no namespaces/aliases/macros): active extensions putMember their symbols straight into the top scope; deactivated extensions have theirs removed (putMember nil). Symbol names are snake-ified by env/set-python-binding!.
Make the Python sandbox's callable globals match active extension state. `install-extension!` keeps every extension row in `:extensions`, but only active extensions contribute callable symbols. Called after per-env installation and again at turn start so `:ext/activation-fn` changes become real tool availability, not just prompt visibility. The Python sandbox is FLAT globals (no namespaces/aliases/macros): active extensions putMember their symbols straight into the top scope; deactivated extensions have theirs removed (putMember nil). Symbol names are snake-ified by env/set-python-binding!.
(turn! environment messages)(turn! environment messages opts)Runs one session turn on an RLM environment using iterative LLM code evaluation.
Params:
environment - RLM environment from create-environment.
messages - Vector of message maps. Always a vector, e.g.:
[(svar/user <prompt-text>)]
[(svar/user <prompt-text> (svar/image <b64> <mime-type>))]
opts - Map, optional:
Returns: Map with:
:error or :cancelled).Runs one session turn on an RLM environment using iterative LLM code evaluation.
Params:
`environment` - RLM environment from create-environment.
`messages` - Vector of message maps. Always a vector, e.g.:
[(svar/user <prompt-text>)]
[(svar/user <prompt-text> (svar/image <b64> <mime-type>))]
`opts` - Map, optional:
- :spec - Output spec for structured answers.
- :model - Override config's default model.
- :max-context-tokens - Token budget for context.
- :debug? - Enable verbose debug logging (default: false). Logs iteration details,
code evaluation, LLM responses at :info level with :rlm-phase context.
- :reasoning-default - Optional base reasoning effort for reasoning-capable models.
Accepts :low/:medium/:high or low/medium/high strings. Adaptive escalation still applies.
- :extra-body - Optional provider-specific request-body params merged into the
upstream LLM call after auto max_tokens + reasoning translation.
Returns:
Map with:
- :trace - Vector of iteration trace entries, each containing:
{:iteration N
:response <llm-response-text>
:blocks [{:id 0 :code <code-str> :result <value> :error nil
:envelope {:started-at-ms 10 :finished-at-ms 15 ...}}
...]}
- :iteration-count - Number of iterations used.
- :duration-ms - Turn duration in milliseconds.
- :tokens - Token usage map {:input N :output N :total N}.
- :cost - Cost map {:input-cost N :output-cost N :total-cost N}.
- :confidence - Confidence level (:high/:medium/:low) from final iteration.
- :reasoning - String summary of how the answer was derived (from LLM's FINAL call).
- :status - Only present on failure (`:error` or `:cancelled`).(validate-iteration-blocks! blocks)Fail fast if a stored/evaluated block lost mandatory envelope. Tool-result envelopes enforce their nested info separately; this spec enforces the outer block-level eval envelope for every executed block.
Fail fast if a stored/evaluated block lost mandatory envelope. Tool-result envelopes enforce their nested info separately; this spec enforces the outer block-level eval envelope for every executed block.
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 |