Autolith is a terminal agent running in a live Common Lisp image. It works on Linux x86-64 and macOS.
You can launch autolith by typing its name into your favorite terminal:
autolithIf you haven’t authenticated already, you can check out the README for how to do that.
Autolith’s prompt window can also evuluate lisp forms directly:
(help)
(resource.read :uri "workspace:.")
(prompt (read-file "review-notes.org"))This can be frequently helpful since you can use the exact same tools the clanker can, and the same machinery is exposed to you.
Nix is the simplest way to install Autolith:
nix run github:luciusmagn/autolithThe first launch builds Lisp images Autolith needs to launch.
These images live under ${XDG_DATA_HOME:-~/.local/share}/autolith/nix/images/}.
It is possible to install autolith on Linux x86_64 via a shell installer:
curl -fsSL https://sh.lambda-symbolics.com/autolith | shRead the installer before piping it to a shell, or use Nix if that tradeoff is unwelcome. Linux binary releases require OpenSSL and Bubblewrap. Bubblewrap provides the filesystem and network isolation used for sandboxed command execution. The installer names the missing dependency instead of installing a release that cannot offer sandboxed or safe automatic execution. The launcher uses the installation layout, rather than an environment marker alone, to distinguish binary, Nix, and source starts.
Autolith automatically checks for updates at most once every 20 hours, but if you like, you can update it from cli or from inside a session:
CLI:
autolith --updateSession:
(update)Release archives are built and checked by the installer host. GitHub publishes release metadata after the host checksum is available. See Release service.
For a fresh macOS machine:
brew install sbcl rust cmake pkg-config openssl@3
curl -o /tmp/quicklisp.lisp https://beta.quicklisp.org/quicklisp.lisp
sbcl --no-sysinit --no-userinit --load /tmp/quicklisp.lisp \
--eval '(quicklisp-quickstart:install)' --quitOn Linux, also install Bubblewrap with your distribution’s package manager.
Autolith uses its bwrap executable to isolate the host filesystem and network
for sandboxed commands and the sandboxed decisions made by automatic permission
mode. Nix installations include it. Bubblewrap is not a macOS or BSD dependency.
Without platform sandbox support, the sandbox choice is unavailable; explicit
once, always, and full approvals run with the user’s full privileges.
With Quicklisp in ~/quicklisp, bootstrap and check the checkout:
./script/bootstrap
./script/checkTo put a checkout into PATH:
mkdir -p ~/.local/bin
ln -sfn "$PWD/bin/autolith" ~/.local/bin/autolithThis is assuming you have .local/bin/ in PATH.
Ordinary text is a prompt to the primary agent. It is equivalent to:
(prompt :to 'autolith "Review this change")autolith is reserved, case-insensitively, for the primary agent.
As a shortcut, if you want to submit something to the main agent, you don’t need to include the :to parameter:
(prompt "Shut yourself down homie")prompt can also compute text or attach local images:
(prompt (read-file "request.org"))
(prompt :images "/tmp/diagram.png" "Review this diagram")
(prompt :to 'test-review "Run focused tests and report failures.")If you message a sub-agent, it can respond to you in the main conversation thread if it wants to.
Start a message with ( to evaluate it as Common Lisp in the active image:
(help)
(resource.read :uri "workspace:.")
(eval-now (setf *print-pretty* nil))Parenthetical commands are canonical. Slash-command syntax is syntactic sugar
for the same Lisp calls: arguments are whitespace-delimited strings, with
double quotes around strings containing whitespace. For example,
/cwd "/tmp/work tree" is equivalent to (cwd "/tmp/work tree"). Commands
with finite static options offer those values as interactive completions.
Use // to send a message beginning with a literal slash.
The clanker sees both your source input and its evaluated result apart from any other effects it may have.
When local Lisp or an interactive command signals a serious condition, the
restart debugger keeps the signaling stack and its live restarts available. Use
Ask Autolith why this failed to run an independent diagnosis while the failed
operation remains suspended. Diagnosis can offer up to three explicitly selected
recoveries that invoke a live restart, run repair source first, retry the whole
operation, return replacement values when supported, or abort the operation.
Retry does not roll back effects completed before the failure.
Escape during diagnosis cancels only the diagnosis and returns to the live restarts. Escape at the ordinary restart picker aborts the failed operation. During an active provider turn, Ctrl-C keeps the existing cancellation and second-interrupt force-exit behavior.
resource.read observes durable state and returns an opaque revision.
resource.edit applies a resource-specific operation only against that exact
observation.
Papercuts use current-workspace resource URIs:
papercut:currentlists active reports and acceptspapercut-report.papercut:id/<percent-encoded-stable-id>reads one active report and acceptspapercut-closewith a complete resolution.
The existing papercut.report tool and /papercuts, /papercut, and
/papercut-close commands remain convenient direct interfaces.
A session gets an immediate local title from its first prompt or image. After the third actual user turn, Autolith may ask the active model for one concise replacement and persist it with the conversation. The local title does not depend on provider generation.
Provider-generated refreshes are enabled by default. Use /titles on|off or
(titles "on") / (titles "off") to inspect or change the durable global
preference.
/resume displays titles in its picker while explicit /resume ID and
(resume "ID") continue to address the stable conversation identifier. The
terminal status bar and local session status display the title when one is
available and otherwise show the conversation ID.
Inspect a saved conversation without resuming or locking it with:
autolith replay ID
autolith replay ID turn 12
autolith replay ID date 2026-08-28
autolith replay ID time 2026-08-28T10:30:00
autolith replay ID sequence 1200On an interactive terminal, replay behaves like a small debugger over durable
conversation events. next and previous step records; turn, date, time,
and sequence jump to a location; raw shows the selected replay form; and
quit exits. Replay reads complete persisted forms without acquiring the
conversation lease or repairing, appending, or resuming the conversation.
Every running Autolith session publishes a private authenticated local endpoint. Inspect them with:
autolith localgroup status
autolith localgroup status --sexpidle means that the process is at input with no active work of any kind.
Control a sessions with the following commands:
autolith localgroup tell SESSION-ID "message"
autolith localgroup pause SESSION-ID
autolith localgroup attach SESSION-ID
autolith localgroup attach SESSION-ID --read-only
autolith localgroup attach SESSION-ID --take-over
autolith localgroup detach SESSION-ID
autolith localgroup kill SESSION-IDtell uses input to tell something to a session and resumes a paused session.
pause cancels active work and holds queued primary work.
kill requests graceful shutdown.
Autolith stores configuration, durable data, process state, and replaceable
caches below ${XDG_CONFIG_HOME:-~/.config}/autolith/,
${XDG_DATA_HOME:-~/.local/share}/autolith/,
${XDG_STATE_HOME:-~/.local/state}/autolith/, and
${XDG_CACHE_HOME:-~/.cache}/autolith/. XDG base-directory values must be
nonempty absolute paths; invalid values use these defaults. Newly created
Autolith roots have mode 0700.
Conversation leases, Localgroup endpoint records, and recovery state live under
${XDG_STATE_HOME:-~/.local/state}/autolith/ rather than XDG_RUNTIME_DIR.
They must survive logout so detached sessions, handoffs, and crash recovery can
continue across login sessions. Autolith uses authenticated loopback TCP rather
than filesystem sockets for its inference and Localgroup protocols, and cleans
stale Localgroup records itself.
Autolith loads optional
${XDG_CONFIG_HOME:-~/.config}/autolith/init.lisp after tracked code and a
selected private image commit, before provider requests. It reloads on
retained generation reconnect.
The file is ordinary Common Lisp in package AUTOLITH, with your full
privileges. It is not copied into private replay or the pristine recovery
image.
The management REPL is a separate opt-in subsystem for trusted programs that
must evaluate Common Lisp in the running AUTOLITH image. It does not use the
localgroup protocol, Swank, or Slynk. It is disabled unless
AUTOLITH_MANAGEMENT_REPL is set to on.
Every connection authenticates with a fresh random nonce and HMAC-SHA-256. The
client never sends the reusable token. Put a nonempty token in a regular file
owned by the current uid with mode 0600. Autolith opens that file without
following symbolic links only while checking a proof, wipes the token octets,
and retains only the token-file pathname.
The following environment variables configure newly created applications:
| Variable | Default | Meaning |
|---|---|---|
AUTOLITH_MANAGEMENT_REPL | off | Enable the endpoint. |
AUTOLITH_MANAGEMENT_REPL_TRANSPORT | unix | unix or tcp. |
AUTOLITH_MANAGEMENT_REPL_UNIX_SOCKET | ${XDG_STATE_HOME:-~/.local/state}/autolith/management/repl.sock | Unix socket pathname. |
AUTOLITH_MANAGEMENT_REPL_TCP_ADDRESS | 127.0.0.1 | IPv4 loopback listener address. |
AUTOLITH_MANAGEMENT_REPL_TCP_PORT | 4141 | TCP listener port. |
AUTOLITH_MANAGEMENT_REPL_TOKEN_FILE | ${XDG_CONFIG_HOME:-~/.config}/autolith/management-repl.token | Mode-0600 token file. |
AUTOLITH_MANAGEMENT_REPL_TIMEOUT | 10 | Evaluation deadline in seconds. |
AUTOLITH_MANAGEMENT_REPL_MAX_FRAME | 1048576 | Maximum wire-frame octets. |
AUTOLITH_MANAGEMENT_REPL_MAX_SOURCE | 262144 | Maximum UTF-8 source octets. |
AUTOLITH_MANAGEMENT_REPL_MAX_OUTPUT | 262144 | Maximum captured output and value text. |
AUTOLITH_MANAGEMENT_REPL_QUEUE_CAPACITY | 8 | Maximum queued evaluations. |
AUTOLITH_MANAGEMENT_REPL_MAX_CLIENTS | 8 | Maximum accepted clients, including authentication. |
AUTOLITH_MANAGEMENT_REPL_AUTH_TIMEOUT | 10 | Absolute authentication deadline in seconds. |
TCP accepts IPv4 loopback addresses only. This endpoint does not provide server TLS, and arbitrary Lisp results may contain secrets. Challenge-response prevents bearer-token replay, but an unauthenticated local process can relay a challenge and proof between trusted local clients. Treat loopback TCP clients as locally trusted. Unix transport creates a mode-0700 private directory and a mode-0600 socket. Startup refuses an active socket and any alien or non-socket path; it removes a socket only after proving that it is a stale current-user endpoint.
Each message is a four-octet unsigned big-endian length followed by exactly that
many UTF-8 octets containing one readable S-expression. Length zero, oversized
or truncated frames, invalid UTF-8, malformed syntax, and trailing forms are
rejected. Readers use standard I/O syntax, a fresh standard readtable, and
*read-eval* nil.
The server starts each connection with:
(:challenge :version 1 :algorithm :hmac-sha-256 :nonce HEX)HEX encodes 32 random nonce octets as 64 lowercase hexadecimal characters.
The client reads its token file, computes HMAC-SHA-256(token, nonce), and
replies:
(:authenticate :proof HEX)A valid proof receives (:authenticated :version 1). A proof is valid only for
that connection’s nonce. Authentication failure closes the connection without
a reusable credential or detailed oracle response.
After authentication, send one request at a time:
(:evaluate :source "(values (+ 20 22) *package*)")The source string must contain exactly one form. A dedicated serial evaluator
thread reads it in package AUTOLITH, applies the configured deadline, prevents
interactive debugger entry, and captures standard, error, trace, query, debug,
and terminal output in a bounded Gray stream. Successful responses have this
shape:
(:evaluation-result
:status :ok
:values ("42" "#<PACKAGE \"AUTOLITH\">")
:values-truncated-p nil
:output ""
:output-truncated-p nil):values contains one bounded readable string for every multiple value.
Conditions return :status :condition, a bounded :condition-type and
:report, captured output, and truncation flags. Deadline expiry returns
:status :timeout. The client may submit further requests on the authenticated
connection.
Evaluation deadlines use in-process interrupts and are best effort for trusted forms; a form that suppresses interrupts may outlive its deadline. Checkpointing stops the listener, clients, evaluator, and queue before saving the image and restarts the endpoint afterward. If an evaluator cannot quiesce within the documented two-second shutdown bound, checkpointing aborts with a typed management quiescence condition rather than saving live management threads. Normal shutdown is idempotent and removes only the exact Unix socket created by that runtime.
Autolith has a concept of ephemeral notes, which can be used to add temporary additional information to the context that will eventually disappear:
(define-context-contributor release-advice (request)
"Add advice for release requests."
(when (search "release" (or (request-context-latest-user-text request) "")
:test #'char-equal)
(make-context-contribution
:identifier "release-check"
:instruction "Verify release artifacts before publishing."
:lifetime ':turn
:priority 40)))- A contributor sees a read-only request snapshot
- It can return none, one, or many contributions
- Notes stack unless they deduplicate, supersede, or conflict
- Priority only comes into play when the advice budget is full
- You can use
(context)
Note that these are provider-request-only, and don’t not count as a conversation turn, and do not get attached to your messages.
The status line keeps a context meter visible while idle. It shows used tokens
and the full model window. | marks the automatic compaction point. The
ChatGPT subscription catalog currently reports a 272K-token window for GPT-5.6
Sol, Luna, and Terra, which compact at about 218K tokens by default:
ctx [==========|..] 218K / 272K used
At narrow widths, the meter keeps the used and window counts and drops the bar.
Register a OpenAI-compatible provider:
(register-openai-compatible-provider
:name "my-provider"
:description "My OpenAI-compatible provider"
:endpoint "https://api.example.com/v1/chat/completions"
:models-endpoint "https://api.example.com/v1/models")Inside a session, /auth and (auth) are the same operation. Omit the
provider to pick one interactively, or use /auth my-provider and
(auth "my-provider") for an explicit selection. Use
autolith auth my-provider to authenticate during startup.
After a key is saved, static metadata or the last good cache keeps models
available. (models) triggers discovery. You can also pass non-secret
:headers and static :models for descriptions, windows, efforts, or
providers that have no model-list endpoint.
Built-in ChatGPT, Grok, and Nous Research subscriptions authenticate through a browser device flow. Gemini uses Google installed-application OAuth with a local loopback callback. Anthropic, Fireworks, OpenCode, OpenRouter, Mistral, and user-registered OpenAI-compatible providers use API keys.
ChatGPT Codex Fast mode sends service_tier=priority when the active Codex model
advertises Fast support and uses 2x plan usage. The built-in GPT-5.6 models
support it. Use /fast or /fast status to inspect it, and /fast on or
/fast off to change the saved preference. The activity status line displays
FAST while the active model uses Fast mode. Other providers and unknown Codex
models keep the preference but use the standard path. Set
AUTOLITH_CODEX_FAST_MODE=on or off to override the saved choice for one
process; while it is set, /fast on and /fast off are unavailable.
ChatGPT Codex uses standard Responses requests and native Responses compaction. Normal requests enable provider parallel tool calls. Autolith executes independent calls concurrently, then records their results in provider wire order. Calls requiring a provider round trip, exclusive tools, and calls sharing one mutable runtime execute in separate ordered waves.
GPT-5.4 and later Codex models use native Responses tool search. Autolith sends
stable namespace names and descriptions up front, marks each function
defer_loading=true, and lets the provider expose matching schemas only when
needed. Other models and providers receive the complete eager tool array.
Authenticate Gemini with autolith auth gemini, /auth gemini, or
(auth "gemini"). Autolith stores its Google OAuth credentials separately at
gemini-auth.sexp under the state root. The provider uses the Gemini CLI Code
Assist subscription service directly, including streaming text and thinking,
tool calls, token usage, account onboarding, and managed companion projects.
AUTOLITH_GEMINI_OAUTH_CLIENT_ID may replace the public installed-application
client ID; AUTOLITH_GEMINI_OAUTH_CLIENT_SECRET supplies a client secret when a
deployment requires one.
autolith auth nousNous models are discovered from the authenticated account. Run (models) after
login to refresh the catalog. Model identifiers beginning with anthropic/
use the native Anthropic Messages route; the rest use OpenAI-compatible Chat
Completions. Hermes model identifiers are omitted from the catalog because they
are not reliable for agentic tool calling.
The Nous integration accepts three deployment overrides:
AUTOLITH_NOUS_PORTAL_URLselects the OAuth portal.AUTOLITH_NOUS_INFERENCE_BASE_URLselects the/v1inference base used for model discovery, Chat Completions, and Messages.AUTOLITH_NOUS_PROVIDER_ENDPOINToverrides only the Chat Completions endpoint.
Autolith stores Nous OAuth state separately at nous-auth.sexp under its state
root and serializes rotating refresh-token updates across processes sharing that
root.
Mistral models are discovered from the authenticated account and filtered to
models advertising Chat Completions support. MISTRAL_API_KEY takes precedence
over Autolith’s private Mistral key. AUTOLITH_MISTRAL_PROVIDER_ENDPOINT and
AUTOLITH_MISTRAL_MODELS_ENDPOINT override the chat and discovery endpoints
independently.
OpenRouter models are discovered from the authenticated catalog and use
openrouter/<vendor>/<model> identifiers locally. Discovery includes only models
that emit text and advertise function tools, tool choice, and normalized
reasoning, matching the request shape Autolith sends. OPENROUTER_API_KEY takes
precedence over Autolith’s private OpenRouter key. Authenticate with
autolith auth openrouter, /auth openrouter, or (auth "openrouter").
AUTOLITH_OPENROUTER_PROVIDER_ENDPOINT and
AUTOLITH_OPENROUTER_MODELS_ENDPOINT override the chat and discovery endpoints
independently.
(define-application-command my-version-command
(:name "/my-version"
:argument "[LABEL]"
:description "show a labeled local integration version"
:tip "shows the version supplied by init.lisp."
:busy-behavior :inspect
:terminal-behavior :shared
:callable t)
(application &optional (label "integration"))
(application-present application (format nil "~A 3" label))
:continue)- Missing interactive arguments open the restart debugger.
supply-argumentstakes one Lisp form of replacement arguments. Excess slash arguments are rejected before dispatch. Programmatic calls keep ordinary Common Lisp arity rules. - Busy behavior is
:inspect,:execute,:apply,:hold, or:cancel. During active work,:executeruns immediately,:applyapplies its change at the turn’s next safe provider boundary, and:holdwaits for the idle queue. Argument-free:inspectand:applyinvocations only display state, so they run immediately. An unknown command reports its error immediately instead of being scheduled. - Terminal behavior is
:shared,:exclusive, or:exclusive-without-arguments. :static-optionsreplaces:argumentfor one finite positional parameter. It derives the help hint and both slash and parenthesized completions.
Roles load from these directories. First match wins:
.autolith/agents/${XDG_CONFIG_HOME:-~/.config}/autolith/agents/- bundled roles
A higher-precedence malformed role is reported, not silently replaced.
A role is one bounded UTF-8 *.sexp property list:
- required:
:name,:description,:instructions - optional:
:tools,:spawns,:models,:reasoning-effort,:output,:blocking-p
Roles cannot grant self.*, task.*, job.*, or yield.*.
:toolsmay benil,:all, or canonical tool names and namespace patterns.:spawnsmay benil,:all, or role names. Task depth still applies.:outputis the native schema DSL. There,nilis JSON false and:nullis JSON null.
Example:
(:name "reviewer"
:description "Review a change for regressions."
:instructions "Report concrete, evidence-backed findings."
:tools ("resource.read" "search.*")
:spawns nil
:models ("@parent")
:reasoning-effort :high
:blocking-p t)task.agents shows effective roles. task.run starts one task or an
independent batch. Nonblocking work detaches by default. blocking: true
waits. A role with :blocking-p t always waits. Each child must finish
with one yield.submit of success, failed, or aborted.
Primary shell and Lisp executions and child agents appear as live rows with
spinners and elapsed time. The command and agent strips share a viewport bound,
and provider activity remains visible below them. shell.run accepts an optional
short :description for its row label; otherwise it derives one from the command.
The default pool admits eight concurrent children.
AUTOLITH_TASK_MAX_CONCURRENCY raises that up to 32. Child jobs have no
deadline unless AUTOLITH_TASK_MAX_RUNTIME_MS is set positive.
(checkpoint) requires a clean Git worktree and a passing ./script/check.
It saves pending reconstructible mutations and publishes a retained image
without stopping the active session.
(generations)lists retained images.(rollback ID)selects a compatible image and exits through recovery.
Inspect or boot retained generations from the CLI:
autolith --recovery --list
autolith --recovery --generation GENERATION-IDRecovery starts the separately built pristine core, not the damaged active core. If that image is missing or invalid, it falls back to source.
Automatic recovery tries, in order:
- the selected generation from the current source revision
- other compatible generations from that revision, newest first
- a private clean checkout of current committed source
Explicit generation selection may use an older revision.
A fatal active failure publishes a private crash capsule and restores the terminal. Recovery restores the conversation without duplicating scrollback. With a valid capsule, it queues one read-only diagnosis turn before ordinary input.
That turn can inspect:
- bounded crash context
- workspace and tracked source
- indexed source
- active state
It has no shell, MCP, hosted provider, image, write, mutation, checkpoint, or rollback tools. It asks before any repair. A manually started recovery image does not queue diagnosis.
Saved cores require their recorded SBCL version, OS build, and architecture.
Source remains authoritative. To reconstruct without a core, check out the
manifest revision, run ./script/bootstrap, load autolith, then load the
manifest’s ordered reconstruct.lisp in package AUTOLITH:
(asdf:load-asd (truename "autolith.asd"))
(asdf:load-system :autolith)
(load #P"/path/from/generation/manifest/reconstruct.lisp")