feat: initial scaffold of unibots — bot platform consuming unibus, first bot = echo

This commit is contained in:
agent
2026-06-04 23:36:50 +02:00
commit 11ef3fe9dd
6 changed files with 626 additions and 0 deletions
+133
View File
@@ -0,0 +1,133 @@
---
name: unibots
lang: go
domain: infra
version: 0.1.0
description: "Plataforma de bots que consumen el bus unibus; primer bot = eco (bot sin LLM que demuestra los dos patrones de conversación del bus)."
tags: [bots, messaging, unibus-client]
uses_functions: []
uses_types: []
framework: ""
entry_point: "cmd/echobot"
dir_path: "projects/message_bus/apps/unibots"
repo_url: ""
e2e_checks:
- id: build
cmd: "CGO_ENABLED=0 go build ./..."
timeout_s: 180
- id: vet
cmd: "CGO_ENABLED=0 go vet ./..."
timeout_s: 120
- id: unit
cmd: "CGO_ENABLED=0 go test ./..."
timeout_s: 180
---
## Qué es
`unibots` es la plataforma de **bots** que consumen el bus de mensajería
[`unibus`](../unibus/). Un **bot** es todo peer automatizado del bus, con o sin
LLM. Un **bot agente** es un bot que contiene un LLM (no aplica todavía aquí). El
término único en código, nombres y docs es **bot**.
El primer bot es el **bot eco** (`cmd/echobot`): un bot **sin LLM** que se une al
bus y devuelve cada mensaje prefijado con `"echo: "`. Existe para demostrar de
forma autónoma los **dos patrones de conversación** que el bus expone, sin
depender de ningún modelo:
| Patrón | Subject | Cómo | Pareja |
|---|---|---|---|
| **Chat** (bot↔humano) | `room.echo` (cleartext, `room.ModeNATS`) | `Subscribe` a la room + `Publish` la respuesta | cualquier peer en el mismo subject |
| **RPC** (bot↔proceso) | `rpc.echo` | request/reply de NATS (`Client.Reply` registra el responder) | cualquier proceso que haga `Client.Request` |
`unibots` es **código de aplicación**, no funciones del registry: orquesta la
librería cliente de `unibus` (`pkg/client`) y no reimplementa nada. Por eso
`uses_functions` está vacío — el crypto/transporte lo aporta `unibus`, que a su
vez importa las primitivas del registry.
Referencia: [[convencion-bot-vs-agente]]. Consumidor de [[unibus]].
## Ejemplo
Lanzar el echobot contra un `membershipd` corriendo (NATS embebido en `:4250`,
HTTP en `:8470`, los defaults productivos de unibus):
```bash
cd projects/message_bus/apps/unibus
# 1. Bus de membresía/claves (NATS embebido + control plane HTTP)
go run ./cmd/membershipd
# 2. En otra terminal: el bot eco (defaults: nats://127.0.0.1:4250, http://127.0.0.1:8470)
cd ../unibots
go run ./cmd/echobot
# Loguea al arrancar: endpoint id, subjects de chat y rpc, y a qué bus apunta.
```
Probarlo desde otra terminal sin escribir más Go:
```bash
# Modo RPC (bot<->proceso) con la CLI de NATS contra el mismo NATS embebido:
# nats --server nats://127.0.0.1:4250 request rpc.echo "ping"
# -> recibe "echo: ping"
# Modo chat (bot<->humano): cualquier peer que publique en el subject room.echo
# (p.ej. el `chat` de unibus apuntando a ese subject, o un cliente propio) recibe
# de vuelta "echo: <su mensaje>".
```
Apuntar a un bus distinto:
```bash
go run ./cmd/echobot \
--nats-url nats://mi-host:4222 \
--ctrl-url http://mi-host:8470 \
--room-subject room.demo \
--rpc-subject rpc.demo
```
## Cuando usarla
- Cuando quieras **validar que un bus unibus está vivo** end-to-end (chat y RPC)
sin montar un bot agente con LLM.
- Como **plantilla mínima** para escribir un bot nuevo: copia `cmd/echobot`,
cambia la lógica del handler (en vez de `"echo: " + body`, llama a tu servicio,
base de datos o, más adelante, a un LLM para convertirlo en bot agente).
- Para **demostrar los dos patrones** del bus (chat por room cleartext vs RPC
request/reply) a alguien que aprende la arquitectura.
## Gotchas
- **Guard anti-bucle (crítico).** El handler de chat ignora los mensajes cuyo
`frame.Frame.Sender == c.Endpoint().ID`. Sin este guard, el bot se haría eco de
su propio `"echo: ..."` indefinidamente (y dos echobots en el mismo subject
entrarían en un bucle infinito). El test `TestChatEcho` verifica que nunca
aparece `"echo: echo: hola"`.
- **Cleartext comparte subject, no room id.** El bot usa `room.ModeNATS`
(cleartext, efímero, sin firma). NATS enruta por **subject**, así que el bot
conversa con cualquier peer en el mismo subject aunque cada uno tenga su propio
`room_id` (mismo patrón que el worker/chat de unibus). No hay "unirse a una room
por nombre": cada `CreateRoom` produce un ULID nuevo mapeado al subject.
- **Modo RPC sí está soportado.** La librería de unibus expone request/reply:
`Client.Request(subject, body, timeout)` y `Client.Reply(subject, handler)`
(cleartext v1, sobre `rpc.*`). El echobot registra un responder con `Reply`. No
hubo que omitir ni inventar nada en unibus.
- **Identidad = secreto crítico.** `local_files/echobot.id` contiene las claves
privadas (Ed25519 + X25519), se escribe 0600. Perderlo no rompe el eco (es
cleartext) pero cambia la identidad del bot. Está gitignorado.
- **Build sin CGO.** Igual que unibus: `CGO_ENABLED=0`, sin `fts5` ni `gcc`. El
crypto del registry (`cybersecurity`) y el driver SQLite pure-Go compilan
limpio.
- **Los tests usan puertos propios aislados.** El test de integración levanta un
`membershipd` con NATS embebido en puertos libres (`:0`) bajo `t.TempDir()`,
nunca en `8470/4250` ni en los del playground del usuario; todo se limpia por
handle vía `t.Cleanup`.
## Convención de subjects (heredada de unibus)
```
proc.<svc>.<canal> telemetría/coordinación de procesos
rpc.<svc> request/reply (rpc.echo)
room.<grupo> chat humano/grupo (room.echo)
agent.<nombre>.{in,out} inbox/outbox de bot agente (futuro)
```