Cogitator
Channels

Middleware

Intercept and filter messages with pairing, rate limiting, and custom middleware.

Overview

Gateway middleware intercepts messages before they reach your agent. Use it for security, rate limiting, logging, and custom processing.

interface GatewayMiddleware {
  name: string;
  handle(msg: ChannelMessage, ctx: MiddlewareContext, next: () => Promise<void>): Promise<void>;
}

Middleware follows an Express-style chain: call next() to pass the message forward, or skip it to block the message. The whole chain completes before the agent runs, so code after await next() executes before the LLM call (use lifecycle hooks such as agent:after_run to react to answers).

Built-in Middleware

Pairing

Prevents unknown users from talking to your bot. New users must be approved by the owner.

import { pairing } from '@cogitator-ai/channels';

const gateway = new Gateway({
  // ...
  middleware: [
    pairing({
      ownerIds: { telegram: '123456789' },
      codeLength: 6,
      expiresIn: 300, // seconds
    }),
  ],
});

Flow:

  1. Unknown user sends a message
  2. Bot responds: Hi! Ask the bot owner to approve you with: `/pair A7X92K`
  3. Owner types /pair A7X92K (or /pair@your_bot A7X92K in groups)
  4. User is now approved and can chat normally

Only owners can approve codes — approved users cannot pair others. Codes use an unambiguous alphabet (no 0/O/1/I) and expired codes are pruned automatically. Approvals live in memory and are lost on restart; for persistent approvals use dmPolicy below. PairingMiddleware applies to every message, including group chats.

DM Policy

Controls who may talk to the bot in direct messages and groups, with approvals persisted to disk.

import { dmPolicy } from '@cogitator-ai/channels';

dmPolicy({
  mode: 'pairing', // 'open' | 'allowlist' | 'pairing' | 'disabled'
  ownerIds: { telegram: '123456789' },
  allowlist: ['telegram:42', '777'], // channel-scoped or bare user ids
  storePath: './data/dm-allowlist.json', // default: ~/.cogitator/dm-allowlist.json
  pairingCodeLength: 6, // default
  pairingExpiresMs: 300_000, // default: 5 minutes
  groupPolicy: 'allowlist', // 'open' | 'allowlist' | 'disabled'
  groupAllowlist: ['-1001234567890'],
  onPairingRequest: (userId, code) => notifyOwner(userId, code),
  onStoreError: (err) => log.error(err),
});
ModeUnknown user in a DM
openPasses
allowlistGets Not authorized.
pairingGets a /pair CODE message; an owner approves it with /pair CODE in DM
disabledSilently ignored

Owners always pass. Group messages (groupId set) are judged only by groupPolicy — allowlist lets in the chats in groupAllowlist plus owners. Approved users are saved to storePath (written atomically, loaded on start) and listed by getApprovedUsers(); write failures go to onStoreError (default: console.error). A leading ~ in storePath (~/...) expands to the home directory. The class form is new DmPolicyMiddleware(config).

Auto-Extract

Extracts entities and relations from every user message into a knowledge graph in the background.

import { autoExtract } from '@cogitator-ai/channels';

autoExtract({
  extractor, // { extract(text) => { entities, relations } }
  graphAdapter, // GraphAdapter from @cogitator-ai/memory
  agentId: 'jarvis',
  coreFacts, // optional { set(key, value) }
  coreFactPatterns: { name: /my name is (\w+)/i },
  onError: (err) => log.warn(err),
});

Unknown entity/relation types are stored as custom (the original type is kept in properties.originalType / the edge label). Existing edges are not duplicated — their confidence is raised instead.

Rate Limiting

Per-user (channelType:userId) sliding one-minute window. The limit notice is sent once per window, not for every blocked message; idle buckets are pruned. Blocked messages never reach the agent.

import { rateLimit } from '@cogitator-ai/channels';

const gateway = new Gateway({
  // ...
  middleware: [
    rateLimit({
      maxPerMinute: 10,
      message: 'Too many messages. Please wait.',
    }),
  ],
});

Owner Commands

Intercepts slash commands from the bot owner. See the Owner Commands page.

Custom Middleware

import type { GatewayMiddleware, ChannelMessage, MiddlewareContext } from '@cogitator-ai/channels';

const loggingMiddleware: GatewayMiddleware = {
  name: 'logging',
  async handle(msg: ChannelMessage, ctx: MiddlewareContext, next: () => Promise<void>) {
    console.log(`[${msg.channelType}] ${msg.userId}: ${msg.text}`);
    await next(); // pass to next middleware or agent
  },
};

Blocking Messages

Skip next() to block a message:

const profanityFilter: GatewayMiddleware = {
  name: 'profanity-filter',
  async handle(msg, ctx, next) {
    if (containsProfanity(msg.text)) {
      await ctx.channel.sendText(msg.channelId, 'Please keep it civil.');
      return; // message blocked
    }
    await next();
  },
};

Middleware Context

interface MiddlewareContext {
  threadId: string;
  user: ChannelUser;
  channel: Channel;
  set(key: string, value: unknown): void;
  get<T>(key: string): T | undefined;
}

Use set/get to pass data between middleware:

// in auth middleware
ctx.set('userRole', 'admin');

// in later middleware
const role = ctx.get<string>('userRole');

On this page