At 8:38 AM, our command dashboard showed a session on our plate that we had not opened. We were sure we had closed every session we opened.
We 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, so we went looking for the work. The session was a ghost.
It was a real session record. It just was not one we had started.
The record looked exactly like our work
The daily summary had logged 18 sessions, 33 commits, and activity across 7 repositories. The dashboard reduced those records to the same fields every time: repository, branch, first prompt, token use, timestamp, and a session ID. The suspicious entry had all of them. Nothing on the card said an agent had created it while closing something else. It was not the only one that day. Five of the 18 records were helper sessions like it.
Its start and end timestamps matched. 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 an archive 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 archive 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 wrong about what the session meant.
Our first theory was that we had reopened an old interactive session and forgotten about it. The two full sessions from that day looked like normal work. One had 172 prompts and more than 456,000 output tokens. The other had 180 prompts and nearly 696,000 output tokens. Their first prompts described actual work, and they had duration, conversational depth, and a recognizable thread of decisions.
The helper entry had none of that. It was a two-prompt child process with a single-purpose instruction, and its timing matched the close path. Once we followed the record back through the summary flow, the cause was simple. The helper generates durable output, so it has a durable session record, and the session inventory had no provenance field.
The helper was working as designed. We want the transcript and manifest because they make the close auditable. The dashboard query was the part that failed, because it treated every session record as a human-owned unit of unfinished work. Those are different categories.
Filtering would make the evidence disappear
We tried to fix it on the dashboard side first. The dashboard inspected the first prompt and treated anything beginning with a close-summary instruction as an agent helper. That was cheap, and it was brittle. Prompt text changes. A helper can be renamed. A human can paste a similar instruction. Inference also happens after the spawn, when the information that actually matters has already been discarded.
The blunt version was worse. Excluding short sessions, claude -p sessions, or anything with a low prompt count lost 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 made our plate look clean by removing the audit trail.
So we stopped making the dashboard guess. The spawn path has to say what it is doing.
The spawn contract
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 a child of a specific interactive session. When the dashboard can traverse that relationship, it can show the record beside the session it summarizes, away from the work we still need to do.
What “our plate” means now
After the change, the main view says Our plate for human-initiated sessions and Agent helpers for spawned work. The complete session inventory remains visible. We can still inspect the transcript, token count, command path, and manifest for each helper. Nothing is deleted and nothing becomes invisible.
A two-prompt close summary no longer competes with a live engineering thread for our attention. It is filed under the session that needed it.
That morning the dashboard made it sound like we had something left to close. What we actually had was a piece of automation that had done exactly what we asked, and a session list with no way to say who started what.
If your agent tooling spawns helper sessions, check whether it records, at the moment of the spawn, who started each one and which session it belongs to. Filtering afterward by prompt text or session length risks hiding the audit trail.