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:
- Wrong channel → pass through. If the command didn’t come from the project channel, return the args unchanged. The original handler processes it normally.
- Already has project → don’t override. If the user explicitly typed
project:betain#project-alpha, respect it. Explicit always wins. - 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 addin #project-alpha → injectproject:alpha - Team —
/task createin #engineering → injectteam:eng - Priority —
/bug reportin #urgent → injectpriority:high - Environment —
/deployin #staging → injectenv: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.