Skip to content

Commit dabe0c6

Browse files
committed
fix(dashboard-agent): say a queue is paused before reciting its metrics
Metrics are a window: they cannot show a pause, and they cannot show a backlog that arrived after the window. get_queue now carries the queue's live row, and the prompt leads with it.
1 parent 2d40177 commit dabe0c6

4 files changed

Lines changed: 74 additions & 23 deletions

File tree

internal-packages/dashboard-agent/GUIDEBOOK.md

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,15 @@ So a queue full of runs shows only `Watch…` until something is actually
9393
executing or waiting too long — filling a queue with nothing to run it does not
9494
make the page degraded.
9595

96-
**Chips in the agent panel.** An empty chat offers up to five, chosen from the
97-
page's live signals — a different decision from the buttons above:
96+
**Chips in the agent panel.** An empty chat offers up to five. They come from
97+
*two* places, decided separately — closing one does not close the other:
98+
99+
*The page registry* (`suggested-prompts/page-prompts.ts`) answers by page kind
100+
and its fields: the queue page's "why is this backed up?" and "tell me when the
101+
backlog drains" live here, gated on health and on `paused`.
102+
103+
*The page's live signals* (`suggested-prompts/signal-prompts.ts`) answer by what
104+
is happening on screen right now:
98105

99106
| Signal | Chip slot |
100107
| --- | --- |
@@ -105,8 +112,12 @@ page's live signals — a different decision from the buttons above:
105112

106113
A backed-up queue therefore offers "tell me when the backlog drains", never
107114
"investigate" — the page's own Investigate button is the one that asks that. A
108-
paused queue offers neither: `concurrency_saturation` is withheld while it is
109-
paused, for the same reason the button is.
115+
paused queue offers neither, in either place.
116+
117+
So there are three sources, not two: the route's own buttons, the page registry,
118+
and the live signals. They look identical on screen and are decided in three
119+
different files — which is exactly how a rule gets fixed in one and left in the
120+
other two.
110121

111122
---
112123

