Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
949bd61
WIP first subagents working
ericdallo Feb 4, 2026
71901ca
Merge branch 'master' into subagents
ericdallo Feb 6, 2026
0cae2b1
Improve stop subagent
ericdallo Feb 6, 2026
8848ccd
Enhance protocol
ericdallo Feb 7, 2026
bbfa0c8
Remove markdown, only via config
ericdallo Feb 7, 2026
0926783
rejected tool calls
ericdallo Feb 8, 2026
3b9587d
Merge branch 'master' into subagents
ericdallo Feb 8, 2026
34b5f96
Merge branch 'master' into subagents
ericdallo Feb 9, 2026
7299a22
config
ericdallo Feb 9, 2026
2b7cf21
rename behavior -> agent
ericdallo Feb 9, 2026
d4983d8
MD support
ericdallo Feb 9, 2026
f195589
Improve requiring-resolve
ericdallo Feb 9, 2026
b181a38
max steps check
ericdallo Feb 9, 2026
29a0cc8
renames
ericdallo Feb 9, 2026
614bb7d
Merge branch 'master' into subagents
ericdallo Feb 9, 2026
011750d
docs
ericdallo Feb 9, 2026
1c14b55
add 2 subagents
ericdallo Feb 10, 2026
594ac97
Merge branch 'master' into subagents
ericdallo Feb 10, 2026
f2c4c58
fix graal
ericdallo Feb 10, 2026
15324ac
Fix tool call approval of subagents
ericdallo Feb 10, 2026
4eb0cda
Improve UI for agents summary
ericdallo Feb 10, 2026
0f2c0fb
Update label summary
ericdallo Feb 10, 2026
7bad381
Merge branch 'master' into subagents
ericdallo Feb 10, 2026
b1568e5
Merge branch 'master' into subagents
ericdallo Feb 10, 2026
c9f9eb9
Merge branch 'master' into subagents
ericdallo Feb 10, 2026
409b086
Merge branch 'master' into subagents
ericdallo Feb 10, 2026
663ebb8
Add subagentFinished hook
ericdallo Feb 10, 2026
132a5a9
Improve subagent thread handling
ericdallo Feb 10, 2026
723ec75
Fix ForkJoinPool.commonPool deadlock in LLM provider HTTP requests
ericdallo Feb 10, 2026
b48d9da
Merge branch 'master' into subagents
ericdallo Feb 10, 2026
2c1b4ab
Merge branch 'master' into subagents
ericdallo Feb 10, 2026
6c91813
Fixes by code-review agent
ericdallo Feb 10, 2026
e80a655
Merge branch 'master' into subagents
ericdallo Feb 10, 2026
9149963
explorer prompt tunning improvement
ericdallo Feb 11, 2026
6f0ef3d
Add /subagents command
ericdallo Feb 11, 2026
a2ea988
Merge branch 'master' into subagents
ericdallo Feb 11, 2026
448a4fd
Do not return subagents chats in resume
ericdallo Feb 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,5 @@ ECA Agent Guide (AGENTS.md)
- Use `clojure.test` + `nubank/matcher-combinators`; keep tests deterministic.
- Put shared test helpers under `test/eca/test_helper.clj`.
- Use java class typing to avoid GraalVM reflection issues
- Avoid adding too many comments, only add essential or when you think is really important to mention something.
- ECA's protocol specification of client <-> server lives in docs/protocol.md
62 changes: 59 additions & 3 deletions docs/config/agents.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
# Agents
# Agents / Subagents

When using ECA chat, you can choose which agent it will use, each allows you to customize its system prompt, tool call approvals, disabled tools, default model, skills and more.

There are 2 types of agents defined via `mode` field (when absent, defaults to primary):

- `primary`: Main agents, used in chat.
- `subagent`: an agent allowed to be spawned inside a chat to do a specific task and return a output to the main agent.

## Built-in agents

- `code`: default agent, generic, used to do most tasks.
- `plan`: specialized agent to build a plan before switching to code agent and executing the complex task.
| name | mode | description |
|--------------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| __code__ | primary | Default, generic, used to do most tasks, has access to all tools by default |
| __plan__ | primary | Specialized in building a plan before user switches to code agent and executes the complex task. Has no edit tools available, only preview changes. |
| __explorer__ | subagent | Fast agent specialized for exploring codebases. Finds files by patterns, searches code for keywords, or answers questions about the codebase. Read-only, no edit tools. |
| __general__ | subagent | General-purpose agent for researching complex questions and executing multi-step tasks. Can be used to execute multiple units of work in parallel. |

