feat(api): per-agent unified control + clear_memory + delete_cache
- Manager: RegisterUnifiedAgent/UnregisterUnifiedAgent/StopUnifiedAgent/
IsUnifiedAgentRunning/UptimeSeconds — cancela goroutines individuales sin
matar el launcher
- Manager: UptimeSeconds en AgentStatus via startedAt map
- api/server: AgentController interface + WithController/WithDataDir builders
+ rutas POST /agents/{id}/clear_memory y /agents/{id}/delete_cache
- api/handlers: handleStartAgent/Stop/Restart delegan a controller en modo
unified; Messages24h enriquecido via queryMessages24h (cache 30s)
- api/handlers: handleClearMemory — para la goroutine, borra messages+facts de
memory.db, responde {status,messages_deleted,facts_deleted}
- api/handlers: handleDeleteCache — para la goroutine, elimina crypto/ y cache/,
responde {status,paths_deleted}
- launcher/registry: launchGoroutine extrae goroutine con contexto per-agente;
deps.procMgr hookea RegisterUnified; startAgent permite relanzar via reload
- launcher/main: agentController implementa api.AgentController sobre registry;
mgr compartido entre API y registry; WithController+WithDataDir cableados
Co-Authored-By: fn-orquestador <noreply@fn-registry>
This commit is contained in:
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -34,6 +35,15 @@ type launchDeps struct {
|
||||
logLevel slog.Level
|
||||
parentCtx context.Context
|
||||
secPolicy pksecurity.SecurityPolicy // centralized security policy loaded from security/
|
||||
procMgr procManagerHook // optional: per-agent goroutine registration for API
|
||||
}
|
||||
|
||||
// procManagerHook allows the registry to register/unregister per-agent goroutine
|
||||
// contexts with the process.Manager so the API can reflect and control individual
|
||||
// agent goroutines in unified mode.
|
||||
type procManagerHook interface {
|
||||
RegisterUnifiedAgent(id string, cancel context.CancelFunc)
|
||||
UnregisterUnifiedAgent(id string)
|
||||
}
|
||||
|
||||
// agentRegistry tracks all running agents by ID, enabling individual hot-reload.
|
||||
@@ -61,10 +71,33 @@ func (r *agentRegistry) register(ra *runningAgent) {
|
||||
runtimeType = "agent"
|
||||
}
|
||||
|
||||
r.launchGoroutine(ra, runtimeType)
|
||||
}
|
||||
|
||||
// launchGoroutine starts a runner goroutine, registering its cancel context with
|
||||
// the process manager hook when available for per-agent stop/start control.
|
||||
func (r *agentRegistry) launchGoroutine(ra *runningAgent, runtimeType string) {
|
||||
agentID := ra.cfg.Agent.ID
|
||||
go func() {
|
||||
// Create a per-agent context derived from parent so we can cancel just
|
||||
// this goroutine without stopping the launcher or other agents.
|
||||
agentCtx, cancel := context.WithCancel(r.deps.parentCtx)
|
||||
defer cancel()
|
||||
|
||||
// Register with process manager for API control (unified mode).
|
||||
if r.deps.procMgr != nil {
|
||||
r.deps.procMgr.RegisterUnifiedAgent(agentID, cancel)
|
||||
defer r.deps.procMgr.UnregisterUnifiedAgent(agentID)
|
||||
}
|
||||
|
||||
ra.logger.Info("runner started", "type", runtimeType)
|
||||
if err := ra.runner.Run(r.deps.parentCtx); err != nil {
|
||||
ra.logger.Error("runner stopped with error", "err", err, "type", runtimeType)
|
||||
if err := ra.runner.Run(agentCtx); err != nil {
|
||||
if agentCtx.Err() == nil {
|
||||
// Not cancelled externally — log as real error
|
||||
ra.logger.Error("runner stopped with error", "err", err, "type", runtimeType)
|
||||
} else {
|
||||
ra.logger.Info("runner stopped (context cancelled)", "type", runtimeType)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -90,6 +123,21 @@ func (r *agentRegistry) stopAndWait(id string) {
|
||||
r.deps.agentBus.Unsubscribe(bus.AgentID(id))
|
||||
}
|
||||
|
||||
// startAgent re-launches a stopped (but registered) agent by calling reload.
|
||||
// Used by the API StartUnifiedAgent flow.
|
||||
// Returns error if agent is not found in the registry.
|
||||
func (r *agentRegistry) startAgent(id string, rulesFor func(string, *slog.Logger) []decision.Rule) error {
|
||||
r.mu.Lock()
|
||||
_, exists := r.agents[id]
|
||||
r.mu.Unlock()
|
||||
if !exists {
|
||||
return fmt.Errorf("agent %q not found in registry", id)
|
||||
}
|
||||
// reload re-reads config and restarts the runner
|
||||
r.reload(id, rulesFor)
|
||||
return nil
|
||||
}
|
||||
|
||||
// reload stops an agent, re-reads its config, recreates it, and restarts it.
|
||||
func (r *agentRegistry) reload(id string, rulesFor func(string, *slog.Logger) []decision.Rule) {
|
||||
r.mu.Lock()
|
||||
@@ -192,12 +240,7 @@ func (r *agentRegistry) reload(id string, rulesFor func(string, *slog.Logger) []
|
||||
if runtimeType == "" {
|
||||
runtimeType = "agent"
|
||||
}
|
||||
go func() {
|
||||
newLogger.Info("runner started", "type", runtimeType)
|
||||
if err := newRunner.Run(r.deps.parentCtx); err != nil {
|
||||
newLogger.Error("runner stopped with error", "err", err, "type", runtimeType)
|
||||
}
|
||||
}()
|
||||
r.launchGoroutine(newRA, runtimeType)
|
||||
|
||||
newLogger.Info("runner_reloaded", "id", id, "type", runtimeType)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user