harn agents
harn agents <subcommand> [options]The window into the coordination layer. Every agent sharing a checkout records a
generation to the V3 event ledger and materializes a disposable cache at
.harnery/active/<instance_id>.json. harn agents reads the authority-safe V3
projection to answer the questions that keep parallel agents out
of each other’s way: who else is here, what are they holding, what is each one
doing, and did anything go wrong.
Reach for it constantly: whoami and status to orient yourself, list to see
peers, show / trace to dig into one agent, and health when the layer
itself feels off.
For the outcome-oriented overview, see the Session state feature page. The Session state concept documents the event and replay semantics behind these fields.
Subcommands
Section titled “Subcommands”harn agents whoami # this agent's name + instance_id + files claimedharn agents list # all active agents (folds transients by default)harn agents status # status box (name, age, files, peers, task)harn agents status --end-turn # issue the box only after owned Git work is finalizedharn agents status --end-turn --end-session # final status, then end after this turn closesharn agents set-task <text...> # declare what you're working on (visible to peers)harn agents lifecycle <state> # declare active, blocked, or done task lifecycleharn agents end # record an authoritative V3 end after final checksharn agents reconcile # reconcile archive, idle, cascade, and host-loss signalsharn agents reconcile --watch # run the host supervisor loopharn agents identity assume <name-or-id> # continue a durable role in this sessionharn agents show <name> # deep-dive on one peer: registry state + session historyharn agents trace <name> # reconstruct one agent's lifecycle from the event ledgerharn agents watch # stream peer state changes in real timeharn agents ping <name> <msg> # message an agent by name, live or notharn agents wait <name> # block until a peer releases its files (or exits)harn agents release-claim <path> # drop a file claim from your own heartbeat (escape hatch)harn agents health # one-screen coord-layer health rollupharn agents heal-events # PIDMAP_HEAL telemetry (how often self-heal fixed drift)harn agents heal # repair this session's disposable cache from V3One further subtree lives under agents for advanced use: harn agents identity
(the durable per-agent persona registry). Two more are recovery tools:
harn agents heal forces a specific recovery action on one agent, and
harn agents adapter-probe diagnoses adapter wiring. Run
harn agents <sub> --help for any of them. Councils are their own top-level
command; see Councils.
When an adapter session survives a WSL or coordination-state restart, let Harnery resolve the current session and rebuild its disposable cache from the authoritative V3 generation:
harn agents healRunning the command with no options is the normal agent recovery path. Operators can still
target a known generation with --kind cache --owner <instance-id>, plus
--session-id and --adapter when the live projection cannot supply them.
Harnery validates the V3 route before it writes the cache. Never edit
.harnery/active/ or ledger rows by hand.
If a prepared authority transaction survives a crash and its prior state no
longer exists, normal writes report pending_transaction:<txn-id>. Inspect the
transaction and obtain operator approval before using the scoped quarantine
mode:
harn agents heal \ --quarantine-transaction txn_... \ --approval-record-id <record-id> \ --yesHarnery refuses the command when normal reconciliation is still possible, when
the transaction’s event reached the ledger or event spool, or when the private
producer state does not own the exact transaction. A successful recovery keeps
the original transaction in .harnery/ledgers/v3/authority-recoveries/quarantine/
and writes a canonical receipt beside it. It does not add the abandoned event
to the ledger. See ADR 0128
for the safety and retry contract.
identity assume
Section titled “identity assume”Use identity assume when a new adapter session is continuing a durable role
from a predecessor:
harn agents identity assume Yann# or, when the shell process cannot resolve its own session anchor:harn agents identity assume Yann --session-id <instance-id>The target may be a persona name, an agent-Name display name, or a persona
UUID. Harnery reuses the durable persona UUID when it exists and creates it when
a new name is first adopted. If another local session still has a fresh
heartbeat for that name but its adapter process is gone (crash, abandoned
tab, healed zombie), assume reclaims that heartbeat and continues. It refuses
only when another live local process still holds the name, or when cached
remote presence reports it.
One command updates every authoritative layer: it appends the binding to name
history, emits identity.assumed to the canonical ledger, and projects the new
name and persona UUID into the heartbeat. agents whoami --json then exposes
that UUID as agent_id. The web identity index is derived from the ledger and
heals automatically. Repeating the same successful command is a no-op.
This is role adoption, not persona renaming. The old session keeps its historical record, while the replacement becomes the current live owner of the role. It is limited to main sessions; subagents cannot assume operator-facing identities.
One target is refused even when nobody holds the name: a persona this session’s
recorded fork lineage descends from (identity_is_ancestor). A branched
conversation inherits a transcript full of its parent’s name, so asking to
become that parent is usually a session misled by its scrollback, not a
handoff. Pass --force-ancestor when succession is genuinely the intent. See
ADR 0057.
whoami, list, status
Section titled “whoami, list, status”The three orientation commands.
whoamiprints your own name,instance_id, and the files you’ve claimed.--jsonreturns the full heartbeat record, includingactivity,activity_updated_at,activity_source,task_state,task_state_updated_at, andtask_state_reason.listshows every active agent. By default it foldskind=transientrows (short-lived helper processes) into their parent;--allshows them raw.--staleincludes heartbeats older than the freshness window (agents that likely exited without cleaning up). Its structured rows keepactivityandtask_stateseparate for local and remote agents. Older heartbeats read asactivity: unknownandtask_state: active.statusrenders the end-of-turn status box: your name, session age, elapsed time for the current turn, current activity, task lifecycle, declared task, context usage, files held, and a one-line peer roster. A blocked lifecycle includes its reason.--jsonreturns the same data unformatted.--session-id <id>looks the heartbeat up by session id directly, for use from a hook whose process tree doesn’t lead back to the agent’s session pid. Add--end-turnfor the required final call: Harnery checks the current session’s held paths, enclosing submodule pointers, and touched repositories before issuing the box. Dirty held work or commits missing from a configured remote make the command fail instead. Add--end-sessionto the closing call to durably request an approved terminal event after this exact turn closes. It requires--end-turn; the status box reportsqueued after this turnwhile the stop hook finishes the sequence.
set-task
Section titled “set-task”Declares a short description of your current focus, stored on your heartbeat and
shown to peers in the per-prompt snapshot and on the dashboard. Pass an empty
string ('') to clear it. Like status, it accepts --session-id <id> for
adapters where the process walk can’t resolve self.
First-of-session session name. The session’s first non-empty focus
declaration builds a suggested_session_name (Agent <you> - <your task>),
stamps it on the heartbeat, and returns it with first_of_session: true for
the operator to set as their adapter’s session/tab title:
{ "task": "Auth Refactor", "first_of_session": true, "suggested_session_name": "Agent Hollis - Auth Refactor", "note": "Next, before any prose or another tool call, send suggested_session_name verbatim by itself in a fenced code block." }first_of_session: true means exactly “this call produced the session name” —
it is never true with a null name. A bare clear ('') as the first call does
not consume the naming window; the next non-empty declaration still names the
session. The --session-id path names the session like any other call.
Subagents and workflow children are never named.
With hooks installed, Harnery treats the suggestion as pending until the exact
copyable block has been displayed. PostToolUse asks for that block as the next
assistant text. Claude Code and Codex verify ordered transcript text before
allowing another tool. Cursor stamps the completed assistant text from
afterAgentResponse; its preToolUse.agent_message is only the current
narration, so missing the earlier block there cannot reject the tool. The block
must be the first non-whitespace content and contain only
suggested_session_name; the fence may carry any single-word language label,
and the closing fence must be as long as the opening one. Chat UIs add a
one-click Copy button, so the operator gets the exact title without
hand-selecting.
Displaying the title Harnery last asked for always satisfies the latch, even when the suggestion has since changed. A title can change after the instruction goes out — a lifecycle transition re-mints it, and a rebuilt generation cache can re-mint from the current task — and an agent must not be blocked for showing exactly what it was handed.
For transcript-backed adapters, the evidence boundary is the first assistant message after the mint result. Later commentary or a user prompt does not erase an exact display. If the adapter transcript is unavailable or has not flushed yet, Harnery keeps the check pending and allows the tool without injecting another reminder. The exact PostToolUse instruction was already delivered for that mint, so repeating a generic reminder on every tool cannot create an unbounded loop. A readable missing or malformed display is still denied.
The gate always allows a single agents set-task, agents status, or
agents suggest-name command. Those are the focus, recovery, and turn-closing
paths, so the naming check must not block them. Routine set-task calls stay
title-silent even while a display is pending. For an explicit retry, run
agents suggest-name --json; it returns the exact stored pending name with
session_name_retry: true and creates a fresh evidence boundary. Shell control
syntax disqualifies the exemption.
The same pending comparison drives prompt reminders, including the one terminal
name re-mint when lifecycle becomes done. An unminted session is reminded on
every prompt until its first focus declaration. A pending session is reminded
once per exact minted name, even if the current task later changes. Subagents
and workflow children are skipped. Every later ordinary set-task returns
first_of_session: false, session_name_retry: false, and a null name.
set-task never changes task lifecycle. If task text changes while the session
is blocked or done, the command returns a warning to reopen the lifecycle
explicitly. The new task text is still recorded.
The Stop-time fallback accepts only the first assistant message after the tool result that minted the name. A plain-text mention or a block printed after substantial work does not count. Claude Code can enforce that fallback; Cursor’s Stop payload has no reply text, and Codex Stop remains observe-only. PreToolUse handles the check before those adapter differences matter.
lifecycle
Section titled “lifecycle”Declares whether the current task is active, blocked, or done:
harn agents lifecycle activeharn agents lifecycle blocked --reason "waiting for repository access"harn agents lifecycle done --reason "implementation and verification complete"blocked requires a reason. done requires a current task and runs the same
Git finalization check as agents status --end-turn. Dirty touched paths,
incomplete claim history, an uncommitted enclosing submodule pointer, an
unverifiable repository, or commits missing from a configured remote make the
command fail without changing lifecycle or recording an event. The gate checks
repository hygiene; it does not certify tests, deployment, or the business
outcome.
A successful transition records one durable coord.lifecycle_changed event and
projects it into the live heartbeat. Repeating the same state and reason is a
no-op, with no duplicate event. Moving away from blocked clears the old
blocker reason. Subagents, transient processes, and workflow children cannot
declare operator-facing lifecycle state.
Lifecycle changes do not rename the session except when it becomes done.
That transition returns the original session name with [DONE] prepended:
active -> no title changeblocked -> no title changedone -> [DONE] Agent Hollis - Auth RefactorThe per-prompt reminder and display latch remain active until that terminal
name is observed. Reopening a done session does not ask the agent to print or
restore another title. Titles are projections for people; editing a title
never changes the underlying lifecycle. Pass --session-id <id> when the
calling process cannot resolve its session anchor.
end and reconcile
Section titled “end and reconcile”harn agents end is a deliberate V3 session-finalization command. It refuses
delegated children and unfinished Git claims. If the generation is already
idle, it appends one approved session.ended event immediately. If the command
runs inside an open turn or tool span, it durably queues the end for the stop
hook. The request is bound to that turn and its existing spans; any new turn,
tool request, delegation, or resume cancels it instead of terminating new work.
Use --session-id or --instance-id when automatic identity resolution is
unavailable.
For an agent ending itself, prefer the one-command closing ritual:
harn agents status --end-turn --end-sessionharn agents reconcile performs one crash-safe lifecycle pass. It discovers
verified adapter archives, long-idle sessions, parent and run completion,
delegated-agent completion, stale sweeps, duplicate live generations, and
previously submitted host-loss observations. --watch repeats the pass at
coord.finalization.reconcile_interval_seconds.
Archive observations can also enter through:
harn agents observe-archive --adapter codex --session-id <native-id>harn agents observe-archive --adapter codex --session-id <native-id> --unarchivedUnarchiving or subsequent activity cancels the pending archive request. A host
supervisor can submit a provisional loss signal with
observe-host-disappeared; it cannot bypass the configured grace period. See
ADR 0077.
suggest-name
Section titled “suggest-name”Reprints a session name (Agent <you> - <description>) for the operator’s tab
title, read-only. This is the secondary path: set-task already suggests a
name on the first focus declaration (above). Reach for suggest-name to reprint
the current session’s name or re-suggest one after a topic pivot. With an explicit
arg it uses that. With no arg it returns the exact stored pending name when one
exists, even if the current task has changed; otherwise it derives a name from
the current task:
$ harn agents suggest-name "Auth Refactor"Agent Hollis - Auth Refactor$ harn agents suggest-name # no arg → from current taskAgent Hollis - Auth RefactorIt prints the bare name on stdout (not a drawn box, since a box would copy its
borders and labels too). --json also returns session_name_retry: true when
it reprints the exact pending suggestion. PostToolUse treats that explicit
response as a fresh display boundary. Like status, it accepts
--session-id <id>. Mutates no state.
show, trace
Section titled “show, trace”Two ways to dig into a single agent, addressed by name (agent-Foo or Foo) or
by instance_id.
showis the current state view: files held, last tool, declared task, current activity, task lifecycle, plus recent session history (latest title, recent prompts, tool-usage tallies). A name is disambiguated to aninstance_idby prefix match.traceis the history view: it reconstructs one agent’s lifecycle from the event ledger in chronological order: session start, prompts, turns, input requests, lifecycle declarations, tools, heals, sweeps, file claims, and exit. Its header carries the latest folded activity and lifecycle, even after the live heartbeat is gone. This is the answer to “what happened to this agent / why did it vanish?” without hand-grepping the stream.--since <window>bounds it (e.g.1h,2d),--limit <n>caps it (default200), and--all-toolsincludes the per-tool noise that’s hidden by default.
watch, ping, wait
Section titled “watch, ping, wait”The coordination-in-motion commands.
-
watchstreams peer state changes live (a file watcher on.harnery/active/), printing one line per delta: started, ended, activity, file claim, task change.--poll-ms <n>tunes the debounce (default200). -
ping <name> <message...>sends a message to an agent by name. It does not require the recipient to be running, which is what makes it safe to use as a single step: a live recipient is shown the message on their next prompt, and a name with no live session has the message held in a durable mailbox until a session of that name next starts. Either way the message is recorded in the recipient’s journal. The only refusal is a name no agent has ever held, and that error lists the closest known names, so a sender never has to look up who is live before sending or find another place to leave a note.Two ceilings keep a mailbox bounded: 4000 bytes per message and 25 pending messages per name. Past either, the send is refused and the error says to write the payload to a managed artifact and send its path instead. Delivered messages are copied to
.harnery/mailbox/delivered/<name>.jsonlfor audit.Claude Code, Codex, and Cursor receive unread messages on their next prompt. A message already waiting when a session starts may arrive with SessionStart context instead. Delivery atomically claims the pending batch, so later prompts cannot replay it. Senders need no adapter knowledge.
-
wait <name>blocks until a peer releases its files (itsfiles_touchedempties) or exits. Scope it to specific paths with--file <path>(repeatable), cap it with--timeout <dur>(suffixs/m/h/d; a bare integer means minutes; default60m), and tune polling with--poll-secs <n>(default5).--quietsuppresses progress lines;--jsonprints the terminal status at exit. Pairs naturally withping.
release-claim
Section titled “release-claim”release-claim <path> drops a single file claim from your own heartbeat. It’s
an operator escape hatch for when an auto-release didn’t fire (e.g. a session
ended mid-edit) and a peer is now blocked on a path you no longer care about.
health, heal-events
Section titled “health, heal-events”The layer’s own diagnostics.
healthis a one-screen rollup: heal events, council activity, zombie detection, and anomalies. Built for a daily glance or dashboard ingestion.--since <window>sets the window (default24h);--jsonfor structured output. When the V3 event ledger is live, anevent ledgersection adds read-only producer counters: open tool spans per live generation (flagging generations whose turn has closed while spans stayed open — the orphan signature that blocks a clean session end), pending finalization requests with trigger and age, intake-spool depth, diagnostics-spool counts by category (with a last-24h split), and span-count pressure against the producer-state cap. On an unavailable or invalid V3 control state the section reports that failure explicitly instead.heal-eventssurfaces PIDMAP_HEAL telemetry: how often the pid-map self-heal had to correct drift. Sustained high counts point at an upstream process-spawn bug.--since <window>(default7d),--limit <n>(default20), plus--json/--csv.
Examples
Section titled “Examples”# Orient yourself at the start of a turnharn agents whoami
# See who else is active and what they're holdingharn agents list
# Declare your focus so peers can see itharn agents set-task "refactoring the ingest retry loop"
# Declare an operator-facing lifecycle transitionharn agents lifecycle blocked --reason "waiting for API access"harn agents lifecycle active
# End-of-turn status boxharn agents status
# Optional guarded form: require owned Git work to be committed and pushedharn agents status --end-turn
# Dig into a peer's current state, then its full historyharn agents show Fooharn agents trace Foo --since 2h
# Leave a note for a peer holding a file you need, then wait for itharn agents ping Foo "can you release src/config.ts when you're done?"harn agents wait Foo --file src/config.ts --timeout 15m
# Watch the layer live while agents workharn agents watch
# Daily health glanceharn agents health --since 24hThe status box
Section titled “The status box”harn agents status prints the box without inspecting Git. The guarded
harn agents status --end-turn form first verifies that the paths held by this
session are committed and that the repositories involved are pushed. It follows
held paths into submodules and checks the enclosing gitlink too. Active edits
held by another session do not block the check.
When the guarded form is required, write tools also preflight their targets.
The coordination repository is authorized automatically. A host can add an
exact sibling Git repository or an intentional non-Git output root through
agents.finalizationRoots in project config. Harnery denies unsupported paths
before the tool mutates them, and it records the selected disposition with the
durable write claim. See the configuration schema
for the policy shape.
Harnery does not impose the guarded form on every project. A host opts into
automatic prompt and Stop enforcement with
{ "agents": { "requireGitFinalization": true } } in
.harnery/config.jsonc; the default is false. The command-line flag remains
available as a manual check either way.
The flag calls this the “final” status because it is the final status checkpoint for a turn, as opposed to a mid-turn coordination glance. It does not end the agent session. The name is compact, but “final” can also suggest session termination; the config and docs therefore describe it explicitly as a turn-ending Git guard.
The gitlink check compares the commit recorded in parent HEAD, the parent
index, and the child repository’s HEAD. It does not treat unrelated modified
or untracked files inside the child as a pointer change.
Committed paths stay in scope through the session’s coord.claim_changed history.
The commit hook can release a clean active claim without letting an unpushed
commit disappear from the final check.
An unsupported claim from an older session is reported separately from dirty Git work. The error explains how to authorize its containing repository or non-Git output root. It does not suggest committing a directory that has no Git repository.
On success, it prints the same fixed-width box as harn agents status, designed
to paste straight into a reply so a human (and peers) can see your state at a
glance:
┌─ agent-Foo ────────────────────────────────────────────────────────┐│ session 48m ││ turn 4m 12s ││ task refactoring the ingest retry loop ││ activity working ││ lifecycle active ││ context 240K / 1M (24%) ││ files 1 held ││ peers Bar (CC), 3 files; Baz (CC) ││ time Tue, Jul 14, 2026, 7:40 AM CDT │└─────────────────────────────────────────────────────────────────────┘session is how long this session has been alive and turn is how long the
turn the box closes has been running, so a long session and a short turn read
differently at a glance. task is what you declared with set-task, and
peers lists the other active agents with their adapter and file counts.
The turn row measures from the turn.started event the operator’s prompt
recorded to the moment the box renders. A Stop-hook remediation restart is
folded back into the turn it retried, because the operator experienced one
turn. The row is best-effort: a session with no turn evidence in the ledger
renders no turn line at all. --json carries the same measurement as
turn_elapsed_secs, with turn_complete saying whether a turn.completed
already closed it.
When coord.run_quality.mode is report, status also performs a non-blocking
due check and shows a quality line. A fresh snapshot can be unknown,
healthy, attention, or critical, followed by up to three active signal
IDs. Missing, expired, truncated, or insufficient evidence is unknown.
shadow mode still evaluates and records snapshots but deliberately exposes no
operator-facing severity. Run quality is advisory and cannot alter the status
command’s Git guard or any hook verdict.
The qa line
Section titled “The qa line”After this session finishes a page-QA run, status carries the verdict on the runner’s clock:
│ qa passed 4m ago · 90s runner (2m queued) ││ qa incomplete 3h ago · 34s runner ││ qa manual 12m ago · not a pass │The line exists so runner time is never read off session. Session age
measures the agent, not the run, and a box showing session 58m beside a QA
result invites the conclusion that QA took 58 minutes.
Read it as: verdict, how long ago the run completed, then how long the runner
itself took. Admission-queue wait is reported separately and is never folded
into runner time, so a slow-looking run that mostly sat waiting for a slot is
visibly distinguishable from one that mostly worked. Hand-recorded evidence
(qa-record) shows manual … · not a pass instead of a verdict and a clock:
nothing re-executable ran, so there is no runner time to attribute and the
contract caps such a result below a pass.
A verdict older than 24 hours renders as stale (2d) and nothing else — a
day-old result says nothing about the page as it stands now. The line is
absent when this session has recorded no run. Every read is best-effort:
a missing, unreadable, or partial pointer renders no line rather than failing
the status command.
- Native children have their own identity. After the adapter hook observes
a delegation, the child gets the exact parent-minted generation, its own
heartbeat, and its own
whoami/set-taskauthority. Child-owned tool signals land on that generation. Delegation start and stop remain parent-owned because they define the parent-child span. - Unbridged subprocesses still inherit the parent. A shell with no native child session ID falls back to the process tree and reports the parent. This avoids guessing when a generic subprocess has no independent identity.
- Name vs instance_id.
show,trace,ping, andwaitall accept a human name (case-insensitive, with or without theagent-prefix) and resolve it to aninstance_idby prefix match. Pass theinstance_iddirectly if a name is ambiguous.