feat: import agents_and_robots platform as unibots (Matrix-out, unibus transport)

Reemplaza el scaffold del echobot por la plataforma completa de bots traida
desde ~/DataProyects/Github/agents_and_robots tras la operacion Matrix-out:
los bots ya no hablan por Matrix sino por el bus unibus (modelo todo-rooms +
E2E via shell/transportunibus sobre github.com/enmanuel/unibus/pkg/client).

- go.mod: replace de unibus -> ../unibus y de fn-registry -> ../../../.. (paths
  relativos reajustados a la nueva ubicacion dentro de fn_registry).
- app.md: bump a 0.2.0, descripcion + arquitectura + comandos + gotchas reales.
- modulo Go conservado como github.com/enmanuel/agents (sin reescribir imports).

agents_and_robots queda archivado como museo de la era Matrix.
This commit is contained in:
agent
2026-06-07 11:50:13 +02:00
parent bb5b0e09b1
commit fc644ecd6e
308 changed files with 38829 additions and 474 deletions
+57
View File
@@ -0,0 +1,57 @@
package transport
import (
"encoding/json"
"fmt"
"os"
)
// Kind identifies which messaging fabric a bot runs on.
type Kind string
const (
// KindMatrix is the proven Matrix (mautrix) path — the default, so master
// stays on the battle-tested transport.
KindMatrix Kind = "matrix"
// KindUnibus routes the bot over the unibus message bus.
KindUnibus Kind = "unibus"
)
// Select chooses a bot's transport. unibus is used only when the global feature
// flag is enabled AND the bot has opted in; otherwise Matrix. This is the
// branch-by-abstraction toggle: with the flag on, bots migrate to unibus one at
// a time by opting in, while every other bot keeps speaking Matrix unchanged.
func Select(flagEnabled, botOptIn bool) Kind {
if flagEnabled && botOptIn {
return KindUnibus
}
return KindMatrix
}
// flagsFile mirrors dev/feature_flags.json (see .claude rule feature_flags.md).
type flagsFile struct {
Flags map[string]struct {
Enabled bool `json:"enabled"`
Issue string `json:"issue"`
Description string `json:"description"`
} `json:"flags"`
}
// FlagEnabled reports whether the named feature flag is enabled in the given
// dev/feature_flags.json file. A missing file or missing flag reads as false
// (fail-safe: default to the Matrix path), not an error — only malformed JSON
// surfaces an error.
func FlagEnabled(path, name string) (bool, error) {
data, err := os.ReadFile(path)
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, fmt.Errorf("transport: read flags %q: %w", path, err)
}
var f flagsFile
if err := json.Unmarshal(data, &f); err != nil {
return false, fmt.Errorf("transport: parse flags %q: %w", path, err)
}
return f.Flags[name].Enabled, nil
}
+60
View File
@@ -0,0 +1,60 @@
package transport
import (
"os"
"path/filepath"
"testing"
)
// TestSelect covers the branch-by-abstraction toggle: unibus only when the flag
// is on AND the bot opted in; Matrix in every other combination (the default,
// so unmigrated bots keep working).
func TestSelect(t *testing.T) {
cases := []struct {
flag, optIn bool
want Kind
}{
{true, true, KindUnibus},
{true, false, KindMatrix},
{false, true, KindMatrix},
{false, false, KindMatrix},
}
for _, c := range cases {
if got := Select(c.flag, c.optIn); got != c.want {
t.Errorf("Select(flag=%v, optIn=%v) = %q, want %q", c.flag, c.optIn, got, c.want)
}
}
}
func TestFlagEnabled(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "feature_flags.json")
const content = `{"flags":{"unibus-transport":{"enabled":true,"issue":"x"},"off":{"enabled":false}}}`
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
t.Fatalf("write flags: %v", err)
}
on, err := FlagEnabled(path, "unibus-transport")
if err != nil {
t.Fatalf("FlagEnabled: %v", err)
}
if !on {
t.Errorf("expected unibus-transport enabled")
}
off, err := FlagEnabled(path, "off")
if err != nil {
t.Fatalf("FlagEnabled off: %v", err)
}
if off {
t.Errorf("expected off flag disabled")
}
// Missing flag and missing file both read as false (fail-safe to Matrix).
if missing, err := FlagEnabled(path, "does-not-exist"); err != nil || missing {
t.Errorf("missing flag should be (false, nil), got (%v, %v)", missing, err)
}
if absent, err := FlagEnabled(filepath.Join(dir, "nope.json"), "x"); err != nil || absent {
t.Errorf("absent file should be (false, nil), got (%v, %v)", absent, err)
}
}
+88
View File
@@ -0,0 +1,88 @@
// Package transport defines the neutral boundary between an agent's core logic
// and the messaging fabric it runs on. It carries NO Matrix (mautrix) types, so
// the same agent code can be driven by Matrix today and by the unibus message
// bus tomorrow, selected per bot behind a feature flag (branch by abstraction).
//
// The two pieces are:
//
// - InboundMessage: a transport-neutral description of an incoming message.
// Both the Matrix listener and the unibus subscriber produce one of these.
// - Transport: the capability an agent depends on to receive messages and
// send replies. A Matrix adapter and a unibus adapter both implement it.
package transport
import "context"
// InboundMessage is a transport-neutral incoming message. It is the single type
// an agent's message handler receives, regardless of whether the underlying
// fabric is Matrix or unibus. It deliberately avoids any mautrix type.
type InboundMessage struct {
// RoomID identifies the conversation on the transport (a Matrix room id or a
// unibus room id). Replies are addressed back to it.
RoomID string
// Subject is the bus subject the message arrived on (unibus). Empty for
// transports that do not have a subject address space (Matrix).
Subject string
SenderID string // stable id of the sender (Matrix user id / unibus endpoint id)
SenderName string // human-friendly display name, when the transport knows it
// MsgID is the unique id of this message on its transport: a Matrix event id
// or a unibus frame MsgID. Used as the reply/thread anchor.
MsgID string
ThreadID string // root message id of the thread, empty if not threaded
ReplyTo string // message id this message replies to, empty if none
Body string // plaintext body / content of the message
Command string // parsed command name (e.g. "deploy"), empty if not a command
Args []string // parsed command arguments
PowerLevel int // sender power level where the transport models one (Matrix); 0 otherwise
IsDirectMsg bool // the message is a direct/1:1 message to the bot
IsMention bool // the message addresses/mentions the bot
}
// OutboundReply is a transport-neutral outgoing reply.
type OutboundReply struct {
RoomID string // conversation to reply into
Subject string // bus subject to publish to (unibus); ignored by Matrix
ReplyTo string // message id being replied to (renders as a reply)
ThreadID string // thread root to keep the reply inside, empty for top-level
Markdown string // reply body, in markdown
}
// Handler processes one inbound message. It is the callback an agent registers
// with a Transport via Run.
type Handler func(ctx context.Context, in InboundMessage)
// Transport is the messaging fabric an agent depends on. Implementations:
// - a Matrix adapter wrapping the existing mautrix client + listener;
// - a unibus adapter over github.com/enmanuel/unibus/pkg/client.
//
// An agent core that depends only on Transport (not on *mautrix.Client) can be
// pointed at either fabric without code changes.
type Transport interface {
// Run delivers each inbound message to handler until ctx is cancelled. It
// blocks for the lifetime of the subscription and returns ctx.Err() (or a
// transport error) when it stops.
Run(ctx context.Context, handler Handler) error
// Reply sends a reply addressed by the OutboundReply envelope.
Reply(ctx context.Context, out OutboundReply) error
// Send posts a standalone markdown message to a conversation (no reply anchor).
Send(ctx context.Context, roomID, markdown string) error
// Close releases the underlying connection.
Close() error
}
// PresenceController is an optional capability for transports that model online
// presence (Matrix). unibus does not, so it simply does not implement this and
// callers type-assert for it.
type PresenceController interface {
SetPresence(ctx context.Context, online bool) error
}
// TypingController is an optional capability for transports that model typing
// indicators (Matrix). Callers type-assert for it.
type TypingController interface {
SetTyping(ctx context.Context, roomID string, typing bool) error
}