12 KiB
12 KiB
Flujo del sistema de agentes — Diagrama de funciones
1. Arranque del sistema (Launcher)
flowchart TD
START["cmd/launcher/main()"] --> NEWLOGGER["newLogger(level)"]
START --> GLOB["Glob: agents/*/config.yaml"]
GLOB --> LOAD["config.Load(path)<br/>→ os.ExpandEnv + validate()"]
LOAD --> RULESFOR["rulesFor(agentID)<br/>→ rulesRegistry[id]()"]
RULESFOR --> AGENTNEW["agents.New(cfg, rules, logger)"]
subgraph "agents.New() — Ensamblado"
AGENTNEW --> MATRIXNEW["matrix.New(cfg.Matrix)<br/>→ crea mautrix.Client"]
MATRIXNEW --> CRYPTO{"encryption.enabled?"}
CRYPTO -->|sí| INITCRYPTO["client.InitCrypto()<br/>→ initCryptoCore()<br/>→ initHelper()<br/>→ resolvePickleKey()<br/>→ logCryptoDiagnostics()"]
INITCRYPTO --> FETCHKEYS{"recovery_key?"}
FETCHKEYS -->|sí| CROSSSIGN["client.FetchCrossSigningKeys()<br/>→ fetchCrossSigningKeysCore()"]
FETCHKEYS -->|no| SSHEXEC
CROSSSIGN --> SSHEXEC
CRYPTO -->|no| SSHEXEC
SSHEXEC["ssh.NewExecutor(cfg.SSH)"]
SSHEXEC --> LLMFACTORY["llm.FromConfig(cfg.LLM.Primary)<br/>→ NewAnthropicComplete() /<br/> NewOpenAIComplete()"]
LLMFACTORY --> FALLBACK{"fallback?"}
FALLBACK -->|sí| WITHFALLBACK["llm.WithFallback(primary, fallback)"]
FALLBACK -->|no| TOOLREG
WITHFALLBACK --> TOOLREG
TOOLREG["buildToolRegistry(cfg, ssh, matrix)<br/>→ NewHTTPGet/Post()<br/>→ NewSSHCommand()<br/>→ NewReadFile()<br/>→ NewCurrentTime()<br/>→ NewMatrixSend()"]
TOOLREG --> RUNNER["effects.NewRunner(matrix, ssh)"]
RUNNER --> LISTENER["matrix.NewListener(client, cfg, handleEvent)"]
end
AGENTNEW --> RUN["agent.Run(ctx)<br/>→ listener.Run(ctx)<br/>→ mautrix.SyncWithContext()"]
START --> SIGNAL["Espera SIGINT / SIGTERM<br/>→ cancel ctx → shutdown"]
2. Procesamiento de eventos (flujo principal)
flowchart TD
SYNC["mautrix SyncWithContext()"] --> EVENT["Evento Matrix recibido<br/>EventMessage / StateMember"]
EVENT --> AUTOJOIN{"StateMember<br/>invite?"}
AUTOJOIN -->|sí| JOIN["Auto-join room"]
AUTOJOIN -->|no| SHOULD["listener.shouldHandle(evt)<br/>→ filtra propios, bots, blocked, rooms"]
SHOULD -->|rechazado| DROP["Descartado"]
SHOULD -->|aceptado| ISDM["listener.checkIsDM(roomID)<br/>→ cache de rooms con 2 miembros"]
ISDM --> PARSE["message.Parse(body, sender, room, ...)<br/>→ detecta mentions<br/>→ parsea command + args<br/>→ retorna MessageContext"]
PARSE --> GOROUTINE["goroutine: agent.handleEvent()"]
subgraph "handleEvent() — Decisión y ejecución"
GOROUTINE --> TYPING["matrix.SendTyping(room, true)"]
TYPING --> EVALUATE["decision.Evaluate(msgCtx, rules)<br/>→ recorre reglas, Match() → []Action"]
EVALUATE --> HASACTIONS{"¿acciones?"}
HASACTIONS -->|sí| CHECKLLM{"¿contiene<br/>ActionKindLLM?"}
HASACTIONS -->|no| FALLBACKLLM{"¿es DM o<br/>mención?"}
FALLBACKLLM -->|sí| RUNLLM["agent.runLLM(ctx, msgCtx)"]
FALLBACKLLM -->|no| NOOP["Sin acción"]
CHECKLLM -->|sí| EXPANDLLM["Expande LLM actions:<br/>runLLM() → ReplyAction"]
CHECKLLM -->|no| EXECUTE
EXPANDLLM --> EXECUTE
RUNLLM --> EXECUTE["runner.Execute(ctx, roomID, actions)"]
end
3. Loop de herramientas del LLM (tool-use)
flowchart TD
RUNLLM["agent.runLLM()"] --> BUILD["Construir CompletionRequest<br/>→ SystemPrompt desde archivo<br/>→ Messages: historial + user<br/>→ Tools: registry.ToLLMSpecs()"]
BUILD --> CALL["CompleteFunc(ctx, request)<br/>→ Anthropic API / OpenAI API"]
subgraph "shell/llm — Proveedores"
CALL --> ANTHROPIC["NewAnthropicComplete()<br/>→ toAnthropicRequest()<br/>→ HTTP POST /v1/messages<br/>→ fromAnthropicResponse()"]
CALL --> OPENAI["NewOpenAIComplete()<br/>→ toOpenAIMessage()<br/>→ toOpenAITools()<br/>→ SDK CreateChatCompletion"]
end
ANTHROPIC --> RESPONSE["CompletionResponse<br/>{Content, ToolCalls, Usage}"]
OPENAI --> RESPONSE
RESPONSE --> HASTOOLS{"¿ToolCalls<br/>en respuesta?"}
HASTOOLS -->|no| RETURN["Retorna Content como texto"]
HASTOOLS -->|sí| EXECTOOLS["Por cada ToolCall:<br/>registry.Execute(name, argsJSON)"]
subgraph "tools/ — Ejecución de herramientas"
EXECTOOLS --> TOOLSWITCH{"tool name"}
TOOLSWITCH --> HTTP_GET["http_get<br/>→ validateDomain()<br/>→ GET request"]
TOOLSWITCH --> HTTP_POST["http_post<br/>→ validateDomain()<br/>→ POST request"]
TOOLSWITCH --> SSH_CMD["ssh_command<br/>→ validateTarget()<br/>→ validateCommand()<br/>→ ssh.Executor.Execute()"]
TOOLSWITCH --> READ_FILE["read_file<br/>→ validatePath()<br/>→ os.ReadFile()"]
TOOLSWITCH --> MATRIX_SEND["matrix_send<br/>→ client.SendText()"]
TOOLSWITCH --> CURRENT_TIME["current_time<br/>→ time.Now().Format()"]
end
EXECTOOLS --> APPEND["Append assistant msg + tool results<br/>a Messages del request"]
APPEND --> ITER{"iteración <<br/>maxIter?"}
ITER -->|sí| CALL
ITER -->|no| RETURN
4. Ejecución de efectos (Runner)
flowchart TD
EXECUTE["runner.Execute(ctx, roomID, actions)"] --> LOOP["Por cada Action en []Action"]
LOOP --> EXECONE["runner.executeOne(ctx, roomID, action)"]
EXECONE --> KIND{"action.Kind"}
KIND -->|ActionKindReply| REPLY["matrix.SendText(ctx, roomID, content)<br/>→ envío auto-encriptado si E2EE"]
KIND -->|ActionKindSSH| SSHEXEC["ssh.Executor.Execute(ctx, spec)"]
KIND -->|otro| UNHANDLED["Result{Err: unhandled}"]
subgraph "shell/ssh — Ejecución SSH"
SSHEXEC --> LOOKUP["Buscar target en config<br/>→ resolver user/port/key"]
LOOKUP --> LOADSIGNER["loadSigner(keyFileEnv)<br/>→ leer clave privada"]
LOADSIGNER --> DIAL["gossh.Dial(tcp, host:port)"]
DIAL --> SESSION["client.NewSession()"]
SESSION --> RUNCMD["session.CombinedOutput(cmd)"]
RUNCMD --> SSHRESULT["Result{Stdout, Stderr, ExitCode}"]
end
REPLY --> RESULT["Result{Action, Output, Err}"]
SSHRESULT --> RESULT
UNHANDLED --> RESULT
5. Motor de reglas puro (decision engine)
flowchart TD
EVAL["decision.Evaluate(ctx, rules)"] --> LOOP["Por cada Rule"]
LOOP --> MATCH["rule.Match(ctx) → bool"]
subgraph "MatchFuncs disponibles (pure)"
MATCH --> CMD["MatchCommand(cmd)<br/>ctx.Command == cmd"]
MATCH --> PREFIX["MatchPrefix(prefix)<br/>strings.HasPrefix(ctx.Content)"]
MATCH --> ANY["MatchAny()<br/>→ true siempre"]
MATCH --> POWER["MatchMinPowerLevel(n)<br/>ctx.PowerLevel >= n"]
MATCH --> COMPOSE["And(...) / Or(...)<br/>composición lógica"]
end
MATCH -->|true| COLLECT["Agregar rule.Actions a resultado"]
MATCH -->|false| NEXT["Siguiente regla"]
COLLECT --> NEXT
NEXT --> LOOP
LOOP -->|fin| ACTIONS["Retorna []Action acumuladas"]
6. Gestión de procesos (agentctl / dev-scripts)
flowchart TD
CLI["cmd/agentctl/main()"] --> MGR["process.NewManager(runDir, glob, bin)"]
MGR --> LISTCMD["listCmd → mgr.StatusAll()"]
MGR --> STARTCMD["startCmd → mgr.Start(info)"]
MGR --> STOPCMD["stopCmd → mgr.Stop(id)"]
MGR --> REMOVECMD["removeCmd → mgr.Stop + setEnabled(false)"]
subgraph "process.Manager — Ciclo de vida"
LISTCMD --> SCAN["mgr.Scan()<br/>→ glob configs<br/>→ config.LoadMeta()"]
SCAN --> STATUS["mgr.Status(info)<br/>→ findProcessPIDs()<br/>→ resolveRunningPID()"]
STARTCMD --> STARTCHECK{"¿ya running?"}
STARTCHECK -->|sí| REJECT["Error: already running"]
STARTCHECK -->|no| LAUNCH["Abrir log file<br/>→ buildEnv()<br/>→ os/exec.Start()<br/>→ escribir PID file"]
STOPCMD --> SIGTERM["SIGTERM a todos los PIDs"]
SIGTERM --> WAIT["Esperar 5s (polls cada 500ms)"]
WAIT --> ALIVE{"¿todavía vivo?"}
ALIVE -->|sí| SIGKILL["SIGKILL"]
ALIVE -->|no| CLEAN["removePID()"]
SIGKILL --> CLEAN
end
subgraph "Monitoreo"
STATUS --> STATS["mgr.Stats(id)<br/>→ statsForPID()<br/>→ /proc/pid/stat (uptime)<br/>→ /proc/pid/status (RSS)<br/>→ ps -o pcpu (CPU)"]
STATUS --> LOGS["mgr.LogTail(id, n)<br/>→ últimas N líneas del log"]
end
7. Dashboard TUI (Bubbletea — pure core / impure shell)
flowchart TD
MAIN["cmd/dashboard/main()"] --> BRIDGE["newBridge(adapter)"]
BRIDGE --> TEA["tea.NewProgram(bridge)"]
subgraph "Pure Core — pkg/tui"
INIT["bridge.Init()<br/>→ IntentLoadAgents"]
UPDATE["puretui.Update(model, msg)<br/>→ (Model, []Intent)"]
VIEW["puretui.View(model)<br/>→ string renderizado"]
UPDATE --> SCREENS{"Screen actual"}
SCREENS --> MAIN_MENU["updateMainScreen()"]
SCREENS --> AGENT_LIST["updateAgentList()"]
SCREENS --> AGENT_ACTIONS["updateAgentActions()<br/>→ executeAction()"]
SCREENS --> LOGS_VIEW["updateLogs()"]
SCREENS --> SERVER_VIEW["updateServerScreen()<br/>→ executeServerAction()"]
end
subgraph "Impure Shell — shell/tui"
ADAPTER["Adapter.RunIntent(intent)"]
ADAPTER --> LOAD["loadAgents()<br/>→ mgr.StatusAll() + Stats()"]
ADAPTER --> START["startAgent(id)<br/>→ mgr.Start()"]
ADAPTER --> STOP["stopAgent(id)<br/>→ mgr.Stop()"]
ADAPTER --> KILL["killAgent(id)<br/>→ mgr.Kill()"]
ADAPTER --> RESTART["restartAgent(id)<br/>→ Stop + Start"]
ADAPTER --> STARTALL["startAll() / stopAll()<br/>restartAll() / killAll()"]
ADAPTER --> LOADLOGS["loadLogs(id)<br/>→ mgr.LogTail()"]
end
TEA --> INIT
INIT --> ADAPTER
TEA --> UPDATE
UPDATE -->|"[]Intent"| ADAPTER
ADAPTER -->|"tea.Cmd → Msg"| UPDATE
TEA --> VIEW
8. Registro y verificación E2EE de bots
flowchart TD
subgraph "cmd/register — Registro en Matrix"
REG["main()"] --> CREATE["createUser(homeserver, token, userID, name, pass)<br/>→ PUT /_synapse/admin/v2/users/"]
CREATE --> LOGIN["loginAs(homeserver, user, pass)<br/>→ POST /_matrix/client/v3/login"]
LOGIN --> TOKEN["Imprime access_token + device_id<br/>→ exportar como MATRIX_TOKEN_BOT"]
REG --> GENPASS["generatePassword()<br/>→ 24 bytes /dev/urandom → hex"]
end
subgraph "cmd/verify — Cross-signing E2EE"
VER["main()"] --> MAUTRIX["Crear mautrix.Client"]
MAUTRIX --> INITCRYPTO["cryptohelper.Init()<br/>→ mismo store que el agente"]
INITCRYPTO --> GENKEYS["GenerateAndUploadCrossSigningKeys<br/>WithPassword()"]
GENKEYS --> RECOVERY["Imprime SSSS recovery key"]
GENKEYS -->|"keys exist"| SIGNOWN["signOwnDevice()<br/>→ mach.SignOwnDevice()"]
end
9. Flujo completo end-to-end
flowchart LR
USER["Usuario Matrix"] -->|"mensaje"| HOMESERVER["Matrix Homeserver"]
HOMESERVER -->|"sync"| LISTENER["Listener.Run()<br/>shouldHandle()<br/>checkIsDM()"]
LISTENER -->|"Parse()"| MSGCTX["MessageContext<br/>(puro)"]
MSGCTX -->|"handleEvent()"| DECIDE["Evaluate(rules)<br/>(puro)"]
DECIDE -->|"[]Action"| BRANCH{"¿tipo?"}
BRANCH -->|"LLM"| LLM["runLLM()<br/>→ tool-use loop"]
LLM -->|"CompleteFunc"| API["Anthropic / OpenAI API"]
API -->|"ToolCalls"| TOOLS["Registry.Execute()<br/>http / ssh / file / time"]
TOOLS -->|"results"| LLM
LLM -->|"texto final"| REPLY
BRANCH -->|"Reply"| REPLY["SendText() / SendMarkdown()"]
BRANCH -->|"SSH"| SSH["Executor.Execute()"]
SSH -->|"resultado"| REPLY
REPLY -->|"respuesta"| HOMESERVER
HOMESERVER -->|"mensaje"| USER