feat(robot): implement global phase agent resolution for improved agent configuration
- Introduced a new global phase agent resolver to streamline agent ID retrieval for various robot pipeline phases, enhancing flexibility in agent configuration. - Updated existing phase agent retrieval logic to prioritize per-robot configurations, falling back to global settings when necessary. - Enhanced error handling to provide clearer messages when no agent is configured for specific phases. - Added tests to validate the new resolution logic and ensure proper functionality across different configurations.
This commit is contained in:
parent
09af247a7c
commit
c4696380f4
13 changed files with 211 additions and 48 deletions
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/yaoapp/yao/agent/assistant"
|
"github.com/yaoapp/yao/agent/assistant"
|
||||||
"github.com/yaoapp/yao/agent/context"
|
"github.com/yaoapp/yao/agent/context"
|
||||||
"github.com/yaoapp/yao/agent/i18n"
|
"github.com/yaoapp/yao/agent/i18n"
|
||||||
|
robottypes "github.com/yaoapp/yao/agent/robot/types"
|
||||||
searchDefaults "github.com/yaoapp/yao/agent/search/defaults"
|
searchDefaults "github.com/yaoapp/yao/agent/search/defaults"
|
||||||
searchTypes "github.com/yaoapp/yao/agent/search/types"
|
searchTypes "github.com/yaoapp/yao/agent/search/types"
|
||||||
storeMongo "github.com/yaoapp/yao/agent/store/mongo"
|
storeMongo "github.com/yaoapp/yao/agent/store/mongo"
|
||||||
|
|
@ -72,6 +73,15 @@ func Load(cfg config.Config) error {
|
||||||
|
|
||||||
agentDSL = &setting
|
agentDSL = &setting
|
||||||
|
|
||||||
|
// Register global phase agent resolver for robot pipeline.
|
||||||
|
// Robot executor falls back to this when no per-robot override is configured.
|
||||||
|
robottypes.GlobalPhaseAgentResolver = func(phase robottypes.Phase) string {
|
||||||
|
if agentDSL == nil || agentDSL.Uses == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return agentDSL.Uses.GetPhaseAgent(string(phase))
|
||||||
|
}
|
||||||
|
|
||||||
// Store Setting
|
// Store Setting
|
||||||
err = initStore()
|
err = initStore()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -477,6 +487,13 @@ func resolveEnvStrings(setting *types.DSL) {
|
||||||
setting.Uses.Keyword = helper.EnvString(setting.Uses.Keyword)
|
setting.Uses.Keyword = helper.EnvString(setting.Uses.Keyword)
|
||||||
setting.Uses.QueryDSL = helper.EnvString(setting.Uses.QueryDSL)
|
setting.Uses.QueryDSL = helper.EnvString(setting.Uses.QueryDSL)
|
||||||
setting.Uses.Rerank = helper.EnvString(setting.Uses.Rerank)
|
setting.Uses.Rerank = helper.EnvString(setting.Uses.Rerank)
|
||||||
|
setting.Uses.Inspiration = helper.EnvString(setting.Uses.Inspiration)
|
||||||
|
setting.Uses.Goals = helper.EnvString(setting.Uses.Goals)
|
||||||
|
setting.Uses.Tasks = helper.EnvString(setting.Uses.Tasks)
|
||||||
|
setting.Uses.Delivery = helper.EnvString(setting.Uses.Delivery)
|
||||||
|
setting.Uses.Learning = helper.EnvString(setting.Uses.Learning)
|
||||||
|
setting.Uses.Host = helper.EnvString(setting.Uses.Host)
|
||||||
|
setting.Uses.Validation = helper.EnvString(setting.Uses.Validation)
|
||||||
}
|
}
|
||||||
|
|
||||||
setting.Cache = helper.EnvString(setting.Cache)
|
setting.Cache = helper.EnvString(setting.Cache)
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,10 @@ func (e *Executor) RunDelivery(ctx *robottypes.Context, exec *robottypes.Executi
|
||||||
locale := getEffectiveLocale(robot, exec.Input)
|
locale := getEffectiveLocale(robot, exec.Input)
|
||||||
e.updateUIFields(ctx, exec, "", getLocalizedMessage(locale, "generating_delivery"))
|
e.updateUIFields(ctx, exec, "", getLocalizedMessage(locale, "generating_delivery"))
|
||||||
|
|
||||||
agentID := "__yao.delivery"
|
// Get agent ID for delivery phase (per-robot config > global Uses > empty)
|
||||||
if robot.Config != nil && robot.Config.Resources != nil {
|
agentID := robottypes.ResolvePhaseAgent(robot.Config, robottypes.PhaseDelivery)
|
||||||
agentID = robot.Config.Resources.GetPhaseAgent(robottypes.PhaseDelivery)
|
if agentID == "" {
|
||||||
|
return fmt.Errorf("no Delivery Agent configured (set uses.delivery in agent.yml or resources.phases in robot config)")
|
||||||
}
|
}
|
||||||
|
|
||||||
formatter := NewInputFormatter()
|
formatter := NewInputFormatter()
|
||||||
|
|
|
||||||
|
|
@ -32,10 +32,10 @@ func (e *Executor) RunGoals(ctx *robottypes.Context, exec *robottypes.Execution,
|
||||||
locale := getEffectiveLocale(robot, exec.Input)
|
locale := getEffectiveLocale(robot, exec.Input)
|
||||||
e.updateUIFields(ctx, exec, "", getLocalizedMessage(locale, "planning_goals"))
|
e.updateUIFields(ctx, exec, "", getLocalizedMessage(locale, "planning_goals"))
|
||||||
|
|
||||||
// Get agent ID for goals phase
|
// Get agent ID for goals phase (per-robot config > global Uses > empty)
|
||||||
agentID := "__yao.goals" // default
|
agentID := robottypes.ResolvePhaseAgent(robot.Config, robottypes.PhaseGoals)
|
||||||
if robot.Config != nil && robot.Config.Resources != nil {
|
if agentID == "" {
|
||||||
agentID = robot.Config.Resources.GetPhaseAgent(robottypes.PhaseGoals)
|
return fmt.Errorf("no Goals Agent configured (set uses.goals in agent.yml or resources.phases in robot config)")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build prompt based on trigger type
|
// Build prompt based on trigger type
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,10 @@ func (e *Executor) CallHostAgent(ctx *robottypes.Context, robot *robottypes.Robo
|
||||||
return nil, fmt.Errorf("robot cannot be nil")
|
return nil, fmt.Errorf("robot cannot be nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
agentID := ""
|
// Get agent ID for host phase (per-robot config > global Uses > empty)
|
||||||
if robot.Config != nil && robot.Config.Resources != nil {
|
agentID := robottypes.ResolvePhaseAgent(robot.Config, robottypes.PhaseHost)
|
||||||
agentID = robot.Config.Resources.GetPhaseAgent(robottypes.PhaseHost)
|
|
||||||
}
|
|
||||||
if agentID == "" {
|
if agentID == "" {
|
||||||
return nil, fmt.Errorf("no Host Agent configured for robot %s", robot.MemberID)
|
return nil, fmt.Errorf("no Host Agent configured for robot %s (set uses.host in agent.yml or resources.phases in robot config)", robot.MemberID)
|
||||||
}
|
}
|
||||||
|
|
||||||
inputJSON, err := json.Marshal(input)
|
inputJSON, err := json.Marshal(input)
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,11 @@ func TestCallHostAgent_NilRobot(t *testing.T) {
|
||||||
|
|
||||||
// H2: no Host Agent configured
|
// H2: no Host Agent configured
|
||||||
func TestCallHostAgent_NoHostAgent(t *testing.T) {
|
func TestCallHostAgent_NoHostAgent(t *testing.T) {
|
||||||
|
// Temporarily clear the global resolver so no fallback is available
|
||||||
|
orig := robottypes.GlobalPhaseAgentResolver
|
||||||
|
robottypes.GlobalPhaseAgentResolver = nil
|
||||||
|
defer func() { robottypes.GlobalPhaseAgentResolver = orig }()
|
||||||
|
|
||||||
e := standard.New()
|
e := standard.New()
|
||||||
ctx := robottypes.NewContext(context.Background(), nil)
|
ctx := robottypes.NewContext(context.Background(), nil)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,10 +35,10 @@ func (e *Executor) RunInspiration(ctx *robottypes.Context, exec *robottypes.Exec
|
||||||
clock = robottypes.NewClockContext(time.Now(), "")
|
clock = robottypes.NewClockContext(time.Now(), "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get agent ID for inspiration phase
|
// Get agent ID for inspiration phase (per-robot config > global Uses > empty)
|
||||||
agentID := "__yao.inspiration" // default
|
agentID := robottypes.ResolvePhaseAgent(robot.Config, robottypes.PhaseInspiration)
|
||||||
if robot.Config != nil && robot.Config.Resources != nil {
|
if agentID == "" {
|
||||||
agentID = robot.Config.Resources.GetPhaseAgent(robottypes.PhaseInspiration)
|
return fmt.Errorf("no Inspiration Agent configured (set uses.inspiration in agent.yml or resources.phases in robot config)")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build prompt using InputFormatter
|
// Build prompt using InputFormatter
|
||||||
|
|
|
||||||
|
|
@ -215,13 +215,13 @@ func TestRunInspirationWithDefaultAgent(t *testing.T) {
|
||||||
|
|
||||||
ctx := types.NewContext(context.Background(), testAuth())
|
ctx := types.NewContext(context.Background(), testAuth())
|
||||||
|
|
||||||
t.Run("uses default agent when not configured", func(t *testing.T) {
|
t.Run("uses global Uses config when per-robot resources not set", func(t *testing.T) {
|
||||||
robot := &types.Robot{
|
robot := &types.Robot{
|
||||||
MemberID: "test-robot-1",
|
MemberID: "test-robot-1",
|
||||||
TeamID: "test-team-1",
|
TeamID: "test-team-1",
|
||||||
Config: &types.Config{
|
Config: &types.Config{
|
||||||
Identity: &types.Identity{Role: "Test Robot"},
|
Identity: &types.Identity{Role: "Test Robot"},
|
||||||
// No Resources configured - should use default __yao.inspiration
|
// No Resources configured — falls back to global uses.inspiration
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
exec := createTestExecution(robot, types.TriggerClock)
|
exec := createTestExecution(robot, types.TriggerClock)
|
||||||
|
|
@ -229,9 +229,8 @@ func TestRunInspirationWithDefaultAgent(t *testing.T) {
|
||||||
e := standard.New()
|
e := standard.New()
|
||||||
err := e.RunInspiration(ctx, exec, nil)
|
err := e.RunInspiration(ctx, exec, nil)
|
||||||
|
|
||||||
// This will fail if __yao.inspiration doesn't exist
|
// With global Uses configured (agent.yml: uses.inspiration = "robot.inspiration"),
|
||||||
// In test environment, we expect it to fail with "agent not found"
|
// the call should succeed. Without global config, it would error.
|
||||||
// In production, it would use the default agent
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
assert.Contains(t, err.Error(), "call failed")
|
assert.Contains(t, err.Error(), "call failed")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,10 +37,10 @@ func (e *Executor) RunTasks(ctx *robottypes.Context, exec *robottypes.Execution,
|
||||||
return fmt.Errorf("goals not available for task planning")
|
return fmt.Errorf("goals not available for task planning")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get agent ID for tasks phase
|
// Get agent ID for tasks phase (per-robot config > global Uses > empty)
|
||||||
agentID := "__yao.tasks" // default
|
agentID := robottypes.ResolvePhaseAgent(robot.Config, robottypes.PhaseTasks)
|
||||||
if robot.Config != nil && robot.Config.Resources != nil {
|
if agentID == "" {
|
||||||
agentID = robot.Config.Resources.GetPhaseAgent(robottypes.PhaseTasks)
|
return fmt.Errorf("no Tasks Agent configured (set uses.tasks in agent.yml or resources.phases in robot config)")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build prompt with goals and available resources
|
// Build prompt with goals and available resources
|
||||||
|
|
|
||||||
|
|
@ -418,11 +418,13 @@ func (v *Validator) hasAgentRules(rules []string) bool {
|
||||||
|
|
||||||
// validateSemantic performs semantic validation using the Validation Agent
|
// validateSemantic performs semantic validation using the Validation Agent
|
||||||
func (v *Validator) validateSemantic(task *robottypes.Task, output interface{}) *robottypes.ValidationResult {
|
func (v *Validator) validateSemantic(task *robottypes.Task, output interface{}) *robottypes.ValidationResult {
|
||||||
// Get validation agent ID
|
// Get validation agent ID (per-robot config > global Uses > empty)
|
||||||
validationAgentID := "__yao.validation" // default
|
validationAgentID := robottypes.ResolvePhaseAgent(v.robot.Config, "validation")
|
||||||
if v.robot.Config != nil && v.robot.Config.Resources != nil {
|
if validationAgentID == "" {
|
||||||
if customID, ok := v.robot.Config.Resources.Phases["validation"]; ok && customID != "" {
|
return &robottypes.ValidationResult{
|
||||||
validationAgentID = customID
|
Passed: false,
|
||||||
|
Score: 0,
|
||||||
|
Issues: []string{"no Validation Agent configured (set uses.validation in agent.yml or resources.phases in robot config)"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -258,14 +258,47 @@ type Resources struct {
|
||||||
MCP []MCPConfig `json:"mcp,omitempty"`
|
MCP []MCPConfig `json:"mcp,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPhaseAgent returns agent ID for phase (default: __yao.{phase})
|
// GlobalPhaseAgentResolver is called by GetPhaseAgent when no per-robot override
|
||||||
|
// is configured. Set by the agent package at init time to read from Uses config.
|
||||||
|
// Returns empty string if the phase has no global default.
|
||||||
|
var GlobalPhaseAgentResolver func(phase Phase) string
|
||||||
|
|
||||||
|
// GetPhaseAgent returns agent ID for a pipeline phase.
|
||||||
|
// Priority: per-robot Resources.Phases > global Uses config > empty string.
|
||||||
func (r *Resources) GetPhaseAgent(phase Phase) string {
|
func (r *Resources) GetPhaseAgent(phase Phase) string {
|
||||||
if r != nil && r.Phases != nil {
|
if r != nil && r.Phases != nil {
|
||||||
if id, ok := r.Phases[phase]; ok && id != "" {
|
if id, ok := r.Phases[phase]; ok && id != "" {
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "__yao." + string(phase)
|
if GlobalPhaseAgentResolver != nil {
|
||||||
|
return GlobalPhaseAgentResolver(phase)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolvePhaseAgent resolves the agent ID for a phase from robot config.
|
||||||
|
// It delegates to Resources.GetPhaseAgent which handles the full priority chain:
|
||||||
|
// per-robot Resources.Phases > GlobalPhaseAgentResolver (Uses config) > empty.
|
||||||
|
// The phase parameter accepts both Phase type and raw string (e.g. "validation").
|
||||||
|
func ResolvePhaseAgent(config *Config, phase interface{}) string {
|
||||||
|
var p Phase
|
||||||
|
switch v := phase.(type) {
|
||||||
|
case Phase:
|
||||||
|
p = v
|
||||||
|
case string:
|
||||||
|
p = Phase(v)
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if config != nil && config.Resources != nil {
|
||||||
|
return config.Resources.GetPhaseAgent(p)
|
||||||
|
}
|
||||||
|
if GlobalPhaseAgentResolver != nil {
|
||||||
|
return GlobalPhaseAgentResolver(p)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// MCPConfig - MCP server configuration
|
// MCPConfig - MCP server configuration
|
||||||
|
|
|
||||||
|
|
@ -216,18 +216,26 @@ func TestQuotaDefaults(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResourcesGetPhaseAgent(t *testing.T) {
|
func TestResourcesGetPhaseAgent(t *testing.T) {
|
||||||
t.Run("nil resources - returns default", func(t *testing.T) {
|
t.Run("nil resources without global resolver - returns empty", func(t *testing.T) {
|
||||||
|
orig := types.GlobalPhaseAgentResolver
|
||||||
|
types.GlobalPhaseAgentResolver = nil
|
||||||
|
defer func() { types.GlobalPhaseAgentResolver = orig }()
|
||||||
|
|
||||||
var resources *types.Resources
|
var resources *types.Resources
|
||||||
agent := resources.GetPhaseAgent(types.PhaseGoals)
|
agent := resources.GetPhaseAgent(types.PhaseGoals)
|
||||||
assert.Equal(t, "__yao.goals", agent)
|
assert.Equal(t, "", agent)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("phase not configured - returns default", func(t *testing.T) {
|
t.Run("phase not configured without global resolver - returns empty", func(t *testing.T) {
|
||||||
|
orig := types.GlobalPhaseAgentResolver
|
||||||
|
types.GlobalPhaseAgentResolver = nil
|
||||||
|
defer func() { types.GlobalPhaseAgentResolver = orig }()
|
||||||
|
|
||||||
resources := &types.Resources{
|
resources := &types.Resources{
|
||||||
Phases: map[types.Phase]string{},
|
Phases: map[types.Phase]string{},
|
||||||
}
|
}
|
||||||
agent := resources.GetPhaseAgent(types.PhaseGoals)
|
agent := resources.GetPhaseAgent(types.PhaseGoals)
|
||||||
assert.Equal(t, "__yao.goals", agent)
|
assert.Equal(t, "", agent)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("custom phase agent", func(t *testing.T) {
|
t.Run("custom phase agent", func(t *testing.T) {
|
||||||
|
|
@ -240,14 +248,80 @@ func TestResourcesGetPhaseAgent(t *testing.T) {
|
||||||
assert.Equal(t, "custom.goals.agent", agent)
|
assert.Equal(t, "custom.goals.agent", agent)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("all phases default names", func(t *testing.T) {
|
t.Run("global resolver fallback", func(t *testing.T) {
|
||||||
|
orig := types.GlobalPhaseAgentResolver
|
||||||
|
types.GlobalPhaseAgentResolver = func(phase types.Phase) string {
|
||||||
|
return "global." + string(phase)
|
||||||
|
}
|
||||||
|
defer func() { types.GlobalPhaseAgentResolver = orig }()
|
||||||
|
|
||||||
resources := &types.Resources{}
|
resources := &types.Resources{}
|
||||||
assert.Equal(t, "__yao.inspiration", resources.GetPhaseAgent(types.PhaseInspiration))
|
assert.Equal(t, "global.inspiration", resources.GetPhaseAgent(types.PhaseInspiration))
|
||||||
assert.Equal(t, "__yao.goals", resources.GetPhaseAgent(types.PhaseGoals))
|
assert.Equal(t, "global.goals", resources.GetPhaseAgent(types.PhaseGoals))
|
||||||
assert.Equal(t, "__yao.tasks", resources.GetPhaseAgent(types.PhaseTasks))
|
assert.Equal(t, "global.tasks", resources.GetPhaseAgent(types.PhaseTasks))
|
||||||
assert.Equal(t, "__yao.run", resources.GetPhaseAgent(types.PhaseRun))
|
assert.Equal(t, "global.run", resources.GetPhaseAgent(types.PhaseRun))
|
||||||
assert.Equal(t, "__yao.delivery", resources.GetPhaseAgent(types.PhaseDelivery))
|
assert.Equal(t, "global.delivery", resources.GetPhaseAgent(types.PhaseDelivery))
|
||||||
assert.Equal(t, "__yao.learning", resources.GetPhaseAgent(types.PhaseLearning))
|
assert.Equal(t, "global.learning", resources.GetPhaseAgent(types.PhaseLearning))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("per-robot override takes precedence over global resolver", func(t *testing.T) {
|
||||||
|
orig := types.GlobalPhaseAgentResolver
|
||||||
|
types.GlobalPhaseAgentResolver = func(phase types.Phase) string {
|
||||||
|
return "global." + string(phase)
|
||||||
|
}
|
||||||
|
defer func() { types.GlobalPhaseAgentResolver = orig }()
|
||||||
|
|
||||||
|
resources := &types.Resources{
|
||||||
|
Phases: map[types.Phase]string{
|
||||||
|
types.PhaseGoals: "my-app.goals",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Equal(t, "my-app.goals", resources.GetPhaseAgent(types.PhaseGoals))
|
||||||
|
assert.Equal(t, "global.inspiration", resources.GetPhaseAgent(types.PhaseInspiration))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolvePhaseAgent(t *testing.T) {
|
||||||
|
t.Run("nil config without global resolver", func(t *testing.T) {
|
||||||
|
orig := types.GlobalPhaseAgentResolver
|
||||||
|
types.GlobalPhaseAgentResolver = nil
|
||||||
|
defer func() { types.GlobalPhaseAgentResolver = orig }()
|
||||||
|
|
||||||
|
assert.Equal(t, "", types.ResolvePhaseAgent(nil, types.PhaseGoals))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("nil config with global resolver", func(t *testing.T) {
|
||||||
|
orig := types.GlobalPhaseAgentResolver
|
||||||
|
types.GlobalPhaseAgentResolver = func(phase types.Phase) string {
|
||||||
|
return "global." + string(phase)
|
||||||
|
}
|
||||||
|
defer func() { types.GlobalPhaseAgentResolver = orig }()
|
||||||
|
|
||||||
|
assert.Equal(t, "global.goals", types.ResolvePhaseAgent(nil, types.PhaseGoals))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("config with resources override", func(t *testing.T) {
|
||||||
|
config := &types.Config{
|
||||||
|
Resources: &types.Resources{
|
||||||
|
Phases: map[types.Phase]string{
|
||||||
|
types.PhaseDelivery: "app.delivery",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Equal(t, "app.delivery", types.ResolvePhaseAgent(config, types.PhaseDelivery))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("string phase argument", func(t *testing.T) {
|
||||||
|
orig := types.GlobalPhaseAgentResolver
|
||||||
|
types.GlobalPhaseAgentResolver = func(phase types.Phase) string {
|
||||||
|
if phase == "validation" {
|
||||||
|
return "app.validation"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
defer func() { types.GlobalPhaseAgentResolver = orig }()
|
||||||
|
|
||||||
|
assert.Equal(t, "app.validation", types.ResolvePhaseAgent(nil, "validation"))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,42 @@ type Uses struct {
|
||||||
Keyword string `json:"keyword,omitempty" yaml:"keyword,omitempty"` // Keyword extraction: "builtin", "<assistant-id>", "mcp:<server>.<tool>"
|
Keyword string `json:"keyword,omitempty" yaml:"keyword,omitempty"` // Keyword extraction: "builtin", "<assistant-id>", "mcp:<server>.<tool>"
|
||||||
QueryDSL string `json:"querydsl,omitempty" yaml:"querydsl,omitempty"` // QueryDSL generation: "builtin", "<assistant-id>", "mcp:<server>.<tool>"
|
QueryDSL string `json:"querydsl,omitempty" yaml:"querydsl,omitempty"` // QueryDSL generation: "builtin", "<assistant-id>", "mcp:<server>.<tool>"
|
||||||
Rerank string `json:"rerank,omitempty" yaml:"rerank,omitempty"` // Result reranking: "builtin", "<assistant-id>", "mcp:<server>.<tool>"
|
Rerank string `json:"rerank,omitempty" yaml:"rerank,omitempty"` // Result reranking: "builtin", "<assistant-id>", "mcp:<server>.<tool>"
|
||||||
|
|
||||||
|
// Robot pipeline phase agents (application-level, not bundled as system agents)
|
||||||
|
// Empty means no default — must be configured per-robot via resources.phases or here globally.
|
||||||
|
Inspiration string `json:"inspiration,omitempty" yaml:"inspiration,omitempty"` // P0: Inspiration phase agent
|
||||||
|
Goals string `json:"goals,omitempty" yaml:"goals,omitempty"` // P1: Goals planning agent
|
||||||
|
Tasks string `json:"tasks,omitempty" yaml:"tasks,omitempty"` // P2: Task breakdown agent
|
||||||
|
Delivery string `json:"delivery,omitempty" yaml:"delivery,omitempty"` // P4: Delivery composition agent
|
||||||
|
Learning string `json:"learning,omitempty" yaml:"learning,omitempty"` // P5: Learning extraction agent
|
||||||
|
Host string `json:"host,omitempty" yaml:"host,omitempty"` // Host: Human interaction agent
|
||||||
|
Validation string `json:"validation,omitempty" yaml:"validation,omitempty"` // Validation: Task output validation agent
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPhaseAgent returns the globally configured agent ID for a robot pipeline phase.
|
||||||
|
// Returns empty string if no global default is set for the phase.
|
||||||
|
func (u *Uses) GetPhaseAgent(phase string) string {
|
||||||
|
if u == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
switch phase {
|
||||||
|
case "inspiration":
|
||||||
|
return u.Inspiration
|
||||||
|
case "goals":
|
||||||
|
return u.Goals
|
||||||
|
case "tasks":
|
||||||
|
return u.Tasks
|
||||||
|
case "delivery":
|
||||||
|
return u.Delivery
|
||||||
|
case "learning":
|
||||||
|
return u.Learning
|
||||||
|
case "host":
|
||||||
|
return u.Host
|
||||||
|
case "validation":
|
||||||
|
return u.Validation
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// System configures connectors for system agents
|
// System configures connectors for system agents
|
||||||
|
|
|
||||||
|
|
@ -28,11 +28,9 @@ func resolveHostAssistantID(ctx context.Context, memberID string) (string, *robo
|
||||||
return "", nil, fmt.Errorf("failed to parse robot config: %w", err)
|
return "", nil, fmt.Errorf("failed to parse robot config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var hostID string
|
hostID := robottypes.ResolvePhaseAgent(config, robottypes.PhaseHost)
|
||||||
if config != nil && config.Resources != nil {
|
if hostID == "" {
|
||||||
hostID = config.Resources.GetPhaseAgent(robottypes.PhaseHost)
|
return "", nil, fmt.Errorf("no Host Agent configured for robot %s (set uses.host in agent.yml or resources.phases in robot config)", memberID)
|
||||||
} else {
|
|
||||||
hostID = "__yao." + string(robottypes.PhaseHost)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return hostID, record, nil
|
return hostID, record, nil
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue