Skip to content

Latest commit

 

History

History
165 lines (142 loc) · 8.69 KB

MIGRATION_PLAN.md

File metadata and controls

165 lines (142 loc) · 8.69 KB

Cardinal-System — Plan de migración a sistema distribuido con agente híbrido

Generado en sesión Copilot CLI. Retomar desde WSL (con toolchain Swift instalado) para poder compilar/testear (swift build, swift test) — en Windows puro no había swift disponible.

Decisión de arquitectura (confirmada con el usuario)

Modelo híbrido: un agente "líder" (Ollama-backed) toma decisiones de alto nivel y delega en agentes locales por nodo/dispositivo. Cada nodo puede resolver localmente lo que sabe resolver; si no puede, escala la decisión al líder.

No es ni "cerebro central puro" ni "swarm puro de agentes iguales": hay jerarquía, pero cada nodo conserva autonomía y su propio ciclo de decisión.

Contexto de partida

  • Paquete SwiftPM (Package.swift, tools 5.9), target Cardinal-System (librería)

    • Run (ejecutable demo).
  • Core viejo: EventManager/ActionExecutor/PermissionManager eran structs con arrays mutables (uno de ellos, PermissionManager, ni siquiera tenía init público utilizable — bug real).

  • WebSocket server ya existente vía SwiftNIO clásico (ChannelInboundHandler, callbacks síncronos) en Sources/Cardinal-System/Network/test.swift.

  • El usuario pegó un diseño de agente (Ollama vía AsyncHTTPClient) con:

    • enum Action (respond / callTool / createSkill)
    • Decision (wire format JSON) + ActionSchema (JSON Schema para Ollama format)
    • OllamaClient (chat no-streaming contra POST /api/chat)
    • PromptBuilder (system prompt con manifest de tools)
    • enum Cardinal (loop de decisión: step())

    ⚠️ Colisión de nombres con el core actual: ya existen protocol Cardinal (entry point de la app) y protocol CardinalAction/Command. Al portar este código hay que renombrar:

    • enum Action → AgentAction
    • enum Cardinal (el loop) → CardinalAgent (actor)

Estado de lo YA HECHO en esta sesión (revisar/compilar en WSL)

  1. Package.swift: agregada dependencia async-http-client (AsyncHTTPClient) al target Cardinal-System.
  2. Sendable en el core:
    • Information: Sendable
    • Event.action → @Sendable () -> Void
    • Listener: Information, Sendable, handleEvent(_:) ahora async
    • CardinalAction: Sendable, execute(_:) ahora async, eventAssociated pasó de get set a get (los var existentes lo siguen satisfaciendo)
    • Context: Sendable
  3. Actors (reemplazan structs con estado mutable inseguro):
    • EventManager → actor. Se agregó subscribe() -> AsyncStream<Event> para pub/sub real (además de los Listeners legacy). Esto es la base para que NetworkManager (pendiente) se suscriba y reenvíe eventos a nodos peer.
    • ActionExecutor → actor con static let shared. Se agregó toolManifest() -> [ToolSpec] (pendiente crear ToolSpec — ver abajo) para exponer acciones registradas como tools del agente.
    • PermissionManager → actor, arreglado el bug de init (antes permissions: [Permission] sin default ni init público). Se agregaron add, remove, isGranted.
  4. Conformidades actualizadas (por los cambios de arriba):
    • Sources/Run/Actions/{ExampleAction,LogAction,PrintAction}.swift: execute ahora async.
    • Sources/Run/Events/PrintEvent.swift: class → struct (Sendable real), action: @Sendable () -> Void.
    • Sources/Run/Events/PrintListener.swift: class → struct, handleEvent ahora async.
    • Sources/Run/Events/ExampleEvent.swift: action ahora @Sendable.
    • Sources/Cardinal-System/Network/test.swift:
      • class PrintEvent → struct PrintEvent (duplicado local a este archivo, distinto del de Run, mismo patrón).
      • execute/registro de acciones ahora vía Task { await ActionExecutor.shared... } porque los callbacks de NIO (channelRead, init()) son síncronos y ActionExecutor ahora es actor-isolated.
    • Sources/Run/ALO.swift: run() ya es async, así que las llamadas a ActionExecutor.shared.registerAction(...) se hacen con await directo (no hace falta Task {} ahí).
    • Tests/.../EventManagerTest.swift: test convertido a async, usa await en createEvent/getAllEvent/getEvent; TestEvent.action ahora @Sendable.

Pendiente (todavía NO implementado — continuar en WSL)

Ver tabla de todos abajo. Orden sugerido:

1. agent-module (depende de add-dep-asynchttp ✅ y sendable-core ✅)

Crear Sources/Cardinal-System/Agent/:

  • ToolSpec.swift — struct Sendable+Codable (name, description, argsHint).
  • OllamaClient.swift — portar tal cual el OllamaClient pegado por el usuario (ya es Sendable, usa AsyncHTTPClient — no requiere casi cambios).
  • AgentAction.swift — el enum Action renombrado a AgentAction (evitar choque con el dominio existente).
  • Decision.swift + ActionSchema — igual que el original, revisar que CardinalError no choque con otro tipo de error ya existente en el proyecto (no lo hay, según lo explorado).
  • PromptBuilder.swift — igual.
  • CardinalAgent.swift — el enum Cardinal { static func step(...) } pegado, renombrado a CardinalAgent (actor o enum con funcs estáticas — evaluar si conviene actor si va a mantener historial de conversación por nodo; si es puramente stateless, un enum/struct con funcs estáticas basta, pero para mantener history por sesión probablemente conviene un actor con estado mutable de conversación).
  • Puente CardinalAction (dominio) ↔ ToolSpec (agente): cada acción registrada en ActionExecutor ya expone toolManifest() — usar eso como fuente de tools disponibles para CardinalAgent.

2. skill-registry (depende de agent-module)

Sources/Cardinal-System/Agent/SkillRegistry.swift — actor que persiste skills creadas dinámicamente (AgentAction.createSkill) vía Storage (reusar ConfigStorage/Yams o un nuevo SkillStorage), y las suma al toolManifest() combinado.

3. network-envelope

Sources/Cardinal-System/Network/Envelope.swift — mensajes Codable+Sendable para el mesh: eventBroadcast(Event), toolManifest([ToolSpec]), delegateRequest(task: String, fromNode: UUID), delegateReply(...). Esto es lo que viaja por los WebSockets entre nodos.

4. node-role (depende de eventbus-actor ✅, network-envelope, agent-module)

Sources/Cardinal-System/System/Node.swift — actor que agrupa:

  • id: UUID, role: NodeRole (.leader / .worker)
  • agent: CardinalAgent local
  • eventBus: EventManager (ya actor)
  • network: NetworkManager (nuevo, envuelve WebSocketServer/cliente)

Lógica clave: el agente local intenta resolver (.respond/.callTool); si el modelo no tiene un tool adecuado o la tarea excede su alcance, produce (nuevo caso a añadir en AgentAction) .delegate(task: String) y el Node lo reenvía al líder vía Envelope.delegateRequest.

Nota: la elección de líder puede empezar simple (config estático, un nodo marcado role: .leader en su config YAML vía ConfigStorage), sin gossip/ elección dinámica por ahora.

5. wire-example (depende de node-role)

Actualizar Sources/Run/ALO.swift para instanciar un Node (worker) real, mostrando: registro de tools locales, conexión al líder, y un caso de delegación.

Riesgos / cosas a vigilar al compilar en WSL

  • AllWebSockets.sockets (static var mutable, en test.swift) no es concurrency-safe; con Swift 5.9 en modo mínimo probablemente solo dé warnings, no errores — pero si se activa modo estricto de concurrencia puede requerir marcarlo como actor o nonisolated(unsafe).
  • Revisar que NIO.Channel sea Sendable/@preconcurrency en la versión de swift-nio usada (2.62.0+) al compilar WebSocket (struct que envuelve Channel).
  • Los dos PrintEvent/PrintAction (uno en target Cardinal-System, otro en Run) son intencionalmente independientes (distintos módulos) — no deberían chocar, pero confirmar al compilar.
  • eventAssociated pasó de { get set } a { get } en CardinalAction: los conformantes actuales declaran var eventAssociated: Event.Type = ... (var con default), que sigue siendo válido para satisfacer un requirement de solo lectura.

Comandos para retomar en WSL

cd /mnt/c/Users/acabrera/source/repos/Cardinal-System
swift build
swift test --parallel

Corregir los errores de compilación que aparezcan (se esperan algunos ajustes menores de await/async en call sites que no se pudieron verificar sin toolchain) y continuar con los todos pendientes de la tabla de arriba.