Skip to content

Commit 9296337

Browse files
committed
feat(orchestration-v2): populate subagent observability from providers
Fills in the data model added by the previous PR. Every adapter now reports what its subagents are actually doing: role provenance, token usage, recent activity, and per-activation records. Usage needs two merge strategies because providers report it differently. Codex sends cumulative snapshots per thread, so those merge by maximum — duplicate and late out-of-order frames then leave the total unchanged instead of inflating it. Claude reports per activation, so lifetime usage accumulates only the delta since the previous snapshot. A subagent identity is reusable, so completing an activation leaves it at "idle" rather than "completed"; the timeline row for that activation is still recorded as completed via the status mapping. Three activation-lifecycle fixes in the Claude adapter, found in review: - A resume that raced past settle was pre-opened by the wake buffer, which erased the terminal status the reopen check reads. The resume then reused the finished activation instead of starting a new one, so its ordinal stopped matching reality and its usage was measured against the previous activation's totals — clamping the delta to zero and silently dropping the resumed run's tokens. The pre-open now records that it happened, and the reopen check honours it. - A replayed "waiting" frame could resurrect a settled subagent; only pending/running were guarded before. - A duplicate terminal frame re-stamped completedAt, drifting the recorded completion time. Codex already preserved it; Claude now matches. Reuse across runs is deliberately not addressed here — a subagent row still carries the run it was spawned under. That is the next PR.
1 parent 877ef38 commit 9296337

15 files changed

Lines changed: 1329 additions & 102 deletions

apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
type OrchestrationV2ProviderTurn,
1313
type OrchestrationV2RuntimeRequest,
1414
type OrchestrationV2Subagent,
15+
type OrchestrationV2SubagentActivation,
1516
type OrchestrationV2TurnItem,
1617
type OrchestrationV2UserInputQuestion,
1718
type ProviderApprovalDecision,
@@ -66,6 +67,7 @@ import {
6667
makeSubagentConversationArtifacts,
6768
subagentThreadTitle,
6869
} from "../SubagentProjection.ts";
70+
import { defaultSubagentRole, subagentActivationId } from "../SubagentObservability.ts";
6971
import {
7072
ProviderAdapterEnsureThreadError,
7173
ProviderAdapterForkThreadError,
@@ -1926,6 +1928,12 @@ export function makeAcpAdapterV2(options: AcpAdapterV2Options): ProviderAdapterV
19261928
const turnItemOrdinal =
19271929
existing?.turnItemOrdinal ?? (yield* resolveItemOrdinal(context, nativeTaskId));
19281930
const taskStatus = update.status;
1931+
const activationId = subagentActivationId(nodeId, 1);
1932+
const settled =
1933+
taskStatus === "completed" ||
1934+
taskStatus === "failed" ||
1935+
taskStatus === "cancelled" ||
1936+
taskStatus === "interrupted";
19291937
const task: OrchestrationV2Subagent = {
19301938
...(existing?.task ?? {
19311939
id: nodeId,
@@ -1942,20 +1950,21 @@ export function makeAcpAdapterV2(options: AcpAdapterV2Options): ProviderAdapterV
19421950
prompt: update.prompt,
19431951
title: update.title,
19441952
model: update.model,
1945-
kind: "subagent",
1946-
role: { name: "general-purpose", source: "app_default" },
1953+
kind: "subagent" as const,
1954+
role: defaultSubagentRole(),
1955+
result: null,
19471956
usage: null,
1948-
currentActivationId: null,
1957+
currentActivationId: activationId,
19491958
activationCount: 1,
19501959
workflow: null,
19511960
workflowMembership: null,
19521961
recentActivity: [],
1953-
result: null,
19541962
startedAt: now,
19551963
}),
19561964
status: taskStatus,
19571965
result: existing?.assistantText || update.result,
1958-
completedAt: taskStatus === "running" ? null : now,
1966+
currentActivationId: settled ? null : activationId,
1967+
completedAt: settled ? (existing?.task.completedAt ?? now) : null,
19591968
updatedAt: now,
19601969
};
19611970
const subagent: ActiveAcpSubagent = existing ?? {
@@ -2073,7 +2082,7 @@ export function makeAcpAdapterV2(options: AcpAdapterV2Options): ProviderAdapterV
20732082
...subagent.task,
20742083
status: taskStatus,
20752084
result,
2076-
completedAt: taskStatus === "running" ? null : now,
2085+
completedAt: settled ? (subagent.task.completedAt ?? now) : null,
20772086
updatedAt: now,
20782087
};
20792088
const providerThreadId = subagent.task.providerThreadId;
@@ -2120,6 +2129,23 @@ export function makeAcpAdapterV2(options: AcpAdapterV2Options): ProviderAdapterV
21202129
},
21212130
});
21222131
yield* emitProviderEvent({ type: "subagent.updated", driver, subagent: subagent.task });
2132+
yield* emitProviderEvent({
2133+
type: "subagent_activation.updated",
2134+
driver,
2135+
activation: {
2136+
id: activationId,
2137+
threadId: subagent.task.threadId,
2138+
subagentId: subagent.task.id,
2139+
runId: subagent.task.runId,
2140+
providerTurnId: subagent.providerTurnId,
2141+
ordinal: 1,
2142+
status: taskStatus,
2143+
usage: null,
2144+
startedAt: subagent.task.startedAt,
2145+
completedAt: settled ? now : null,
2146+
updatedAt: now,
2147+
} satisfies OrchestrationV2SubagentActivation,
2148+
});
21232149
yield* emitProviderEvent({
21242150
type: "turn_item.updated",
21252151
driver,

0 commit comments

Comments
 (0)