Skip to content

Repository files navigation

RPLACA

WARNING: This project is pre-dogfood and under active development.

Macros for your agent!

Repository: github.com/htayj/rplaca

Users of pre-rename builds should follow the Clawmacs-to-RPLACA migration guide before moving configuration or sessions.

Introduction

Anyone who has used Emacs before knows the benefit of a tight feedback loop. Write lisp code, eval it, instant results. RPLACA brings that same philosophy to agentic programming: the tighter the feedback loop between you and the agent, and the more the agent can introspect its own environment, the more powerful the collaboration becomes.

RPLACA is a Lisp-native, Emacs-inspired LLM chat interface written entirely in Common Lisp. It gives you and your AI agent a shared, programmable environment while keeping provider work off the CLIM frame process. The agent can read, write, and edit files directly through Lisp data mode, and it can use isolated lisp_eval workers to test Common Lisp without freezing or terminating the live UI.

docs/img/genera-style.png

Features

Multi-Provider LLM Support

RPLACA supports multiple LLM providers, switchable per buffer:

  • Z.AI (Zhipu AI) — GLM models
  • OpenAI Codex — with shared Codex OAuth/API-key auth and the Responses API
  • OpenRouter — access to hundreds of models with dynamic model discovery

Switch agents on the fly with C-c A. Switch models on the fly with C-c C-m (fuzzy minibuffer selector) or C-c M (overlay selector). Run interactive commands with M-x using fuzzy command-name completion, candidate rows, Tab=/=Down and Backtab=/=Up navigation, and minibuffer prompting for command arguments.

OpenAI Codex Auth

OpenAI Codex auth follows Codex’s own credential layout:

  • ~/.config/rplaca/openai-codex-token is the highest-precedence raw bearer override.
  • Otherwise RPLACA reads and refreshes shared Codex credentials from ~/.codex/auth.json.
  • The browser login flow uses a localhost callback at http://localhost:1455/auth/callback and falls back to another local port if 1455 is busy.
  • ChatGPT-backed Codex auth uses https://chatgpt.com/backend-api/codex, while API-key auth uses https://api.openai.com/v1.

Agent Tools

The default built-in agent tool surface follows Pi-style coding tools, with provider tools as the primary action surface:

ToolDescription
readRead text files
findSearch files by name or wildcard
grepSearch file contents by literal text
writeCreate or overwrite text files
editReplace exact text in text files
lisp_evalEvaluate Common Lisp in an isolated worker (mode=isolated required for provider calls)
recovery_listInspect recent durable tool/eval/checkpoint journal events

RPLACA is a full-trust application. Agent tools, package code, init files, shell commands, and live Lisp all run with the operating-system authority of the RPLACA process. There is deliberately no in-application permission prompt or sandbox policy. If containment is required, run the entire application in a user-controlled container, VM, restricted account, or OS sandbox. Tool and package selection controls workflow composition and prompt exposure; it is not a security boundary.

By default, agents use exposed provider tools for workspace search and text changes. Tool inputs and tool results are represented as Lisp data, for example (edit :path "src/foo.lisp" :old-text "old" :new-text "new"). The write and edit tools reject content that leaves Lisp parentheses unbalanced. Custom tools may be registered explicitly with register-tool or tagged through deftool. Imported packages contribute tools as soon as their deftool forms are evaluated.

The lisp_eval tool is the fallback for Common Lisp tests and isolated introspection when no exposed tool fits. Provider calls must pass :mode "isolated"; live evaluation is refused at the provider boundary because arbitrary code can block or terminate the CLIM frame process. Trusted users can still use the listener or direct Lisp API for deliberate live evaluation. Standard Common Lisp reference lookup is available through a vendored offline snapshot of cl-community-spec, and imported library discovery is handled locally through ASDF metadata, package exports, docstrings, and source/doc search helpers.

lisp_eval captures printed output, error output, and multiple return values. Its tool result is a printed Lisp plist. Recent calls are available with (eval-history-to-string), and the latest success/error are exposed through *last-eval-result* and *last-eval-condition*. Isolated mode evaluates in a fresh SBCL worker process without mutating or crashing the live RPLACA image. The worker inherits the user’s filesystem and network authority; this separation is for application stability, not containment.

