{"slug":"manage-channels","title":"manage-channels","summary":"Wire channels to agent groups, manage isolation levels, add new channel groups. Use after adding a channel, during setup, or standalone to reconfigure.","platform":"Claude","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-08-24T05:37:20.517409Z","repo":{"url":"https://github.com/nanocoai/nanoclaw","stars":30841,"forks":12823,"license":"MIT","updatedAt":"2026-09-23T17:25:39Z"},"bodyHtml":"<hr>\n<h2>name: manage-channels\ndescription: Wire channels to agent groups, manage isolation levels, add new channel groups. Use after adding a channel, during setup, or standalone to reconfigure.</h2>\n<h1>Manage Channels</h1>\n<p>Wire messaging channels to agent groups. See <code>docs/isolation-model.md</code> for the full isolation model.</p>\n<p>Privilege is a <strong>user-level</strong> concept, not a channel-level one (see <code>src/modules/permissions/db/user-roles.ts</code>, <code>src/modules/permissions/access.ts</code>). There is no \"main channel\" / \"main group\" — any user can be granted <code>owner</code> or <code>admin</code> (global or scoped to an agent group) via <code>grantRole()</code>, and messages from unknown senders are gated per-messaging-group by <code>unknown_sender_policy</code> (<code>strict</code> | <code>request_approval</code> | <code>public</code>).</p>\n<h2>Assess Current State</h2>\n<p>Read the central DB (<code>data/v2.db</code>) using these canonical queries (column names match the schema, not the CLI flags — the <code>register</code> command's <code>--assistant-name</code> is stored in <code>agent_groups.name</code>).</p>\n<p>Run each via the in-tree wrapper — the host setup deliberately ships no <code>sqlite3</code> CLI:</p>\n<pre><code>pnpm exec tsx scripts/q.ts data/v2.db \"&lt;query&gt;\"\n</code></pre>\n<pre><code>SELECT id, name AS assistant_name, folder, agent_provider FROM agent_groups;\nSELECT id, channel_type, platform_id, name, unknown_sender_policy FROM messaging_groups;\nSELECT messaging_group_id, agent_group_id, engage_mode, engage_pattern, session_mode, threads, priority FROM messaging_group_agents;\nSELECT user_id, role, agent_group_id FROM user_roles ORDER BY role='owner' DESC;\n</code></pre>\n<p>Also check <code>.env</code> for channel tokens and <code>src/channels/index.ts</code> for uncommented imports.</p>\n<p>Categorize channels as: <strong>wired</strong> (has DB entities + messaging_group_agents row), <strong>configured but unwired</strong> (has credentials + barrel import, no DB entities), or <strong>not configured</strong>.</p>\n<p>If the instance has no owner yet (<code>SELECT COUNT(*) FROM user_roles WHERE role='owner' AND agent_group_id IS NULL</code> returns 0), tell the user they should run <code>/init-first-agent</code> first — it stands up the first agent group, promotes the operator to owner, and verifies delivery end-to-end by having the agent DM them. Then return here for any additional channels/groups.</p>\n<h2>First Channel (No Agent Groups Exist)</h2>\n<p><strong>Delegate to <code>/init-first-agent</code>.</strong> It handles: channel choice, operator identity lookup, DM platform id resolution (with cold-DM or pair-code fallback), agent group creation, wiring, and the welcome DM. Return here afterward for any additional channels.</p>\n<h2>Channel Defaults: The Two-Level Model</h2>\n<p>Wiring defaults (engage mode/pattern, threading, <code>unknown_sender_policy</code>) resolve through <strong>exactly two levels</strong>:</p>\n<ol>\n<li><strong>Adapter declaration</strong> — each channel adapter declares <code>ChannelDefaults</code> (separate DM and group contexts, plus a <code>mentions</code> capability) in its source file. The adapter copy is skill-installed and user-owned: to change a default install-wide, edit <code>src/channels/&lt;channel&gt;.ts</code> and restart. Declarations are never persisted to the DB.</li>\n<li><strong>Per-wiring/per-mg values chosen at creation</strong> — every creation surface (<code>ncl wirings create</code> / <code>ncl messaging-groups create</code>, the <code>register</code> wizard step, the approval-card flow, <code>/init-first-agent</code>) fills omitted fields from the declaration and stores the result on the row. Pass explicit flags to override one wiring.</li>\n</ol>\n<p>There is no third level: existing rows are never re-resolved, so editing a declaration only affects wirings created afterward. The one exception is the <strong><code>threads</code> column</strong>, which stays live — <code>NULL</code> means \"inherit the declaration at message time\".</p>\n<p>Channels with no declaration (stale adapter copies) fall back to the legacy behavior; run <code>/update-skills</code> to pull current adapters.</p>\n<h3>Wiring via ncl</h3>\n<p><code>ncl</code> requires the <strong>host service to be running</strong> (it connects over a Unix socket):</p>\n<pre><code>ncl messaging-groups create --channel-type &lt;type&gt; --platform-id \"&lt;id&gt;\" --name \"&lt;name&gt;\" [--is-group 1]\nncl wirings create --messaging-group-id &lt;mg-id&gt; --agent-group-id &lt;ag-id&gt; [--session-mode &lt;mode&gt;]\n</code></pre>\n<p>Omitted <code>engage_mode</code>/<code>engage_pattern</code>/<code>unknown_sender_policy</code> come from the adapter declaration for the right context (DM vs group). Run <code>ncl wirings help</code> / <code>ncl messaging-groups help</code> for the full flag list.</p>\n<h3>Threading override (<code>--threads</code>)</h3>\n<p><code>ncl wirings create/update ... --threads true|false</code> controls whether platform thread ids are honored for this wiring. <code>true</code> (in groups) means per-thread sessions and in-thread replies/typing/cards; <code>false</code> collapses to a flat session with top-level replies. Omitted = <code>NULL</code> = inherit the channel declaration. A wiring can <em>disable</em> threads on a threaded platform (Slack, Discord, GitHub), never enable them on a non-threaded one.</p>\n<p>Two consequences to warn the user about:</p>\n<ul>\n<li><strong>Session identity</strong>: sessions are never deleted. Flipping <code>threads</code> on a live wiring orphans existing per-thread sessions (or splinters a shared one) — history stays in the old sessions; new messages start fresh ones.</li>\n<li><strong><code>mention-sticky</code> needs threads</strong>: sticky engagement is keyed on per-thread session existence, so with resolved threads off it would engage once and never disengage. Creation and update coerce <code>mention-sticky</code> → <code>mention</code> (with a warning) when the effective thread policy is off.</li>\n</ul>\n<h3>Mention capability</h3>\n<p>Each declaration states which mention signal the adapter emits: <code>platform</code> (real platform mentions), <code>dm-only</code> (only DMs are flagged), or <code>never</code>. On a <code>mentions: 'never'</code> channel (Linear OAuth apps, WhatsApp personal-number mode, Emacs), <code>mention</code>/<code>mention-sticky</code> wirings are <strong>inert — they can never engage</strong> — and <code>ncl</code> rejects them at create/update with an error citing the declaration. For groups on those channels, use a name pattern instead:</p>\n<pre><code>ncl wirings update &lt;id&gt; --engage-mode pattern --engage-pattern '(?i)^@?&lt;Name&gt;\\b'\n</code></pre>\n<p><strong>Renaming an agent group does not update stored patterns.</strong> Declared group patterns containing <code>{name}</code> are substituted with the agent group's name <em>at creation</em> and stored literally — after <code>ncl groups update &lt;id&gt; --name &lt;NewName&gt;</code>, audit that group's wirings for patterns still matching the old name and update them.</p>\n<h2>Wire New Channel</h2>\n<p>For each unwired channel:</p>\n<ol>\n<li>Read its SKILL.md <code>## Channel Info</code> for terminology, how-to-find-id, typical-use, and default-isolation</li>\n<li>Ask for the platform ID using the platform's terminology</li>\n<li>Ask the isolation question (see below)</li>\n<li>Register with the appropriate flags</li>\n</ol>\n<h3>Isolation Question</h3>\n<p>Present a multiple-choice with a contextual recommendation. The three options:</p>\n<ul>\n<li><strong>Same conversation</strong> (<code>--session-mode \"agent-shared\"</code> + existing folder) — all messages land in one session. Recommend for webhook + chat combos (GitHub + Slack).</li>\n<li><strong>Same agent, separate conversations</strong> (<code>--session-mode \"shared\"</code> + existing folder) — shared workspace/memory, independent threads. Recommend for same user across platforms.</li>\n<li><strong>Separate agent</strong> (new <code>--folder</code>) — full isolation. Recommend when different people are involved.</li>\n</ul>\n<p>Use the channel's <code>typical-use</code> and <code>default-isolation</code> fields to pick the recommendation. Offer to explain more if the user is unsure — reference <code>docs/isolation-model.md</code> for the detailed explanation.</p>\n<h3>Register Command</h3>\n<pre><code>pnpm exec tsx setup/index.ts --step register -- \\\n  --platform-id \"&lt;id&gt;\" --name \"&lt;name&gt;\" \\\n  --folder \"&lt;folder&gt;\" --channel \"&lt;type&gt;\" \\\n  --session-mode \"&lt;shared|agent-shared|per-thread&gt;\" \\\n  --assistant-name \"&lt;name&gt;\"\n</code></pre>\n<p>The <code>register</code> step creates the agent group (reusing it if the folder already exists), the messaging group, and the wiring row. <code>createMessagingGroupAgent</code> auto-creates the companion <code>agent_destinations</code> row so the agent can address the channel by name.</p>\n<p>Omitted engage/policy fields default from the channel adapter's declaration (see \"Channel Defaults\" above). Optional overrides: <code>--trigger \"&lt;regex&gt;\"</code> (explicit engage pattern), <code>--engage-mode &lt;pattern|mention|mention-sticky&gt;</code>, <code>--is-group &lt;true|false&gt;</code>, <code>--unknown-sender-policy &lt;strict|request_approval|public&gt;</code>. Don't pick a mention mode on a channel whose declaration says <code>mentions: 'never'</code> — it can never engage there.</p>\n<p>New agent groups are created on the instance default provider (<code>DEFAULT_AGENT_PROVIDER</code> in <code>.env</code>, or <code>claude</code> when unset). To run a group on a different provider, switch it after creation with <code>ncl groups config update --provider &lt;name&gt;</code> (e.g. <code>codex</code>).</p>\n<p>For separate agents, also ask for a folder name and optionally a different assistant name.</p>\n<h2>Add Channel Group</h2>\n<p>When adding another group/chat on an already-configured platform, open\n<code>.claude/skills/add-&lt;channel&gt;/SKILL.md</code>, follow its current group-discovery\ninstructions, ask the isolation question, then register. Channel-specific\nprocedures belong in that channel's skill, not here.</p>\n<h2>Change Wiring</h2>\n<ol>\n<li>Show current wiring (agent_groups × messaging_group_agents)</li>\n<li>Ask which channel to move and to which agent group</li>\n<li>Delete the old <code>messaging_group_agents</code> entry, create a new one</li>\n<li>Note: existing sessions stay with the old agent group; new messages route to the new one. The <code>agent_destinations</code> row created for the old wiring is NOT automatically removed — if you want the old agent to stop seeing the channel as a named target, delete it from <code>agent_destinations</code> manually.</li>\n</ol>\n<h2>One-Time Check: Legacy Mis-Wired WhatsApp Groups</h2>\n<p>Installs that approved WhatsApp group registration cards before the channel-defaults model wired those groups as <code>engage_mode='pattern'</code>, <code>engage_pattern='.'</code> — respond-to-everything (the card flow couldn't tell groups from DMs on non-threaded platforms). Check once:</p>\n<pre><code>pnpm exec tsx scripts/q.ts data/v2.db \"SELECT mga.id, mg.platform_id, mg.name FROM messaging_group_agents mga JOIN messaging_groups mg ON mg.id = mga.messaging_group_id WHERE mg.channel_type='whatsapp' AND mg.is_group=1 AND mga.engage_mode='pattern' AND mga.engage_pattern='.'\"\n</code></pre>\n<p>For any hit the operator didn't deliberately configure as always-on, offer the repair options in <code>/add-whatsapp</code>'s \"Migration audit\" section (flip to mention/name-pattern engagement, or delete the wiring).</p>\n<h2>Show Configuration</h2>\n<p>Display a readable summary showing:</p>\n<ul>\n<li><strong>Agent groups</strong> with their wired channels (from <code>messaging_group_agents</code>)</li>\n<li><strong>Configured-but-unwired</strong> channels (credentials present, no DB entities)</li>\n<li><strong>Unconfigured</strong> channels</li>\n<li><strong>Privileged users</strong>: <code>SELECT user_id, role, agent_group_id FROM user_roles ORDER BY role='owner' DESC</code></li>\n</ul>\n","files":[{"path":"SKILL.md","sizeBytes":10426,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"trusted-source-unreviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"trusted-source-unreviewed","screen":{"ran":true,"outcome":"notes-only","suspicious":0,"notes":4,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-08-24T05:39:20.851902Z","sha256":"9AA726B6C1E0F596BC147635CBC8188F5FA0045C2A5F7BC3525199F271EC86A9","sizeBytes":4499},"review":null,"source":{"repositoryUrl":"https://github.com/nanocoai/nanoclaw","path":".claude/skills/manage-channels","license":"MIT","commit":"143db6c907c652773a536c7c9e96269fdad0a4a4","subtreeSha":"DFBF2863E2C5AF5935CB3D22C2C0D1BC85732F5F195A4FB1417138006BF3DA6F","lastSyncedAt":"2026-09-24T06:48:46.168681Z"},"reviewedAt":"2026-08-24T05:46:23.477639Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/nanocoai/nanoclaw/tree/main/.claude/skills/manage-channels"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install nanocoai-nanoclaw@llmmart"},{"target":"git","command":"git clone https://github.com/nanocoai/nanoclaw.git"}]}