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)
This skill applies only 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 mcp__argus__hera_* tools are not registered in this session — there
is no CLI fallback and nothing below applies.
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 ten cover bootstrap, messaging, status, 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])
— 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_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 outside that flow
(ad hoc spawns, or roles with no plan-DAG dependents).
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.
- 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):
- 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.
This is the worker's self-report, NOT task closure. Once you (the coordinator) have independently
verified the outcome (the PR actually merged, CI green, deliverables shipped—not just taken the worker's
word for it), call hera_accept(cwd=$PWD, role_name=<name>) in the SAME turn. Don't wait to be
asked, and don't treat a status message alone as sufficient closure. 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 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.
1---2name: hera-23description: 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.4---56# Hera — native multi-agent coordination inside argus78Hera is argus's **native, in-tree** layer for running a *team* of agents. It is not a separate9daemon or plugin — coordination runs in-process in the argus daemon, the rail/tree render directly10in the TUI's second tab, and agents drive it entirely through the `mcp__argus__hera_*` MCP11tools. State lives in the same `~/.argus/data.sql` (the `hera_*` tables). You never touch the12plumbing; you call the tools.1314## 1. When this applies (and when it does NOT)1516This skill applies **only inside an argus task sandbox**. You are in one if **either** holds:1718- `ARGUS_TASK_ID` is set, **or**19- the current working directory is under `~/.argus/worktrees/`.2021**If neither holds, stop.** The `mcp__argus__hera_*` tools are not registered in this session — there22is no CLI fallback and nothing below applies.2324**Every hera tool takes `cwd` — always pass `cwd=$PWD`.** That is how hera resolves which argus task25(and therefore which role) this session is. There is no separate "auth" or session handle.2627## 2. The role model2829- **Orchestrator** — a named coordination graph (one per project / feature / wave). Roles live under30 it. Created by `hera_new_orchestrator` (idempotent by name).31- **Coordinator role** — the orchestrator's driver. Created by `hera_new_orchestrator`. Talks to the32 human in its own agent pane; talks to other roles via `hera_send`. Folded into the rail's33 orchestrator header (it is not a separate row).34- **Worker role** — does the actual work. Normally **born-bound**: spawned by a coordinator with35 `hera_spawn_worker`, which creates the argus task (worktree + session) AND the role + binding in one36 transaction. A born-bound worker does *not* need to `hera_join` — its session opens already bound.37- **Freelance role** — a helper that attaches itself to an existing orchestrator on its own38 initiative (no coordinator spawned it). Created via `hera_join` attach mode with `kind=freelance`.39- **Binding** — the live link between *this argus task* and *one role*. Invariants:40 - **One live binding per `(task, orchestrator)`** — enforced by a partial unique index.41 - **A task MAY hold several live bindings at once** — one per orchestrator (e.g. a worker in A that42 promotes itself to coordinator of nested orchestrator B). When a task holds **2+** live bindings,43 you **must** pass `orchestrator=<name>` to every tool so hera knows which role you are acting as;44 omitting it returns an ambiguity error listing your options.4546## 3. The coordination tools4748All take `cwd`. `orchestrator` is optional with exactly one live binding and **required** with 2+.49Arg names below are exact — do not invent others. These ten cover bootstrap, messaging, status, and50completion; the plan-DAG authoring/mutation tools live in the companion `hera-plan` skill (pointer at51the end of this section).5253### Bootstrap / join5455- **`hera_new_orchestrator(cwd, name, coordinator_role_name, [prompt])`** — "I am the coordinator."56 Creates (or fetches, idempotent-by-name) the orchestrator, creates the named coordinator role, and57 binds this task to it. Returns the orchestrator name, role name, `binding_id`, and argus task id.58 Rejects if this task already holds a live binding under that orchestrator (use `hera_join` to59 retrieve it). The canonical "become an orchestrator" entry point — don't `hera_join` first; there is60 nothing to join yet.6162- **`hera_join(cwd, [orchestrator], [role_name], [kind], [prompt], [status])`** — two modes:63 - **Claim mode** (`role_name` omitted) — retrieves this task's existing live binding + role and its64 unread message count. Use right after a born-bound worker terminal opens to read your assigned65 role/mission. Pass `orchestrator=` if the task has 2+ bindings. Claim mode does **not** consume the66 inbox.67 - **Attach mode** (`role_name` + `kind` supplied) — creates a **new** role under an *existing*68 orchestrator (`orchestrator` required) and binds this task to it. `kind` must be `worker` or69 `freelance` (`coordinator` is rejected — use `hera_new_orchestrator`). Optional `prompt` (stored on70 the role) and `status` (`idle`/`working`/`blocked`/`done`). Use to join a team nobody spawned you71 into.7273- **`hera_spawn_worker(cwd, prompt, [orchestrator], [role_name], [project], [branch], [backend], [model])`**74 — spawn a new **born-bound** worker task + session under the caller's orchestrator. **Caller must hold75 a live coordinator binding.** Creates an argus task (worktree + session) and, transactionally, a worker76 role + binding pre-bound to it; an orientation prefix naming the coordinator + orchestrator is prepended77 to the prompt automatically. Args:78 - `prompt` (**required**) — the worker's MISSION/task only. The verbatim prompt is also stored on the79 role row and shown as the node's description in the plan-DAG view. **Do NOT prepend the org/security80 policy** — see the mission-only gotcha in §6.81 - `project` — defaults to the **coordinator's own task project** (authoritative, not `role.ArgusProject`).82 - `branch` — base branch passed to argus task creation. **Defaults to the project default — see the83 base-branch gotcha in §6.**84 - `backend` — defaults to project default.85 - `model` — per-worker model override, scoped to the worker's resolved backend (claude: opus/sonnet/86 haiku; codex: e.g. gpt-5; pi: its ids). Empty = backend default. Match it to task complexity.87 - `role_name` — derived from a prompt slug if omitted; uniquified within the orchestrator.88 - `orchestrator` — disambiguates when the calling task holds multiple live coordinator bindings.8990 Returns the orchestrator, worker role name, `binding_id`, argus task id, and project.9192### Messaging (idle-gated bus)9394- **`hera_send(cwd, body, tldr, status, [to], [in_reply_to], [orchestrator])`** — message another role95 **in the same orchestrator**. `body` and `tldr` are **required**; `tldr` is a one-line summary ≤12096 chars, written from the recipient's perspective (see §5). **`status` is REQUIRED for worker/freelance97 senders** (one of `idle`/`working`/`blocked`/`done`/`failed`) and is applied to the sender's role98 **synchronously** before the message is sent — it is never delivered async. Omitting `status` as a99 worker/freelance sender is an error; coordinator senders may omit it. Worker/freelance senders may100 omit `to` — it default-routes to the orchestrator's coordinator. **Coordinators must supply an101 explicit `to`.** `in_reply_to` threads a reply to a prior message id. Returns the `message_id`,102 recipient, and delivery mode. Caps: 64 KiB body, 500 unread per recipient, 50 sends/min/sender.103 Cross-orchestrator messaging is not possible — `to` always resolves within your own orchestrator.104105- **`hera_inbox(cwd, [orchestrator])`** — fetch all unread messages addressed to your role, oldest106 first. **Reading IS acknowledgment**: this both cancels pending pane deliveries AND marks the107 messages read — no separate `hera_mark_read` needed for normal consumption. Call it whenever you get a108 doorbell.109110- **`hera_mark_read(cwd, message_ids, [orchestrator])`** — explicitly mark specific message ids read and111 cancel their pending deliveries. Use when you read via `hera_get_messages` instead of `hera_inbox`.112113### Status / tree114115- **`hera_status(cwd, status, [orchestrator])`** — set your role status: `idle` | `working` | `blocked`116 | `done` | `failed`. Mirrored (best-effort) to argus `task_meta` so the coordinator sees it without117 asking. **A `worker`-kind role reporting `status=done` also rolls its bound argus task to `in_review`118 and stamps `ready_to_close`** (visible in the rail) — see §4. **A worker reporting `status=failed`119 rolls its task to `in_review` WITHOUT `ready_to_close`** (needs-attention, not ready to check off).120 The gater treats a `failed` blocker as explicitly failed (no need to wait for session death).121 Coordinators/freelancers just update status.122123- **`hera_accept(cwd, role_name, [orchestrator], [message])`** — coordinator-only: mark a role's bound124 task complete and send it a check-in asking whether it's winding down, has more work to do, or is125 unsure. The reply is informational only — never auto-reopens the task; a premature accept is undone126 via `hera_revive` alone. Never stops the session (completion and detachment are separate). Acts from127 any non-complete status; a no-op if already complete. The plan-DAG gater fires this automatically128 for blockers when dependent nodes materialize — use this tool for accepting work *outside* that flow129 (ad hoc spawns, or roles with no plan-DAG dependents).130131- **`hera_tree_updates(cwd, [orchestrator], [since])`** — scan the caller's orchestrator **subtree**132 (nested sub-orchestrators included) for messages since a cursor. Returns **TLDR-only subject lines —133 no bodies** (capped at 200), plus a `next_cursor`. The cursor is stored **per-role** and auto-advances134 when you omit `since`; passing an explicit `since` is a one-off scan that does NOT clobber the stored135 cursor. The token-efficient way to see whole-team activity without flooding context with bodies.136137- **`hera_get_messages(cwd, ids, [orchestrator])`** — fetch full message bodies by id list, after138 scanning `hera_tree_updates`. Access is scoped to the caller's orchestrator **subtree** (sender OR139 recipient must live in it); inaccessible / missing ids get a per-id `error` field rather than a140 top-level error.141142### Plan-DAG authoring (staged, dependency-ordered work) → the `hera-plan` skill143144> **With a live coordinator binding, do NOT use the harness `TaskCreate` system-reminder for145> coordinated work — coordinate via the hera tools (`hera_spawn_worker`, or the plan-DAG), never146> bare task creation.**147148For work that decomposes into multiple hera worker units **with dependencies among them** (one stage149needs another's output, or a required ordering), hera offers a **plan-DAG**: planned nodes wired by150blocking edges that the daemon gater materializes into born-bound workers in dependency order,151auto-stacking each stage's branch on the prior. It renders in the TUI's second tab and is a *living*152graph (edit / re-point / cancel as work evolves).153154**Load the companion `hera-plan` skill** before authoring or driving a plan-DAG. It carries the six155plan tools (`hera_plan`, `hera_plan_node`, `hera_block`, `hera_plan_node_update`, `hera_unblock`,156`hera_plan_node_cancel`), the gating contract (a node materializes ONLY when every blocker reaches157role-status `done`), automatic branch-stacking and its fan-in footgun, short-id node naming,158sub-coordinator nodes, and the self-guard prompt patterns — none of which the tool schemas convey. The159decision triad in §4 tells you *when* the plan-DAG is the right tool versus spawning workers directly160or using in-session sub-agents.161162## 4. Decision rules163164- **Starting a coordination effort?** `hera_new_orchestrator`. Don't `hera_join` first.165- **A coordinator spawned you (fresh born-bound worker terminal)?** `hera_join(cwd)` to read your role166 + mission, then `hera_status(working)`. You are already bound — no attach needed.167- **Joining a team that didn't spawn you?** `hera_join` attach mode with explicit `role_name` + `kind`.168- **`new_orchestrator` vs `join`:** `new_orchestrator` makes you a coordinator of a *new* orchestrator;169 `join` claims/attaches a role under an *existing* one. A worker can do BOTH — stay a worker in the170 parent and `hera_new_orchestrator` to become a coordinator of a nested team (multi-binding).171- **`spawn_worker` vs adopt:** native hera has **no adopt step** — workers are born bound at spawn time.172 (The old `depends_on`-driven auto-adopt watcher was retired with the DAG.) To delegate, just173 `hera_spawn_worker`.174- **Never `hera_move` your OWN coordinator binding to join another team.** `hera_move`'s `kind` is175 `worker`/`freelance` only — moving away from a live coordinator role ends that binding and orphans176 the whole orchestrator/subtree you were coordinating, leaving a disconnected freelance/worker stub177 under the target with no link back to it (hera-freelancer-bug; the tool now rejects this outright).178 There is no agent-facing tool to nest an existing coordinator + subtree under a new parent — that's179 the Hera TUI's `J` (adopt/reparent) key, human-only. If a human wants your whole team folded under180 another coordinator, tell them to press `J` on your orchestrator; don't try to self-relocate.181- **The coordination decision — in-session sub-agents vs hera workers vs the plan-DAG (settle this182 BEFORE delegating):**183 1. **Ephemeral, in-session work** — research, review, fan-out reads, anything that returns results184 to you and needs no worktree/PR of its own? → use **Claude's native sub-agents** (Agent/Task185 tool). NOT hera. Hera is overkill and slower for in-session parallelism.186 2. **Work whose unit must be its OWN argus session** — separate worktree / its own PR / long-running187 / its own sandbox? → **hera**. Then split by dependency:188 - Units have **dependencies among them** (one needs another's output, or a required ordering)? →189 **plan-DAG**: load the `hera-plan` skill, author planned nodes + blocking edges, let the gater190 run them in dependency order. *Decide this yourself when the dependency is obvious — don't ask191 the human.* **Internal dependencies are the clean signal to plan a DAG** rather than spawn ad hoc.192 - Units are **independent** (no ordering)? → just `hera_spawn_worker` them in parallel; no DAG.193 3. **Ask the human only** when it's genuinely ambiguous whether the work warrants multi-session194 orchestration at all — never for the routine dependency call above.195- **This task holds 2+ bindings?** Pass `orchestrator=` on EVERY tool call.196- **Got a doorbell?** Call `hera_inbox(cwd=$PWD)` immediately — the content is in the inbox, not the197 doorbell line.198- **Want whole-team state?** `hera_tree_updates(cwd=$PWD)`, then `hera_get_messages(ids=[…])` for the199 ones worth reading.200- **How completion flows back:** a worker finishing sends a closing `hera_send(status="done", …)` — the201 synchronous status apply rolls its task to `in_review` + `ready_to_close`, visible in the rail.202 **This is the worker's self-report, NOT task closure.** Once you (the coordinator) have independently203 verified the outcome (the PR actually merged, CI green, deliverables shipped—not just taken the worker's204 word for it), **call `hera_accept(cwd=$PWD, role_name=<name>)` in the SAME turn.** Don't wait to be205 asked, and don't treat a status message alone as sufficient closure. A worker that cannot complete206 sends `hera_send(status="failed", …)` — rolls to `in_review` WITHOUT `ready_to_close` (needs207 attention, not ready to check off); the gater holds any dependent planned nodes and pings you. Both208 rolls are idempotent and only fire when the task is still `in_progress`. The live session is left209 running.210- **Don't** use `hera_send` to talk to the human — the human reads the coordinator's own agent pane;211 the bus is role-to-role only.212213## 5. TLDR discipline214215Every `hera_send` requires a `tldr` ≤120 chars, written from the recipient's perspective. It is shown216in the doorbell, returned by `hera_tree_updates`, and stored permanently.217218- Good: `"PR #47 open, tests green, needs review"` / `"Blocked on missing API key — need rotation"`219- Bad: `"update"` (says nothing) / `"Done with the work"` (no specifics) / multi-line.220221## 6. Gotchas worth calling out222223- **`hera_send` requires `status` for worker/freelance senders — omitting it is an error.** The status224 is applied synchronously before the send completes; it never rides the async delivery bus. This means225 every `hera_send` call doubles as a role-status heartbeat. There is no default; the error message on226 omission names the valid values (`idle`/`working`/`blocked`/`done`/`failed`).227- **Spawned workers default to the project's stale default branch, NOT the coordinator's branch.**228 `hera_spawn_worker`'s `branch` defaults to the *project* default (e.g. an old `master`/`main`), not the229 coordinator's current worktree branch. If the worker must build on the coordinator's (or a sibling's)230 work, **pass `branch=` explicitly and verify ancestry** — otherwise the worker starts from stale code.231- **Pass the MISSION only in `hera_spawn_worker` / `hera_plan_node` prompts — never prepend the232 org/security policy.** A hera worker is a full argus session that receives its org instructions233 independently, via its OWN session's harness injection (an `<organizationInstructions>` block, verified234 present in every spawned session regardless of the parent). So a manually prepended copy is a redundant235 duplicate — and argus stores the prompt verbatim on the role and renders its opening lines as the node's236 description in the plan-DAG, so a prepended policy pollutes the DAG (every node reads the boilerplate237 instead of its mission). Write the prompt as exactly the message you want the worker to act on.238- **Bake all requirements into the initial `hera_spawn_worker` prompt.** Mid-flight `hera_send` to a239 worker is often missed: delivery is idle-gated and best-effort, so a busy worker never receives it and240 an idle/finished worker may not act on it. Put the full spec in the spawn prompt; verify via the branch241 diff and re-dispatch a fresh worker if one idled out without the requirement.242- **The message bus is idle-gated and best-effort for *delivery*, durable for *storage*.** Storage always243 succeeds (the row is committed); live pane delivery soft-fails (logged, never rolled back) when the244 recipient has no live binding or never becomes idle. `hera_inbox` always returns the durable rows, so245 the recipient can always catch up by reading — don't assume a sent message was seen just because it sent.246- **`worker done` must keep the role messageable.** Native deliberately does NOT auto-archive a role on247 `hera_status(done)` (the external plugin did). An archived role drops out of name-keyed recipient248 resolution, so auto-archiving a still-live worker would make the coordinator's `hera_send` to it bounce.249 Done flips the task to in_review; it does not archive the role.250- **Coordinators must name a recipient.** `hera_send` from a coordinator with `to` omitted errors —251 only worker/freelance senders get the default-to-coordinator routing.252253## 7. Worked workflows254255### (a) Bootstrap an orchestrator, spawn two workers, collect results256257You are a coordinator-to-be in your argus sandbox:2582591. `hera_new_orchestrator(cwd=$PWD, name="checkout-revamp", coordinator_role_name="coord", prompt="Coordinate the checkout revamp")`.2602. Spawn workers, each with the FULL spec baked in and an explicit base branch:261 - `hera_spawn_worker(cwd=$PWD, role_name="cart-api", branch="argus/<base>", prompt="<complete cart-API spec…>")`262 - `hera_spawn_worker(cwd=$PWD, role_name="checkout-ui", branch="argus/<base>", prompt="<complete checkout-UI spec…>")`2633. Poll progress without flooding context: `hera_tree_updates(cwd=$PWD)` → scan TLDRs →264 `hera_get_messages(cwd=$PWD, ids=[…])` for the interesting ones.2654. When a worker reports `done` (its task rolls to in_review + ready_to_close in the rail), review its266 branch/PR independently (verify the PR merged, tests actually green, etc.). Once satisfied, call267 `hera_accept(cwd=$PWD, role_name=<worker-name>)` to mark the task complete, then reply or spawn the268 next stage. To stack work, branch the next worker off the prior worker's branch via `branch=`.269270### (b) A spawned worker reports completion271272You opened in a born-bound worker terminal:2732741. `hera_join(cwd=$PWD)` → read your role name, mission (role prompt), and unread count.2752. `hera_status(cwd=$PWD, status="working")`.2763. Do the work in your worktree. If you hit a fork that needs the coordinator's call:277 `hera_send(cwd=$PWD, status="working", body="<question + context>", tldr="Need decision: X vs Y for the cart schema")`278 (no `to` needed — default-routes to the coordinator), then check `hera_inbox(cwd=$PWD)` on the279 doorbell for the answer. **Always supply `status` on every `hera_send` — it is required for280 worker/freelance senders.**2814. Land your work (open a PR via iris, or leave commits for the coordinator to pull).2825. `hera_send(cwd=$PWD, status="done", body="<summary + PR link>", tldr="cart-api done, PR #47, tests green")`283 — the synchronous status apply rolls your task to in_review + ready_to_close so the coordinator284 sees you finished. If you cannot complete: `hera_send(cwd=$PWD, status="failed", body="<reason>", …)`.285286### (c) Author a staged plan-DAG287288When the work decomposes into multiple multi-session units with dependencies among them, **load the289`hera-plan` skill** and author a plan-DAG — it carries the tools, gating, branch-stacking, naming, and290a full worked seed→fan-out→fan-in example. The §4 decision triad tells you when that's the right tool.291292### Worker promotion: becoming a sub-coordinator293294> If you already know up front (at plan-authoring time) that a stage is a sub-team, prefer the295> **declarative** form: a `kind=subcoord` plan node (see the `hera-plan` skill). The gater then296> materializes the sub-coordinator for you when its blockers finish. The runtime promotion below is for297> when a worker discovers the need *mid-task*.298299If a worker realizes mid-task it needs its own team (cross-repo work, real parallelism, a long sub-task):3003011. `hera_new_orchestrator(cwd=$PWD, name="<sub-team>", coordinator_role_name="coord", prompt="…")` — now302 this session is a coordinator of a nested orchestrator AND still a worker in the parent (multi-binding;303 pass `orchestrator=` on subsequent calls).3042. `hera_spawn_worker(...)` to dispatch into the right project.3053. Report the sub-orchestrator name back to the parent coordinator via `hera_send`. Prefer using Claude's306 native sub-agents for in-session parallelism; reserve `hera_spawn_worker` for work where the session307 itself (separate worktree / repo / sandbox) is the unit.308309## 8. Composition with sibling argus tools310311Hera owns **identity, messaging, and coordination** — nothing else. Reach the rest through their own MCP312tools:313314- **iris** (`mcp__argus__iris_*`) — host-side git/gh. A worker codes + commits locally in its worktree,315 then uses iris to push / open a PR / merge back. **Use `iris_gh_pr_create` rather than `gh pr create`**316 so the PR is stamped onto the task's `pr` meta namespace — that is what the Hera rail's PR indicator317 reads (best-effort, never fetched by the view).318- **plannotator-argus** (`mcp__argus__plannotator_*`) — review UI. A coordinator routes a worker's output319 to review there; hera carries the *decision* and the *handoff message*, plannotator carries the *review320 surface*.321322These are orthogonal — hera does not wrap them and they do not wrap hera. Pick per op: iris when an action323touches the host, plannotator when it's a review surface, hera when it's about roles or messaging.