Skip to main content
The bot is organized as a pnpm monorepo with three packages. Each package has a single responsibility, and the bot application assembles them via Effect’s Layer system.

Packages

@chat/discord-bot

The main application. Contains every feature and command: AutoThreads, AiResponse, Summarizer, Playground, Reminders, and more.

@chat/discord

The Discord infrastructure layer. Provides the gateway WebSocket connection, the REST client, channel and member caches, and configuration.

@chat/shared

Shared utilities used by any package. Currently exports TracerLayer, the OpenTelemetry setup for Honeycomb or a local OTLP collector.
@chat/discord-bot
  └─ depends on @chat/discord   (DiscordGatewayLayer, ChannelsCache, MemberCache, Messages)
  └─ depends on @chat/shared    (TracerLayer)

@chat/discord
  └─ wraps dfx                  (DiscordIxLive, DiscordRESTMemoryLive, DiscordConfig)

@chat/shared
  └─ wraps effect observability (OtlpTracer, Otlp)

Entry point

The application starts in packages/discord-bot/src/main.ts. It builds a single MainLive layer by merging every feature layer, provides the tracer and log-level layers, then hands it to NodeRuntime.runMain.
// packages/discord-bot/src/main.ts
const LogLevelLive = Layer.effect(
  References.MinimumLogLevel,
  Config.withDefault(Config.boolean("DEBUG"), false)
    .pipe(Config.map((debug) => (debug ? "All" : "Info")))
    .asEffect(),
)

const MainLive = Layer.mergeAll(
  AiResponse,
  AutoThreadsLive,
  DadJokesLive,
  NoEmbedLive,
  DocsLookupLive,
  IssueifierLive,
  NotificationsLayer,
  PlaygroundLive,
  RemindersLive,
  ReproRequesterLive,
  Summarizer.layer,
).pipe(Layer.provide(TracerLayer("discord-bot")), Layer.provide(LogLevelLive))

NodeRuntime.runMain(Layer.launch(MainLive))
Layer.launch acquires every layer’s resources and keeps the process alive. NodeRuntime.runMain attaches SIGINT/SIGTERM handlers for graceful shutdown.

Layer composition model

Every feature is a self-contained Effect Layer. A layer declares its own dependencies and wires them internally before being merged into MainLive.
1

Feature layer

Each feature exports a Layer (e.g. AutoThreadsLive, Summarizer.layer). It uses Layer.effectDiscard for fire-and-forget features or Layer.effect for services that expose an API to other features.
2

Discord infrastructure

Features that react to Discord events or call the API declare a dependency on DiscordGatewayLayer from @chat/discord. That single layer brings in the gateway WebSocket, the REST client, and bot application metadata.
3

Merge into MainLive

main.ts calls Layer.mergeAll to combine all feature layers. Because each layer resolved its own dependencies, MainLive has no remaining requirements — it is a Layer<never, never, never> that can be launched directly.
4

Runtime launch

Layer.launch(MainLive) is passed to NodeRuntime.runMain, which runs the Effect on the Node.js runtime and handles process lifecycle.

Dependency graph

NodeRuntime.runMain
  └─ Layer.launch(MainLive)
       ├─ AiResponse          → DiscordGatewayLayer, ChannelsCache, AiHelpers, EffectRepo
       ├─ AutoThreadsLive     → DiscordGatewayLayer, ChannelsCache, AiHelpers
       ├─ Summarizer.layer    → DiscordGatewayLayer, ChannelsCache, MemberCache, Messages
       ├─ DocsLookupLive      → DiscordGatewayLayer
       ├─ IssueifierLive      → DiscordGatewayLayer, Github
       ├─ ...other features...
       └─ TracerLayer("discord-bot") → Honeycomb / local OTLP

DiscordGatewayLayer (@chat/discord)
  ├─ DiscordIxLive           (dfx — gateway WebSocket + InteractionsRegistry)
  ├─ NodeHttpClient
  ├─ NodeSocket
  ├─ DiscordConfigLayer      (bot token + gateway intents)
  └─ DiscordApplication      (REST call to fetch bot application info)

Build docs developers (and LLMs) love