275 lines
12 KiB
Markdown
275 lines
12 KiB
Markdown
# Flujo del sistema de agentes — Diagrama de funciones
|
|
|
|
## 1. Arranque del sistema (Launcher)
|
|
|
|
```mermaid
|
|
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)
|
|
|
|
```mermaid
|
|
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)
|
|
|
|
```mermaid
|
|
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)
|
|
|
|
```mermaid
|
|
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)
|
|
|
|
```mermaid
|
|
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)
|
|
|
|
```mermaid
|
|
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)
|
|
|
|
```mermaid
|
|
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
|
|
|
|
```mermaid
|
|
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
|
|
|
|
```mermaid
|
|
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
|
|
```
|