Session state
Harnery describes a session with two independent state axes.
For a concise explanation you can share, start with the Session state feature page. To apply the same model to Claude Desktop, use the session-management guide.
| Axis | Values | Question answered |
|---|---|---|
activity |
unknown, working, needs_input, idle |
What is the agent doing now? |
task_state |
active, blocked, done |
Is the declared task still open? |
The separation matters. An agent waiting for approval can have
activity: needs_input and task_state: active. After the turn ends, it can
be idle while the task remains active. Neither state implies the other.
Activity evidence
Section titled “Activity evidence”Harnery changes activity only when a canonical event supplies evidence.
| Canonical event | Activity | Notes |
|---|---|---|
session.started, session.resumed, agent.started |
idle |
The session exists, but no open turn is implied. |
turn.started, tool.requested |
working |
A turn or tool action is in progress. |
command.started |
working |
Applied only during an already evidenced open turn. |
wait.started |
needs_input |
The adapter directly reported an operator prompt. |
wait.ended |
working |
The reported wait ended and execution resumed. |
turn.completed, session.ended, agent.completed |
idle |
The current execution window ended. |
Output chunks, post-tool events, narration, unrelated coordination commands,
and elapsed time do not clear needs_input. New progress evidence or a stop
event does.
Codex provides a PermissionRequest hook, which Harnery records as
wait.started. Claude Code and Cursor sessions still use
unknown when their available events cannot prove an input wait. Harnery does
not turn a nearby pre-execution event into approval evidence.
Lifecycle evidence
Section titled “Lifecycle evidence”Only coord.lifecycle_changed changes task lifecycle. Stopping a process or reaching
the end of a turn does not mark the task done.
Human-facing sessions declare lifecycle explicitly:
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. Leaving blocked clears that reason. Repeating
the same state and reason records nothing, which keeps replay idempotent.
Subagents, transient processes, and workflow children cannot make these
operator-facing declarations.
done also requires a current task and a passing Git finalization check. The
check covers the session’s durable write-claim history, dirty touched paths,
enclosing submodule pointers, repository verification, and remote reachability.
A refusal writes no lifecycle event and leaves the heartbeat unchanged. This
gate certifies repository hygiene, not tests or business correctness.
The per-turn agents set-task command remains lifecycle-neutral. Changing task
text while blocked or done returns a warning, but it does not reopen the task.
Use lifecycle active to resume explicitly.
The compatibility default is active when an older heartbeat or event stream
has no lifecycle event. A blocked state may carry a reason. Reopening the task
clears an obsolete blocker reason.
Session finalization
Section titled “Session finalization”Task lifecycle and session lifecycle are separate. lifecycle done says the
declared task is complete; agents end records terminal authority for the V3
session. A completed response (turn.completed) ends only one response.
Native harness callbacks remain the strongest end signal. When a harness lacks one, Harnery can approve an explicit end, wait through a reversible archive grace period, or reconcile idle, parent/run completion, stale, superseded, and host-loss observations. Every path converges on one leased finalizer, so a generation receives at most one terminal event and cannot be resurrected.
An agent can request its own end from the closing status command even though
that command still belongs to an open tool span and turn. Harnery persists the
intent, waits for the matching tool.completed and turn.completed, then lets
the stop hook record session.ended. Starting new work cancels the request.
The terminal row therefore never appears before the work that proves the turn
closed.
Automatic policy ends clean coordination state but do not qualify as run-quality evidence. Verified archive ends require an independent ambiguity review before corpus admission.
Session-name projection
Section titled “Session-name projection”A lifecycle change is title-silent unless it marks the session done:
| Lifecycle | Suggested name |
|---|---|
active |
No title change |
blocked |
No title change |
done |
[DONE] Agent <name> - <task> |
The coord.lifecycle_changed event carries suggested_session_name only for
the done projection. Prompt context keeps reminding the agent until that name
is observed, and the display latch treats it like the first session name.
Reopening does not print another title. The title is still a projection;
editing it does not mutate lifecycle.
Claude Desktop cleanup uses the same one-way projection. harn claude-desktop tidy maps a sidebar entry to its Harnery session and checks the latest durable
lifecycle event. It archives mapped done entries only when --yes is present.
A [DONE] title without lifecycle evidence is skipped unless the operator adds
--include-legacy-prefix; a [BLOCKED] title never qualifies. Titles remain an
output of lifecycle, not an input to it.
Live state and replay
Section titled “Live state and replay”The heartbeat projector writes these fields to .harnery/active/<id>.json:
{ "activity": "needs_input", "activity_updated_at": "2026-08-12T10:02:00.000Z", "activity_source": "wait.started", "task_state": "active", "task_state_updated_at": "2026-08-12T09:30:00.000Z"}Heartbeats are a live projection. The canonical event ledger is the durable
record. The public foldSessionState() helper rebuilds both axes from a session
slice, even after its heartbeat has been removed. Live projection and replay
call the same transition reducer. Replay orders evidence by event timestamp,
so an older event appended later cannot roll the state backward.
Where the state appears
Section titled “Where the state appears”Harnery carries the same two labels through every operator surface:
harn agents whoami,list,status,show, andtrace, including JSON- prompt-time peer context and its change-detection hash
- cross-machine presence blobs and remote peer readers
- web cards, agent chips, the agent detail page, and ended-session detail reads
The presence hash includes activity, lifecycle, and the bounded blocker reason. A state-only change therefore reaches another machine without waiting for a task or file claim to change. The web refresh signal also observes heartbeat writes, so lifecycle-only updates repaint the live view.
Ended and swept heartbeats disappear from the live board. Their event-derived identity and session state remain available to history and detail readers. This keeps the live board honest without discarding the lifecycle record.
See ADR 0072 for the decision and rejected inference rules.