Harmless is an AI coding harness that lives entirely inside Emacs. It is written in Emacs Lisp. It is not gptel, and it does not wrap Grok Build, Claude CLI, or any other external agent.
Conceptually, Harmless is similar to Claude Code, or OpenAI Codex, or Grok Build, among many other examples, but is not built upon any of those, and lives inside of your Emacs.
Several sessions can run at once in one Emacs, each pinned to a provider and model. Most people will configure a single provider (xAI, OpenAI, Anthropic, or a local OpenAI-compatible server). Mixing is optional: one session on Grok 4.6, another on a local LLM, a third on Claude, each in its own project.
Requires GNU Emacs 31.
(add-to-list 'load-path "~/programming/emacs/harmless/lisp")
(require 'harmless)Browser login is the same flow as Grok Build: Harmless opens auth.x.ai,
you sign in, and tokens are stored in auth.json under
harmless-directory (mode 0600). They refresh automatically.
(setq harmless-providers (list (harmless-make-xai))
harmless-default-model "grok-4.6"
harmless-default-reasoning-effort "xhigh")Then M-x harmless-login. That command is the shared entry for every
provider login. With more than one named connection it asks which
account. Prefix argument (C-u M-x harmless-login) uses xAI's
device-code flow, which is also used automatically if port 56121 is
already taken (for example by Grok Build itself).
Several accounts on the same vendor are separate connections: give each
a name. The default names (xAI, Anthropic, OpenAI) keep the
original token files (auth.json, auth-anthropic.json,
auth-openai.json). Extra connections get auth-<slug>.json.
(setq harmless-providers
(list (harmless-make-xai "xAI")
(harmless-make-xai "xAI work")
(harmless-make-anthropic "Anthropic")
(harmless-make-anthropic "Anthropic work")
(harmless-make-openai))
harmless-default-provider-name "xAI"
harmless-default-model "grok-4.6"
harmless-default-reasoning-effort "xhigh")Then M-x harmless-login and pick xAI work (or Anthropic work)
to sign that account in. Sessions pin to a connection name, so one
buffer can stay on personal Grok while another uses work Claude.
Anthropic / Claude uses Claude Code's OAuth client. The browser shows
a code; paste it at the minibuffer prompt. Tokens go in
auth-anthropic.json under harmless-directory. If you already ran
claude login, Harmless will reuse a still-valid
~/.claude/.credentials.json access token (it never writes or
refreshes that file).
If you already ran grok login, Harmless will reuse ~/.grok/auth.json
until you sign in or out from Emacs. It never writes that file.
An API key still works as a fallback when no OAuth session is active:
put it in ~/.authinfo (machine api.x.ai login apikey password xai-...)
or export XAI_API_KEY. Settings can live in your init file or in
harmless/config.el under user-emacs-directory (typically
~/.emacs.d/harmless/config.el).
OpenAI:
(setq harmless-providers (list (harmless-make-openai))
harmless-default-model "gpt-5.5")M-x harmless-login → OpenAI opens ChatGPT OAuth (Codex CLI's
public client, loopback on localhost:1455). Prefix argument pastes
the redirect URL instead, which is also used if that port is busy
(for example Codex CLI itself). Tokens go in auth-openai.json. A
still-valid ~/.codex/auth.json is reused (never written). ChatGPT
subscription tokens are sent to the Codex Responses API
(chatgpt.com/backend-api/codex/responses), not api.openai.com.
An OPENAI_API_KEY still works as a usage-based fallback on Chat
Completions.
Anthropic:
(setq harmless-providers (list (harmless-make-anthropic "Anthropic"))
harmless-default-model "claude-sonnet-4-6")Local OpenAI-compatible server (Ollama, llama.cpp, vLLM, …):
(setq harmless-providers
(list (harmless-make-openai-compat
"local"
:host "127.0.0.1:11434"
:protocol "http"
:endpoint "/v1/chat/completions"
:key "none"
:models '("llama3.2"))))If nothing is configured, M-x harmless walks through a short setup.
| Command | What it does |
|---|---|
M-x harmless |
Open or resume this project's session |
M-x harmless-new |
Start a new session (picks a model) |
M-x harmless-chat |
Start a chat with no project and no working directory |
M-x harmless-dashboard |
All sessions, grouped by project |
M-x harmless-switch |
Jump to a live or saved session |
M-x harmless-menu |
Transient: new / switch / model / effort / permissions / abort |
M-x harmless-info |
Open the Info manual (h in the transcript, dashboard, usage buffer, or menu) |
M-x harmless-set-reasoning-effort |
Set low / medium / high / xhigh |
Click xAI/grok-4.6 (xhigh) in the session header |
Provider, then model, then effort |
M-x harmless-login |
Sign in (xAI, Anthropic, or OpenAI) |
M-x harmless-logout |
Sign out of a provider |
M-x harmless-abort |
Cancel the in-flight turn or shell |
M-x harmless-reload-all-harmless |
Reload the Lisp checkout into this Emacs |
In the prompt window: C-c C-c sends. When a tool needs approval: y allow,
n deny, ! always allow that class for the rest of the session.
In the transcript, a tool run is a collapsed line. TAB toggles the
entry at point, Left closes it, Right opens it, and S-TAB toggles
every entry. A run of reads and searches is one explored line.
Permission modes: ask (default), accept-edits, always-approve. Shell
commands still prompt in accept-edits.
Each session is a read-only transcript buffer plus a small prompt window at
the bottom. The dashboard is a tabulated list. Config and sessions live
under harmless-directory, which defaults to harmless/ in
user-emacs-directory (so ~/.emacs.d/harmless/ for a typical setup).
The buffer is a view, not the source of truth.
Harmless reads AGENTS.md, HARMLESS.md, and .harmless/HARMLESS.md
from the session directory and from each parent directory up through
your home directory. CLAUDE.md is included too, according to
harmless-claude-md: nil never, t always, and anthropic (the
default) only when the session model id starts with claude-. Each
file applies to its directory and everything under it. Outer
directories come first. At each directory, CLAUDE.md comes first
when it is included, then AGENTS.md, then HARMLESS.md, then the
file inside .harmless/. When two files disagree, the later one wins.
The text is sent with every turn and is not stored in the transcript.
A skill is a directory with a SKILL.md file. The file starts with
name and description frontmatter. Each turn lists the name,
description, and path. The model reads that file with read_file
when the task fits. The body is not sent until then.
Harmless looks for skills/ inside these directories, at every level
from your home directory down to the session. A skill closer to the
session replaces one of the same name farther away. In a single
directory, later entries in this list replace earlier ones:
| Directory | Whose layout |
|---|---|
.agents/skills/, .codex/skills/ |
Codex |
.claude/skills/ |
Claude Code |
.grok/skills/ |
Grok Build |
.harmless/skills/ |
Harmless |
/etc/codex/skills is included when it exists, at the lowest priority.
Codex's own bundled skills are not on disk and are not read.
Memory is Markdown under harmless-directory, in memory/. Global
notes apply to every project. Workspace notes belong to one project
directory. A new fact goes to observations/_inbox/. M-x harmless-dream folds the inbox into topics/ and moves those files
to archive/. MEMORY.md is a generated index.
Each turn sends the index, not the notes. The model reads a note with
memory_read, saves a fact with memory_remember, and replaces a
topic with memory_write_topic. M-x harmless-remember saves a note
directly. A prefix argument stores it in global memory.
M-x harmless-plan turns on plan mode for the current session. A
prefix argument turns it off. The same command is P in the Harmless
menu. The model may also ask to enter plan mode.
In plan mode, write_file and replace fail. The model writes
plan.md in the session directory with write_plan, then calls
exit_plan_mode. That shows the plan and asks you to approve it,
send revision notes, or quit. Approving turns plan mode off so the
same turn can start changing project files. Shell commands are not
inspected for writes.
The session header shows plan while the mode is on. The flag is
saved with the session.
M-x harmless-usage shows the current session and one row per
connection. The session block is the model's context window, the last
request's prompt size, and what remains in the window, then the running
prompt and completion totals for this chat. Window sizes live in
harmless-usage-context-windows. The account block is the last
rate-limit remainder each provider sent. A connection with no such
headers says so. This is not an account credit balance.
make test # byte-compile with warnings as errors, then ERT
make compile
make clean3-clause BSD. See LICENSE. Copyright (c) 2026 Christopher Mark Gore, Soli Deo Gloria.
