ECA Agent Guide (AGENTS.md)
ECA (Editor Code Assistant) is a Clojure server that editors talk to over stdin/stdout JSON-RPC to provide AI coding features (chat, tools, MCP, agents). Shipped as a GraalVM native binary.
-
Architecture (request flow: editor → JSON-RPC stdio →
handlers→features→llm_api→llm_providers→ streamed back viamessenger):src/eca/server.clj/src/eca/main.clj: stdio server entrypoint / CLI interface.src/eca/handlers.clj: entrypoint for all protocol methods, dispatches to features.src/eca/features/: high-level capabilities —chat.clj(+chat/lifecycle, history, tool-calls),tools.clj(+tools/built-in tools: filesystem, shell, git, mcp, task…),agents.clj,skills.clj,rules.clj,hooks.clj,commands.clj,context.clj,prompt.clj,login.clj,plugins.clj.src/eca/llm_api.clj: façade used by features to call an LLM; vendor adapters insrc/eca/llm_providers/(anthropic, openai, google, ollama, copilot…).src/eca/db.clj: in-memory state (sessions, chats, MCP);src/eca/config.clj: config resolution from multiple sources.src/eca/messenger.clj: sends requests/notifications back to the client;src/eca/remote/: remote HTTP/SSE server mode.src/eca/nrepl.clj: starts an nREPL when nrepl/cider-nrepl are on the classpath (i.e. thebb debug-clibuild; no flag needed) — port is logged to stderr, and you can eval against the live process.
-
Build (requires Clojure CLI + Babashka):
- All-in-one debug CLI (JVM, nREPL):
bb debug-cli - Production CLI (JVM):
bb prod-cli| Production JAR:bb prod-jar - In production we use a native image (GraalVM,
GRAALVM_HOMEset):bb native-cli
- All-in-one debug CLI (JVM, nREPL):
-
Test (Kaocha via
:testalias):- Run all unit tests:
bb test(same asclojure -M:test) - Run a single unit test namespace:
clojure -M:test --focus eca.main-test - Run a single unit test var:
clojure -M:test --focus eca.main-test/parse-opts-test - Run all integration tests (requires built
./ecaoreca.exeat repo root):bb integration-test - Run a single integration test:
bb integration-test --dev --ns integration.chat.mcp-remote-test --devruns the server from source via theclojureCLI (no binary build needed);--list-nslists test namespaces;--proxyroutes via Tinyproxy (must be installed).- Integration tests use mock LLM/MCP servers (
integration-test/llm_mock,mcp_mock) — no API keys needed.
- Run all unit tests:
-
Lint/format:
- Lint:
clj-kondo --lint src test dev integration-test - Formatting not enforced in CI; follow idiomatic Clojure.
.cljfmt.edndefines extra indents:taskandfuture*are[[:inner 0]].
- Lint:
-
Namespaces/imports:
- One file per
ns; always(set! *warn-on-reflection* true)near top. - Group
:requireas: Clojure stdlib, third‑party, theneca.*; sort within groups. - Prefer
:asaliases; avoid:referexcept in tests (clojure.testand what you use).
- One file per
-
Naming/types/data:
- Internal Clojure names and domain keys use kebab-case (
chat-id,tool-call-id,parent-chat-id). - Client protocol/config JSON uses camelCase (
initializationOptions,toolCall,contentReceived); keep conversions at boundaries (shared/map->camel-cased-map, config normalization). - Provider/MCP payloads mirror vendor specs and may use mixed conventions (
input_tokens,function_call,inputSchema); do not “fix” these to Clojure style. - Hook script stdin uses top-level snake_case for shell ergonomics (
hook_name,chat_id,db_cache_path), while nested tool data may keep its original shape. -
- Add type hints only to remove reflection where it shows up.
- Internal Clojure names and domain keys use kebab-case (
-
Errors/logging/flows:
- Use
ex-infowith data for exceptional paths; return{:result-code ...}maps from CLI flows. - stdout is the JSON-RPC channel — never print to stdout; use
eca.logger/error|warn|info|debug(stderr-based) for all app logs. - If a chat-scoped function contains any
logger/...call, wrap the relevant body inlogger/with-chat-context. Pass bothchat-idand the current chat’sparent-chat-id. - Consider wrapping chat-scoped functions that call downstream code known to log, or that start
future*work whose logs should be attributed to the chat. If there is no downstream logging,with-chat-contextis unnecessary; instead, consider whether the function should log an important chat lifecycle event.
- Use
-
Tests:
- Use
clojure.test+nubank/matcher-combinators; keep tests deterministic. - Put shared test helpers under
test/eca/test_helper.clj. - CI runs Linux, macOS, and Windows — use
eca.test-helper/file-path/file-urifor Windows-safe paths in tests.
- Use
-
General:
- Use concrete Java class type hints when they prevent GraalVM/reflection issues.
- If changing dependency inputs in
deps.ednorbb.edn, runnix develop --command deps-lock-update; PR CI fails if this leaves adeps-lock.jsondiff. - ECA's protocol specification of client <-> server lives in docs/protocol.md
- If changing ECA config structure, remember to update its docs/config.json
- When adding support to a new feature or fixing an existing GitHub issue, add a concise Unreleased
CHANGELOG.mdentry (max 180 chars, issue number if known).