stablyai/orca · error · Error

projection mismatch after a ping at ${agents} agents

Error message

projection mismatch after a ping at ${agents} agents

What it means

Same correctness invariant as 153 but exercised AFTER a ping (one agent's status changed). The cold call after an incremental update is where stale-cache bugs hide: the cache may serve a row computed before the ping. Fires for agent counts 3/8/20/40.

Source

Thrown at config/scripts/mobile-agent-status-projection-benchmark.mjs:182

  samples.sort((a, b) => a - b)
  return samples[2]
}

const pad = (value, width) => String(value).padStart(width)
console.log('Mobile agent-status projection, per status ping (one agent changed)')
console.log(`bucket=${BUCKET_MS}ms iterations=${ITERATIONS} warmup=${WARMUP} (median of 5 rounds)`)
console.log(`${pad('agents', 8)} ${pad('full', 11)} ${pad('cached', 11)} ${pad('speedup', 9)}`)
for (const agents of [3, 8, 20, 40]) {
  const map = makeMap(agents)
  const cachedBuilder = makeCachedBuilder()
  if (buildFull(map) !== cachedBuilder(map)) {
    throw new Error(`projection mismatch at ${agents} agents`)
  }
  // Why also after a ping: the cold call reuses nothing, so a stale-row bug would
  // only surface once the cache is actually exercised.
  const pinged = ping(map, 0)
  if (buildFull(pinged) !== cachedBuilder(pinged)) {
    throw new Error(`projection mismatch after a ping at ${agents} agents`)
  }
  const full = measure(buildFull, map)
  const cached = measure(makeCachedBuilder(), map)
  console.log(
    `${pad(agents, 8)} ${pad(`${full.toFixed(4)} ms`, 11)} ${pad(`${cached.toFixed(4)} ms`, 11)} ${pad(`${(full / cached).toFixed(1)}x`, 9)}`
  )
}
console.log(
  '\nThis runs on the global store subscriber, so the cost is paid per status ping\nand scales with the number of agents running in parallel — the workload this\napp exists for.'
)

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Confirm ping invalidates the cache entry for agent 0 and any row derived from it
  2. Verify the cache key includes updatedAtBucket (the value BUCKET_MS is parsed for)
  3. Make cachedBuilder copy-on-write so map mutation does not corrupt cached state
  4. Add a regression test that pings each agent index, not just 0
Defensive patterns

Strategy: validation

Validate before calling

function assertProjectionAfterPing(builder, cached, map, idx, agents) {
  const pinged = ping(map, idx)
  if (builder(pinged) !== cached(pinged)) {
    throw new Error(`cached projection diverges after ping(${idx}) at ${agents} agents`)
  }
}

Prevention

When it happens

Trigger: buildFull(pinged) !== cachedBuilder(pinged) where pinged = ping(map, 0). The cached builder served a pre-ping row for agent 0 (or failed to invalidate dependent rows).

Common situations: Cache invalidation keyed only on the changed agent without propagating to derived rows; updatedAtBucket changes but the cache key omits it; the ping mutates the map in place but the cache holds a reference to the old shape.

Related errors


AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12). Data as JSON: /api/errors/44ac112661387eba. Report an issue: GitHub.