Skip to content

Commit 197c153

Browse files
committed
perf: update bootstrap and tighten config loading
1 parent dc69aaa commit 197c153

File tree

12 files changed

+1041
-834
lines changed

12 files changed

+1041
-834
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ require (
88
github.com/charmbracelet/bubbletea v1.3.10
99
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834
1010
github.com/muesli/reflow v0.3.0
11-
github.com/voocel/agentcore v1.6.0
11+
github.com/voocel/agentcore v1.6.1
1212
github.com/voocel/mcp-sdk-go v1.2.7
1313
gopkg.in/yaml.v3 v3.0.1
1414
)

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEV
7070
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
7171
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
7272
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
73-
github.com/voocel/agentcore v1.6.0 h1:olMgqdqyHeaIwDzNaU9l1ZcmMKC/z5tBttWk56TWMB8=
74-
github.com/voocel/agentcore v1.6.0/go.mod h1:gG3cQIuoG5extUNWrmMmRov72yrqfBassNlU66SJSKc=
73+
github.com/voocel/agentcore v1.6.1 h1:eUf+R4Wb0QrK6A/o3J0Uhkv+LXs+MmlUN1nBrXViroo=
74+
github.com/voocel/agentcore v1.6.1/go.mod h1:gG3cQIuoG5extUNWrmMmRov72yrqfBassNlU66SJSKc=
7575
github.com/voocel/litellm v1.6.5 h1:4UaZ0Br9P0+0cJvgrj/V0OBUWi8ILwfSFCDrd0GJbLk=
7676
github.com/voocel/litellm v1.6.5/go.mod h1:6MBUu3I4DHm7h72Vl+3nqLruSwYmgqMf/I9BGoordJ4=
7777
github.com/voocel/mcp-sdk-go v1.2.7 h1:R1AF8HSUjHmwWc17kjmzEJKQzYEeP3XMh3n7Kr6Yvro=
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
package bootstrap
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"io"
8+
"os"
9+
"os/exec"
10+
"path/filepath"
11+
"strings"
12+
13+
"github.com/voocel/agentcore"
14+
agentctx "github.com/voocel/agentcore/context"
15+
"github.com/voocel/codebot/internal/agent"
16+
"github.com/voocel/codebot/internal/config"
17+
"github.com/voocel/codebot/internal/storage"
18+
localtools "github.com/voocel/codebot/internal/tools"
19+
)
20+
21+
func assembleRuntime(input *resolvedInput, services *bootServices, assembly *sessionAssembly) (*Runtime, error) {
22+
taskRT := agentcore.NewTaskRuntime()
23+
taskTools := localtools.NewTaskTools(services.taskStore, taskRT, assembly.hookRunner)
24+
tools := make([]agentcore.Tool, 0, len(assembly.tools)+len(taskTools))
25+
tools = append(tools, assembly.tools...)
26+
tools = append(tools, taskTools...)
27+
baseTools := make([]agentcore.Tool, 0, len(assembly.baseTools)+len(taskTools))
28+
baseTools = append(baseTools, assembly.baseTools...)
29+
baseTools = append(baseTools, taskTools...)
30+
31+
contextEngine, summaryCompact := buildContextEngine(assembly.chatModel, assembly.settings.ContextWindow)
32+
agentCore, err := buildAgent(assembly, services, contextEngine, taskRT, tools)
33+
if err != nil {
34+
return nil, err
35+
}
36+
37+
if err := restoreAgentState(input, assembly, tools, agentCore); err != nil {
38+
return nil, err
39+
}
40+
41+
session := buildSession(input, services, assembly, contextEngine, agentCore, tools)
42+
wireSessionRuntime(input, assembly, services, session, baseTools, tools, agentCore, taskRT, contextEngine, summaryCompact)
43+
44+
return &Runtime{
45+
Cwd: input.cwd,
46+
GitBranch: detectGitBranch(input.cwd),
47+
ApprovalEngine: services.approvalEngine,
48+
TaskRuntime: taskRT,
49+
Settings: assembly.settings,
50+
Session: session,
51+
SessionStore: input.sessionStore,
52+
PluginCatalog: services.pluginCatalog,
53+
SkillCatalog: services.skillCatalog,
54+
MCPManager: services.mcpManager,
55+
MCPServers: services.mcpServers,
56+
HookRunner: assembly.hookRunner,
57+
EnvHint: input.envHint,
58+
PlanSlug: input.sessionSnapshot.PlanSlug,
59+
PlanTitle: input.sessionSnapshot.PlanTitle,
60+
PlanPhase: input.sessionSnapshot.PlanPhase,
61+
PlanPreMode: input.sessionSnapshot.PlanPreMode,
62+
PlanAllowedCommands: append([]storage.AllowedCommandEntry(nil), input.sessionSnapshot.PlanAllowedCommands...),
63+
}, nil
64+
}
65+
66+
func buildContextEngine(chatModel agentcore.ChatModel, contextWindow int) (*agentctx.ContextEngine, *agentctx.FullSummaryStrategy) {
67+
toolCompact := agentctx.NewToolResultMicrocompact(agentctx.ToolResultMicrocompactConfig{
68+
Classifier: agent.CodebotToolClassifier,
69+
KeepRecent: 5,
70+
})
71+
trimCompact := agentctx.NewLightTrim(agentctx.LightTrimConfig{})
72+
summaryCompact := agentctx.NewFullSummary(agentctx.FullSummaryConfig{
73+
Model: chatModel,
74+
})
75+
engine := agentctx.NewEngine(agentctx.EngineConfig{
76+
ContextWindow: contextWindow,
77+
Strategies: []agentctx.Strategy{
78+
toolCompact,
79+
trimCompact,
80+
summaryCompact,
81+
},
82+
})
83+
return engine, summaryCompact
84+
}
85+
86+
func buildAgent(assembly *sessionAssembly, services *bootServices, contextEngine agentcore.ContextManager, taskRT *agentcore.TaskRuntime, tools []agentcore.Tool) (*agentcore.Agent, error) {
87+
opts := []agentcore.AgentOption{
88+
agentcore.WithModel(assembly.chatModel),
89+
agentcore.WithSystemBlocks(assembly.systemBlocks),
90+
agentcore.WithTools(tools...),
91+
agentcore.WithMaxTurns(assembly.settings.MaxTurns),
92+
agentcore.WithMaxToolErrors(3),
93+
agentcore.WithMaxToolConcurrency(4),
94+
agentcore.WithContextManager(contextEngine),
95+
agentcore.WithConvertToLLM(agentctx.ContextConvertToLLM),
96+
agentcore.WithContextWindow(assembly.settings.ContextWindow),
97+
agentcore.WithContextEstimate(agentctx.ContextEstimateAdapter),
98+
agentcore.WithPermissionEngine(services.approvalEngine),
99+
agentcore.WithTaskRuntime(taskRT),
100+
}
101+
if assembly.hookMiddleware != nil {
102+
opts = append(opts, agentcore.WithMiddlewares(assembly.hookMiddleware))
103+
}
104+
return agentcore.NewAgent(opts...), nil
105+
}
106+
107+
func restoreAgentState(input *resolvedInput, assembly *sessionAssembly, tools []agentcore.Tool, ag *agentcore.Agent) error {
108+
if len(input.sessionSnapshot.Messages) > 0 {
109+
if err := ag.SetMessages(input.sessionSnapshot.Messages); err != nil {
110+
return fmt.Errorf("restore agent messages: %w", err)
111+
}
112+
agentcore.ReactivateDeferred(tools, input.sessionSnapshot.Messages)
113+
}
114+
if input.sessionSnapshot.Thinking != "" {
115+
ag.SetThinkingLevel(agentcore.ThinkingLevel(input.sessionSnapshot.Thinking))
116+
assembly.settings.ThinkingLevel = input.sessionSnapshot.Thinking
117+
} else if assembly.settings.ThinkingLevel != "" {
118+
ag.SetThinkingLevel(agentcore.ThinkingLevel(assembly.settings.ThinkingLevel))
119+
}
120+
return nil
121+
}
122+
123+
func buildSession(input *resolvedInput, services *bootServices, assembly *sessionAssembly, contextEngine agentcore.ContextManager, ag *agentcore.Agent, tools []agentcore.Tool) *agent.Session {
124+
return agent.NewSession(agent.SessionConfig{
125+
Agent: ag,
126+
ContextManager: contextEngine,
127+
Store: input.sessionStore,
128+
Manager: input.sessionManager,
129+
Registry: input.registry,
130+
Settings: assembly.settings,
131+
Cwd: input.cwd,
132+
CreateModel: input.modelFactory,
133+
ChatModel: assembly.chatModel,
134+
Tools: tools,
135+
TaskStore: services.taskStore,
136+
ContextFiles: assembly.contextFiles,
137+
Skills: services.skills,
138+
SkillCatalog: services.skillCatalog,
139+
SkillUsage: services.skillUsage,
140+
HookRunner: assembly.hookRunner,
141+
DeferredToolsPreamble: assembly.deferredToolsPreamble,
142+
Reminders: assembly.reminders,
143+
PreambleInjected: len(input.sessionSnapshot.Messages) > 0,
144+
SkillAllowsSetter: services.approvalEngine.SetSkillAllows,
145+
})
146+
}
147+
148+
func wireSessionRuntime(input *resolvedInput, assembly *sessionAssembly, services *bootServices, session *agent.Session, baseTools, tools []agentcore.Tool, ag *agentcore.Agent, taskRT *agentcore.TaskRuntime, contextEngine *agentctx.ContextEngine, summaryCompact *agentctx.FullSummaryStrategy) {
149+
summaryCompact.SetPostSummaryHooks(session.PostSummaryRecoveryHook())
150+
contextEngine.SetProjectHook(session.HandleProjectedRewrite)
151+
contextEngine.SetRecoverHook(session.HandleOverflowRewrite)
152+
153+
for _, tool := range tools {
154+
if st, ok := tool.(*localtools.SkillTool); ok {
155+
st.SetInvocationApplier(session.ApplySkillInvocation)
156+
}
157+
}
158+
159+
assembly.subagentTool.SetTaskRuntime(taskRT)
160+
assembly.subagentTool.SetNotifyFn(ag.FollowUp)
161+
162+
sessionID := input.sessionStore.Header().SessionID
163+
bgDir := filepath.Join(config.SessionsDir(input.cwd), sessionID, "bg")
164+
assembly.subagentTool.SetBgOutputFactory(func(taskID, agentName string) (io.WriteCloser, string, error) {
165+
dir := filepath.Join(bgDir, taskID)
166+
if err := os.MkdirAll(dir, 0o700); err != nil {
167+
return nil, "", err
168+
}
169+
path := filepath.Join(dir, "output.jsonl")
170+
f, err := os.Create(path)
171+
if err != nil {
172+
return nil, "", err
173+
}
174+
meta, _ := json.Marshal(map[string]string{"agent": agentName})
175+
_ = os.WriteFile(filepath.Join(dir, "meta.json"), meta, 0o600)
176+
return f, path, nil
177+
})
178+
179+
if assembly.bashTool != nil {
180+
assembly.bashTool.SetTaskRuntime(taskRT)
181+
assembly.bashTool.SetNotifyFn(ag.FollowUp)
182+
assembly.bashTool.SetBgOutputFactory(func(shellID string) (io.WriteCloser, string, error) {
183+
dir := filepath.Join(bgDir, shellID)
184+
if err := os.MkdirAll(dir, 0o700); err != nil {
185+
return nil, "", err
186+
}
187+
path := filepath.Join(dir, "output.log")
188+
f, err := os.Create(path)
189+
return f, path, err
190+
})
191+
}
192+
193+
if services.mcpManager != nil {
194+
session.SetBeforePrompt(func() {
195+
mcpTools, ok := services.mcpManager.RefreshIfDirty(context.Background())
196+
if !ok {
197+
return
198+
}
199+
all := make([]agentcore.Tool, len(baseTools), len(baseTools)+len(mcpTools))
200+
copy(all, baseTools)
201+
all = append(all, mcpTools...)
202+
session.ReplaceAllTools(all)
203+
})
204+
}
205+
206+
if assembly.hookRunner != nil {
207+
assembly.hookRunner.RunSessionStart(context.Background())
208+
}
209+
}
210+
211+
func detectGitBranch(cwd string) string {
212+
cmd := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD")
213+
cmd.Dir = cwd
214+
out, err := cmd.Output()
215+
if err != nil {
216+
return ""
217+
}
218+
return strings.TrimSpace(string(out))
219+
}

0 commit comments

Comments
 (0)