Skip to content

Commit ab805fd

Browse files
authored
Merge pull request #131 from Leeaandrob/feat/multi-agent-routing
feat: model fallback chain + multi-agent routing
2 parents e61786c + 447c17a commit ab805fd

29 files changed

+4351
-342
lines changed

pkg/agent/instance.go

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
package agent
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"strings"
7+
8+
"github.com/sipeed/picoclaw/pkg/config"
9+
"github.com/sipeed/picoclaw/pkg/providers"
10+
"github.com/sipeed/picoclaw/pkg/routing"
11+
"github.com/sipeed/picoclaw/pkg/session"
12+
"github.com/sipeed/picoclaw/pkg/tools"
13+
)
14+
15+
// AgentInstance represents a fully configured agent with its own workspace,
16+
// session manager, context builder, and tool registry.
17+
type AgentInstance struct {
18+
ID string
19+
Name string
20+
Model string
21+
Fallbacks []string
22+
Workspace string
23+
MaxIterations int
24+
ContextWindow int
25+
Provider providers.LLMProvider
26+
Sessions *session.SessionManager
27+
ContextBuilder *ContextBuilder
28+
Tools *tools.ToolRegistry
29+
Subagents *config.SubagentsConfig
30+
SkillsFilter []string
31+
Candidates []providers.FallbackCandidate
32+
}
33+
34+
// NewAgentInstance creates an agent instance from config.
35+
func NewAgentInstance(
36+
agentCfg *config.AgentConfig,
37+
defaults *config.AgentDefaults,
38+
cfg *config.Config,
39+
provider providers.LLMProvider,
40+
) *AgentInstance {
41+
workspace := resolveAgentWorkspace(agentCfg, defaults)
42+
os.MkdirAll(workspace, 0755)
43+
44+
model := resolveAgentModel(agentCfg, defaults)
45+
fallbacks := resolveAgentFallbacks(agentCfg, defaults)
46+
47+
restrict := defaults.RestrictToWorkspace
48+
toolsRegistry := tools.NewToolRegistry()
49+
toolsRegistry.Register(tools.NewReadFileTool(workspace, restrict))
50+
toolsRegistry.Register(tools.NewWriteFileTool(workspace, restrict))
51+
toolsRegistry.Register(tools.NewListDirTool(workspace, restrict))
52+
toolsRegistry.Register(tools.NewExecToolWithConfig(workspace, restrict, cfg))
53+
toolsRegistry.Register(tools.NewEditFileTool(workspace, restrict))
54+
toolsRegistry.Register(tools.NewAppendFileTool(workspace, restrict))
55+
56+
sessionsDir := filepath.Join(workspace, "sessions")
57+
sessionsManager := session.NewSessionManager(sessionsDir)
58+
59+
contextBuilder := NewContextBuilder(workspace)
60+
contextBuilder.SetToolsRegistry(toolsRegistry)
61+
62+
agentID := routing.DefaultAgentID
63+
agentName := ""
64+
var subagents *config.SubagentsConfig
65+
var skillsFilter []string
66+
67+
if agentCfg != nil {
68+
agentID = routing.NormalizeAgentID(agentCfg.ID)
69+
agentName = agentCfg.Name
70+
subagents = agentCfg.Subagents
71+
skillsFilter = agentCfg.Skills
72+
}
73+
74+
maxIter := defaults.MaxToolIterations
75+
if maxIter == 0 {
76+
maxIter = 20
77+
}
78+
79+
// Resolve fallback candidates
80+
modelCfg := providers.ModelConfig{
81+
Primary: model,
82+
Fallbacks: fallbacks,
83+
}
84+
candidates := providers.ResolveCandidates(modelCfg, defaults.Provider)
85+
86+
return &AgentInstance{
87+
ID: agentID,
88+
Name: agentName,
89+
Model: model,
90+
Fallbacks: fallbacks,
91+
Workspace: workspace,
92+
MaxIterations: maxIter,
93+
ContextWindow: defaults.MaxTokens,
94+
Provider: provider,
95+
Sessions: sessionsManager,
96+
ContextBuilder: contextBuilder,
97+
Tools: toolsRegistry,
98+
Subagents: subagents,
99+
SkillsFilter: skillsFilter,
100+
Candidates: candidates,
101+
}
102+
}
103+
104+
// resolveAgentWorkspace determines the workspace directory for an agent.
105+
func resolveAgentWorkspace(agentCfg *config.AgentConfig, defaults *config.AgentDefaults) string {
106+
if agentCfg != nil && strings.TrimSpace(agentCfg.Workspace) != "" {
107+
return expandHome(strings.TrimSpace(agentCfg.Workspace))
108+
}
109+
if agentCfg == nil || agentCfg.Default || agentCfg.ID == "" || routing.NormalizeAgentID(agentCfg.ID) == "main" {
110+
return expandHome(defaults.Workspace)
111+
}
112+
home, _ := os.UserHomeDir()
113+
id := routing.NormalizeAgentID(agentCfg.ID)
114+
return filepath.Join(home, ".picoclaw", "workspace-"+id)
115+
}
116+
117+
// resolveAgentModel resolves the primary model for an agent.
118+
func resolveAgentModel(agentCfg *config.AgentConfig, defaults *config.AgentDefaults) string {
119+
if agentCfg != nil && agentCfg.Model != nil && strings.TrimSpace(agentCfg.Model.Primary) != "" {
120+
return strings.TrimSpace(agentCfg.Model.Primary)
121+
}
122+
return defaults.Model
123+
}
124+
125+
// resolveAgentFallbacks resolves the fallback models for an agent.
126+
func resolveAgentFallbacks(agentCfg *config.AgentConfig, defaults *config.AgentDefaults) []string {
127+
if agentCfg != nil && agentCfg.Model != nil && agentCfg.Model.Fallbacks != nil {
128+
return agentCfg.Model.Fallbacks
129+
}
130+
return defaults.ModelFallbacks
131+
}
132+
133+
func expandHome(path string) string {
134+
if path == "" {
135+
return path
136+
}
137+
if path[0] == '~' {
138+
home, _ := os.UserHomeDir()
139+
if len(path) > 1 && path[1] == '/' {
140+
return home + path[1:]
141+
}
142+
return home
143+
}
144+
return path
145+
}

0 commit comments

Comments
 (0)