obi-jam pattern intermediate 30 minutes

Channel-Aware Command Enrichment for Discord Agents

Every time we added a contact in the sales channel, we had to type project:ClientName at the end. Every time. Then we realized — if the command is in the sales channel, the project is obvious. The channel IS the context.

Multi-project agents have a context problem. When your agent manages tasks, contacts, or notes across multiple projects, every command needs a project qualifier. /contact add Sarah Chen project:Acme works, but nobody wants to type project:Acme on every command when they’re already in the #acme channel.

The channel is the context. Use it.

The Problem

You have a Discord agent that handles CRM commands across multiple projects. Users work in project-specific channels:

  • #project-alpha — commands here should default to Project Alpha
  • #project-beta — commands here should default to Project Beta
  • #general — no default, require explicit project

Without enrichment, every command needs the full qualifier:

#project-alpha: /contact add Sarah Chen email:[email protected] project:alpha
#project-alpha: /deal add Widget Corp pipeline 50000 project:alpha
#project-alpha: /contact add James Park project:alpha

That last project:alpha is pure noise. The user is in #project-alpha. The project is obvious.

The Pattern

Create an enrichment module that sits between the transport layer and the command handler. It checks the channel, injects defaults, and delegates:

const channelId = '148833076664'; // #project-alpha
const defaultProject = 'alpha';

function isInProjectChannel(channelMeta) {
  return channelMeta?.channelId === channelId;
}

function injectDefaultProject(args, channelMeta) {
  // Not in the project channel — don't touch it
  if (!isInProjectChannel(channelMeta)) return args;
  if (!args) return args;

  // Don't override if project is already specified
  if (/project:/i.test(args)) return args;

  return `${args} project:${defaultProject}`;
}

Three rules:

  1. Wrong channel → pass through. If the command didn’t come from the project channel, return the args unchanged. The original handler processes it normally.
  2. Already has project → don’t override. If the user explicitly typed project:beta in #project-alpha, respect it. Explicit always wins.
  3. Right channel, no project → inject. Append the default.

Wiring It Into Command Handling

The enrichment module intercepts specific commands and enriches them before delegating to the original handler:

async function handleContactPassthrough(sender, content, channelMeta) {
  // Only enrich in the project channel
  if (!isInProjectChannel(channelMeta)) return null;

  const [sub, ...rest] = (content || '').split(/\s+/);
  const args = rest.join(' ');

  if (sub?.toLowerCase() === 'add') {
    const enrichedArgs = injectDefaultProject(args, channelMeta);
    console.log(`Enriching /contact add → "${enrichedArgs}"`);
    return crmServices.addContact(enrichedArgs);
  }

  // Not an 'add' subcommand — let the original handler deal with it
  return null;
}

The return value matters:

  • Return a result → the enrichment module handled it, skip the original handler
  • Return null → the enrichment module passed, route to the original handler

This is a pass-through pattern. The module only intercepts what it knows how to enrich. Everything else falls through.

The Message Flow

User types "/contact add Sarah Chen" in #project-alpha

Discord transport extracts channelMeta: { channelId: "1488...", channelName: "project-alpha" }

Command router receives: command="/contact", content="add Sarah Chen", channelMeta

Enrichment module checks: isInProjectChannel? Yes.

injectDefaultProject("Sarah Chen") → "Sarah Chen project:alpha"

Delegates to CRM handler: addContact("Sarah Chen project:alpha")

CRM creates contact with project:alpha association

Same command in #general:

User types "/contact add Sarah Chen" in #general

Enrichment module checks: isInProjectChannel? No.

Returns null — falls through to original CRM handler

CRM handler processes without project context (or prompts for it)

Scaling to Multiple Projects

Map channel IDs to project contexts:

const channelProjects = {
  '14883307666': { project: 'alpha', client: 'Acme Corp' },
  '14994418777': { project: 'beta', client: 'Widget Inc' },
  '15005529888': { project: 'gamma', client: 'Startup Co' },
};

function getChannelContext(channelMeta) {
  return channelProjects[channelMeta?.channelId] || null;
}

function injectDefaultProject(args, channelMeta) {
  const ctx = getChannelContext(channelMeta);
  if (!ctx) return args;
  if (!args) return args;
  if (/project:/i.test(args)) return args;
  return `${args} project:${ctx.project}`;
}

Add a new project channel? Add one entry to the map. No handler changes needed.

What Else Can You Enrich?

The pattern works for any context that’s implied by the channel:

  • Project/contact add in #project-alpha → inject project:alpha
  • Team/task create in #engineering → inject team:eng
  • Priority/bug report in #urgent → inject priority:high
  • Environment/deploy in #staging → inject env:staging

The enrichment module doesn’t know or care what the downstream handler does with the context. It just makes implicit context explicit before the command arrives.

Key Takeaway

If your agent handles commands across multiple contexts (projects, teams, environments), and users tend to work in context-specific channels, don’t make them repeat what the channel already tells you. A channel-aware enrichment layer is 30 lines of code that eliminates the most tedious part of multi-project command entry. Check the channel, inject the default, don’t override explicit values. The channel is the context — use it.

FAQ

How do I make Discord bot commands context-aware based on which channel they're in?

Pass channel metadata (channelId, channelName) through your message handling pipeline. Create an enrichment module that checks the channel ID and injects default parameters before the command reaches its handler. The enrichment module sits between the transport layer and the command router — it intercepts commands, adds context, and delegates to the original handler.

How do I avoid overriding explicitly set parameters when enriching commands?

Check if the parameter is already present in the command args before injecting. Use a simple regex test like /project:/i.test(args). If the user explicitly typed project:SomeProject, don't override it. Only inject when the parameter is missing.

Can I use channel-aware enrichment for multiple projects?

Yes. Map channel IDs to default contexts in a configuration object. Each project channel gets its own ID and default parameters. When a command comes from a mapped channel, inject that channel's defaults. Unmapped channels pass through unchanged.