Canonical registry of VIS-MANAGED STATEFUL RESOURCES — the one interface every long-lived thing vis spawns (an nREPL, a daemon, a background shell, a file watch, a capture) registers under, so a single definition drives THREE spouts:
:session/resources in ctx + resource_stop/resource_restartSESSION-SCOPED. The registry is partitioned by session-id: a resource one
session registers is INVISIBLE to every other session, and ids only need to
be unique WITHIN a session (the (session, id) pair is the global key). Every
public verb takes the owning session as its first arg; the per-session
sandbox tools + ctx are closed over that id, so the agent in session A can
neither see nor stop session B's resources.
B-DISPATCH model. A resource is split into:
~/.vis/resources.edn so a
resource survives a vis restart (display + pid re-attach).:stop-fn :restart-fn :alive-fn :logs-fn :health-fn. Never serialized. Across a restart the OWNER
re-registers them (e.g. the Clojure pack re-attaches its nREPLs by pid on
init) — exactly the pattern repl-manager already uses, lifted here so
EVERY kind gets it. :health-fn answers "alive, but is it WORKING?"
(:up :starting :failed :down …) and is probed — parallel, hard timeout —
on every list-resources, flipping the stored status to reality.:kind is OPEN-ENDED — a bare keyword, no closed enum. The registry never
switches on it; only owners do.
Model ctx stays PURE DATA and groups the flat registry through model-view:
REPLs live at session["resources"]["repls"][language][cwd]; cwd is
their model-facing identity. It advertises can_stop/can_restart but never
carries a callable. Killing goes through stop!/restart! (by session + id) — the
single path the agent tool AND the footer both call. id IS the binding.
Canonical registry of VIS-MANAGED STATEFUL RESOURCES — the one interface every
long-lived thing vis spawns (an nREPL, a daemon, a background shell, a file
watch, a capture) registers under, so a single definition drives THREE spouts:
1. the agent — `:session/resources` in ctx + `resource_stop`/`resource_restart`
2. the footer/TUI — a live count + a dialog that stops/restarts by id
3. the engine — teardown on shutdown
SESSION-SCOPED. The registry is partitioned by `session-id`: a resource one
session registers is INVISIBLE to every other session, and `id`s only need to
be unique WITHIN a session (the `(session, id)` pair is the global key). Every
public verb takes the owning `session` as its first arg; the per-session
sandbox tools + ctx are closed over that id, so the agent in session A can
neither see nor stop session B's resources.
B-DISPATCH model. A resource is split into:
- DATA (serializable, STRING-KEYED): id, kind, label, status, detail,
pid, owner, session, can_stop, can_restart, created_at. This is
what the footer renders and what persists to `~/.vis/resources.edn` so a
resource survives a vis restart (display + pid re-attach).
- LIFECYCLE THUNKS (live, in-memory only): `:stop-fn :restart-fn :alive-fn
:logs-fn :health-fn`. Never serialized. Across a restart the OWNER
re-registers them (e.g. the Clojure pack re-attaches its nREPLs by pid on
init) — exactly the pattern `repl-manager` already uses, lifted here so
EVERY kind gets it. `:health-fn` answers "alive, but is it WORKING?"
(:up :starting :failed :down …) and is probed — parallel, hard timeout —
on every `list-resources`, flipping the stored `status` to reality.
`:kind` is OPEN-ENDED — a bare keyword, no closed enum. The registry never
switches on it; only owners do.
Model ctx stays PURE DATA and groups the flat registry through `model-view`:
REPLs live at `session["resources"]["repls"][language][cwd]`; `cwd` is
their model-facing identity. It advertises `can_stop`/`can_restart` but never
carries a callable. Killing goes through `stop!`/`restart!` (by session + id) — the
single path the agent tool AND the footer both call. `id` IS the binding.(get-resource session id)DATA map for session+id, or nil.
DATA map for `session`+`id`, or nil.
(list-resources session)Vector of live resource DATA maps for session, dead ones pruned and every
health-capable status refreshed first (parallel, hard-timeout probes).
This is what the footer renders and what :session/resources carries into
ctx — ONLY the calling session's resources.
Vector of live resource DATA maps for `session`, dead ones pruned and every health-capable `status` refreshed first (parallel, hard-timeout probes). This is what the footer renders and what `:session/resources` carries into ctx — ONLY the calling session's resources.
(logs session id)Captured output lines for session+id, via the resource's :logs-fn thunk.
Returns a vector of line strings (newest last), or nil when the resource has
no logs-fn (can_logs false) or is unknown. Shell backgrounds expose their
ring buffer; managed language REPLs can expose launcher logs.
Captured output lines for `session`+`id`, via the resource's `:logs-fn` thunk. Returns a vector of line strings (newest last), or nil when the resource has no logs-fn (`can_logs false`) or is unknown. Shell backgrounds expose their ring buffer; managed language REPLs can expose launcher logs.
(model-view resource-data {:keys [root languages]})Nested model-facing resource ground truth. REPLs are directly addressable as
["repls"][language][workspace-relative-dir]; active languages are seeded
with empty maps so absence at a dir means inactive. Non-REPL resources live
under ["other"][kind][id]. The registry/footer keep their flat DATA API.
Nested model-facing resource ground truth. REPLs are directly addressable as `["repls"][language][workspace-relative-dir]`; active languages are seeded with empty maps so absence at a dir means inactive. Non-REPL resources live under `["other"][kind][id]`. The registry/footer keep their flat DATA API.
(pid-alive? pid)Best-effort: is OS process pid still running? nil pid -> true (can't tell,
assume the in-process resource is alive until explicitly unregistered).
Best-effort: is OS process `pid` still running? nil pid -> true (can't tell, assume the in-process resource is alive until explicitly unregistered).
(prune! session)Drop session resource generations whose process is gone (per
:alive-fn/pid). A replacement installed while an old liveness probe is
blocked survives. Returns the vector of generations actually pruned. Cheap
enough to call before every list/render.
Drop `session` resource generations whose process is gone (per `:alive-fn`/pid). A replacement installed while an old liveness probe is blocked survives. Returns the vector of generations actually pruned. Cheap enough to call before every list/render.
(register! session resource)(register! session
resource
{:keys [stop-fn restart-fn alive-fn logs-fn health-fn] :as fns})Register (or replace) a resource UNDER session. resource is the DATA map
(needs at least :id, unique within the session; :kind defaults to
:resource). fns carries the live lifecycle thunks {:stop-fn :restart-fn :alive-fn :logs-fn :health-fn} (all optional — a resource with no :stop-fn
reports can_stop false). Returns the stored DATA map.
Register (or replace) a resource UNDER `session`. `resource` is the DATA map
(needs at least `:id`, unique within the session; `:kind` defaults to
`:resource`). `fns` carries the live lifecycle thunks `{:stop-fn :restart-fn
:alive-fn :logs-fn :health-fn}` (all optional — a resource with no `:stop-fn`
reports `can_stop false`). Returns the stored DATA map.(restart! session id)Run a resource's :restart-fn (kept registered). Scoped to session. The
restart-fn owns re-registration of any changed DATA (e.g. a new port).
Run a resource's `:restart-fn` (kept registered). Scoped to `session`. The restart-fn owns re-registration of any changed DATA (e.g. a new port).
(sandbox-bindings session)Map of engine-builtin tool fns the loop merges into session's agent sandbox.
Closures bind the session so the tools are session-scoped by construction.
Returns are projected to strings-only (->model-result) since they cross the
boundary as the tool result; stop!/restart! stay keyword-keyed for
internal callers (e.g. the REPL pack's repl_stop).
Map of engine-builtin tool fns the loop merges into `session`'s agent sandbox. Closures bind the session so the tools are session-scoped by construction. Returns are projected to strings-only (`->model-result`) since they cross the boundary as the tool result; `stop!`/`restart!` stay keyword-keyed for internal callers (e.g. the REPL pack's `repl_stop`).
(shutdown!)Process-wide teardown spout (daemon/engine shutdown): stop EVERY registered
resource across ALL sessions, so no background child (a background shell, a
REPL) outlives the process that owns its stop-fn. Sessions registered during
teardown are chased too. Best-effort; returns {session-id [stop! results]}.
Process-wide teardown spout (daemon/engine shutdown): stop EVERY registered
resource across ALL sessions, so no background child (a background `shell`, a
REPL) outlives the process that owns its stop-fn. Sessions registered during
teardown are chased too. Best-effort; returns `{session-id [stop! results]}`.(stop! session id)Atomically claim a resource and run its :stop-fn.
THE single stop path — the agent tool and the footer both land here, always
scoped to session so no session can stop another's resource. A successful
stop leaves the claimed generation unregistered. If its callback throws, that
generation is restored only when the id is still vacant, preserving both a
retry handle and any replacement registered during teardown. Returns a result
map.
Atomically claim a resource and run its `:stop-fn`. THE single stop path — the agent tool and the footer both land here, always scoped to `session` so no session can stop another's resource. A successful stop leaves the claimed generation unregistered. If its callback throws, that generation is restored only when the id is still vacant, preserving both a retry handle and any replacement registered during teardown. Returns a result map.
(stop-all! session)Teardown spout for one session (engine end-of-session): stop every stoppable
resource generation registered before or during teardown. Unlike one stop!,
teardown also chases replacements installed while an older callback is
blocked. Non-stoppable and failed generations are reported once and left
registered; a failed stop retains its handle for a later retry. Best-effort;
returns the vector of stop! results.
Teardown spout for one `session` (engine end-of-session): stop every stoppable resource generation registered before or during teardown. Unlike one `stop!`, teardown also chases replacements installed while an older callback is blocked. Non-stoppable and failed generations are reported once and left registered; a failed stop retains its handle for a later retry. Best-effort; returns the vector of stop! results.
(unregister! session id)Drop the resource generation current when this call began from session
(does NOT run its stop-fn — caller decides). A concurrent replacement is never
removed. Returns true only when the captured generation was removed.
Drop the resource generation current when this call began from `session` (does NOT run its stop-fn — caller decides). A concurrent replacement is never removed. Returns true only when the captured generation was removed.
(update! session id patch)Patch the DATA of the resource generation current when this call began (e.g.
flip :status, refresh :detail). No-op if unknown or if that generation was
replaced while the patch was being prepared. Returns the updated DATA map or
nil.
Patch the DATA of the resource generation current when this call began (e.g. flip `:status`, refresh `:detail`). No-op if unknown or if that generation was replaced while the patch was being prepared. Returns the updated DATA map or nil.
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 |