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.
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.:kind is OPEN-ENDED — a bare keyword, no closed enum. The registry never
switches on it; only owners do.
ctx stays PURE DATA: 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 ctx carries (crossing the Python boundary as session[resources]),
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`.
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.
`:kind` is OPEN-ENDED — a bare keyword, no closed enum. The registry never
switches on it; only owners do.
ctx stays PURE DATA: 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 first. 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 first. 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.
(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 resources whose process is gone (per :alive-fn/pid). Returns
the vector of pruned ids. Cheap enough to call before every list/render.
Drop `session` resources whose process is gone (per `:alive-fn`/pid). Returns the vector of pruned ids. Cheap enough to call before every list/render.
(register! session resource)(register! session
resource
{:keys [stop-fn restart-fn alive-fn logs-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} (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}` (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`).
(stop! session id)Run a resource's :stop-fn and unregister it. 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. Returns a result map.
Run a resource's `:stop-fn` and unregister it. 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. Returns a result map.
(stop-all! session)Teardown spout for one session (engine end-of-session): stop every resource
that session registered. Best-effort; returns the vector of stop! results.
Teardown spout for one `session` (engine end-of-session): stop every resource that session registered. Best-effort; returns the vector of stop! results.
(unregister! session id)Drop a resource from session (does NOT run its stop-fn — caller decides).
Returns true if something was removed.
Drop a resource from `session` (does NOT run its stop-fn — caller decides). Returns true if something was removed.
(update! session id patch)Patch the DATA of an existing resource (e.g. flip :status, refresh
:detail). No-op if unknown. Returns the updated DATA map or nil.
Patch the DATA of an existing resource (e.g. flip `:status`, refresh `:detail`). No-op if unknown. 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 |