feat: add graceful shutdown

This commit is contained in:
Carlos Florêncio
2026-01-16 01:04:04 +00:00
parent 41bad65020
commit 34b86cfab2
6 changed files with 99 additions and 3 deletions
-1
View File
@@ -21,7 +21,6 @@ vendor/
# IDE and editor files
.idea/
.vscode/
*.swp
*.swo
*~
+22
View File
@@ -0,0 +1,22 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Kandev Backend",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${workspaceFolder}/apps/backend/cmd/kandev",
"cwd": "${workspaceFolder}/apps/backend",
"env": {
"PATH": "${workspaceFolder}/apps/backend/bin:${env:PATH}"
},
"preLaunchTask": "build-backend",
"console": "integratedTerminal",
"stopOnEntry": false,
"showLog": true,
"trace": "verbose",
"logOutput": "debugger,dap",
}
]
}
+26
View File
@@ -0,0 +1,26 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build-backend",
"type": "shell",
"command": "make",
"args": ["build-backend"],
"options": {
"cwd": "${workspaceFolder}"
},
"problemMatcher": ["$go"],
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared",
"showReuseMessage": false
},
"group": {
"kind": "build",
"isDefault": true
}
}
]
}
+7
View File
@@ -806,6 +806,13 @@ func main() {
}
if lifecycleMgr != nil {
log.Info("Stopping agents gracefully...")
stopCtx, stopCancel := context.WithTimeout(context.Background(), 20*time.Second)
if err := lifecycleMgr.StopAllAgents(stopCtx); err != nil {
log.Error("Graceful agent stop error", zap.Error(err))
}
stopCancel()
if err := lifecycleMgr.Stop(); err != nil {
log.Error("Lifecycle manager stop error", zap.Error(err))
}
@@ -161,4 +161,3 @@ func (c *Controller) ListAgentTypes(ctx context.Context, req dto.ListAgentTypesR
return resp, nil
}
@@ -4,6 +4,7 @@ package lifecycle
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
@@ -375,6 +376,32 @@ func (m *Manager) Stop() error {
return nil
}
// StopAllAgents attempts a graceful shutdown of all active agents.
func (m *Manager) StopAllAgents(ctx context.Context) error {
instanceIDs := make([]string, 0)
m.mu.RLock()
for instanceID := range m.instances {
instanceIDs = append(instanceIDs, instanceID)
}
m.mu.RUnlock()
if len(instanceIDs) == 0 {
return nil
}
var errs []error
for _, instanceID := range instanceIDs {
if err := m.StopAgent(ctx, instanceID, false); err != nil {
errs = append(errs, err)
m.logger.Warn("failed to stop agent during shutdown",
zap.String("instance_id", instanceID),
zap.Error(err))
}
}
return errors.Join(errs...)
}
// Launch launches a new agent for a task
func (m *Manager) Launch(ctx context.Context, req *LaunchRequest) (*AgentInstance, error) {
m.logger.Info("launching agent",
@@ -596,6 +623,9 @@ func (m *Manager) launchStandalone(ctx context.Context, instanceID string, req *
agentCommand = agentCommand + " " + agentConfig.ModelFlag + " " + profileInfo.Model
}
if req.ACPSessionID != "" && profileInfo != nil && profileInfo.AgentName == "auggie" {
m.logger.Info("adding auggie resume flag to command (standalone)",
zap.String("task_id", req.TaskID),
zap.String("session_id", req.ACPSessionID))
agentCommand = agentCommand + " --resume " + req.ACPSessionID
}
// If empty, agentctl will use its default (auggie --acp)
@@ -835,6 +865,10 @@ func (m *Manager) initializeACPSession(ctx context.Context, instance *AgentInsta
}
instance.ACPSessionID = sessionID
m.logger.Info("ACP session ready",
zap.String("instance_id", instance.ID),
zap.String("session_id", sessionID),
zap.String("agent_name", agentName))
m.publishACPSessionCreated(instance, sessionID)
// 3. Set up updates stream to receive session notifications
@@ -1594,6 +1628,9 @@ func (m *Manager) buildContainerConfig(instanceID string, req *LaunchRequest, ag
cmd = append(cmd, agentConfig.ModelFlag, profileInfo.Model)
}
if req.ACPSessionID != "" && profileInfo != nil && profileInfo.AgentName == "auggie" {
m.logger.Info("adding auggie resume flag to command",
zap.String("task_id", req.TaskID),
zap.String("session_id", req.ACPSessionID))
cmd = append(cmd, "--resume", req.ACPSessionID)
}
@@ -1631,7 +1668,7 @@ func (m *Manager) expandMountTemplate(source, workspacePath, taskID string) stri
}
augmentSessionsDir := filepath.Join(homeDir, ".augment", "sessions")
// Ensure the directory exists
_ = os.MkdirAll(augmentSessionsDir, 0755)
_ = os.MkdirAll(augmentSessionsDir, 0o755)
result = strings.ReplaceAll(result, "{augment_sessions}", augmentSessionsDir)
}
@@ -2059,6 +2096,12 @@ func (m *Manager) publishACPSessionCreated(instance *AgentInstance, sessionID st
zap.String("event_type", events.AgentACPSessionCreated),
zap.String("instance_id", instance.ID),
zap.Error(err))
} else {
m.logger.Info("published ACP session event",
zap.String("event_type", events.AgentACPSessionCreated),
zap.String("task_id", instance.TaskID),
zap.String("agent_instance_id", instance.ID),
zap.String("acp_session_id", sessionID))
}
}