| name | hera |
| description | Inside an argus sandbox (cwd under ~/.argus/worktrees/ or ARGUS_TASK_ID set), coordinate multi-agent work via hera's mcp__argus__hera_* tools — bootstrap an orchestrator, claim or attach a worker/freelance role, spawn workers, and message roles over the idle-gated bus. Load this skill when you are (or are becoming) a coordinator, when you're spawned as a hera worker, or before delegating work that must run as its OWN argus session (separate worktree / long-running agent / own PR / own sandbox) — as opposed to in-process Claude sub-agents, which stay the right tool for ephemeral in-session fan-out. For staged or dependency-ordered multi-worker plans, this skill routes you to the companion `hera-plan` skill. NOT for non-argus sessions, where these MCP tools are not registered. |
Hera — native multi-agent coordination inside argus
Hera is argus's native, in-tree layer for running a team of agents. It is not a separate
daemon or plugin — coordination runs in-process in the argus daemon, the rail/tree render directly
in the TUI's second tab, and agents drive it entirely through the mcp__argus__hera_* MCP
tools. State lives in the same ~/.argus/data.sql (the hera_* tables). You never touch the
plumbing; you call the tools.
1. When this applies (and when it does NOT)
The mcp__argus__hera_* tools are only registered inside an argus task sandbox. You are in one if
either holds:
ARGUS_TASK_ID is set, or
- the current working directory is under
~/.argus/worktrees/.
If neither holds, stop. The tools are not registered in this session — there is no CLI fallback and
nothing below applies.
Sandbox residency alone is NOT a reason to use hera. Most argus sessions are plain solo tasks that
should stay solo. The "coordinate via hera, never hand-roll it" imperative in this skill applies only
once this session already has evidence of being hera-managed:
- it was spawned as a hera worker — its prompt carries the orientation prefix naming the
coordinator + orchestrator (see
hera_spawn_worker's prompt contract in §3), or
- it already holds, or is actively creating (e.g. via
hera_new_orchestrator), a
coordinator/freelance binding for this session.
No such evidence? This is a bare argus task the human is driving directly. Don't assume hera and
don't self-promote into a coordinator just because the work could be split up — at most, mention hera as
an available option for multi-session work ("this could be split into a hera team with its own
worktrees/PRs per stage if you want — say so, or I'll keep it in this session") and only act on it if
they opt in. Once you're spawned/promoted per the bullets above, everything below governs.
Every hera tool takes cwd — always pass cwd=$PWD. That is how hera resolves which argus task
(and therefore which role) this session is. There is no separate "auth" or session handle.
2. The role model
- Orchestrator — a named coordination graph (one per project / feature / wave). Roles live under
it. Created by
hera_new_orchestrator (idempotent by name).
- Coordinator role — the orchestrator's driver. Created by
hera_new_orchestrator. Talks to the
human in its own agent pane; talks to other roles via hera_send. Folded into the rail's
orchestrator header (it is not a separate row).
- Worker role — does the actual work. Normally born-bound: spawned by a coordinator with
hera_spawn_worker, which creates the argus task (worktree + session) AND the role + binding in one
transaction. A born-bound worker does not need to hera_join — its session opens already bound.
- Freelance role — a helper that attaches itself to an existing orchestrator on its own
initiative (no coordinator spawned it). Created via
hera_join attach mode with kind=freelance.
- Binding — the live link between this argus task and one role. Invariants:
- One live binding per
(task, orchestrator) — enforced by a partial unique index.
- A task MAY hold several live bindings at once — one per orchestrator (e.g. a worker in A that
promotes itself to coordinator of nested orchestrator B). When a task holds 2+ live bindings,
you must pass
orchestrator=<name> to every tool so hera knows which role you are acting as;
omitting it returns an ambiguity error listing your options.
3. The coordination tools
All take cwd. orchestrator is optional with exactly one live binding and required with 2+.
Arg names below are exact — do not invent others. These eleven cover bootstrap, messaging, status,
revive, and completion; the plan-DAG authoring/mutation tools live in the companion hera-plan skill
(pointer at the end of this section).
Bootstrap / join
-
hera_new_orchestrator(cwd, name, coordinator_role_name, [prompt]) — "I am the coordinator."
Creates (or fetches, idempotent-by-name) the orchestrator, creates the named coordinator role, and
binds this task to it. Returns the orchestrator name, role name, binding_id, and argus task id.
Rejects if this task already holds a live binding under that orchestrator (use hera_join to
retrieve it). The canonical "become an orchestrator" entry point — don't hera_join first; there is
nothing to join yet.
-
hera_join(cwd, [orchestrator], [role_name], [kind], [prompt], [status]) — two modes:
- Claim mode (
role_name omitted) — retrieves this task's existing live binding + role and its
unread message count. Use right after a born-bound worker terminal opens to read your assigned
role/mission. Pass orchestrator= if the task has 2+ bindings. Claim mode does not consume the
inbox.
- Attach mode (
role_name + kind supplied) — creates a new role under an existing
orchestrator (orchestrator required) and binds this task to it. kind must be worker or
freelance (coordinator is rejected — use hera_new_orchestrator). Optional prompt (stored on
the role) and status (idle/working/blocked/done). Use to join a team nobody spawned you
into.
-
hera_spawn_worker(cwd, prompt, [orchestrator], [role_name], [project], [branch], [backend], [model], [archetype])
— spawn a new born-bound worker task + session under the caller's orchestrator. Caller must hold
a live coordinator binding. Creates an argus task (worktree + session) and, transactionally, a worker
role + binding pre-bound to it; an orientation prefix naming the coordinator + orchestrator is prepended
to the prompt automatically. Args:
prompt (required) — the worker's MISSION/task only. The verbatim prompt is also stored on the
role row and shown as the node's description in the plan-DAG view. Do NOT prepend the org/security
policy — see the mission-only gotcha in §6.
Messaging (idle-gated bus)
-
hera_send(cwd, body, tldr, status, [to], [in_reply_to], [orchestrator]) — message another role
in the same orchestrator. body and tldr are required; tldr is a one-line summary ≤120
chars, written from the recipient's perspective (see §5). status is REQUIRED for worker/freelance
senders (one of idle/working/blocked/done/failed) and is applied to the sender's role
synchronously before the message is sent — it is never delivered async. Omitting status as a
worker/freelance sender is an error; coordinator senders may omit it. Worker/freelance senders may
omit to — it default-routes to the orchestrator's coordinator. Coordinators must supply an
explicit to. in_reply_to threads a reply to a prior message id. Returns the message_id,
recipient, and delivery mode. Caps: 64 KiB body, 500 unread per recipient, 50 sends/min/sender.
Cross-orchestrator messaging is not possible — to always resolves within your own orchestrator.
-
hera_inbox(cwd, [orchestrator]) — fetch all unread messages addressed to your role, oldest
first. Reading IS acknowledgment: this both cancels pending pane deliveries AND marks the
messages read — no separate hera_mark_read needed for normal consumption. Call it whenever you get a
doorbell.
-
hera_mark_read(cwd, message_ids, [orchestrator]) — explicitly mark specific message ids read and
cancel their pending deliveries. Use when you read via hera_get_messages instead of hera_inbox.
Status / tree
-
hera_status(cwd, status, [orchestrator]) — set your role status: idle | working | blocked
| done | failed. Mirrored (best-effort) to argus task_meta so the coordinator sees it without
asking. A worker-kind role reporting status=done also rolls its bound argus task to in_review
and stamps ready_to_close (visible in the rail) — see §4. A worker reporting status=failed
rolls its task to in_review WITHOUT ready_to_close (needs-attention, not ready to check off).
The gater treats a failed blocker as explicitly failed (no need to wait for session death).
Coordinators/freelancers just update status.
-
hera_revive(cwd, role_name, [orchestrator]) — coordinator-only PULL-revive of one role you
coordinate. Reach for this when a role you spawned looks stuck — hera_tree_updates/hera_status
show no progress, especially after something like a session-supervisor restart (which SIGHUPs every
PTY it owns, leaving a worker dead or suspended). It inspects the role's live session and takes
exactly one action: a dead session is restarted in place; a live-but-genuinely-stuck session (idle,
NOT parked at a prompt) is kicked (stopped and resumed in place); anything else — busy, blocked on a
question, a live coordinator, or a kick already in flight — is left untouched and reported as such
(skipped_busy / skipped_blocked_on_prompt / skipped_coordinator_live / skipped_restart_pending).
This is pull-only — nothing calls it automatically, and it can never thrash a session that is
actually working or waiting on an answer, since it applies the identical idle+not-blocked gate the
TUI's own Enter-key revive uses. It targets a DIFFERENT role than your own (self-targeting errors).
-
hera_accept(cwd, role_name, [orchestrator], [message]) — coordinator-only: mark a role's bound
task complete and send it a check-in asking whether it's winding down, has more work to do, or is
unsure. The reply is informational only — never auto-reopens the task; a premature accept is undone
via hera_revive alone. Never stops the session (completion and detachment are separate). Acts from
any non-complete status; a no-op if already complete. The plan-DAG gater fires this automatically
for blockers when dependent nodes materialize — use this tool for accepting work that flow
(ad hoc spawns, or roles with no plan-DAG dependents).
Plan-DAG authoring (staged, dependency-ordered work) → the hera-plan skill
With a live coordinator binding, do NOT use the harness TaskCreate system-reminder for
coordinated work — coordinate via the hera tools (hera_spawn_worker, or the plan-DAG), never
bare task creation.
For work that decomposes into multiple hera worker units with dependencies among them (one stage
needs another's output, or a required ordering), hera offers a plan-DAG: planned nodes wired by
blocking edges that the daemon gater materializes into born-bound workers in dependency order,
auto-stacking each stage's branch on the prior. It renders in the TUI's second tab and is a living
graph (edit / re-point / cancel as work evolves).
Load the companion hera-plan skill before authoring or driving a plan-DAG. It carries the six
plan tools (hera_plan, hera_plan_node, hera_block, hera_plan_node_update, hera_unblock,
hera_plan_node_cancel), the gating contract (a node materializes ONLY when every blocker reaches
role-status done), automatic branch-stacking and its fan-in footgun, short-id node naming,
sub-coordinator nodes, and the self-guard prompt patterns — none of which the tool schemas convey. The
decision triad in §4 tells you when the plan-DAG is the right tool versus spawning workers directly
or using in-session sub-agents.
4. Decision rules
- Starting a coordination effort?
hera_new_orchestrator. Don't hera_join first.
- A coordinator spawned you (fresh born-bound worker terminal)?
hera_join(cwd) to read your role
- mission, then
hera_status(working). You are already bound — no attach needed.
- Joining a team that didn't spawn you?
hera_join attach mode with explicit role_name + kind.
new_orchestrator vs join: new_orchestrator makes you a coordinator of a new orchestrator;
join claims/attaches a role under an existing one. A worker can do BOTH — stay a worker in the
parent and hera_new_orchestrator to become a coordinator of a nested team (multi-binding).
spawn_worker vs adopt: native hera has no adopt step — workers are born bound at spawn time.
(The old depends_on-driven auto-adopt watcher was retired with the DAG.) To delegate, just
hera_spawn_worker.
- Never
hera_move your OWN coordinator binding to join another team. hera_move's kind is
worker/freelance only — moving away from a live coordinator role ends that binding and orphans
the whole orchestrator/subtree you were coordinating, leaving a disconnected freelance/worker stub
under the target with no link back to it (hera-freelancer-bug; the tool now rejects this outright).
There is no agent-facing tool to nest an existing coordinator + subtree under a new parent — that's
the Hera TUI's J (adopt/reparent) key, human-only. If a human wants your whole team folded under
another coordinator, tell them to press J on your orchestrator; don't try to self-relocate.
- The coordination decision — in-session sub-agents vs hera workers vs the plan-DAG (settle this
BEFORE delegating). Delegate with prejudice, but don't be dumb about it:
- Ephemeral, in-session work — research, review, fan-out reads, anything that returns results
to you and needs no worktree/PR of its own? → use Claude's native sub-agents (Agent/Task
tool). NOT hera. Hera is overkill and slower for in-session parallelism. But don't reflexively
round-trip a sub-agent for every read: a single small file or a one-shot grep with a few hits is
cheaper read inline than dispatched — delegate when the exploration volume clearly dwarfs the
answer needed back, not on principle.
5. TLDR discipline
Every hera_send requires a tldr ≤120 chars, written from the recipient's perspective. It is shown
in the doorbell, returned by hera_tree_updates, and stored permanently.
- Good:
"PR #47 open, tests green, needs review" / "Blocked on missing API key — need rotation"
- Bad:
"update" (says nothing) / "Done with the work" (no specifics) / multi-line.
6. Gotchas worth calling out
hera_send requires status for worker/freelance senders — omitting it is an error. The status
is applied synchronously before the send completes; it never rides the async delivery bus. This means
every hera_send call doubles as a role-status heartbeat. There is no default; the error message on
omission names the valid values (idle/working/blocked/done/failed).
- Spawned workers default to the project's stale default branch, NOT the coordinator's branch.
hera_spawn_worker's branch defaults to the project default (e.g. an old master/main), not the
coordinator's current worktree branch. If the worker must build on the coordinator's (or a sibling's)
work, pass branch= explicitly and verify ancestry — otherwise the worker starts from stale code.
- Pass the MISSION only in
hera_spawn_worker / hera_plan_node prompts — never prepend the
org/security policy. A hera worker is a full argus session that receives its org instructions
independently, via its OWN session's harness injection (an <organizationInstructions> block, verified
present in every spawned session regardless of the parent). So a manually prepended copy is a redundant
duplicate — and argus stores the prompt verbatim on the role and renders its opening lines as the node's
description in the plan-DAG, so a prepended policy pollutes the DAG (every node reads the boilerplate
instead of its mission). Write the prompt as exactly the message you want the worker to act on.
- Bake all requirements into the initial
hera_spawn_worker prompt. Mid-flight hera_send to a
worker is often missed: delivery is idle-gated and best-effort, so a busy worker never receives it and
an idle/finished worker may not act on it. Put the full spec in the spawn prompt; verify via the branch
diff and re-dispatch a fresh worker if one idled out without the requirement.
- The message bus is idle-gated and best-effort for delivery, durable for storage. Storage always
succeeds (the row is committed); live pane delivery soft-fails (logged, never rolled back) when the
recipient has no live binding or never becomes idle.
hera_inbox always returns the durable rows, so
the recipient can always catch up by reading — don't assume a sent message was seen just because it sent.
7. Worked workflows
(a) Bootstrap an orchestrator, spawn two workers, collect results
You are a coordinator-to-be in your argus sandbox:
hera_new_orchestrator(cwd=$PWD, name="checkout-revamp", coordinator_role_name="coord", prompt="Coordinate the checkout revamp").
- Spawn workers, each with the FULL spec baked in and an explicit base branch:
hera_spawn_worker(cwd=$PWD, role_name="cart-api", branch="argus/<base>", prompt="<complete cart-API spec…>")
hera_spawn_worker(cwd=$PWD, role_name="checkout-ui", branch="argus/<base>", prompt="<complete checkout-UI spec…>")
- Poll progress without flooding context:
hera_tree_updates(cwd=$PWD) → scan TLDRs →
hera_get_messages(cwd=$PWD, ids=[…]) for the interesting ones.
- When a worker reports
done (its task rolls to in_review + ready_to_close in the rail), review its
branch/PR independently (verify the PR merged, tests actually green, etc.). Once satisfied, call
hera_accept(cwd=$PWD, role_name=<worker-name>) to mark the task complete, then reply or spawn the
next stage. To stack work, branch the next worker off the prior worker's branch via branch=.
(b) A spawned worker reports completion
You opened in a born-bound worker terminal:
hera_join(cwd=$PWD) → read your role name, mission (role prompt), and unread count.
hera_status(cwd=$PWD, status="working").
- Do the work in your worktree. If you hit a fork that needs the coordinator's call:
hera_send(cwd=$PWD, status="working", body="<question + context>", tldr="Need decision: X vs Y for the cart schema")
(no to needed — default-routes to the coordinator), then check hera_inbox(cwd=$PWD) on the
doorbell for the answer. Always supply status on every hera_send — it is required for
worker/freelance senders.
- Land your work (open a PR via iris, or leave commits for the coordinator to pull).
hera_send(cwd=$PWD, status="done", body="<summary + PR link>", tldr="cart-api done, PR #47, tests green")
— the synchronous status apply rolls your task to in_review + ready_to_close so the coordinator
sees you finished. If you cannot complete: hera_send(cwd=$PWD, status="failed", body="<reason>", …).
(c) Author a staged plan-DAG
When the work decomposes into multiple multi-session units with dependencies among them, load the
hera-plan skill and author a plan-DAG — it carries the tools, gating, branch-stacking, naming, and
a full worked seed→fan-out→fan-in example. The §4 decision triad tells you when that's the right tool.
Worker promotion: becoming a sub-coordinator
If you already know up front (at plan-authoring time) that a stage is a sub-team, prefer the
declarative form: a kind=subcoord plan node (see the hera-plan skill). The gater then
materializes the sub-coordinator for you when its blockers finish. The runtime promotion below is for
when a worker discovers the need mid-task.
If a worker realizes mid-task it needs its own team (cross-repo work, real parallelism, a long sub-task):
hera_new_orchestrator(cwd=$PWD, name="<sub-team>", coordinator_role_name="coord", prompt="…") — now
this session is a coordinator of a nested orchestrator AND still a worker in the parent (multi-binding;
pass orchestrator= on subsequent calls).
hera_spawn_worker(...) to dispatch into the right project.
- Report the sub-orchestrator name back to the parent coordinator via
hera_send. Prefer using Claude's
native sub-agents for in-session parallelism; reserve hera_spawn_worker for work where the session
itself (separate worktree / repo / sandbox) is the unit.
8. Composition with sibling argus tools
Hera owns identity, messaging, and coordination — nothing else. Reach the rest through their own MCP
tools:
- iris (
mcp__argus__iris_*) — host-side git/gh. A worker codes + commits locally in its worktree,
then uses iris to push / open a PR / merge back. Use iris_gh_pr_create rather than gh pr create
so the PR is stamped onto the task's pr meta namespace — that is what the Hera rail's PR indicator
reads (best-effort, never fetched by the view).
- plannotator-argus (
mcp__argus__plannotator_*) — review UI. A coordinator routes a worker's output
to review there; hera carries the decision and the handoff message, plannotator carries the review
surface.
These are orthogonal — hera does not wrap them and they do not wrap hera. Pick per op: iris when an action
touches the host, plannotator when it's a review surface, hera when it's about roles or messaging.
9. Diligence-profile awareness (ARGUS_PROFILE / ARGUS_ARCHETYPE)
Argus can route model choice per archetype via diligence profiles — named, on-disk presets that
map an archetype (what kind of job a task is) to a model/effort/window. Resolution happens daemon-side
at spawn, outside your sandbox; the result is handed to you as environment variables, not files. You
do not load or parse any profile — you only read the env.
What you can read (when set):
ARGUS_ARCHETYPE — your task's archetype: one of brainstorm, orchestrator, big_build,
code_slice, bug_fix, review, security_review, synthesis, spec_audit, ci_loop, verify,
recovery, docs. It tells you what kind of job this is — a code_slice worker should behave like a
focused implementer, a review worker like a reviewer, a ci_loop worker like a mechanical green-the-
build loop. Treat it as a hint about the diligence expected of you, not a hard contract.
ARGUS_PROFILE — the name of the bound profile that drove the choice (e.g. default, lean,
customer_grade).
ARGUS_MODEL — the model the profile selected for your archetype.
Critical: all three are exported together or not at all, and may be absent even when you have an
archetype. They are exported only when a bound profile actively contributed a backend-valid model.
If the project has no bound profile, the profile is missing/invalid, your archetype isn't mapped, or the
profile's model isn't valid for your backend, resolution fails open (the agent runs on its own CLI
default) and none of the three vars are set. So: read ARGUS_ARCHETYPE if present for a behavior
hint, but never assume it exists, and never block on it.
You do not consult profile files. Reading ~/.argus/profiles/ from inside the sandbox can EPERM;
that is exactly why resolution runs daemon-side and arrives by env. If you spawn workers yourself, pass
archetype= on hera_spawn_worker (and on plan-DAG nodes — see the hera-plan skill) to set their
archetype; you do not set your own.
Let the archetype resolve the model: omit model on hera_spawn_worker whenever you pass archetype=,
so the profile's per-archetype tier actually takes effect (precedence is task.Model → profile[archetype].model → backend default — an explicit model always wins over the profile). Only pass an explicit model
alongside archetype= when you deliberately want to override that tier for this one spawn — otherwise you
silently defeat the archetype's whole purpose (e.g. a ci_loop worker spawned with model="opus" throws away
the cheap tier ci_loop exists to select, and just runs at full price with no signal anything went wrong).
Reviewer panels are NOT driven here (deferred). A customer_grade-style profile may carry a [panel]
reviewer block, but this skill does not consume it — composing and running a reviewer panel is owned by
the sibling 2a-xvendor-review capability and is not wired up yet. Do not attempt to assemble a review
panel from the profile; treat [panel] as out of scope for now.