The card says what was observed¶
rig-conductor#2265, parts 1 and 2. Part 3 — allowlisting the trace and cost routes in the Worker proxy so a stage can be expanded into its attempts — is still open.
The lie¶
implementing printed:
That stage is entered from the agent-ready label, which the rig applies to its own issues before any dispatch. Nothing on
that path had observed an agent doing anything, so the sentence was a guess wearing a specific name.
Measured on task ff18ab60 (2026-09-22, the first end-to-end run that could be falsified): the card read "Dev-E is
implementing" from 11:14 to 11:58. Dev-E's assignment was published at 11:15:00 and not read until 11:56:04, because the
single-slot dev-e-node@dashecorp lane was busy with another repo's issue. The actual work took 2m23s.
So the operator watched a named false claim for 42 minutes with no way to tell it from the truth — and every dispatch failure has that shape: the label is applied, and the card says a specific agent is working, indefinitely.
The rule¶
A detail line may name only what the route observed. Not what the rig intended, not who it would normally be, not what is probably happening. Where nothing was observed, the line says what was: the label, and how long it has been sitting there.
| before | after | |
|---|---|---|
agent-ready, no PR |
Dev-E is implementing issue #207 |
Issue #207 is agent-ready, no pull request yet |
agent-ready ≥ 10 min, no PR |
(the same sentence, forever) | No pull request 10 min after agent-ready |
| draft PR | Draft pull request #208 |
Draft pull request #208 by dev-e-bot[bot] |
| open PR in review | Pull request #208 in review |
Pull request #208 by dev-e-bot[bot] in review |
Two deliberate limits:
- The stall is named only from the label EVENT time. With the label present but its event unread there is nothing to measure,
so nothing is claimed however long the card polls. This mirrors
queued, which only says "No plan after 10 min" when it can date the issue. - An author the read did not answer is left unsaid.
By(null)is the empty string, not a plausible default — a plausible default is exactly what made the old line wrong.
Part 2: the actors the conductor already had¶
Part 1 left the line under-claiming — Issue #207 is agent-ready, no pull request yet stayed on the card while Dev-E was
working, because the only thing the route could observe was a pull request. The conductor had more all along, in its own
IssueStatus document, keyed {repo}#{issueNumber} — the key the task already holds:
| Field | Written from | Used for |
|---|---|---|
AgentId |
IssueAssigned |
<agent> claimed issue #N |
ReviewAgentId |
ReviewAssigned |
<reviewer> is reviewing pull request #N |
PrAuthorId |
PR_CREATED |
fills in the PR author when GitHub's own read did not answer one |
IOperatorTaskActorReader is that read: one document, in the repo tenant's database, on the same memo window as the GitHub
read, and carried when a read does not answer — an actor already seen does not disappear because one read failed, the same rule
Carry applies to GitHub.
Three things it deliberately does not do:
- Null is "not observed", never "nobody". An assignment that has not been read yet and one lost to the rc#2227 concurrency retry are indistinguishable from here (that retry leaves no gap in the stream, so no consumer can detect it). So a name is only ever shown, never inferred from an absence — and the line falls back to part 1's wording rather than claiming nothing happened.
- A document with no actor is a different fact from no document. The first says the conductor has the issue and has seen
nobody on it; the second says it cannot tell.
OperatorTaskActors.Nonevsnull. - The PR's own author wins over the conductor's record of it. One is who GitHub says opened it, the other is who the conductor was told opened it, and they differ exactly in the case rc#430 exists for — a review-e-bot-authored PR on an issue assigned to someone else.
The one implementer that can be observed¶
The login that opened the pull request. ParsePull already fetched it in the payload it parses and dropped it on the floor
(user.login), so OperatorTaskPrSnapshot.Author costs one line and no extra request. It is reported exactly as GitHub sends it —
dev-e-bot[bot], brackets included — and never normalised into a friendly name, because the login is the fact.
The other actors live in IssueStatus, which part 2 (above) now joins — so implementing can say "claimed by X" rather than
only "no pull request yet".
merged stays attributed to nobody: MergeGate merges, and the Merged event carries no actor field to read. queued, verified
and expired are a label, a probe and a clock. "Verified by Dev-E" would be the same class of error as the line this removes.
Proof¶
Mutation, not a green run — each guard gutted, its test required to fail, then reverted:
| Guard gutted | Test that must fail |
|---|---|
| the line reporting the label instead of naming an agent | Implementing_says_what_was_observed_and_never_names_an_agent_it_did_not_see |
| the stall named only from the label event time | the same |
an unread author left unsaid (By hard-coded to a name) |
the same |
the parser reading user.login |
GitHubTaskSnapshotReaderTests + the lifecycle walk |
| the line naming the observed agent | An_observed_actor_is_named_and_an_unobserved_one_leaves_the_line_as_it_was |
| the line naming the observed reviewer | the same |
| the conductor's PR author filling in what GitHub did not answer | the same |
| an observed actor carried when a read fails | The_observed_actors_are_read_on_the_github_window_and_carried_when_a_read_does_not_answer |
| the adapter keying the document by repo AND number | MartenOperatorTaskActorReaderTests |
| a missing document answering null rather than an empty observation | the same |
All four fail when gutted. The lifecycle walk asserts the new lines end to end, and the reader's PR fixture now carries
"user":{"login":"dev-e-bot[bot]"} so the two exhaustive OperatorTaskPrSnapshot assertions pin the parse.