mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-24 05:41:40 +00:00
feat(delegate): show user's actual concurrency / spawn-depth limits in tool description (#22694)
The delegate_task tool description hardcoded 'default 3' / 'default 2' for max_concurrent_children / max_spawn_depth, which misled the model on any install that raised these limits — the schema text said 'default 3' even when the user had set max_concurrent_children=15 / max_spawn_depth=3, so the model would self-cap at 3 and never use the headroom. Make the description dynamic. ToolEntry gains an optional dynamic_schema_overrides callable; registry.get_definitions() merges its output on top of the static schema before returning it. delegate_tool registers a builder that reads the current delegation.* config and emits: - 'up to N items concurrently for this user' (N = max_concurrent_children) - 'Nested delegation IS enabled / OFF for this user (max_spawn_depth=N)' - 'orchestrator children can themselves delegate up to M more level(s)' - 'orchestrator_enabled=false' when the kill switch is set The model_tools cache key already includes config.yaml mtime+size, so edits to delegation.* in config invalidate the cached tool definitions without an explicit hook. CLI_CONFIG staleness within a process is a pre-existing limitation of _load_config and out of scope here. Static description / tasks.description / role.description in DELEGATE_TASK_SCHEMA are placeholders so module import doesn't trigger cli.CLI_CONFIG load before the test conftest can redirect HERMES_HOME.
This commit is contained in:
parent
000ddb8a93
commit
1f4200debf
3 changed files with 222 additions and 23 deletions
|
|
@ -80,12 +80,12 @@ class ToolEntry:
|
|||
__slots__ = (
|
||||
"name", "toolset", "schema", "handler", "check_fn",
|
||||
"requires_env", "is_async", "description", "emoji",
|
||||
"max_result_size_chars",
|
||||
"max_result_size_chars", "dynamic_schema_overrides",
|
||||
)
|
||||
|
||||
def __init__(self, name, toolset, schema, handler, check_fn,
|
||||
requires_env, is_async, description, emoji,
|
||||
max_result_size_chars=None):
|
||||
max_result_size_chars=None, dynamic_schema_overrides=None):
|
||||
self.name = name
|
||||
self.toolset = toolset
|
||||
self.schema = schema
|
||||
|
|
@ -96,6 +96,14 @@ class ToolEntry:
|
|||
self.description = description
|
||||
self.emoji = emoji
|
||||
self.max_result_size_chars = max_result_size_chars
|
||||
# Optional zero-arg callable returning a dict of schema overrides
|
||||
# applied at get_definitions() time. Use for fields that depend on
|
||||
# runtime config (e.g. delegate_task's description must reflect the
|
||||
# user's current delegation.max_concurrent_children / max_spawn_depth
|
||||
# so the model isn't told the wrong limits). The callable is invoked
|
||||
# on every get_definitions() call; results are merged shallow on top
|
||||
# of the base schema before the {"type": "function", ...} wrap.
|
||||
self.dynamic_schema_overrides = dynamic_schema_overrides
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -235,6 +243,7 @@ class ToolRegistry:
|
|||
description: str = "",
|
||||
emoji: str = "",
|
||||
max_result_size_chars: int | float | None = None,
|
||||
dynamic_schema_overrides: Callable = None,
|
||||
):
|
||||
"""Register a tool. Called at module-import time by each tool file."""
|
||||
with self._lock:
|
||||
|
|
@ -272,6 +281,7 @@ class ToolRegistry:
|
|||
description=description or schema.get("description", ""),
|
||||
emoji=emoji,
|
||||
max_result_size_chars=max_result_size_chars,
|
||||
dynamic_schema_overrides=dynamic_schema_overrides,
|
||||
)
|
||||
if check_fn and toolset not in self._toolset_checks:
|
||||
self._toolset_checks[toolset] = check_fn
|
||||
|
|
@ -337,6 +347,22 @@ class ToolRegistry:
|
|||
continue
|
||||
# Ensure schema always has a "name" field — use entry.name as fallback
|
||||
schema_with_name = {**entry.schema, "name": entry.name}
|
||||
# Apply runtime-dynamic overrides (e.g. delegate_task description
|
||||
# depends on current delegation.max_concurrent_children /
|
||||
# max_spawn_depth). Caller side (model_tools.get_tool_definitions)
|
||||
# already keys its memo on config.yaml mtime + size, so changes
|
||||
# to delegation.* in config invalidate the cache automatically.
|
||||
if entry.dynamic_schema_overrides is not None:
|
||||
try:
|
||||
overrides = entry.dynamic_schema_overrides()
|
||||
if isinstance(overrides, dict):
|
||||
schema_with_name.update(overrides)
|
||||
except Exception as exc:
|
||||
logger.warning(
|
||||
"dynamic_schema_overrides for tool %s raised %s; "
|
||||
"using static schema",
|
||||
name, exc,
|
||||
)
|
||||
result.append({"type": "function", "function": schema_with_name})
|
||||
return result
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue