Navigation
Copy page

Run Commands and Eval Lisp

On this page

kli has two tools for executing code in a session: bash runs a shell command in a child process, and eval evaluates Common Lisp forms inside the running kli image. The agent calls them while it works, and you can call either one directly with a slash command.

Both tools are gated behind capabilities. They run only when the session grants them: bash needs process/exec and eval needs image/eval. With the default settings (no capabilities key) every tool is allowed. See Restrict Tools With Capabilities to deny one.

Run a shell command

Type /bash followed by the command:

/bash ls -la src

Everything after /bash is the command line, passed to sh -c. So pipes, redirects, globs, and && work as written:

/bash grep -rn TODO src | head -20

The tool returns stdout on success. When the command exits non-zero, the result is marked an error and includes stderr. An empty result means the command produced no output and exited zero.

Set the working directory or pass stdin

The agent can run a command in a specific directory or feed it input. These are tool parameters, not shell syntax, so the agent supplies them on the tool call rather than you typing them after /bash. The bash tool accepts:

Parameter Required Effect
command yes The command line to run.
directory no Working directory for the child process.
input no Text written to the command's stdin.
shell no Shell to invoke. Defaults to sh.

From the /bash slash command, only the command line is available; the rest take their defaults. To change the working directory yourself, cd inside the command itself.

Evaluate Common Lisp

Type /eval followed by one or more forms:

/eval (+ 1 2 3)

The forms are read and evaluated in order. The result is whatever the forms printed, followed by the value of the last form. Several forms in one call run left to right:

/eval (defparameter *x* 10) (* *x* *x*)

Forms read and evaluate in the CL-USER package by default. The agent can target another package through the tool's package parameter; unqualified symbols then intern there. Values print under bounded printer control: *print-length* is 100, *print-level* is 20, and *print-circle* is on, so a long or circular value prints a bounded representation instead of running away.

The forms run in the same image kli runs in, so they reach live state and can inspect or change kli mid-session. For what that image is and why it matters, see The Live Image.

Stay inside the timeouts

Both tools time out at 30 seconds by default. The hard maximum is 300 seconds; a longer request is clamped to it. There is no way to extend a single call past 300 seconds.

When a bash command times out, kli kills its whole process group (SIGTERM, then SIGKILL) and returns the output captured so far, marked as a timeout error. When an eval form times out, kli interrupts the evaluating thread. An interrupted form can leave image state partially modified, since it stops wherever it was, so the result says so.

To run something longer than 300 seconds, start it in the background from a bash command and poll its progress with later commands, rather than waiting inside one call.

Know the output cap

Each tool caps its captured output at 1 MiB (1,048,576 characters):

  • bash caps stdout and stderr at 1 MiB each. Past the cap the stream is truncated and the result notes [stdout truncated at ... characters].
  • eval caps total output at 1 MiB across everything the forms print. The cap applies as output is written, so even a non-terminating printing loop stops adding to the result at the cap.

When you expect a large result, narrow it before it reaches the tool: pipe a bash command through head, tail, or grep, and have eval return a count or a slice rather than a whole collection.

Commands bash refuses

The bash tool runs to completion and returns captured output; it has no terminal to drive. So it refuses commands whose first word is an interactive program and returns an error instead of hanging:

vi  vim  nvim  nano  emacs
less  more  man
top  htop  watch
ssh  mosh
tmux  screen

The check looks past leading variable assignments and wrappers (env, command, exec, time, sudo, doas) to find the real command, so sudo vim file is refused too. Reach for non-interactive equivalents: read a file with cat instead of less, search with grep instead of opening an editor, run a remote command through whatever your environment exposes rather than an interactive ssh session.