feat: hot-reload de agentes individuales via SIGHUP

Implementa el mecanismo de hot-reload descrito en el issue 0013:

- agents/runtime.go: añade Agent.Stop() y Agent.Done() para ciclo de vida
  individual. Run() crea un contexto hijo cancelable y cierra el canal
  done al retornar.

- cmd/launcher/registry.go (nuevo): agentRegistry rastrea agentes vivos
  por ID. Métodos: register, stopAndWait, reload, reloadAll, waitAll,
  cleanupLogs. reload() sigue el flujo completo: stop→wait→unsubscribe
  →reload config→recreate→rewire bus/orch→start nueva goroutine.

- cmd/launcher/main.go: usa agentRegistry en lugar de sync.WaitGroup.
  Añade handler de SIGHUP en goroutine separada que lee run/reload.txt
  para determinar el agente objetivo (* o vacío = todos). Tras leer,
  borra run/reload.txt para no afectar el siguiente SIGHUP.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-08 18:41:32 +00:00
parent f25ebc7b79
commit 069f8758b1
3 changed files with 316 additions and 28 deletions
+21
View File
@@ -62,6 +62,10 @@ type Agent struct {
logger *slog.Logger
cryptoStore io.Closer // non-nil when E2EE is enabled; closed on shutdown
// Lifecycle — cancel stops this agent individually; done is closed when Run returns.
cancel context.CancelFunc
done chan struct{}
// Access control
acl acl.ACL
@@ -268,6 +272,7 @@ func New(cfg *config.AgentConfig, rules []decision.Rule, logger *slog.Logger) (*
toolReg: toolReg,
logger: logger,
cryptoStore: cryptoStore,
done: make(chan struct{}),
commands: make(map[string]CommandHandler),
cmdAliases: command.BuiltinNames(),
startTime: time.Now(),
@@ -363,8 +368,24 @@ func (a *Agent) RawMatrixClient() *mautrix.Client {
return a.matrix.Raw()
}
// Stop cancels this agent's individual context, causing Run to return.
// Safe to call multiple times.
func (a *Agent) Stop() {
if a.cancel != nil {
a.cancel()
}
}
// Done returns a channel that is closed when Run has returned.
func (a *Agent) Done() <-chan struct{} {
return a.done
}
// Run starts the agent sync loop. Blocks until ctx is cancelled.
func (a *Agent) Run(ctx context.Context) error {
ctx, a.cancel = context.WithCancel(ctx)
defer close(a.done)
if a.cryptoStore != nil {
defer a.cryptoStore.Close()
}