Skip to content

Latest commit

 

History

History
641 lines (492 loc) · 25.1 KB

File metadata and controls

641 lines (492 loc) · 25.1 KB

New User Guide

Intro

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:

autolith

If 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.

Install and update

Nix

Nix is the simplest way to install Autolith:

nix run github:luciusmagn/autolith

The first launch builds Lisp images Autolith needs to launch. These images live under ${XDG_DATA_HOME:-~/.local/share}/autolith/nix/images/}.

Linux binary release

It is possible to install autolith on Linux x86_64 via a shell installer:

curl -fsSL https://sh.lambda-symbolics.com/autolith | sh

Read 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 --update

Session:

(update)

Release archives are built and checked by the installer host. GitHub publishes release metadata after the host checksum is available. See Release service.

Source checkout

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)' --quit

On 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/check

To put a checkout into PATH:

mkdir -p ~/.local/bin
ln -sfn "$PWD/bin/autolith" ~/.local/bin/autolith

This is assuming you have .local/bin/ in PATH.

Talk to Autolith

Prose, Lisp, and commands

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.

Managed resources

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:current lists active reports and accepts papercut-report.
  • papercut:id/<percent-encoded-stable-id> reads one active report and accepts papercut-close with a complete resolution.

The existing papercut.report tool and /papercuts, /papercut, and /papercut-close commands remain convenient direct interfaces.

Session titles and resume

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 1200

On 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.

Local sessions

Every running Autolith session publishes a private authenticated local endpoint. Inspect them with:

autolith localgroup status
autolith localgroup status --sexp

idle 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-ID

tell 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.

Configure Autolith

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.

Active-image management endpoint

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:

VariableDefaultMeaning
AUTOLITH_MANAGEMENT_REPLoffEnable the endpoint.
AUTOLITH_MANAGEMENT_REPL_TRANSPORTunixunix or tcp.
AUTOLITH_MANAGEMENT_REPL_UNIX_SOCKET${XDG_STATE_HOME:-~/.local/state}/autolith/management/repl.sockUnix socket pathname.
AUTOLITH_MANAGEMENT_REPL_TCP_ADDRESS127.0.0.1IPv4 loopback listener address.
AUTOLITH_MANAGEMENT_REPL_TCP_PORT4141TCP listener port.
AUTOLITH_MANAGEMENT_REPL_TOKEN_FILE${XDG_CONFIG_HOME:-~/.config}/autolith/management-repl.tokenMode-0600 token file.
AUTOLITH_MANAGEMENT_REPL_TIMEOUT10Evaluation deadline in seconds.
AUTOLITH_MANAGEMENT_REPL_MAX_FRAME1048576Maximum wire-frame octets.
AUTOLITH_MANAGEMENT_REPL_MAX_SOURCE262144Maximum UTF-8 source octets.
AUTOLITH_MANAGEMENT_REPL_MAX_OUTPUT262144Maximum captured output and value text.
AUTOLITH_MANAGEMENT_REPL_QUEUE_CAPACITY8Maximum queued evaluations.
AUTOLITH_MANAGEMENT_REPL_MAX_CLIENTS8Maximum accepted clients, including authentication.
AUTOLITH_MANAGEMENT_REPL_AUTH_TIMEOUT10Absolute 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.

Wire protocol

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.

Context

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.

Providers

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 nous

Nous 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_URL selects the OAuth portal.
  • AUTOLITH_NOUS_INFERENCE_BASE_URL selects the /v1 inference base used for model discovery, Chat Completions, and Messages.
  • AUTOLITH_NOUS_PROVIDER_ENDPOINT overrides 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.

Commands

(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-arguments takes 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, :execute runs immediately, :apply applies its change at the turn’s next safe provider boundary, and :hold waits for the idle queue. Argument-free :inspect and :apply invocations 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-options replaces :argument for one finite positional parameter. It derives the help hint and both slash and parenthesized completions.

Child roles

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.*.

  • :tools may be nil, :all, or canonical tool names and namespace patterns.
  • :spawns may be nil, :all, or role names. Task depth still applies.
  • :output is the native schema DSL. There, nil is JSON false and :null is 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.

Generations and recovery

(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-ID

Recovery 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")