The safe-reload-rplaca-command gives users a deliberate development hot-reload path for updated RPLACA source. It is not exposed as a provider tool because live class and method redefinition cannot be safely interleaved with a background agent tool worker. The command first loads the current checkout in an isolated SBCL worker; only if that preflight succeeds does it reload :rplaca in the live image, refresh command/tool/package registrations, and insert a visible system message in the current buffer. Existing buffers, sessions, the frame, and compose draft are not reset. Preflight, busy, and live reload failures are returned as Lisp data instead of crashing the app.

Agent tool calls are durably journaled in the session transcript. File mutation tools record before/after checkpoint metadata and diffs, and lisp_eval records before/after recovery checkpoints. If a turn is interrupted, recovery_list can summarize recent tool, file, and eval recovery events for repair.

Skills

RPLACA supports Codex-style local skills: a skill is a directory containing a SKILL.md file with YAML frontmatter and task-specific instructions. Enabled skills are listed in the agent system prompt, can be selected from the UI, and can be invoked in chat with $skill-name. When a user message mentions a skill, RPLACA injects a Codex-compatible contextual block for that turn:

<skill>
<name>demo</name>
<path>/path/to/demo/SKILL.md</path>
...
</skill>

Skill discovery scans repo-local .agents/skills/ directories, then ~/.rplaca.d/skills/, ~/.agents/skills/, ~/.rplaca.d/skills/.system/, and roots registered from Lisp. Use C-c s to insert an exact linked skill mention and C-c S to toggle skills.

(list-skills)
(describe-skill-to-string "common-lisp")
(read-skill-instructions "common-lisp")
(skill-list-files "common-lisp")
(skill-read-file "common-lisp" "references/notes.md")
(skill-search-to-string "common-lisp" "hash table")

Skills do not add new agent tools by themselves. Agents inspect skill instructions and resources through lisp_eval, and scripts/assets are local resources unless other RPLACA APIs explicitly execute or use them. Skill instructions are trusted input to the full-trust agent workflow.

Built-in Packages

RPLACA ships with a package system for optional tool and UI surfaces. Built-in packages cover structural Lisp editing and lookup (sexed, slop), web access (netcons), TODO/project management (organa), self-observation (speculum), testing (prove), subagents (subagent), deterministic pipelines (pipelines), Git workflows (git), prompt templates (templata), model-routing helpers (modelaria), artifacts (artifactum), structured user questions and message queues (quaestor), and MCP bridging (mcp-bridge).

Packages can be installed without being enabled. Enablement is scoped per global config, agent, or buffer. Use M-x package-dashboard-command for a presentation-driven browser, or M-x minibuffer-toggle-package-command to cycle enablement from the selector.

Project Resources

Project resources back file buffers, structured project APIs, and package-aware workflows. They coexist with direct provider tools like read, write, and edit. Project definitions can be registered from ~/.rplaca.d/init.lisp with define-project, and inert manifest files are autoloaded from *project-definitions-directory*, which defaults to ~/.rplaca.projects.d/. The user configuration directory is always available as the config project unless init defines config first.

