About creating-letta-code-channels
A skill for developers adding or debugging channels in Letta Code. It explains when to use dynamic user plugins versus first-party channels and how to implement, route, and test replies.
Key features
- 01Choosing user plugins or first-party channels
- 02Scaffolding a user channel plugin
- 03Implementing tool and relay reply modes
- 04Testing discovery, routing, and delivery
- 05Handling pairing and public-channel safety
Use cases
- 01Prototyping a headless channel plugin
- 02Adding a channel with custom Desktop UI
- 03Debugging inbound routing and pairing
- 04Verifying outbound reply behavior
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ letta-ai/ skills --skill creating-letta-code-channelsAfter installing, type/creating-letta-code-channelsin Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
references/first-party-channels.mdtesting.mduser-plugins.mdscripts/scaffold-user-channel-plugin.tsDetails
From the project READMECreating Letta Code channels
Use this when adding or debugging Letta Code channel support.
First choice
- User plugin (
~/.letta/channels/<id>/) for headless experiments, community plugins, and fast workflow tests. - First-party channel (
src/channels/<id>/) when the channel needs bespoke Desktop UI, custom account snapshots, Slack/Discord-style auto-routing, rich protocol fields, or migration/compatibility shims.
User plugins cannot shadow first-party ids: telegram, slack, and discord are ignored under ~/.letta/channels/. Use ids like telegram-test, whatsapp-community, or custom-chat.
Core workflow
- Work in
~/letta/letta-codeor a worktree. - Read
src/channels/README.mdon branches with dynamic plugins. - For a user plugin, create:
~/.letta/channels/<id>/channel.json~/.letta/channels/<id>/plugin.mjs~/.letta/channels/<id>/accounts.json
- Implement
messageActionsfor both reply modes: explicit tool replies and automatic relay text use the common outbound adapter path. - Start with
dmPolicy: "pairing"for testing, orallowlist/openfor known headless deployments. - Test all four legs:
- plugin discovery/import
- inbound
adapter.onMessage(msg)to route/pairing - routed channel notification reaches the agent
- outbound behavior matches the account's captured reply mode: explicit
MessageChanneldelivery in tool mode, or automatic finalized-text delivery in relay mode
- Run targeted tests, then
bun run typecheck,bun run lint,bun run build.
References
Read only what is needed:
references/user-plugins.md— dynamic plugin manifest/account/runtime/headless flow and gotchas.references/first-party-channels.md— first-party channel file cascade and safety checks.references/testing.md— smoke-test checklist and commands.
Scaffold helper
Use the bundled scaffold for a minimal user plugin skeleton. Replace <path-to-this-skill> with this skill directory path:
npx tsx <path-to-this-skill>/scripts/scaffold-user-channel-plugin.ts \
my-channel "My Channel" \
--runtime-package [email protected] \
--runtime-module some-sdk
It creates channel.json, plugin.mjs, and accounts.example.json. Replace the TODO inbound/outbound implementation with the real SDK calls.
Hard lessons
- Accounts default to tool mode when
reply_modeis unset or invalid. Add"reply_mode": "relay"to an existing saved account, preserving its actual ID, root fields, and credentials. - Relay requires a build containing PR 4043; v0.34.1 predates it. The on-device gateway implements delivery; remote hosts must supply transport and captured per-input policy. This is a host distinction, not an agent-state restriction. Mode changes affect new inputs, not active or queued ones.
- A single-destination relay input sends finalized text without
MessageChannel; unfinished error/cancel text, reasoning, and subagent output are excluded. Multi-destination inputs require explicit replies, never broadcasts. Queued inputs stay separate. Use tool mode for reactions, files, and proactive sends. - For public channels, suppress tool approval/control prompts unless there is verified operator routing. Posting approval prompts publicly leaks tool input and invites forged
approvereplies. - User plugin runtime resolution must not count parent/dev
node_modules. Runtime modules should resolve from explicit runtime dirs only. - Headless pairing is CLI-first:
letta channels pair --channel <id> --code <code> --agent <agent-id> --conversation <conversation-id>. - Running listeners reload
pairing.yamlandrouting.yamlon the next inbound miss; restart only when adapter/account config itself changed.