Skip to content

Latest commit

 

History

History
494 lines (436 loc) · 30 KB

architecture.org

File metadata and controls

494 lines (436 loc) · 30 KB

Autolith Architecture

Autolith is a terminal agent in a live Common Lisp image. The running system can be inspected and changed by both the agent, and the user.

All application definitions live in the single autolith package, which may be considered non-standard or unwise by some Common Lisp developers, but I consider correct for self-modifiability.

System

           +--------------------+
           | stable launcher    |
           | provenance checks  |
           +---------+----------+
                     |
      +--------------v---------------+
      | active Autolith Lisp image    |
      | terminal, agent, tools, state |
      +--+-----------+-----------+----+
         |           |           |
+--------v--+   +----v----+  +---v----------------+
| provider  |   | durable |  | isolated workers    |
| HTTP/SSE  |   | records |  | search and lisp.*   |
+-----------+   +----+----+  +---------------------+
                    |
      +-------------v--------------+
      | XDG data and state roots    |
      | conversations, memories,    |
      | agendas, images, credentials|
      +----------------------------+
  • The stable launcher checks runtime and source provenance, then starts the active image or recovery
  • The active image operates the session: input, provider turns, tools, and the live terminal region
  • Workers handle search (the special fff worker) and lisp.* REPLs. They live in separate Lisp images and cannot directly modify the agent.
  • Durable records live under reasonable XDG folders. This roughly includes conversations, memories, agendas, images, and credentials
  • The recovery image is a separately built pristine core, which Autolith will reboot into in case the current image is FUBAR. Autolith can work using the recovery image to either fix figure out the issue with your current image, or try to finish your task if that’s a priority.

Configuration layers

Autolith keeps packaged defaults separate from the user’s XDG configuration. An optional absolute site configuration root supplies init.lisp, mcp.sexp, skills/, and agents/. Site definitions have lower precedence than the corresponding user definitions. Trusted directory configuration follows user native MCP configuration, while the user XDG init.lisp loads last among filesystem Lisp configuration. Live registrations remain independent runtime state. The user-owned directory-scopes.sexp is never read from the site root.

The site layer is a deployment and composition mechanism, not a security boundary. Privileged operations must remain behind the supervisor boundary even when site or user Lisp replaces every guest-side definition.

Agent turns

user input
   |
   v
responsive reader --> application operation --> agent turn
   |                         |                    |
   |                         |                    +--> provider stream
   |                         |                    +--> tool batches
   |                         |                    +--> durable records
   v                         v
terminal prompt          local Lisp result       finalized scrollback
  • The reader classifies prose, explicit Lisp, and callable operations
  • The application evaluates one top-level form and shows values, output, conditions, and restarts
  • lambda-debugger owns live restart extent, validated recoveries, and portable condition details, source, restart, capability, and argument-free stack snapshots
  • src/application/debugger.lisp owns independent model diagnosis over a snapshot; lisp-machine.lisp supplies Autolith’s control policy and terminal presentation
  • src/agent/runtime.lisp runs one model turn at a time
  • User input is persisted first before we do anything else
  • Context pressure triggers inline durable compaction
  • Local operations that dont conflict with a turn can run immediately
  • Modal, terminal-owning, authorization-gated etc actions wait for a safe point

Library boundaries

