Emacs frontend for the pi coding agent. Two-window UI: markdown chat buffer + prompt composition buffer. Communicates with the pi CLI via JSON-over-stdio (RPC).
Eight source modules with a strict dependency chain (no cycles):
pi-coding-agent.el ← entry point, autoloads
├── pi-coding-agent-menu.el ← transient menu, session management
├── pi-coding-agent-input.el ← input buffer, history, completion
└── pi-coding-agent-render.el ← chat rendering, tool output
├── pi-coding-agent-table.el ← display-only table decoration
└── pi-coding-agent-ui.el ← shared state, faces, modes
├── pi-coding-agent-core.el ← JSON/RPC protocol
└── pi-coding-agent-grammars.el ← tree-sitter grammar recipes
External package dependency:
md-ts-mode← tree-sitter markdown major mode used by chat buffers (loadingpi-coding-agentmust not globally claim unrelated Markdown files)
menu.el and input.el are siblings — neither requires the other.
They communicate through shared variables in ui.el (e.g., --commands).
table.el and ui.el are siblings under render.el. table.el
depends on ui.el for visible-text extraction and scroll preservation.
Cross-module state mutations use accessor functions defined in ui.el
(e.g., --set-process, --set-aborted, --push-followup). Within a
module, direct setq is fine.
| File | Purpose |
|---|---|
pi-coding-agent.el |
Entry point, autoloads, --setup-session |
pi-coding-agent-core.el |
JSON parsing, line buffering, RPC protocol |
pi-coding-agent-ui.el |
Shared state, faces, customization, modes, display primitives, header-line |
pi-coding-agent-render.el |
Streaming chat rendering, tool output, fontification, diffs |
pi-coding-agent-table.el |
Display-only pipe table decoration, wrapping, overlay management |
pi-coding-agent-input.el |
Input history, isearch, send/abort, file/path/slash completion, queuing |
pi-coding-agent-menu.el |
Transient menu, session management, model selection, commands |
pi-coding-agent-grammars.el |
Tree-sitter grammar recipes, install prompts, M-x pi-coding-agent-install-grammars |
| File | Covers |
|---|---|
test/pi-coding-agent-core-test.el |
Core/RPC protocol |
test/pi-coding-agent-ui-test.el |
Buffer naming, modes, session dir, startup header, grammar install |
test/pi-coding-agent-render-test.el |
Response display, tools, diffs |
test/pi-coding-agent-table-test.el |
Table decoration, overlays, streaming, resize |
test/pi-coding-agent-input-test.el |
History, send/abort, queuing, completion |
test/pi-coding-agent-menu-test.el |
Session management, transient menu, reconnect |
test/pi-coding-agent-build-test.el |
Batch helper scripts for dependency and grammar installation |
test/pi-coding-agent-test.el |
Entry point / cross-module integration |
test/pi-coding-agent-test-common.el |
Shared fixtures: mock-session macro, toolcall helpers, fake-pi launch helpers |
test/pi-coding-agent-integration-test-common.el |
Shared integration backend helpers and contract macros |
test/pi-coding-agent-integration-test-common-test.el |
Unit tests for shared integration helper macros |
test/pi-coding-agent-integration-rpc-smoke-test.el |
Cheap shared fake/real RPC canaries |
test/pi-coding-agent-integration-prompt-contract-test.el |
Shared fake/real prompt lifecycle + abort contracts |
test/pi-coding-agent-integration-session-contract-test.el |
Shared fake/real session-file persistence contract |
test/pi-coding-agent-integration-steering-contract-test.el |
Shared fake/real steering contract |
test/pi-coding-agent-integration-tool-contract-test.el |
Shared fake/real tool execution contract |
test/pi-coding-agent-integration-test.el |
Integration suite entry point (loads all shared contract modules) |
test/pi-coding-agent-gui-tests.el |
GUI tests (require display or xvfb) |
| File | Purpose |
|---|---|
Makefile |
Build, test, lint targets |
bench/pi-coding-agent-bench.el |
Table rendering benchmark harness (xvfb GUI or batch) |
bench/run-bench.sh |
Benchmark runner script; --batch for headless lane |
bench/fixtures/tables.md |
Sample pipe tables used by the benchmark |
scripts/check.sh |
Pre-commit hook: byte-compile + lint + tests |
scripts/pi-coding-agent-build.el |
Shared batch helpers for dependency and grammar installation |
scripts/install-deps.el |
Batch script: install required Emacs package dependencies |
scripts/install-ts-grammars.el |
Batch script: install tree-sitter grammars |
Run all unit tests:
make testRun tests for a single module:
make test-core
make test-ui
make test-render
make test-input
make test-menu
make test-buildRun shared integration contracts:
make test-integration # fake + real
make test-integration-fake # fake only, fast
make test-integration-real # real onlyRun a filtered subset by ERT pattern:
make test SELECTOR=fontify-buffer-tail
make test SELECTOR=toolcall-delta
make test SELECTOR='abort\|followup'
make test-integration-fake SELECTOR=rpc-smoke
make test-integration-real SELECTOR=steering-contractThe SELECTOR value is an ERT selector string — a substring match
against test names.
make test is intentionally terse on green runs (summary-focused output).
For full raw ERT output, use:
make test VERBOSE=1
make test VERBOSE=1 SELECTOR=toolcall-deltaWhen tests intentionally trigger minibuffer message output, capture/mock
message in the test and assert on it. This keeps batch logs concise
without losing behavioral coverage.
make bench # GUI lane via xvfb (font metrics matter)
make bench-batch # batch lane (no display, faster but no font engine)The GUI lane is the primary measurement; batch is a quick sanity check.
Fixtures live in bench/fixtures/tables.md.
make lint # checkdoc + package-lint
make lint-checkdoc # docstring warnings only
make lint-package # MELPA package conventions only
make check-parens # verify balanced parentheses in all source files
make check # byte-compile + lint + all tests (= pre-commit hook)make test auto-installs Emacs package deps (transient, md-ts-mode) on first
run and caches via .deps-stamp. To force reinstall: make clean then make test.
The git pre-commit hook runs scripts/check.sh (byte-compile + checkdoc +
package-lint + all unit tests, ~12s). Install with make install-hooks.
To skip for WIP commits: git commit --no-verify
For reproducing visual bugs or testing interactive behavior, write a spike
script in ./tmp/ (gitignored) and load it into Emacs inside tmux.
Every spike script needs this boilerplate at the top (use the actual absolute path to your checkout):
(setq inhibit-startup-screen t)
(add-to-list 'load-path "/absolute/path/to/pi-coding-agent")
(require 'package)
(package-initialize)
(require 'pi-coding-agent)package-initialize is required here so installed dependencies like
md-ts-mode and transient are on load-path before require runs.
Launch with (from the project root):
tmux new-session -d -s test -x 120 -y 40 \
"emacs -nw -Q -l $PWD/tmp/spike.el 2>tmp/spike.log"
sleep 2 && tmux capture-pane -t test -pTo start a full interactive pi-coding-agent session in tmux:
tmux new-session -d -s test -x 120 -y 40 \
"emacs -nw -Q --eval \"(progn (require 'package) (package-initialize) \
(add-to-list 'load-path \\\"$PWD\\\") \
(require 'pi-coding-agent) (pi-coding-agent))\""Common gotchas:
-Qis required but skips package init — the boilerplate above fixes that- Sleep timing: use
sleep 2for UI ops,sleep 10+ for LLM responses - Buffer names follow
*pi-coding-agent-{chat,input}:<dir>*(abbreviated), e.g.*pi-coding-agent-chat:~/co/pi-coding-agent/* - Window focus: the pi-coding-agent layout has two windows;
C-x oswitches between them. Prefer spike scripts over interactivetmux send-keyswhen possible — they're reproducible, debuggable, and don't require tracking focus state
The pi CLI (TypeScript) is the reference implementation. When implementing new RPC commands, understanding event formats, or checking how the TUI handles something, consult its source.
Finding the checkout: Look for a local clone, or clone one:
PI_MONO=$(find ~/co ~/src ~/projects /tmp -maxdepth 2 -name "pi-mono" -type d 2>/dev/null | head -1)
if [ -z "$PI_MONO" ]; then
git clone git@github.com:badlogic/pi-mono.git /tmp/pi-mono
PI_MONO=/tmp/pi-mono
fiKey files (under $PI_MONO/packages/coding-agent/):
| File | When to consult |
|---|---|
docs/rpc.md |
RPC command/event format, protocol overview |
src/modes/rpc/rpc-types.ts |
Type definitions for all RPC commands and events |
src/modes/rpc/rpc-mode.ts |
How RPC commands are dispatched and events emitted |
src/modes/interactive/interactive-mode.ts |
How the TUI handles events, tool display, streaming |
src/modes/interactive/components/tool-execution.ts |
TUI tool output rendering |
src/core/agent-session.ts |
Session lifecycle, forking, message handling |
Other useful packages:
| Path | Contents |
|---|---|
$PI_MONO/packages/agent/src/agent-loop.ts |
Core agent loop, tool execution |
$PI_MONO/packages/agent/src/types.ts |
Agent-level type definitions |
$PI_MONO/packages/ai/src/types.ts |
AI provider types (messages, tools, content blocks) |
When to look: Before implementing a new RPC command, when an event format is unclear, when the Emacs behavior should match the TUI, or when debugging protocol mismatches.
Always use git add <specific-files> instead of git add -A. The latter
stages everything including spike scripts, test artifacts, and .pi/ files.
Run git status before committing to verify what's staged.
- All public symbols are prefixed
pi-coding-agent- - Internal symbols use
pi-coding-agent--(double dash) - Tests are named
pi-coding-agent-test-<description> - Test files require
pi-coding-agent-test-commonfor shared fixtures