Skip to content

Subagent Architecture

Hermes creates child AIAgent instances for delegate_task. Each child:

  • Auto-generates its own session_id (format: {YYYYMMDD_HHMMSS}_{uuid6})
  • Calls run_conversation() which fires the full hook lifecycle
  • Records its own runs row independently

Verified finding (A3, superseded by #49): the delegate_task result in model_tools.py:999 ({results:[{tokens, model, api_calls, status, ...}], total_duration_seconds}) still carries no child session_id. But subagent_start fires with both parent_session_id and child_session_id before the child ever dispatches, and subagent_stop does carry child_session_id (verified in tools/delegate_tool.py’s subagent_stop invocation; an earlier version of this doc claimed otherwise — that was stale). That pair is enough to build the full delegation tree without touching the delegate_task result at all.

How attribution works now (schema v11, issue #49): every subagent_start records a subagent_edges row — child_session_id → parent_session_id, plus role, subagent ids, and a start timestamp (db.record_subagent_start). subagent_stop finalizes that row with stopped_at + child_status (db.record_subagent_stop), backfilling a missing edge from its own kwargs in the rare case subagent_start was never seen. db.spend_by_scope("cron_job", ...) seeds from the cron job’s own root session(s) and walks the edge tree down with a recursive CTE, summing cost over the root plus every descendant — so both async and nested delegation resolve back to the root cron job. global still sums every runs row unconditionally, so per_cron_job only regroups the same cost rows; there is no double counting.

runs.parent_session_id stays dormant, on purpose. subagent_edges is the single source of truth for the delegation tree, and it’s a persistent table, so there’s no in-memory map to leak or lose on restart. The dormant column is not being repurposed.

Phase-1 boundary: this closes the reporting/aggregation gap only. A detached child still self-enforces its own budget only against global while it runs — an in-path stop for a runaway async child mid-flight (pausing it before it racks up more cost) is a planned Phase 2, not part of this change. db.unattributed_child_cost is a query-time diagnostic that flags child spend recorded in subagent_edges whose parent has no matching runs row, so the chain can’t resolve to a real root; surfaced in /stats.

hermes-telemetry is an independent community plugin for Hermes Agent. It is not affiliated with or endorsed by Nous Research.