Skip to content

mark-active

The single freshness signal for a session. Any work a session does — a new prompt, or any tool about to run — flips its declared state to active, which also drops a now-stale proposal or note: once the agent is moving again, an old "ready to merge" claim no longer stands. The one exception is asking the human: when the tool is AskUserQuestion the state becomes asking, carrying the question itself as the note, so a pause for the human is captured deterministically without the agent having to also declare it.

The state is read from ONE structured field in the hook payload, never sniffed from the terminal UI, so the signal is hard rather than guessed. Because it fires before the tool runs, a deliberate stop-gate declaration (itself made via a tool) lands after this and wins; the next real tool flips back to active, forcing a fresh declaration at the following stop. It is pure shell so it stays cheap firing on every tool call.

The one activity that does NOT count as the session acting is an IN-PROCESS SUBAGENT's tool call (the harness's Task tool — a sub-conversation inside the same process). Such a call fires the parent's hooks carrying the parent's session_id, so without a discriminator a supervising parent could never hold a declared state: its own subagents erased every park/ask within seconds and raced the stop-gate into "undeclared stop". The harness stamps subagent-executed calls with a top-level agent_id field the parent's own calls never carry; hp_is_subagent reads that stamp deterministically (scanning only the pre-tool_input payload prefix, where a tool parameter or file content can never fake an unescaped key), and this hook skips the flip entirely. A subagent working is its parent supervising, not the parent moving on — the parent's own next tool call still flips as before.

It is a board-lifecycle hook, so it acts only on a GOVERNED (dashboard-launched) session — it resolves that session's record in the global per-session store from the payload's session_id and no-ops unless governed: true. The state it writes lives in that record's session.json (state), which it edits directly in shell (the hot path stays jq-free).

This is the freshness half of the core discipline: it keeps the board honest about whether a session is working, waiting, or asking, so the gates and the dashboard read a true present state rather than a stale one.