57a1602e8f
- reports/ Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
182 lines
11 KiB
Markdown
182 lines
11 KiB
Markdown
# Report 0001 — unibus: servicio LAN + Fase 2 (transporte sobre el bus)
|
|
|
|
- **Fecha:** 06/06/2026
|
|
- **Autor:** Claude (Opus 4.8)
|
|
- **Ámbito:** `projects/message_bus/apps/unibus` (sub-repo `dataforge/unibus`) y `~/DataProyects/Github/agents_and_robots` (Gitea `egutierrez/agents_and_robots`)
|
|
- **Estado:** done (con un gap acotado documentado en Fase 2)
|
|
|
|
## Resumen
|
|
|
|
Dos entregas. **A)** `membershipd` pasa de proceso manual loopback a service systemd real
|
|
alcanzable por la LAN: flag `--bind` que gobierna a la vez el HTTP de control y el NATS
|
|
embebido, unit systemd-user con `Restart=always`, bloque `service:` completo y health
|
|
verificado desde la IP de la LAN. Esto desbloquea que un teléfono/PC remoto conecte al bus.
|
|
**B)** Fase 2 (branch-by-abstraction): se introduce una interfaz `Transport` + un tipo
|
|
`InboundMessage` neutral (sin mautrix), se desacoplan las firmas del core de agentes, se
|
|
implementa un `unibusTransport` sobre `pkg/client` de unibus, y un feature flag elige Matrix
|
|
vs unibus por bot. Pre-requisito cumplido: el `Frame` de unibus gana threading/reply/reaction
|
|
de forma aditiva.
|
|
|
|
Trabajo en ramas TBD: `issue/fase2-transport-service` (unibus) y `issue/unibus-transport`
|
|
(agents_and_robots). Master nunca tocado directamente.
|
|
|
|
## Cambios
|
|
|
|
### A — Servicio (unibus)
|
|
|
|
| Cambio | Archivo | Por qué |
|
|
|---|---|---|
|
|
| Flag `--bind` (default 127.0.0.1) aplicado a HTTP + NATS | `cmd/membershipd/main.go` | Un solo flag gobierna la alcanzabilidad de ambos planos; `0.0.0.0` expone a la LAN |
|
|
| `embeddednats.StartHost(storeDir, host, port)` (y `Start` backward-compatible) | `pkg/embeddednats/embeddednats.go` | Controlar la interfaz del NATS embebido sin romper playground/tests |
|
|
| systemd-user unit `Restart=always` | `deploy/unibus-membershipd.service` | Service persistente; `on-failure` NO reinicia tras un SIGTERM limpio (exit 0) — el gotcha de `sqlite_api` |
|
|
| `install.sh` idempotente + `README.md` | `deploy/` | build + symlink unit + enable/restart en un comando; operar/health documentado |
|
|
| Bloque `service:` completo | `app.md` | `systemd-user`, `restart_policy: always`, health `/healthz`, `is_local_only: false`; valida `fn doctor services-spec` |
|
|
|
|
### Pre-Fase 2 — Frame threading (unibus, aditivo)
|
|
|
|
| Cambio | Archivo | Por qué |
|
|
|---|---|---|
|
|
| Campos `ThreadID`, `ReplyTo` (omitempty) + tipo `REACT` | `pkg/frame/frame.go` | Bots de chat necesitan replies, hilos y reacciones |
|
|
| `PublishReply`, `React` (refactor de `Publish` a `publishFrame` compartido) | `pkg/client/client.go` | API de threading; la reacción viaja en el payload cifrado (confidencial en rooms E2E) |
|
|
| Tests: round-trip threaded, back-compat de wire/firma, error path, reply+react E2E | `pkg/frame/*_test.go`, `pkg/client/client_test.go` | golden + edge + error path |
|
|
|
|
Aditivo de verdad: un frame sin threading serializa **sin** las claves `thr`/`re`, así que su
|
|
wire y su firma Ed25519 son idénticos a antes → no rompe `Subscribe`/`Publish` ni los tests
|
|
existentes.
|
|
|
|
### B — Fase 2 (agents_and_robots)
|
|
|
|
| Cambio | Archivo | Por qué |
|
|
|---|---|---|
|
|
| `Transport` interface + `InboundMessage` neutral + `Select`/`FlagEnabled` | `pkg/transport/` | Seam neutral (cero mautrix) entre el core del agente y el fabric de mensajería |
|
|
| Flag `unibus-transport` (default off) | `dev/feature_flags.json` | Convivencia: con el flag ON cada bot opta in; el resto sigue en Matrix |
|
|
| `handleEvent(…, *event.Event)` → `handleInbound(…, transport.InboundMessage)` (Agent + Robot) | `agents/handler.go`, `agents/robot.go` | Quitar `*event.Event` de las firmas; `inboundToMsgCtx` es el único punto de conversión a `decision.MessageContext` |
|
|
| `RawMatrixClient() *mautrix.Client` → `Scanner() RoomScanner` | `agents/runtime.go`, `cmd/launcher/main.go` | Quitar el handle al cliente mautrix de la API pública; capability read-only |
|
|
| Listener entrega `InboundMessage` (no el evento) | `shell/matrix/listener.go` | El evento mautrix ya no cruza la frontera al core |
|
|
| `unibusTransport` + `DemoEchoHandler` (módulo Go anidado) | `shell/transportunibus/` | Implementa `Transport` sobre `pkg/client`; subjects `agent.<name>.{in,out}` |
|
|
|
|
## Verificación
|
|
|
|
### A — Servicio (evidencia ejecutable)
|
|
|
|
```
|
|
$ systemctl --user is-active unibus-membershipd.service
|
|
active
|
|
|
|
$ systemctl --user status unibus-membershipd.service # (extracto)
|
|
Active: active (running)
|
|
ExecStart=…/membershipd --bind 0.0.0.0
|
|
|
|
$ ss -tln | grep -E ':8470|:4250'
|
|
LISTEN 0 4096 *:8470 *:* # HTTP control en todas las interfaces
|
|
LISTEN 0 4096 *:4250 *:* # NATS data plane en todas las interfaces
|
|
|
|
$ curl -fsS -w " [HTTP %{http_code}]\n" http://127.0.0.1:8470/healthz
|
|
{"status":"ok"} [HTTP 200]
|
|
|
|
$ curl -fsS -w " [HTTP %{http_code}]\n" http://192.168.1.129:8470/healthz # IP LAN
|
|
{"status":"ok"} [HTTP 200]
|
|
|
|
$ ./fn doctor services-spec | grep unibus
|
|
OK unibus systemd-user 8470 /healthz unibus-membershipd.service lucas-linux -
|
|
```
|
|
|
|
**Cliente NATS conecta por IP LAN y publica/recibe** (worker→chat, ambos contra
|
|
`nats://192.168.1.129:4250` + `http://192.168.1.129:8470`, vía el service systemd):
|
|
|
|
```
|
|
[chat] subscribed to "proc.test.ticks"; waiting for messages
|
|
[proc.test.ticks] DGCob41a: tick 1 @ 2026-06-06T16:07:16Z
|
|
[proc.test.ticks] DGCob41a: tick 2 @ 2026-06-06T16:07:17Z
|
|
[proc.test.ticks] DGCob41a: tick 3 @ 2026-06-06T16:07:18Z
|
|
[proc.test.ticks] DGCob41a: tick 4 @ 2026-06-06T16:07:19Z
|
|
```
|
|
|
|
### Pre-Fase 2 — Frame (unibus)
|
|
|
|
```
|
|
$ cd projects/message_bus/apps/unibus
|
|
$ CGO_ENABLED=0 go build ./... && CGO_ENABLED=0 go vet ./... # exit 0
|
|
$ CGO_ENABLED=0 go test ./...
|
|
ok github.com/enmanuel/unibus/pkg/frame
|
|
ok github.com/enmanuel/unibus/pkg/client
|
|
ok github.com/enmanuel/unibus/pkg/membership
|
|
```
|
|
|
|
Tests nuevos: `TestThreadingRoundTrip` (golden), `TestNonThreadedWireBackCompat` (edge:
|
|
asegura que `thr`/`re` no aparecen en frames no-threaded), `TestUnmarshalRejectsGarbage`
|
|
(error path), `TestThreadedReplyAndReaction` (reply + reacción E2E en room cifrada ModeMatrix).
|
|
|
|
### B — Fase 2 (agents_and_robots)
|
|
|
|
Toda la compilación/test del repo `agents` se hace con `-tags goolm` (olm puro-Go) porque
|
|
`libolm` (CGO) no está instalado en esta máquina; en producción se usa `libolm`.
|
|
|
|
```
|
|
# Módulo anidado del transporte unibus (flag ON / camino unibus):
|
|
$ cd shell/transportunibus && go test -tags goolm ./...
|
|
ok github.com/enmanuel/agents/shell/transportunibus
|
|
# TestDemoBotOverUnibus: un user-peer publica en agent.demo.in; el bot, movido por
|
|
# unibusTransport, recibe un InboundMessage y responde "echo: hola bus" / "pong" en
|
|
# agent.demo.out. TestRunStopsOnContextCancel: lifecycle (error path).
|
|
|
|
# Módulo principal (flag OFF / camino Matrix sigue vivo):
|
|
$ go build -tags goolm ./... # exit 0
|
|
$ go vet -tags goolm ./agents/ ./shell/matrix/ ./pkg/transport/ # exit 0
|
|
$ go test -tags goolm ./pkg/transport/ ./agents/
|
|
ok github.com/enmanuel/agents/pkg/transport # TestSelect (flag ON/OFF) + TestFlagEnabled
|
|
ok github.com/enmanuel/agents/agents # core decoplado compila+pasa
|
|
$ go test -tags goolm ./... # 26 paquetes ok, 1 FAIL pre-existente (ver gaps)
|
|
```
|
|
|
|
## Gaps / pendientes
|
|
|
|
1. **Segundo host físico real para A.** El round-trip de A se probó desde esta misma máquina
|
|
contra su IP de LAN `192.168.1.129` (no loopback), lo que ejercita el bind `0.0.0.0`. No
|
|
había un segundo PC/teléfono disponible para conectar desde otra máquina; como los sockets
|
|
escuchan en `*` (todas las interfaces, confirmado por `ss`), el comportamiento desde un host
|
|
remoto es idéntico. El agente de frontend puede confirmarlo desde el móvil.
|
|
|
|
2. **El Agent "pesado" todavía no corre sobre unibus.** Conflicto real de dependencias entre
|
|
ecosistemas: `unibus` depende de `fn-registry`, cuyo módulo raíz exige
|
|
`maunium.net/go/mautrix v0.28.0`, incompatible con el `shell/matrix` del repo `agents`
|
|
(escrito para `v0.21.1`; MVS forzaría v0.28 y rompe 3 call-sites de la API mautrix). Por eso
|
|
el `unibusTransport` vive en un **módulo Go anidado** (`shell/transportunibus/go.mod`): tira
|
|
de la mautrix nueva transitivamente sin recompilar el código Matrix del padre, y ambos
|
|
ecosistemas conviven. Consecuencia: el core del Agent (módulo principal) no puede instanciar
|
|
`unibusTransport` directamente sin reintroducir el conflicto. El seam neutral
|
|
(`pkg/transport.Transport`) sí está y el bot demo corre sobre el bus; **migrar el Agent
|
|
completo a unibus requiere primero subir `agents` a mautrix v0.28** (migración aparte, fuera
|
|
de alcance de esta tanda).
|
|
|
|
3. **`Scanner() RoomScanner` sigue devolviendo tipos mautrix** en sus métodos
|
|
(`*mautrix.RespJoinedRooms`, …). El escaneo de rooms es una capability intrínsecamente de
|
|
Matrix (enumera rooms de Matrix); la neutralización quita el handle al cliente crudo y lo
|
|
sustituye por una interfaz read-only mínima, pero no puede ser 100% neutral porque el unibus
|
|
no participa en ese escaneo.
|
|
|
|
4. **`libolm` no instalado** en esta máquina → el repo `agents` se compila/test con
|
|
`-tags goolm` (olm puro-Go). En producción se usa `libolm`. Pendiente: instalar
|
|
`libolm-dev` si se quiere validar el path libolm localmente.
|
|
|
|
5. **Test pre-existente en rojo, ajeno a este trabajo:** `shell/logger/TestCleanOldLogs` falla
|
|
porque es dependiente de fecha (espera logs de marzo 2026 que la limpieza por antigüedad
|
|
borra estando hoy a 06/06/2026). Ya fallaba en el baseline antes de tocar nada; no toqué
|
|
`shell/logger`. Issue separado recomendado.
|
|
|
|
6. **Sin push a remoto.** El trabajo queda consolidado en `master` local de ambos repos; el
|
|
push a Gitea queda para `/full-git-push` del operador.
|
|
- `agents_and_robots`: `master` = merge `--no-ff` de `issue/unibus-transport` (commit
|
|
`aa595f5`), build + tests verdes; rama borrada.
|
|
- `unibus`: **condición de carrera entre dos agentes en el mismo repo.** El agente de
|
|
frontend trabajaba en paralelo en el mismo working tree de unibus y nuestros `git checkout`
|
|
se interleavearon: mis commits A1/A2/A3 (`--bind`, deploy, service block) acabaron
|
|
directamente en `master`, y los de threading + bump (`2209283`, `69079d1`) cayeron sobre la
|
|
rama del otro agente `issue/frontend-web-movil`. Como esos dos commits son lineales sobre
|
|
mi A3 (`b2e6712`) y NO contenían trabajo del otro agente, consolidé `master` con
|
|
`git merge --ff-only 69079d1` (sin duplicar commits, sin tocar la rama del frontend). Estado
|
|
final: `master` de unibus contiene TODO mi trabajo (A1-A3 + threading + bump 0.3.0), tests
|
|
`pkg/...` verdes, working tree con el WIP del frontend (mobile/playground/android/web)
|
|
intacto. **Lección operativa:** dos agentes Claude en el mismo repo comparten HEAD/índice;
|
|
hace falta worktrees separados (`git worktree`) o repos clonados por agente para evitarlo.
|