on main help sign in
perf: improve startup and shutdown speed (#1829)

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

commit6f646c9151a63d2c609f349847ef670420dddc4e authorCarlos Alexandro Becker <caarlos0@users.noreply.github.com> dateMon 12 Jan 2026, 10:37 (9 months ago) parenta447f55
7 files changed+57−37
internal/agent/agent.go+5 internal/agent/coordinator.go+11−6 internal/agent/tools/mcp/init.go+14−15 internal/app/app.go+7−4 internal/lsp/client.go−4 internal/shell/background.go+19−7 internal/skills/skills.go+1−1
internal/agent/agent.go+5
@@ -68,6 +68,7 @@ type SessionAgent interface {
6868 Run(context.Context, SessionAgentCall) (*fantasy.AgentResult, error)
6969 SetModels(large Model, small Model)
7070 SetTools(tools []fantasy.AgentTool)
71+ SetSystemPrompt(systemPrompt string)
7172 Cancel(sessionID string)
7273 CancelAll()
7374 IsSessionBusy(sessionID string) bool
@@ -964,6 +965,10 @@ func (a *sessionAgent) SetTools(tools []fantasy.AgentTool) {
964965 a.tools = tools
965966}
966967
968+func (a *sessionAgent) SetSystemPrompt(systemPrompt string) {
969+ a.systemPrompt = systemPrompt
970+}
971+
967972func (a *sessionAgent) Model() Model {
968973 return a.largeModel
969974}
internal/agent/coordinator.go+11−6
@@ -322,17 +322,12 @@ func (c *coordinator) buildAgent(ctx context.Context, prompt *prompt.Prompt, age
322322 return nil, err
323323 }
324324
325− systemPrompt, err := prompt.Build(ctx, large.Model.Provider(), large.Model.Model(), *c.cfg)
326− if err != nil {
327− return nil, err
328− }
329−
330325 largeProviderCfg, _ := c.cfg.Providers.Get(large.ModelCfg.Provider)
331326 result := NewSessionAgent(SessionAgentOptions{
332327 large,
333328 small,
334329 largeProviderCfg.SystemPromptPrefix,
335− systemPrompt,
330+ "",
336331 isSubAgent,
337332 c.cfg.Options.DisableAutoSummarize,
338333 c.permissions.SkipRequests(),
@@ -340,6 +335,16 @@ func (c *coordinator) buildAgent(ctx context.Context, prompt *prompt.Prompt, age
340335 c.messages,
341336 nil,
342337 })
338+
339+ c.readyWg.Go(func() error {
340+ systemPrompt, err := prompt.Build(ctx, large.Model.Provider(), large.Model.Model(), *c.cfg)
341+ if err != nil {
342+ return err
343+ }
344+ result.SetSystemPrompt(systemPrompt)
345+ return nil
346+ })
347+
343348 c.readyWg.Go(func() error {
344349 tools, err := c.buildTools(ctx, agent)
345350 if err != nil {
internal/agent/tools/mcp/init.go+14−15
@@ -107,29 +107,28 @@ func GetState(name string) (ClientInfo, bool) {
107107
108108// Close closes all MCP clients. This should be called during application shutdown.
109109func Close() error {
110− var errs []error
111110 var wg sync.WaitGroup
112− for name, session := range sessions.Seq2() {
113− wg.Go(func() {
114− done := make(chan bool, 1)
115− go func() {
111+ done := make(chan struct{}, 1)
112+ go func() {
113+ for name, session := range sessions.Seq2() {
114+ wg.Go(func() {
116115 if err := session.Close(); err != nil &&
117116 !errors.Is(err, io.EOF) &&
118117 !errors.Is(err, context.Canceled) &&
119118 err.Error() != "signal: killed" {
120− errs = append(errs, fmt.Errorf("close mcp: %s: %w", name, err))
119+ slog.Warn("Failed to shutdown MCP client", "name", name, "error", err)
121120 }
122− done <- true
123− }()
124− select {
125− case <-done:
126− case <-time.After(time.Millisecond * 250):
127− }
128− })
121+ })
122+ }
123+ wg.Wait()
124+ done <- struct{}{}
125+ }()
126+ select {
127+ case <-done:
128+ case <-time.After(5 * time.Second):
129129 }
130− wg.Wait()
131130 broker.Shutdown()
132− return errors.Join(errs...)
131+ return nil
133132}
134133
135134// Initialize initializes MCP clients based on the provided configuration.
internal/app/app.go+7−4
@@ -405,12 +405,15 @@ func (app *App) Shutdown() {
405405 })
406406
407407 // Shutdown all LSP clients.
408+ shutdownCtx, cancel := context.WithTimeout(app.globalCtx, 5*time.Second)
409+ defer cancel()
408410 for name, client := range app.LSPClients.Seq2() {
409411 wg.Go(func() {
410− shutdownCtx, cancel := context.WithTimeout(app.globalCtx, 5*time.Second)
411− defer cancel()
412− if err := client.Close(shutdownCtx); err != nil {
413− slog.Error("Failed to shutdown LSP client", "name", name, "error", err)
412+ if err := client.Close(shutdownCtx); err != nil &&
413+ !errors.Is(err, io.EOF) &&
414+ !errors.Is(err, context.Canceled) &&
415+ err.Error() != "signal: killed" {
416+ slog.Warn("Failed to shutdown LSP client", "name", name, "error", err)
414417 }
415418 })
416419 }
internal/lsp/client.go−4
@@ -153,10 +153,6 @@ func (c *Client) Initialize(ctx context.Context, workspaceDir string) (*protocol
153153
154154// Close closes the LSP client.
155155func (c *Client) Close(ctx context.Context) error {
156− // Try to close all open files first
157− ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
158− defer cancel()
159−
160156 c.CloseAllFiles(ctx)
161157
162158 // Shutdown and exit the client
internal/shell/background.go+19−7
@@ -4,6 +4,7 @@ import (
44 "bytes"
55 "context"
66 "fmt"
7+ "slices"
78 "sync"
89 "sync/atomic"
910 "time"
@@ -163,15 +164,26 @@ func (m *BackgroundShellManager) Cleanup() int {
163164
164165// KillAll terminates all background shells.
165166func (m *BackgroundShellManager) KillAll() {
166− shells := make([]*BackgroundShell, 0, m.shells.Len())
167− for shell := range m.shells.Seq() {
168− shells = append(shells, shell)
169− }
167+ shells := slices.Collect(m.shells.Seq())
170168 m.shells.Reset(map[string]*BackgroundShell{})
169+ done := make(chan struct{}, 1)
170+ go func() {
171+ var wg sync.WaitGroup
172+ for _, shell := range shells {
173+ wg.Go(func() {
174+ shell.cancel()
175+ <-shell.done
176+ })
177+ }
178+ wg.Wait()
179+ done <- struct{}{}
180+ }()
171181
172− for _, shell := range shells {
173− shell.cancel()
174− <-shell.done
182+ select {
183+ case <-done:
184+ return
185+ case <-time.After(time.Second * 5):
186+ return
175187 }
176188}
177189
internal/skills/skills.go+1−1
@@ -147,7 +147,7 @@ func Discover(paths []string) []*Skill {
147147 slog.Warn("Skill validation failed", "path", path, "error", err)
148148 return nil
149149 }
150− slog.Info("Successfully loaded skill", "name", skill.Name, "path", path)
150+ slog.Debug("Successfully loaded skill", "name", skill.Name, "path", path)
151151 mu.Lock()
152152 skills = append(skills, skill)
153153 mu.Unlock()