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
runsrow 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.