From dc02446ff4adc1568404a59a1d8d28c23f89c292 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sat, 6 Jun 2026 21:51:09 -0400 Subject: [PATCH] refactor(core): document task result correlation boundary --- packages/core/src/tool/task.ts | 5 +-- packages/core/test/tool-task.test.ts | 53 +++++++++++++--------------- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/packages/core/src/tool/task.ts b/packages/core/src/tool/task.ts index ffb59c1c366..c5f43de8108 100644 --- a/packages/core/src/tool/task.ts +++ b/packages/core/src/tool/task.ts @@ -50,8 +50,9 @@ export const make = Effect.fn("TaskTool.make")(function* ( model: agent.model ?? parent.model, }) - // TODO: Replace this fresh-child composition with Session.run, preserving admission/execution - // separation while returning the assistant response after the admitted boundary. + // TODO: Replace this fresh-child-only composition once Session execution exposes a bounded + // activity/result identity. An admission ID alone cannot correlate a response when one drain + // processes later queued work. const run = Effect.gen(function* () { yield* sessions.prompt({ sessionID: child.id, diff --git a/packages/core/test/tool-task.test.ts b/packages/core/test/tool-task.test.ts index b1e06074a93..be0c58a8746 100644 --- a/packages/core/test/tool-task.test.ts +++ b/packages/core/test/tool-task.test.ts @@ -64,9 +64,9 @@ describe("TaskTool", () => { const inputs: Parameters[0][] = [] let resumed = 0 const sessions = mockSessions({ - prompt: (input) => { - inputs.push(input) - return Effect.succeed(admission(input)) + prompt: (value) => { + inputs.push(value) + return Effect.succeed(admission(value)) }, resume: () => Effect.sync(() => resumed++), }) @@ -90,8 +90,6 @@ describe("TaskTool", () => { created = true return child }), - prompt: (input) => Effect.succeed(admission(input)), - resume: () => Effect.void, }) const tool = yield* TaskTool.make(sessions, () => Effect.succeed(undefined)) @@ -102,24 +100,6 @@ describe("TaskTool", () => { }), ) - it.live("interrupts the child when foreground waiting is interrupted", () => - Effect.gen(function* () { - const resumed = yield* Deferred.make() - const interrupts: SessionV2.ID[] = [] - const sessions = mockSessions({ - prompt: (input) => Effect.succeed(admission(input)), - resume: () => Deferred.succeed(resumed, undefined).pipe(Effect.andThen(Effect.never)), - interrupt: (sessionID) => Effect.sync(() => interrupts.push(sessionID)), - }) - const tool = yield* TaskTool.make(sessions, resolveAgent) - const fiber = yield* execute(tool, input, "call_task_interrupt").pipe(Effect.forkChild) - - yield* Deferred.await(resumed) - yield* Fiber.interrupt(fiber) - expect(interrupts).toEqual([childID]) - }), - ) - it.live("does not notify the parent when background work is interrupted", () => Effect.gen(function* () { let notified = false @@ -140,17 +120,34 @@ describe("TaskTool", () => { }), ) + it.live("interrupts the child when foreground waiting is interrupted", () => + Effect.gen(function* () { + const resumed = yield* Deferred.make() + const interrupts: SessionV2.ID[] = [] + const sessions = mockSessions({ + resume: () => Deferred.succeed(resumed, undefined).pipe(Effect.andThen(Effect.never)), + interrupt: (sessionID) => Effect.sync(() => interrupts.push(sessionID)), + }) + const tool = yield* TaskTool.make(sessions, resolveAgent) + const fiber = yield* execute(tool, input, "call_task_interrupt").pipe(Effect.forkChild) + + yield* Deferred.await(resumed) + yield* Fiber.interrupt(fiber) + expect(interrupts).toEqual([childID]) + }), + ) + it.live("returns before background completion and steers the result into the parent", () => Effect.gen(function* () { const gate = yield* Deferred.make() const notified = yield* Deferred.make[0]>() const inputs: Parameters[0][] = [] const sessions = mockSessions({ - prompt: (input) => { - inputs.push(input) - return input.sessionID === parentID - ? Deferred.succeed(notified, input).pipe(Effect.as(admission(input))) - : Effect.succeed(admission(input)) + prompt: (value) => { + inputs.push(value) + return value.sessionID === parentID + ? Deferred.succeed(notified, value).pipe(Effect.as(admission(value))) + : Effect.succeed(admission(value)) }, resume: () => Deferred.await(gate), })