## Custom agents and prompts

Expand Down Expand Up @@ -37,3 +46,50 @@ You can create an agent and define its prompt, tool call approval and default mo
}
```

## Subagents

ECA can spawn foreground subagents in a chat, they are agents which `mode` is `subagent`.

Subagents can be configured in config or markdown and require `description` and `systemPrompt` (or markdown content):

=== "Markdown"

Agents can be defined in local or global markdown files inside a `agents` folder, those will be merged to ECA config. Example:

```markdown title="~/.config/eca/agents/my-agent.md"
---
mode: subagent
description: You sleep one second when asked
model: anthropic/sonnet-4.5
tools:
byDefault: ask
deny:
- my_mcp__my_tool
allow:
- eca__shell_command
---

You should run sleep 1 and return "I slept 1 second"
```

!!! info "Tool call approval"

For more complex tool call approval, use toolCall via config

=== "Config"

```javascript title="~/.config/eca/config.json"
{
"agent": {
"sleeper": {
"mode": "subagent",
"description": "You sleep one second when asked",
"systemPrompt": "You should run sleep 1 and return \"I slept 1 second\"",
"defaultModel": "anthropic/sonnet-4.5",
"toolCall": {...},
"maxSteps": 25 // Optional: to limit turns in subagent
}
}
}
```

21 changes: 20 additions & 1 deletion docs/config/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ Hooks are shell actions that run before or after specific events, useful for not
| `chatStart` | New chat or resumed chat | Can inject `additionalContext` |
| `chatEnd` | Chat deleted | - |
| `preRequest` | Before prompt sent to LLM | Can rewrite prompt, inject context, stop request |
| `postRequest` | After prompt finished | - |
| `postRequest` | After primary agent prompt finished | - |
| `subagentFinished` | After a subagent prompt finished | - |
| `preToolCall` | Before tool execution | Can modify args, override approval, reject |
| `postToolCall` | After tool execution | Can inject context for next LLM turn |

Expand All @@ -35,6 +36,7 @@ Hooks receive JSON via stdin with event data (top-level keys `snake_case`, neste
- Chat hooks add: `chat_id`, `agent`, `behavior` (deprecated alias)
- Tool hooks add: `tool_name`, `server`, `tool_input`, `approval` (pre) or `tool_response`, `error` (post)
- `chatStart` adds: `resumed` (boolean)
- `subagentFinished` adds: `parent_chat_id`

Hooks can output JSON to control execution:

Expand Down Expand Up @@ -159,6 +161,23 @@ To reject a tool call, either output `{"approval": "deny"}` or exit with code `2
}
```

=== "Notify when subagent finishes"

```javascript title="~/.config/eca/config.json"
{
"hooks": {
"subagent-done": {
"type": "subagentFinished",
"visible": false,
"actions": [{
"type": "shell",
"shell": "jq -r '.agent' | xargs -I{} notify-send 'Subagent {} finished'"
}]
}
}
}
```

=== "Use external script file"

```javascript title="~/.config/eca/config.json"
Expand Down
35 changes: 34 additions & 1 deletion docs/protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,12 @@ interface ChatContentReceivedParams {
* The chat session identifier this content belongs to
*/
chatId: string;

/**
* If this chat is a subagent, the parent chat id.
* Useful for clients to associate subagent messages with the parent conversation.
*/
parentChatId?: string;

/**
* The content received from the LLM
Expand Down Expand Up @@ -1000,7 +1006,7 @@ interface ChatToolCallRejectedContent {

type ToolCallOrigin = 'mcp' | 'native';

type ToolCallDetails = FileChangeDetails | JsonOutputsDetails;
type ToolCallDetails = FileChangeDetails | JsonOutputsDetails | SubagentDetails;

interface FileChangeDetails {
type: 'fileChange';
Expand Down Expand Up @@ -1035,6 +1041,33 @@ interface JsonOutputsDetails {
jsons: string[];
}

interface SubagentDetails {
type: 'subagent';

/**
* The chatId of this running subagent, useful to link other chat/ContentReceived
* messages to this tool call.
* Available from toolCallRun afterwards
*/
subagentChatId?: string;

/**
* The model this subagent is using.
*/
model: string;

/**
* The max number of steps this subagent is limited.
* When not set, the subagent runs with no step limit (infinite interaction).
*/
maxSteps?: number;

/**
* The current step.
*/
step: number;
}

/**
* Extra information about a chat
*/
Expand Down
2 changes: 1 addition & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ nav:
- Skills: config/skills.md
- Rules: config/rules.md
- Commands: config/commands.md
- Agents: config/agents.md
- Agents / Subagents: config/agents.md
- Hooks: config/hooks.md
- Completion: config/completion.md
- Rewrite: config/rewrite.md
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Args=-J-Dborkdude.dynaload.aot=true \
-H:IncludeResources=ECA_VERSION \
-H:IncludeResources=prompts/plan_agent.md \
-H:IncludeResources=prompts/code_agent.md \
-H:IncludeResources=prompts/explorer_agent.md \
-H:IncludeResources=prompts/additional_system_info.md \
-H:IncludeResources=prompts/init.md \
-H:IncludeResources=prompts/skill_create.md \
Expand All @@ -33,6 +34,7 @@ Args=-J-Dborkdude.dynaload.aot=true \
-H:IncludeResources=prompts/tools/read_file.md \
-H:IncludeResources=prompts/tools/shell_command.md \
-H:IncludeResources=prompts/tools/skill.md \
-H:IncludeResources=prompts/tools/spawn_agent.md \
-H:IncludeResources=prompts/tools/write_file.md \
-H:IncludeResources=webpages/oauth.html \
-H:IncludeResources=logo.svg
23 changes: 23 additions & 0 deletions resources/prompts/explorer_agent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
You are a fast, efficient codebase explorer. Your caller is an LLM agent, not a human — optimize your output for machine consumption, not readability.

Your goal is to answer the question with the fewest tool calls and shortest output possible.

## Efficiency rules

- Stop as soon as you have enough information. Do not exhaustively verify or over-explore.
- Prefer `eca__grep` to locate code, only use `eca__read_file` when you need to analyze content beyond what grep shows.
- Use targeted regex patterns and file-glob filters (`include`) to narrow searches. Avoid broad unfiltered searches.
- Batch independent searches into a single response when possible (multiple tool calls at once).
- Use `eca__directory_tree` with a shallow `max_depth` first. Only go deeper if needed.
- Never read an entire large file when a line range suffices — use `line_offset` and `limit`.

## Output rules

- Return file paths as absolute paths.
- Be terse: return raw data (paths, line numbers, code snippets) not prose. Skip introductions, summaries, and explanations unless specifically asked.
- No markdown formatting, headers, or bullet lists unless it aids parsing. Plain text is preferred.

## Restrictions

- Read-only: do not create or modify any files, or run state-modifying shell commands.

12 changes: 12 additions & 0 deletions resources/prompts/tools/spawn_agent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Spawn a specialized agent to perform a focused task in isolated context.

The agent runs independently with its own conversation history and returns a summary of its findings/actions. Use this for:
- Codebase exploration without polluting your context
- Focused research on specific areas
- Delegating specialized tasks (review, analysis, etc.)

The spawned agent:
- Has access only to its configured tools
- Cannot spawn other agents (no nesting)
- Returns a summary when complete
- Does not share your conversation history
10 changes: 8 additions & 2 deletions src/eca/client_http.clj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
Proxy
Proxy$Type
ProxySelector
URI]))
URI]
[java.util.concurrent Executors]))

(set! *warn-on-reflection* true)

(defn hato-client-make
"Builds an options map for creating a Hato HTTP client.
Expand Down Expand Up @@ -71,6 +74,9 @@
proxy-creds
(assoc :authenticator proxy-creds))))

(def ^:private shared-executor*
(delay (Executors/newCachedThreadPool)))

(def ^:dynamic *hato-http-client*
"Global Hato HTTP client used throughout the application for making
HTTP requests"
Expand Down Expand Up @@ -100,7 +106,7 @@
the corresponding proxy configuration is added to the build."
[hato-opts]
(let [{:keys [http https] :as _env-proxies} (proxy/env-proxy-urls-parse)
opts (cond-> hato-opts
opts (cond-> (assoc hato-opts :executor ^java.util.concurrent.ExecutorService @shared-executor*)
http
(assoc :eca.client-http/proxy-http http)
https
Expand Down
Loading