mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-17 07:31:21 +08:00
Compare commits
9 Commits
sid/discor
...
fix/hindsi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2246c81d0d | ||
|
|
8877688b34 | ||
|
|
9d42aca29a | ||
|
|
6407b3d5b3 | ||
|
|
0a59994030 | ||
|
|
0ed37c0ca4 | ||
|
|
1c8ce33d51 | ||
|
|
2182de55bb | ||
|
|
3cf13747b7 |
@@ -790,8 +790,11 @@ code_execution:
|
||||
# Supports single tasks and batch mode (default 3 parallel, configurable).
|
||||
delegation:
|
||||
max_iterations: 50 # Max tool-calling turns per child (default: 50)
|
||||
# max_concurrent_children: 3 # Max parallel child agents (default: 3)
|
||||
# max_spawn_depth: 1 # Tree depth cap (1-3, default: 1 = flat). Raise to 2 or 3 to allow orchestrator children to spawn their own workers.
|
||||
# max_concurrent_children: 3 # Max parallel child agents per batch (default: 3, floor: 1, no ceiling).
|
||||
# WARNING: values above 10 multiply API cost linearly.
|
||||
# max_spawn_depth: 1 # Delegation tree depth cap (range: 1-3, default: 1 = flat).
|
||||
# Raise to 2 to allow workers to spawn their own subagents.
|
||||
# Requires role="orchestrator" on intermediate agents.
|
||||
# orchestrator_enabled: true # Kill switch for role="orchestrator" children (default: true).
|
||||
# inherit_mcp_toolsets: true # When explicit child toolsets are narrowed, also keep the parent's MCP toolsets (default: true). Set false for strict intersection.
|
||||
# model: "google/gemini-3-flash-preview" # Override model for subagents (empty = inherit parent)
|
||||
|
||||
@@ -532,6 +532,20 @@ class MatrixAdapter(BasePlatformAdapter):
|
||||
)
|
||||
await crypto_store.open()
|
||||
|
||||
# Bind the store to the runtime device_id before any
|
||||
# put_account() runs. PgCryptoStore defaults _device_id
|
||||
# to "" and its crypto_account UPSERT never updates the
|
||||
# device_id column on conflict — so once put_account
|
||||
# writes blank, it stays blank forever. That breaks
|
||||
# every downstream device-scoped olm operation: peer
|
||||
# to-device ciphertext can't find our identity key and
|
||||
# no megolm sessions ever land. Setting _device_id here
|
||||
# (in-memory; the on-disk row may not exist yet) makes
|
||||
# the first put_account write the correct value.
|
||||
# DeviceID is a NewType(str) so plain str works at runtime.
|
||||
if client.device_id:
|
||||
await crypto_store.put_device_id(client.device_id)
|
||||
|
||||
crypto_state = _CryptoStateStore(state_store, self._joined_rooms)
|
||||
olm = OlmMachine(client, crypto_store, crypto_state)
|
||||
|
||||
|
||||
@@ -478,7 +478,9 @@ class HindsightMemoryProvider(MemoryProvider):
|
||||
existing = {}
|
||||
if config_path.exists():
|
||||
try:
|
||||
existing = json.loads(config_path.read_text())
|
||||
parsed = json.loads(config_path.read_text())
|
||||
if isinstance(parsed, dict):
|
||||
existing = parsed
|
||||
except Exception:
|
||||
pass
|
||||
existing.update(values)
|
||||
@@ -589,12 +591,35 @@ class HindsightMemoryProvider(MemoryProvider):
|
||||
val = input(f" LLM model [{default_model}]: ").strip()
|
||||
provider_config["llm_model"] = val or default_model
|
||||
|
||||
sys.stdout.write(" LLM API key: ")
|
||||
existing_llm_key = os.environ.get("HINDSIGHT_LLM_API_KEY", "")
|
||||
if not existing_llm_key:
|
||||
existing_llm_key = _load_simple_env(Path(hermes_home) / ".env").get(
|
||||
"HINDSIGHT_LLM_API_KEY",
|
||||
"",
|
||||
)
|
||||
if not existing_llm_key:
|
||||
saved_config = dict(provider_config)
|
||||
config_path = Path(hermes_home) / "hindsight" / "config.json"
|
||||
try:
|
||||
parsed = json.loads(config_path.read_text(encoding="utf-8"))
|
||||
if isinstance(parsed, dict):
|
||||
saved_config.update(parsed)
|
||||
except Exception:
|
||||
pass
|
||||
saved_config.update(provider_config)
|
||||
existing_llm_key = _load_simple_env(
|
||||
_embedded_profile_env_path(saved_config)
|
||||
).get("HINDSIGHT_API_LLM_API_KEY", "")
|
||||
|
||||
if existing_llm_key:
|
||||
masked = f"...{existing_llm_key[-4:]}" if len(existing_llm_key) > 4 else "set"
|
||||
sys.stdout.write(f" LLM API key (current: {masked}, blank to keep): ")
|
||||
else:
|
||||
sys.stdout.write(" LLM API key: ")
|
||||
sys.stdout.flush()
|
||||
llm_key = getpass.getpass(prompt="") if sys.stdin.isatty() else sys.stdin.readline().strip()
|
||||
# Always write explicitly (including empty) so the provider sees ""
|
||||
# rather than a missing variable. The daemon reads from .env at
|
||||
# startup and fails when HINDSIGHT_LLM_API_KEY is unset.
|
||||
if not llm_key and existing_llm_key:
|
||||
llm_key = existing_llm_key
|
||||
env_writes["HINDSIGHT_LLM_API_KEY"] = llm_key
|
||||
|
||||
# Step 4: Save everything
|
||||
@@ -602,7 +627,9 @@ class HindsightMemoryProvider(MemoryProvider):
|
||||
provider_config["recall_budget"] = "mid"
|
||||
# Read existing timeout from config if present, otherwise use default
|
||||
existing_timeout = self._config.get("timeout") if self._config else None
|
||||
timeout_val = existing_timeout if existing_timeout else _DEFAULT_TIMEOUT
|
||||
if not existing_timeout:
|
||||
existing_timeout = _load_simple_env(Path(hermes_home) / ".env").get("HINDSIGHT_TIMEOUT")
|
||||
timeout_val = int(existing_timeout) if existing_timeout else _DEFAULT_TIMEOUT
|
||||
provider_config["timeout"] = timeout_val
|
||||
env_writes["HINDSIGHT_TIMEOUT"] = str(timeout_val)
|
||||
config["memory"]["provider"] = "hindsight"
|
||||
|
||||
@@ -503,6 +503,9 @@ AUTHOR_MAP = {
|
||||
"codex@openai.invalid": "teknium1",
|
||||
"screenmachine@gmail.com": "teknium1",
|
||||
"chenzeshi@live.com": "chen1749144759",
|
||||
"mor.aleksandr@yahoo.com": "MorAlekss",
|
||||
"poruru.code@gmail.com": "poruru-code",
|
||||
"138243371+poruru-code@users.noreply.github.com": "poruru-code",
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -197,10 +197,14 @@ def _make_fake_mautrix():
|
||||
self.account_id = account_id
|
||||
self.pickle_key = pickle_key
|
||||
self.db = db
|
||||
self._device_id = ""
|
||||
|
||||
async def open(self):
|
||||
pass
|
||||
|
||||
async def put_device_id(self, device_id):
|
||||
self._device_id = device_id
|
||||
|
||||
mautrix_crypto_store_asyncpg.PgCryptoStore = PgCryptoStore
|
||||
|
||||
# --- mautrix.util ---
|
||||
|
||||
@@ -329,8 +329,89 @@ class TestPostSetup:
|
||||
|
||||
profile_env = user_home / ".hindsight" / "profiles" / "hermes.env"
|
||||
assert profile_env.exists()
|
||||
assert (hermes_home / ".env").read_text() == "HINDSIGHT_LLM_API_KEY=existing-key\nHINDSIGHT_TIMEOUT=120\n"
|
||||
assert "HINDSIGHT_API_LLM_API_KEY=existing-key\n" in profile_env.read_text()
|
||||
|
||||
def test_local_embedded_setup_preserves_existing_key_from_nondefault_profile_env_when_input_left_blank(self, tmp_path, monkeypatch):
|
||||
hermes_home = tmp_path / "hermes-home"
|
||||
user_home = tmp_path / "user-home"
|
||||
user_home.mkdir()
|
||||
monkeypatch.setenv("HOME", str(user_home))
|
||||
|
||||
selections = iter([1, 0]) # local_embedded, openai
|
||||
monkeypatch.setattr("hermes_cli.memory_setup._curses_select", lambda *args, **kwargs: next(selections))
|
||||
monkeypatch.setattr("shutil.which", lambda name: None)
|
||||
monkeypatch.setattr("builtins.input", lambda prompt="": "")
|
||||
monkeypatch.setattr("sys.stdin.isatty", lambda: True)
|
||||
monkeypatch.setattr("getpass.getpass", lambda prompt="": "")
|
||||
monkeypatch.setattr("hermes_cli.config.save_config", lambda cfg: None)
|
||||
|
||||
provider = HindsightMemoryProvider()
|
||||
provider.save_config({"profile": "coder"}, str(hermes_home))
|
||||
|
||||
profile_env = user_home / ".hindsight" / "profiles" / "coder.env"
|
||||
profile_env.parent.mkdir(parents=True, exist_ok=True)
|
||||
profile_env.write_text("HINDSIGHT_API_LLM_API_KEY=existing-key\n")
|
||||
|
||||
provider.post_setup(str(hermes_home), {"memory": {}})
|
||||
|
||||
assert (hermes_home / ".env").read_text() == "HINDSIGHT_LLM_API_KEY=existing-key\nHINDSIGHT_TIMEOUT=120\n"
|
||||
assert "HINDSIGHT_API_LLM_API_KEY=existing-key\n" in profile_env.read_text()
|
||||
|
||||
def test_local_embedded_setup_ignores_nondict_saved_config_when_input_left_blank(self, tmp_path, monkeypatch):
|
||||
hermes_home = tmp_path / "hermes-home"
|
||||
user_home = tmp_path / "user-home"
|
||||
user_home.mkdir()
|
||||
monkeypatch.setenv("HOME", str(user_home))
|
||||
|
||||
selections = iter([1, 0]) # local_embedded, openai
|
||||
monkeypatch.setattr("hermes_cli.memory_setup._curses_select", lambda *args, **kwargs: next(selections))
|
||||
monkeypatch.setattr("shutil.which", lambda name: None)
|
||||
monkeypatch.setattr("builtins.input", lambda prompt="": "")
|
||||
monkeypatch.setattr("sys.stdin.isatty", lambda: True)
|
||||
monkeypatch.setattr("getpass.getpass", lambda prompt="": "")
|
||||
monkeypatch.setattr("hermes_cli.config.save_config", lambda cfg: None)
|
||||
|
||||
config_path = hermes_home / "hindsight" / "config.json"
|
||||
config_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
config_path.write_text("[]")
|
||||
|
||||
env_path = hermes_home / ".env"
|
||||
env_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
env_path.write_text("HINDSIGHT_LLM_API_KEY=existing-key\n")
|
||||
|
||||
provider = HindsightMemoryProvider()
|
||||
provider.post_setup(str(hermes_home), {"memory": {}})
|
||||
|
||||
profile_env = user_home / ".hindsight" / "profiles" / "hermes.env"
|
||||
assert profile_env.exists()
|
||||
assert (hermes_home / ".env").read_text() == "HINDSIGHT_LLM_API_KEY=existing-key\nHINDSIGHT_TIMEOUT=120\n"
|
||||
assert "HINDSIGHT_API_LLM_API_KEY=existing-key\n" in profile_env.read_text()
|
||||
|
||||
def test_local_embedded_setup_preserves_existing_timeout(self, tmp_path, monkeypatch):
|
||||
hermes_home = tmp_path / "hermes-home"
|
||||
user_home = tmp_path / "user-home"
|
||||
user_home.mkdir()
|
||||
monkeypatch.setenv("HOME", str(user_home))
|
||||
|
||||
selections = iter([1, 0]) # local_embedded, openai
|
||||
monkeypatch.setattr("hermes_cli.memory_setup._curses_select", lambda *args, **kwargs: next(selections))
|
||||
monkeypatch.setattr("shutil.which", lambda name: None)
|
||||
monkeypatch.setattr("builtins.input", lambda prompt="": "")
|
||||
monkeypatch.setattr("sys.stdin.isatty", lambda: True)
|
||||
monkeypatch.setattr("getpass.getpass", lambda prompt="": "sk-test")
|
||||
monkeypatch.setattr("hermes_cli.config.save_config", lambda cfg: None)
|
||||
|
||||
env_path = hermes_home / ".env"
|
||||
env_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
env_path.write_text("HINDSIGHT_LLM_API_KEY=sk-test\nHINDSIGHT_TIMEOUT=300\n")
|
||||
|
||||
provider = HindsightMemoryProvider()
|
||||
provider.post_setup(str(hermes_home), {"memory": {}})
|
||||
|
||||
env_content = (hermes_home / ".env").read_text()
|
||||
assert "HINDSIGHT_TIMEOUT=300" in env_content
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tool handler tests
|
||||
|
||||
@@ -276,7 +276,14 @@ def _get_max_concurrent_children() -> int:
|
||||
val = cfg.get("max_concurrent_children")
|
||||
if val is not None:
|
||||
try:
|
||||
return max(1, int(val))
|
||||
result = max(1, int(val))
|
||||
if result > 10:
|
||||
logger.warning(
|
||||
"delegation.max_concurrent_children=%d: each child consumes API tokens "
|
||||
"independently. High values multiply cost linearly.",
|
||||
result,
|
||||
)
|
||||
return result
|
||||
except (TypeError, ValueError):
|
||||
logger.warning(
|
||||
"delegation.max_concurrent_children=%r is not a valid integer; "
|
||||
@@ -2229,8 +2236,8 @@ DELEGATE_TASK_SCHEMA = {
|
||||
"never enter your context window.\n\n"
|
||||
"TWO MODES (one of 'goal' or 'tasks' is required):\n"
|
||||
"1. Single task: provide 'goal' (+ optional context, toolsets)\n"
|
||||
"2. Batch (parallel): provide 'tasks' array with up to delegation.max_concurrent_children items (default 3). "
|
||||
"All run concurrently and results are returned together.\n\n"
|
||||
"2. Batch (parallel): provide 'tasks' array with up to delegation.max_concurrent_children items (default 3, configurable via config.yaml, no hard ceiling). "
|
||||
"All run concurrently and results are returned together. Nested delegation requires role='orchestrator' and delegation.max_spawn_depth >= 2.\n\n"
|
||||
"WHEN TO USE delegate_task:\n"
|
||||
"- Reasoning-heavy subtasks (debugging, code review, research synthesis)\n"
|
||||
"- Tasks that would flood your context with intermediate data\n"
|
||||
|
||||
@@ -2789,6 +2789,23 @@ def _(rid, params: dict) -> dict:
|
||||
_write_config_key("display.tui_statusbar", nv)
|
||||
return _ok(rid, {"key": key, "value": nv})
|
||||
|
||||
if key == "mouse":
|
||||
raw = str(value or "").strip().lower()
|
||||
display = _load_cfg().get("display") if isinstance(_load_cfg().get("display"), dict) else {}
|
||||
current = bool(display.get("tui_mouse", True))
|
||||
|
||||
if raw in ("", "toggle"):
|
||||
nv = not current
|
||||
elif raw == "on":
|
||||
nv = True
|
||||
elif raw == "off":
|
||||
nv = False
|
||||
else:
|
||||
return _err(rid, 4002, f"unknown mouse value: {value}")
|
||||
|
||||
_write_config_key("display.tui_mouse", nv)
|
||||
return _ok(rid, {"key": key, "value": "on" if nv else "off"})
|
||||
|
||||
if key in ("prompt", "personality", "skin"):
|
||||
try:
|
||||
cfg = _load_cfg()
|
||||
@@ -2917,6 +2934,10 @@ def _(rid, params: dict) -> dict:
|
||||
display.get("tui_statusbar", "top") if isinstance(display, dict) else "top"
|
||||
)
|
||||
return _ok(rid, {"value": _coerce_statusbar(raw)})
|
||||
if key == "mouse":
|
||||
display = _load_cfg().get("display")
|
||||
on = display.get("tui_mouse", True) if isinstance(display, dict) else True
|
||||
return _ok(rid, {"value": "on" if on else "off"})
|
||||
if key == "mtime":
|
||||
cfg_path = _hermes_home / "config.yaml"
|
||||
try:
|
||||
|
||||
@@ -53,7 +53,7 @@ export function AlternateScreen(t0: Props) {
|
||||
}
|
||||
|
||||
writeRaw(
|
||||
ENTER_ALT_SCREEN + ERASE_SCROLLBACK + ERASE_SCREEN + CURSOR_HOME + (mouseTracking ? ENABLE_MOUSE_TRACKING : '')
|
||||
ENTER_ALT_SCREEN + ERASE_SCROLLBACK + ERASE_SCREEN + CURSOR_HOME + (mouseTracking ? ENABLE_MOUSE_TRACKING : DISABLE_MOUSE_TRACKING)
|
||||
)
|
||||
ink?.setAltScreenActive(true, mouseTracking)
|
||||
|
||||
|
||||
@@ -1121,6 +1121,23 @@ export default class Ink {
|
||||
this.repaint()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle mouse tracking at runtime while the alt screen is active.
|
||||
* Writes the appropriate DEC reset/set sequences so the terminal
|
||||
* (and ConPTY on Windows WSL2) reflects the change immediately.
|
||||
*/
|
||||
setAltScreenMouseTracking(enabled: boolean): void {
|
||||
if (this.altScreenMouseTracking === enabled) {
|
||||
return
|
||||
}
|
||||
|
||||
this.altScreenMouseTracking = enabled
|
||||
|
||||
if (this.altScreenActive) {
|
||||
this.options.stdout.write(enabled ? ENABLE_MOUSE_TRACKING : DISABLE_MOUSE_TRACKING)
|
||||
}
|
||||
}
|
||||
get isAltScreenActive(): boolean {
|
||||
return this.altScreenActive
|
||||
}
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
import { useStore } from '@nanostores/react'
|
||||
|
||||
import { GatewayProvider } from './app/gatewayContext.js'
|
||||
import { useMainApp } from './app/useMainApp.js'
|
||||
import { $uiState } from './app/uiStore.js'
|
||||
import { AppLayout } from './components/appLayout.js'
|
||||
import { MOUSE_TRACKING } from './config/env.js'
|
||||
import type { GatewayClient } from './gatewayClient.js'
|
||||
|
||||
export function App({ gw }: { gw: GatewayClient }) {
|
||||
const { appActions, appComposer, appProgress, appStatus, appTranscript, gateway } = useMainApp(gw)
|
||||
const { mouseTracking } = useStore($uiState)
|
||||
|
||||
return (
|
||||
<GatewayProvider value={gateway}>
|
||||
<AppLayout
|
||||
actions={appActions}
|
||||
composer={appComposer}
|
||||
mouseTracking={MOUSE_TRACKING}
|
||||
mouseTracking={mouseTracking}
|
||||
progress={appProgress}
|
||||
status={appStatus}
|
||||
transcript={appTranscript}
|
||||
|
||||
@@ -88,6 +88,7 @@ export interface UiState {
|
||||
detailsMode: DetailsMode
|
||||
info: null | SessionInfo
|
||||
inlineDiffs: boolean
|
||||
mouseTracking: boolean
|
||||
sections: SectionVisibility
|
||||
showCost: boolean
|
||||
showReasoning: boolean
|
||||
|
||||
@@ -84,6 +84,27 @@ export const coreCommands: SlashCommand[] = [
|
||||
run: (_arg, ctx) => ctx.session.die()
|
||||
},
|
||||
|
||||
{
|
||||
aliases: ['scroll'],
|
||||
help: 'toggle mouse/wheel tracking [on|off|toggle]',
|
||||
name: 'mouse',
|
||||
run: (arg, ctx) => {
|
||||
const current = ctx.ui.mouseTracking
|
||||
const next = flagFromArg(arg, current)
|
||||
|
||||
if (next === null) {
|
||||
return ctx.transcript.sys('usage: /mouse [on|off|toggle]')
|
||||
}
|
||||
|
||||
patchUiState({ mouseTracking: next })
|
||||
ctx.gateway
|
||||
.rpc<ConfigSetResponse>('config.set', { key: 'mouse', value: next ? 'on' : 'off' })
|
||||
.catch(() => {})
|
||||
|
||||
queueMicrotask(() => ctx.transcript.sys(`mouse tracking ${next ? 'on' : 'off'}`))
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
aliases: ['new'],
|
||||
help: 'start a new session',
|
||||
|
||||
@@ -2,6 +2,7 @@ import { atom } from 'nanostores'
|
||||
|
||||
import { ZERO } from '../domain/usage.js'
|
||||
import { DEFAULT_THEME } from '../theme.js'
|
||||
import { MOUSE_TRACKING } from '../config/env.js'
|
||||
|
||||
import type { UiState } from './interfaces.js'
|
||||
|
||||
@@ -12,6 +13,7 @@ const buildUiState = (): UiState => ({
|
||||
detailsMode: 'collapsed',
|
||||
info: null,
|
||||
inlineDiffs: true,
|
||||
mouseTracking: MOUSE_TRACKING,
|
||||
sections: {},
|
||||
showCost: false,
|
||||
showReasoning: false,
|
||||
|
||||
@@ -46,6 +46,7 @@ export const applyDisplay = (cfg: ConfigFullResponse | null, setBell: (v: boolea
|
||||
compact: !!d.tui_compact,
|
||||
detailsMode: resolveDetailsMode(d),
|
||||
inlineDiffs: d.inline_diffs !== false,
|
||||
mouseTracking: d.tui_mouse !== false,
|
||||
sections: resolveSections(d.sections),
|
||||
showCost: !!d.show_cost,
|
||||
showReasoning: !!d.show_reasoning,
|
||||
|
||||
@@ -61,6 +61,7 @@ export interface ConfigDisplayConfig {
|
||||
streaming?: boolean
|
||||
thinking_mode?: string
|
||||
tui_compact?: boolean
|
||||
tui_mouse?: boolean
|
||||
tui_statusbar?: 'bottom' | 'off' | 'on' | 'top' | boolean
|
||||
}
|
||||
|
||||
|
||||
@@ -216,8 +216,24 @@ Restricting toolsets keeps the subagent focused and prevents accidental side eff
|
||||
|
||||
## Constraints
|
||||
|
||||
- **Default 3 parallel tasks** — batches default to 3 concurrent subagents (configurable via `delegation.max_concurrent_children` in config.yaml — no hard ceiling, only a floor of 1)
|
||||
- **Nested delegation is opt-in** — leaf subagents (default) cannot call `delegate_task`, `clarify`, `memory`, `send_message`, or `execute_code`. Orchestrator subagents (`role="orchestrator"`) retain `delegate_task` for further delegation, but only when `delegation.max_spawn_depth` is raised above the default of 1 (1-3 supported); the other four remain blocked. Disable globally via `delegation.orchestrator_enabled: false`.
|
||||
- **Default 3 parallel tasks**: batches default to 3 concurrent subagents (configurable via `delegation.max_concurrent_children` in config.yaml, no hard ceiling, only a floor of 1)
|
||||
- **Nested delegation is opt-in**: leaf subagents (default) cannot call `delegate_task`, `clarify`, `memory`, `send_message`, or `execute_code`. Orchestrator subagents (`role="orchestrator"`) retain `delegate_task` for further delegation, but only when `delegation.max_spawn_depth` is raised above the default of 1 (1-3 supported); the other four remain blocked. Disable globally via `delegation.orchestrator_enabled: false`.
|
||||
|
||||
### Tuning Concurrency and Depth
|
||||
|
||||
| Config | Default | Range | Effect |
|
||||
|--------|---------|-------|--------|
|
||||
| `max_concurrent_children` | 3 | >=1 | Parallel batch size per `delegate_task` call |
|
||||
| `max_spawn_depth` | 1 | 1-3 | How many delegation levels can spawn further |
|
||||
|
||||
Example: running 30 parallel workers with nested subagents:
|
||||
|
||||
```yaml
|
||||
delegation:
|
||||
max_concurrent_children: 30
|
||||
max_spawn_depth: 2
|
||||
```
|
||||
|
||||
- **Separate terminals** — each subagent gets its own terminal session with separate working directory and state
|
||||
- **No conversation history** — subagents see only the `goal` and `context` the parent agent passes when calling `delegate_task`
|
||||
- **Default 50 iterations** — set `max_iterations` lower for simple tasks to save cost
|
||||
|
||||
Reference in New Issue
Block a user