LibraryGeneric implementationAutolith integration
agentcommsACP v1 schema, symmetric JSON-RPC, owned request handlers and session lifecycleHeadless application ownership, durable replay, observer projection and editor approvals
cl-llm-provider-apiWire clients, request execution, context resolution, structured contracts, layered provider registry with model discoveryBuiltin registrations, extension sources, the model cache file, subscription adapters, conversation projection, contributors
cl-rfc8628Scoped credential use and coordinated refreshCredential paths, bootstrap ownership, provider exchanges, checkpoint quiescence
mcparen/managedConnection lifecycle, discovery snapshots and budgetsConfiguration, authorization, tool registry and provider schema projection
cl-lspStdio JSON-RPC, language-server lifecycle, document synchronization and diagnosticsTrusted configuration, project selection, tool operations and edit hooks
lambda-debuggerLive restart selection, recovery validation and execution, observable outcomes and typed condition metadataControl conditions, source evaluation policy, model diagnosis and terminal renderer
surgeonDefinition kinds, apropos, lambda lists, restart choice, definition signatures, exact undo capture and source form spansTool rendering, mutation journal, image commits, replay and the contributor and command undo methods
cl-hashlineVisible-line validation, SHA-256 anchors, original-coordinate edits and revision-gated publication callbacksJSON operations, observation state, bounded reads, temporary files and platform atomic publication
setinkaTyped settings, layered sources, durable stores, listeners and dependent reconciliationSetting definitions, provider-aware model checks, the preferences file and runtime side effects
argoJSON value model, strict RFC 8259 decoding, validated encoding and limitsProvider wire items, tool arguments and conversation replay
sexp-storeValidated transactions, segmented logs and sidecarsRecord schemas, scope, lifecycle and corruption policy
clinker-transcriptOrdered projection and reconciliationConversation meaning and durable repair decisions
image-daemon/runtimeService registry, relays, attachment queues and shutdownConversation leases, steering, handoff and input-reader coordination
clinediModal sessions, editor/history state and terminal transportsCompletion, submission, paste and interruption policy
cl-termdownColumn layout, Markdown fence lifecycle and semantic span presentationTranscript composition, source-copy widgets and stream finalization
clermaidBounded Mermaid parsing and terminal-cell diagram layoutMermaid fenced blocks through cl-termdown
cl-exec-sandboxBounded raw capture files, process cleanup, capture metadata and byte head/tail samplingPrivate execution-owned log artifacts, preview budgets, retention, durable references and authorized resource reads

Retained shell output is split across src/task/shell-logs.lisp (ownership, private manifests and pruning), src/tools/shell-output.lisp (bounded previews) and src/resource/shell-log.lisp (authorized byte windows and search). Jobpond execution identities and durable tool, mission-gate and completion evidence connect capture lifetime to the task runtime. Shell jobs opt into Jobpond’s cooperative cancellation policy and poll from the executor so process and capture cleanup finish before terminal publication. Portable result details preserve references in conversation records; raw output is independent of context storage.

ACP service

src/acp/session.lisp owns leased headless applications and serializes primary operations. observer.lisp projects native streaming/tool events and editor permissions. mcp.lisp builds session-local client declarations through the native MCP manager and credential scopes. runtime.lisp supplies the agent methods, chronological replay, bootstrap, and disconnect cleanup. extensions.lisp owns typed dispatch, bounded JSON projection, advisory events, and session-scoped read/write admission. The extension-*.lisp adapters project native settings, model/operation catalogs, conversations, owned jobs and structured resource windows. completions.lisp owns the condition-variable controller for receipt-backed automatic turns, sharing prompt admission, observer projection, cancellation and shutdown with explicit prompts.

The source entry and stable launcher route ACP diagnostics to stderr and avoid interactive recovery. The serving lifetime holds the secret-use guard while client credentials are retained. Cancellation targets registered prompt/tool owners; durable turn finalization defers queued interrupts before lease teardown.

User state

Autolith stores portable, readable S-expressions (for now, it is possible we migrate to sqlite or similar in the future).

Use sexp-store transactions for locked read/modify/publish operations. Caller state is published after durable records. Conversation sidecar reconstruction holds the same file lock as source invalidation and writes.

