Navigation
Copy page

Recoding Live

On this page

You can change kli's behavior in the session you are already in, without restarting it. The recode operations below are ordinary functions: one swaps a whole extension, two hot-patch a single behavior in place. Each takes the running context and the object it acts on, and each is gated by a capability, so a restricted session can deny it.

This page assumes you have written or loaded the extension you want to change. For the extension shape itself, see Write your first Lisp extension. For why a running kli can do this at all, see The live image.

Where the recode call runs

A recode needs the live context and protocol. Both arrive as arguments wherever your extension's code already runs: a command handler is called as (command arguments context &key call-id on-update), and an effect installer as (protocol contribution context). Call the recode functions from inside one of those, where the objects are already in hand. The examples below show a command handler that does the recode when the user types its command.

To reach the active protocol from a context, use (kli:active-protocol context); to find a live object by its id, use (kli:find-live-object (kli:context-registry context) id).

Swap a whole extension

Use recode-extension to replace one extension with a new version while everything else keeps running. It deactivates the old extension, then activates the new source. If activation fails, it re-activates the original, so the session is left on the version that worked rather than in a half-installed state.

lisp
(command "swap-greeter"
  :description "Replace the greeter extension with its next version."
  :handler (lambda (command arguments context &key call-id on-update)
             (declare (ignore command arguments call-id on-update))
             (let* ((protocol (kli:active-protocol context))
                    (extension (kli:find-live-object
                                (kli:context-registry context)
                                :greeter)))
               (kli/ext:recode-extension protocol extension
                                         #'greeter-v2 context)
               (reply "Greeter swapped."))))

The third argument is the new source: the same kind of value you would activate an extension from, such as a manifest thunk or a defextension name. Deactivation retracts the old extension's tools, commands, providers, and methods together; activation installs the new one's. A consumer of the extension sees the swap as a single step.

recode-extension requires the image/recode capability. Granting image/recode also grants manifest/install and manifest/retract, because a recode is a retract followed by an install. A session whose capabilities array omits image/recode cannot swap extensions; see Restrict what kli can do.

Hot-patch a behavior cell

When you want to change one function rather than a whole extension, patch a behavior cell. A behavior cell holds a single function behind a fault barrier, and recode-behavior swaps that function in place. The cell keeps its identity, its version counter increments, and callers go on calling the same cell.

lisp
(let ((cell (kli:find-live-object
             (kli:context-registry context)
             :my-behavior)))
  (kli/tui/core:recode-behavior cell :function #'my-new-function))

recode-behavior takes the cell and keyword arguments:

  • :function — the new function to run.
  • :version — set the version explicitly; omit it to increment by one.
  • :state — replace the cell's state.
  • :metadata — replace the cell's metadata.
  • :capabilities — replace the capability list the cell declares.

recode-behavior requires the behavior/hotpatch capability. Passing :state additionally requires behavior/state, because changing live state is a stronger act than swapping the function; a session can be allowed to patch functions while still being denied state edits. The cell's fault policy and fault fallback are set when the cell is built and a recode cannot touch them, so a patched function that throws is still contained by the barrier it was installed behind.

Patch a terminal-UI behavior

Terminal-UI components expose their behaviors through recode-tui-behavior, a generic that dispatches on what you hand it. Given a behavior cell, it delegates to recode-behavior with the same keyword arguments. Given a concrete UI object — an editor, the transcript, the input decoder, a frame renderer — the owning extension specializes it to find the right cell and patch that.

lisp
(kli/tui/core:recode-tui-behavior
 (kli:find-live-object (kli:context-registry context) :editor)
 :function #'my-editor-input-handler)

The gating is the same: behavior/hotpatch, plus behavior/state when you pass :state. The UI behaviors that ship — editor input and paste, transcript scrollback, input decoding, frame rendering — each declare behavior/hotpatch and behavior/state, so a restricted session can permit or deny patching them as a group.

Recode a policy in place

Some behavior is not a whole extension or a single behavior cell but a field of a policy on a live service. The session's context transform, which decides what extra messages are spliced into each turn, is one such policy. kli/agent/session:recode-context-transform-policy rebuilds that policy with one field replaced and leaves the rest intact. The new function runs behind the session fault barrier, so a transform that throws yields no extra messages rather than breaking the turn.

cairn uses this in tree to splice live task context into every turn. Its cairn-context effect reads the service's current extra-messages-fn, saves it, and recodes the policy to a function that appends cairn's task messages onto whatever the previous one returned:

lisp
(let ((service (kli:find-live-object (kli:context-registry context)
                                     :agent-session-service)))
  (when service
    (let ((previous (getf (funcall (kli/agent/session:session-context-transform-policy
                                    service)
                                   :inspect)
                          :extra-messages-fn)))
      (kli/agent/session:recode-context-transform-policy
       service
       :extra-messages-fn
       (lambda ()
         (append (and previous (funcall previous))
                 (cairn-extra-messages context))))
      (list :service service :previous-fn previous))))

The effect's retractor reverses it exactly, recoding the same field back to the saved previous-fn. The session never restarts and never loses its scrollback: one field of a live policy is swapped, and swapped back on retract. This is the second pillar in production, rewrite without restarting, keep the state. Unlike the kernel recodes above, it is a plain function on the agent-session service rather than a capability-gated kernel op; an extension reaches it through the live object and keeps the saved state on its own contribution.

Snapshot the active protocol

A snapshot captures the active protocol as durable data: the list of installed extensions in activation order, the protocol's storage, and the serializable slot state of every contributed live object. Take one before a recode you are unsure about, or to move a session's state to another image.

lisp
(let ((snapshot (kli/ext:provider-call
                 (kli/ext:require-capability-provider
                  (kli:active-protocol context)
                  :runtime/snapshot)
                 :snapshot-context context)))
  snapshot)

A snapshot is honest about what it cannot carry. A value it cannot serialize is named, not encoded lossily: an extension with no reconstructable manifest is listed under :unrestorable-extensions, storage entries it skipped under :skipped-storage, and per-object slots it dropped under :skipped-slots. What it skips is code-derived structure that reinstalling the manifests rebuilds, so the snapshot records the names rather than the bytes.

To restore, call :restore-active-protocol with a snapshot. A still-registered protocol is rehydrated in place; a protocol that was discarded, or one absent because the image restarted, is rebuilt from scratch by installing the captured manifests in order and rehydrating the captured storage and slot state.

lisp
(kli/ext:provider-call
 (kli/ext:require-capability-provider
  (kli:active-protocol context)
  :runtime/snapshot)
 :restore-active-protocol context snapshot)

snapshot-context requires protocol/snapshot and restore-active-protocol requires protocol/restore. A session can be allowed to capture state without being allowed to overwrite it.

The capabilities each step needs

A session that omits one of these from its capabilities array is denied that step; a session with the key absent runs fully permissioned and can do all of them. For the full model and how to set the array, see Permissions and capabilities.

Operation Capability Notes
Evaluate a form ad hoc image/eval Gates the /eval command and eval tool, the way to run a recode form without writing an extension.
Swap an extension image/recode Implies manifest/install and manifest/retract.
Patch a behavior function behavior/hotpatch Covers recode-behavior and recode-tui-behavior.
Change a behavior's state behavior/state Required in addition when you pass :state.
Capture a snapshot protocol/snapshot
Restore a snapshot protocol/restore

Next