Skip to content

Commit 237c7a2

Browse files
committed
fix(agent-gateway): report run_in_background on the task wire
1 parent 5bcd3cc commit 237c7a2

7 files changed

Lines changed: 28 additions & 11 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@pymodel/pythinker-code": patch
3+
---
4+
5+
Fix foreground subagents being reported as background tasks on the task list.

packages/agent-gateway/src/protocol/task.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,6 @@ export const taskSchema = z.object({
3030
agent_id: z.string().optional(),
3131
subagent_type: z.string().optional(),
3232
parent_tool_call_id: z.string().optional(),
33-
run_in_background: z.boolean().optional(),
33+
run_in_background: z.boolean(),
3434
});
3535
export type Task = z.infer<typeof taskSchema>;

packages/agent-gateway/src/routes/tasks.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ function toWireTask(
272272
status,
273273
created_at: createdIso,
274274
started_at: createdIso,
275-
run_in_background: info.detached !== false,
275+
run_in_background: info.detached ?? true,
276276
};
277277
if (info.endedAt !== null && info.endedAt !== undefined) {
278278
base.completed_at = new Date(info.endedAt).toISOString();

packages/agent-gateway/test/tasks.test.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -235,21 +235,21 @@ describe('server-v2 /api/v1/sessions/{sid}/tasks', () => {
235235
expect(byId.get(questionId)?.parent_tool_call_id).toBeUndefined();
236236
});
237237

238-
it('reports run_in_background false for a foreground (non-detached) task', async () => {
238+
it('reports run_in_background from the task detached flag', async () => {
239239
const id = await createSession();
240240
const tasks = await mainAgentTasks(id);
241+
const backgroundId = tasks.registerTask(fakeTask('agent'));
241242
const foregroundId = tasks.registerTask(fakeTask('agent'), { detached: false });
242243
await flush();
243244

244-
const listed = await getJson<ListWire>(`/api/v1/sessions/${id}/tasks`);
245-
expect(listed.body.code).toBe(0);
246-
const entry = listed.body.data.items.find((t) => t.id === foregroundId);
247-
expect(entry).toMatchObject({ kind: 'subagent', status: 'running' });
248-
expect(entry?.run_in_background).toBe(false);
245+
const { body } = await getJson<ListWire>(`/api/v1/sessions/${id}/tasks`);
246+
expect(body.code).toBe(0);
247+
const byId = new Map(body.data.items.map((t) => [t.id, t]));
248+
expect(byId.get(backgroundId)?.run_in_background).toBe(true);
249+
expect(byId.get(foregroundId)?.run_in_background).toBe(false);
249250

250-
const got = await getJson<TaskWire>(`/api/v1/sessions/${id}/tasks/${foregroundId}`);
251-
expect(got.body.code).toBe(0);
252-
expect(got.body.data.run_in_background).toBe(false);
251+
const single = await getJson<TaskWire>(`/api/v1/sessions/${id}/tasks/${foregroundId}`);
252+
expect(single.body.data.run_in_background).toBe(false);
253253
});
254254

255255
it('filters the list by wire status', async () => {

packages/protocol/src/__tests__/rest-task.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ describe('getTaskResponseSchema', () => {
4949
description: 'spin up x',
5050
status: 'running' as const,
5151
created_at: '2026-06-04T10:00:00.000Z',
52+
run_in_background: true,
5253
};
5354
expect(getTaskResponseSchema.parse(t).kind).toBe('subagent');
5455
});

packages/protocol/src/__tests__/task.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,19 @@ describe('taskSchema', () => {
4343
status: 'running',
4444
created_at: '2026-06-04T10:00:00.000Z',
4545
started_at: '2026-06-04T10:00:00.000Z',
46+
run_in_background: true,
4647
};
4748

4849
it('round-trips a running task', () => {
4950
expect(taskSchema.parse(full)).toEqual(full);
5051
});
5152

53+
it('accepts run_in_background when present and omits it freely (optional)', () => {
54+
const { run_in_background: _omitted, ...withoutFlag } = full;
55+
expect(taskSchema.safeParse(withoutFlag).success).toBe(true);
56+
expect(taskSchema.safeParse({ ...full, run_in_background: false }).success).toBe(true);
57+
});
58+
5259
it('round-trips a completed task with completed_at + output fields', () => {
5360
const completed: Task = {
5461
...full,

packages/protocol/src/task.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ export const taskSchema = z.object({
3131
/** Subagent tasks only: the child's effective thinking effort at spawn. */
3232
thinking_effort: z.string().optional(),
3333
agent_id: z.string().optional(),
34+
/** Whether the task runs detached from the caller's turn (background).
35+
* Optional: producers that predate the field (e.g. the agent-core v1
36+
* task service) omit it — consumers apply the foreground fallback. */
37+
run_in_background: z.boolean().optional(),
3438
});
3539
export type Task = z.infer<typeof taskSchema>;
3640

0 commit comments

Comments
 (0)