@g3un/pi-orchestra 0.2.1 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -3
- package/docs/orchestration-model.md +17 -12
- package/package.json +5 -1
- package/skills/pi-orchestra/SKILL.md +92 -0
- package/src/adapters/in-memory-store.ts +45 -20
- package/src/adapters/index.ts +1 -0
- package/src/adapters/pi-runtime.ts +114 -38
- package/src/adapters/sqlite-store.ts +276 -0
- package/src/adapters/store-subscriptions.ts +20 -0
- package/src/core/bus-format.ts +13 -4
- package/src/core/bus.ts +66 -0
- package/src/core/orchestra.ts +21 -26
- package/src/core/store.ts +12 -3
- package/src/core/subagent.ts +5 -3
- package/src/core/workflow.ts +5 -4
- package/src/core/workgroup.ts +3 -3
- package/src/extension/index.ts +13 -5
- package/src/extension/orchestra-events.ts +107 -24
- package/src/extension/workflow-monitor.ts +16 -14
- package/src/profiles/code-reviewer.ts +20 -0
- package/src/profiles/evidence-synthesizer.ts +20 -0
- package/src/profiles/external-researcher.ts +20 -0
- package/src/profiles/index.ts +5 -1
- package/src/profiles/profile.ts +31 -0
- package/src/profiles/source-code-qa.ts +19 -0
- package/src/tools/bus.ts +93 -35
- package/src/tools/subagent.ts +29 -8
- package/src/tools/workflow.ts +47 -50
- package/src/tools/workgroup.ts +22 -14
- package/src/utils.ts +12 -2
- package/src/profiles/stage-leader.ts +0 -20
package/src/tools/bus.ts
CHANGED
|
@@ -1,28 +1,43 @@
|
|
|
1
1
|
import { defineTool, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
|
-
import
|
|
3
|
+
import {
|
|
4
|
+
createBusSubscription,
|
|
5
|
+
createBusSubscriptionId,
|
|
6
|
+
type Bus,
|
|
7
|
+
type BusMessage,
|
|
8
|
+
type BusSubscription,
|
|
9
|
+
} from "../core/bus.ts";
|
|
4
10
|
import type { OrchestraApi } from "../core/orchestra.ts";
|
|
5
|
-
import {
|
|
11
|
+
import type { AgentStore } from "../core/store.ts";
|
|
6
12
|
|
|
7
13
|
export type BusInput =
|
|
8
14
|
| {
|
|
9
15
|
action: "create";
|
|
10
|
-
name
|
|
16
|
+
name: string | undefined;
|
|
11
17
|
}
|
|
12
18
|
| {
|
|
13
19
|
action: "status";
|
|
14
|
-
|
|
20
|
+
name: string;
|
|
15
21
|
}
|
|
16
22
|
| {
|
|
17
23
|
action: "publish";
|
|
18
|
-
|
|
24
|
+
name: string;
|
|
19
25
|
message: string;
|
|
20
|
-
from
|
|
26
|
+
from: string;
|
|
27
|
+
}
|
|
28
|
+
| {
|
|
29
|
+
action: "subscribe";
|
|
30
|
+
name: string;
|
|
31
|
+
}
|
|
32
|
+
| {
|
|
33
|
+
action: "unsubscribe";
|
|
34
|
+
name: string;
|
|
21
35
|
};
|
|
22
36
|
|
|
23
37
|
export interface BusOutput {
|
|
24
38
|
bus?: Bus;
|
|
25
39
|
busMessage?: BusMessage;
|
|
40
|
+
subscription?: BusSubscription;
|
|
26
41
|
message: string;
|
|
27
42
|
}
|
|
28
43
|
|
|
@@ -33,11 +48,13 @@ export interface BusTool {
|
|
|
33
48
|
|
|
34
49
|
export interface BusToolDeps {
|
|
35
50
|
orchestra: OrchestraApi;
|
|
51
|
+
store: AgentStore;
|
|
36
52
|
}
|
|
37
53
|
|
|
38
54
|
const BusActionParams = Type.String({
|
|
39
|
-
enum: ["create", "status", "publish"],
|
|
40
|
-
description:
|
|
55
|
+
enum: ["create", "status", "publish", "subscribe", "unsubscribe"],
|
|
56
|
+
description:
|
|
57
|
+
"create/status/publish/subscribe/unsubscribe shared context buses; completion is delivered through pi-orchestra events.",
|
|
41
58
|
});
|
|
42
59
|
|
|
43
60
|
const BusToolParams = Type.Object(
|
|
@@ -45,45 +62,52 @@ const BusToolParams = Type.Object(
|
|
|
45
62
|
action: BusActionParams,
|
|
46
63
|
name: Type.Optional(
|
|
47
64
|
Type.String({
|
|
48
|
-
description:
|
|
49
|
-
|
|
50
|
-
),
|
|
51
|
-
id: Type.Optional(
|
|
52
|
-
Type.String({
|
|
53
|
-
description: "Required except create. Bus id/name returned by action=create.",
|
|
65
|
+
description:
|
|
66
|
+
"Optional for action=create; required for action=status/publish. Use the short bus name, not a bus id.",
|
|
54
67
|
}),
|
|
55
68
|
),
|
|
56
69
|
message: Type.Optional(
|
|
57
70
|
Type.String({
|
|
58
|
-
description: "Required for action=publish. Shared context for
|
|
71
|
+
description: "Required for action=publish. Shared context for subscribed agents.",
|
|
59
72
|
}),
|
|
60
73
|
),
|
|
61
74
|
},
|
|
62
75
|
{ additionalProperties: false },
|
|
63
76
|
);
|
|
64
77
|
|
|
65
|
-
export function createBusTool({ orchestra }: BusToolDeps): BusTool {
|
|
78
|
+
export function createBusTool({ orchestra, store }: BusToolDeps): BusTool {
|
|
66
79
|
return {
|
|
67
80
|
name: "bus",
|
|
68
81
|
|
|
69
82
|
async execute(input) {
|
|
70
83
|
if (input.action === "create") {
|
|
71
84
|
const bus = orchestra.createBus({ name: input.name });
|
|
72
|
-
return { bus, message: formatBusStatus(bus, `Created bus ${
|
|
85
|
+
return { bus, message: formatBusStatus(bus, store, `Created bus ${formatBusLabel(bus)}.`) };
|
|
73
86
|
}
|
|
74
87
|
|
|
75
|
-
const bus = orchestra.getBus(input.
|
|
76
|
-
if (!bus) return { message: formatBusNotFound(input.
|
|
88
|
+
const bus = orchestra.getBus(input.name);
|
|
89
|
+
if (!bus) return { message: formatBusNotFound(input.name) };
|
|
77
90
|
|
|
78
91
|
if (input.action === "status") {
|
|
79
|
-
return { bus, message: formatBusStatus(bus) };
|
|
92
|
+
return { bus, message: formatBusStatus(bus, store) };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (input.action === "subscribe") {
|
|
96
|
+
const subscription = subscribeMainToBus(store, bus);
|
|
97
|
+
return { bus, subscription, message: `Subscribed main to bus ${formatBusLabel(bus)} for new messages.` };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (input.action === "unsubscribe") {
|
|
101
|
+
const subscriptionId = createBusSubscriptionId(bus.id, "main", "main");
|
|
102
|
+
store.deleteBusSubscription(subscriptionId);
|
|
103
|
+
return { bus, message: `Unsubscribed main from bus ${formatBusLabel(bus)}.` };
|
|
80
104
|
}
|
|
81
105
|
|
|
82
|
-
const published = await orchestra.publishBus(input.
|
|
106
|
+
const published = await orchestra.publishBus(input.name, input.message, input.from ?? "main");
|
|
83
107
|
return {
|
|
84
108
|
bus: published.bus,
|
|
85
109
|
busMessage: published.busMessage,
|
|
86
|
-
message: formatBusStatus(published.bus, `Published message to bus ${
|
|
110
|
+
message: formatBusStatus(published.bus, store, `Published message to bus ${formatBusLabel(published.bus)}.`),
|
|
87
111
|
};
|
|
88
112
|
},
|
|
89
113
|
};
|
|
@@ -97,7 +121,8 @@ export function defineBusPiTool(resolveTool: (ctx: ExtensionContext) => BusTool)
|
|
|
97
121
|
promptSnippet: "Use one bus per delegated work item; spawn related subagents or workgroups on it.",
|
|
98
122
|
promptGuidelines: [
|
|
99
123
|
"Use bus create before spawning related subagents or workgroups; reuse it for that work item.",
|
|
100
|
-
"Use bus publish to send shared context to
|
|
124
|
+
"Use bus publish with the bus name, not a bus id, to send shared context to subscribed agents; bus status shows published messages.",
|
|
125
|
+
"Use bus subscribe when main needs live bus messages; it starts from new messages after subscription. Unsubscribe when tracking is no longer useful.",
|
|
101
126
|
"Do not wait on buses; pi-orchestra delivers subagent and workgroup finish events automatically.",
|
|
102
127
|
],
|
|
103
128
|
parameters: BusToolParams,
|
|
@@ -117,35 +142,68 @@ function toBusInput(params: RawBusParams): BusInput {
|
|
|
117
142
|
if (params.action === "create") return { action: "create", name: params.name };
|
|
118
143
|
|
|
119
144
|
if (params.action === "status") {
|
|
120
|
-
if (!params.
|
|
121
|
-
return { action: "status",
|
|
145
|
+
if (!params.name) throw new Error("bus action=status requires name.");
|
|
146
|
+
return { action: "status", name: params.name };
|
|
122
147
|
}
|
|
123
148
|
|
|
124
|
-
if (
|
|
149
|
+
if (params.action === "subscribe") {
|
|
150
|
+
if (!params.name) throw new Error("bus action=subscribe requires name.");
|
|
151
|
+
return { action: "subscribe", name: params.name };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (params.action === "unsubscribe") {
|
|
155
|
+
if (!params.name) throw new Error("bus action=unsubscribe requires name.");
|
|
156
|
+
return { action: "unsubscribe", name: params.name };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (!params.name) throw new Error("bus action=publish requires name.");
|
|
125
160
|
if (!params.message) throw new Error("bus action=publish requires message.");
|
|
126
|
-
return { action: "publish",
|
|
161
|
+
return { action: "publish", name: params.name, message: params.message, from: "main" };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function subscribeMainToBus(store: AgentStore, bus: Bus): BusSubscription {
|
|
165
|
+
const id = createBusSubscriptionId(bus.id, "main", "main");
|
|
166
|
+
const subscription =
|
|
167
|
+
store.getBusSubscription(id) ??
|
|
168
|
+
createBusSubscription({
|
|
169
|
+
busId: bus.id,
|
|
170
|
+
subscriberId: "main",
|
|
171
|
+
subscriberKind: "main",
|
|
172
|
+
deliveredMessageIds: bus.messages.map((message) => message.id),
|
|
173
|
+
});
|
|
174
|
+
store.saveBusSubscription(subscription);
|
|
175
|
+
return subscription;
|
|
127
176
|
}
|
|
128
177
|
|
|
129
|
-
function formatBusNotFound(
|
|
130
|
-
return `Bus ${
|
|
178
|
+
function formatBusNotFound(name: string): string {
|
|
179
|
+
return `Bus ${name} not found.`;
|
|
131
180
|
}
|
|
132
181
|
|
|
133
182
|
function formatBusStatus(
|
|
134
183
|
bus: Bus,
|
|
135
|
-
|
|
184
|
+
store: AgentStore,
|
|
185
|
+
headline = `Bus ${formatBusLabel(bus)} has ${bus.messages.length} message(s).`,
|
|
136
186
|
): string {
|
|
137
187
|
if (bus.messages.length === 0) return headline;
|
|
138
188
|
|
|
139
|
-
return [headline, "", "Messages:", ...bus.messages.map(formatBusMessage)].join("\n");
|
|
189
|
+
return [headline, "", "Messages:", ...bus.messages.map((message) => formatBusMessage(message, store))].join("\n");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function formatBusMessage(message: BusMessage, store: AgentStore): string {
|
|
193
|
+
return [`- ${message.id} from ${formatBusMessageFrom(message.from, store)}:`, message.message].join("\n");
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function formatBusMessageFrom(from: string, store: AgentStore): string {
|
|
197
|
+
if (from === "main") return from;
|
|
198
|
+
return store.getRun(from)?.name ?? from;
|
|
140
199
|
}
|
|
141
200
|
|
|
142
|
-
function
|
|
143
|
-
return
|
|
201
|
+
function formatBusLabel(bus: Bus): string {
|
|
202
|
+
return bus.name;
|
|
144
203
|
}
|
|
145
204
|
|
|
146
205
|
type RawBusParams = {
|
|
147
|
-
action: "create" | "status" | "publish";
|
|
206
|
+
action: "create" | "status" | "publish" | "subscribe" | "unsubscribe";
|
|
148
207
|
name?: string;
|
|
149
|
-
id?: string;
|
|
150
208
|
message?: string;
|
|
151
209
|
};
|
package/src/tools/subagent.ts
CHANGED
|
@@ -9,7 +9,7 @@ export interface SubagentSpawnInput {
|
|
|
9
9
|
profile: AgentProfile;
|
|
10
10
|
task: string;
|
|
11
11
|
busId: string;
|
|
12
|
-
name
|
|
12
|
+
name: string | undefined;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export type SubagentInput =
|
|
@@ -17,18 +17,18 @@ export type SubagentInput =
|
|
|
17
17
|
| {
|
|
18
18
|
action: "status";
|
|
19
19
|
id: string;
|
|
20
|
-
busId
|
|
20
|
+
busId: string | undefined;
|
|
21
21
|
}
|
|
22
22
|
| {
|
|
23
23
|
action: "message";
|
|
24
24
|
id: string;
|
|
25
25
|
message: string;
|
|
26
|
-
busId
|
|
26
|
+
busId: string | undefined;
|
|
27
27
|
}
|
|
28
28
|
| {
|
|
29
29
|
action: "close";
|
|
30
30
|
id: string;
|
|
31
|
-
busId
|
|
31
|
+
busId: string | undefined;
|
|
32
32
|
};
|
|
33
33
|
|
|
34
34
|
export interface SubagentOutput {
|
|
@@ -53,7 +53,10 @@ export const AgentProfileParams = Type.Object(
|
|
|
53
53
|
{
|
|
54
54
|
name: Type.String({ description: "Short role/name for the subagent." }),
|
|
55
55
|
systemPrompt: Type.String({ description: "System prompt for the subagent." }),
|
|
56
|
-
tools: Type.
|
|
56
|
+
tools: Type.Array(Type.String(), {
|
|
57
|
+
description:
|
|
58
|
+
"Explicit tool allowlist for the subagent. Use [] for no work tools; finish and publish_bus are added automatically.",
|
|
59
|
+
}),
|
|
57
60
|
model: Type.Optional(
|
|
58
61
|
Type.String({
|
|
59
62
|
description: "Optional provider/model id.",
|
|
@@ -143,7 +146,7 @@ export function defineSubagentPiTool(resolveTool: (ctx: ExtensionContext) => Sub
|
|
|
143
146
|
description: "Create and manage isolated subagents.",
|
|
144
147
|
promptSnippet: "Spawn a subagent on an existing bus, then status/message/close it later.",
|
|
145
148
|
promptGuidelines: [
|
|
146
|
-
"Create a bus first; spawn
|
|
149
|
+
"Create a bus first; spawn subscribes the subagent to busId.",
|
|
147
150
|
"Attach cooperating subagents to the same bus.",
|
|
148
151
|
"Use returned run id/name for status, message, or close.",
|
|
149
152
|
],
|
|
@@ -166,7 +169,13 @@ function toSubagentInput(params: RawSubagentParams): SubagentInput {
|
|
|
166
169
|
if (!params.profile) throw new Error("subagent action=spawn requires profile.");
|
|
167
170
|
if (!params.task) throw new Error("subagent action=spawn requires task.");
|
|
168
171
|
if (!params.busId) throw new Error("subagent action=spawn requires busId.");
|
|
169
|
-
return {
|
|
172
|
+
return {
|
|
173
|
+
action: "spawn",
|
|
174
|
+
profile: toAgentProfile(params.profile),
|
|
175
|
+
task: params.task,
|
|
176
|
+
busId: params.busId,
|
|
177
|
+
name: params.name,
|
|
178
|
+
};
|
|
170
179
|
}
|
|
171
180
|
|
|
172
181
|
if (params.action === "status") {
|
|
@@ -192,6 +201,16 @@ function withDefaultModel(input: SubagentInput, ctx: ExtensionContext): Subagent
|
|
|
192
201
|
};
|
|
193
202
|
}
|
|
194
203
|
|
|
204
|
+
export function toAgentProfile(profile: RawAgentProfileParams): AgentProfile {
|
|
205
|
+
if (!Array.isArray(profile.tools)) throw new Error(`Profile "${profile.name}" requires tools.`);
|
|
206
|
+
return {
|
|
207
|
+
name: profile.name,
|
|
208
|
+
systemPrompt: profile.systemPrompt,
|
|
209
|
+
tools: profile.tools,
|
|
210
|
+
model: profile.model,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
195
214
|
export function withDefaultProfileModel(profile: AgentProfile, ctx: ExtensionContext): AgentProfile {
|
|
196
215
|
if (profile.model || !ctx.model) return profile;
|
|
197
216
|
return {
|
|
@@ -232,7 +251,7 @@ function formatModelId(model: AgentProfileModel): string {
|
|
|
232
251
|
|
|
233
252
|
type RawSubagentParams = {
|
|
234
253
|
action: "spawn" | "status" | "message" | "close";
|
|
235
|
-
profile?:
|
|
254
|
+
profile?: RawAgentProfileParams;
|
|
236
255
|
task?: string;
|
|
237
256
|
busId?: string;
|
|
238
257
|
name?: string;
|
|
@@ -240,4 +259,6 @@ type RawSubagentParams = {
|
|
|
240
259
|
message?: string;
|
|
241
260
|
};
|
|
242
261
|
|
|
262
|
+
export type RawAgentProfileParams = Omit<AgentProfile, "model"> & Partial<Pick<AgentProfile, "model">>;
|
|
263
|
+
|
|
243
264
|
type AgentProfileModel = NonNullable<ExtensionContext["model"]>;
|
package/src/tools/workflow.ts
CHANGED
|
@@ -5,13 +5,13 @@ import type { OrchestraApi } from "../core/orchestra.ts";
|
|
|
5
5
|
import type { AgentStore } from "../core/store.ts";
|
|
6
6
|
import type { WorkflowRun, WorkflowStageOutput, WorkflowStageRun, WorkflowStageSpec } from "../core/workflow.ts";
|
|
7
7
|
import { WORKGROUP_STRATEGY_VALUES, type WorkgroupMember, type WorkgroupStrategy } from "../core/workgroup.ts";
|
|
8
|
-
import { createStageLeaderProfile } from "../profiles/stage-leader.ts";
|
|
9
8
|
import {
|
|
10
9
|
closeAgentRuns,
|
|
11
10
|
createEntityIdentity,
|
|
12
11
|
formatError,
|
|
13
12
|
findWorkflow,
|
|
14
13
|
formatNamedEntityLabel,
|
|
14
|
+
isAgentRunFinished,
|
|
15
15
|
isTerminalAgentState,
|
|
16
16
|
normalizeEntityName,
|
|
17
17
|
requireWorkflow,
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
export type WorkflowInput =
|
|
30
30
|
| {
|
|
31
31
|
action: "start";
|
|
32
|
-
name
|
|
32
|
+
name: string | undefined;
|
|
33
33
|
goal: string;
|
|
34
34
|
stages: WorkflowStageSpec[];
|
|
35
35
|
}
|
|
@@ -58,8 +58,8 @@ export interface WorkflowToolDeps {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
export interface WorkflowPiToolOptions {
|
|
61
|
-
onWorkflowInput
|
|
62
|
-
onWorkflowOutput
|
|
61
|
+
onWorkflowInput: ((ctx: ExtensionContext, input: WorkflowInput) => void) | undefined;
|
|
62
|
+
onWorkflowOutput: ((ctx: ExtensionContext, output: WorkflowOutput) => void) | undefined;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
const WorkflowStageParams = Type.Object(
|
|
@@ -78,7 +78,7 @@ const WorkflowStageParams = Type.Object(
|
|
|
78
78
|
description: "Worker subagents for this stage.",
|
|
79
79
|
minItems: 1,
|
|
80
80
|
}),
|
|
81
|
-
leader:
|
|
81
|
+
leader: WorkgroupMemberParams,
|
|
82
82
|
},
|
|
83
83
|
{ additionalProperties: false },
|
|
84
84
|
);
|
|
@@ -108,7 +108,8 @@ const WorkflowToolParams = Type.Object(
|
|
|
108
108
|
),
|
|
109
109
|
stages: Type.Optional(
|
|
110
110
|
Type.Array(WorkflowStageParams, {
|
|
111
|
-
description:
|
|
111
|
+
description:
|
|
112
|
+
"Required for action=start. Linear stages executed in order; each stage specifies an explicit leader.",
|
|
112
113
|
minItems: 1,
|
|
113
114
|
}),
|
|
114
115
|
),
|
|
@@ -117,7 +118,12 @@ const WorkflowToolParams = Type.Object(
|
|
|
117
118
|
);
|
|
118
119
|
|
|
119
120
|
export function createWorkflowTool({ orchestra, store }: WorkflowToolDeps): WorkflowTool {
|
|
120
|
-
const workgroupTool = createWorkgroupTool({
|
|
121
|
+
const workgroupTool = createWorkgroupTool({
|
|
122
|
+
orchestra,
|
|
123
|
+
onWorkgroupLaunching: undefined,
|
|
124
|
+
onWorkgroupLaunched: undefined,
|
|
125
|
+
onWorkgroupLaunchFailed: undefined,
|
|
126
|
+
});
|
|
121
127
|
const runnerTasks = new Map<string, Promise<void>>();
|
|
122
128
|
|
|
123
129
|
const startRunner = (workflowId: string) => {
|
|
@@ -152,16 +158,17 @@ export function createWorkflowTool({ orchestra, store }: WorkflowToolDeps): Work
|
|
|
152
158
|
|
|
153
159
|
export function defineWorkflowPiTool(
|
|
154
160
|
resolveTool: (ctx: ExtensionContext) => WorkflowTool,
|
|
155
|
-
options: WorkflowPiToolOptions = {},
|
|
161
|
+
options: WorkflowPiToolOptions = { onWorkflowInput: undefined, onWorkflowOutput: undefined },
|
|
156
162
|
) {
|
|
157
163
|
return defineTool({
|
|
158
164
|
name: "workflow",
|
|
159
165
|
label: "Workflow",
|
|
160
|
-
description: "Run linear workgroup stages with
|
|
166
|
+
description: "Run linear workgroup stages, each with an explicit leader that synthesizes its workers' output.",
|
|
161
167
|
promptSnippet: "Launch a multi-stage workflow; completion is delivered automatically as a pi-orchestra event.",
|
|
162
168
|
promptGuidelines: [
|
|
163
169
|
"Use workflow for ordered multi-stage work; not branching/DAG plans.",
|
|
164
|
-
"Each stage gets its own bus and
|
|
170
|
+
"Each stage gets its own bus and requires an explicit leader that synthesizes its workers' output; previous stage outputs feed the next stage.",
|
|
171
|
+
"Prefer the evidence-synthesizer profile for stage leaders unless the stage needs a specialized synthesizer; give the leader an explicit tool allowlist (often the union of its workers' tools).",
|
|
165
172
|
"Use compete when one worker success is enough; use synthesize when findings must be combined.",
|
|
166
173
|
"Use workflow status for progress; workflow.finished events deliver terminal success/blocked/failed/closed results.",
|
|
167
174
|
],
|
|
@@ -195,7 +202,7 @@ async function runWorkflow(workflowId: string, deps: WorkflowRunnerDeps): Promis
|
|
|
195
202
|
|
|
196
203
|
const stageIndex = workflow.stages.findIndex((stage) => stage.state === "idle" && !stage.phase);
|
|
197
204
|
if (stageIndex < 0) {
|
|
198
|
-
if (workflow.state === "
|
|
205
|
+
if (workflow.state === "running")
|
|
199
206
|
deps.store.saveWorkflow({
|
|
200
207
|
...workflow,
|
|
201
208
|
state: "success",
|
|
@@ -216,7 +223,12 @@ async function runStage(workflowId: string, stageIndex: number, deps: WorkflowRu
|
|
|
216
223
|
const workflow = requireWorkflow(deps.store, workflowId);
|
|
217
224
|
const stage = workflow.stages[stageIndex];
|
|
218
225
|
const bus = deps.orchestra.createBus({ name: `${workflow.name}-${stage.name}` });
|
|
219
|
-
updateStage(deps.store, workflow, stageIndex, {
|
|
226
|
+
updateStage(deps.store, workflow, stageIndex, {
|
|
227
|
+
state: "running",
|
|
228
|
+
phase: "workers",
|
|
229
|
+
startedAtMs: Date.now(),
|
|
230
|
+
busId: bus.id,
|
|
231
|
+
});
|
|
220
232
|
|
|
221
233
|
const workgroupOutput = await deps.workgroupTool.execute({
|
|
222
234
|
busId: bus.id,
|
|
@@ -251,10 +263,10 @@ async function runStage(workflowId: string, stageIndex: number, deps: WorkflowRu
|
|
|
251
263
|
return;
|
|
252
264
|
}
|
|
253
265
|
|
|
254
|
-
await
|
|
266
|
+
await runEvidenceSynthesizer(workflowId, stageIndex, bus.id, settledWorkgroup.workerResults, deps);
|
|
255
267
|
}
|
|
256
268
|
|
|
257
|
-
async function
|
|
269
|
+
async function runEvidenceSynthesizer(
|
|
258
270
|
workflowId: string,
|
|
259
271
|
stageIndex: number,
|
|
260
272
|
busId: string,
|
|
@@ -270,12 +282,12 @@ async function runStageLeader(
|
|
|
270
282
|
{ name: stage.leader.name },
|
|
271
283
|
);
|
|
272
284
|
if (isWorkflowClosed(deps.store, workflowId)) {
|
|
273
|
-
await deps.orchestra.closeAgent(leaderRun.id);
|
|
285
|
+
await deps.orchestra.closeAgent(leaderRun.id, { busId: undefined });
|
|
274
286
|
return;
|
|
275
287
|
}
|
|
276
288
|
|
|
277
289
|
updateStage(deps.store, requireWorkflow(deps.store, workflowId), stageIndex, {
|
|
278
|
-
state: "
|
|
290
|
+
state: "running",
|
|
279
291
|
phase: "leader",
|
|
280
292
|
leaderRunId: leaderRun.id,
|
|
281
293
|
});
|
|
@@ -309,21 +321,20 @@ function createWorkflowRun(
|
|
|
309
321
|
): WorkflowRun {
|
|
310
322
|
validateStages(input.stages);
|
|
311
323
|
const identity = createEntityIdentity(input.name, "workflow", existingWorkflows, "Workflow");
|
|
324
|
+
const startedAtMs = Date.now();
|
|
312
325
|
return {
|
|
313
326
|
...identity,
|
|
314
327
|
goal: input.goal,
|
|
315
|
-
startedAtMs
|
|
316
|
-
state: "
|
|
328
|
+
startedAtMs,
|
|
329
|
+
state: "running",
|
|
317
330
|
currentStageIndex: 0,
|
|
318
|
-
stages: input.stages.map((stage) => {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
return { ...stageRun, leader: resolveStageLeader(stage, identity.name) };
|
|
326
|
-
}),
|
|
331
|
+
stages: input.stages.map((stage) => ({
|
|
332
|
+
...stage,
|
|
333
|
+
name: normalizeEntityName(stage.name, "Workflow stage"),
|
|
334
|
+
state: "idle" as const,
|
|
335
|
+
startedAtMs,
|
|
336
|
+
workerRunIds: [],
|
|
337
|
+
})),
|
|
327
338
|
};
|
|
328
339
|
}
|
|
329
340
|
|
|
@@ -339,20 +350,6 @@ function validateStages(stages: WorkflowStageSpec[]): void {
|
|
|
339
350
|
}
|
|
340
351
|
}
|
|
341
352
|
|
|
342
|
-
function resolveStageLeader(stage: WorkflowStageSpec, workflowName: string): WorkgroupMember {
|
|
343
|
-
const stageName = normalizeEntityName(stage.name, "Workflow stage");
|
|
344
|
-
const leader = stage.leader ?? {
|
|
345
|
-
profile: createStageLeaderProfile({ name: `${workflowName}-${stageName}-leader` }),
|
|
346
|
-
};
|
|
347
|
-
return {
|
|
348
|
-
...leader,
|
|
349
|
-
profile: {
|
|
350
|
-
...leader.profile,
|
|
351
|
-
tools: [],
|
|
352
|
-
},
|
|
353
|
-
};
|
|
354
|
-
}
|
|
355
|
-
|
|
356
353
|
function toWorkflowInput(params: RawWorkflowParams): WorkflowInput {
|
|
357
354
|
if (params.action === "start") {
|
|
358
355
|
if (!params.goal) throw new Error("workflow action=start requires goal.");
|
|
@@ -369,15 +366,15 @@ function toWorkflowStageSpec(stage: RawWorkflowStageParams): WorkflowStageSpec {
|
|
|
369
366
|
if (!stage.goal) throw new Error(`workflow stage ${stage.name} requires goal.`);
|
|
370
367
|
if (!stage.strategy) throw new Error(`workflow stage ${stage.name} requires strategy.`);
|
|
371
368
|
if (!stage.members || stage.members.length === 0) throw new Error(`workflow stage ${stage.name} requires members.`);
|
|
369
|
+
if (!stage.leader) throw new Error(`workflow stage ${stage.name} requires a leader.`);
|
|
372
370
|
|
|
373
|
-
|
|
371
|
+
return {
|
|
374
372
|
name: stage.name,
|
|
375
373
|
goal: stage.goal,
|
|
376
374
|
strategy: stage.strategy,
|
|
377
375
|
members: stage.members.map((member, index) => toWorkgroupMember(member, `workflow member ${index + 1}`)),
|
|
376
|
+
leader: toWorkgroupMember(stage.leader, "workflow leader"),
|
|
378
377
|
};
|
|
379
|
-
if (stage.leader) spec.leader = toWorkgroupMember(stage.leader, "workflow leader");
|
|
380
|
-
return spec;
|
|
381
378
|
}
|
|
382
379
|
|
|
383
380
|
function withDefaultModels(input: WorkflowInput, ctx: ExtensionContext): WorkflowInput {
|
|
@@ -387,7 +384,7 @@ function withDefaultModels(input: WorkflowInput, ctx: ExtensionContext): Workflo
|
|
|
387
384
|
stages: input.stages.map((stage) => ({
|
|
388
385
|
...stage,
|
|
389
386
|
members: withDefaultModelsForWorkgroupMembers(stage.members, ctx),
|
|
390
|
-
leader:
|
|
387
|
+
leader: withDefaultModelForWorkgroupMember(stage.leader, ctx),
|
|
391
388
|
})),
|
|
392
389
|
};
|
|
393
390
|
}
|
|
@@ -478,7 +475,7 @@ function buildLeaderTask(workflow: WorkflowRun, stageIndex: number, workerResult
|
|
|
478
475
|
return [
|
|
479
476
|
"You are the leader for this workflow stage.",
|
|
480
477
|
...strategyInstructions,
|
|
481
|
-
"
|
|
478
|
+
"Treat supplied context as primary; prefer finish results over bus context.",
|
|
482
479
|
"",
|
|
483
480
|
"Workflow goal:",
|
|
484
481
|
workflow.goal,
|
|
@@ -572,7 +569,7 @@ function buildStageOutput(
|
|
|
572
569
|
if (!leaderRun.result) {
|
|
573
570
|
return {
|
|
574
571
|
status: "failed",
|
|
575
|
-
summary: `
|
|
572
|
+
summary: `Evidence synthesizer ${leaderRunId} reached ${leaderRun.state} without a result payload.`,
|
|
576
573
|
leaderRunId,
|
|
577
574
|
workerResults: workerResults.map(toWorkflowRunResult),
|
|
578
575
|
};
|
|
@@ -606,13 +603,13 @@ function formatStageOutput(output: WorkflowStageOutput): string {
|
|
|
606
603
|
|
|
607
604
|
function terminalRunEvent(store: AgentStore, runId: string): Promise<AgentRun> {
|
|
608
605
|
const initialRun = store.getRun(runId);
|
|
609
|
-
if (initialRun &&
|
|
606
|
+
if (initialRun && isAgentRunFinished(initialRun)) return Promise.resolve(initialRun);
|
|
610
607
|
|
|
611
608
|
return new Promise((resolve) => {
|
|
612
609
|
let unsubscribe: () => void = () => undefined;
|
|
613
610
|
unsubscribe = store.subscribeRuns(
|
|
614
611
|
(run) => {
|
|
615
|
-
if (!
|
|
612
|
+
if (!isAgentRunFinished(run)) return;
|
|
616
613
|
|
|
617
614
|
unsubscribe();
|
|
618
615
|
resolve(run);
|
|
@@ -632,7 +629,7 @@ function formatWorkflowMessage(
|
|
|
632
629
|
): string {
|
|
633
630
|
const parts = [headline, "", `Goal: ${workflow.goal}`, "", "Stages:"];
|
|
634
631
|
for (const [index, stage] of workflow.stages.entries()) {
|
|
635
|
-
const current = index === workflow.currentStageIndex && workflow.state === "
|
|
632
|
+
const current = index === workflow.currentStageIndex && workflow.state === "running" ? " current" : "";
|
|
636
633
|
parts.push(`- ${stage.name}: ${stage.state}${current}${stage.busId ? ` bus=${stage.busId}` : ""}`);
|
|
637
634
|
}
|
|
638
635
|
if (workflow.result) parts.push("", "Result:", formatStageOutput(workflow.result));
|