Skip to content

Commit 0edd693

Browse files
committed
Update editor-multi-prompt to use latest patterns
1 parent 1381736 commit 0edd693

File tree

2 files changed

+142
-80
lines changed

2 files changed

+142
-80
lines changed

.agents/editor/best-of-n/editor-implementor.ts

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,25 +22,24 @@ export const createBestOfNImplementor = (options: {
2222
: 'openai/gpt-5.1',
2323
displayName: 'Implementation Generator',
2424
spawnerPrompt:
25-
'Generates a complete implementation plan with all code changes',
25+
'Generates a complete implementation using propose_* tools that draft changes without applying them',
2626

2727
includeMessageHistory: true,
2828
inheritParentSystemPrompt: true,
2929

30-
toolNames: [],
30+
toolNames: ['propose_write_file', 'propose_str_replace'],
3131
spawnableAgents: [],
3232

3333
inputSchema: {},
34-
outputMode: 'last_message',
34+
outputMode: 'structured_output',
3535

3636
instructionsPrompt: `You are an expert code editor with deep understanding of software engineering principles. You were spawned to generate an implementation for the user's request.
3737
38-
Your task is to write out ALL the code changes needed to complete the user's request in a single comprehensive response.
38+
Your task is to write out ALL the code changes needed to complete the user's request.
3939
40-
Important: You can not make any other tool calls besides editing files. You cannot read more files, write todos, spawn agents, or set output. Do not call any of these tools!
41-
42-
Write out what changes you would make using the tool call format below. Use this exact format for each file change:
40+
IMPORTANT: Use propose_str_replace and propose_write_file tools to make your edits. These tools draft changes without actually applying them - they will be reviewed first. DO NOT use any other tools. Do not spawn any agents, read files, or set output.
4341
42+
You can make multiple tool calls across multiple steps to complete the implementation. Only the file changes will be passed on, so you can say whatever you want to help you think. Do not write any final summary as that would be a waste of tokens because no one is reading it.
4443
<codebuff_tool_call>
4544
{
4645
"cb_tool_name": "str_replace",
@@ -116,15 +115,64 @@ More style notes:
116115
- Optional arguments are code smell and worse than required arguments.
117116
- New components often should be added to a new file, not added to an existing file.
118117
119-
Write out your complete implementation now, formatting all changes as tool calls as shown above.`,
120-
121-
handleSteps: function* () {
122-
yield 'STEP'
118+
Write out your complete implementation now. Do not write any final summary.`,
119+
120+
handleSteps: function* ({ agentState: initialAgentState }) {
121+
const initialMessageHistoryLength =
122+
initialAgentState.messageHistory.length
123+
124+
const { agentState } = yield 'STEP_ALL'
125+
126+
const postMessages = agentState.messageHistory.slice(
127+
initialMessageHistoryLength,
128+
)
129+
130+
// Extract tool calls from assistant messages
131+
const toolCalls: { toolName: string; input: any }[] = []
132+
for (const message of postMessages) {
133+
if (message.role !== 'assistant' || !Array.isArray(message.content))
134+
continue
135+
for (const part of message.content) {
136+
if (part.type === 'tool-call') {
137+
toolCalls.push({
138+
toolName: part.toolName,
139+
input: part.input ?? (part as any).args ?? {},
140+
})
141+
}
142+
}
143+
}
144+
145+
// Extract tool results (unified diffs) from tool messages
146+
const toolResults: any[] = []
147+
for (const message of postMessages) {
148+
if (message.role !== 'tool' || !Array.isArray(message.content)) continue
149+
for (const part of message.content) {
150+
if (part.type === 'json' && part.value) {
151+
toolResults.push(part.value)
152+
}
153+
}
154+
}
155+
156+
// Concatenate all unified diffs for the selector to review
157+
const unifiedDiffs = toolResults
158+
.filter((result: any) => result.unifiedDiff)
159+
.map((result: any) => `--- ${result.file} ---\n${result.unifiedDiff}`)
160+
.join('\n\n')
161+
162+
yield {
163+
toolName: 'set_output',
164+
input: {
165+
toolCalls,
166+
toolResults,
167+
unifiedDiffs,
168+
},
169+
includeToolCall: false,
170+
}
123171
},
124172
}
125173
}
126174
const definition = {
127-
...createBestOfNImplementor({ model: 'sonnet' }),
175+
...createBestOfNImplementor({ model: 'opus' }),
128176
id: 'editor-implementor',
129177
}
130178
export default definition

.agents/editor/best-of-n/editor-multi-prompt.ts

Lines changed: 82 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,20 @@
11
import { publisher } from '../../constants'
22

3-
import type {
4-
AgentStepContext,
5-
StepText,
6-
ToolCall,
7-
} from '../../types/agent-definition'
3+
import type { AgentStepContext, ToolCall } from '../../types/agent-definition'
84
import type { SecretAgentDefinition } from '../../types/secret-agent-definition'
95

106
/**
117
* Creates a multi-prompt editor agent that spawns one implementor per prompt.
128
* Each prompt specifies a slightly different implementation strategy/approach.
9+
* Uses propose_* tools to draft changes, then applies the chosen implementation.
1310
*/
1411
export function createMultiPromptEditor(): Omit<SecretAgentDefinition, 'id'> {
1512
return {
1613
publisher,
1714
model: 'anthropic/claude-opus-4.5',
1815
displayName: 'Multi-Prompt Editor',
1916
spawnerPrompt:
20-
'Edits code by spawning multiple implementor agents with different strategy prompts, selects the best implementation, and applies the changes. Pass an array of short prompts specifying different implementation approaches. Make sure to read any files intended to be edited before spawning this agent.',
17+
'Edits code by spawning multiple implementor agents with different strategy prompts, selects the best implementation, and applies the changes. It also returns further suggested improvements which you should take seriously and act on. Pass as input an array of short prompts specifying different implementation approaches or strategies. Make sure to read any files intended to be edited before spawning this agent.',
2118

2219
includeMessageHistory: true,
2320
inheritParentSystemPrompt: true,
@@ -30,7 +27,7 @@ export function createMultiPromptEditor(): Omit<SecretAgentDefinition, 'id'> {
3027
'set_output',
3128
],
3229
spawnableAgents: [
33-
'best-of-n-selector-opus',
30+
'best-of-n-selector2',
3431
'editor-implementor-opus',
3532
'editor-implementor-gpt-5',
3633
],
@@ -58,7 +55,6 @@ export function createMultiPromptEditor(): Omit<SecretAgentDefinition, 'id'> {
5855
function* handleStepsMultiPrompt({
5956
agentState,
6057
params,
61-
logger,
6258
}: AgentStepContext): ReturnType<
6359
NonNullable<SecretAgentDefinition['handleSteps']>
6460
> {
@@ -111,77 +107,114 @@ function* handleStepsMultiPrompt({
111107
includeToolCall: false,
112108
} satisfies ToolCall<'spawn_agents'>
113109

114-
// Extract spawn results
115-
const spawnedImplementations = extractSpawnResults(
116-
implementorResults,
117-
) as any[]
110+
// Extract spawn results - each is structured output with { toolCalls, toolResults, unifiedDiffs }
111+
const spawnedImplementations = extractSpawnResults<{
112+
toolCalls: { toolName: string; input: any }[]
113+
toolResults: any[]
114+
unifiedDiffs: string
115+
}>(implementorResults)
118116

119-
// Extract all the implementations from the results
117+
// Build implementations for selector using the unified diffs
120118
const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
121-
const strategies = [...prompts, prompts[0]]
122-
const implementations = spawnedImplementations.map((result, index) => ({
123-
id: letters[index],
124-
strategy: strategies[index],
125-
content:
126-
'errorMessage' in result
127-
? `Error: ${result.errorMessage}`
128-
: extractLastMessageText(result) ?? '',
129-
}))
130-
131-
// Spawn selector with implementations as params
132-
const { toolResult: selectorResult, agentState: selectorAgentState } = yield {
119+
const implementations = spawnedImplementations.map((result, index) => {
120+
if (!result || (typeof result === 'object' && 'errorMessage' in result)) {
121+
return {
122+
id: letters[index],
123+
strategy: prompts[index] ?? 'unknown',
124+
content: `Error: ${(result as any)?.errorMessage ?? 'Unknown error'}`,
125+
toolCalls: [] as { toolName: string; input: any }[],
126+
}
127+
}
128+
129+
return {
130+
id: letters[index],
131+
strategy: prompts[index] ?? 'unknown',
132+
content: result.unifiedDiffs || 'No changes proposed',
133+
toolCalls: result.toolCalls ?? [],
134+
}
135+
})
136+
137+
// Spawn selector with implementations (showing unified diffs for review)
138+
const { toolResult: selectorResult } = yield {
133139
toolName: 'spawn_agents',
134140
input: {
135141
agents: [
136142
{
137-
agent_type: 'best-of-n-selector-opus',
138-
params: { implementations },
143+
agent_type: 'best-of-n-selector2',
144+
params: {
145+
implementations: implementations.map((impl) => ({
146+
id: impl.id,
147+
strategy: impl.strategy,
148+
content: impl.content,
149+
})),
150+
},
139151
},
140152
],
141153
},
142154
includeToolCall: false,
143155
} satisfies ToolCall<'spawn_agents'>
144156

145157
const selectorOutput = extractSpawnResults<{
146-
value: {
147-
implementationId: string
148-
reasoning: string
149-
}
158+
implementationId: string
159+
reason: string
160+
suggestedImprovements: string
150161
}>(selectorResult)[0]
151162

152-
if ('errorMessage' in selectorOutput) {
163+
if (!selectorOutput || !('implementationId' in selectorOutput)) {
153164
yield {
154165
toolName: 'set_output',
155-
input: { error: selectorOutput.errorMessage },
166+
input: { error: 'Selector failed to return an implementation' },
156167
} satisfies ToolCall<'set_output'>
157168
return
158169
}
159-
const { implementationId } = selectorOutput.value
170+
171+
const { implementationId } = selectorOutput
160172
const chosenImplementation = implementations.find(
161173
(implementation) => implementation.id === implementationId,
162174
)
175+
163176
if (!chosenImplementation) {
164177
yield {
165178
toolName: 'set_output',
166-
input: { error: 'Failed to find chosen implementation.' },
179+
input: {
180+
error: `Failed to find chosen implementation: ${implementationId}`,
181+
},
167182
} satisfies ToolCall<'set_output'>
168183
return
169184
}
170185

171-
const numMessagesBeforeStepText = selectorAgentState.messageHistory.length
186+
// Apply the chosen implementation's tool calls as real edits
187+
const appliedToolResults: any[] = []
188+
for (const toolCall of chosenImplementation.toolCalls) {
189+
// Convert propose_* tool calls to real edit tool calls
190+
const realToolName =
191+
toolCall.toolName === 'propose_str_replace'
192+
? 'str_replace'
193+
: toolCall.toolName === 'propose_write_file'
194+
? 'write_file'
195+
: toolCall.toolName
196+
197+
if (realToolName === 'str_replace' || realToolName === 'write_file') {
198+
const { toolResult } = yield {
199+
toolName: realToolName,
200+
input: toolCall.input,
201+
includeToolCall: true,
202+
} satisfies ToolCall<'str_replace'> | ToolCall<'write_file'>
203+
204+
appliedToolResults.push(toolResult)
205+
}
206+
}
172207

173-
const { agentState: postEditsAgentState } = yield {
174-
type: 'STEP_TEXT',
175-
text: chosenImplementation.content,
176-
} as StepText
177-
const { messageHistory } = postEditsAgentState
208+
// Extract suggested improvements from selector output
209+
const { suggestedImprovements } = selectorOutput
178210

179-
// Set output with the messages from running the step text of the chosen implementation
211+
// Set output with the applied results and suggested improvements
180212
yield {
181213
toolName: 'set_output',
182214
input: {
183215
chosenStrategy: chosenImplementation.strategy,
184-
messages: messageHistory.slice(numMessagesBeforeStepText),
216+
toolResults: appliedToolResults,
217+
suggestedImprovements,
185218
},
186219
includeToolCall: false,
187220
} satisfies ToolCall<'set_output'>
@@ -199,31 +232,12 @@ function* handleStepsMultiPrompt({
199232
? jsonResult.value
200233
: [jsonResult.value]
201234

202-
return spawnedResults.map((result: any) => result?.value).filter(Boolean)
203-
}
204-
205-
/**
206-
* Extracts the text content from a 'lastMessage' AgentOutput.
207-
*/
208-
function extractLastMessageText(agentOutput: any): string | null {
209-
if (!agentOutput) return null
210-
211-
if (
212-
agentOutput.type === 'lastMessage' &&
213-
Array.isArray(agentOutput.value)
214-
) {
215-
for (let i = agentOutput.value.length - 1; i >= 0; i--) {
216-
const message = agentOutput.value[i]
217-
if (message.role === 'assistant' && Array.isArray(message.content)) {
218-
for (const part of message.content) {
219-
if (part.type === 'text' && typeof part.text === 'string') {
220-
return part.text
221-
}
222-
}
223-
}
224-
}
225-
}
226-
return null
235+
return spawnedResults
236+
.map((result: any) => result?.value)
237+
.map((result: any) =>
238+
result && 'value' in result ? result.value : result,
239+
)
240+
.filter(Boolean)
227241
}
228242
}
229243

0 commit comments

Comments
 (0)