At 8:38 AM, my command dashboard showed a session on my plate that I had not opened. I was sure I had closed every session I opened.
I use that dashboard to decide what needs attention before the next release or ticket. An open session means there may be an unfinished decision or failure behind it. I went looking for the work. The session was a ghost.
It was a real session record. It just was not a session I had started.
The record looked exactly like my work
The daily summary had logged 18 sessions, 33 commits, and activity across 7 repositories. The dashboard reduced those records to familiar things: repository, branch, first prompt, token use, timestamp, and a session ID. The suspicious entry had all of those fields. Nothing in the card told me that an agent had created it as part of closing something else. It was not the only one that day. Five of the 18 records were helper sessions like it.
The entry lasted from 8:38 AM to 8:38 AM. It had two prompts, five input tokens, 676 output tokens, and 91,958 cached tokens. Its first prompt was this:
You are reviewing the tail of a Claude Code session to produce a structured close summary for a vault task page.
That was the clue. The close process runs a helper through claude -p. The helper reads the tail of a completed interactive session, writes a transcript, and produces the vault manifest that lets the rest of our workflow find the result. It runs in the same project context as the session it is summarizing. As far as the command-line session store is concerned, it is another session in the same repository.
The dashboard was faithfully reporting that a session existed. It was lying about what the session meant.
I checked the obvious explanation first
My first theory was that I had reopened an old interactive session without remembering it. The two full sessions from that day looked like normal work: one had 172 prompts and more than 456,000 output tokens; another had 180 prompts and nearly 696,000 output tokens. Their first prompts described actual work. They had duration, conversational depth, and a recognizable thread of decisions.
The 8:38 entry had none of that. It was a two-prompt child process with a single-purpose instruction. Its timing also matched the close path, not a new task. Once I followed the record back through the summary flow, the cause was simple: the helper was generating durable output, which meant it had a durable session record, but the session inventory had no provenance field.
The helper was not the failure. I want the transcript and manifest because they make the close auditable. The dashboard query failed by treating every session record as a human-owned unit of unfinished work.
Those are different categories.
Filtering would make the evidence disappear
I considered three ways to fix it.
The first was inference. The dashboard could inspect the first prompt and decide that anything beginning with a close-summary instruction was probably an agent helper. That is cheap, and it is also brittle. Prompt text changes. A helper can be renamed. A human can paste a similar instruction. More importantly, inference happens after the spawn, when the information that actually matters has already been discarded.
The second was a blunt filter. I could exclude short sessions, claude -p sessions, or anything with a low prompt count. That loses useful data. A two-prompt session can be a real incident response, a reproduction case, or a deliberate one-shot operation. A command-line helper can fail, produce a bad manifest, or consume an unexpected number of tokens. Hiding it from the dashboard would make my personal plate look clean by removing the audit trail.
So I stopped trying to make the dashboard guess. The spawn path has to say what it is doing.
The spawn contract is the fix
Every helper session now needs provenance at creation time. The minimum shape is small:
{ "kind": "close_summary", "initiator": "agent", "parent_session_id": "<interactive-session-id>", "visibility": "agent_helpers"}kind answers what this session is for. parent_session_id answers which real session caused it to exist. initiator answers whether a person opened the session or an automated path did. visibility gives the dashboard an explicit filing rule instead of a heuristic.
The important field is the parent. A close-summary helper is not merely agent-generated work. It is a child of a specific interactive session. When the dashboard can traverse that relationship, it can show the record beside the session it summarizes, not beside the work I still need to do.
The dashboard does not need less information. It needs a better model of the information it already has.
“My plate” is a claim about ownership
After the change, the main view can say My plate for human-initiated sessions and Agent helpers for spawned work. The complete session inventory remains visible. I can still inspect the transcript, token count, command path, and manifest for each helper. Nothing is deleted, and nothing becomes invisible.
The difference is that a two-prompt close summary no longer competes with a live engineering thread for my attention. It is filed under the session that needed it.
That morning, the dashboard made it sound like I had something left to close. In reality, I had one session to understand: a piece of automation that had done exactly what I asked.
A dashboard that cannot tell who started a session turns reliable automation into phantom work. The cure is not a cleverer filter. It is recording the relationship at the moment the work begins.