← Home

SmolPaws Slack Message Relay live canary proven

A greenfield Slack bridge for the durable SmolPaws Message Relay architecture.
Source: apps/slack/  ·  Core: messageRelay.ts  ·  Outbound: outboundRelay.ts  ·  Dispatcher: deliveryDispatcher.ts

Status · 2026-08-16: the complete Liberty Labs Socket Mode round trip has been proven through the TypeScript agent-server, real agent loop, durable outbox sync, Delivery Dispatcher, and Slack Web API. The isolated canary used a deterministic test LLM; the ordinary long-running paws process still needs its real-provider production soak.

Authority: current code, executable tests, the agent-server EventLog, and Message Relay SQLite records. This page describes the architecture. A visible Slack reply alone is not sufficient evidence that the intended process handled it.

Current architecture

apps/slack is intentionally greenfield. It runs as a standalone Socket Mode process, owns its Bolt connection and SlackRelayRuntime directly, and does not inherit BaseBridgeAdapter, join the shared bridge loader, or fall back to the legacy /turns path on port 8788.

flowchart LR
    U["Slack user"] --> S["SlackBridge / paws\nSocket Mode"]
    S --> I["Message Relay intake\nSQLite"]
    I --> A["TypeScript OpenHands\nagent-server :8790"]
    A --> E["durable EventLog"]
    E --> R["Outbound Relay\nsyncDeliveryOutbox()"]
    R --> O["delivery outbox\nSQLite"]
    O --> D["Delivery Dispatcher"]
    D --> T["SlackDeliveryTarget"]
    T --> U
    

Ownership

FactOwner
Conversation events and agent executionTypeScript agent-server EventLog
External intake, ordering, retries, claims, and delivery outcomesMessage Relay SQLite store
Slack connection, formatting, thread coordinates, and network sendSlackBridge and SlackDeliveryTarget

The agent-server remains upstream-shaped. External queue semantics, Slack retries, and uncertain network effects stay in SmolPaws-owned Relay infrastructure.

Message flow

sequenceDiagram
    participant U as Slack user
    participant S as SlackBridge
    participant M as Message Relay
    participant A as TS agent-server
    participant R as Outbound Relay
    participant D as Delivery Dispatcher
    participant API as Slack Web API

    U->>S: DM or @paws mention
    S->>S: policy, short-lived gate, thread context
    S->>M: accept message
    M->>M: persist intake row
    M-->>S: durable acceptance complete
    M->>A: deterministic user event + run=true
    A->>A: agent loop writes durable events
    R->>A: search events from cursor
    R->>M: syncDeliveryOutbox()
    M->>M: insert idempotent delivery row
    D->>M: claim lane-head delivery
    D->>M: mark send_attempted
    D->>API: chat.postMessage
    API-->>D: Slack message ts
    D->>M: settle done + external_message_id
    API-->>U: reply appears once
    

Components

ComponentResponsibility
apps/slack/src/adapter.tsStandalone Bolt/Socket Mode lifecycle and Slack Web API wiring
slackHandler.tsAccess policy, normalization, mention stripping, bounded thread context, and a short-lived duplicate gate
relayRuntime.tsSlack-hosted intake, reconciliation, outbox synchronization, and bounded delivery workers
MessageRelayLane identity, durable intake, deterministic event append, and syncDeliveryOutbox()
OutboundRelayCatches EventLog changes up into the outbox and performs a bounded dispatch drain
DeliveryDispatcherClaim → validate → send fence → external call → fenced settlement
SlackDeliveryTargetSends chunks to the durable Slack channel/thread coordinates

Why syncDeliveryOutbox()?

The Outbound Relay repeatedly catches a conversation up from its durable agent-event cursor. For the first Slack generation, the successful terminal finish observation becomes one durable delivery row. Repeating the sync is safe because the source identity combines the originating event and destination lane.

agent event id + destination lane
              ↓
unique delivery source_key
              ↓
replay becomes a no-op

The name is intentionally plainer than “projector”: the method synchronizes an outbox. It does not itself contact Slack.

Delivery semantics

The Dispatcher validates work before the external boundary. Immediately before calling Slack, it durably sets send_attempted = true.

SituationOutcome
Invalid payload or no registered targetKnown failed work before any network send
Claim expires before a send attemptSafe to make ready again
Exception after the send fencedelivery_unknown; never blindly retry
Slack confirms the messagedone with Slack ts as external_message_id

Idempotency boundaries

Liberty Labs proof

On 2026-08-16, an isolated, self-expiring canary at fork commit a69456fc6f818f23ecb6e2e064f3e03fceeafaf4 completed the real Slack transport path. The observed response was:

RELAY-LIVE-a69456fc6f81

The response travelled through real Socket Mode ingress, durable Relay intake, the real TypeScript agent-server and agent loop, a deterministic test LLM calling finish, syncDeliveryOutbox(), Delivery Dispatcher, Slack Delivery Target, and chat.postMessage. The canary used separate state, port, checkout, and SQLite storage, and stopped itself automatically.

Open the Liberty Labs canary thread.

What remains

The architectural round trip is proven. The remaining boundary is operational rather than structural:

  1. restart the ordinary host so the obsolete Slack Socket Mode connection is released;
  2. run standalone paws against port 8790 with the configured real LLM profile;
  3. verify the durable intake/delivery rows and EventLog;
  4. soak restart and delivery_unknown reconciliation behavior before calling it the production Slack path.

A response containing 🐾 Done — nothing to report back. identifies the old BaseBridgeAdapter//turns fallback. It is not proof of the Message Relay path.

Verification layers

LayerEvidence
Message Relay state machinesReal-SQLite tests for dedup, ordering, fencing, retries, replay, and ambiguous sends
Agent-server seamReal in-process TypeScript agent-server tests with deterministic LLM output
Outbound pathOutbox sync, Dispatcher, Slack target, thread routing, send fence, and external-ID assertions
Workspace canaryReal Liberty Labs Socket Mode round trip with the unique Relay response token

Operational commands and read-only inspection queries live in docs/slack/instructions.md.

← Back to architecture index