Skip to main content

Public Subagent Lifecycle API

Plugins can launch and supervise fresh Hermes child sessions without importing tools.delegate_tool, gateway internals, TUI state, or AIAgent fields. The service resolves its parent from the current agent turn, so it works in CLI, gateway, non-interactive, and kanban-worker sessions. Launching outside an active agent turn fails closed with No active Hermes parent session. The facade also works when a plugin runs under plugins.isolation: host.

Requests accept goal, context, role, model, provider, allowed_toolsets, blocked_tools, working_directory, parent_session_id, correlation_id, metadata, and timeout_seconds, subject to the restrictions below.

from agent.subagent_lifecycle import SubagentLaunchRequest

def launch_review(ctx):
# Call from a plugin tool or hook while an agent turn is active.
service = ctx.subagent_lifecycle
handle = service.launch(SubagentLaunchRequest(
goal="Review this change for regressions.",
context="Only inspect the supplied repository.",
role="leaf",
correlation_id="review-42",
allowed_toolsets=("file",),
))
# Persist handle.to_dict() if desired.
if service.wait(handle, timeout_seconds=2).timed_out:
return handle.to_dict()
return service.result(handle)

SubagentHandle is serializable and carries a versioned, opaque capability. Requests and handles may also arrive as mappings, including plugin-host wire records. Pass a handle back to status, wait, cancel, steer, result, or reconnect; malformed or forged handles return UNKNOWN/UNKNOWN_HANDLE and cannot access a child.

steer(handle, text) queues text for the child's next iteration boundary and returns True while the child accepts steering. It returns False for empty text, unknown or forged handles, handles owned by another parent session, or a child that has finished or stopped accepting steering.

The stable states are PENDING, STARTING, RUNNING, SUCCEEDED, FAILED, INTERRUPTED, CANCEL_REQUESTED, CANCELLED, and UNKNOWN.

cancel(handle, reason=...) is cooperative: it asks the child agent to interrupt at its next safe boundary and returns CANCEL_REQUESTED; it never claims completion until wait or result observes a terminal state. Terminal results are immutable, idempotent, bounded to 32k characters, omit transcripts and hidden reasoning, and include a stable result hash.

This API is lifecycle-managed asynchronous execution. Child construction and completion use the same host-owned path as delegate_task, including parent tool-resolution restoration, memory notification, serialized subagent_stop hooks, resource cleanup, and child-cost rollup. It does not change the synchronous delegate_task tool, batch delegation, or its gateway/TUI display. The initial implementation retains metadata and terminal results in-process for one hour. After a process restart, reconnect returns RECONNECT_UNAVAILABLE and never starts a replacement child. Running Python threads also cannot survive process exit; callers must treat those handles as interrupted by process exit.

Requests are fail-closed: goal/context/metadata sizes are capped, unknown or parent-broadening toolsets are rejected (plugin-registered toolsets count as known), and per-tool blocks, working-directory overrides, and per-launch timeouts are explicitly rejected until Hermes can support them without weakening isolation. Use allowed_toolsets to narrow a child; Hermes's existing unsafe-tool block remains enforced.

Routing​

Children follow the delegation config (provider, model, base_url, api_key, api_mode, request_overrides) exactly as delegate_task children do. The request's model overrides the configured model. Its optional provider names a configured provider for this launch, with precedence request > delegation config > parent; the selected provider supplies its own credential bundle. An unresolvable provider raises SubagentLifecycleError before launch. Core does not expose provider to the model: the plugin chooses it, for example from operator config.

The lifecycle API does not consult delegation.backend: the plugin that launches a child already chooses its route. To change how delegate_task routes the model's subagents, write a delegation provider plugin.