Skip to content

Commit bf7d299

Browse files
author
Agent User
committed
Implement 4-agent Braintrust system with structured JSON for three parallel agents + synthesizer
- Set default teamSize=4 - Add synthesizer role (agent-4) that outputs structured JSON {final, reasoning, sources} - Update roleFor/modelFor/synth detection to use agent-4 for synthesis - Enhance synthesizeDeterministic to parse JSON from synthesizer when present - Update docs and plugin config - Use subagent runtime parallelism via existing Promise.all fan-out Closes braintrust-json-agents task.
1 parent e45d895 commit bf7d299

5 files changed

Lines changed: 27 additions & 6 deletions

File tree

extensions/braintrust/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ pnpm vitest run extensions/braintrust/src/policy.test.ts extensions/braintrust/s
3333
## Default model routing
3434
- solver (`model`): `gemini-3-flash-preview`
3535
- critic (`criticModel`): `openai-codex/gpt-5.3-codex`
36-
- synthesizer (`synthModel`): `gemini-3.1-pro-preview`
3736
- researcher (`researcherModel`): `grok-4-1-fast-reasoning`
37+
- synthesizer (`synthModel`): `gemini-3.1-pro-preview` (now agent-4, outputs structured JSON with `{"final": "...", "reasoning": "...", "sources": [...]}`)
3838

3939

4040
Note: plugin id is `braintrust-plugin`; command remains `/braintrust`.

extensions/braintrust/openclaw.plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"type": "integer",
1313
"minimum": 1,
1414
"maximum": 4,
15-
"default": 3
15+
"default": 4
1616
},
1717
"strategy": {
1818
"type": "string",

extensions/braintrust/src/runtime-bridge.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { BraintrustSettings } from "./settings.js";
33
import { synthesizeDeterministic, type SynthesisOutput } from "./synth.js";
44

55
export type CandidateRunnerInput = {
6-
role: "solver" | "critic" | "researcher";
6+
role: "solver" | "critic" | "researcher" | "synthesizer";
77
model: string;
88
prompt: string;
99
timeoutSeconds: number;
@@ -33,11 +33,12 @@ export type RuntimeBridgeResult = {
3333
function roleFor(index: number): CandidateRunnerInput["role"] {
3434
if (index === 1) return "critic";
3535
if (index === 2) return "researcher";
36+
if (index === 3) return "synthesizer";
3637
return "solver";
3738
}
3839

3940
function modelFor(index: number, settings: BraintrustSettings): string {
40-
if (index === settings.teamSize - 1) return settings.synthModel;
41+
if (index === 3) return settings.synthModel;
4142
if (index === 1) return settings.criticModel;
4243
if (index === 2) return settings.researcherModel;
4344
return settings.model;
@@ -92,7 +93,9 @@ export async function runRuntimeBridge(
9293
runCandidate({
9394
role: roleFor(i),
9495
model,
95-
prompt: input.prompt,
96+
prompt: i === 3
97+
? `${input.prompt}\n\nRespond ONLY with valid JSON in this exact format (no extra text, no markdown):\n{\n "final": "the single best synthesized answer",\n "reasoning": "brief explanation of how you combined the inputs",\n "sources": ["list of agent ids used"]\n}`
98+
: input.prompt,
9699
timeoutSeconds: input.settings.timeoutSeconds,
97100
}),
98101
input.settings.timeoutSeconds,

extensions/braintrust/src/settings.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export type BraintrustSettings = {
1717

1818
export const DEFAULT_SETTINGS: BraintrustSettings = {
1919
enabled: false,
20-
teamSize: 3,
20+
teamSize: 4,
2121
strategy: "panel",
2222
model: "gemini-3-flash-preview",
2323
criticModel: "openai-codex/gpt-5.3-codex",

extensions/braintrust/src/synth.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,24 @@ export function synthesizeDeterministic(input: SynthesisInput): SynthesisOutput
2222
final: "Braintrust temporarily unavailable (no usable candidate output).",
2323
};
2424
}
25+
26+
// New structured JSON mode for 4-agent system
27+
const synthAgent = input.candidates.find((c) => c.id === "agent-4");
28+
if (synthAgent?.status === "ok" && synthAgent.text) {
29+
try {
30+
const parsed = JSON.parse(synthAgent.text.trim());
31+
if (parsed.final && typeof parsed.final === "string") {
32+
return {
33+
final: parsed.final,
34+
winnerId: "agent-4",
35+
};
36+
}
37+
} catch (e) {
38+
// fallback if not valid JSON
39+
}
40+
}
41+
42+
// fallback to shortest successful
2543
ok.sort((a, b) => {
2644
const la = (a.text ?? "").length;
2745
const lb = (b.text ?? "").length;

0 commit comments

Comments
 (0)