Clouade did up to this point
This commit is contained in:
150
orchestrator.go
Normal file
150
orchestrator.go
Normal file
@@ -0,0 +1,150 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const managerSystemPrompt = `You are the Manager agent. You coordinate between the user, a Programmer agent, and a QA agent.
|
||||
|
||||
You have two orchestration tools:
|
||||
- assign_task(instructions): delegate coding work to the Programmer. Returns the Programmer's report.
|
||||
- request_qa_review(focus): ask QA to validate specific files/changes. Returns QA's report.
|
||||
|
||||
Workflow:
|
||||
1. Read the user's request. If it is purely conversational or meta (a greeting, a question about the agent itself), answer directly with no tool calls.
|
||||
2. Otherwise, plan the work, then call assign_task with clear, self-contained instructions for the Programmer.
|
||||
3. When the Programmer reports back, call request_qa_review naming the files touched and what QA should verify.
|
||||
4. If QA reports issues, call assign_task again with specific fix instructions that reference QA's findings.
|
||||
5. Iterate until QA approves, then reply to the user with a concise summary of what changed and any caveats.
|
||||
|
||||
Important:
|
||||
- You have NO file tools. Do not try to read or edit files yourself.
|
||||
- Every call to assign_task/request_qa_review spawns a fresh sub-agent with no memory of prior calls — put all needed context into the brief.
|
||||
- Keep the final user-facing reply brief: what was done, where, and anything flagged by QA.`
|
||||
|
||||
func managerTools() []Tool {
|
||||
return []Tool{
|
||||
{
|
||||
Type: "function",
|
||||
Function: ToolFunction{
|
||||
Name: "assign_task",
|
||||
Description: "Delegate a coding task to the Programmer agent. The Programmer has read/create/edit/delete/list file tools scoped to the project root. Returns the Programmer's written report.",
|
||||
Parameters: map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"instructions": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Clear, self-contained instructions for the Programmer. Include all context needed (files, goals, constraints).",
|
||||
},
|
||||
},
|
||||
"required": []string{"instructions"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: "function",
|
||||
Function: ToolFunction{
|
||||
Name: "request_qa_review",
|
||||
Description: "Ask the QA agent to review code. QA has read-only file tools (read_file, list_directory) plus a shell command runner (run_command) that can execute any test suite, linter, type-checker, or build command. Returns QA's written report with a verdict.",
|
||||
Parameters: map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"focus": map[string]any{
|
||||
"type": "string",
|
||||
"description": "What QA should review: specific files, what aspect of the change, what to look for. Be explicit.",
|
||||
},
|
||||
},
|
||||
"required": []string{"focus"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type Orchestrator struct {
|
||||
client *Client
|
||||
root string
|
||||
manager *Agent
|
||||
history []Message
|
||||
}
|
||||
|
||||
func NewOrchestrator(client *Client, root string) *Orchestrator {
|
||||
o := &Orchestrator{client: client, root: root}
|
||||
o.manager = &Agent{
|
||||
Name: "manager",
|
||||
Client: client,
|
||||
SystemPrompt: managerSystemPrompt,
|
||||
Tools: managerTools(),
|
||||
ToolExec: o.execManagerTool,
|
||||
OnToolCall: logToolCall(""),
|
||||
}
|
||||
o.history = []Message{{Role: "system", Content: managerSystemPrompt}}
|
||||
return o
|
||||
}
|
||||
|
||||
// Handle advances the Manager conversation with one user turn.
|
||||
func (o *Orchestrator) Handle(ctx context.Context, userInput string) (string, error) {
|
||||
o.history = append(o.history, Message{Role: "user", Content: userInput})
|
||||
reply, updated, err := o.manager.Run(ctx, o.history)
|
||||
o.history = updated
|
||||
return reply, err
|
||||
}
|
||||
|
||||
func (o *Orchestrator) execManagerTool(ctx context.Context, name, argsJSON string) string {
|
||||
switch name {
|
||||
case "assign_task":
|
||||
var a struct {
|
||||
Instructions string `json:"instructions"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(argsJSON), &a); err != nil {
|
||||
return "error: invalid arguments: " + err.Error()
|
||||
}
|
||||
if strings.TrimSpace(a.Instructions) == "" {
|
||||
return "error: instructions is required"
|
||||
}
|
||||
prog := newProgrammerAgent(o.client, o.root, logToolCall(" "))
|
||||
report, err := prog.Do(ctx, a.Instructions)
|
||||
if err != nil {
|
||||
return "error: programmer failed: " + err.Error()
|
||||
}
|
||||
printReport("programmer", report)
|
||||
return report
|
||||
|
||||
case "request_qa_review":
|
||||
var a struct {
|
||||
Focus string `json:"focus"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(argsJSON), &a); err != nil {
|
||||
return "error: invalid arguments: " + err.Error()
|
||||
}
|
||||
if strings.TrimSpace(a.Focus) == "" {
|
||||
return "error: focus is required"
|
||||
}
|
||||
qa := newQAAgent(o.client, o.root, logToolCall(" "))
|
||||
report, err := qa.Do(ctx, a.Focus)
|
||||
if err != nil {
|
||||
return "error: qa failed: " + err.Error()
|
||||
}
|
||||
printReport("qa", report)
|
||||
return report
|
||||
|
||||
default:
|
||||
return "error: unknown tool " + name
|
||||
}
|
||||
}
|
||||
|
||||
func logToolCall(indent string) func(string, ToolCall, string) {
|
||||
return func(agent string, tc ToolCall, _ string) {
|
||||
fmt.Printf("%s[%s] %s %s\n", indent, agent, tc.Function.Name, tc.Function.Arguments)
|
||||
}
|
||||
}
|
||||
|
||||
func printReport(from, report string) {
|
||||
fmt.Printf(" [%s → manager]\n", from)
|
||||
for _, line := range strings.Split(strings.TrimRight(report, "\n"), "\n") {
|
||||
fmt.Printf(" %s\n", line)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user