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.
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.
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 follows Codex’s own credential layout:
~/.config/rplaca/openai-codex-tokenis 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/callbackand 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 useshttps://api.openai.com/v1.
The default built-in agent tool surface follows Pi-style coding tools, with provider tools as the primary action surface:
| Tool | Description |
|---|---|
read | Read text files |
find | Search files by name or wildcard |
grep | Search file contents by literal text |
write | Create or overwrite text files |
edit | Replace exact text in text files |
lisp_eval | Evaluate Common Lisp in an isolated worker (mode=isolated required for provider calls) |
recovery_list | Inspect 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.
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.
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 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")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.
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.
| Key | Action |
|---|---|
C-a / C-e | Beginning / end of line |
C-f / C-b | Forward / backward char |
M-f / M-b | Forward / backward word |
C-d | Delete char forward |
Backspace | Delete char backward |
C-k | Kill line |
C-u | Kill backward line |
M-d | Kill word forward |
C-w | Kill word backward |
C-y | Yank (paste) |
M-y | Yank pop (cycle kill ring) |
C-o | Insert newline |
Esc | Stop active LLM response |
RET | Send message |
| Key | Action |
|---|---|
C-x n | New buffer |
C-x l | New listener buffer |
C-x k | Kill buffer |
C-x b | Buffer selector (minibuffer) |
C-x C-b | Buffer selector (minibuffer) |
C-x C-s | Save session |
C-x C-r | Load session |
C-x t | Session tree |
C-x T | Fork session |
C-x C-c | Quit |
| Key | Action |
|---|---|
C-x 2 | Split window below |
C-x 3 | Split window right |
C-x 0 | Delete selected window |
C-x 1 | Delete other windows |
C-x o | Select other window |
| Key | Action |
|---|---|
Page-Up / M-v | Scroll up (history) |
Page-Down / C-v | Scroll down |
| Key | Action |
|---|---|
C-c A | Select agent (fuzzy minibuffer) |
C-c C-m | Select model (fuzzy minibuffer) |
C-c M | Select model (overlay) |
C-c C-r | Select think level (fuzzy minibuffer) |
C-c t | Toggle tool result display |
C-c V | Toggle reasoning display |
C-c I | Toggle metadata display |
C-c C-d | Toggle API debug mode |
| Key | Action |
|---|---|
M-x | Fuzzy execute command |
C-h b | Describe keybindings |
C-h f | Describe function |
C-h v | Describe variable |
C-h i | Info directory |
C-h I | RPLACA manual buffer |
C-h T | Describe type |
C-h F | Customize face (interactive) |
C-h D | McCLIM debug status |
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.
Agent responses stream in real time via background threads. The event loop polls for updates, so you see tokens as they arrive.
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
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.
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.
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.
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.
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.
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.
- GNU Guix (for the containerized environment)
- An X11 display (for the McCLIM application)
./run.sh
./run.sh --clean-buildThis 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.shNative 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-logsGUI 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 stabilityThe 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.
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.
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")))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)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.
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.
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.
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.