(define-project "rplaca" :root #P"/path/to/rplaca/")
(create-project "scratch-work" :root #P"/tmp/scratch-work/" :persist nil)
(list-projects)
(project-list-files "rplaca")
(project-search-to-string "rplaca" "send-message")
(project-read-file "rplaca" "src/main.lisp")
(project-save-file "rplaca" "notes.org" "* Notes\n")
(project-open-file "rplaca" "src/main.lisp")

Open project resources become editable file buffers. C-x C-f opens a project file, C-x p selects the current buffer’s project, and C-x C-s saves a file buffer back to its project resource. Programmatic project writes synchronize any already-open file buffer for the same resource; agents can inspect unsaved buffer state with (file-buffer-dirty-p BUFFER).

For durable coding work, agents should prefer transactional change sets. A change set stages project edits, exposes a Lisp-readable diff, and can be applied, discarded, or reverted:

(let ((cs (begin-change-set :name "demo-edit")))
  (stage-project-file "rplaca" "notes/demo.lisp" "(defun demo () :ok)"
                      :change-set cs)
  (change-set-diff-to-string cs)
  (apply-change-set cs))

Projects can also expose validation and reload hooks through define-project:

(define-project "rplaca"
  :root #P"/path/to/rplaca/"
  :systems '("rplaca" "rplaca/tests")
  :check-functions
  (list (lambda (project)
          (declare (ignore project))
          ;; Project-specific checks can call FiveAM, ASDF, or other Lisp APIs.
          :ok)))

(run-project-checks "rplaca")
(reload-project-system "rplaca")

Agent Structural Editing

RPLACA includes sexed, an agent-oriented s-expression editor inspired by Paredit but exposed as Lisp functions instead of cursor commands. It lets agents inspect, select, and edit Lisp forms by structure while rejecting edits that would leave unbalanced text.

(sexed-project-outline-to-string "rplaca" "src/main.lisp" :head "defun" :limit 20)
(sexed-project-form-text "rplaca" "src/main.lisp"
                         '(:head "defun" :name "ensure-scratch-buffer"))
(sexed-replace-project-form
 "rplaca"
 "src/foo.lisp"
 '(:head "defun" :name "foo")
 "(defun foo () :ok)")

(sexed-stage-replace-project-form
 "rplaca"
 "src/foo.lisp"
 '(:head "defun" :name "foo")
 "(defun foo () :better)")
(change-set-diff-to-string)
(apply-change-set)

(ensure-scratch-buffer)
(setf (scratch-buffer-text) "(workspace (todo alpha) (todo beta))")
(sexed-replace-scratch-form '(:head "todo" :nth 1) "(done beta)")
(sexed-insert-after-scratch-form '(:head "done") "(note \"checked\")")
(scratch-buffer-text)

Selectors are plists such as (:head "defun" :name "foo"), (:head "let*" :nth 0), or (:id 7). The :nth selector is zero-based. Pure functions such as sexed-replace-form return edited strings; project, file, message, and scratch adapters validate the full result before mutating resources or editable RPLACA messages. Insert helpers add separator whitespace when adjacent forms would otherwise touch.

McCLIM Chat Interface

The current McCLIM interface is a small presentation-based chat frame. It has a transcript pane, a Drei-backed compose pane, an info line, and a minibuffer pane for prompts and fuzzy completion rows. Transcript messages are CLIM presentations, and middle-clicking a message opens a one-pane metadata help window.

Editing Keybindings

KeyAction
C-a / C-eBeginning / end of line
C-f / C-bForward / backward char
M-f / M-bForward / backward word
C-dDelete char forward
BackspaceDelete char backward
C-kKill line
C-uKill backward line
M-dKill word forward
C-wKill word backward
C-yYank (paste)
M-yYank pop (cycle kill ring)
C-oInsert newline
EscStop active LLM response
RETSend message

Buffer Management

KeyAction
C-x nNew buffer
C-x lNew listener buffer
C-x kKill buffer
C-x bBuffer selector (minibuffer)
C-x C-bBuffer selector (minibuffer)
C-x C-sSave session
C-x C-rLoad session
C-x tSession tree
C-x TFork session
C-x C-cQuit

Window Management

KeyAction
C-x 2Split window below
C-x 3Split window right
C-x 0Delete selected window
C-x 1Delete other windows
C-x oSelect other window

Navigation

KeyAction
Page-Up / M-vScroll up (history)
Page-Down / C-vScroll down

Model & Provider

KeyAction
C-c ASelect agent (fuzzy minibuffer)
C-c C-mSelect model (fuzzy minibuffer)
C-c MSelect model (overlay)
C-c C-rSelect think level (fuzzy minibuffer)
C-c tToggle tool result display
C-c VToggle reasoning display
C-c IToggle metadata display
C-c C-dToggle API debug mode

Help & Introspection

KeyAction
M-xFuzzy execute command
C-h bDescribe keybindings
C-h fDescribe function
C-h vDescribe variable
C-h iInfo directory
C-h IRPLACA manual buffer
C-h TDescribe type
C-h FCustomize face (interactive)
C-h DMcCLIM debug status

Shell Prefix

Type ! followed by a shell command (e.g., !ls -la) to execute it directly. Output is inserted as a system message in the buffer without being sent to the agent. You can then discuss the output with the agent by sending a follow-up message.

Streaming Responses

Agent responses stream in real time via background threads. The event loop polls for updates, so you see tokens as they arrive.

Face System

RPLACA has a full face (color/attribute) system inspired by Emacs:

  • Per-sender color schemes (user messages on blue, agent on black, system in cyan)
  • Global faces for modeline, minibuffer, selectors, structured question prompts, and diff highlighting
  • Face inheritance chains with full resolution
  • Interactive face customization via C-h F
  • Supports CGA (16-color), 256-color, and hex (#RRGGBB) color specs

Bitmap Font Editor

The bitmap font editor saves its native editable format as .rplacafont using write-rplacafont-file and read-rplacafont-file. It can import legacy .clawfont files for compatibility, but saves only the canonical RPLACA format. BDF and CADR AST font import remain available through read-font-file.

Session Persistence

Chat buffers attach durable sessions by default. C-x C-s writes a manifest, and transcripts are appended to on-disk JSONL sidecars under ~/.config/rplaca/sessions/. Session history is branch-shaped rather than a flat list, and the session tree can be navigated or forked from the UI. On compaction, transcript writing rolls to a new segment and records the previous segment location. Snapshots are also autosaved as chat buffers change, and each interactive chat buffer shows its current full system prompt as a synthetic header at the top of the transcript.

Scratch Buffer

RPLACA keeps a process-local *scratch* buffer loaded in the buffer list for temporary notes and programmatic coordination. It is editable, never killed by normal buffer-kill commands, and lasts only until RPLACA exits.

Listener and Manuals

RPLACA includes a Lisp listener buffer (C-x l) for direct evaluation, comma commands, and #!-prefixed shell commands, plus an Info/manual browser. C-h i opens the system Info directory, and C-h I opens the RPLACA manual buffer placeholder or manual when one is installed.

McCLIM Interface

RPLACA runs as a McCLIM application frame with a presentation-rich transcript pane, a Drei-backed compose pane, an info line, and a minibuffer pane. The transcript uses CLIM output records, message presentations, and incremental redisplay; the minibuffer uses normal CLIM stream output for prompt and candidate rows. Async provider updates are bridged back into McCLIM by queueing a small frame event that calls redisplay-frame-pane for the transcript.

Boot Files

RPLACA loads optional markdown files at startup to customize the agent’s system prompt:

  • AGENTS.md, SOUL.md, USER.md, IDENTITY.md, TOOLS.md

Checked in the working directory first, then ~/.config/rplaca/. Compatible with OpenClaw workspace conventions.

Installation

Prerequisites

  • GNU Guix (for the containerized environment)
  • An X11 display (for the McCLIM application)

Running

./run.sh
./run.sh --clean-build

This launches RPLACA in a Guix container with all dependencies (SBCL, McCLIM, OpenSSL, fonts) automatically provisioned. Quicklisp is bootstrapped on first run.

A native, non-container launcher is also available for machines with local SBCL and GUI dependencies installed:

./run-native.sh

Native runs need SBCL, a Quicklisp-compatible setup file, OpenSSL, X11 client libraries, fontconfig/freetype/Xft-style font libraries, and TrueType fonts. The native launcher uses the Ultralisp Quicklisp-compatible distribution via scripts/run-ultralisp.lisp, so it can resolve newer Common Lisp dependencies without entering the Guix container. It also constrains McCLIM’s native TrueType scan to the DejaVu font bundle by default; set RPLACA_TRUETYPE_FONT_PATH to a compact font directory if you need an override. Set RPLACA_SBCL_DYNAMIC_SPACE_SIZE to adjust SBCL’s heap size in MiB; the default is 2048.

By default, run.sh reuses cached ASDF/FASL build artifacts for fast startup. Pass --clean-build, or its alias --force-clean-build, to clear the local Common Lisp output cache before loading RPLACA.

For one-shot, non-interactive harness checks, use prompt.sh. It runs inside the same Guix container and prints only the final assistant response to stdout by default:

./prompt.sh 'Use lisp_eval with mode="isolated" to inspect the current default provider.'
./prompt.sh --show-tools --show-metadata "List the exported rplaca functions."
./prompt.sh --json --provider openai-codex --model gpt-5.6-sol "Summarize this repo."
./prompt.sh --clean-build "Run this prompt after clearing cached Lisp build artifacts."
./prompt.sh --isolated --show-tools "Exercise project APIs without user config state."
./prompt.sh --isolated --skill-root /tmp/my-skills 'Use $demo to answer.'

Plain prompt.sh runs default to openai-codex/gpt-5.6-sol. Passing --agent lets that agent’s routing decide unless --provider or --model is also supplied. Interactive run.sh sessions use the normal RPLACA defaults and any overrides from ~/.rplaca.d/init.lisp.

By default, prompt.sh reuses cached ASDF/FASL build artifacts for fast iterations. Pass --clean-build, or its alias --force-clean-build, to clear the local Common Lisp output cache before loading RPLACA. If no prompt argument is supplied, prompt.sh reads non-interactive stdin.

Live harness regression probes are available through:

scripts/prompt-probes.sh
scripts/prompt-probes.sh --only transaction --keep-logs

GUI E2E runs are opt-in and documented in docs/GUI-E2E.md:

./scripts/run-gui-e2e.sh --preflight-only
./scripts/run-gui-e2e.sh --suite smoke
./scripts/run-gui-e2e.sh --suite mx
./scripts/run-gui-e2e.sh --suite features
./scripts/run-gui-e2e.sh --suite keybinds
./scripts/run-gui-e2e.sh --suite reload
./scripts/run-gui-e2e.sh --suite stability

The crash and lifecycle hardening plan, implemented findings, residual risks, and validation evidence are recorded in docs/STABILITY.md. Suspected framework failures are classified separately, with minimal controls and a strict fork threshold, in docs/MCCLIM-ISSUES.md.

The probes run through prompt.sh --isolated, write logs under /tmp, and check real agent behavior for docs discovery, skill loading, transactional project edits, and scratch/file-buffer workflows. By default they use openai-codex/gpt-5.6-sol first, then retry with openrouter/openai/gpt-5.6-sol if the primary provider fails.

Provider Credentials

Set provider credentials through the supported provider-specific sources. Z.AI and OpenRouter can use environment variables:

export ZAI_CODING_MAX_API_KEY="..."
# or
export OPENROUTER_API_KEY="..."

OpenAI Codex uses the shared Codex auth store or a token file as described above.

Interactive sessions default to zai/glm-5.2. The model selector retains older GLM releases for explicitly pinned configurations; GLM-5.2 exposes its documented high and max reasoning-effort choices.

OpenRouter model discovery uses its live /api/v1/models catalog when an API key is configured. Without a successful live fetch, RPLACA falls back to a curated tool-capable catalog containing the GPT-5.6 family, GLM-5.2, current Claude models, and current Gemini Flash models. Anthropic and Google models are OpenRouter routes; RPLACA does not provide direct adapters for them.

User Init File

Customize RPLACA by creating ~/.rplaca.d/init.lisp:

;; Switch default provider and model
(setf rplaca:*default-provider* :openai-codex)
(setf rplaca:*default-model* "gpt-5.6-sol")

;; Override the default personality prompt or point to a different prompt file
(setf rplaca:*default-personality-prompt* "You are a pragmatic pair programmer.")
(setf rplaca:*personality-prompt-path* #P"/home/tay/.config/rplaca/pair-system-prompt.txt")
(rplaca:load-personality-prompt-file)

;; Register custom agents for the C-c A selector
(rplaca:register-agent-definition
 "writer"
 :provider :openai-codex
 :model "gpt-5.6-sol"
 :think-level "high"
 :core-prompt "Use lisp_eval only with mode=isolated for concrete work. Inspect first, then modify."
 :personality-prompt "You are a concise writing partner focused on structure and tone.")

;; Register additional skill roots or programmatic skills
(rplaca:register-skill-root #P"/home/tay/.local/share/rplaca/skills/")
(rplaca:register-skill-definition
 "repo-review"
 :description "Review Common Lisp changes in this repository."
 :contents
 (format nil "---~%name: repo-review~%description: Review Common Lisp changes.~%---~%Use project APIs, run tests, and report risks first."))

;; Customize seeded defaults after startup initialization
(rplaca:add-hook
 'rplaca:*startup-hook*
 (lambda ()
   (rplaca:keymap-bind rplaca:*default-keymap*
                         '(:ctrl-c #\z)
                         'rplaca:toggle-debug-mode-command)))

;; Customize the initial buffer before the McCLIM interface starts
(rplaca:add-hook
 'rplaca:*initial-buffer-hook*
 (lambda (buffer)
   (rplaca:buffer-insert-system-message
    buffer
    "init.lisp customized this buffer")))

Packages

Install and load a package from a git repository in ~/.rplaca.d/init.lisp:

(rplaca-use-package :src-type :git
                      :repo "https://example.com/user/cool-plugin.git")

Packages are cloned into ~/.rplaca.d/packages/ and must contain a manifest.lisp file at the repo root. The manifest is a property list that declares the package name and entrypoint file:

(:name "cool-plugin"
 :entrypoint "cool-plugin.lisp")

After installation, enable packages globally, per agent, or per buffer. The fastest user-facing path is the package dashboard:

(package-dashboard-command (current-buffer))
(set-package-enablement-scope "cool-plugin" :global)

Architecture

src/
  packages.lisp        Package definition and exports
  faces.lisp           Color specs, faces, face sets, global registry
  message.lisp         Line/message data structures, editing operations
  buffer.lisp          Buffer ring, conversation management, display state
  session.lisp         Durable sessions, transcripts, branch tree
  commands.lisp        defcommand macro and command metadata
  keymap.lisp          Keymap data structure, default bindings
  windows.lisp         Logical window model and split management
  projects.lisp        Project/resource registry, manifests, file buffers
  skills.lisp          Codex-style local skill discovery and resource access
  matching-core.lisp   Fuzzy matching implementation
  matching.lisp        Common Lisp wrappers for minibuffer matching
  minibuffer.lisp      Selector and minibuffer state machines
  llm.lisp             Multi-provider LLM integration, streaming, OAuth
  tools.lisp           Agent tool definitions and execution
  prefix.lisp          Chat prefix command dispatch (for =!shell= and peers)
  package-manager.lisp Package discovery, install, enablement, prompt injection
  pipelines.lisp       Deterministic pipeline runtime
  info.lisp            Texinfo/Info browser support
  hooks.lisp           Hook and advice system
  sexed.lisp           Agent-oriented structural s-expression editing
  slop.lisp            Common Lisp symbol lookup and reference analysis
  listener.lisp        In-buffer Common Lisp listener state and commands
  prompt-core.lisp     Shared prompt harness option parsing and defaults
  prompt-runner.lisp   Non-interactive prompt/session execution path
  session-export.lisp  HTML session export and share handlers
  mcclim-interface.lisp Fresh presentation-based McCLIM chat frame
  debug.lisp           Debug logging helpers
  main.lisp            Commands, key dispatch, startup, prompt entrypoints
  docs.lisp            Extended documentation entries

The core architecture follows Emacs patterns: buffers hold messages (not file lines), keymaps dispatch to commands, and commands operate on buffer state.

vs Emacs

RPLACA is not intended to replace GNU Emacs. It is another of the Emacsen — built from the same philosophical foundations (extensibility, introspection, Lisp as the extension language) but purpose-built for conversational agent interaction rather than text editing.

Where Emacs operates on files and buffers of text, RPLACA operates on conversations and messages. RPLACA now has both M-x for user-facing interactive commands and an agent that can eval arbitrary Lisp. The feedback loop is the same; the domain is different.

Future Plans

RPLACA takes its name from Common Lisp’s destructive list update operation. It is designed to bridge the gap between current agentic editors and OpenClaw by providing a truly introspective and recursive agentic editor — one where the agent can modify the editor itself, inspect its own tool definitions, customize its own rendering, and evolve the environment collaboratively with the user.

License

RPLACA itself is licensed under the GNU Affero General Public License v3.0 only. See LICENSE for the full text.

Third-party vendored material may use different licenses. In particular, vendor/cl-community-spec is kept under its upstream MIT license; see vendor/cl-community-spec/LICENSE.

About

An LLM-augmented Lisp machine environment built with Common Lisp and McCLIM.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages