An agent in the little world
An agent should be able to stand inside luvcraft the way a player does: see
what is there, act on it with the same verbs, and leave a record others can
read. The harness notes (#YY6EYZ) settled what
any such surface must do. This page says how luv does it: with the CLIM
vocabulary the game already speaks -- command tables, presentation types,
views, output records -- and with the world itself as the place the agent's
work shows up. The OpenAI websocket client
(openai/agent.lisp) is the first provider
behind it; nothing here depends on which provider it is. The four harnesses converged on seams (#YY6EYZ), and CLIM already has a name
for each of them. That is the whole design bet of this page: Nothing in the table is a new mechanism. The work is to choose the
presentation types, write the views, and route a provider's tool calls
through Referenced from code: Preserve Luvcraft's public vocabulary while making these names the exact
shared objects. Remaining game files specialize and consume the common
protocols without wrappers or a duplicate compatibility layer. Views and handles Tools The transcript Commands The HUD The wall Embodied agents command-menu.lisp already answers
"what can I do?" for the player by walking the command tables, and it skips
parameterized commands because M-x cannot yet prompt for their arguments.
The agent's tool list is the same walk with the same gap. One should yield a keystroke, an M-x entry that prompts through Concretely: a Mentioned in: The agent surface is a command table, a view, and a handle space, Wire three commands into an agent and draw their cassettes, Parameterized M-x prompts through presentation types Froth's lesson is that what the tool found, how much the model sees,
and how it is spelled for a provider are three policies (#5GU1VI). In
CLIM a tool body does not return a string; it =present=s objects to a
recording stream. The three policies become three views over the same
records: Because rendering is Mentioned in: A glance is an image and the small truth that frames it, Wire three commands into an agent and draw their cassettes Sheaf's six characters (#9P5YLW) and this wiki's figure IDs are the same
idea: a short handle the model can cite by typing. In the agent stream,
every presented object of interest -- a chunk, a block position, a session,
a folded body -- is entered in a presentation history under a short id, and
Mentioned in: The agent surface is a command table, a view, and a handle space, A tool result is a presentation, and the model reads it under a view Before nxt deleted its UI runtime (nxtui commit Mentioned in: The agent surface is a command table, a view, and a handle space, A tool result is a presentation, and the model reads it under a view, Draw and print: live cards and committed records, Wire three commands into an agent and draw their cassettes Referenced from code: The cassette HUD: the current turn drawn as cards in the corner (#9K823O,
#14ZP6S). A McCLIM frame composited over the view, the way the hotbar and the legend
are. Every frame the overlay reads the turn and repaints when anything
it shows has changed -- a call's status, its elapsed tenths of a second, a
new line of thought -- so a running call's clock ticks and a finished one
flips in place. The drawing itself is presentation methods under the
cassette view, one per kind of record, so a wall can draw the same cards
later from the same objects. The agent's wall: the transcript on a terminal wall, drawn the way the
phone and the shell walls are drawn -- Ghostty cells as Slug glyph
geometry, sharp at any distance (#GJUPMQ) -- rather than through a McCLIM
mirror. The wall is a Two threads meet here and one thing crosses between them, the way the
Telegram wall already does it: the turn's thread formats text and posts it
to an outbox; the canvas thread drains the outbox into the terminal at the
frame boundary, where it also reads the terminal to draw it. Nothing
writes the terminal from anywhere else. A terminal wall showing one agent's transcript. The same nxtui tree ( Luv has both surfaces already. The HUD overlay (the legend and the tape
prompt are McCLIM frames composited over the view) is the live surface:
running calls are Mentioned in: The agent surface is a command table, a view, and a handle space, The agent has a body, Wire three commands into an agent and draw their cassettes Referenced from code: The cassette HUD: the current turn drawn as cards in the corner (#9K823O,
#14ZP6S). A McCLIM frame composited over the view, the way the hotbar and the legend
are. Every frame the overlay reads the turn and repaints when anything
it shows has changed -- a call's status, its elapsed tenths of a second, a
new line of thought -- so a running call's clock ticks and a finished one
flips in place. The drawing itself is presentation methods under the
cassette view, one per kind of record, so a wall can draw the same cards
later from the same objects. nxtui's In luv the scope is a CLOS object rather than a coroutine zone: a
Mentioned in: The agent surface is a command table, a view, and a handle space An agent that is only a websocket is a chat window with extra steps. The
point of doing this inside luvcraft is that the agent can be somewhere:
a presence object with a position and a facing, so Mentioned in: The agent surface is a command table, a view, and a handle space, The gnome is an agent occupying a cell, not a block Referenced from code: The gnome: an agent with a body (#5JDNKF), not a block (#V9DHV5). A gnome occupies a discrete world cell but is not a block. The cell is
semantic state -- where this agent stands -- while its animated,
camera-facing figure is an independently rendered scene object. Focusing
it is how you talk to it: the view leans in the way it does for a wall, the
keys become a prompt line at the bottom of the screen, and RET asks. The
gnome then thinks, and its thinking shows above its head as a bubble: a
matte rounded cassette standing in the world, drawn through McCLIM the way
the Telegram panel is (a world widget, in the scene, with relief). Each
tool call is the next cassette, and pushes the ones before it upward.
When the gnome talks -- the SAY command, which is a tool like any other --
it is not a cassette but a title at the bottom of the view, the way an NPC
line is subtitled. gnome Commands run on the canvas thread; a provider turn runs on its own.
Mentioned in: A tool is a CLIM command whose arguments are presentation types, Discrete destinations, continuously embodied, The language-agent harness is not the gnome Referenced from code: A parked tool call is a semantic object, not a closure waiting to happen.
The canvas owns every transition; the provider thread only waits on the
private mailbox through One inert proposed tool effect waiting for a player's decision. A tool approval should show its consequence, not merely ask whether a verb may
run. Construction makes that unusually literal. The construction specialization carries a The first Mentioned in: Agent work has four visible surfaces, Make wiki media executable without committing it Referenced from code: The construction proposal as a reproducible little scene. This is the
first capture recipe attached to the possible-world design. #Y7X7WK Call The lobby is useful in a game and accidental in a design plate. An embodied agent's The observation keeps unavailable cells distinct from air (#YUBB7X). It
selects each intersecting resident chunk once, borrows that chunk's palette
and dense index column, and counts raw lanes inside the box. The retained
result is a semantic object with an encoded PNG and a small material
histogram; the model reads its compact textual presentation and the provider
receives the same result's PNG as image content (#WS5OEX). A glance therefore
remains cheap enough to be an ordinary verb and rich enough to orient action. Facing belongs to the body rather than the observer. Movement updates a
retained yaw and stopping preserves it, so both the over-the-shoulder camera
and the analytic gnome or cat field read the same direction. The proxy quad
may face the camera for conservative rasterization while the figure inside it
looks where the agent is going; an avatar need not stare at every eye that can
see it. Evidence (2026-08-21). The first live gnome glance was an 819 by 614 clean
PNG looking east over the gnome's back. Its paired reading counted 10,625
sites in Mentioned in: Give embodied agents a truthful third-person glance Intent. Add a Evidence. Done when. A wall, a diagonally joined ornament, and two genuinely separate
works produce stable, inspectable swaths without treating generated blocks as
edits or merging distant work merely because their bounding boxes overlap. Intent. Add a Evidence. The block world is volumetric, while a landscape survey is a
derived two-dimensional product: the highest occupied site per horizontal
column plus an availability mask. Naming that product prevents caves,
unloaded columns, and absent terrain from being silently flattened into a
height. The descending component pass is then dense work over the finite
window, not repeated sparse world lookup. Done when. Synthetic plateaus, twin peaks joined by a known saddle, a peak
touching the window boundary, and unavailable columns have exact results; a
live survey names a few durable landforms rather than dumping every local
maximum. The basic Intent. Let an embodied agent propose a voxel change set which remains inert
while the player sees the actual candidate materials in the world, flies
around them in focus chat, and approves, denies, or steers in ordinary words. Evidence. Done when. A proposal never edits terrain before approval; its candidate is
visually legible as the after-world; focus supplies decision affordances and a
cinematic view; approve revalidates and commits once; deny and steering release
the preview and wake the waiting tool call; and the behavior has focused tests
plus a live capture. Intent. The first slice: a Evidence. Mentioned in: A luv agent surface would inherit these seams The body (#5JDNKF) is deliberately skew to the block field. A The visible bodies are analytic SDFs launched from conservative scene
proxies: a red-hatted gnome and an orange tabby cat, each with its own live
metabar vocabulary. Their world-space fields follow continuous body
positions, so focus rays, lighting, and silhouettes move with the same body
that collides with terrain. Focusing one with TAB steps the camera back to a
conversational distance, aims it at the face with the focused field of view,
and turns the keys into a prompt line; RET asks. Speech bubbles remain
retained McCLIM world widgets above that analytic body. What the gnome shows is the transcript in three registers: The threads meet the same way as everywhere else on this page: the turn's
observer posts notes to the gnome's mailbox; the gnome's own
Mentioned in: Wire three commands into an agent and draw their cassettes Referenced from code: The gnome: an agent with a body (#5JDNKF), not a block (#V9DHV5). A gnome occupies a discrete world cell but is not a block. The cell is
semantic state -- where this agent stands -- while its animated,
camera-facing figure is an independently rendered scene object. Focusing
it is how you talk to it: the view leans in the way it does for a wall, the
keys become a prompt line at the bottom of the screen, and RET asks. The
gnome then thinks, and its thinking shows above its head as a bubble: a
matte rounded cassette standing in the world, drawn through McCLIM the way
the Telegram panel is (a world widget, in the scene, with relief). Each
tool call is the next cassette, and pushes the ones before it upward.
When the gnome talks -- the SAY command, which is a tool like any other --
it is not a cassette but a title at the bottom of the view, the way an NPC
line is subtitled. gnome Intent. An embodied agent acts in places rather than holding a WASD key:
A bounded four-connected search names standable cells, routes around terrain,
and permits one-cell steps up or down. The path is an inspectable part of the
Evidence. Done when. A cat asked to move reaches a nearby supported place by a visible
continuous route; its tool call waits without parking rendering; the same
primitive moves a player body; manual player input takes authority back; and
terrain routing and invalid destinations have tests. Mentioned in: The gnome is an agent occupying a cell, not a block Referenced from code: The cell is the discrete public place; One embodied agent at a discrete cell: its thread, figure, and voice. Intent. Finish what #0IF4TY promised: the command menu should offer commands
with arguments, prompting through Evidence. The tool side already reads the command parser table. The M-x gap
is Done when. M-x "Place Block At" prompts for a kind and three integers and
places the block. Mentioned in: Wire three commands into an agent and draw their cassettes, M-x and the metabar reveal one application vocabulary, Extract the application workbench beneath both games, Grow the workbench and the game as one command vocabularyThe agent surface is a command table, a view, and a handle space #4Y8Q8B
harness seam CLIM concept tool definition and JSON schema define-command with presentation-typed argumentsargument validation, "did you mean" accept-from-string, complete-input, presentation errorstyped result with several renderings a presentation, presented under different views short stable handles presentation history, a handle presentation type"what can I do with this?" find-applicable-translators on a presentationhistory as data, re-rendered later the output-record history of the agent's stream accept and execute-frame-command instead of around them.
#0IF4TY through #1W8K03 take the rows in turn; #9K823O and #14ZP6S take the
rendering from nxt; #UTQQV9 and #5JDNKF put the agent in the world.defpackage #:luvcraft.agent package.lisp:11 ↗
A tool is a CLIM command whose arguments are presentation types #0IF4TY
accept, and a
JSON-schema tool whose arguments are parsed with accept-from-string per
presentation type. Finishing parameterized M-x and building the agent
surface are one piece of work.command-tool is an openai:tool wrapping a command name; a
generic presentation-type-json-schema with EQL methods on type names
(integer, string, (member ...) as an enum, block-position as an
object of three integers, handle as a pattern) derives the schema once
from the command's parser table; and call-tool accepts each argument by
its declared type and hands the finished command to the canvas thread
(#T6PEVY). An argument the model gets wrong is a presentation-parse error
rendered back to it, not a crash, which is nxt's "failures as values"
(#KZ019W) without writing a second validator.A tool result is a presentation, and the model reads it under a view #WS5OEX
+model-view+ renders compact text for the provider: the output of
present-to-string on the record, folded by an :around method that
keeps a head and a tail and a handle (#1W8K03) when the body is long.
Folding lives here and nowhere else, so tool bodies never truncate.+trace-view+ is the three-line preview used when an old turn is replayed
inside a later prompt: for remembering, not for working, exactly as froth
distinguishes live from trace.present to a string stream, escaping and layout are
in one place; there is no string concatenation anywhere between a tool and
the model. And because the records are kept, the whole transcript can be
redrawn under any view later without rerunning a thing, which is the
"history is data first" point of #YY6EYZ with the output-record history as
the data.Handles are presentation history #1W8K03
handle is a presentation type whose accept method resolves the id back
to the live object. The model says #AB12 in a tool argument; accept
gives the tool the object; an unknown or stale handle is a parse error the
model can read. The pager (#FDMSXG) is then nothing special: accept a
handle, present a slice of it under the model view.The cassette is one presentation file #9K823O
3d692d0), a tool call was
drawn as a cassette (#7FFNVT): a trace as XML (turn > thought | call[name,
status, elapsed] > args, result[kind], affordances[key]), and one XSLT
stylesheet, cassette/baltic-birch-v1.xsl, holding the whole visual
language in about three hundred lines organized as✓ in the tool's colour, ! rose, ?
amber for pending approval, a spinner while running, N denied),⨉309 22K, 109L 3K, exit 0),[A] see all 309 [P] by path [C] context for
#1 -- "the same surface the model parses as its action menu."<xsl:template match"result[@kind='matches']">= is a declarative visitor
case, and define-presentation-method present (object (type matches) stream
(view cassette-view)) is the same case with a live object behind it. So
the luv cassette is one file: a cassette-view, presentation methods keyed
on result kind for the window and the chip, a spine keyed on call status,
the per-tool colour as a generic on the command name, and the affordances
strip as find-applicable-translators printed as a menu. That last point
is the nicest: the affordances are not a list a tool author writes, they
are whatever translators apply to the result's type.defparameter *agent-hud-width* hud.lisp:14 ↗
defclass agent-terminal-display wall.lisp:20 ↗
terminal-display with no PTY. What it shows is VT bytes the
agent writes into its Ghostty terminal: the prompt the player types at it,
the thought as it streams, and each tool call as an ANSI cassette (#9K823O)
once it has finished. The terminal does the scrolling and the wrapping;
we do the colours.Draw and print: live cards and committed records #14ZP6S
src/nxtrt/ui_runtime.hpp, src/nxtai/tool_ui.cpp)
drew a batch of parallel tool calls with two verbs: draw(layout) replaces
a live surface shown as a bottom HUD, and print(layout) commits a
rendered block to durable scrollback above it. Each tool call was a child
with its own slot; the batch drew a column of the children's slots; each
child redrew its card every frame with elapsed milliseconds while running,
flipped it to ok or error with a few sanitized lines of output and a memory
chip when done, held it for 900 ms so a person could see the flip, and
returned. When the batch finished the live column was cleared and the
assistant's text was printed as a block.call-view objects, each presented every frame under
#9K823O with its status. The terminal wall and the agent's output-record
history are the committed surface. Keep the settle delay: a card that
flips and vanishes in the same frame cannot be read. And keep the
no-terminal fallback -- when nothing is drawing, one line per call on a
plain stream -- so the agent is usable from ./sly alone.defparameter *agent-hud-width* hud.lisp:14 ↗
A shell scope is a session object with a screen and an observation #UTQQV9
demo/shell_scope_demo.cpp ran a shell as systemd-run --user
--scope so it had a cgroup by construction, then forked four siblings into
one scope: a PTY reader, a stdin pump, a 33 ms cgroup sampler keeping a
sparkline series, and a 16 ms renderer drawing memory and CPU sparklines
above the live PTY screen. The observation (unit, cgroup path, samples)
rode along inside the tool result, which is how a finished cassette could
show a memory chip (#KZ019W).shell-session owning a PTY (the world terminal already has one serialized
PTY owner, #NMAD2U), a sampler thread, and its observation, all presentable.
A shell a tool starts should become a terminal-material wall the agent
stands beside and the player can walk up to. The concurrency story is the
one mcclim/telegram.lisp already states
for the Telegram console: a mailbox one way, a published immutable snapshot
the other, and no lock anywhere. Swash's default applies too (#I0U0LI): a
shell tool returns within a few seconds with either the exit and a
screenful, or a handle to the still-running session and the command to
follow it.The agent has a body #5JDNKF
look, walk and
place are presentation-typed commands relative to it, and its transcript
is the terminal wall it is standing at. The playful surface follows from
that without new machinery:thought element of a trace, a reasoning summary from the
provider) is a thought bubble drawn above the avatar, a McCLIM frame in
world space like the instruments in #AX9ZTA;accept against the player, offered as a HUD gadget
like the tape prompt, and the turn resumes when it returns;./sly eval shape with a
three-second detach (#ENM1B2); a form that returns objects gets them
presented, so the agent receives handles and applicable commands for what
it just computed -- froth's discover, inspect, act loop by way of
translators and describe.defparameter *gnome-name* gnome.lisp:18 ↗
The turn runs beside the canvas, and commands cross by mailbox #T6PEVY
call-tool must therefore never call execute-frame-command from the
websocket thread. It accepts its arguments and posts the finished command
through request-canvas-frame -- froth's prepare/commit split (#IOGTGD),
with accept as the prepare. Most commands return their values in that
one frame. A command may instead return a running semantic object and let
settle-command-result wait for it on the provider's turn thread. Move
To does exactly that: the canvas returns a body-move-action immediately,
keeps advancing it at frame boundaries, and publishes its terminal state to
a private mailbox. The tool call is deliberately blocking in this first
version while the canvas never is. A later duration bound can return a
handle to the same action without changing either the command or movement
protocol.defclass tool-approval approval.lisp:7 ↗
settle-command-result (#T6PEVY).A proposal is a temporary possible world #Y7X7WK
tool-approval retains an
inert semantic payload, its :proposed state, and a private decision mailbox;
the provider's turn may wait in settle-command-result while the canvas keeps
rendering. validate-tool-approval, commit-tool-approval and
detach-tool-approval are the open CLOS protocol. Approval validates the
frozen expectations again and commits, denial drops the temporary presence,
and typed feedback finishes as :changes-requested so the model can propose a
different world. There is no retained arbitrary thunk to become dangerous or
opaque.block-change-set: dense integer
coordinate and value columns at the semantic boundary, plus a small lookup for
counterfactual neighbour queries. Its scene overlay uses the ordinary block
mesh vertex product, atlas, material shader, depth and lighting vocabulary.
Only the emission lane is raised, enough for the post stack to give the
candidate a soft possible-world glow. In focused conversation the same
approval supplies a slowly orbiting camera pose, and the dialogue line offers
Ctrl-Y approve, Ctrl-N deny, or prose plus Enter to steer. What the player
judges is therefore recognizably the finished result, not a wireframe bill of
materials.
Propose Block Box primitive is intentionally additive and capped
at 512 resident cells. It refuses to replace an occupied cell, freezes air as
the expected value, and rechecks every site immediately before one
with-world-change-transaction commit. Additions can be drawn exactly over
the current scene. Removal and replacement previews need the next rendering
step: candidate chunk products substituted for the live products in the
affected window, so the possible world can hide geometry as truthfully as it
adds it. That extension changes the preview realization, not the approval
protocol or change-set object.defun call-with-construction-proposal-capture construction.lisp:6 ↗
function with a hidden session, gnome, and inert wall proposal.A glance is an image and the small truth that frames it #IFEAQ6
View Surroundings is one observation at one canvas
boundary, not a screenshot followed by unrelated queries. Its image comes
from a camera above and behind the body, aimed forward over its shoulder; its
text names the body and camera poses, the player relative to the body, the
world revision, and one exact integer box whose block population was counted.
The box is deliberately called the sampled neighbourhood, not the viewport:
a perspective frustum sees sky and distant geometry and has no honest voxel
bounding box without reading depth back too.(-570 -2 -532)..(-546 14 -508): 9,375 resident, including 6,868
air, and 1,250 unavailable. The two SDF pipelines validated after their
instance records gained facing, and the provider boundary received the
observation as text plus image content.IDEA Authored edits form sparse swaths, not another terrain field #758F9X
Survey Edits observation over the little world's sparse
authored overlay. Nearby edited sites should be clustered into
semi-contiguous swaths and each swath presented by its integer bounding box,
edited-site count, box fill ratio, and final block-kind distribution (with
explicit air retained as a value). This is archaeology of authored intent,
not a census of the current materialized terrain.little-world-source-edits already owns one hash entry per final
authored site and survives chunk departure. A neighbourhood query can filter
those sparse keys before a bounded 26-neighbour or small-gap component walk;
it need not scan chunks or retain an object per ordinary cell.IDEA Terrain summits are born at peaks and merge at saddles #ANOLKV
Survey Terrain observation derived from a resident
height-field window. Besides elevation range, relief, and slope summaries,
report the strongest summits by a discrete merge tree: descend through height
levels, birth a component at each local maximum, and record the saddle where
it joins a component with a higher peak. Peak height minus that saddle is a
useful topological persistence -- the playful "geotopological peak" with a
real definition.DONE Give embodied agents a truthful third-person glance #AHAI6I
view-surroundings verb now realizes #IFEAQ6: it captures on the
canvas boundary under a temporary over-the-shoulder pose, restores the
player's camera, retains one semantic observation, and sends its clean PNG
beside the compact textual census. Stable body yaw is shared by movement,
where-am-i, capture, and the gnome and cat fields. Dense census tests keep
air separate from unavailable space; the shader validator covers both
oriented figures.DONE Give construction tools a visible possible world #YXUKSU
Propose Block Box installs a construction-approval and a
GPU-owned construction-preview-overlay. The focused tests freeze and stale
check four cells, verify that one proposed block is the ordinary 36-vertex
block mesh with the proposal emission lane, prove a finished approval does not
wait, and find the command in the gnome toolbox. On 2026-08-21 a live
six-block stone-brick possible world rendered beside the gnome while the
dialogue showed all three decisions and the focus camera orbited it; denial
removed the GPU buffer without editing any cell.DONE Wire three commands into an agent and draw their cassettes #YDAQNO
luvcraft.agent layer where a command-tool
wraps a CLIM command (#0IF4TY), presentation-type-json-schema derives its
schema, tool bodies' values are presented under +model-view+ (#WS5OEX),
and a cassette-view draws each call while it runs and after (#9K823O,
#14ZP6S).luvcraft/agent/ (2026-08-19). make-command-tool reads
climi::*command-parser-table*; accept-from-string parses every argument
and a bad one comes back as a parse error the model reads ("cheese is not a
block kind; try one of grass, dirt, ..."). Results are presented under the
model view and objects worth returning to carry #ABCD handles
(describe-handle reads them; folded long text names its remainder by
handle). Commands run on the canvas thread through request-canvas-frame;
eval runs on the turn's thread. The cassette is one file of presentation
methods keyed on status and command (hud.lisp), drawn both on a HUD pane
and -- the thing that turned out to matter -- as bubbles in the world
(#V9DHV5). A real turn against GPT-5.6 over the websocket placed blocks,
surveyed its neighbours in one parallel batch of block-at calls, and, asked
to remove a block without a removal tool, used eval with apropos to find
edit-block-at on its own. The provider client had to move from
response.append to response.create with previous_response_id along the
way. Parameterized M-x is still open (#OL2JZ1).The gnome is an agent occupying a cell, not a block #V9DHV5
gnome or
cat belongs to *agents*, not *critters*, and Spawn Gnome or Spawn
Cat chooses an empty supported cell ahead of the player without editing
terrain. Its integer (x y z) is the public, intentional place; continuous
position and velocity are the physical authority while it travels between
places (#YPGXKI). The old gnome-block-kind and its atlas tiles no longer
exist.gnome-bubble frames --
McCLIM world widgets of the same family as the Telegram panel (direct
replay into the scene pass, a relief-design slab), billboarded above the
hat every frame, content-fitted, and stacked: a new bubble appears low and
eases the older ones upward; past four they are taken down, and a finished
turn's bubbles linger a few seconds. A pane that paints nothing is what
makes them float: the pane and the frame's top-level sheet take
never-repaint-background-mixin, since McCLIM otherwise composes every
background over black.say is a tool like any other, and the only way the gnome is
heard: the dialogue HUD at the bottom of the view titles what it said,
the way an NPC line is subtitled, and shows the prompt being typed while
the gnome is focused. say reaches its gnome through *current-agent*,
bound around the command by call-tool.transcript-lines for a windowless
session, and on an agent terminal wall if one is open.refresh-luvcraft-overlay, on the canvas thread, turns them into bubbles
and lines. Repainting a mirror from any other thread races the frame and
faults; every repaint here happens at the frame boundary.defparameter *gnome-name* gnome.lisp:18 ↗
DONE Discrete destinations, continuously embodied #YPGXKI
Move To (x y z) is one discrete, nearby destination and one terminal
result. Intent does not flatten physics. The body has a continuous position
and velocity, accelerates through the same collision motor as the player,
falls under gravity, steps one-cell ledges, and only changes its reported cell
as its base crosses the lattice boundary. Those are two explicit authorities
rather than an interpolation pretending not to be state.body-move-action. Stalling replans twice before failing; impossible,
nonresident, or distant destinations fail as values. The player implements
the same body protocol and can use the primitive internally, while held
movement or jump input cancels it immediately rather than fighting an
autopilot.simulation.lisp factors step-walking-body out of the player;
locomotion.lisp owns path search, action state, completion mailbox, and the
fixed-step motor; embodied-agent supplies the same body methods and advances
from the overlay frame clock. com-move-to chooses the current command
subject (agent presence or player), returns on the canvas, and settles beside
it (#T6PEVY). On 2026-08-20 the live SDF cat travelled four terrain cells
while all 60 film frames rendered; the waiting worker began in :running and
returned :arrived after 2.458 simulated seconds at the requested cell.defclass embodied-agent gnome.lisp:86 ↗
position and VELOCITY are the
physical realization moving between such places. Keeping both makes
the intentional and simulation authorities explicit (#YPGXKI).NEXT Parameterized M-x prompts through presentation types #OL2JZ1
accept with the presentation types the
agent already uses, so place-block-at is one definition reachable by key,
M-x, and tool.command-menu-command-without-arguments-p in
luvcraft/clim/command-menu.lisp.
(name (&rest lambda-list) documentation &body body)A terminal wall showing one agent's transcript.
(command value)Finish VALUE's off-canvas work on the provider worker before presentation.
One inert proposed tool effect waiting for a player's decision.
(x y z)(&key
(title "luv little block world — click, look, walk")
;; NIL means "as much of this display as
;; comfortably fits"; a capture asks for the
;; exact frame it intends to write out. A
;; KMSDRM console can only present at a real
;; display mode, so the environment may pin
;; the canvas to the panel's native size.
(width (let ((value (uiop:getenv "LUVCRAFT_WIDTH")))
(and value (parse-integer value))))
(height (let ((value (uiop:getenv "LUVCRAFT_HEIGHT")))
(and value (parse-integer value))))
(frames-per-second 60)
(visible-p t)
(fullscreen-p nil)
(high-pixel-density-p t)
(world (make-empty-little-block-world))
(mesher (make-instance
'exposed-face-mesher))
(camera (make-instance 'fly-camera))
player
(selected-block *stone-block*)
(inventory (make-block-inventory))
quit-function
;; A hidden capture wanting animals in frame
;; hands in a population it has already
;; placed; the ordinary game grows its own
;; around the player as it plays.
(critters
(make-instance 'critter-population))
checkpoint-writer
(provider *gpu-provider*)
(sky-clock (make-instance 'sky-clock))
(sky-profile (make-default-sky-profile))
(shadow-diagnostic-p nil)
;; The world-text banner is a proof of the
;; Slug path, not scenery: a caller that
;; wants one asks for it, and the ordinary
;; game sky stays empty.
(world-text-string nil)
(world-text-font-pathname
(cl-dejavu:font-pathname "DejaVuSans.ttf"))
(world-text-distance 8.0)
(world-text-lift 3.0)
(world-text-units-per-em 0.55)
(video-pathname nil)
(video-distance 13.0)
(video-lift 4.5)
(video-height 5.0)
(residency-radius 6)
(publication-limit 2)
(load-schedule-limit 4)
(mesh-capture-limit 1))Open a little CPU-meshed block world. Click to capture the pointer, look with the mouse, walk with WASD, and jump with Space. Once captured, left click removes the block at the centre of view and right click places the selected block. Number keys select materials, middle click picks the targeted material, Shift…
(day-fraction)(session overlay &key (release-p t))Detach OVERLAY at a frame boundary and optionally release it there. The no-release path is intentionally preserved for FUSE-LUVCRAFT-OVERLAY: that path may run inside a frame whose command stream still borrows OVERLAY.
A small SDF-rendered garden gnome carrying a WORLD-AGENT.
(&key (session luvcraft:*session*) x y z)(world kind x1 y1 z1 x2 y2 z2)(name &optional (error-p t))Return the shared block kind whose durable semantic name is NAME. When ERROR-P is true, signal an error rather than returning NIL for an unknown name. Save files and other external descriptions refer to block kinds through this vocabulary instead of printing CLOS object identities.
Approval for a frozen additive block change set.
(approval)(session focus)Enter modal interaction with FOCUS, suspending ordinary player input. This is the common session transition behind using a world terminal or book, mounting a vehicle, and other interactions described by #8JCMA5.
(session)Stop SESSION exactly once and publish its result to every caller. The sole owner attempts every named release step and closes the canvas and device last. Concurrent and later callers observe the same values or RELEASE-ERROR without releasing a native handle twice.
One embodied agent at a discrete cell: its thread, figure, and voice.
Read together, the four repositories converge on a few points that any future luv agent surface should probably accept as given: – Results are typed values with more than one rendering. Froth's block trees (#5GU1VI), sheaf's result structs (#GR1GL1), nxt's tool_result with telemetry (#KZ019W), swash's journal entries…
command-menu.lisp already answers "what can I do?" for the player by walking the command tables, and it skips parameterized commands because M-x cannot yet prompt for their arguments. The agent's tool list is the same walk with the same gap. One should yield a keystroke, an M-x entry that prompts through accept, and…
Sheaf's six characters (#9P5YLW) and this wiki's figure IDs are the same idea: a short handle the model can cite by typing. In the agent stream, every presented object of interest -- a chunk, a block position, a session, a folded body -- is entered in a presentation history under a short id, and handle is a…
nxtui's demo/shell_scope_demo.cpp ran a shell as systemd-run --user --scope so it had a cgroup by construction, then forked four siblings into one scope: a PTY reader, a stdin pump, a 33 ms cgroup sampler keeping a sparkline series, and a 16 ms renderer drawing memory and CPU sparklines above the live PTY screen. The…
Tools are structs checked by a function_tool concept: name, description, strictness, a parameters struct, a raw JSON schema literal, a parser, and run returning a task -- a missing schema is a static_assert. Any exception becomes tool_result{failed, "tool execution failed: ..."}, an agent-visible message rather than…
The strongest idea in froth is Froth.Context.Block (lib/froth/context/block.ex): a keyword list of attributes (headers, like MIME parts, order preserved so :kind stays first), an optional binary body, and child blocks. A tool returns blocks and nothing else -- it "does not measure bodies, does not decide whether…
lib/sheaf/id.ex mints six-character handles from a 32-symbol alphabet with ambiguous glyphs removed, and maps handle to IRI by concatenation onto a resource base: #HCFU75 is literally the tail of a URL. Every paragraph, section, document, note, image, conversation and spreadsheet query result is minted the same way…
Froth.Blob (lib/froth/blob.ex) is a content-addressed store kept as bytea in Postgres so blobs stay transactional with everything else, deduplicated on SHA-256, and shown to the model as blob:01K... ULIDs that are globally addressable. The pager tool (lib/froth/tools/pager.ex) offers read | head | tail | grep | stat…
Runs are written as Arrow IPC files (seq, elapsed_ms, unix_ms, phase, event_type, data, payload_json, run_id), compacted to zstd Parquet (about 31 runs, 170 MB to 3 MB), and queried in place with DuckDB: cassette/trace-to-xml.sql derives turns with a window function over response.created, joins call and result rows…
The luv terminal should be an object in the little world, not a conventional terminal window captured into a texture. Its rectangular cell grid has a position, orientation, depth relationship, and input surface in the scene. Ghostty supplies terminal semantics; luv's Slug path supplies glyph outlines; the ordinary…
swash run (cmd/swash/main.go) follows the new session and returns either when it exits or when a detach threshold trips: --detach-after defaults to three seconds and --detach-after-output to 80×24 = 1,920 bytes. On detach it prints to stderr "still running after 3s, detaching" or "output exceeded 1920 bytes,…
The first embedded frames were made against the session's own canvas, context, and device, and each mirror owned a texture. Everything the frame drew -- draw-text*, draw-analytic-rounded-rectangle*, a decoded photograph as a pattern -- is rasterized into that texture at the size the frame was laid out at. The…
Froth.Agent.Worker is a state machine over :initial | {:thinking, task} | {:working, batch} | {:awaiting_user_input, batch} | :done, running all tool uses of a turn as unlinked supervised tasks with per-tool timers. RFC 0003 admits that this parallelism was once fake -- every task funnelled through one bot GenServer…
lib/froth/tools/elixir_eval.ex describes itself as "a capability browser and an Elixir evaluator" with a DISCOVER, INSPECT, ACT loop: docs with no target returns the module hierarchy (fetching and caching stdlib source for the running version when asked), eval runs against the live BEAM. Bindings persist per session…
A local domain can say that a step crossed its boundary. A chunk window answers what spatial materialization, if any, can continue the query: The window reports availability; the subsystem interprets it. Lighting may distinguish open sky, closed world, and unknown terrain. Meshing may treat an absent sample as air,…
Froth's lesson is that what the tool found, how much the model sees, and how it is spelled for a provider are three policies (#5GU1VI). In CLIM a tool body does not return a string; it =present=s objects to a recording stream. The three policies become three views over the same records: – +model-view+ renders…
An embodied agent's View Surroundings is one observation at one canvas boundary, not a screenshot followed by unrelated queries. Its image comes from a camera above and behind the body, aimed forward over its shoulder; its text names the body and camera poses, the player relative to the body, the world revision, and…
Intent. Finish what #0IF4TY promised: the command menu should offer commands with arguments, prompting through accept with the presentation types the agent already uses, so place-block-at is one definition reachable by key, M-x, and tool. Evidence. The tool side already reads the command parser table. The M-x gap is…
The four harnesses converged on seams (#YY6EYZ), and CLIM already has a name for each of them. That is the whole design bet of this page: Nothing in the table is a new mechanism. The work is to choose the presentation types, write the views, and route a provider's tool calls through accept and execute-frame-command…
The terminal wall does not rasterize anything into a texture. It places one instanced quad per glyph directly in world space -- see make-terminal-display-glyph-instances and the world text run it feeds -- and Slug evaluates the outline analytically in the fragment shader, at whatever resolution that pixel happens to…
Before nxt deleted its UI runtime (nxtui commit 3d692d0), a tool call was drawn as a cassette (#7FFNVT): a trace as XML (turn > thought | call[name, status, elapsed] > args, result[kind], affordances[key]), and one XSLT stylesheet, cassette/baltic-birch-v1.xsl, holding the whole visual language in about three hundred…
The same nxtui tree (src/nxtrt/ui_runtime.hpp, src/nxtai/tool_ui.cpp) drew a batch of parallel tool calls with two verbs: draw(layout) replaces a live surface shown as a bottom HUD, and print(layout) commits a rendered block to durable scrollback above it. Each tool call was a child with its own slot; the batch drew…
Commands run on the canvas thread; a provider turn runs on its own. call-tool must therefore never call execute-frame-command from the websocket thread. It accepts its arguments and posts the finished command through request-canvas-frame -- froth's prepare/commit split (#IOGTGD), with accept as the prepare. Most…
A tool approval should show its consequence, not merely ask whether a verb may run. Construction makes that unusually literal. tool-approval retains an inert semantic payload, its :proposed state, and a private decision mailbox; the provider's turn may wait in settle-command-result while the canvas keeps rendering. …
An agent that is only a websocket is a chat window with extra steps. The point of doing this inside luvcraft is that the agent can be somewhere: a presence object with a position and a facing, so look, walk and place are presentation-typed commands relative to it, and its transcript is the terminal wall it is…
The body (#5JDNKF) is deliberately skew to the block field. A gnome or cat belongs to *agents*, not *critters*, and Spawn Gnome or Spawn Cat chooses an empty supported cell ahead of the player without editing terrain. Its integer (x y z) is the public, intentional place; continuous position and velocity are the…
Intent. An embodied agent acts in places rather than holding a WASD key: Move To (x y z) is one discrete, nearby destination and one terminal result. Intent does not flatten physics. The body has a continuous position and velocity, accelerates through the same collision motor as the player, falls under gravity,…
An agent in the little world.
This package is where a language model meets luvcraft, and it meets it through CLIM: a tool is a command whose arguments are presentation types, a tool result is an object presented under a view the model reads, and the running transcript is a list of typed records that the HUD, a wall, and a plain ./sly session all draw from. The design is the wiki page "An agent in the little world" (#4Y8Q8B); the provider behind it is the OpenAI websocket client, but nothing here is about OpenAI.