Prompt assembly.
Provider messages are explicit blocks in send order: core system rules,
project instructions (AGENTS.md / CLAUDE.md when present), extension
fragments, current user message. Per-iteration user-role context is the
engine snapshot rendered as a Python dict (session) by the loop.
Prompt assembly. Provider messages are explicit blocks in send order: core system rules, project instructions (AGENTS.md / CLAUDE.md when present), extension fragments, current user message. Per-iteration user-role context is the engine snapshot rendered as a Python dict (`session`) by the loop.
(active-extensions environment)Returns the seq of registered extensions whose :ext/activation-fn returns
truthy for environment, in registration order. Single source of truth for
activation; call ONCE at the top of a turn.
Returns the seq of registered extensions whose `:ext/activation-fn` returns truthy for `environment`, in registration order. Single source of truth for activation; call ONCE at the top of a turn.
(assemble-initial-messages {:keys [stable-prompt-messages initial-user-content
previous-turn-context user-images
skipped-images]})Initial provider messages for one turn. Deliberately excludes full prior
dialog transcript: Vis state flows through persisted iterations,
defs, and DB-backed tools. The current user message is tagged as
CURRENT-USER-MESSAGE.
One full previous-turn context block may be prepended so short follow-ups can inspect the prior exchange without replaying the whole session.
:user-images (from attachments/collect-user-images) turns the user
message multimodal: svar image blocks ride ahead of the text block and
an ATTACHED-IMAGES manifest inside the text names each one.
:skipped-images entries appear in the manifest only.
Initial provider messages for one turn. Deliberately excludes full prior dialog transcript: Vis state flows through persisted iterations, defs, and DB-backed tools. The current user message is tagged as `CURRENT-USER-MESSAGE`. One full previous-turn context block may be prepended so short follow-ups can inspect the prior exchange without replaying the whole session. `:user-images` (from `attachments/collect-user-images`) turns the user message multimodal: svar image blocks ride ahead of the text block and an `ATTACHED-IMAGES` manifest inside the text names each one. `:skipped-images` entries appear in the manifest only.
(assemble-stable-prompt-messages
environment
{:keys [system-prompt active-extensions session-context] :as opts})Assemble provider-prefix messages.
Send order is explicit and tested:
SYSTEM-PROMPT - CORE_SYSTEM_PROMPT + caller addendum
PROJECT-INSTRUCTIONS - AGENTS.md / CLAUDE.md contents (when present)
TURN-SYSTEM-CONTEXT - turn-scoped runtime capability context. Today
it contains extension prompt fragments; future
extension reloads should replace this one
message, never append a second extension
context.
Extension fragments are separate from the core system prompt and are not repeated in per-iteration trailers.
Required opts:
:active-extensions - vec from (active-extensions env). Drives
environment, extension prompt, and hint collection.
Optional opts:
:system-prompt - caller addendum appended to CORE.
:session-context - rendered fenced-Python session = {…} block
(standing session state: workspace / env / routing / tools). Embedded
ONCE here as a cached system message; the loop re-emits only the
session[...] = … structural delta in the conversation when it changes
mid-turn.
Assemble provider-prefix messages.
Send order is explicit and tested:
`SYSTEM-PROMPT` - CORE_SYSTEM_PROMPT + caller addendum
`PROJECT-INSTRUCTIONS` - AGENTS.md / CLAUDE.md contents (when present)
`TURN-SYSTEM-CONTEXT` - turn-scoped runtime capability context. Today
it contains extension prompt fragments; future
extension reloads should replace this one
message, never append a second extension
context.
Extension fragments are separate from the core system prompt and are not
repeated in per-iteration trailers.
Required opts:
`:active-extensions` - vec from `(active-extensions env)`. Drives
environment, extension prompt, and hint collection.
Optional opts:
`:system-prompt` - caller addendum appended to CORE.
`:session-context` - rendered fenced-Python `session = {…}` block
(standing session state: workspace / env / routing / tools). Embedded
ONCE here as a cached system message; the loop re-emits only the
`session[...] = …` structural delta in the conversation when it changes
mid-turn.(build-system-prompt {:keys [system-prompt]})Core system prompt + optional caller addendum + config prompt + SYSTEM.md / APPEND_SYSTEM.md file overrides.
Assembled in send order (later blocks positionally reinforce earlier):
base, then the caller's :system-prompt addendum, then the
:system-prompt pulled from Vis config (vis.edn / .vis/config.edn /
~/.vis/config.edn, deep-merged), then ~/.vis/APPEND_SYSTEM.md, then
<workspace>/.vis/APPEND_SYSTEM.md. The config + file hooks let a project
append house rules without any caller having to pass them.
Full rewrite precedence for the base: <workspace>/.vis/SYSTEM.md >
~/.vis/SYSTEM.md > config :system-prompt map with :replace? true >
CORE_SYSTEM_PROMPT. When a file/config replaces the base, addenda and
append files are still appended after it.
Core system prompt + optional caller addendum + config prompt + SYSTEM.md / APPEND_SYSTEM.md file overrides. Assembled in send order (later blocks positionally reinforce earlier): base, then the caller's `:system-prompt` addendum, then the `:system-prompt` pulled from Vis config (`vis.edn` / `.vis/config.edn` / `~/.vis/config.edn`, deep-merged), then `~/.vis/APPEND_SYSTEM.md`, then `<workspace>/.vis/APPEND_SYSTEM.md`. The config + file hooks let a project append house rules without any caller having to pass them. Full rewrite precedence for the base: `<workspace>/.vis/SYSTEM.md` > `~/.vis/SYSTEM.md` > config `:system-prompt` map with `:replace? true` > `CORE_SYSTEM_PROMPT`. When a file/config replaces the base, addenda and append files are still appended after it.
Override injected ONLY for the non-interactive :cli channel (headless
bin/vis '<task>' one-shot runs). No human is in the loop, so the model
must never wait for input — it makes reasonable assumptions and drives the
work to a finished prose answer.
Override injected ONLY for the non-interactive `:cli` channel (headless `bin/vis '<task>'` one-shot runs). No human is in the loop, so the model must never wait for input — it makes reasonable assumptions and drives the work to a finished prose answer.
(extensions-snapshot active-extensions)Build the active extension summary placed under (:extensions ctx) from a
precomputed active-extensions vec.
Returns a vec of compact, fully-realized data maps - NO functions,
NO atoms, NO opaque runtime objects. The model walks this with a
comprehension / filter / any exactly like any other Python list of
dicts; never has to reach into an extensions() call just to discover
what's loaded.
Per element:
:alias - short symbol the model calls under ('v, 'z,
'git, ...). nil when the extension didn't declare
an :ext.engine/alias.
:namespace - fully-qualified ns symbol of the extension.
:doc - one-line LLM description from :ext/description (when set).
:kind - categorical bucket (providers, channels, foundation,
languages, persistance, ...) used as the section
label both in this snapshot and in vis extensions list (when set).
:registry-id - canonical manifest id, usually the alias symbol.
:symbols - vec of bare symbol names the extension intern'd into
the sandbox.
The vec is bound ONCE at turn start (see iteration-loop) and
stays frozen for the rest of the turn - every iteration sees the
same value.
Build the active extension summary placed under `(:extensions ctx)` from a
precomputed active-extensions vec.
Returns a vec of compact, fully-realized data maps - NO functions,
NO atoms, NO opaque runtime objects. The model walks this with a
comprehension / `filter` / `any` exactly like any other Python list of
dicts; never has to reach into an `extensions()` call just to discover
what's loaded.
Per element:
:alias - short symbol the model calls under (`'v`, `'z`,
`'git`, ...). nil when the extension didn't declare
an `:ext.engine/alias`.
:namespace - fully-qualified ns symbol of the extension.
:doc - one-line LLM description from `:ext/description` (when set).
:kind - categorical bucket (providers, channels, foundation,
languages, persistance, ...) used as the section
label both in this snapshot and in `vis extensions
list` (when set).
:registry-id - canonical manifest id, usually the alias symbol.
:symbols - vec of bare symbol names the extension intern'd into
the sandbox.
The vec is bound ONCE at turn start (see `iteration-loop`) and
stays frozen for the rest of the turn - every iteration sees the
same value.(previous-turn-context-block turns)Cross-process RESUME context: every prior ANSWERED turn rendered oldest→newest
as user asked → what you ran → you answered, so a fresh process reconstructs
the conversation. Every prior answer is rendered in FULL — history is
never truncated. nil when there are no prior turns.
Takes a VEC of {:user-request :answer :interrupted? :results} (results =
[{:scope :src}]). An :interrupted? turn was cut off mid-flight (e.g. a
process restart) with no answer — it's surfaced so a follow-up continue
knows the pending work.
Cross-process RESUME context: every prior ANSWERED turn rendered oldest→newest
as `user asked → what you ran → you answered`, so a fresh process reconstructs
the conversation. Every prior answer is rendered in FULL — history is
never truncated. nil when there are no prior turns.
Takes a VEC of `{:user-request :answer :interrupted? :results}` (results =
`[{:scope :src}]`). An `:interrupted?` turn was cut off mid-flight (e.g. a
process restart) with no answer — it's surfaced so a follow-up `continue`
knows the pending work.Reasoning fallback for models with no native thinking channel. The host
injects this (see with-reasoning-comments-nudge) only when a reasoning
level was requested AND the resolved model is not :reasoning?-capable —
giving weaker / local models (e.g. LM Studio) a scratchpad in the one
channel they always have: the code itself.
Reasoning fallback for models with no native thinking channel. The host injects this (see `with-reasoning-comments-nudge`) only when a reasoning level was requested AND the resolved model is not `:reasoning?`-capable — giving weaker / local models (e.g. LM Studio) a scratchpad in the one channel they always have: the code itself.
(stable-prompt-text messages)Join stable prompt message contents for token budgeting and debug bindings only. Provider sends the original message vector; this is not a send path.
Join stable prompt message contents for token budgeting and debug bindings only. Provider sends the original message vector; this is not a send path.
The few rules weaker / local models most often break, restated in plain
imperative form. Injected (with reason-via-comments-instruction) only for
non-:reasoning? models — the same audience that drowns in the full system
prompt. Recency-weighted: it rides right before the conversation. Keep it
SHORT; it reinforces, it does not re-teach the whole surface.
The few rules weaker / local models most often break, restated in plain imperative form. Injected (with `reason-via-comments-instruction`) only for non-`:reasoning?` models — the same audience that drowns in the full system prompt. Recency-weighted: it rides right before the conversation. Keep it SHORT; it reinforces, it does not re-teach the whole surface.
(with-reasoning-comments-nudge messages)Append the reason-via-code-comments instruction PLUS the weak-model
operating rules as a single turn-scoped system message, after any leading
system messages and before the conversation. Use when a reasoning level was
requested but the model cannot reason natively. No-op-safe: returns
messages unchanged if the nudge can't build.
Append the reason-via-code-comments instruction PLUS the weak-model operating rules as a single turn-scoped system message, after any leading system messages and before the conversation. Use when a reasoning level was requested but the model cannot reason natively. No-op-safe: returns `messages` unchanged if the nudge can't build.
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 |