StateComponents
Conversationsheaders, messages, provider items, model choices
Memoriesworkspace and global facts, sources and premises, tombs
Knowledgepredicates, evidence, supports, rules, retractions,
learned procedures and their runs, diagnoses and cases
Viewersthe last knowledge snapshot each viewer saw (derived)
Agendasversioned workspace plans and memory identifiers
Papercutsuser-visible defect reports and closure tombstones
Input vaultqueued follow-ups and steering after a crash
  • The knowledge log is append-only: a batch of records is one :transaction form, so an interrupted write leaves none of it, and a reader folds only what was appended since its cached read. It lives beside the memory log rather than in it so older generations still read every memory after a rollback; they never read the knowledge log. Derivations, statuses, and conflicts are evaluated from the log, the cited files, and the time, and are never stored.
  • Procedures are learned from tool-call traces read from conversation logs. Traces are cached in memory per conversation and extended from where the last read stopped; they are never written anywhere, so the transcript stays the one record of what a turn did.
  • Conversation records are authoritative provider history
  • The header records the selected model and effort. Later changes are durable configuration records, so resume restores the latest choices
  • Identifiers use idsmall Base58 IDs, which are not the most secure, but they encode a timestamp and are short enough that you can at least remember the prefix.
  • src/conversation/compaction.lisp captures the provider cutoff and uses a pure clinker-transcript carry-forward plan. It validates late arrivals and truthful repairs before publishing a self-contained checkpoint, then installs the replacement projection. :cutoff-seq identifies the request snapshot; :through-seq covers every record before checkpoint publication.
  • src/conversation/input-history.lisp maintains complete editable input recall in conversation-picker/<id>.inputs.sexp. Missing or invalid indices rebuild from authoritative records once. Each new input appends one indexed sequence; compaction flushes the index before retiring the active segment. Checkpoints carry the last input sequence, not the input strings.

Configuration

A configuration holds a table of settings and their values. Each setting is an instance of a setting kind (boolean-setting, choice-setting, integer-setting, string-setting, pathname-setting, derived-setting, and the Autolith-specific model-setting and reasoning-effort-setting) declaring its name, type, documentation, label and group for the settings page, default, options, scope, and environment variable. define-setting registers a knob once; each configuration snapshots the registry at creation, so user initialization may add knobs and children may carry different ones.

config reads a value and (setf config) coerces, validates, stores, and notifies listeners; *configuration* supplies the default instance. Values are chosen at creation by source precedence: explicit override, environment, durable file, default. Scopes are :process (fixed for the process), :durable (persisted through the preferences store on every session change), :session (forgotten at exit), and :derived (computed on read). configuration-copy produces an independent instance for children and inference frames; listeners stay with the original.

preferences.lisp reads and atomically writes version 8 records of durable settings, preserving unknown keys across rewrites. Unsupported or malformed records are reported through preferences-load-warning during configuration creation.

Provider and tools

  • src/provider/client.lisp integrates product providers with cl-llm-provider-api wire clients and request execution. Render semantic stream events in the terminal and record projected provider history in conversations.
  • src/provider/wire-protocol.lisp anchors Codex’s identical, currently supplied goal and remaining developer-context tail before appended assistant/tool rounds, after context-delivery-project-items has placed opted-in anchored contributions. Exact suffix validation uses that assembly’s actual tail, not the full delivery rendering that also contains anchored blocks. Only the current request’s objects are moved; durable history is unchanged. Weak conversation-keyed state retains two positions and case-sensitive SHA-256 digests, not prompt text. Reuse requires the complete preceding history to remain an exact prefix and no intervening user, developer, or system message. Changed, omitted, partially omitted or unrecognized context, rewritten/shortened history, and direct compaction requests reset placement. When compaction uses a detached request view, the original conversation resets on the changed history at its next ordinary request. Compaction wire input itself is unchanged. The placement-state mutex does not serialize conversation mutation or network requests: concurrent constructors can reset cache continuity, but each uses only its own context.
  • Tools have metadata, namespaces, validation, and a result for every call, including failed ones
  • src/tools/defaults.lisp assembles the registry
provider request --> semantic events --> agent --> validated tool calls
                                             |
                                             +--> matching tool results
  • Configuration records credential locations only
  • A provider request sees tokens only in its dynamic scope
  • The optional Codex file is a one-time access-token import
  • Renewable ChatGPT credentials come from browser OAuth or the optional device-code flow

Revisioned resources

cl-resources defines authority-neutral identities, resolver registries, observations, revision guards, operation discovery, typed conditions and staged change-set lifecycles. Autolith registers product resolvers and retains conversation-local observation aliases in src/resource/protocol.lisp. Caller authorization and resource-specific storage policies belong to Autolith. Each resolver documents its own scheme for reading and editing, and the resource.read and resource.edit descriptions are composed from the resolvers of their registry, so a scheme registered only in some sessions appears only there.

src/resource/workspace-changes.lisp adapts staged changes to exact UTF-8 file snapshots. Cooperating operations share the workspace mutation lock. Publication is atomic per file; failed batches compensate in reverse order, retaining owned undo handles when cleanup or safe rollback requires explicit recovery. Staging ownership transfer defers asynchronous interruption. Restorable host permission values retain source access state before ordered moves remove their source paths.

Workspace and workers

  • Command execution uses platform-call-with-command-sandbox to own policy construction and scope cleanup. cl-exec-sandbox selects the backend:
    • Bubblewrap on Linux
    • Seatbelt via sandbox-exec on macOS
    • AppContainer, a restricted token, scoped ACLs, and a Job Object on Windows
  • The default policy provides:
    • no network
    • host read-only on POSIX; explicit tool reads and system application access on Windows
    • workspace and temporary writes
    • protected repository metadata
    • 60 second default timeout
  • The Windows adapter serializes scope construction through cleanup and sets child-only temporary/cache environment paths. The release launcher selects the bundled native helper outside writable scopes. Both Windows authorization modes execute commands through native PowerShell.
  • Search is one supervised native helper per fff registry.
  • lisp.* operations run in named persistent SBCL subprocesses. A pristine worker, like the private replay probe, boots the installed active core when its manifest matches the source and runtime exactly, and loads the system from source otherwise; the choice is made again at every start.
  • Worker images never participate in active startup or recovery
  • sexp-config reads skills, MCP configuration, child roles, etc.
  • Tasks use cl-jobpond for admission, threads, deadlines, cancellation, and generic durable completion subscriptions. src/task/completions.lisp projects bounded outcomes and reconciles mailbox claims with durable conversation receipts. src/application/job-completions.lisp admits coalesced primary continuations through the ordinary input controller. Bounded metadata-only controller work frees mailbox capacity occupied by notify-only or inadmissible outcomes. Producer callbacks only signal its condition variable; provider requests occur on the normal turn path.
  • src/task/unfinished-work.lisp reads owned durable continuity and exact-owner conversation receipts for bounded outstanding-job context. Compaction records contain reference snapshots; later request contributions re-read job authority.

Headless job observation

src/task/job-boundary.lisp runs one contracted role through the application runtime and atomically publishes its terminal result. src/startup/main.lisp accepts the opt-in run-job --events sexp flag. The event stdout stream is captured before redirecting diagnostic streams to stderr and interactive input to EOF. Bordeaux Threads initial bindings include recursively inherited stream bindings for nested workers; no scheduler or provider transport is added. After application cleanup and bounded delivery, the streaming CLI exits directly with its result status, without flushing or interrupting a blocked stdout writer.

src/task/job-event-observer.lisp adapts the existing orchestrator listener and child observer events. Its scope is one generated root admission name in the owning conversation, then that root’s exact job identifier and descendant ancestry. It retains only the root identifier. Wire job IDs are execution UUIDs; provider call IDs are hashed with the run UUID, without a growing call map. Projection permits registered canonical tool names, approved statuses, and bounded normalized numeric usage fields. Continuity precedes task start events. Descendant terminal events follow durable task results or tool terminal metadata; root terminal events follow publication of the outer result artifact. Listener registration is removed on every exit.

src/task/job-events.lisp owns the version-one closed vocabulary, bounded sexp-config grammar, validation, UTF-8 serialization, finite queue, and single writer. Producers enqueue bounded owned snapshots without waiting on stdout. Backpressure evicts progress first and reports lifecycle overflow with a fixed warning. Final delivery has a reserved slot; shutdown waits only for a bounded interval. An in-flight blocked writer retains one bounded payload; it is not killed or used to interrupt task execution. :run-finished is the last delivered event when the consumer remains connected. The stream has no durable replay authority; consumers reconcile with the output artifact.

Native language servers

src/lsp/configuration.lisp validates user-owned lsp.sexp with the closed sexp-config grammar and selects workspace-bounded project roots. cl-lsp implements binary Content-Length framing, request correlation, ordered server callbacks, deadlines, process/thread cleanup, initialization, disk synchronization, UTF-16 positions, and version-aware push/pull diagnostics. src/lsp/tools.lisp registers the query, diagnostics, status, and restart tools, appends bounded diagnostics after successful workspace resource edits, and contributes a request-context note naming enabled servers. src/lsp/semantic-proposals.lisp retains bounded conversation-scoped edit plans and compiles ordered server proposals into resource change sets. src/lsp/semantic-tools.lisp registers semantic request, preview, resolution and apply operations. Each affected path is authorized and revalidated before application; annotated confirmations use the command-authorization boundary.

Each registry owns one lazy manager. Registries create it and its tools only when lsp.sexp enables at least one server. Operations share its recursive lock; notification delivery uses the client state lock. Runtime retirement closes the manager once through shared runtime identity. Saver-child detachment closes inherited descriptors without signaling the parent’s language servers. Configured executables run as trusted local processes, separately from the shell sandbox. Server-driven workspace edits are declined; file changes use revision-gated resources. cl-lsp encodes language-server file URIs.

Emacs and Slynk

src/slynk/endpoint.lisp owns one process-wide Slynk server serving the active image on loopback, loaded by exact path from the person’s own sly installation. ACP serving and the terminal session own its lifetime; checkpoints quiesce it and serve the same port again. Slynk’s global debugger, stream redirection and log output stay off, so Autolith’s threads never report into a sly connection. src/emacs/server.lisp speaks the Emacs server line protocol over the configured Unix socket, with base64 arguments and JSON results. src/emacs/resource.lisp resolves the emacs: scheme: emacs:current renders the visible windows, region and file buffers, and emacs:buffer/NAME snapshots a whole widened buffer as a workspace-style observation. Edits reuse the original-line operations and observation state of workspace files, but publish by one Emacs request that compares the buffer with the observed text and calls replace-buffer-contents, so concurrent typing makes the edit stale and never lost. src/emacs/tools.lisp holds the action tools, emacs.visit and emacs.eval, and registers them with the scheme only while that socket exists. emacs/autolith.el wires both from Emacs.

Terminal

Autolith uses the primary screen and leaves finalized output in normal scrollback by default. An opt-in fullscreen terminal UI owns the transcript viewport in an alternate screen, keeps application scrollback, and pins the composer to the bottom. The persistent setting is stored in the global preferences record; --fullscreen overrides it for one launch.

  • Reasoning previews enter scrollback only once finalized
  • Autolith provides semantic styling, candidate policy, terminal mode, and emits OSC 133 prompt blocks
  • OSC 133: A and B precede input, C starts execution, D;STATUS closes execution. This way you can rely on smarter terminals
  • OSC 0: the interactive window title is autolith - PATH. Autolith writes it at UI start, after localgroup attach, and after (cwd "PATH").
  • OSC 7501: protocol.lisp encodes bounded root and job program-status reports; ui.lisp serializes output, caches job records, and replays active records after OSC 133 prompt-start. application/runtime.lisp maps conversation-owned jobs to independent records. Root reports include the current conversation title. Foreground execution and modal input boundaries publish root status.
  • Clinedi owns editor state, input decoding, geometry, and live repainting
  • The fullscreen viewport owns transcript wrapping, application scrolling, reflow anchors, and bottom-pinned composer geometry
  • PageUp and PageDown page through fullscreen history; Ctrl-Home and Ctrl-End jump to its ends, and mouse-wheel events scroll incrementally
  • cl-colorist is used for ANSI capability detection
  • Text is sanitized before styles are added
  • :surface, :surface-code, :surface-diff-add, and :surface-diff-remove carry backgrounds for whole-row fills. terminal--render-spans takes a :background style and terminal--pad-spans extends a row to the terminal width, so a theme fills panels, code, and diff rows without losing span colors
  • Ctrl-C on an empty prompt exits.

