How efr works
This page shows the parts of efr and how a prompt moves through them. You do not need it to use efr. It helps you understand what you see, and where to look when something does not work.
The parts
zsh + efr.plugin.zsh
| runs efr for each , line
v
efr
| Unix socket
v
efrd
|-- the model provider
|-- the database
`-- a hidden zsh for each
conversation
`-- efr-sbx (auto)
- The zsh plugin (
efr.plugin.zsh) watches your shell. For each,line, it runsefr. It reads your directory and your recent commands, but it runs no git and decides nothing. efris the command line client. It sends a request to the daemon and shows the answer, such as a reply that streams. It is a thin relay: the daemon holds the state.efrdis the daemon. It runs as the systemd user serviceefrd.service. It owns the conversations, the model loop, the hidden shells and the database.efr-sbxis the launcher of theautosandbox. efrd and the hidden shells run it. It is never on yourPATH.
efrd talks to the model API directly. It does not wrap another coding agent.
The path of a prompt
- You type
, why does cargo test fail?in~/p/app. - The plugin starts
efr. It gives efr your directory, the terminal, the last command with its exit status and the recent commands of the terminal. It puts them in the environment of efr, never in its arguments, because any user on the machine can read the arguments of a process. - efr connects to efrd over a Unix socket.
- efrd decides the scope of the prompt: the registered project that holds the directory, or the scope outside projects. It sends the prompt to this terminal's conversation of that scope.
- The conversation runs a turn. It sends the prompt to the model. When the model calls a tool, such as the shell, efrd checks the call against the permission mode and your rules. Then it runs the call, runs it in the sandbox, sends it to the reviewer model, asks you or denies it.
- efrd sends each part of the turn to efr as it happens. efr shows it below your prompt.
- When the turn ends, efr exits and your shell prompt comes back.
The turn runs in efrd, not in efr. When a turn ends in a terminal that does not follow it, the next prompt there shows one line about it.
Conversations and scopes
A conversation is the history of your prompts, the answers and the calls of the agent. efrd keeps it in its database.
A terminal has one conversation for each registered project that you worked in, and
one for all places outside projects. A registered project is a directory that you
listed with efr project add. The scope of a turn decides the conversation and what
the agent can write without a question.
Conversations, resume and search tells more.
What the agent can change depends on the target path, not on the directory where you
stand. In cautious, a write in the registered project of the turn runs with no
question. A write to a file in your home directory, outside the project, asks you. It
asks also when you send the prompt from inside the project.
The hidden shell
Each conversation has its own hidden zsh. It is a real zsh with a terminal engine
behind it, so the agent sees a rendered screen, not raw escape codes. efrd uses the
engine of Ghostty (libghostty-vt) when the build has it. If not, it uses the vt100
engine. The screen key of config.toml can choose vt100. efr status shows which one runs.
The hidden shell stays between calls, so a cd of one call holds for the next call.
When a command asks for input, efr shows the question below the reply, and you answer
it there.
efrd closes a hidden shell that waited at its prompt, with no input, no output and no
terminal that follows it, for 60 minutes
(shell.idle_minutes; 0 keeps it). A hidden shell ends when
efrd stops. The hidden shell tells more.
Permissions
Each turn runs in one of four permission modes:
manual: everything asks you, except what your rules allow.cautious(the default): reads, read-only commands and writes in the project and in$SCRATCHrun with no question. Other actions ask you.auto: each command runs in a kernel sandbox. An action that leaves the sandbox goes to a reviewer model or to you.full: commands run as you, with no sandbox. efr asks only before a few actions, such assudoor a delete of~. You must allow this mode inconfig.tomlfirst.
Permission modes tells more.
Where efr keeps its files
efr has four roots:
- config,
~/.config/efrby default:config.tomlandprojects.toml. - data,
~/.local/share/efrby default: the database, the scratch directories, the snapshots for undo and the logins. - state,
~/.local/state/efrby default: the logs, the model catalog and the state of the sandbox. - runtime,
$XDG_RUNTIME_DIR/efrby default: the socket, the notices for each terminal and the files of each call.
EFR_CONFIG_DIR, EFR_DATA_DIR, EFR_STATE_DIR and EFR_RUNTIME_DIR move one root.
EFR_HOME moves all four. efr paths shows each root and where it came from.
Settings and Storage tell more.
The protocol
efr and efrd speak one protocol over the socket daemon.sock in the runtime root
($XDG_RUNTIME_DIR/efr/daemon.sock by default).
The socket has mode 0600, so only you can connect to it. Each frame is a 4-byte length
and one JSON object. Each side first sends its protocol version. When the versions do
not match, efr tells you that efr and efrd come from different builds.
Protocol tells more.
When efrd starts
efrd does these steps in this order:
- It takes a lock, so that only one efrd runs for each data root.
- It loads
config.toml. - It copies the database to a backup, then it updates the database to the new version.
- It cleans up what the last efrd left. It cancels a turn that ran, and a question that waited expires. A prompt that waited in the queue does not run, and its terminal gets a notice to send it again. Nothing continues on its own.
- It starts the providers, the tools and the shells. It checks whether the
autosandbox can run on this machine. - It opens the socket.
- It tells systemd that it is ready.
While efrd restarts, efr finds no daemon. It exits with code 3, and you can send the prompt again. A restart ends every hidden shell, and the commands that run in it.
Next steps
- Permission modes tells what the agent can do in each mode.
- The sandbox tells how the
automode works. - Architecture is the map of the code.