Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 48 additions & 9 deletions apps/server/src/mcp/OrchestratorMcpService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,21 @@ function pendingTaskApproval(
return approval;
}

/** Every task `directAppOwnedChildTasks` can return, so callers load only those. */
function directChildTaskIds(
target: Pick<OrchestrationV2ThreadProjection, "thread" | "runs">,
returnedRunIds: ReadonlySet<RunId | null>,
): ReadonlyArray<NodeId> {
const ids = new Set<NodeId>();
if (target.thread.forkedFrom?.type === "node") ids.add(target.thread.forkedFrom.nodeId);
for (const run of target.runs) {
if (returnedRunIds.has(run.id) && run.delegatedTaskId !== undefined) {
ids.add(run.delegatedTaskId);
}
}
return [...ids];
}

function directAppOwnedChildTasks(
parent: Pick<OrchestrationV2ThreadProjection, "thread" | "subagents">,
target: Pick<OrchestrationV2ThreadProjection, "thread" | "runs">,
Expand Down Expand Up @@ -1016,7 +1031,6 @@ const make = Effect.gen(function* () {
[
"runs",
"messages",
"subagents",
"providerThreads",
"providerTurns",
"runtimeRequests",
Expand All @@ -1034,6 +1048,15 @@ const make = Effect.gen(function* () {
),
);

// A dispatcher thread holds thousands of tasks, each with its full prompt; read only the ones named.
const loadTasks = (threadId: ThreadId, taskIds: ReadonlyArray<NodeId>) =>
threadManagement.getThreadRecords(threadId, ["subagents"], { subagentIds: taskIds }).pipe(
Effect.map(({ subagents }) => subagents),
Effect.mapError((error) =>
failure("orchestration_error", `Unable to read thread ${threadId}: ${errorMessage(error)}`),
),
);

// Task waits read on every wake of a parent that may hold thousands of
// tasks, so they read only the task they watch and what its status needs.
const loadTaskParent = (threadId: ThreadId, taskId: NodeId) =>
Expand Down Expand Up @@ -2385,7 +2408,7 @@ const make = Effect.gen(function* () {
const childThreadId = current.childThreadId;
if (childThreadId === null) {
// The task runs in a linked environment: its thread there is stopped through the link.
const { subagents } = yield* loadProjection(scope.thread.threadId);
const subagents = yield* loadTasks(scope.thread.threadId, [input.taskId]);
const remoteTask = subagents.find(
(task) =>
task.id === input.taskId &&
Expand Down Expand Up @@ -2428,7 +2451,7 @@ const make = Effect.gen(function* () {
}
});
yield* assertStoppable(childThreadId);
const parentTask = parentProjection.subagents.find(
const parentTask = (yield* loadTasks(scope.thread.threadId, [input.taskId])).find(
(task) => task.id === input.taskId && task.origin === "app_owned",
);
const disposeCompletionDelivery =
Expand Down Expand Up @@ -2548,9 +2571,11 @@ const make = Effect.gen(function* () {
runId: parentTask.runId,
subagentId: parentTask.id,
});
const { subagents } = yield* threadManagement.getThreadRecords(scope.thread.threadId, [
"subagents",
]);
const { subagents } = yield* threadManagement.getThreadRecords(
scope.thread.threadId,
["subagents"],
{ subagentIds: [input.taskId] },
);
return subagents.some((task) => task.id === input.taskId && task.status === "cancelled");
}).pipe(Effect.mapError(threadManagementFailure));
if (isTerminalTaskStatus(current.status)) {
Expand Down Expand Up @@ -2847,7 +2872,19 @@ const make = Effect.gen(function* () {
page.filter((row) => row.sourceThreadId === target.thread.id).map((row) => row.item.runId),
);
const tasks =
parent === undefined ? [] : directAppOwnedChildTasks(parent, target, returnedRunIds);
parent === undefined
? []
: directAppOwnedChildTasks(
{
thread: parent.thread,
subagents: yield* loadTasks(
parent.thread.id,
directChildTaskIds(target, returnedRunIds),
),
},
target,
returnedRunIds,
);
if (
parent !== undefined &&
scope.thread !== undefined &&
Expand Down Expand Up @@ -2978,14 +3015,16 @@ const make = Effect.gen(function* () {
const ownedTask =
result.timedOut &&
status !== "waiting" &&
parent?.subagents.some(
parent !== undefined &&
taskId !== undefined &&
(yield* loadTasks(parent.thread.id, [taskId])).some(
(task) =>
task.id === taskId &&
task.origin === "app_owned" &&
task.threadId === parent.thread.id &&
task.childThreadId === input.threadId &&
task.completionDelivery?.state !== "disposed",
) === true;
);
// A child waiting on a question or approval needs an answer, not a wake.
const childRequests = ownedTask
? (yield* threadManagement
Expand Down
5 changes: 3 additions & 2 deletions apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4944,7 +4944,8 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
});

// Claude has no recovered frame; the next assistant message proves recovery.
const completeProviderRetry = Effect.fn("ClaudeAdapterV2.completeProviderRetry")(function* (
// Runs on every model output frame and is almost always a no-op; only a pending retry gets a span.
const completeProviderRetry = Effect.fnUntraced(function* (
context: ActiveClaudeTurnContext,
updatedAt: DateTime.Utc,
) {
Expand Down Expand Up @@ -4977,7 +4978,7 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
startedAt: providerRetry.startedAt,
updatedAt,
}),
});
}).pipe(Effect.withSpan("ClaudeAdapterV2.completeProviderRetry"));
});

const rememberProviderThread =
Expand Down
5 changes: 3 additions & 2 deletions apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1945,7 +1945,8 @@ export const makeCodexAdapterV2 = Effect.fn("makeCodexAdapterV2")(function* (
});

// Only new model output proves recovery; local item/completed can arrive during upstream retries.
const completeProviderRetry = Effect.fn("CodexAdapterV2.completeProviderRetry")(function* (
// Runs on every model output frame and is almost always a no-op; only a pending retry gets a span.
const completeProviderRetry = Effect.fnUntraced(function* (
context: ActiveCodexTurnContext,
updatedAt: DateTime.Utc,
) {
Expand All @@ -1965,7 +1966,7 @@ export const makeCodexAdapterV2 = Effect.fn("makeCodexAdapterV2")(function* (
type: "turn_item.updated",
driver: CODEX_PROVIDER,
turnItem: codexRetryTurnItem(context, providerRetry, "completed", updatedAt),
});
}).pipe(Effect.withSpan("CodexAdapterV2.completeProviderRetry"));
});

const registerRootTurn = Effect.fn("CodexAdapterV2.registerRootTurn")(function* (input: {
Expand Down
57 changes: 37 additions & 20 deletions apps/server/src/orchestration-v2/Orchestrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1153,6 +1153,9 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
turnItemTypes: ["user_message", "error"],
messageRoles: ["user"],
omitSettledLeafNodes: true,
// Commands here read only undelivered or active tasks; a dispatcher thread keeps
// thousands of delivered ones, each carrying its full prompt.
omitSettledSubagents: true,
Comment thread
macroscopeapp[bot] marked this conversation as resolved.
Resolved
},
)
.pipe(
Expand Down Expand Up @@ -7289,14 +7292,12 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
effects: Ref.Ref<Array<PendingOrchestrationEffectV2>>,
) {
const parentProjection = yield* projectionStore
.getThreadRecords(command.parentThreadId, [
"runs",
"nodes",
"subagents",
"providerThreads",
"providerTurns",
"attempts",
])
.getThreadRecords(
command.parentThreadId,
["runs", "nodes", "providerThreads", "providerTurns", "attempts"],
// The parent node must belong to the active run, whose nodes this keeps.
{ omitSettledLeafNodes: true },
)
.pipe(
Effect.mapError(
(cause) =>
Expand Down Expand Up @@ -7366,7 +7367,7 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
parentTitle: parentProjection.thread.title,
prompt: command.task,
...(command.title === undefined ? {} : { title: command.title }),
ordinal: parentProjection.subagents.length + 1,
ordinal: (yield* subagentCount(command.parentThreadId)) + 1,
});
const childThread: OrchestrationV2AppThread = {
...makeSubagentChildThread({
Expand Down Expand Up @@ -7601,7 +7602,9 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
);
if (run === undefined || run.delegatedTaskId !== undefined) return;
const parent = yield* projectionStore
.getThreadRecords(parentThreadId, ["runs", "subagents", "providerTurns", "attempts"])
.getThreadRecords(parentThreadId, ["runs", "subagents", "providerTurns", "attempts"], {
subagentIds: child.forkedFrom?.type === "node" ? [child.forkedFrom.nodeId] : [],
})
.pipe(mapDispatchError(command));
const original = parent.subagents.find(
(task) =>
Expand Down Expand Up @@ -7729,13 +7732,7 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
events: Ref.Ref<Array<OrchestrationV2DomainEvent>>,
) {
const parentProjection = yield* projectionStore
.getThreadRecords(command.parentThreadId, [
"runs",
"nodes",
"subagents",
"providerTurns",
"attempts",
])
.getThreadRecords(command.parentThreadId, ["runs", "providerTurns", "attempts"])
.pipe(
Effect.mapError(
(cause) => new OrchestratorProjectionError({ threadId: command.parentThreadId, cause }),
Expand Down Expand Up @@ -7767,7 +7764,7 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
parentTitle: parentProjection.thread.title,
prompt: command.task,
...(command.title === undefined ? {} : { title: command.title }),
ordinal: parentProjection.subagents.length + 1,
ordinal: (yield* subagentCount(command.parentThreadId)) + 1,
});
const common = {
threadId: command.parentThreadId,
Expand Down Expand Up @@ -7872,11 +7869,19 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
>,
events: Ref.Ref<Array<OrchestrationV2DomainEvent>>,
) {
// Delivery planning reads only this task and the undelivered tasks of the queued delivery;
// the task's own node and turn item are all it updates besides.
const parentProjection = yield* projectionStore
.getThreadRecords(
command.parentThreadId,
["runs", "messages", "subagents", "providerTurns", "nodes", "turnItems"],
{ turnItemTypes: ["subagent"], messageRoles: ["user"] },
{
turnItemTypes: ["subagent"],
messageRoles: ["user"],
omitSettledSubagents: { except: [command.taskId] },
nodeIds: [command.taskId],
turnItemNodeIds: [command.taskId],
},
)
.pipe(
Effect.mapError(
Expand Down Expand Up @@ -8102,8 +8107,15 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
command: Extract<OrchestrationV2Command, { readonly type: "delegated_task.wake-policy" }>,
events: Ref.Ref<Array<OrchestrationV2DomainEvent>>,
) {
// Delivery planning reads only this task and the undelivered tasks of the queued delivery.
const parentProjection = yield* projectionStore
.getThreadRecords(command.parentThreadId, ["subagents", "runs", "messages", "providerTurns"])
.getThreadRecords(
command.parentThreadId,
["subagents", "runs", "messages", "providerTurns"],
{
omitSettledSubagents: { except: [command.taskId] },
},
)
.pipe(
Effect.mapError(
(cause) =>
Expand Down Expand Up @@ -9546,6 +9558,11 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
}
});

const subagentCount = (threadId: ThreadId) =>
projectionStore
.getSubagentCount(threadId)
.pipe(Effect.mapError((cause) => new OrchestratorProjectionError({ threadId, cause })));

const loadProjectionForCommand = <K extends ProjectionRecordField>(
command: OrchestrationV2Command,
fields: ReadonlyArray<K>,
Expand Down
Loading
Loading