| 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 nine cover bootstrap, messaging, and status;
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])
— 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.
project — defaults to the coordinator's own task project (authoritative, not role.ArgusProject).
branch — base branch passed to argus task creation. Defaults to the project default — see the
base-branch gotcha in §6.
backend — defaults to project default.
model — per-worker model override, scoped to the worker's resolved backend (claude: opus/sonnet/
haiku; codex: e.g. gpt-5; pi: its ids). Empty = backend default. Match it to task complexity.
role_name — derived from a prompt slug if omitted; uniquified within the orchestrator.
orchestrator — disambiguates when the calling task holds multiple live coordinator bindings.
Returns the orchestrator, worker role name, binding_id, argus task id, and project.
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_tree_updates(cwd, [orchestrator], [since]) — scan the caller's orchestrator subtree
(nested sub-orchestrators included) for messages since a cursor. Returns TLDR-only subject lines —
no bodies (capped at 200), plus a next_cursor. The cursor is stored per-role and auto-advances
when you omit since; passing an explicit since is a one-off scan that does NOT clobber the stored
cursor. The token-efficient way to see whole-team activity without flooding context with bodies.
-
hera_get_messages(cwd, ids, [orchestrator]) — fetch full message bodies by id list, after
scanning hera_tree_updates. Access is scoped to the caller's orchestrator subtree (sender OR
recipient must live in it); inaccessible / missing ids get a per-id error field rather than a
top-level error.
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.
- The coordination decision — in-session sub-agents vs hera workers vs the plan-DAG (settle this
BEFORE delegating):
- 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.
- Work whose unit must be its OWN argus session — separate worktree / its own PR / long-running
/ its own sandbox? → hera. Then split by dependency:
- Units have dependencies among them (one needs another's output, or a required ordering)? →
plan-DAG: load the
hera-plan skill, author planned nodes + blocking edges, let the gater
run them in dependency order. Decide this yourself when the dependency is obvious — don't ask
the human. Internal dependencies are the clean signal to plan a DAG rather than spawn ad hoc.
- Units are independent (no ordering)? → just
hera_spawn_worker them in parallel; no DAG.
- Ask the human only when it's genuinely ambiguous whether the work warrants multi-session
orchestration at all — never for the routine dependency call above.
- This task holds 2+ bindings? Pass
orchestrator= on EVERY tool call.
- Got a doorbell? Call
hera_inbox(cwd=$PWD) immediately — the content is in the inbox, not the
doorbell line.
- Want whole-team state?
hera_tree_updates(cwd=$PWD), then hera_get_messages(ids=[…]) for the
ones worth reading.
- How completion flows back: a worker finishing sends a closing
hera_send(status="done", …) — the
synchronous status apply rolls its task to in_review + ready_to_close, visible in the rail. A
worker that cannot complete sends hera_send(status="failed", …) — rolls to in_review WITHOUT
ready_to_close (needs attention, not ready to check off); the gater holds any dependent planned
nodes and pings you. Both rolls are idempotent and only fire when the task is still in_progress.
The live session is left running.
- Don't use
hera_send to talk to the human — the human reads the coordinator's own agent pane;
the bus is role-to-role only.
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.
worker done must keep the role messageable. Native deliberately does NOT auto-archive a role on
hera_status(done) (the external plugin did). An archived role drops out of name-keyed recipient
resolution, so auto-archiving a still-live worker would make the coordinator's hera_send to it bounce.
Done flips the task to in_review; it does not archive the role.
- Coordinators must name a recipient.
hera_send from a coordinator with to omitted errors —
only worker/freelance senders get the default-to-coordinator routing.
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, 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.