Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
72 changes: 60 additions & 12 deletions .agents/editor/best-of-n/editor-implementor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,24 @@ export const createBestOfNImplementor = (options: {
: 'openai/gpt-5.1',
displayName: 'Implementation Generator',
spawnerPrompt:
'Generates a complete implementation plan with all code changes',
'Generates a complete implementation using propose_* tools that draft changes without applying them',

includeMessageHistory: true,
inheritParentSystemPrompt: true,

toolNames: [],
toolNames: ['propose_write_file', 'propose_str_replace'],
spawnableAgents: [],

inputSchema: {},
outputMode: 'last_message',
outputMode: 'structured_output',

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.

Your task is to write out ALL the code changes needed to complete the user's request in a single comprehensive response.
Your task is to write out ALL the code changes needed to complete the user's request.

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!

Write out what changes you would make using the tool call format below. Use this exact format for each file change:
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.

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.
<codebuff_tool_call>
{
"cb_tool_name": "str_replace",
Expand Down Expand Up @@ -116,15 +115,64 @@ More style notes:
- Optional arguments are code smell and worse than required arguments.
- New components often should be added to a new file, not added to an existing file.

Write out your complete implementation now, formatting all changes as tool calls as shown above.`,

handleSteps: function* () {
yield 'STEP'
Write out your complete implementation now. Do not write any final summary.`,

handleSteps: function* ({ agentState: initialAgentState }) {
const initialMessageHistoryLength =
initialAgentState.messageHistory.length

const { agentState } = yield 'STEP_ALL'

const postMessages = agentState.messageHistory.slice(
initialMessageHistoryLength,
)

// Extract tool calls from assistant messages
const toolCalls: { toolName: string; input: any }[] = []
for (const message of postMessages) {
if (message.role !== 'assistant' || !Array.isArray(message.content))
continue
for (const part of message.content) {
if (part.type === 'tool-call') {
toolCalls.push({
toolName: part.toolName,
input: part.input ?? (part as any).args ?? {},
})
}
}
}

// Extract tool results (unified diffs) from tool messages
const toolResults: any[] = []
for (const message of postMessages) {
if (message.role !== 'tool' || !Array.isArray(message.content)) continue
for (const part of message.content) {
if (part.type === 'json' && part.value) {
toolResults.push(part.value)
}
}
}

// Concatenate all unified diffs for the selector to review
const unifiedDiffs = toolResults
.filter((result: any) => result.unifiedDiff)
.map((result: any) => `--- ${result.file} ---\n${result.unifiedDiff}`)
.join('\n\n')

yield {
toolName: 'set_output',
input: {
toolCalls,
toolResults,
unifiedDiffs,
},
includeToolCall: false,
}
},
}
}
const definition = {
...createBestOfNImplementor({ model: 'sonnet' }),
...createBestOfNImplementor({ model: 'opus' }),
id: 'editor-implementor',
}
export default definition
150 changes: 82 additions & 68 deletions .agents/editor/best-of-n/editor-multi-prompt.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
import { publisher } from '../../constants'

import type {
AgentStepContext,
StepText,
ToolCall,
} from '../../types/agent-definition'
import type { AgentStepContext, ToolCall } from '../../types/agent-definition'
import type { SecretAgentDefinition } from '../../types/secret-agent-definition'

/**
* Creates a multi-prompt editor agent that spawns one implementor per prompt.
* Each prompt specifies a slightly different implementation strategy/approach.
* Uses propose_* tools to draft changes, then applies the chosen implementation.
*/
export function createMultiPromptEditor(): Omit<SecretAgentDefinition, 'id'> {
return {
publisher,
model: 'anthropic/claude-opus-4.5',
displayName: 'Multi-Prompt Editor',
spawnerPrompt:
'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.',
'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.',

includeMessageHistory: true,
inheritParentSystemPrompt: true,
Expand All @@ -30,7 +27,7 @@ export function createMultiPromptEditor(): Omit<SecretAgentDefinition, 'id'> {
'set_output',
],
spawnableAgents: [
'best-of-n-selector-opus',
'best-of-n-selector2',
'editor-implementor-opus',
'editor-implementor-gpt-5',
],
Expand Down Expand Up @@ -58,7 +55,6 @@ export function createMultiPromptEditor(): Omit<SecretAgentDefinition, 'id'> {
function* handleStepsMultiPrompt({
agentState,
params,
logger,
}: AgentStepContext): ReturnType<
NonNullable<SecretAgentDefinition['handleSteps']>
> {
Expand Down Expand Up @@ -111,77 +107,114 @@ function* handleStepsMultiPrompt({
includeToolCall: false,
} satisfies ToolCall<'spawn_agents'>

// Extract spawn results
const spawnedImplementations = extractSpawnResults(
implementorResults,
) as any[]
// Extract spawn results - each is structured output with { toolCalls, toolResults, unifiedDiffs }
const spawnedImplementations = extractSpawnResults<{
toolCalls: { toolName: string; input: any }[]
toolResults: any[]
unifiedDiffs: string
}>(implementorResults)

// Extract all the implementations from the results
// Build implementations for selector using the unified diffs
const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
const strategies = [...prompts, prompts[0]]
const implementations = spawnedImplementations.map((result, index) => ({
id: letters[index],
strategy: strategies[index],
content:
'errorMessage' in result
? `Error: ${result.errorMessage}`
: extractLastMessageText(result) ?? '',
}))

// Spawn selector with implementations as params
const { toolResult: selectorResult, agentState: selectorAgentState } = yield {
const implementations = spawnedImplementations.map((result, index) => {
if (!result || (typeof result === 'object' && 'errorMessage' in result)) {
return {
id: letters[index],
strategy: prompts[index] ?? 'unknown',
content: `Error: ${(result as any)?.errorMessage ?? 'Unknown error'}`,
toolCalls: [] as { toolName: string; input: any }[],
}
}

return {
id: letters[index],
strategy: prompts[index] ?? 'unknown',
content: result.unifiedDiffs || 'No changes proposed',
toolCalls: result.toolCalls ?? [],
}
})

// Spawn selector with implementations (showing unified diffs for review)
const { toolResult: selectorResult } = yield {
toolName: 'spawn_agents',
input: {
agents: [
{
agent_type: 'best-of-n-selector-opus',
params: { implementations },
agent_type: 'best-of-n-selector2',
params: {
implementations: implementations.map((impl) => ({
id: impl.id,
strategy: impl.strategy,
content: impl.content,
})),
},
},
],
},
includeToolCall: false,
} satisfies ToolCall<'spawn_agents'>

const selectorOutput = extractSpawnResults<{
value: {
implementationId: string
reasoning: string
}
implementationId: string
reason: string
suggestedImprovements: string
}>(selectorResult)[0]

if ('errorMessage' in selectorOutput) {
if (!selectorOutput || !('implementationId' in selectorOutput)) {
yield {
toolName: 'set_output',
input: { error: selectorOutput.errorMessage },
input: { error: 'Selector failed to return an implementation' },
} satisfies ToolCall<'set_output'>
return
}
const { implementationId } = selectorOutput.value

const { implementationId } = selectorOutput
const chosenImplementation = implementations.find(
(implementation) => implementation.id === implementationId,
)

if (!chosenImplementation) {
yield {
toolName: 'set_output',
input: { error: 'Failed to find chosen implementation.' },
input: {
error: `Failed to find chosen implementation: ${implementationId}`,
},
} satisfies ToolCall<'set_output'>
return
}

const numMessagesBeforeStepText = selectorAgentState.messageHistory.length
// Apply the chosen implementation's tool calls as real edits
const appliedToolResults: any[] = []
for (const toolCall of chosenImplementation.toolCalls) {
// Convert propose_* tool calls to real edit tool calls
const realToolName =
toolCall.toolName === 'propose_str_replace'
? 'str_replace'
: toolCall.toolName === 'propose_write_file'
? 'write_file'
: toolCall.toolName

if (realToolName === 'str_replace' || realToolName === 'write_file') {
const { toolResult } = yield {
toolName: realToolName,
input: toolCall.input,
includeToolCall: true,
} satisfies ToolCall<'str_replace'> | ToolCall<'write_file'>

appliedToolResults.push(toolResult)
}
}

const { agentState: postEditsAgentState } = yield {
type: 'STEP_TEXT',
text: chosenImplementation.content,
} as StepText
const { messageHistory } = postEditsAgentState
// Extract suggested improvements from selector output
const { suggestedImprovements } = selectorOutput

// Set output with the messages from running the step text of the chosen implementation
// Set output with the applied results and suggested improvements
yield {
toolName: 'set_output',
input: {
chosenStrategy: chosenImplementation.strategy,
messages: messageHistory.slice(numMessagesBeforeStepText),
toolResults: appliedToolResults,
suggestedImprovements,
},
includeToolCall: false,
} satisfies ToolCall<'set_output'>
Expand All @@ -199,31 +232,12 @@ function* handleStepsMultiPrompt({
? jsonResult.value
: [jsonResult.value]

return spawnedResults.map((result: any) => result?.value).filter(Boolean)
}

/**
* Extracts the text content from a 'lastMessage' AgentOutput.
*/
function extractLastMessageText(agentOutput: any): string | null {
if (!agentOutput) return null

if (
agentOutput.type === 'lastMessage' &&
Array.isArray(agentOutput.value)
) {
for (let i = agentOutput.value.length - 1; i >= 0; i--) {
const message = agentOutput.value[i]
if (message.role === 'assistant' && Array.isArray(message.content)) {
for (const part of message.content) {
if (part.type === 'text' && typeof part.text === 'string') {
return part.text
}
}
}
}
}
return null
return spawnedResults
.map((result: any) => result?.value)
.map((result: any) =>
result && 'value' in result ? result.value : result,
)
.filter(Boolean)
}
}

Expand Down
29 changes: 2 additions & 27 deletions .agents/editor/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,44 +103,19 @@ More style notes:

Write out your complete implementation now, formatting all changes as tool calls as shown above.`,

handleSteps: function* ({ agentState: initialAgentState }) {
handleSteps: function* ({ agentState: initialAgentState, logger }) {
const initialMessageHistoryLength =
initialAgentState.messageHistory.length
const { agentState } = yield 'STEP'
const { messageHistory } = agentState

const newMessages = messageHistory.slice(initialMessageHistoryLength)
const assistantText = newMessages
.filter((message) => message.role === 'assistant')
.flatMap((message) => message.content)
.filter((content) => content.type === 'text')
.map((content) => content.text)
.join('\n')

// Extract tool calls from the assistant text
const toolCallsText = extractToolCallsOnly(assistantText)

const { agentState: postAssistantTextAgentState } = yield {
type: 'STEP_TEXT',
text: toolCallsText,
} as StepText

const postAssistantTextMessageHistory =
postAssistantTextAgentState.messageHistory.slice(
initialMessageHistoryLength,
)
const toolResults = postAssistantTextMessageHistory
.filter((message) => message.role === 'tool')
.flatMap((message) => message.content)
.filter((content) => content.type === 'json')
.map((content) => content.value)

yield {
toolName: 'set_output',
input: {
output: {
message: toolCallsText,
toolResults,
messages: newMessages,
},
},
includeToolCall: false,
Expand Down
Loading