Skip to content

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:

Dev-E is implementing issue #207

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.None vs null.
  • 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.