feat(observability): turns per human message — the other half of the #70 ledger - #124
Conversation
…etone#70 ledger The wake-economics panel answers "of the turns that fired, how many said nothing". This adds the figure the yetone#70 review asked for before any further routing change: how many turns a human message fires in the first place. The scheduler picks recipients purely by membership, so fan-out width is the number a routing change claims to shrink — measured here rather than argued. Attribution: agent_runs.input_message_ids is the inbox a run actually drained, so a message's turn count is its token-bearing runs. A run draining a burst counts toward each message it read — deliberate (the message genuinely participated in that turn), and why the panel reports counts, not dollars. Denominator: human non-system messages in group/direct rooms with at least one agent member, so a message nobody's inbox reached shows up as 0 turns instead of vanishing. Room-wide by design: the agentId filter does not apply, because width is a property of the room. The panel says so when a filter is active. Rides on the existing /agents/observability/wakes response so the panel keeps one fetch; buckets are group/direct, never averaged, with avg, median, and a 0/1/2/3-5/6+ distribution bar.
|
Thanks — the framing is right, and step ② is exactly the order we agreed in the #70 review: measure the width before changing the routing. The attribution choice (a run counts toward each message it drained, counts not dollars) is well argued and I agree with it. One blocking issue before this can land.
|
|
Merged — with the aggregate blocker fixed in place rather than held any longer. The framing and the attribution argument were right, and the panel is worth having. The two COALESCE(avg(COALESCE(t.turns, 0)), 0) AS avg_turns,
COALESCE(percentile_cont(0.5) WITHIN GROUP (ORDER BY COALESCE(t.turns, 0)), 0) AS median_turns,Verified against Postgres — 4 messages, 6 turns, 2 of them never woken: avg 1.50, median 1.00, which now agrees with the I also added one test that runs the aggregate shape against a real Postgres and pins both the correct value and the wrong one. Your other cases mock Thanks for the panel. #123 is the thing it was built to judge, whenever you pick it back up. |
Step ② of the ladder agreed in the #70 review: make the waste measurable before acting on it. Turns per human message, over
agent_runs— the number any routing change (#92'sme, a futureone-of-us) claims to shrink, so its effect can be read off a panel instead of argued from vibes.What it measures
agent_runs.input_message_ids) included it. A run draining a burst of k messages counts toward each of them — deliberate: the message genuinely participated in that turn. This is also why the panel reports counts, not dollars: dollars per message would need fractional run attribution, a different and denser metric (the silent-rate panel next to it already owns the dollars for the runs that said nothing).percentile_cont), and a 0 / 1 / 2 / 3–5 / 6+ histogram bar. A group width near 1 is the state feat(agent-routing): avoid waking every agent with the same group context #70's routing work is trying to reach.Where it lives
Rides on the existing
GET /agents/observability/wakesresponse (turnsPerMessagefield) so the panel keeps one fetch and one window selector. Room-wide by design — the agentId filter does not apply, because fan-out width is a property of the room; the panel says so explicitly when a filter is active rather than silently mixing scopes. Implemented inobservability.tsnext togetWakeEconomics(one aggregate query, grouped in SQL, no per-row JS); rendered as a second card in the wake-economics panel; zh-CN included.Not in this PR (on purpose)
CUMORA_MODEL_PRICES_JSON: the review also noted every cost row iscost_estimatedtoday. That affects the dollar columns, which this PR doesn't add — but the reminder stands for the silent-spend column that already exists;costEstimatedon this response already surfaces it.Tests & gates
agents-observability-turns.test.ts: bucket mapping, fixed histogram order (the bar renderer depends on it), pg numeric-string conversion, rides-along on the wake-economics response,sinceHoursclamp parity. Gates:typecheck,server:typecheck,lint(pre-existing single info), threeguard:*scripts, routing + observability test files green.