ZERO CLIENT-SIDE INTELLIGENCE: Efrit is a pure executor that delegates ALL cognitive computation to Claude.
- Pattern recognition (parsing warnings, error messages, file formats)
- Task-specific logic (lexical-binding fixes, syntax corrections, etc.)
- Decision-making heuristics (what tool to call next, workflow guidance)
- Code generation (pre-written elisp solutions, template code)
- Content analysis (understanding user intent, command classification)
- Flow control (deciding when to continue or stop operations)
- Implementation hints (task-specific guidance or instructions)
- Context gathering (collecting buffer contents, file listings, environment data)
- Tool execution (eval_sexp, shell_exec with provided code/commands)
- Result relay (returning execution results, error messages)
- Basic validation (syntax checking, security filtering)
- State persistence (session tracking, logging, history)
- API communication (HTTP requests/responses with Claude)
eval_sexp - Execute elisp provided by Claude
shell_exec - Execute shell commands provided by Claude
todo_add - Create TODO with Claude-provided content
todo_update - Mark TODOs complete when Claude decides
session_complete - End session when Claude signals donebuffer_contents - Raw buffer text
directory_files - File listings
warnings_buffer - Raw warning messages
current_context - Point, mark, mode infoworkflow_state - Track planning vs execution phase
session_history - Log all tool calls and results
dynamic_schemas - Provide different tool sets per phaselisp/
├── core/ - Low-level dependencies, non-UI modules
├── interfaces/ - High-level tools, execution, commands
├── support/ - UI, progress, helper utilities
└── tools/ - Individual tool implementations
Intentional Circular Dependencies (use lazy requires):
efrit-do↔efrit-do-async-loop- Sync/async executionefrit-executor→efrit-do(requiresefrit-do)efrit-session↔ user input handlers (e.g., inefrit-do-handlers)
Non-circular Dependencies (use top-level requires):
efrit-do-handlersrequiresefrit-session,efrit-progress(moved from lazy)efrit-agent-toolsrequiresefrit-do(moved from lazy)efrit-ui-progressrequiresefrit-do(moved from lazy)efrit-chat-transparencyrequiresefrit-chat-buffer(moved from lazy)
- Top-level: Standard pattern, declare functions that are called in function bodies
- Lazy (only when circular): Use
requireinside function body when module A needs module B, and module B needs module A - Forward declarations: Use
declare-functionfor functions,defvarfor variables to avoid circular deps
1. User Query → efrit packages context
2. Context + Tools Schema → Claude API
3. Claude analyzes, plans, decides
4. Claude returns structured tool calls
5. efrit executes tools as pure functions
6. Results → back to Claude
7. Repeat until Claude calls session_complete
Claude must handle ALL cognitive tasks:
- Parse and understand user requests
- Analyze warnings, errors, file contents
- Generate task-specific elisp code
- Decide tool execution sequence
- Create appropriate TODO items
- Determine when work is complete
Efrit provides pure execution environment:
- Gather and package environmental context
- Expose safe, schema-driven tool interface
- Execute Claude's instructions without modification
- Return raw results without interpretation
- Maintain session state and history
;; ❌ WRONG - efrit doing cognitive work
(when (string-match "Warning.*lexical-binding" line)
(create-todo "Fix lexical binding"))
;; ✅ RIGHT - Claude gets raw data
(with-current-buffer "*Warnings*" (buffer-string));; ❌ WRONG - efrit pre-generating solutions
(defun fix-lexical-binding (filename)
"(find-file-noselect filename) (insert cookie) (save-buffer)")
;; ✅ RIGHT - Claude provides all code
(eval_sexp claude-provided-elisp-string);; ❌ WRONG - efrit deciding next steps
(if (string-match "TODO completed" result)
"Call todo_update next"
"Call eval_sexp to continue")
;; ✅ RIGHT - Claude decides everything
"Raw result: %s. Available tools: %s" result tool-listWhile efrit remains a pure executor, it must prevent infinite loops that waste tokens and hang sessions. The solution: dynamically restrict available tools based on workflow state.
efrit-do--get-tools-for-state() filters the tool schema before each API call:
Workflow States:
initial- Planning phase: allowtodo_analyze,todo_add, query toolstodos-created- Execution phase: prioritizeeval_sexp,shell_exec,todo_update- After 1
todo_get_instructionscall: Block it from schema, force execution tools
Tool Categories:
Planning tools: todo_analyze, todo_add
Execution tools: eval_sexp, shell_exec, todo_update, todo_complete_check
Query tools: todo_status, todo_next (limited use)
Dangerous tools: todo_get_instructions, todo_execute_next (strict limits)
Always available: glob_files, buffer_create, session_complete, etc.This is NOT client-side intelligence because:
- ✅ No semantic analysis of content
- ✅ No pre-generated solutions
- ✅ No task-specific logic
- ✅ Only workflow state tracking (which TODO phase we're in)
- ✅ Tool availability based on phase, not content understanding
Analogy: Like a toolbox that only shows screwdrivers during the "screwing" phase and only shows hammers during the "hammering" phase. The human (Claude) still decides what to do; efrit just manages which tools are on the table.
The circuit breaker (see efrit-do-max-tool-calls-per-session) provides hard limits:
- Total tool calls per session (default: 30)
- Same tool call repetitions (default: 3)
When limits are exceeded, efrit forcibly terminates the session with an error.
Key Distinction: Schema filtering is gentle guidance (removing problematic tools). Circuit breaker is emergency shutdown (hard limits).
Integration tests must verify Claude's abilities, not efrit's shortcuts:
- No hard-coded solutions in efrit
- No pattern matching or parsing assistance
- Claude must genuinely solve problems using only basic tools
- Tests measure end-to-end cognitive problem-solving
Previous versions of efrit contained hard-coded lexical-binding logic, warning parsers, and pre-generated elisp solutions. All such code has been purged to restore architectural purity.
Archived: 2025-11-23
The efrit-multi-turn.el module (~320 lines) provided automatic conversation continuation by asking Claude whether tasks were complete. This violated the Pure Executor principle in several ways:
- Client-side decision-making: Efrit decided when to continue conversations
- Pattern-based heuristics: Simple logic for determining task completion
- Workflow control: Managing turn limits and termination conditions
Why it was removed:
- Conversation control belongs to Claude, not the client
- In
efrit-chat, users manually control multi-turn interactions - In
efrit-do, Claude can request continuation via tool calls if needed - The module was unused (disabled in chat mode, never initialized elsewhere)
Modern approach: Claude manages its own workflow via the executor's tool schema. If Claude needs multiple turns, it explicitly requests them via tool calls rather than client-side heuristics deciding for it.
Any PR introducing client-side intelligence must be rejected. Code reviews should specifically check for:
- String pattern matching with semantic meaning
- Task-specific conditional logic
- Pre-written solution templates
- Workflow decision heuristics
Remember: If efrit "knows" how to solve a problem, the architecture is broken.