Skip to content

feat(gateway): pluggable NEXUS gateway with notification bridge and Discord rewrite#83

Open
Christiantyemele wants to merge 9 commits into
mainfrom
communication-slack
Open

feat(gateway): pluggable NEXUS gateway with notification bridge and Discord rewrite#83
Christiantyemele wants to merge 9 commits into
mainfrom
communication-slack

Conversation

@Christiantyemele

@Christiantyemele Christiantyemele commented May 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

Replaces the monolithic nexus-chat crate with a pluggable nexus-gateway crate that decouples domain logic (forge, vessel, pair-harness) from notification delivery, and introduces a notification bridge that routes all stakeholder events to Discord (and other channels).

Core Changes

  • New nexus-gateway crate with ChannelPlugin trait, Gateway struct, and channel-agnostic InboundMessage/OutboundMessage types — adding a new channel requires only implementing the trait, no gateway modifications
  • NotificationBridge — polls the SharedStore event ring buffer every 2s, maps domain events to OutboundMessages, and broadcasts them through the gateway to all registered channel plugins. Decouples forge/vessel/nexus from notification delivery entirely
  • Discord plugin rewrite — replaced ~700 lines of manual WebSocket handling with ~100 lines using twilight-gateway, which handles heartbeat, reconnection, resume, and session invalidation automatically. Rich embeds for PR opened, CI failures, merge blocked, conflicts detected, worker suspended, human intervention, etc.
  • Comprehensive OutboundMessageType variantsPrOpened, PrMerged, CiFailed, CiTimeout, CiMissing, MergeBlocked, ConflictsDetected, TicketFailed, TicketExhausted, FuelExhausted, WorkerSuspended, HumanIntervention
  • CommandInterpreter — extracted from NexusNode with layered interpretation: pattern match → RAG stub → LLM fallback. RAG KnowledgeStore trait with stub implementation for future vector DB integration
  • ReAct loop — iterative Reason→Act→Observe loop for processing human messages, replacing single-shot LLM calls
  • Background gateway processor — Nexus processes human messages even when the flow is at other nodes, with AgentRunner reuse across iterations to avoid repeated MCP server spawning

Event → Notification Mapping

Source Event Notification
Forge work_completed (pr_opened) PrOpened
Forge work_completed (other) AgentCompleted
Forge work_suspended WorkerSuspended
Forge human_intervention HumanIntervention
Forge work_failed / ticket_exhausted / fuel_exhausted TicketFailed / TicketExhausted / FuelExhausted
Vessel ticket_merged PrMerged
Vessel ci_failed / ci_timeout / ci_missing CiFailed / CiTimeout / CiMissing
Vessel merge_blocked / conflicts_detected MergeBlocked / ConflictsDetected
Nexus work_assigned / worker_assigned WorkflowStarted / AgentAssigned

Supporting Changes

  • agent-forge: Emits store.emit("forge", "work_completed", ...) on PR outcome, store.emit("forge", "work_suspended", ...) on blocked work, etc.
  • agent-vessel: VesselNotifier emits structured events for CI failures, merge outcomes, conflict detection
  • agent-nexus: Emits work_assigned and worker_assigned events via bridge; background gateway processor with heartbeat
  • pair-harness: Emits work_completed notification events on pair lifecycle completion
  • binary: Wires Gateway + NotificationBridge into main, registers channel plugins from GatewayConfig::from_env()
  • .env.example: Added NEXUS_GATEWAY_* env vars for Slack, Discord, WhatsApp configuration

Testing

  • All 99 tests pass (cargo test)
  • cargo clippy --all-targets -- -D warnings clean
  • cargo fmt -- --check clean

AgentFlow added 7 commits May 13, 2026 06:38
- Add DiscordClient with embed-based rich messages via Discord Bot API
- Add WhatsAppClient using WhatsApp Cloud API for business messaging
- Update ChatConfig with multi-channel fields and active_channels() method
- Refactor HumanChannel to aggregate multiple ChatClient implementations
- Update chat_loop to poll all configured channels every 2 seconds
- Add ChannelType enum for type-safe channel identification
- Update .env.example with Discord and WhatsApp environment variables
- Update NEXUS persona and skill documentation for multi-channel support
…ion/prefix filtering and command parsing fixes

- Add Discord Gateway WebSocket client (discord_gateway.rs) with heartbeat,
  automatic reconnection, and opcode 1 (Heartbeat Request) handling.
- Filter messages: respond only when @mentioned or addressed by bot username.
- Strip mentions and username prefix before passing content to interpreter.
- Add background command processor in agentflow.rs so human commands are
  processed even while the flow is at forge_pair or vessel nodes.
- Fix chat_loop shutdown: use tokio::sync::watch to keep Gateway alive
  instead of dropping the mpsc sender immediately.
- Fix command prefix matching to accept common typos (assigne, assigning).
- Add extract_two_workers() and read_worker() helpers to parse space-separated
  worker IDs like "forge 1" as "forge-1".
- Add helpful reply message when a command cannot be interpreted.
- Improve LLM interpreter prompt with strict flat-string rules and examples.
- Fix ack_message to compare deterministic fields (user_id, channel_id, text)
  instead of timestamp which drifts through JSON round-trips.
…ifecycles

Two root causes prevented forge-2 from spawning alongside forge-1:

1. Git worktree creation race condition: BatchNode::run_batch() runs
   exec_one for all workers concurrently via join_all. Both pairs called
   WorktreeManager::create_worktree() simultaneously on the same .git
   directory, causing index.lock contention that silently killed forge-2.

2. Async runtime starvation: ForgeSentinelPair::run() is a long-lived
   event loop. When two pairs ran via join_all on the same tokio runtime,
   the first pair's loop starved the second.

Fixes:
- Add process-wide std::sync::Mutex (GIT_WORKTREE_MUTEX) around
  synchronous git operations in create_worktree, released before async
  configure_git_identity to avoid Send issues across .await points.
- Add retry_git_operation() with exponential backoff for transient lock
  contention from external git processes.
- Add is_git_lock_error() to detect git lock patterns in stderr.
- Split create_worktree into sync (create_worktree_sync) and async phases.
- Run ForgePairNode::exec_one inside tokio::task::spawn_blocking with a
  dedicated current_thread runtime per pair.
- Move 'already exists' fallback inside retry closure to eliminate dead code.
- Add notification bridge events for forge lifecycle state changes.
- Add 7 unit tests for lock detection and retry logic.

Verified: both forge-1 and forge-2 now create worktrees and make progress
when running concurrently.
…R events and ticket assignments

- Add NotificationBridge that polls SharedStore events and routes them as
  OutboundMessages through the gateway to Discord/Slack/WhatsApp
- Bridge maps forge work_completed → PrOpened, vessel events → CiFailed/
  MergeBlocked/ConflictsDetected, nexus worker_assigned → AgentAssigned
- Rewrite Discord plugin with twilight-gateway for reliable WebSocket handling
- Add comprehensive OutboundMessageType variants for all stakeholder events
- Discord plugin renders rich embeds for PR opened, CI failures, merge blocked
- Fix clippy: remove unused GatewayConfig import, allow dead_code on ReActDecision::Act
- Clean up redundant comments across nexus-gateway, agent-forge, agent-vessel
@Christiantyemele Christiantyemele changed the title Communication slack feat(gateway): pluggable NEXUS gateway with notification bridge and Discord rewrite May 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant