Files

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