A radically extensible coding agent for the modern Lisp hacker.

$ curl -fsSL kli.kleisli.io | sh

kli runs inside its own live SBCL image. The boot kernel knows only how to install, switch, and roll back protocols. The extension system is itself a protocol installed on that kernel, and every contribution carries a retractor, so retraction drains exactly what it added. Extensions all the way down.

Specialize the running program.

A :method contribution adds a method to any generic in the live image, and retraction is remove-method. You change the program's own behavior and roll it back.

Rewrite without restarting, keep the state.

Behavior lives in CLOS cells over pandoric closures. A hot patch swaps the code and keeps the closed-over state, so the editor keeps its buffer and the transcript its scrollback. It runs behind a condition-system fault barrier, so a hot-patched function that throws degrades instead of killing the session.

Switch the whole world, safely.

A protocol switch validates, smoke-tests, swaps, and rolls back on any error.

If that makes you want to open src/kernel.lisp, kli is for you.

kli
render my edits as diffs
Edits print as a dim one-line note. Find the method that renders them.
search pattern=render-edit path=src/extensions/tui/transcript/render.l
isp
search render-edit (1 match)
src/extensions/tui/transcript/render.lisp
*446:a2 (defun render-edit (event theme width)
447:1b (declare (ignore width))
448:9d (list (style theme "dim" (format nil "edit ~A" (event-text event)))))
read render.lisp (586 lines)
edit render.lisp (+6 -2)
1 1 (defun render-edit (event theme width)
2 - (declare (ignore width))
3 - (list (style theme "dim" (format nil "edit ~A" (event-text e
vent)))))
2 + (destructuring-bind (path before after) (event-details event
)
3 + (multiple-value-bind (added removed) (diff-counts before a
fter)
4 + (render-file-update "edit"
5 + (list (list :path path :old before :new after
6 + :added added :removed removed))
7 + theme width))))
eval (compile 'render-edit)
eval render-edit
Done. The edit you just saw is now a diff — and every edit after it.
now number them
Numbering means a gutter in the scrollback render loop.
search pattern=render-scrollback path=src/extensions/tui/transcript/sc
rollback.lisp
search render-scrollback (1 match)
src/extensions/tui/transcript/scrollback.lisp
*87:4d (defun render-scrollback (sb proto width)
88:3e (loop for ev in (scrollback-events sb)
89:c1 append (render-event proto ev width)))
read scrollback.lisp (142 lines)
edit scrollback.lisp (+2 -1)
1 1 (defun render-scrollback (sb proto width)
2 2 (loop for ev in (scrollback-events sb)
3 - append (render-event proto ev width)))
3 + for n from 1
4 + append (gutter n (render-event proto ev width))))
eval (compile 'render-scrollback)
eval render-scrollback
Every line is numbered now, and the scrollback survived the swap.
undo that
Revert the gutter I just added; the diff rendering stays.
search pattern=gutter path=src/extensions/tui/transcript/scrollback.li
sp
search gutter (1 match)
src/extensions/tui/transcript/scrollback.lisp
89:2d for n from 1
*90:b9 append (gutter n (render-event proto ev width))))
read scrollback.lisp (143 lines)
edit scrollback.lisp (+1 -2)
1 1 (defun render-scrollback (sb proto width)
2 2 (loop for ev in (scrollback-events sb)
3 - for n from 1
4 - append (gutter n (render-event proto ev width))))
3 + append (render-event proto ev width)))
eval (compile 'render-scrollback)
eval render-scrollback
Reverted. The numbering is gone; the diffs remain.
render the install
The install lives in this site's own advance control. Edit that.
search pattern=advance-control path=src/components.lisp
search advance-control (1 match)
src/components.lisp
*105:7c (defun advance-control ()
108:2e (:div :class "advance-row"
*109:b3 (:button :class "advance" :data-advance "1"
110:9f "render my edits as diffs")))
read components.lisp (165 lines)
edit components.lisp (+4 -2)
1 1 (defun advance-control ()
2 2 (lol-web:htm-str
3 3 (:div :class "advance-row"
4 - (:button :class "advance" :data-advance "1"
5 - "render my edits as diffs"))))
4 + (:span :class "cmd-line"
5 + (:span :class "sigil" "$ ")
6 + (:span :class "cmd copy"
7 + "curl -fsSL kli.kleisli.io | sh")))))
eval (compile 'advance-control)
eval advance-control
There's the install — the button is now the command. Run it.