Skip to content

Latest commit

 

History

History
2808 lines (2304 loc) · 141 KB

guide.org

File metadata and controls

2808 lines (2304 loc) · 141 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.

Type (, [, or { in the composer to insert a matching pair with the cursor between the braces. Type the closing brace to move past it, or press Backspace between an empty pair to delete both braces.

Tab completes slash commands, operation names, finite options, and workspace paths, and Shift-Tab steps back through the suggestions. With nothing to complete, Tab sends the input like Enter, or queues it as a follow-up while a turn is running. Inside a form you are still typing, such as (job.continuity :, Tab does nothing rather than send half a form. The work indicator displays REPL with one letter highlighted in sequence. (settings :transcript-style :repl) opens messages like a Lisp REPL exchange, with > before yours and < before Autolith’s, and the time on the line above while turn timestamps are on; :labels restores the ❯ you and ● autolith headers. Below the modeline, a line names what the turn is doing, such as pondering; (settings :activity-line-p nil) hides it and spares the row, while the modeline keeps timing the turn. A streaming response needs no such line.

Terminal diagrams

Ask for a fenced mermaid block to view a diagram directly in the terminal. Autolith uses Clermaid for flowcharts, sequence diagrams, flat state diagrams, class diagrams, and entity relationships. The library documents the supported syntax for each family. Boxes, connections, arrowheads and labels use separate theme colors. Graph layers are centered, with ordered ports and routes that favor fewer crossings. Unicode diagrams use rounded connection corners and directional end caps.

```mermaid
flowchart LR
  A[Read] --> B[Evaluate]
  B --> C[Print]
```

During streaming, view the source in the live tail until the closing fence. Use the diagram’s copy action to copy its original Mermaid source. For syntax outside the supported subset or a diagram too wide for the terminal, read the ordinary source block instead.

Command arguments

Pass native Lisp values to commands. Use keyword actions, Boolean switches, integers, pathnames, and quoted data directly:

(settings :boot-screen-seconds 2)
(settings :onboarding-p nil)
(trace t)
(trace nil)
(mcp :refresh)
(permissions :ask)
(tasks :transcript "ID" :offset 0 :limit 4000)
(tasks :send "ID" :message "Keep the original message intact.")

Omit a switch argument to inspect its value; pass t to enable it or nil to disable it. Strings represent text such as objectives, messages, names, and identifiers. Pass structured arguments as Lisp data rather than source text inside a string.

Onboarding

At the opening boot screen, press Space or Enter to start the tour. Press Esc or begin typing to open the listener instead. Typed and pasted text is kept in the prompt; the lessons are available on the next launch or with (onboard).

On your first interactive launch, try the prompt, (help), background Lisp, and (settings) in the fullscreen tour. Edit the lesson input normally and press Enter to check it. Resize your terminal at any point. Use Page Up/Page Down to scroll long lessons and Shift-Tab to go back. Press Tab to skip one slide, including an exercise, without changing its settings.

Press Esc to skip the tour. Completed and skipped lesson versions are recorded in your state directory. Ctrl-C interrupts the tour without acknowledging the unfinished lessons. Later launches show only new or revised lessons. Evaluate (onboard) to replay the whole tour.

Disable automatic tours through (settings :onboarding-p nil) or set AUTOLITH_ONBOARDING to off. If you disable the boot screen, enter the tour directly without its opening animation.

Goals and verified missions

Use (goal "OBJECTIVE") for lightweight autonomous work. Inspect it with (goal) or (goal :status). Use (goal :pause), (goal :resume) or (goal :clear) to control it, and (goal :update "NEW-OBJECTIVE") to edit it in place. String arguments are literal objectives, including "pause". Unknown keyword actions signal application-goal-error without changing the goal.

For externally verifiable work, install explicit acceptance policy with (mission :start specification):

(mission :start
 '(:objective "Deliver the accepted build"
   :turn-limit 40 :token-limit 100000 :wall-seconds 3600
   :criteria ((:id "build" :description "The build passes" :gates ("build"))
              (:id "review" :description "The user accepts the result"))
   :gates ((:id "build" :kind :command :command "make check"
            :inputs ("Makefile" "src/main.lisp") :deterministic-p t
            :attempt-limit 3 :timeout-seconds 300))))

Use (mission :run) to begin, (mission :status) to inspect the complete durable state, and (mission :verify) to request harness verification. The model can also call mission.status, mission.verify, mission.stop and mission.invalidate. Only the primary agent may mutate mission policy through these model operations. Configure acceptance criteria yourself; the model cannot weaken them or supply user acceptance.

Each criterion has a unique :id, a :description and optional ordered gate IDs in :gates. Criteria without gates require your explicit evidence:

(mission :accept "review" "I inspected the artifact and accept the result")
(mission :resume)
(mission :verify)

The model’s completion marker requests verification. Passing a gate proves only the criteria you explicitly associated with it. Verification requires all gates and all criteria. Inspect the acceptance mapping before beginning work.

The turn allowance counts provider inference calls across the primary agent, children, compaction and recursive inference, including mapped and nested frames. Reported billable tokens use the provider API’s usage normalization and exclude known prompt-cache reads. Admissions and settlements are serialized within a mission; provider requests execute concurrently. Each admission reserves a turn and an output ceiling from the unspent, unreserved tokens. The default ceiling divides those tokens among the remaining turns; an explicit provider output cap replaces that division. Reviewer requests reserve from both allowances. Settlement releases the ceiling and charges reported input and output usage, so a request can exhaust the token allowance. Missing usage is recorded as unknown and blocks new mission work. Already-running children may finish within the existing hard budgets. Install a new explicit budget to admit new work after unknown usage.

The wall-clock deadline includes gate execution, delegated tools, pauses and time away. Work runs under cl-jobpond supervision. Use (goal :pause) and (mission :resume) without resetting any mission budget; use (mission :cancel "REASON") to terminate it. Terminal states are verified, exhausted, cancelled and failed. A blocked mission retains the evidence needed to resolve its blocker and resume. Interrupted inference restores as blocked when its usage cannot be recovered. Gate attempts, fingerprints and evidence restore with the conversation and survive compaction independently of model summaries.

Gates run in order, stopping at the first failure. Command gates use the ordinary shell authorization and sandbox boundary. Artifact gates require :path and optionally an expected :sha256. Lisp gates require a :predicate string and run an assertion in the existing isolated Lisp worker. Every gate supports :timeout-seconds and a separate :attempt-limit, defaulting to 60 seconds and three executions. Each gate retains bounded evidence.

For deterministic gates, declare all relevant file paths in :inputs and set :deterministic-p t. File contents and missing-file states form the fingerprint; directory paths are rejected. Unchanged passing gates supply current proof, and unchanged failed gates are withheld while the model works on their relevant inputs. For remote state, omit deterministic policy or invalidate a particular gate explicitly:

(mission :invalidate "build" "The remote build configuration changed")

Invalidation permits another execution without replenishing its attempt budget. Before acceptance, every gate fingerprint is checked again under the workspace mutation lock. Changes from a later gate invalidate earlier passing evidence. Collect terminal results and outstanding inference usage before verifying.

Independent review checkpoints

Attach a reviewer to an active mission with mission-review.configure, or evaluate:

(application-mission-review-configure
 *active-application*
 '(:id "release-review" :phase :final-acceptance
   :inputs ("src/main.lisp" "tests/main-tests.lisp")
   :context "Review the changed behavior and test coverage. Report concrete findings."
   :turn-limit 4 :token-limit 16000 :run-limit 3))

Declare the complete relevant file set. The reviewer receives an independent snapshot and background, returns contracted findings, and has no tools or spawning permission. Reviewer call and token allowances are cumulative across changed snapshots and also count against the mission allowances. :run-limit bounds the number of distinct snapshots reviewed.

Use mission-review.trigger with id to review a checkpoint and mission-review.inspect to read its durable findings, decisions and usage. Unchanged snapshots reuse completed, failed or interrupted runs. Resolve or reject each finding through mission-review.decide with id, finding, decision (resolve or reject) and an evidence-bearing reason. Only the primary agent or local user may configure, trigger and adjudicate reviews.

Final verification runs configured final-acceptance checkpoints. Pending findings and failed or interrupted reviews block acceptance. For before-integration and after-edit-set checkpoints, trigger the named review explicitly at the chosen boundary. Reviewer findings are advisory; mission gates and user acceptance supply harness evidence.

Scheduled mission continuations

Attach explicit wakeups to the current mission with mission-schedule.add. Supply an immutable id, continuation content, and a time policy: at is an absolute Common Lisp universal time, interval is a positive number of seconds, and event names an external event instead of a time policy. For example:

(application-mission-schedule-add
 *active-application* :id "build-followup"
 :content "Check the pending build and continue the mission."
 :at (+ (get-universal-time) 300) :missed-policy :latest)

For recurring wakeups, choose missed-policy latest (the default), all or skip to select the newest missed occurrence, every missed occurrence or none. Use mission-schedule.event with an event name and stable external event id; repeated delivery of that ID is deduplicated. The local daemon listener hosts time wakeups. Due work enters the ordinary primary command queue as a persisted (mission-wakeup ticket) command with a structured ticket and executes as a mission continuation under the existing turn, token and wall-clock budgets.

Only the primary agent may use the mission-schedule tools. Each wakeup retains its exact mission identity and version, checked at admission and execution; replaced or inactive mission work is cancelled. Use mission-schedule.inspect to read schedules, occurrence IDs and claim tokens, and mission-schedule.cancel with the schedule id to cancel future and pending work.

Interrupted admissions and executions restore as uncertain outcomes. Inspect the external effects before calling mission-schedule.resolve with the readable occurrence ID, current token and action completed, failed or retry. Retry is an explicit decision and is refused while the occurrence is executing. Cancellation retains claim and duplicate history.

Editor sessions over ACP

Configure your ACP-compatible editor to launch:

autolith acp

Authenticate the selected provider with autolith auth before starting the editor session. ACP uses UTF-8 JSON-RPC on stdin/stdout; diagnostics go to stderr. The normal launch loads durable mutations and user initialization. For AUI, use autolith --permissions auto acp. Use --pristine when recovering from a damaged mutable image.

New sessions use the editor’s absolute working directory and a durable Autolith conversation ID. Load that ID to replay messages, visible reasoning, tool calls, and results. Text prompts, resource links, and embedded text resources are supported. File and process operations use the native Autolith tools.

The default ask mode requests command and external-tool approval in the editor. An approval for the session covers the exact command and working directory, or external tool identity and arguments. Select auto, sandboxed, or full-access through ACP session modes; use --permissions auto|sandbox|full on the command line. Changing modes clears session approvals.

Client-supplied stdio and HTTP MCP servers are merged with native registrations for that session. Literal environment values and headers are ephemeral and redacted from server output. Cancel a prompt to stop its provider, tools, and owned jobs; close the session or disconnect to release its resources and lease. One primary prompt, setting mutation, tool invocation, or session setup runs at a time in each ACP process. Catalog reads and owned-job controls can run alongside it. Ctrl-C ends an ACP process with status 130 after it releases its sessions.

Remote clients

autolith acp --http serves remote clients on one endpoint, /acp, over the transports of the ACP proposal “Streamable HTTP & WebSocket Transport”: Streamable HTTP over HTTP/1.1 or cleartext HTTP/2, and WebSocket. autolith acp --websocket serves WebSocket alone. Both listen on 127.0.0.1:8737 by default. Name another port, or a host and a port, to change that:

autolith acp --websocket 9000
autolith acp --http 0.0.0.0:8737 --token "$ACP_TOKEN"
autolith acp --http [::1]:8737

Autolith prints the endpoint URL on stderr and serves until Ctrl-C. Each client gets its own sessions. All clients share the process’s one primary operation, so while one client’s prompt runs, another client’s prompt is refused as busy.

With --token, a client must send Authorization: Bearer TOKEN or add ?token=TOKEN to the URL. Other users of the machine can read the command line, the token included. Requests from web pages, which carry an Origin header, are refused.

Any host but loopback makes the agent reachable from other machines, and Autolith warns that the endpoint is not secure. The traffic, the token included, is unencrypted: put a reverse proxy with TLS in front of it, and use a token.

Typed client extensions

Request _autolith/capabilities to discover installed methods and schema version. Include sessionId in every other extension request. Responses contain outcome (ok, condition, busy, unsupported, or cancelled), with value on success and a bounded report on failure. Unknown measurements are JSON null.

Method suffixValue or action
stateSession, turn, model, permission, usage, plan and health snapshot
settings, models, operationsTyped catalog items
set-settingSet name to value; return the updated setting row
invokeInvoke operation with JSON arguments; return presented text
steer, queueDurably admit primary text; return accepted and delivery
conversationsPage durable conversations using limit and cursor; optionally filter cwd
jobs, jobPage owned jobs or inspect one jobId
job-transcriptRead jobId with offset, limit and output (text or structured)
job-send, job-cancelSend text to or cancel an owned jobId
resourceRead bounded workspace: lines or shell-log: bytes from uri

Prefix these suffixes with _autolith/. Catalog operations expose admission and availability; tools use their native decoders and ACP approval callbacks. Terminal-only operations return an explicit unsupported outcome. Workspace resource windows use startLine and lineCount and include the observed revision. Shell-log windows use byteOffset and byteCount and require session ownership.

Invoke ordinary commands with arguments: {"input": "..."}, using slash-command text without the command name. text is an alias for input. Commands such as skills, hurry-up and papercuts return their presented text. Operations with terminalWithoutArguments require an argument to avoid a terminal picker. During an active turn, native inspection and execution policies apply; boundary-applied commands return delivery: "scheduled" and present their results as ACP updates.

steer delivers text at the next safe boundary, including submissions made while an approval is pending. Late steering is promoted ahead of ordinary queued follow-ups. queue appends a follow-up for the next available primary turn. Both accept up to 262144 characters and return delivery: "steering" or "queued". Display accepted input separately until its durable user_message_chunk arrives. Cancellation pauses pending input; a new submission resumes it. Accepted input survives session close and reload. ACP has no per-item withdrawal operation.

For structured child transcripts, value.updates contains ordinary ACP SessionUpdate bodies, with messages, reasoning, tool calls and result content. Offsets count updates from zero; the default limit is 100 and the maximum is 200. The typed response envelope is bounded to 262144 UTF-8 bytes. A single update over that bound returns a condition instead of advancing the cursor. nextOffset is JSON null when complete is true, meaning the current snapshot is exhausted. For a running child, poll again from total to read later updates. Text output uses character offsets and retains its existing numeric end cursor.

Treat _autolith/event state and catalog notifications as invalidation hints. Refetch the affected snapshot after a sequence gap. Events with kind turn are ordered lifecycle boundaries: data contains state (running or idle), an integer epoch, source (prompt, queue, completion, or invoke), and a terminal outcome (end-turn, cancelled, or condition; null while running). Match the epoch when handling approvals, cancellation and transcript boundaries.

Automatic job continuations stream through ordinary ACP updates. Cancellation or an inference failure pauses continuation; submit an explicit prompt to resume.

Emacs and sly

Autolith’s image can be your Common Lisp development image. emacs/autolith.el in the source tree sets up one frame with three windows: your code, a sly REPL connected to Autolith’s own image, and an agent-shell conversation with the same Autolith. What you define in the REPL lives beside the agent, which can inspect it and advise.

The sly REPL spans the bottom quarter of the whole frame. Above it, edit code on the left and converse in the right third.

Install sly and agent-shell, load the file, and run M-x autolith-ide from the buffer you are editing:

(load "/path/to/autolith/emacs/autolith.el")

Optional Sly contribs need their Lisp dependencies in Autolith’s image. For sly-stepper, install agnostic-lizard in a persistent ASDF source registry visible to the Autolith process before Slynk starts.

autolith-ide starts the Emacs server if needed and launches autolith acp through agent-shell with three environment variables:

  • AUTOLITH_SLYNK_DIRECTORY is your sly’s own slynk/ directory, so the server speaks exactly your client’s protocol. Autolith loads that slynk.asd by path and compiles it into the ASDF cache on first use.
  • AUTOLITH_SLYNK_PORT_FILE receives the loopback port once Slynk listens, or error: REASON when it cannot start. autolith-ide waits for it, up to autolith-slynk-timeout seconds, and connects sly.
  • AUTOLITH_EMACS_SERVER is this Emacs’s server socket and enables the emacs: resources and emacs.* tools.

M-x autolith-agent-shell starts only the conversation, with Emacs access and without Slynk. The same variables, or the :slynk-directory, :slynk-port-file and :emacs-server-socket settings, work outside Emacs. In the terminal, (slynk) starts Slynk and shows the port for M-x sly-connect, and (slynk :stop) closes it.

The agent reaches your definitions with lisp.describe, lisp.source and lisp.apropos using target self, or with self.eval. While Slynk serves, a context note tells it so. Your Emacs appears to the agent only while the configured server socket exists:

  • emacs:current, read with resource.read, lists authorized visible windows with point, the active region and open file buffers, each by its emacs:buffer/NAME URI. Outside-root files and non-file buffers require full-access approval; denied buffers are omitted from this snapshot.
  • emacs:buffer/NAME is a live buffer’s text, unsaved edits included, with the buffer name percent-encoded. resource.edit applies line operations to the buffer itself: the change stays unsaved and undoable, and is refused as stale when you edited the buffer since the agent read it. A buffer visiting a file outside the workspace and source roots asks the same permission as that path.
  • emacs.visit shows a file position in a file window of the selected frame.
  • emacs.eval evaluates Emacs Lisp. It asks command authorization for emacs.eval -- FORM and runs only with full access.

Slynk listens on 127.0.0.1 only. Like ordinary sly it has no authentication beyond the optional ~/.sly-secret, so anyone who can reach loopback on this machine can evaluate in the image. Your code shares the agent’s heap: a fatal error or (sb-ext:exit) ends the session, which recovers like any crash. Slynk’s debugger and output hooks stay off for Autolith’s own threads, so your REPL sees only your evaluations. A checkpoint stops Slynk, closing sly connections, and serves the same port again; reconnect with M-x autolith-ide-reconnect. A checkpoint waits for in-flight sly evaluations and fails if they exceed the shutdown timeout.

Fullscreen terminal

Autolith uses the ordinary inline terminal presentation by default. Opt into the application-owned fullscreen viewport for a session with:

autolith --fullscreen

Save the preference for future launches at the Autolith prompt:

(preferences-set-fullscreen (application-configuration *active-application*) t)

In fullscreen mode, scroll the transcript above the bottom-pinned composer, modeline, command, RLM, and task activity rows. Use PageUp and PageDown for page movement, Ctrl-PageUp and Ctrl-PageDown to jump between the user and assistant messages while skipping reasoning and tool activity, Ctrl-Home and Ctrl-End for the oldest and newest transcript positions, and the mouse wheel for incremental scrolling. A message jump ends the message on the row above the composer, where your eyes already are; a message taller than the window shows its header at the top instead. The --fullscreen option enables it for one launch without changing the saved preference. Pass nil to preferences-set-fullscreen to return future launches to inline mode. Because the viewport owns the mouse, hold Shift while dragging to select text with the terminal emulator. Fullscreen history lives in the application rather than the terminal emulator’s native scrollback.

For input with matching saved memories, a dim @ N relevant memories exist line appears below your message. Its count is the number offered to the model for that request, followed by how many of them are stale or unverified (see the Memories section). Each input gets one notice across tool calls and provider retries.

Input recall includes every user message, command, and Lisp submission in the conversation. Automatic messages and assistant output are excluded. On first use, the complete input-only sidecar is built from the conversation records; later submissions are appended to it. Resuming reads this sidecar instead of replaying archived transcript chunks.

Clicking a web address in the fullscreen transcript opens it in the default browser, and every fenced code block ends with a ⧉ copy label that copies the block’s source. Copies go to the host clipboard through wl-clipboard, xclip or xsel, pbcopy, or the Windows clipboard, and the terminal also receives an OSC 52 request, so a terminal reached over SSH or a localgroup relay copies on your own machine. Web addresses in every mode are emitted as OSC 8 hyperlinks, which terminals open with their usual modifier-click.

Clicking a tool call’s header in the fullscreen transcript inserts its Lisp invocation, such as (resource.read :uri "file:///tmp/notes.org"), into the composer at the cursor. Submitting it alone repeats the call with the same arguments; editing it first runs a similar call; writing around it asks the model about that exact call. Arrays are spelled as (vector ...) and objects as (dict ...), and argument names with uppercase letters keep their case as :|filePath| or a string key.

Interactive sessions report their root status with the OSC 7501 Program Status Protocol, with app=autolith. Reports use idle at startup, working during foreground input and resumed model turns, done after success, and error after failure. Cancellation returns to idle. Completion status persists until the next input or model turn starts. Input selectors report blocked with kind=question; command and tool approvals use kind=permission, and provider authentication uses kind=auth. These reports contain no prompt text or credentials. Reports use the interactive terminal transport in inline and fullscreen mode.

Root reports include the current conversation title. Child agents and background tool jobs use independent jobs/<execution UUID without hyphens> records for the same conversation. Queued and running jobs report working; completed, failed, and cancelled jobs report done, error, and idle. Titles use job names or tool descriptions, with control characters removed and a 192-byte UTF-8 limit. Reports contain no assignments or result text and no estimated progress percentages.

Autolith retains at most 63 job records and reserves capacity for active jobs before completed history. It sends changed records and clears removed records. After an OSC 133 prompt-start marker, it replays active records only. Disconnecting the job observer clears its records. Terminal presentation depends on the terminal.

Fullscreen startup presents a brief Lisp-machine boot screen with a centered tip and a rock mascot that adapts to short viewports, then the session banner and transcript above the composer. Mouse wheel scrolling also works through a controlling local-session attachment.

The boot screen then waits for Space or Enter before opening the listener, rotating the startup tip every ten seconds while it waits. Ctrl-C on the waiting boot screen halts the session. Turn the wait off for future launches with (setf (config :boot-screen-linger-p) nil) or for one launch with AUTOLITH_BOOT_LINGER=off, and change the rotation with (setf (config :boot-screen-tip-seconds) 30) or AUTOLITH_BOOT_TIP_SECONDS. The animation takes (config :boot-screen-seconds) in total, 3.5 by default and also set by AUTOLITH_BOOT_DURATION, shared evenly by its phases. Skip the boot screen entirely with (setf (config :boot-screen-p) nil) or AUTOLITH_BOOT_SCREEN=off; (settings) lists all four.

Almighty theme

autolith --almighty

forces the fullscreen viewport and presents the Almighty Lisp theme from almightylisp.com: near-white text on the site’s blue, yellow for everything emphasized, cyan for code and strings, and an ALMIGHTY LITH boot mark. Terminals that advertise true color through COLORTERM get the exact colors; others get the nearest indexed or basic ones. The theme sets the terminal’s default colors only while the viewport is active and restores them on exit. Save it for future launches with (setf (config :terminal-theme) :almighty); :autolith restores the default presentation. Detached sessions carry the theme across handoff.

Install, update, and uninstall

Nix

Nix is the simplest way to install Autolith:

nix run github:lambda-symbolics/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

The installer verifies the release archive’s SHA-256 before extraction. The checksum must identify that exact archive; malformed or missing checksums abort installation. The initial shell script is fetched over HTTPS.

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. To install the latest binary release and exit without starting a session:

autolith update

The command exits successfully when updated or already current, and returns a nonzero status if checking or installing fails. autolith --update is an equivalent update-and-exit alias. Run autolith afterward to start the new release. Both spellings accept --help or -h; extra arguments exit with status 64 before checking for updates. Use your flake or Nix profile to update Nix installations; update the checkout and run ./script/bootstrap for source installations. These installations explain their update mechanism without building or starting Lisp images.

To update from a session and restart it with its original command arguments:

(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

The check runs FiveAM tests in isolated SBCL processes, defaulting to the available logical CPU count (one worker if detection fails), and verifies the pristine recovery image. Each run first saves an active image of the checked source, so cases that start a fresh Autolith boot that core instead of loading the system. List or select test suites and individual cases with:

./script/check --list
./script/check --suite workspace-resource --suite fixtures
./script/check --test test-workspace-file-resources --jobs 1
./script/check --jobs 8 --timeout 900

Selected runs skip recovery probes. Selectors are exact, case-insensitive names and can be repeated; unknown names fail. --jobs controls process concurrency, and --timeout sets the deadline per worker in seconds (default 600). Results include per-case timings and the slowest cases. AGENTS.md documents test registration, reusable fixtures, and interactive Lisp entry points.

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.

Windows

For the Windows x86-64 development artifact, extract the archive into a writable folder and run .\bin\autolith.cmd from PowerShell. Use Windows 10 version 1903 or newer. The bundled SBCL executable has a UTF-8 process manifest for Unicode paths; system-wide locale changes and 8.3 filenames are unnecessary. The first launch builds per-user cores under .autolith-images beside the release.

To build the source checkout, install Git for Windows, Rust with the MSVC toolchain and Cargo, and Quicklisp under %USERPROFILE%\quicklisp. Put clang on PATH for the ColorLisp syntax library. An SBCL 2.6.6 or newer on PATH is used when present; otherwise the bootstrap downloads the official SBCL Windows binary pinned in sbcl-windows-releases.sha256, verifies its SHA-256, and unpacks it below the data root without registering it with Windows. OpenSSL 3 comes from Git for Windows, from AUTOLITH_OPENSSL_DIRECTORY, or from a native\openssl directory beside the sources.

From PowerShell, bootstrap and check the checkout:

script\bootstrap.ps1
script\check.ps1

script\check.ps1 takes the same selectors as ./script/check. Checks that need FIFOs, forked children, pseudo-terminals, POSIX file modes, or the POSIX shell are recorded as skipped. To package a Windows archive, install the Windows SDK (including mt.exe) and run script\package-windows-release.ps1 -Tag VERSION. The POSIX release-server scripts are not built on Windows.

Start a session with bin\autolith.cmd or bin\autolith.ps1; both take the arguments bin/autolith takes. Windows Terminal is the primary console; legacy consoles without virtual-terminal input are refused with a message.

Windows sandboxed commands use the bundled AppContainer helper. They can write in the workspace and a private temporary directory, read system applications and explicit tool directories, and have no network access. Repository metadata is protected. Bootstrap installs the helper, both compatibility DLLs, and the Detours license into a content-addressed generation under the private data root’s native/sandbox/ directory. After validating every file, it atomically replaces the selected record. Older generations are retained. Release archives contain exactly the selected generation and its relative selector.

Set CL_EXEC_SANDBOX_WINDOWS_HELPER to a nonempty executable path to override the launcher default. Otherwise, the launcher validates the selected bundle and exports its generation-specific executable path. After source bootstrap, only launcher-generated defaults are refreshed. Re-run script\bootstrap.ps1 to install the bundle when upgrading from a single-helper installation.

Windows supports supervised detached sessions, client-first starts, and exact-heap restart checkpoints. It withholds fork-based checkpoint saving and the unix management REPL transport.

Uninstall

To remove Autolith and keep your data:

autolith uninstall

The launcher lists what it will remove, asks for confirmation, and exits without starting a session; --yes skips the prompt, which is required when no terminal is attached. It removes the packaged installation and its command link, managed SBCL runtimes, the active and recovery images, retained generation cores, saved worker images, Nix image trees, release markers, crash capsules, launcher pointers, session sockets and leases, provider caches, and the cache root.

User data stays in place: conversations, inference traces, memories, papercuts, agendas, plans, pending input, private image commits, the mutation journal and mutation history, permissions, preferences, credentials, and the configuration root. Stop running sessions first. A source checkout stays as well; delete it yourself. Nix installations also need the package removed from the flake or profile. On Windows the launcher prints the folders to delete, because the runtime in use lives inside them.

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

Use (help) for a short overview. Choose a topic or look up one command:

(help :conversations)
(help :workspace)
(help 'resume)
(help "lisp.eval")

Keyword topics open sections. Quoted symbols and strings open command or tool help, including usage and parameter descriptions. Browse tools by namespace with (help :lisp), list all commands with (help :commands), or request the full reference with (help :all). Help uses the terminal Markdown renderer and Lisp syntax highlighting.

Press Tab after =(help = to browse topics and registered operations. Command options also appear after a command name and a space, for example =(ste =.

Type @ in prose or inside a Lisp string to fuzzy-search workspace files through fff. Accepting a match inserts the path and drops the @. Directories keep a trailing slash so you can keep completing. A completed file stays in the draft.

The clanker sees both your source input and its evaluated result apart from any other effects it may have.

Local Lisp results use syntax highlighting and width-aware pretty printing. Small values fit on one line; longer property lists use aligned keyword/value rows. Each returned value has its own ⇒ marker. Captured output appears in a separate section. Large or circular values use bounded previews.

Direct tool results use report text without string-literal wrapping. Native S-expression reports from task, job, and mission tools use the Lisp formatter, with parentheses, tagged roots, and fields intact. Ordinary strings use Lisp string notation. Tool calls return their usual values, so (list (task.agents)) produces a list containing the report string. Copied or modified report strings also use string notation. Synchronous and asynchronous evaluation retain the same display text for replay.

Provider tool calls show parenthesized names, such as (shell.run), with Lisp keyword labels, such as :directory and :timeout-seconds. Short argument tables fit inside the parentheses on one line; wider calls use aligned rows below the name. Shell commands and Lisp source appear in highlighted rails directly below the arguments, without a blank row between them. Consecutive single-line tool success messages share a block without blank rows. Consecutive single-line calls to the same tool also share a block without blank rows. Unified diffs in captured output use the ordinary output rail, with their original change prefixes and indentation.

Start input with ? followed by whitespace to evaluate one form asynchronously in the active image. Use this for a blocking operation or a bare variable:

? *package*
? (progn (sleep 10) (values :finished 42))

The editor displays ? in place of the normal prompt. Submit with Enter, then continue using the session while the form runs. ?? and ?text are chat; spaces, tabs, and line breaks after ? select asynchronous Lisp. Continue an incomplete form on another line as with ordinary Lisp input.

Inspect or cancel the returned exec:N job with job.get, job.wait, or job.cancel. The bare number works too, as in (job.cancel :id 1), while exactly one visible job ends in :1; otherwise the error lists the candidates. Standard output, error output, and Lisp diagnostic streams are captured with bounded retention and rate-limited, sanitized terminal display. On completion, receive all returned values or the error, together with captured output. Asynchronous evaluation uses noninteractive error handling and EOF input rather than a restart picker.

The submitted form and its eventual result are retained in the originating session, including across a session switch. The model receives these messages at a safe request boundary; submitting a form does not start a model turn.

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.

The debugger shows the condition type and report, followed by available details such as the offending value, expected type, operation, filename, and reader location. These fields come from the condition itself and are also included in model diagnosis.

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, Escape cancels the turn and continues queued steering in submission order, oldest first. Ctrl-C cancels and pauses queued work until you submit input; a second interrupt within the force-exit window exits the application.

Parking and editing queued input

Run (vault-store) to move queued work and unconsumed steering into the recovery input vault immediately, including during an active turn. Active work continues. Use (vault-contents) to read the parked entries in restoration order and setf to replace the whole list or one zero-based entry:

(vault-store)
(vault-contents)
(setf (vault-contents 0) '(:message "Revised task"))
(setf (vault-contents) '((:message "First task") (:lisp "(+ 1 2)")))
(vault-restore)

Entries are (:message INPUT), (:command TEXT), (:lisp SOURCE), or (:native-lisp SOURCE) for noninteractive scheduled wakeups. Direct vault reads and top-level setf forms containing only vault-contents places execute during an active turn. Returned lists are detached snapshots; publish edits with setf. Use (setf (vault-contents) nil) to clear parked input. Run (vault) for a summary or (vault-restore) to prepend parked work to the live queue.

Managed resources

resource.read observes durable state and returns an opaque revision. resource.edit applies a resource-specific operation only against that exact observation.

For multi-file semantic changes, Autolith prepares a cl-resources change set against retained file revisions and validates every member before publication. Each file is published atomically. On a later failure, Autolith restores earlier members; conflicting external updates require explicit recovery rather than replacement. File moves preserve source permissions captured during staging.

Papercuts report concrete defects or repeated friction in the running Autolith harness or its tools. Record project source bugs and test, build, or CI failures in the task plan or agenda, even when developing Autolith itself; use a project tracker only when authorized. A project failure alone is not evidence of a harness defect. Papercuts are not progress reports or records of ordinary uncertainty.

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 "ID"), and (papercut-close "ID") commands remain convenient direct interfaces.

Conversation history uses read-only resource URIs:

  • conversation:current is the current conversation’s complete durable history, including the records compaction removed from the model context. A plain read returns the newest records; start-sequence and record-count (default 20, maximum 200) page by durable record sequence. A window holds at most 7,000 characters and shortens long records, so read a record alone to see all of it.
  • With query, the read lists the newest records containing every whitespace-separated term, each with its sequence and an excerpt. ASCII letters match in either case and other characters match exactly. max-results defaults to 20 and is capped at 50. Messages, assistant replies, reasoning summaries, tool call arguments and outputs, web searches, and compaction summaries are searched.
  • conversation:id/<id> reads or searches another conversation by its identifier. Searches never span several conversations.

Each model report is acknowledged durably in the conversation, and a report that repeats an active papercut of the workspace is not recorded again: a title matching word for word, or a title and body whose words largely overlap a report from the last day, answers with that existing report instead. A conversation re-filing within an hour of its own report is matched more loosely, since rewritten repeats share fewer words.

Memories

Autolith remembers durable facts, preferences, and decisions per workspace or globally, and offers the related ones with each request. A memory can record where it came from:

  • a quoted passage of a file, or the whole file;
  • your own words in the conversation;
  • a web page;
  • other memories it was derived from, its premises.

When a cited file no longer contains its quoted passage, or a premise changes or is forgotten, the memory becomes stale, and so does every memory derived from it. Restoring the passage makes them current again. Quotes are compared ignoring whitespace, so reformatting a file leaves its memories alone. Your words and web pages are provenance: they are never refetched, and once a memory cites files, those files decide whether it is current. A memory whose only cited files cannot be read is unverified. Status is worked out when a memory is listed or recalled, from the files as they are then; checking makes no model requests.

(memories)            ; relevant memories, stale ones first
(memories "deploy")   ; only those matching a query
(memory "3afbfa1a")   ; one memory, its evidence, and what relies on it
(forget "3afbfa1a")   ; stop recalling it

In fullscreen, open the memory manager with (memories). Browse notes and facts side by side with their evidence; on a narrow terminal, press Enter to read the selected item. Use / to search titles, bodies, tags, IDs, and evidence, Tab to choose All/Notes/Facts, and Shift-Tab to choose a scope. Scroll an open explanation with the arrow keys or Page Up/Down. Press Esc to return to the list, then Esc again to close.

Press n to create a note, e to edit one, d to forget a note or retract an asserted fact, and r to reload. Confirm deletion with y. In the note form, use Tab to move between title, tags, scope, and content; Space to choose a scope; Shift-Enter for a content newline; and Enter to save. A changed or deleted note must be reloaded before editing. Editing prose preserves its recorded evidence and premise versions. Inspect derived facts to find their supporting premises and rules. (memory "ID") opens the corresponding explanation directly.

In inline or noninteractive sessions, these commands print textual listings and explanations.

Any unique prefix of a memory ID selects it. (forget) names the memories that relied on the forgotten one, which are now stale. Recall marks stale memories for the model, which rechecks them before relying on them and replaces or forgets them, and the recall notice counts them: @ 3 relevant memories exist · 1 stale.

The model records evidence through the memory: resources. memory-remember and memory-replace accept sources, objects naming one file, url, or user with an optional quote, and premises, memory IDs. A file quote must occur in the file and a user quote in your messages in this conversation when the memory is written. Replacing a memory without sources or premises keeps the recorded versions, so restating or retagging a stale memory leaves it stale; supplying them again rebinds it to the evidence as it is now. Workspace memories record files inside the workspace relatively, so they survive moving or importing the workspace, and a workspace export includes the global memories its memories rely on.

Facts and rules

Besides prose memories, Autolith keeps structured facts: a predicate applied to typed values, such as event.start review 2026-10-22T13:00:00Z. Each fact cites its evidence, like a memory, and says why it is believed: an observation of a file, page, tool, or mission, a user assertion, an assumption, or the model’s own proposal. Where a fact came from decides its basis, not what the model calls it: a file Autolith read is an observation, your own words a user assertion, and a web page an observation once web_extra.gist retrieves it with the quoted text. The retrieval receipt identifies the retrieved content, the bounded tool preview, and the original observation time. Quotations can cite the complete retained overflow object; if overflow storage is unavailable, only text in the retrieved prefix counts. The omission notice is presentation, not source evidence. A fact stated by two sources has two supports and holds while either does. Web pages expire a week after retrieval and tool output after a day; fetch the source again to renew its evidence. Your statements and recorded runs do not expire.

A structured fact’s file evidence applies only to the exact observed content, even when its quotation survives an edit elsewhere in the file. Review the new revision and observe it again before using it as current evidence. The original quotation and digest are kept for historical inspection.

Rules derive facts from facts, with dates, durations, numbers, and quantities in units:

;; departure = start - (travel + buffer)
(:premises (("event.start" "?event" "?start")
            ("event.travel" "?event" "?travel")
            ("event.buffer" "?event" "?buffer"))
 :bindings (("?leave" ("-" "?start" ("+" "?travel" "?buffer"))))
 :conclusion ("event.departure" "?event" "?leave"))

When a premise changes, Autolith recomputes the conclusion locally, without a model request: move the meeting and the departure moves with it. A derived fact keeps the basis of its premises, so deriving from an assumption never yields an observation. A negated premise matches only a fact explicitly stated not to hold; a missing fact never satisfies it. Two facts that disagree about one keyed value, or a fact and its negation, are conflicted: both are kept and shown, and nothing picks a winner. Rules cannot depend on their own conclusions, and an evaluation that reaches its work limit reports the remaining conclusions as unknown rather than false.

A rule the model proposes derives nothing until it is admitted. Admission uses the ordinary command approval: with full access it is admitted at once, otherwise you are asked to allow rule.admit -- departure v1: ....

(memories)            ; memories, then facts: conflicted and no longer holding first
(why "c4dfffae")      ; why a fact holds: its sources, rule, and premises
(what-changed)        ; facts that changed since you last asked, with the cause
(rules)               ; rules, their versions, and whether they are admitted
(admit-rule "departure")
(retire-rule "departure")
(forget "c4dfffae")   ; retract a fact; derived facts that relied on it go too

(why "ID") is (memory "ID"), and accepts a memory or a fact. Recall offers current facts related to your message to the model complete, so it can use them without rereading, and counts them in the notice: @ 2 relevant memories exist · 3 facts. When a fact the model was given earlier in a conversation changes, the next turn explains the change to it once, and @ 1 fact given earlier changed appears. A memory can name a fact as a premise and goes stale when the fact stops holding; a fact can cite a memory as evidence.

The model reads memory:facts, memory:claim/ID, memory:predicates, memory:rules, memory:procedures, and memory:changes, and edits memory:facts with predicate-define, fact-assert, fact-retract, and rule-propose, up to 16 per edit. Mission status and gate results are recorded as autolith.mission.* facts.

Starter recipes give common work a ready vocabulary. memory:recipes lists them: appointments derives when to leave from an event’s start, travel, and buffer, and service-dependencies marks a service at risk while something it depends on is down. A recipe-use operation on memory:facts defines a recipe’s predicates and proposes its rules, which are admitted like any other.

The knowledge-mode setting is active by default. passive keeps recording observations such as mission results but offers nothing to the model, and off records nothing; in both, model requests are exactly as without structured knowledge. Change it in (settings) or with AUTOLITH_KNOWLEDGE.

Knowledge is evaluated locally when it is listed or recalled, and evaluated again only after a cited file, the knowledge, or a validity period changes. Each request where knowledge took part records its share, so its cost can be measured:

(knowledge-usage-report (application-conversation *active-application*))

Facts, rules, and predicates travel with memories in data exports; a relocated workspace keeps its facts.

Plans, methods, gates, and contracts

A plan step can name the facts it requires. Its text takes their values, so Leave at {?leave} requiring event.departure review ?leave reads Leave at 12:20 and follows the departure when the meeting moves, without replanning. A step whose requirements nothing satisfies is shown blocked with the missing pattern. A step marked done records the facts it was done with; if they later stop holding, the plan says so instead of repeating the step. When such a change happens between turns, the model is told once at its next turn and @ 1 plan step changed with its facts appears.

A method says how to do a task when facts allow it: its conditions, then subtasks and plain steps in order. plan.expand turns a task such as prepare review into a checked plan locally, trying methods in order and expanding subtasks within a depth of 8 and 1,000 tasks. It reports a task no method describes, methods whose conditions do not hold (naming them), and a search that ran out of budget, separately, and leaves the plan unchanged then.

A mission gate of kind :fact passes when a fact matching its pattern holds on accepted evidence, observations and your statements unless it says otherwise:

(:id "departure-known" :kind :fact :pattern ("event.departure" "review" "?t"))

A model’s predictions never satisfy it, nor does a fact that another accepted fact disputes, and a derived fact counts only if everything it rests on, negated premises included, is accepted. A negated pattern such as (:not ("item.blocked" "a")) passes on an accepted explicit negation. A gate passed on facts that stop holding before acceptance is checked again.

An action contract describes a tool call: argument values it applies to, facts it requires, facts it is expected to cause, and facts to read from its arguments and JSON result, such as $arg.to and $result.id. A call whose requirements do not hold is refused before it runs, with the reason; like a gate, a requirement holds only on undisputed observations or your statements, unless the contract’s accepts also names model-proposal so that an earlier call’s predicted effects count. A successful call records its expected effects as predictions and its observed values as observations of that call, which is how structured tool output, MCP results included, becomes facts. The model defines methods with method-define and proposes contracts with contract-define on memory:facts. For each new contract version, approve the contract.admit request containing its complete definition, including observation mappings and accepts. Declining leaves that version inactive and any previously admitted version active. Any edit requires fresh admission; proposing the same admitted content again does not. Read proposed and active versions from memory:contracts, and methods from memory:methods.

Contract admissions are persisted for the exact definition and version. Older contract records without admission provenance are inactive. Exports retain admission metadata for provenance; imported contracts require local admission. Trusted Lisp callers of knowledge-define-contract admit their definitions by default; pass :admit nil to propose one for separate authorization.

Requirements may bind variables used by effects and observations. A call needs one distinct binding environment; ambiguous bindings or an incomplete search refuse it before execution. Evidence records retain the supporting claim IDs. If a successful call’s observation mapping fails, its plain result reports externalSuccess: true, a bounded externalResult excerpt, and structured contractErrors with contractRecording: "failed". Rich results include a bounded diagnostic after their ordered content. Durable details retain the recording diagnostic and subtype metadata; clipped MCP sections include their original metadata as provenance.

Learned procedures

When three turns in a workspace make the same tool calls in the same order, Autolith learns a procedure from them after the turn, locally and without a model request: arguments that never change stay as they were, a value taken from an earlier call’s JSON result is taken from it again, and values that vary become parameters, matched with the words that varied in your requests. A procedure is kept only if each of those turns is reproduced by the procedure learned from the others. If another recent request worded the same way did something else, the wording does not show when the procedure applies, so it is never offered, though it can still be run. A new procedure shows @ learned procedure make-sales-report from 3 turns · (procedures).

When a later request fits, such as Make the sales report for q5., the model is offered the one procedure.run call that makes the same calls with q5, and @ learned procedure make-sales-report fits this request appears. The first run asks to admit the procedure (procedure.admit -- make-sales-report v1: sales.load, sales.report, notify.send); full access admits it at once. Automatic execution requires completed, successful model turns running the exact admitted procedure version and content, with complete usage measurements showing both fewer model requests and fewer tokens than its learning baseline. A request matching its wording exactly, apart from its parameters, then runs it before the model’s first request, shown as its procedure.run call, and the model answers from the results in one request. A wording learned with a word that varied, such as Please or Kindly, matches any word there, so it stays an offer the model may decline. A request worded differently, such as Make the sales forecast for q9., matches no pattern, but when it shares at least two words with a procedure, up to three such procedures are listed as possible fits with their parameters to fill in, and @ learned procedure make-sales-report may fit this request appears; the model decides, and nothing runs on its own. Procedures are learned only from turns the model finished: every call succeeded, the model answered, and the turn was not cancelled.

Every call still asks its own approval and passes its contracts. A run whose contract requirements do not hold for the request’s values is refused before any call and is not offered; a call needing something an earlier call builds is checked when it is made. If a call fails, the run stops and the model continues the work with ordinary calls; two failed runs in a row suspend the procedure until you admit it again.

(procedures)          ; procedures, their calls, status, and what they saved
(run-procedure "make-sales-report" :file "q5")   ; without the model
(admit-procedure "make-sales-report")             ; allow it, or lift a suspension
(retire-procedure "make-sales-report")            ; never run or learn it again

(procedures) compares the turns a procedure was learned from with recent turns that ran it, in model requests and tokens. The model reads memory:procedures. Procedures are kept in the knowledge log, with every run, and are learned and offered only while knowledge-mode is active.

Diagnosis

With Diagnostic probes on, as they are by default (AUTOLITH_DIAGNOSIS=0 or (settings) turns them off), and structured knowledge active, the model can find out why something fails before repairing it, instead of trying repairs in turn. diagnosis.run takes up to four hypotheses, each predicting facts, and probes: tool calls whose contracts observe facts from their results. Facts already known are checked first; then the probe telling apart the most remaining hypotheses per cost runs, at most three, with no model request between them. A hypothesis is ruled out only by an undisputed observation or statement contradicting one of its predictions, so a case no probe can settle is reported as still open rather than guessed. A diagnosis can be saved by name and run again with new values.

Diagnosis remote-service: stale-endpoint. Repair: Update its endpoint.
probe 1: service.ping {"service":"billing"} ok → service.reachable(billing, true)
probe 2: service.config {"service":"billing"} ok → endpoint.current(billing, false)
ruled out unavailable: service.reachable(billing, true)
ruled out credentials: endpoint.current(billing, false)

(diagnoses) lists saved diagnoses and recent cases: the cause found, its repair, and the facts that ruled out the others.

Exporting and importing data

Use the same portable archive from the command line or an active session:

autolith data export all.sexp --all
autolith data export project.sexp --workspace /home/al/code/project
autolith data import project.sexp --workspace /home/al/src/project
(data.export #P"all.sexp" :all t)
(data.export "project.sexp" :workspace "/home/al/code/project")
(data.import "project.sexp" :workspace "/home/al/src/project")

Export defaults to all workspaces and global data. A workspace export contains its conversations, memories, agendas, plans, papercuts, and associated session assets. The archive contains Autolith data rather than the repository files; clone or copy the repository separately. Credentials, configuration, executable image state, and caches are excluded.

Import preserves stable identifiers and merges into local data. Identical data is skipped; a conflicting identity aborts the import before publication. For a single-workspace archive, --workspace DIRECTORY or :workspace "DIRECTORY" sets its new home directory. Without that option, workspace locations are preserved. Relative paths are resolved against the current working directory.

Export creates a new private archive and refuses to overwrite an existing file. The CLI exits without starting a session: success is 0, invalid arguments are 64, and transfer failures are 1. Both command groups provide --help.

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), (titles t) or (titles nil) to inspect or change the durable global preference.

(resume) displays titles in its picker while an explicit (resume "ID") continues 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.

A resumed conversation runs in a fresh process, so background jobs, asynchronous shell runs, and Lisp worker state from before the restart no longer exist. The first user turn after a resume carries a request-local notice telling the model so, which keeps it from polling or waiting on dead jobs. Only durable child task results stay readable through job.get.

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. Its session ID is the active conversation ID, shared with resume and stable across detach, restart and checkpoint quiescence. Selecting another conversation with new, resume or fork moves the endpoint to that conversation ID while attached terminals continue using the same connection. Inspect endpoints with:

autolith localgroup status
autolith lg status --sexp

idle means that the process is at input with no active work of any kind.

Control a session with the following commands:

autolith lg tell   "message"
autolith lg tell   SESSION-ID "message"
autolith lg pause  [SESSION-ID]
autolith lg attach [SESSION-ID]
autolith lg attach [SESSION-ID] --read-only
autolith lg attach [SESSION-ID] --take-over
autolith lg detach [SESSION-ID]
autolith lg kill   [SESSION-ID]

lg is an alias for localgroup. When exactly one endpoint is reachable, these commands select it when SESSION-ID is omitted. With multiple sessions, provide a full ID or a prefix that matches one session. Prefixes accept IDs with or without the display hyphen, for example b, b-6, or b6 for b-6uouHJ. Full IDs take priority over prefix matches. If a prefix matches more than one session, the error lists the matching IDs; use a longer prefix. Current IDs are case-sensitive. Legacy hexadecimal IDs accept either case.

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.

Detaching hands a session to a supervised process group that outlives the terminal. Windows has no process groups or detached sessions, so sessions there always run directly in the terminal that started them: (detach) and client-first starts report that the handoff is withheld, while the loopback endpoint and the localgroup commands work as elsewhere.

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.

On Windows the same four roots are, unless an absolute XDG variable overrides one, %APPDATA%\autolith\, %LOCALAPPDATA%\autolith\data\, %LOCALAPPDATA%\autolith\state\, and %LOCALAPPDATA%\autolith\cache\. Private files and directories carry an access control list granting only the owner and SYSTEM, which is what mode 0600 and 0700 mean there; a file Autolith publishes read-only withholds write access through that list and keeps the read-only attribute clear, so it can still be replaced and deleted.

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 the optional user file ${XDG_CONFIG_HOME:-~/.config}/autolith/init.lisp after tracked code, a selected private image commit, and any site or trusted directory configuration. This order also applies to autolith models and autolith auth. Use --pristine to skip private replay and executable initialization. The user file 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.

Packagers may supply one separate, read-only configuration tree with --site-config-root DIRECTORY or AUTOLITH_SITE_CONFIG_ROOT. The directory must already exist and be absolute. It may contain init.lisp, mcp.sexp, skills/, and agents/. Site configuration loads before user configuration, so it provides defaults without replacing the user’s normal XDG tree. A site init.lisp has the same full privileges as user initialization and is not a security boundary.

Executable initialization loads in this order:

  1. site init.lisp
  2. trusted directory .autolith/init.lisp files, outermost to nearest
  3. the user XDG init.lisp

Missing files are ordinary. A present file that cannot be read or evaluated fails startup or reload with its exact pathname. directory-scopes.sexp is user-owned.

Settings

Every knob Autolith exposes is a named setting with a type, a description, a default, and, where the choices are finite, its options. (settings) opens the settings page: a picker listing each setting with its current value and where that value came from. Choosing an adjustable setting opens its options; choosing a read-only one explains why it cannot change in a running session. The page returns to the list after every change until you cancel.

The same operations work without the page. (settings :boot-screen-seconds) describes one setting, and (settings :boot-screen-seconds 2) changes it. Use native values: t and nil for switches, integers for counts, and strings or pathnames for paths. Omit the value to inspect a setting; an explicit nil sets its value. A change to a durable setting is saved at once for future sessions. In a script, (settings) prints every setting grouped by area.

Settings live in one of four scopes. Durable settings, such as the model, reasoning effort, Fast mode, reasoning traces, compact view, timestamps, the transcript style, the activity line, cache miss notices, Simple Technical English, generated titles, fullscreen, and the saved permission mode, persist in preferences.sexp under the state directory. Session settings, such as hurry-up mode, last until exit. Process settings, such as the immutable flag, the management endpoint, and the roots, come from the command line, the environment, or defaults at startup and stay fixed. Derived settings, such as the context window and provider endpoint, follow the model. The window uses a declared model value, else the catalog value, else the default.

Enable the session’s private context-pressure reminder with (relax t), or (settings :relax-p t). Use (relax nil) to disable it and (relax) to inspect its state.

The private system-prompt reminder warns that the agent’s session, context and token estimates are unreliable and often too small. Automatic compaction is available; perceived length is no reason to shrink scope, skip verification, rush, or end early. The reminder is rebuilt after compaction, outside conversation turns. The setting lasts until exit and defaults to nil.

A durable value is chosen at startup from the first source that supplies it: an explicit command-line choice, the setting’s environment variable, then the saved file. A saved model or effort that no registered provider serves is dropped rather than applied.

From Lisp, user initialization and replay scripts read a setting with (config :model), which uses the current configuration, or (config :model configuration) for an explicit one, and change it with (setf (config :reasoning-traces-p) t). Unknown setting names signal a configuration error. Create an independent configuration with configuration-copy.

The preferences file uses version 8: one flat record of setting names and values that keeps unknown keys. Before upgrading a preferences file from versions 1 through 7 to 0.54 or later, run a 0.53 release once to write version 8.

Language servers

Install your language server, then configure it in lsp.sexp under Autolith’s user configuration directory, alongside init.lisp. For example:

(:version 1
 :servers ((:name "clangd"
            :command "clangd"
            :arguments ("--background-index")
            :extensions (".c" ".h")
            :language-id "c"
            :root-markers ("compile_commands.json" ".git")
            :timeout-seconds 30)))

Configuration is declarative. Supply a unique :name, executable :command, nonempty :extensions list, and :language-id for each entry. Arguments are literal strings. Optional :initialization-options and :settings are JSON object strings; :disabled-p t disables an entry. The request timeout is an integer from 1 through 120 seconds, defaulting to 30.

Only configure commands you trust: language servers run with your privileges and inherited environment, outside the shell approval sandbox. Keep credentials out of this file. Executables must already be installed; commands are resolved on PATH. Repository-local LSP configuration is not loaded.

Autolith exposes the lsp tools only when lsp.sexp defines at least one enabled server; without it the registry omits them. A malformed lsp.sexp keeps the tools registered so the configuration error surfaces when a tool runs. When at least one server is enabled, each provider request includes a context note naming those servers.

Ask Autolith to use these tools:

  • lsp.status: inspect configuration and running project/server pairs.
  • lsp.query: request definition, references, hover, implementation, type-definition, document-symbols, or workspace-symbols for a workspace file. Position queries require one-based line and character coordinates, with columns measured in UTF-16 code units. For workspace symbols, supply query; the file selects the project and server.
  • lsp.diagnostics: synchronize a file and retrieve diagnostics.
  • lsp.restart: stop servers and reload the configuration after changing it.

Use lsp.semantic for revisioned transformations:

  • prepare-rename: inspect the symbol at path, line and character.
  • rename: propose a symbol rename with those coordinates and new-name.
  • code-actions: discover choices for a range, using line, character, end-line and end-character. Optionally filter only action kinds or supply diagnostics.
  • resolve: retrieve a complete action using its returned choice ID.
  • propose: retain an action’s edit as an applicable proposal using choice.
  • move: propose server preparatory edits followed by moving path to new-path. Both paths must identify regular files or a missing destination.
  • inspect: review a retained proposal ID.
  • apply: apply a retained proposal. Obtain explicit approval for annotations with needsConfirmation, then include their IDs in approved-annotations. Confirm each synthetic lsp.semantic-confirmation request through the ordinary command-permission prompt. Without a permission callback, confirmation is denied.

Proposals preserve ordered text, create, move and delete effects. Every affected file passes the ordinary resource authority checks before observation and again before application. Resource revisions cover file kind and content; synchronized document versions must also match. Request a fresh proposal after a conflicting edit or server restart. Moves preserve the source permissions captured during staging, including edited multi-hop moves. Publication is atomic per file with change-set rollback on failure. Proposals and action choices own their exact snapshots and are conversation-local. Retain up to 32 choices, 240,000 encoded characters per choice, 128 resources per choice, and 512 resources or 16 MiB of UTF-8 snapshots per conversation. Older choices expire when those budgets fill. Server commands are presented as data for separate explicit execution. Use local file:/// URIs; directories, service resources and URI authorities are unsupported.

Servers start lazily on file requests, including diagnostic requests after a successful resource.edit workspace edit. All enabled entries with matching filename suffixes are queried. The project root is the nearest ancestor with a configured root marker, bounded by the workspace; otherwise it is the workspace. Open files are refreshed from disk before queries, including shell edits.

Results contain native file URIs and zero-based UTF-16 ranges. Push and pull reports are retained independently and combined, removing duplicate items. Diagnostic state received identifies versioned reports, unversioned means a report’s snapshot cannot be confirmed, and pending means no report arrived. The sources field gives push and pull freshness separately.

Explicit diagnostics allow two seconds for push reports and use the configured request timeout for pull requests. Edit follow-ups have one shared one-second blocking deadline across lock acquisition, startup, synchronization, and all matching servers, with at most half a second for push settling. Failed startup also reaps its process. If automatic diagnostics time out, use lsp.diagnostics for the full server timeout. Reports retain at most 200 diagnostics per file and mark truncation. Files are limited to 2 MiB, with 64 open documents per server and 16 live project/server pairs per registry. Use lsp.restart to release them. Server shutdown occurs when the tool runtime is retired.

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 is image-daemon’s evaluation endpoint, whose eval-connect and eval-call form a complete Lisp client, and 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_TRANSPORTunix, or tcp on Windowsunix 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. Windows has no filesystem sockets, so unix is refused there with a message and tcp is the default.

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 management-repl-error whose operation is :quiesce 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.

Register selective project or user advice as a declarative rule:

(register-context-rule
 '(:id "lisp-edits"
   :when (:paths ("src/") :languages ("common-lisp")
          :events ("tool-success"))
   :instruction "Check the changed declarations before committing."
   :priority 30 :lifetime :while-relevant))

Use :paths, :languages, :tools, :namespaces, :roles, :diagnostics, :events and :output-contains in :when. Fields are conjunctive; values within a field are alternatives. Paths match prefixes; :output-contains matches literal case-insensitive substrings of recent completed assistant text and generated tool arguments. Other values match exactly. Add an absolute :workspace directory for project scope. Authorized tool operations and structured LSP severities supply bounded current-turn metadata. Asynchronous completions use their originating turn identity. Rules share the ordinary advice budget, priority, lifetime and deduplication policy. Inspect delivery with context-status; remove a registered rule with unregister-context-rule and its original ID.

To turn a complaint into a persistent correction, use:

(complain "Your explanations are too long. Give the result first.")
(wtf "Use the existing library instead of reimplementing it.")

Both names open the same interactive review. Inspect the generated instruction and literal trigger, amend the candidate, then choose workspace or global scope to save it. Saving an unmatched trigger and replacing an existing rule each require separate confirmation. Cancel to discard the candidate. Omit the text to enter a complaint in the editor.

Generation uses bounded recent assistant output and tool arguments as evidence. On later provider requests, matching corrections enter the ordinary request-local advice budget. Inspect them with (context). Global corrections are stored in data-root/context-rules.sexp; workspace collections live under data-root/context-rules/, keyed by the canonical workspace directory. A same-ID workspace correction overrides the global one in that workspace. Saves take effect on the next request, including in other sessions using the same data root. Edit or remove the data files to change saved collections.

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 the GPT-6.1, GPT-6, and GPT-5.6 models, which compact at about 218K tokens by default:

ctx [==========|..] 218K / 272K used

Before provider usage is available, ~ marks an estimate from the known system prompt, session state, retained conversation, and tool schemas. The estimate uses four characters per token. After compaction, the meter estimates the replacement context until the next provider report. Compaction request usage is recorded separately as work spent on the checkpoint and summary.

At narrow widths, the meter keeps the used and window counts and drops the bar.

Use (compact) to compact the current conversation immediately, regardless of the meter. It uses the same provider-native checkpoint and portable summary path as automatic compaction. During an active turn, the command waits in the idle queue. An empty conversation needs no provider request. Compaction adds a durable checkpoint without adding a user message or starting a model turn. The native checkpoint and the portable summary are two provider requests. Each is recorded with its usage as a numbered request of the turn, so the request count and the persisted usage cover compaction work instead of hiding it.

Before either request, Autolith captures a fixed history cutoff. The checkpoint includes unresolved calls with correlated actual or interrupted/unknown outputs, plus results and steering received during summarization. Running jobs and undelivered outcomes are recorded separately from the model-written summary. Ordinary requests include bounded authoritative job references and an omitted count; inspect job.continuity, job.* and their resources for full details. Provider-family changes use the portable handoff and exclude foreign private context.

Set ANALYTICS_EVENTS_FILE to export completed streaming calls as UTF-8 JSONL usage events. ANALYTICS_ACTOR overrides the operating-system username. Each schema-v1 llm-usage event contains the UTC timestamp, actor, model, request count, elapsed seconds, and known input, output, and cached token counts. Unknown token counts are omitted. Export is disabled without a destination. Appends are synchronous and serialized across processes; export errors do not abort a completed provider call.

Mechanical precompaction removes failed tool exchanges from earlier turns before the first request after resume, a provider/model change, or estimated prompt-cache expiry. Calls, correlated outputs and reasoning used only for those calls are removed together. Successful exchanges and failures in the latest user turn are retained. Pruning changes provider context, with every record available in the durable transcript. It runs before ordinary requests and native or portable compaction. Idle pruning uses the same retention estimate described below; providers without an estimate prune only on resume or provider/model changes.

Idle cache compaction is enabled by default. After a completed request with at least 32,768 prompt tokens and 131,072 characters of summarizable history, an idle session can compact two minutes before its estimated cache expiry. The timer starts when the request starts, not when its response finishes. User input, drafts, active work and shutdown take priority. Each opportunity is used once, including after failure. Resumed sessions need a new completed request before scheduling one.

Use (settings :idle-compaction-p nil) to disable it. The ChatGPT subscription route uses a conservative five-minute inactivity estimate. Other providers need an explicit estimate, for example (settings :prompt-cache-retention-seconds 1800) for thirty minutes. Set it to zero to use the provider estimate. Both settings persist across restarts.

Cache retention depends on the provider, model, routing and account policy. The OpenAI API cache documentation describes a thirty-minute default for GPT-5.6 and later; it does not establish a retention contract for the ChatGPT subscription route. Idle compaction uses the ordinary paid checkpoint and summary path. It can reduce the context sent after a long pause, but neither a cache hit nor a net saving is guaranteed.

(cache-misses t) enables notices when cached reads fall by at least 128 tokens, including accumulated smaller losses, or remain flat while shared request history grows by at least 16,384 characters and one sixteenth of its reference size. Temporary context and newly offered history are excluded from shared growth. A cache miss reports measured lost reads; a stall reports shared-history growth in characters because its token count is unknown. Both show cached and total input tokens. Notices start with a red !. Model changes, idle intervals and smaller prompts are local observations, not proof of eviction or routing failure. The first request after resuming or compacting establishes a fresh baseline. Without comparable read counters or prefix measurements, no loss is inferred from total input. The setting is off by default and persists across restarts.

In Responses requests, skill catalogs and selected skill instructions keep their first surviving history position instead of moving after each new message. Autolith reads the current skill contents and checks activation on every request. Selected instructions expire with the logical turn; only the small selection call and result enter durable history. Mutable session state and other ordinary request context follow history. After compaction or removed history boundaries, Autolith places active skill context after the new history. Use (context) to inspect contribution sizes. Provider cache hits and savings depend on the provider and are not guaranteed.

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

List every model ID known to Autolith without authenticating or starting a session:

autolith models | grep gpt

The command prints one sorted, deduplicated model ID per line from built-in and locally configured catalogs.

Inside a session, (auth) without a provider picks one interactively, and (auth "my-provider") selects one explicitly. 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.

Select a model with (model "glm-5.3-flash" :provider "neuralwatt") or (model "neuralwatt/glm-5.3-flash"). Use (models :provider "neuralwatt") to refresh and list one provider, or (model nil :provider "neuralwatt") to filter the interactive picker. The picker and listings show the provider with each model, including identical IDs offered by different providers.

Bare model IDs use the existing registration precedence. Only a registered provider name introduces a provider prefix; catalog IDs containing slashes, such as hf:zai-org/GLM-5.3-Flash, are matched intact. If a catalog ID also looks like another provider’s full ID, specify :provider to select its exact catalog ID. An explicit provider and a conflicting qualified prefix signal a selection error. The selected provider is saved with the model in preferences and conversation history, so reopening a session uses the same registration.

Select a model for one launch with --model MODEL. Use a provider-qualified ID, or pass --provider PROVIDER together with --model for an exact catalog ID. These options work before or after run-job and resume:

autolith --model my-provider/my-model
autolith --provider my-provider --model hf:custom/model
autolith resume --model my-provider/my-model
autolith run-job --model my-provider/my-model --input job.sexp --output result.sexp

The launch selection takes precedence over environment variables, saved preferences, and a resumed conversation’s model. It does not save a new default; use (model ...) to save a selection. Autolith loads executable initialization and the local provider catalog before validating the requested model, so providers registered in init.lisp are available. Startup does not fetch a remote catalog; use autolith models to list locally known IDs and (models) to refresh them.

The built-in model picker uses https://models.dev/api.json for ChatGPT, Gemini, Grok, Anthropic, Fireworks, OpenCode, OpenRouter, and Mistral metadata. An explicit (models) refresh downloads the public catalog once without provider credentials, selects tool-capable text models, and updates descriptions, context windows, and reasoning efforts. Startup reads the last-good local cache without a network request. Failed refreshes retain cached models; built-in seed metadata supports a first offline start. Custom provider registrations and overridden discovery endpoints retain their own discovery. Copilot retains its account-specific catalog because it also supplies authorized protocol routes; Nous retains its account catalog because models.dev has no corresponding source. ChatGPT, Gemini, and Grok subscription adapters update metadata only for the model IDs their transports support; public API availability alone does not establish subscription access. Catalog refresh for the picker works independently of telemetry being enabled.

Built-in ChatGPT subscriptions use browser OAuth with PKCE and a local callback on localhost:1455 or localhost:1457 by default. Use (auth "chatgpt" :device-code) or autolith auth chatgpt device to select the device-code flow instead. The browser authorization URL is always printed for manual use. Grok and Nous Research use browser device flows. Gemini uses Google installed-application OAuth with a local loopback callback. Anthropic, Fireworks, OpenCode, OpenRouter, Mistral, OpenAI API, and user-registered OpenAI-compatible providers use API keys.

For the official OpenAI API, choose openai-api during onboarding, run autolith auth openai-api, or use (auth "openai-api"). Paste your API key at the hidden prompt. Autolith stores it in its private credential store and loads the models available to that key from https://api.openai.com/v1/models. Requests use https://api.openai.com/v1/responses. No init.lisp registration is needed. The chatgpt provider uses a ChatGPT subscription instead.

Authenticate a GitHub Copilot subscription with autolith auth copilot or (auth "copilot"). Approve the displayed device code at GitHub. Credentials are stored in the private copilot-auth.sexp under the state root. Tokens renew automatically before expiry.

Login discovers tool-capable picker models and copilot/auto, and attempts to enable unconfigured picker models. Models are named copilot/<model-id> locally to avoid collisions with direct vendor subscriptions. Successful Copilot login selects Auto and saves it as the default, unless the current Copilot model is still available. Use (models) to refresh the catalog, then (model "copilot/auto") to select Auto on an existing installation, or launch with --model copilot/auto. Normal startup reads the last successful catalog without a network request.

Use Auto with Copilot Student or Free plans, which do not offer manual model selection. Each request resolves an account-authorized concrete model and uses its Chat Completions, Responses, or Messages protocol with a short-lived session token. Auto keeps the previous concrete model while it is available. Hidden named models are not offered as manual choices. For other plans, select an available named model with (model). Disabled picker models are excluded.

For GitHub Enterprise Cloud with data residency, set AUTOLITH_COPILOT_DOMAIN to your TENANT.ghe.com hostname during both login and subsequent launches. Tokens are pinned to that host and streams use the subscription-specific endpoint returned by Copilot. Chat Completions, Responses, and Anthropic Messages reuse the existing transports. This integration uses GitHub’s internal Copilot endpoints, following pi and VS Code, so it may require updates when GitHub changes that service.

ChatGPT models use Codex’s default context windows, currently 272,000 tokens, instead of public OpenAI API limits from models.dev. In (settings), toggle prefer-codex-context-window-p off to use public catalog limits, or evaluate (setf (config :prefer-codex-context-window-p) nil). This saved choice applies only to the built-in ChatGPT provider. Set AUTOLITH_PREFER_CODEX_CONTEXT_WINDOW=off to override it for one process. AUTOLITH_CONTEXT_WINDOW takes precedence over both sources. Compaction uses compaction-threshold-percent of the selected window, 80% by default.

ChatGPT Codex Fast mode sends service_tier=priority when the active Codex model advertises Fast support. Fast responses count more heavily against plan usage; the exact rate is set by OpenAI per model and is not tracked here. The built-in GPT-6.1, GPT-6, and GPT-5.6 models support it. Use (fast) to inspect it, and (fast t) or (fast nil) 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 t) and (fast nil) are unavailable.

A request that fails before model generation begins is retried up to six times with jittered delays. Once reasoning or output has streamed, or the provider reports token exhaustion (length, max_output_tokens, or max_tokens), at most two retries are made: three generation attempts including the original. Token exhaustion before visible text uses the same limit. Set autolith::*provider-maximum-streaming-retries* in init.lisp to change this retry count; zero disables these retries. Every failed attempt is recorded next to its request number with the attempt count, elapsed seconds, whether output was received, and provider identifiers.

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 declares a client-executed tool_search. When the model searches, Autolith answers from its own registry over the namespaces that request advertised, scoring namespace names, tool names, and description words, and appends the tool_search_output as a durable provider item. The expansion replays intact in every later request, which keeps those tools loaded and the prompt prefix cacheable; only native compaction requests replay it empty. Other models and providers receive the complete eager tool array.

Authenticate Gemini with autolith 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.

Use vertex-gemini or vertex-anthropic for paid Google Vertex AI access. Create local application default credentials with gcloud:

gcloud auth application-default login
export GOOGLE_CLOUD_PROJECT=your-project-id
export GOOGLE_CLOUD_LOCATION=global

autolith --model vertex-gemini/gemini-3.5-flash
# Or:
autolith --model vertex-anthropic/claude-sonnet-4-6

Enable billing and the Vertex AI API for that project. Your account needs aiplatform.endpoints.predict permission, such as the Vertex AI User role. For Claude, enable the model in Model Garden. Choose a location where the model is available; global is the default.

Autolith reads local authorized_user ADC files. It checks GOOGLE_APPLICATION_CREDENTIALS first, then CLOUDSDK_CONFIG, then gcloud’s usual config directory. Run gcloud auth application-default login even if you have already run gcloud auth login. Service-account, federation, and metadata-server credentials are not supported by this adapter.

Each request exchanges a fresh access token. Autolith does not copy or write ADC credentials. Use GOOGLE_CLOUD_QUOTA_PROJECT to override the ADC quota project. The settings :vertex-project, :vertex-location, and :vertex-quota-project provide the same routing choices in init.lisp.

Run autolith auth vertex-gemini or autolith auth vertex-anthropic to check the local ADC token exchange. This does not check model access. Send a prompt to test project permissions, billing, location, and model access.

See Google’s ADC guide and Anthropic’s Vertex guide for cloud setup.

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 come from models.dev and support tool calls and text output. 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 come from models.dev and use openrouter/<vendor>/<model> identifiers locally. The picker includes models that emit text and advertise tool calls. OPENROUTER_API_KEY takes precedence over Autolith’s private OpenRouter key. Authenticate with autolith auth openrouter or (auth "openrouter"). AUTOLITH_OPENROUTER_PROVIDER_ENDPOINT and AUTOLITH_OPENROUTER_MODELS_ENDPOINT override the chat and discovery endpoints independently.

Set :openrouter-provider-routing to a JSON map of wire model names to OpenRouter provider preference objects. Omit the local openrouter/ prefix from model keys. Use * for models without their own entry. For example:

(setf (config :openrouter-provider-routing)
      "{\"deepseek/deepseek-v4.1-flash\":{\"order\":[\"baseten\",\"together\"],\"allow_fallbacks\":true}}")

You can also supply this JSON through AUTOLITH_OPENROUTER_PROVIDER_ROUTING. Supply an empty string to use OpenRouter’s normal routing. Provider preference fields are sent as supplied, including order, allow_fallbacks, only, ignore, sort, and max_price.

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)
  • Callable handlers accept ordinary Lisp lambda lists, including &key and supplied-p parameters. Lisp argument values reach the handler directly.
  • Use :slash-parser function-name for commands with structured or action arguments. Define the function before the command. It receives the complete slash remainder and returns a list of native handler arguments. Parser and handler functions are captured when you register the command.
  • 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. It declares which invocations open a modal picker so active work keeps them off the immediate reader path. A modal picker requires exclusive terminal input when open. The responsive reader continues animating until then.
  • :static-options replaces :argument for one finite positional parameter. Supply native strings, keywords, or booleans to derive the help hint and completions. Use (t nil) for boolean controls. Define a slash parser to translate text choices to native values.

Live definitions

Use self.redefine to install loaded Lisp definitions, new or replacing, including functions and methods outside the Autolith package. self.define names the same tool. Qualify the target symbol in the source, or supply the package argument to select its reader package. Use qualified symbols with self.set for foreign global bindings.

Installation, discard and replay temporarily unlock the reader and target packages, then restore their locks. New definition records include the target’s owning package separately from the reader package. Replay skips stale definitions whose ownership moved to another library. Reapply an old skipped definition to record an intentional replacement under its current owner.

Tracked source stays authoritative over private replay. Each published definition records the tracked source it shadowed, or that it introduced a new name. Startup replay skips a definition whose tracked definition changed, was removed, or appeared since publication, lists every skip in one startup notice grouped by reason, and leaves it out of the next self.commit. Commits published before this record replay their tracked overrides only while the image runs the source revision the lineage was published against. Reapply a skipped definition against the current source to restore it, or use (fix-skipped-definitions) to hand every skipped definition to the model with its persisted source, the tracked source it was written against, and the current tracked source. Supply an optional comment to direct which overrides to retain or drop:

(fix-skipped-definitions "Drop all of these. They were temporary development experiments.")

The comment takes precedence over the default repair policy. By default, the model rebuilds each override on the current source with self.redefine and drops any the tracked source already covers. It publishes the result with self.commit, including when all skipped overrides are dropped. During an active turn, the command waits in the idle queue.

Refinement

Use self.refine to record trajectory evidence, choose the smallest useful target or none, and exercise existing memory, context, skill, role, RLM or mutation operations. Every transition names the proposal ID and its observed revision. Evaluation records actual task observations separately from owner compile/replay checks. Inspect proposals with (refine) and supply a local assessment before promotion:

(refine :assess "PROPOSAL-ID" :revision 3 :verdict :passed
        :evidence "Evidence from a separate task")

Promotion proceeds from session to project to global scope, with renewed evaluation at each scope and an owner-state guard against intervening changes. Recover an interrupted operation explicitly using recorded evidence; replay never repeats its effects automatically.

Executable Skills

Use skill.load with an exact catalog name to select prose instructions for the logical turn. To publish executable behavior, put a bounded data-only EXECUTABLE.sexp beside SKILL.md or SKILL.sexp. Declare the Skill and ASDF system versions, entrypoint, input/output contracts, capabilities and exact host tool names. An executable manifest has this shape:

(:skill-executable :format-version 1 :skill-version "1.0.0"
 :system "my-workflow" :system-version "1.0.0"
 :entrypoint ("MY-WORKFLOW" "RUN")
 :self-test ("MY-WORKFLOW" "SELF-TEST")
 :verify ("MY-WORKFLOW" "VERIFY")
 :capabilities ("workspace-read") :tools ("resource.read")
 :input (:type :object) :output (:type :object))

The invocation function accepts input &key context; self-test and verification functions accept &key context. Input and output use the native portable JSON representation, such as (:object ("status" "ready")). Install the declared ASDF system in the worker environment, or supply its authorized asd pathname. Call skill.invoke with name and contract-valid input:

{"name":"my-workflow","input":{},
 "asd":".autolith/skills/my-workflow/my-workflow.asd","async":true}

Use skill.verify with name to run the self-test; set entrypoint to verify for the verification function. Each operation requires ordinary explicit full-access code authorization and runs in a fresh disposable worker. Capabilities are declarations for admission; only declared exact host tools allowed by the originating agent policy enter the callback bridge. Source and sidecar identity and the complete manifest are rechecked before loading executable code; declared ASDF versions are checked during authorized system resolution.

Invocation output is contract-validated and retained with its executable identity as native execution evidence. Result publication is bounded to 1 MiB and read as one data-only form after stopping the worker. Use async: true for managed jobs; completion policy applies as described below. Inspect full results with job.get or deliberately await them with job.wait. Mission accounting and ordinary execution supervision apply to workflows and their callbacks.

Local text redaction

Call (redact text) in the user REPL, or autolith:redact from lisp.eval and Lisp scratchpads, to use the local censorship model:

(redact "Alice Smith lives in Amsterdam.")
(redact text :language "en")

Read two return values: the complete redacted string and :ok, or nil and a failure status. Long prose is processed in bounded pieces. A failed piece, an overlong unbroken token, or saturated native output rejects the whole result. Inference runs locally without telemetry consent or network access. Review the result before sharing it; model redaction is not a guarantee of anonymity.

Packaged installations use the bundled model and ONNX Runtime. For a source installation, set AUTOLITH_DEFINGERPRINTER_LIBRARY to the absolute native library path and pass both :model-directory and :runtime-library as absolute paths when the library has no packaged defaults.

Worker tool calls

Supply host-tools, an array of exact tool names, to lisp.eval or lisp.scratchpad-run for an explicitly authorized workflow request. Inside that request, call:

(autolith:worker-tool-call "resource.read" "{\"uri\":\"workspace:src/main.lisp\"}")
(autolith:worker-tool-context)

Read :success-p, :content, :code and :details from the returned result. The host dispatch retains the originating conversation, callbacks, capability policy, mission context and job lineage. Requests and results are bounded and correlated in the conversation audit. Sequential callbacks are supported; choose a different REPL for nested worker operations. Omit host-tools for ordinary worker execution.

For a Lisp-controlled workflow, branch on returned native results and make the next authorized call from the same evaluation. For example, pass these arguments to lisp.eval:

{"host-tools":["resource.read"],
 "forms":["(let ((result (autolith:worker-tool-call \"resource.read\" \"{\\\"uri\\\":\\\"workspace:README.org\\\"}\"))) (if (getf result :success-p) (getf result :content) (getf result :code)))"]}

Host calls pass through ordinary registry authorization with the captured mission admission, including its absence. An interrupted callback is recorded as unknown-outcome in the audit. Inspect effects before repeating a mutating call.

Structural code edits

Load the optional autolith/structural system and configure an ast-grep executable before creating the tool registry:

(asdf:load-system "autolith/structural")
(setf autolith:*structural-program* "/path/to/ast-grep")

Use structural.query with an observed workspace URI and revision. Use structural.rewrite to retain a complete preview, inspect the proposal, then publish with structural.apply. Executable and file access require ordinary command authorization. Proposals are conversation-owned, bounded and single-use; publication uses the same stale-revision checks as resource edits.

For an existing registry, call structural-register-tools explicitly with :program. Run the optional checks with CLASTED_AST_GREP set to the backend path and (asdf:test-system "autolith/structural/tests").

Debug adapter sessions

Load autolith/debug before creating a registry. Configure an adapter in user initialization, or pass an explicit adapter to debug.launch / debug.attach:

(asdf:load-system "autolith/debug")
(setf autolith:*debug-adapter-program* "/path/to/debug-adapter"
      autolith:*debug-adapter-arguments* nil)

For an existing registry, call debug-register-tools with :program and literal :arguments. Loading the system starts no adapter. Adapter execution, launch, attach and expression evaluation require exact full-access command approval. Authorize source and program paths through ordinary path permissions.

Use the returned session ID for debug.breakpoints, debug.continue, debug.pause, debug.step, debug.threads, debug.stack, debug.scopes, debug.variables and debug.evaluate. Inspect bounded queued events with debug.events, using count and wait-for as needed. Session IDs belong to the originating conversation. Use debug.cancel to interrupt an operation, and debug.terminate to close the session and reap its adapter. Timeouts and cancellation close the owned adapter. Run the optional checks with (asdf:test-system "autolith/debug/tests").

Child roles

Roles load from these directories. First match wins:

  • .autolith/agents/
  • ${XDG_CONFIG_HOME:-~/.config}/autolith/agents/
  • <site-config-root>/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.*, or yield.*. Grant job.* when a role needs to inspect, await, or cancel asynchronous work. Children can access only their own jobs and descendant jobs, not parent or sibling jobs. This is independent of permission to spawn another child.

  • :tools may be nil, :all, or canonical tool names and namespace patterns.
  • :spawns may be nil, :all, or role names. Task depth still applies.
  • :reasoning-effort names a supported effort. Omitting it or writing :auto selects medium, so routine delegated work does not inherit the parent’s effort. Children never inherit Codex Fast mode.
  • :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. Persisted child results must contain one data-only form with valid terminal status and field types. The default reading limits are 16 MiB, 128 nesting levels, and 262,144 tree nodes.

Completion-driven turns

Set completion-policy to continue (the default) or notify on asynchronous tools and task.run. A batch inherits its outer policy unless an item overrides it. The policy also applies after the synchronous grace period hands work off.

In a primary terminal session, continue independent work while jobs run. When only job results are pending, yield the current turn. Receive a bounded result summary, truthful outcome and artifact reference in the exact owning conversation. Idle completions within a one-second window share one automatic turn through the ordinary controller. Busy turns receive results at their next safe request boundary. With notify, receive results on the next ordinary turn instead.

An empty final reply while eligible jobs run appears as an Autolith yield notice with the number of unfinished jobs that can wake this conversation. Replayed notices retain the count at the time of yielding; completed jobs and notify-only work do not contribute to a new notice.

An active goal or mission does not urge the model on while a job that will wake it is queued, running or undelivered. Its automatic continuations wait, the goal stays active, and the transcript shows ∙ continuing with job results when the wakeup arrives. The goal’s continuations resume after that turn.

Delivery receipts survive restart and conversation compaction. Notification reconstruction reads terminal artifacts without repeating job effects. Cancellation, paused or terminal missions, spent budgets, and conversation ownership constrain automatic turns. Inspect retained results with job.get, job.continuity or (tasks). Child-owned outcomes are inspection-only outside their exact conversation; completing a child does not reopen it or redirect descendant results into the primary context. ACP sessions also own a completion controller and stream automatic turns to the connected client. Interfaces without an input controller deliver results on subsequent ordinary turns. Use job.wait when deliberate waiting is appropriate.

Task inspection and continuity

Use (tasks) for a compact nonmodal inventory of owned live and durable work, including lineage, timing, reported usage, recent activity and artifact paths:

(tasks :list :offset 32 :limit 32)
(tasks :get "JOB-ID")
(tasks :transcript "JOB-ID" :offset 0 :limit 4000)
(tasks :send "JOB-ID" :message "Inspect the failing check before continuing.")
(tasks :cancel "JOB-ID")

Lists show 32 tasks per page, with live work first. Use :offset and :limit (up to 64) to navigate.

Transcript bounds are character counts, defaulting to 0 and 4000. Use the shown next offset for another window. The :get, :send and :cancel actions use the ordinary owned job operations and authorization.

Use job.continuity with action list or get to classify retained work as live detached, completed, reconstructible from a checkpoint, safe to restart, abandoned or needing a decision. List pages contain whole records with :total, :next-offset and :more-p; offset defaults to 0 and limit to 20 (maximum 100). Supply a visible job or durable execution id for other actions.

The primary owner may declare allow-restart with a safety rationale and authorize: true, or declare checkpoint with a compatible saved worker image, target repl and the same explicit authority. A safety rationale is an assertion about repeating external effects. Revival reattaches live work, starts the named REPL from its saved image, or dispatches a new child from a safe retained shared-workspace task specification:

(tasks :revive "JOB-ID" :authorize t)
(tasks :abandon "EXECUTION-ID" :authorize t)

Equivalent job.continuity actions are revive and abandon with id and authorize: true. The durable recovery decision precedes dispatch and links the old execution to the new execution. Failed or interrupted revival consumes its claim and records an uncertain outcome for explicit inspection. Resuming a conversation presents this inventory for a decision rather than replaying work.

Granted peer messages

The primary owner establishes directional communication with peer.grant. Supply an immutable grant id, sender and receiver as primary-visible job IDs or primary. For example, these arguments grant one child permission to send coordination data to the primary:

{"id":"build-report","sender":"CHILD-JOB-ID","receiver":"primary","context":true}

Each participating actor uses peer.discover to inspect its current grants and exact endpoint identities. Call peer.send with a stable message id, the receiver’s endpoint ID from discovery, and text of 1 to 8192 characters. The sender is the actual executing actor. peer.receive claims incoming work and returns a delivery token; use peer.ack with id and token after handling it. peer.wait waits for incoming work or a named message acknowledgment, with timeout bounded to five seconds. peer.inspect returns scoped metadata pages or the payload and recovery token for an exact message id.

With context: true on the grant, receive coordination data at the existing safe steering boundary. Acknowledgment follows durable persistence of that input. Peer text is coordination data, not additional tool or execution authority. Children need their ordinary role permission for each peer tool; grants do not expose parent or sibling job-control operations.

Cross-session communication requires paired explicit grants on both sessions. Set remote-session and direction (outgoing or incoming); name a remote child with both remote-job and remote-execution, or omit both for its primary. Transport uses authenticated local daemon discovery. peer.revoke durably revokes a grant. Endpoints include exact execution identity, so a replacement child does not inherit its predecessor’s grant.

Grants and messages restore with the session; interrupted deliveries become uncertain. Keep the same stable message ID when transport completion is unknown. Only the primary may call peer.resolve with id, current token and action acknowledge, retry or cancel; retry requires a current grant. Final session finish closes its mailbox, while transient daemon handoff preserves delivery state.

Engineering task artifacts

Select the bundled engineering role or give a native role :output :engineering. Its version-1 contract requires schemaVersion, baseline (kind, workspace, revision), change (kind, reference), paths, checks, evidence, issues and integrationNotes. symbols is optional. Each check names the command, status (passed, failed, skipped or not-run) and summary, with optional evidence references. Use baseline kind unknown when no revision is available and change kind none when no change was produced.

Extend the common contract in a native role:

:output (:type :engineering
         :properties (("review" (:type :string)))
         :required ("review"))

Common properties cannot be overridden. The usual structured yield.submit and durable result transport validate and retain these values. Checks and evidence are child-reported observations; mission gates establish harness acceptance. A failed or aborted child follows the ordinary error-only terminal contract.

Isolated modifying tasks

Supply an isolation object in a flat task.run call or in each selected batch item. Omit it for the ordinary shared workspace. The object accepts baseline (a commit-resolving revision, default HEAD), dirtyPolicy (reject by default, or explicit ignore), and artifactKind (patch by default, or commit-range). With ignore, use only the committed baseline; staged, unstaged and untracked source edits are excluded. Git isolation is unavailable for non-Git workspaces.

Sandboxed commands in an isolated child may write its owned checkout and the repository’s Git administrative directories, including a linked worktree’s common Git directory. Integration requires authorization for the target workspace.

The child edits and tests in its detached checkout. Before it starts, persist the source repository, exact resolved baseline, isolated path and conversation-qualified owner. Stage new files before yielding. A patch includes committed and uncommitted tracked changes; a commit range requires a clean checkout and ancestor baseline. Terminal results, including aborted tasks, identify the secured engineering artifact. Extraction failures retain the checkout and report :worktree-error; a successful child without a complete artifact is reported as failed.

Use task.worktree with an explicit action:

  • inspect returns provenance, touched paths and the durable artifact pathname; extract explicitly retries extraction after repairing an incomplete checkout. Supply a visible terminal job id or its durable execution ID.
  • check verifies patch applicability without mutation. apply applies the patch to the clean target’s index and working tree; commits cherry-picks the recorded commit range. Supply id and an explicit target repository.
  • conflicts inspects unmerged paths; abort explicitly aborts an interrupted cherry-pick. Supply the target repository.
  • cleanup removes only the task’s owned checkout. Dirty cleanup requires discardDirty: true. Secured artifacts are independent of checkout removal.
  • discover lists this conversation’s active and absent owned records. Supply source to recover interrupted registry publication from Git markers. cleanup-orphan uses a discovery worktree id and explicit source, including after task-result publication failed. Unrelated worktrees are excluded.

Git lifecycle and integration commands use ordinary command authorization and execution policy. Target paths use ordinary tool-path authorization. Isolation is an accidental-overlap boundary, not hostile-code confinement. The parent chooses integration order and resolves conflicts deliberately.

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. Use POSIX shell syntax for commands on POSIX hosts and PowerShell syntax on Windows. Both Windows authorization modes use native PowerShell; MSYS/Git Bash requires a shared object namespace that AppContainer does not permit. Windows sandboxed commands execute serially while temporary filesystem ACLs are installed. Their writable temporary directory is removed after completion or cancellation. Add existing read-only tool directories through *win32-sandbox-read-roots* for tools installed outside system application directories. Windows scopes must be local directories without reparse points or hard-linked files. Unsupported scopes or an unavailable helper fail instead of running without isolation.

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.

Retained command output

Use shell.run for synchronous commands, detached commands and command mission gates. Each authorized invocation receives private execution-owned raw captures before launch. Results include exit, timeout and cancellation status, retained and observed byte counts, capture completeness, and stable shell-log: references. The inline preview samples the beginning and end, with most space reserved for trailing diagnostics. Set separate-output: true to capture stdout and stderr independently; the preview allocates more space to stderr.

Read a reference with resource.read using zero-based byte-offset and byte-count (default and maximum 4096 bytes). Use query for a literal UTF-8 search, optionally max-results and byte-offset. Continue with the returned next offset. Each search scans at most 1 MiB and returns at most 20 excerpts. Text rendering replaces invalid UTF-8; the retained files contain the original bytes. Access follows the owning conversation and ordinary task-descendant inspection authority.

References are included in job inspection, durable tool-result metadata and completion notices. Reopen them after restart or compaction without executing the command again. Missing, interrupted and pruned captures return diagnostics.

Defaults are 64 MiB per stream, 1 GiB of reserved capture capacity and 256 retained artifacts. Eligible completed captures become pruneable after seven days or under capacity pressure, on the next allocation. Active captures and undelivered completion evidence are protected. Adjust *shell-log-capture-byte-limit*, *shell-log-retention-byte-limit*, *shell-log-retention-count-limit* and *shell-log-retention-age* in trusted configuration. Captures exceeding their limit are explicitly incomplete. Session export collects task metadata independently of local log storage.

Non-interactive jobs

run-job executes one caller-supplied child role without an interactive terminal. It uses normal project, user, and bundled task-agent discovery, provider and MCP configuration, role tool and spawn policy, task traces, usage accounting, and native structured-output contracts.

autolith --permissions auto run-job \
  --input /path/to/job.sexp \
  --output /path/to/result.sexp

The input is exactly one bounded data-only S-expression:

(:autolith-job
 :version 1
 :id "job-identifier"
 :role "project-role"
 :prompt "Perform the assigned work."
 :input (:caller-owned "opaque data")
 :output-contract (:type :object
                   :properties (("answer" (:type :string)))
                   :required ("answer")
                   :additional-properties nil)
 :timeout-seconds 900)

The supplied output contract applies only to that run and does not alter the role definition. The terminal artifact is atomically installed at --output as (:autolith-job-result :version 1 ...). Structured objects and arrays use :OBJECT and :ARRAY tags, so false, null, and empty arrays remain distinct. Failures use stable categories and statuses :failed, :cancelled, or :timed-out. Successful jobs use :succeeded and include the contracted result. Trace and usage fields are included when available.

Headless execution never opens an approval picker. auto classifies shell commands and denies decisions that would require approval. sandbox permits only sandboxed commands. full permits full command execution. External tool calls whose configured policy requires approval are denied unless full access was selected. The role allowlist remains the child capability boundary.

Live S-expression events

Add --events sexp to observe a run on stdout. Capture diagnostics separately on stderr and read the authoritative terminal result from --output:

autolith --permissions auto run-job \
  --input /path/to/job.sexp --output /path/to/result.sexp \
  --events sexp >events.sexp 2>diagnostics.log

Read consecutive top-level data forms, rather than splitting physical lines. Strings may contain newlines. Each version-one record has this shape:

(:autolith-event :version 1
 :run-id "a-generated-run-uuid" :seq 1 :time "2026-10-08T09:00:00Z"
 :kind :run-started :data nil)

The event kinds are :run-started, :job-started, :tool-started, :progress, :tool-finished, :job-finished, :usage, :warning, :progress-omitted, and :run-finished. Ignore unknown versioned kinds. Sequence numbers increase strictly within one run. Job and execution identifiers are opaque execution UUIDs; provider call identifiers are pseudonymous within that run. Tool names come from the registered tool catalog. Observe only the requested root and its owned descendants, excluding other roots in the same conversation. Event data contains allowlisted statuses and numeric counters, not prompts, tool arguments, tool output, or provider payloads. Interactive input is EOF, and diagnostic/query streams go to stderr in the initiating thread and its workers.

Use run-job-event-read-stream to read one validated record at a time, returning NIL at EOF. It uses the bounded sexp-config data grammar with *read-eval* NIL. Only approved keywords, strings, NIL/T, bounded nonnegative integers, and finite proper lists are accepted. The maximum record size is 16 KiB of UTF-8; character streams supplied to the library must already use UTF-8. Binary streams receive UTF-8 octets.

Root :job-finished and final :run-finished records follow atomic result publication. Published terminal job events include a :result-uri resource reference; descendant references identify execution-qualified artifacts. The final data includes :status and :published-p; successful publication also includes :output-path. An output write failure reports :failed, :published-p nil, and :category :result-publication-failed, with a nonzero exit status. Inspect the result artifact for contracted data and full failure details.

Delivery is best effort through one bounded queue and a single writer. Progress may be omitted under backpressure, with :progress-omitted reporting its count. Lifecycle overflow is reported with :warning and :code :queue-full; final delivery has a reserved slot. A slow or disconnected consumer may miss the terminal record; execution and result publication continue. There is no replay or reconnect cursor. For a new invocation, expect a new run UUID and sequence beginning at one.

From Lisp, call run-job-run with :events :sexp and :event-output STREAM. Without the events option, use the existing artifact-only interface.

Generations and recovery

(checkpoint) requires a clean Git worktree and a passing ./script/check. On POSIX it saves a retained image without stopping the active session. On Windows it saves the exact live heap, restarts through the stable launcher, and resumes the active conversation after the saved image passes its probe.

  • (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 boots a private clean checkout of current committed source with --pristine. It does not load retained generations or private mutation replay. An intentional (rollback ID) still boots the selected generation. Explicit generation selection may use an older revision.

To start a rescue session directly, use:

autolith resume --pristine --permissions full

--pristine forces tracked source instead of a saved active core and skips the selected private image commit, mutation journal reconstruction, and executable user initialization for this process. It implies --immutable so ordinary self-mutation tools cannot replace the selected private commit. It does not erase any mutation or conversation data. Ordinary startup remains unchanged.

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