Foundation relay

Foundation Relay (channel adapters)

  • *rea:*Foundation
  • *ath:*services/foundation/relay
  • *ind:*Go module (interface + scaffolded adapters)
  • *tatus:*scaffold complete — interface + * adapter skeletons* wire protocols pending (tracked under KRELAY–KRELAY + KRELAY- Telegram migration)

Role in the stack

The Kode Relay server in services/ai/kode/platform/relay speaks one WebSocket protocol to Kode frontends (CLI, TUI, mobile, desktop, web). To extend the same persistentsession model to *hirdparty messaging channels*— Slack, Discord, WhatsApp, Signal, Matrix, Microsoft Teams, iMessage, Google Chat — the relay needs a uniform bridge layer.

services/foundation/relay is that layer. It is intentionally *I-free*and lives under services/foundation/ (not services/ai/) because channel I/O is reusable infrastructure: the same Slack adapter could serve a non-AI bot, an alerting daemon, or a CRM webhook proxy. Per RFC-003, code consumed by multiple sectors via API/import sits in services/foundation/.

The module exports a single contract — adapter.Adapter — that every channel implementation satisfies. The Kode Relay server (the AI-specific consumer) imports concrete adapters and routes traffic; each adapter only knows its own platform.

Primary couplings

Module Direction Why
services/ai/kode/platform/relay imports relay Routes Kode traffic across channels
services/foundation/relay/adapter exported Shared interface + types + errors
Each adapters/<channel>/ imports adapter Compile-time var _ adapter.Adapter = (*Adapter)(nil)

No outbound dependencies on other Koder modules — adapters speak only to their respective third-party APIs (Slack Bolt, Discord Gateway, WhatsApp Cloud API, etc.).

Interface

type Adapter interface {
    Name() string
    Start(ctx context.Context) error
    Stop(ctx context.Context) error
    Send(ctx context.Context, msg OutboundMessage) (MessageID, error)
    Subscribe(ctx context.Context) (<-chan InboundMessage, error)
}

Plus shared types: OutboundMessage, InboundMessage, Body, File, Identity, MessageID. Sentinel errors: ErrNotImplemented, ErrInvalidConfig, ErrNotStarted.

Status (20260430)

Adapter Scaffold Config validation Wire protocol
slack yes BotToken + Socket/HTTP credentials + WebhookListenAddr (HTTP) + APIURL (test override) *ocket Mode (KRELAY) + HTTP Events mode (KRELAY) + files upload (KRELAY) shipped 20260430* URLpull file mode deferred
discord yes BotToken; ApplicationID + Intents optional + APIURL (test override) *ateway + REST shipped (KRELAY, 202604-30)* slash commands + file uploads deferred
whatsapp yes AccessToken + PhoneNumberID + VerifyToken + WebhookSecret + APIURL *loud API + webhook listener shipped (KRELAY, 202604-30)* media upload + interactive deferred
telegram not yet live in services/ai/kode/platform/kode-bridge (legacy); migration is *RELAY-*
signal yes RestEndpoint + PhoneNumber pending
matrix yes HomeserverURL + UserID + AccessToken pending
teams yes AppID + AppPassword + TenantID pending
imessage yes ServerURL + Password (BlueBubbles) pending
gchat yes CredentialsPath + ProjectID + PubsubSubscription pending

All 8 scaffolded adapters compile, satisfy the interface (asserted at compile time), validate their configs, and return ErrNotImplemented for the wire-protocol surfaces.

Subtickets opened from KRELAY

The original epic was decomposed in 20260430 into 8 actionable subtickets (KRELAY–KRELAY-) so each line of work — wire protocol, migration, inbox routing, CLI, deploy, credentials docs — can land independently:

Ticket Scope
*RELAY-* Slack wire protocol (slack-go/slack)
*RELAY-* Discord wire protocol (bwmarrin/discordgo)
*RELAY-* WhatsApp wire protocol (Cloud API; hand-rolled HTTP)
*RELAY-* Telegram migration: kode-bridgeadapters/telegram/
*RELAY-* Multi-channel inbox routing in Kode TUI
*RELAY-* kode relay status CLI command
*RELAY-* Deploy Slack + Discord in koder-relay LXC on s.r1
*RELAY-* Per-channel credentials docs in meta/context/credentials/

Why the legacy Telegram bridge stays put for now

services/ai/kode/platform/kode-bridge was the original Telegram implementation, written before this adapter framework existed. It ships in production today. Migrating it into adapters/telegram/ under the new contract is a separate work item (tracked in ticket projects/koder-stack/backlog/pending/024-kode-relay-multi-canal-adapters.md) and intentionally not part of this scaffold.

Reporter (KRELAY, 202604-30)

services/foundation/relay/reporter/ aggregates lifecycle + health signal across the configured adapter pool. The flagship consumer is kode relay status, but the library is presentation-agnostic so a Prometheus exporter, dashboard, or alerting daemon can reuse the snapshot shape.

r := reporter.New(slackAdapter, discordAdapter, whatsappAdapter)
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
for _, status := range r.Snapshot(ctx) {
    fmt.Println(status.Name, status.State, status.HealthError)
}

AdapterStatus.State is one of running | degraded | not_started | no_health_signal. The last bucket covers adapters that don't implement the optional adapter.HealthChecker interface — they get reported but with no liveness conclusion, so operators can tell scaffoldswithoutwire apart from genuinely stopped channels.

Three adapters implement HealthChecker today (slack, discord, whatsapp — the wired-up trio); the remaining five report no_health_signal until each lands its wire protocol.

A standalone binary lives at cmd/kode-relay/. kode-relay status (text + --json) probes every adapter and exits non-zero if any reported degraded. Integration into the main Kode CLI (kode relay status) is tracked under *RELAY-*— the services/ai/kode/app lock blocked it during KRELAY-'s session.

Tests

Each adapter ships ~7 unit tests covering config rejection paths, valid construction, and behavioural assertions for the wire protocol (or ErrNotImplemented while scaffolded). The reporter package adds 9 tests covering every State bucket plus context-deadline propagation. Total: *0 packages*(1 contract + 8 adapters + reporter), all green under go test -race -count=1 ./... from the module root. The standalone cmd/kode-relay binary is smoke-tested manually (no automated test until the conf loader lands and loadAdapters() returns a non-empty slice).

Source: ../home/koder/dev/koder/meta/docs/stack/modules/foundation-relay.md