Skip to content

Commit b491d45

Browse files
Append pending sub-agent nudges onto existing ephemeral turns (#897)
1 parent 586a21f commit b491d45

2 files changed

Lines changed: 46 additions & 3 deletions

File tree

src/subagent/nudge-director.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,45 @@ describe("SubAgentDirector tool failure recovery", () => {
277277
expect(ephemeralTexts(later)).toBeUndefined();
278278
});
279279

280+
test("recovery nudge appends to ephemeral turns already on the infer", async () => {
281+
const director = new SubAgentDirector("system", [], undefined, 30);
282+
const caps = capabilities();
283+
// Seed an ephemeral turn only when the caller did not supply any, so the
284+
// infer action applyPendingNudge rewrites already carries ephemeral turns.
285+
const seeding: ReactorCapabilities = {
286+
...caps,
287+
infer: (options) => {
288+
const existing = (options as { ephemeralTurns?: unknown[] } | undefined)
289+
?.ephemeralTurns;
290+
return caps.infer({
291+
...(options ?? {}),
292+
...(existing === undefined
293+
? {
294+
ephemeralTurns: [
295+
{
296+
role: "user",
297+
content: [{ type: "text", text: "PRE-SEEDED-EPHEMERAL" }],
298+
timestamp: 0,
299+
},
300+
],
301+
}
302+
: {}),
303+
});
304+
},
305+
};
306+
307+
await director.decide(inferenceDone(["seeded-fail"]), state, seeding);
308+
const texts = ephemeralTexts(
309+
inferAction(
310+
await director.decide(toolDone("seeded-fail", true), state, seeding),
311+
),
312+
);
313+
314+
expect(texts).toHaveLength(2);
315+
expect(texts?.[0]).toBe("PRE-SEEDED-EPHEMERAL");
316+
expect(texts?.[1]).toContain("A tool call failed");
317+
});
318+
280319
test("failed-tool recovery supersedes soft re-read guidance", async () => {
281320
const director = new SubAgentDirector("system", [], undefined, 30);
282321
const caps = capabilities();

src/subagent/nudge-director.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import type {
1414
ReactorAction,
1515
ToolDefinition,
1616
ConversationTurn,
17-
InferenceOptions,
1817
RetryPolicy,
1918
} from "@intx/types/runtime";
2019
import {
@@ -97,10 +96,15 @@ function inferWithSubAgentNudge(
9796
* once the pending tool calls have actually executed.
9897
*/
9998
function withEphemeralNudge(
100-
options: InferenceOptions | undefined,
99+
options: ExtendedInferenceOptions | undefined,
101100
text: string,
102101
): ExtendedInferenceOptions {
103-
return { ...(options ?? {}), ephemeralTurns: [ephemeralNudgeTurn(text)] };
102+
const turn = ephemeralNudgeTurn(text);
103+
const existing = options?.ephemeralTurns;
104+
if (existing === undefined || existing.length === 0) {
105+
return { ...(options ?? {}), ephemeralTurns: [turn] };
106+
}
107+
return { ...(options ?? {}), ephemeralTurns: [...existing, turn] };
104108
}
105109

106110
function isEmptyContinuation(event: ReactorInboundEvent): boolean {

0 commit comments

Comments
 (0)