Skip to content

Commit d9ebf95

Browse files
committed
fix(dashboard-agent): summarise what the transcript recorded about a watch
A watch can expire or be cancelled with nothing written back into the transcript, so asking the summariser for "any watch that is running" preserved an old confirmation as current state.
1 parent 8437d28 commit d9ebf95

2 files changed

Lines changed: 29 additions & 3 deletions

File tree

internal-packages/dashboard-agent/src/compaction.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
safeTail,
2020
shouldCompactConversation,
2121
STATIC_PREFIX_TOKENS,
22+
SUMMARY_INSTRUCTION,
2223
withDurableState,
2324
} from "./compaction";
2425

@@ -389,6 +390,31 @@ describe("the summariser's input", () => {
389390
});
390391
});
391392

393+
/**
394+
* A watch's lifecycle is server-side: it can expire or be cancelled with nothing written back
395+
* into the transcript. So the summary can only ever say what the transcript RECORDED — asking
396+
* for what is running turns an old confirmation into a claim that it still is, and the next
397+
* answer tells the user a watch is on that ended hours ago. The property, not the sentence:
398+
* the watch line asks for a record and never for present state.
399+
*/
400+
describe("the summary instruction never asks for present state", () => {
401+
const watchLine = SUMMARY_INSTRUCTION.split("\n").find((line) => /watch/i.test(line));
402+
403+
it("has a line about watches at all", () => {
404+
expect(watchLine).toBeDefined();
405+
});
406+
407+
it("asks what the transcript recorded, not what is true now", () => {
408+
expect(watchLine).toMatch(/record/i);
409+
// The transcript cannot know, so the instruction has to say why.
410+
expect(watchLine).toMatch(/expire|cancel/i);
411+
});
412+
413+
it("never asks for a watch that is running, scheduled or active", () => {
414+
expect(watchLine).not.toMatch(/(?:that is|still|currently)\s+(?:running|active|scheduled)/i);
415+
});
416+
});
417+
392418
/** Records what each model call was actually given, and summarises predictably. */
393419
function capturingModel(prompts: string[], summarized: string[] = []) {
394420
return new MockLanguageModelV3({

internal-packages/dashboard-agent/src/compaction.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,16 +61,16 @@ const SUMMARY_MODEL = "anthropic:claude-haiku-4-5" as const;
6161
*/
6262
const SUMMARY_MAX_OUTPUT_TOKENS = 1_000;
6363

64-
const SUMMARY_INSTRUCTION = `You are compacting a support conversation between a user and an agent that reads a Trigger.dev dashboard, so the agent can keep going with a shorter history.
64+
export const SUMMARY_INSTRUCTION = `You are compacting a support conversation between a user and an agent that reads a Trigger.dev dashboard, so the agent can keep going with a shorter history.
6565
6666
Write a summary in under 400 words, as notes rather than prose. Keep, in this order:
6767
1. What the user is trying to do, in their own terms, and anything they asked to be remembered.
6868
2. Facts already established, with the run ids, queue names, task identifiers, error fingerprints and numbers they rest on. Never restate a number you cannot see.
6969
3. Any investigation that is open: its investigationId, its title and its current outcome.
70-
4. Any watch that is running or has reported, and what it said.
70+
4. Any watch the transcript records — what it was set up to watch, and what it said if it reported. Write it as what the transcript recorded, never as what is true now: a watch can expire or be cancelled without saying so here, so never present one as current.
7171
5. What was asked most recently and what is still unanswered.
7272
73-
Drop tool mechanics, retries, and anything already superseded. Do not add advice, and do not invent anything that is not in the transcript.`;
73+
Drop tool mechanics, retries, and anything already superseded. Do not add advice, and do not invent anything that is not in the transcript. Everything you write is a record of what the transcript said, not a claim about the present.`;
7474

7575
/** A summary that reads as a summary, and never as the user's next question. */
7676
export function summaryMessage(summary: string, durableState?: string): ModelMessage {

0 commit comments

Comments
 (0)