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
+272
View File
@@ -0,0 +1,272 @@
// Package agents defines the Agent runtime that ties core and shell together.
package agents
import (
"context"
"fmt"
"log/slog"
"sync"
"time"
"github.com/enmanuel/agents/internal/config"
"github.com/enmanuel/agents/pkg/acl"
"github.com/enmanuel/agents/pkg/command"
"github.com/enmanuel/agents/pkg/decision"
coretypes "github.com/enmanuel/agents/pkg/llm"
"github.com/enmanuel/agents/pkg/memory"
"github.com/enmanuel/agents/pkg/personality"
"github.com/enmanuel/agents/pkg/sanitize"
"github.com/enmanuel/agents/shell/bus"
shellcron "github.com/enmanuel/agents/shell/cron"
"github.com/enmanuel/agents/shell/effects"
shellknowledge "github.com/enmanuel/agents/shell/knowledge"
shellmcp "github.com/enmanuel/agents/shell/mcp"
shellskills "github.com/enmanuel/agents/shell/skills"
"github.com/enmanuel/agents/shell/ssh"
"github.com/enmanuel/agents/shell/transportunibus"
"github.com/enmanuel/agents/tools"
toolmemory "github.com/enmanuel/agents/tools/memorytools"
)
const (
defaultMaxToolIterations = 5
defaultWindowSize = 20
)
// CommandHandler executes a built-in command and returns the response text.
type CommandHandler func(ctx context.Context, msgCtx decision.MessageContext) string
// Agent is the assembled runtime: pure core + impure shell.
type Agent struct {
cfg *config.AgentConfig
personality personality.Personality
rules []decision.Rule
llm coretypes.CompleteFunc // nil when no LLM configured (simple_bot)
transport *transportunibus.Transport
sender effects.Sender // bus-backed sender used for replies and tools
runner *effects.Runner
toolReg *tools.Registry
logger *slog.Logger
mcpManager *shellmcp.Manager // nil when MCP client is disabled
// Lifecycle — cancel stops this agent individually; done is closed when Run returns.
cancel context.CancelFunc
done chan struct{}
// Access control
acl acl.ACL
// Commands — handlers keyed by canonical name; cmdAliases maps alias → canonical
commands map[string]CommandHandler
cmdAliases map[string]string // alias → canonical name
customSpecs []command.Spec // specs from RegisterCommand (for !help)
startTime time.Time
// Memory
windows map[string]memory.Window
windowsMu sync.RWMutex
memStore memory.Store // nil when memory is disabled
windowSize int
roomCtx *toolmemory.RoomContext
// Prompt-commands — loaded from prompts/*.md at startup
promptCmds map[string]string // name → prompt content
// Knowledge store — non-nil when knowledge is enabled
knowledgeStore *shellknowledge.FileStore
// Shared knowledge store — non-nil when shared_knowledge is enabled
sharedKnowledgeStore *shellknowledge.FileStore
// Skills loader — non-nil when skills are enabled
skillLoader *shellskills.Loader
// Sanitization options — nil when sanitization is disabled
sanitizeOpts *sanitize.Options
// Bus — set via SetBus() when running under the unified launcher
agentBus *bus.Bus
// Scheduler — nil when no schedules are configured
scheduler *shellcron.Scheduler
}
// New assembles an Agent from its config, rules, pre-resolved ACL, and logger.
// The ACL is resolved externally (e.g. from security/ YAML files) and injected here.
// Pass acl.ACL{} (empty) for open access (no restrictions).
func New(cfg *config.AgentConfig, rules []decision.Rule, agentACL acl.ACL, logger *slog.Logger) (*Agent, error) {
// unibus transport — discovers rooms, receives messages, sends replies.
tr, err := transportunibus.New(cfg.Bus, logger)
if err != nil {
return nil, fmt.Errorf("unibus transport: %w", err)
}
sender := tr.Sender()
// SSH executor
sshExec := ssh.NewExecutor(cfg.SSH, logger)
// LLM client — optional; if no provider is configured, the agent runs as simple_bot
llmFunc, err := initLLM(cfg, logger)
if err != nil {
return nil, err
}
// Effects runner
runner := effects.NewRunner(sender, sshExec, logger)
// Resolve base data path for this agent
dataBase := resolveDataBase(cfg)
logger.Debug("data base path", "path", dataBase)
// Memory subsystem
memInit, err := initMemoryStore(cfg.Memory.Enabled, cfg.Memory.WindowSize, cfg.Memory.DBPath, dataBase, logger)
if err != nil {
return nil, err
}
// Tool dependencies (knowledge, MCP, skills)
deps := initToolDeps(cfg, dataBase, logger)
if !agentACL.Empty() {
logger.Info("acl enabled (centralized security policy)")
}
// Tool registry — register tools enabled in config
roomCtx := &toolmemory.RoomContext{}
toolReg := buildToolRegistry(cfg, sshExec, sender, memInit.store, deps.kStore, deps.sharedKStore, deps.mcpManager, deps.skillLoader, deps.skillExecutor, roomCtx, logger)
// Rate limiting for tools
initRateLimiter(cfg, toolReg, logger)
a := &Agent{
cfg: cfg,
acl: agentACL,
personality: personality.FromConfig(cfg.Personality),
rules: rules,
llm: llmFunc,
transport: tr,
sender: sender,
runner: runner,
toolReg: toolReg,
logger: logger,
mcpManager: deps.mcpManager,
done: make(chan struct{}),
commands: make(map[string]CommandHandler),
cmdAliases: command.BuiltinNames(),
startTime: time.Now(),
windows: make(map[string]memory.Window),
memStore: memInit.store,
knowledgeStore: deps.kStore,
sharedKnowledgeStore: deps.sharedKStore,
skillLoader: deps.skillLoader,
windowSize: memInit.windowSize,
roomCtx: roomCtx,
}
// Configure sanitization if enabled
if cfg.Security.Sanitize.Enabled {
minSev := parseSeverity(cfg.Security.Sanitize.MinSeverity)
a.sanitizeOpts = &sanitize.Options{
Mode: sanitize.ParseMode(cfg.Security.Sanitize.Mode),
MinSeverity: minSev,
DisabledPatterns: cfg.Security.Sanitize.DisabledPatterns,
}
logger.Info("input sanitization enabled",
"mode", a.sanitizeOpts.Mode,
"min_severity", minSev,
)
}
// Register built-in command handlers
a.registerBuiltinCommands()
// Load prompt-commands from prompts/ directory
a.loadPromptCommands()
// Register memory_clear_context with self as WindowClearer (after a is created)
if cfg.Tools.Memory.Enabled && memInit.store != nil {
toolReg.Register(toolmemory.NewMemoryClearContext(a, roomCtx))
}
// Cron scheduler — only when schedules are configured
if len(cfg.Schedules) > 0 {
a.scheduler = shellcron.New(cfg.Schedules, sender, llmFunc, cfg.LLM.Primary.Model, logger)
logger.Info("cron scheduler configured", "schedules", len(cfg.Schedules))
}
return a, nil
}
// RegisterCommand adds a custom command handler for this agent.
// The spec provides metadata (aliases, description, usage) for !help.
// Must be called before Run().
func (a *Agent) RegisterCommand(spec command.Spec, handler CommandHandler) {
a.commands[spec.Name] = handler
a.cmdAliases[spec.Name] = spec.Name
for _, alias := range spec.Aliases {
a.cmdAliases[alias] = spec.Name
}
a.customSpecs = append(a.customSpecs, spec)
a.logger.Info("command_registered", "command", spec.Name, "aliases", spec.Aliases)
}
// SetBus attaches the agent to the inter-agent bus for orchestration.
// Must be called before Run().
func (a *Agent) SetBus(b *bus.Bus) {
a.agentBus = b
}
// Stop cancels this agent's individual context, causing Run to return.
// Safe to call multiple times.
func (a *Agent) Stop() {
if a.cancel != nil {
a.cancel()
}
}
// Done returns a channel that is closed when Run has returned.
func (a *Agent) Done() <-chan struct{} {
return a.done
}
// Run starts the agent's transport loop. Blocks until ctx is cancelled.
func (a *Agent) Run(ctx context.Context) error {
ctx, a.cancel = context.WithCancel(ctx)
defer close(a.done)
if a.transport != nil {
defer a.transport.Close()
}
if a.memStore != nil {
defer a.memStore.Close()
}
if a.knowledgeStore != nil {
defer a.knowledgeStore.Close()
}
if a.sharedKnowledgeStore != nil {
defer a.sharedKnowledgeStore.Close()
}
if a.mcpManager != nil {
defer a.mcpManager.Close()
}
a.logger.Info("agent starting",
"id", a.cfg.Agent.ID,
"name", a.cfg.Agent.Name,
"endpoint", a.transport.Endpoint(),
"tools", a.toolReg.Names(),
)
// Start bus listener if connected to the orchestration bus
if a.agentBus != nil {
ch := a.agentBus.Subscribe(bus.AgentID(a.cfg.Agent.ID))
go a.listenBus(ctx, ch)
a.logger.Info("bus listener started")
}
// Start cron scheduler in background goroutine (blocks until ctx cancelled)
if a.scheduler != nil {
go a.scheduler.Start(ctx)
}
return a.transport.Run(ctx, a.handleInbound)
}