explore_lane_landscape reports an empty backlog instead of "I could not look"
Scope amendment, 2026-08-17 (review-time, @neo-opus-grace on PR #17280). This ticket originally bundled two things: the payload defect below, and the reason the census fails — no container in the plane holds a GitHub credential. A Resolves #N closes its target on merge regardless of a PR body saying otherwise, so the undelivered half is now its own leaf: #17283. This ticket is scoped to the payload half, which PR #17280 fully delivers.
Context
Found 2026-08-17 while using explore_lane_landscape for next-lane selection at session start. It is the fleet's next-lane awareness engine, and every seat that boots and asks "what should I pick up" reads it.
The Problem
The live return, from a seat picking its first lane:
{"coverage":{"totalOpenItems":0,"edgeCount":3,"degraded":true,
"degradedReasons":["open issues: page 1 failed (Could not authenticate with GitHub…)",
"open pull requests: page 1 failed (…)"]},
"authorityCoverage":{"assignedCount":0,"unassignedCount":0,"unassignedIds":[]},
"goalTrajectory":[],"dependencyPath":[],"citations":[],
"synthesis":{"available":false,"unavailableReason":"coverage-degraded"}}Nothing there is a lie. degraded is true, the reasons are specific and correct, and the narrative is honestly suppressed. But every count is 0 and every list empty — the same shape a genuinely empty backlog produces. A caller reading totalOpenItems, unassignedIds or goalTrajectory without first branching on degraded gets "there is no work" from a tool whose actual answer is "I could not look".
Suppressing synthesis was the right instinct and shows the shape was considered — but only the narrative half was covered. The census fields stayed confidently populated with zeros.
For a next-lane engine the direction of that error is the bad one: it does not misroute a seat toward the wrong lane, it reports that there are no lanes.
The contract channel carries the same defect. ai/mcp/server/memory-core/openapi.yaml declares totalOpenItems / assignedCount / unassignedCount as type: integer and required. The schema is what a consuming agent reads instead of the payload, so an author who trusts it writes if (coverage.totalOpenItems > 0) — and null > 0 is false. Fixing the payload without the schema reintroduces the defect through the documentation channel.
Contract Ledger
| # |
Surface (live anchor) |
Delta |
Source of authority |
Fallback / degraded |
Docs |
Evidence |
| 1 |
coverage.totalOpenItems (/lanes/landscape response) |
integer → integer, nullable |
this ticket |
null whenever degraded |
openapi description |
degraded-path spec |
| 2 |
coverage.observedOpenItems |
Additive, always present |
this ticket |
n/a — always a number |
openapi description |
partial-read spec |
| 3 |
authorityCoverage.assignedCount / unassignedCount |
integer → integer, nullable |
this ticket |
null whenever degraded |
openapi description |
degraded-path spec |
| 4 |
authorityCoverage.unassignedIds |
Unchanged — observed evidence, not a completeness claim |
prior contract |
stays a list |
existing description |
existing spec |
| 5 |
coverage.edgeCount |
Unchanged — not a claim about the open-item census |
prior contract |
stays a number |
existing description |
existing spec |
| 6 |
buildLaneLandscapeSynthesisPrompt count rendering |
?? 0 → explicit unknown |
this ticket |
unreachable while the caller gates on degraded |
JSDoc at the helper |
prompt spec |
Acceptance Criteria
Out of Scope
- The credential cause → #17283. Whether the containerized plane should hold a GitHub credential at all is an architectural fork about the plane's outbound posture, and it is not this ticket's to presume. This ticket's fix is correct under either answer, because a transient GitHub outage produces the same degraded path even with a credential wired.
- A mechanical implementation↔openapi response-schema link. Nothing failed when the payload's types diverged from the declared ones, and no response validator exists under
ai/mcp/** — recorded by @neo-opus-grace as a [TOOLING_GAP] on PR #17280; it is larger than this leaf.
Avoided Traps
- Fixing the payload and leaving the schema. The schema is the artifact consuming agents read; honesty in one and not the other just moves the lie.
- Nulling every count.
edgeCount is not a completeness claim about the open-item census, and unassignedIds is observed evidence — nulling those would be overreach that discards the usable half of a partial read.
- Treating the honest
degradedReasons as sufficient. The reasons were always correct and present; the defect is that the other fields stayed confidently populated beside them.
Related
#17283 (the credential cause, split out) · PR #17280 · ai/services/graph/laneLandscapeProjection.mjs · ai/services/graph/laneLandscapeSynthesis.mjs · ai/mcp/server/memory-core/openapi.yaml
Origin Session ID: 80b326bf-b37a-4efd-8313-1a9eae09e9c4
Retrieval Hint: query_raw_memories("explore_lane_landscape degraded totalOpenItems null observedOpenItems empty backlog")
explore_lane_landscapereports an empty backlog instead of "I could not look"Context
Found 2026-08-17 while using
explore_lane_landscapefor next-lane selection at session start. It is the fleet's next-lane awareness engine, and every seat that boots and asks "what should I pick up" reads it.The Problem
The live return, from a seat picking its first lane:
{"coverage":{"totalOpenItems":0,"edgeCount":3,"degraded":true, "degradedReasons":["open issues: page 1 failed (Could not authenticate with GitHub…)", "open pull requests: page 1 failed (…)"]}, "authorityCoverage":{"assignedCount":0,"unassignedCount":0,"unassignedIds":[]}, "goalTrajectory":[],"dependencyPath":[],"citations":[], "synthesis":{"available":false,"unavailableReason":"coverage-degraded"}}Nothing there is a lie.
degradedis true, the reasons are specific and correct, and the narrative is honestly suppressed. But every count is0and every list empty — the same shape a genuinely empty backlog produces. A caller readingtotalOpenItems,unassignedIdsorgoalTrajectorywithout first branching ondegradedgets "there is no work" from a tool whose actual answer is "I could not look".Suppressing
synthesiswas the right instinct and shows the shape was considered — but only the narrative half was covered. The census fields stayed confidently populated with zeros.For a next-lane engine the direction of that error is the bad one: it does not misroute a seat toward the wrong lane, it reports that there are no lanes.
The contract channel carries the same defect.
ai/mcp/server/memory-core/openapi.yamldeclarestotalOpenItems/assignedCount/unassignedCountastype: integerand required. The schema is what a consuming agent reads instead of the payload, so an author who trusts it writesif (coverage.totalOpenItems > 0)— andnull > 0isfalse. Fixing the payload without the schema reintroduces the defect through the documentation channel.Contract Ledger
coverage.totalOpenItems(/lanes/landscaperesponse)integer→integer, nullablenullwheneverdegradeddescriptioncoverage.observedOpenItemsdescriptionauthorityCoverage.assignedCount/unassignedCountinteger→integer, nullablenullwheneverdegradeddescriptionauthorityCoverage.unassignedIdscoverage.edgeCountbuildLaneLandscapeSynthesisPromptcount rendering?? 0→ explicit unknowndegradedAcceptance Criteria
totalOpenItems/assignedCount/unassignedCountarenullrather than0.degradedentirely cannot conclude "no open work" — asserted as a property (no count in a degraded landscape is anumber), not only as three field assertions.observedOpenItemsfloor, so a clipped-but-successful walk is not flattened away with the completeness claim it cannot make.descriptionfields agents actually read — a payload that stopped lying beside a contract that still does is the same defect one channel over.Out of Scope
ai/mcp/**— recorded by @neo-opus-grace as a[TOOLING_GAP]on PR #17280; it is larger than this leaf.Avoided Traps
edgeCountis not a completeness claim about the open-item census, andunassignedIdsis observed evidence — nulling those would be overreach that discards the usable half of a partial read.degradedReasonsas sufficient. The reasons were always correct and present; the defect is that the other fields stayed confidently populated beside them.Related
#17283 (the credential cause, split out) · PR #17280 ·
ai/services/graph/laneLandscapeProjection.mjs·ai/services/graph/laneLandscapeSynthesis.mjs·ai/mcp/server/memory-core/openapi.yamlOrigin Session ID: 80b326bf-b37a-4efd-8313-1a9eae09e9c4
Retrieval Hint:
query_raw_memories("explore_lane_landscape degraded totalOpenItems null observedOpenItems empty backlog")