internal-packages/dashboard-agent/src/__snapshots__/prompt-prefix.test.ts.snap

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,34 +4,34 @@ exports[`the prefix stays inside its budget > matches the committed measurement
44
{
55
"assistant": {
66
"prompt": {
7-
"chars": 24504,
8-
"estimatedTokens": 6126,
7+
"chars": 24871,
8+
"estimatedTokens": 6218,
99
},
1010
"tools": {
11-
"chars": 49210,
11+
"chars": 49377,
1212
"count": 24,
13-
"estimatedTokens": 12303,
13+
"estimatedTokens": 12344,
1414
},
1515
"total": {
16-
"chars": 73715,
17-
"estimatedTokens": 18429,
18-
"fingerprint": "376f30ba",
16+
"chars": 74249,
17+
"estimatedTokens": 18562,
18+
"fingerprint": "cdd191df",
1919
},
2020
},
2121
"code": {
2222
"prompt": {
23-
"chars": 27259,
24-
"estimatedTokens": 6815,
23+
"chars": 27626,
24+
"estimatedTokens": 6907,
2525
},
2626
"tools": {
27-
"chars": 52219,
27+
"chars": 52386,
2828
"count": 28,
29-
"estimatedTokens": 13055,
29+
"estimatedTokens": 13097,
3030
},
3131
"total": {
32-
"chars": 79479,
33-
"estimatedTokens": 19870,
34-
"fingerprint": "2cdf079b",
32+
"chars": 80013,
33+
"estimatedTokens": 20003,
34+
"fingerprint": "4a1d2f84",
3535
},
3636
},
3737
}

internal-packages/dashboard-agent/src/tool-api.ts

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,29 @@ export function queueMetricsAreEmpty(data: unknown): boolean {
6969
);
7070
}
7171

72+
/**
73+
* Metrics plus the queue's live row. `paused` is the part the model must lead with: a queue
74+
* someone stopped explains its own emptiness, and every metric below it is a consequence
75+
* rather than a finding.
76+
*/
77+
function withLiveState(
78+
metrics: unknown,
79+
queueType: "task" | "custom",
80+
state: Record<string, unknown> | undefined
81+
) {
82+
const row = (state as { data?: Record<string, unknown> })?.data ?? state;
83+
if (!row) return { ...(metrics as object), queueType, exists: false };
84+
return {
85+
...(metrics as object),
86+
queueType: (row.type as string) ?? queueType,
87+
exists: true,
88+
paused: Boolean(row.paused),
89+
queuedNow: row.queued ?? null,
90+
runningNow: row.running ?? null,
91+
concurrencyLimit: row.concurrencyLimit ?? null,
92+
};
93+
}
94+
7295
export function buildApiTools(args: {
7396
ctx: DashboardAgentToolContext;
7497
client: DashboardAgentApiClient;
@@ -356,6 +379,16 @@ export function buildApiTools(args: {
356379
return result;
357380
};
358381

382+
// Live state first: metrics are a window, and a window can't say "paused" or show a
383+
// backlog that arrived after it. A queue nobody is running is not the same as a queue
384+
// someone stopped, and the answer has to lead with which one it is.
385+
const live = async (kind: "task" | "custom") => {
386+
const result = await envApiGet(
387+
`/api/v1/queues/${encodeURIComponent(queue)}?type=${kind}`
388+
);
389+
return result?.ok ? (result.data as Record<string, unknown>) : undefined;
390+
};
391+
359392
const first = await read(type ?? "task");
360393
if (!first) return { error: "No current environment is available to read queues from." };
361394
if (!first.ok) {
@@ -364,12 +397,19 @@ export function buildApiTools(args: {
364397
};
365398
}
366399
if (queueMetricsAreEmpty(first.data)) {
367-
const other = await read(type === "custom" ? "task" : "custom");
400+
const otherKind = type === "custom" ? "task" : "custom";
401+
const other = await read(otherKind);
368402
if (other?.ok && !queueMetricsAreEmpty(other.data)) {
369-
return { ...(other.data as object), queueType: type === "custom" ? "task" : "custom" };
403+
return withLiveState(other.data, otherKind, await live(otherKind));
370404
}
405+
// Neither kind has metrics, so the live row is the only thing that can tell them
406+
// apart: a paused or empty queue that exists, against a name that doesn't.
407+
const kind = type ?? "task";
408+
const state = (await live(kind)) ?? (await live(otherKind));
409+
return withLiveState(first.data, kind, state);
371410
}
372-
return { ...(first.data as object), queueType: type ?? "task" };
411+
const kind = type ?? "task";
412+
return withLiveState(first.data, kind, await live(kind));
373413
},
374414
}),
375415

internal-packages/dashboard-agent/src/tool-schemas.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ export const getReportSchema = tool({
175175

176176
export const getQueueSchema = tool({
177177
description:
178-
"Get one queue's metrics over a window: wait latency (p50/p95), peak depth, how many runs started (throughput), and how often the queue was throttled by its concurrency limit. Use this for 'how deep is the X queue', 'is X backed up', or 'why are runs waiting'.",
178+
"Get one queue's metrics over a window: wait latency (p50/p95), peak depth, how many runs started (throughput), and how often the queue was throttled by its concurrency limit. Use this for 'how deep is the X queue', 'is X backed up', or 'why are runs waiting'. The answer also carries the queue's live row: `paused`, `queuedNow`, `runningNow`, `concurrencyLimit`, and `exists: false` when no queue of that name is there at all.",
179179
inputSchema: z.object({
180180
queue: z
181181
.string()
@@ -468,7 +468,7 @@ You have read-only tools that act as the user against their own account:
468468
- ask_support: ask the Trigger.dev support assistant about how Trigger.dev works (docs, concepts, features, configuration, how-tos).
469469
- render_view: render a structured view in the panel from the block catalog. The catalog has the "diagnosis" block (a failure card for a single run), the "chart" block (a line/bar chart of run_query results), the "actions" block (a row of 1-3 buttons offering next steps — a watch intent opens the watch card pre-filled, an ask intent sends the labelled question as the user's next message), and the "investigation" block (a live card for a hypothesis-driven investigation).
470470
- get_report: the composed health report for the current environment (flow, execution, liveness), with a severity and the metrics behind each.
471-
- get_queue: one queue's wait latency, peak depth, throughput, and throttling over a window.
471+
- get_queue: one queue's wait latency, peak depth, throughput, and throttling over a window, plus its live row. Lead with paused when it is true: a paused queue explains its own emptiness, and every metric under it is a consequence, not a finding — say it is paused, and only then the numbers. queuedNow is what is waiting right now, which a window of metrics cannot show; exists:false is the only thing that means the queue isn't there, never zeroed metrics.
472472
- list_deploys: recent deployments (versions) in the current environment, with status and commit message.
473473
- get_deploy: one deployment's detail, or the current promoted one when you omit the version.
474474
- correlate_version: the version, commit, and pull request a specific run actually ran.

0 commit comments

Comments
 (0)