mark-active¶
Provenance¶
- Source:
.spec/spexcode/.plugins/core/mark-active/spec.md - Source SHA-256:
ce9d94e3807b364e7696dac2b5659a19fd2455fb1ba81d13ce8951f1ba04a65b
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]]), but it never edits that file itself: it READS it in pure shell (whole-line matches, the hot path stays jq-free) and hands every write to spex internal session-state, the one structured writer the CLI declarations use — an asking note is arbitrary prose, and a shell that substitutes prose into existing JSON eventually writes a record nothing can parse.
This hook carries no conversation. A message addressed to the session reaches its agent as an ordinary prompt through the harness adapter ([[delivery-queue]]), which is the only way anything enters a turn, so an inter-agent message is indistinguishable from a human one at the point of arrival. A hook that also injected mail delivered every message a second time and made the agent's context depend on which of two paths won a race; a freshness signal reports a fact about the session and hands nothing over.
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.