Live mutation and generations

The self.* tools can inspect the active image and install a complete definition.

journal proposal
   |
   v
compile and install --> checks --> replay script and manifest
                                              |
                                              v
                                  private mutation-history Git commit
                                              |
                                              v
                                  atomically select normal startup state
                                              |
                                              v
                                      journal durable result
  • Exploratory changes stop after install and journal publication
  • self.diff shows effective state. self.exercise records focused evidence. self.discard reverses one effective change
  • self.commit creates a private snapshot, replay-probes it in a clean process (the probe loads the script, then asserts the core surface of load-bearing functions and classes survived), commits it to the separate mutation-history repository, and atomically selects it
  • Replay entries record the tracked source they shadowed. Startup replay skips and reports an entry whose tracked definition moved since publication, so newer tracked code is never shadowed by a stale override
  • Checkpoints use sbcl-generations. Before the fork, Autolith quiesces work, clears credentials, detaches workers, and checks provenance
  • Windows cannot fork. Checkpoints and rollback are withheld there, and a worker saves its image by exiting through save-lisp-and-die and restarting from the saved core instead of forking a saver

Startup, update, and recovery

stable launcher
   |
   +--> verify runtime and source provenance --> active image
   |                                               |
   |                                               +--> retained generation
   |
   +--> failure --> recovery --> pristine exact-source checkout
   |
   +--> explicit generation or rollback --> recovery --> retained generation
  • active-image.lisp builds the preloaded image for normal startup and for every fresh Autolith process, such as pristine lisp.* workers
  • script/bootstrap finds a supported SBCL, builds the pristine recovery core and a separate active image, and boot-probes both
  • Updates validate provenance and refresh a readable availability cache. Startup reads cached state only. An accepted packaged update runs the bundled installer and restarts.
  • Recovery reports sanitized crash context. Automatic failure recovery boots current committed source without private mutations or user init. Explicit generation selection and rollback retain their saved-state path

Source map

Here’s a rough, probably outdated map of the repository

AreaPrimary source boundaries and dependencies
Application loopsrc/startup/, src/agent/, src/application/, src/terminal/responsive-input.lisp
Terminalsrc/terminal/, Clinedi, cl-colorist
Providers and credentialssrc/provider/
Tools and workspace resourcessrc/tools/, src/resource/, cl-exec-sandbox, clifff
Readable durable statesrc/conversation/, src/state/, src/resource/{memory,agenda}.lisp, sexp-store
Structured knowledgesrc/knowledge/, src/resource/memory-knowledge.lisp, src/agent/memory-context.lisp
Procedure learningsrc/learning/, src/agent/procedure-context.lisp, src/tools/procedure.lisp
Workers and taskssrc/workers/, src/task/, cl-jobpond, sbcl-workers
Live mutationsrc/self/, src/state/{durable-mutations,image-commits}.lisp, surgeon
Images and recoverysrc/startup/active-image.lisp, src/state/generations.lisp, recovery/, bin/, sbcl-generations
Configuration and transfersrc/configuration/, src/localgroup/, src/state/updates.lisp, sexp-config

Read the guide for operating Autolith, and AGENTS.md for repository rules and development policy, especially using clankers.