Feishu Advanced Configuration: Accounts, Limits, and Routing
Covers Feishu multi-account setup, message limits, streaming cards, workspace tools, ACP sessions, and multi-agent routing. Written for operators tuning multi-bot Feishu deployments.
Read this when
- Running more than one Feishu bot from one Gateway
- Tuning streaming card replies or message chunking
- Enabling the Feishu doc, wiki, drive, or Bitable tools
- Routing Feishu DMs or groups to different agents
All of the material that used to sit under the Advanced configuration heading: multiple accounts, delivery limits, streaming cards, quota flags, group session scope, workspace tools, ACP sessions, and multi-agent routing.
Advanced configuration
Multiple accounts
{
channels: {
feishu: {
defaultAccount: "main",
accounts: {
main: {
appId: "cli_xxx",
appSecret: "xxx",
name: "Primary bot",
tts: {
providers: {
openai: { voice: "shimmer" },
},
},
},
backup: {
appId: "cli_yyy",
appSecret: "yyy",
name: "Backup bot",
enabled: false,
},
},
},
},
}
When outbound APIs leave out an accountId, the account selected is the one named by defaultAccount. Per-account entries take their defaults from the top level, and most top-level keys can be overridden on an individual account.
accounts.<id>.tts follows the same structure as tts and is deep-merged on top of the global TTS config, which lets multi-bot Feishu deployments keep shared provider credentials at the global level and override only voice, model, persona, or auto mode for a given account.
Message limits
textChunkLimit- size of outbound text chunks (default:4000chars)streaming.chunkMode-"length"(default) cuts at the limit;"newline"favours newline boundariesmediaMaxMb- cap on media upload and download (default:30MB)
Feishu caps serialized messages at 30 KB, so ordinary Markdown cards and rich-text posts get split to fit as well. JSON escaping, UTF-8 text, headers, notes, and mentions all count toward that cap, which means chunks can end up shorter than textChunkLimit. When a media caption is long, it goes out as text or card chunks ahead of the attachment.
Streaming
Interactive cards give Feishu/Lark streaming replies through the Card Kit streaming API. Once this is on, the card is updated live while the bot produces text.
Should Card Kit accept a streaming preview and then reject the finished answer, OpenClaw delivers the full answer as a separate static reply rather than leaving just the preview.
{
channels: {
feishu: {
streaming: {
mode: "partial", // streaming card output (default: "partial")
block: { enabled: true }, // opt into completed-block streaming
},
},
},
}
With streaming.mode: "off" set, the completed reply is sent with no streaming updates; long replies are still split at the message limits described above. Streaming cards are also turned off by renderMode: "raw" (plain text in place of cards). streaming.block.enabled is disabled by default, so turn it on only if you want completed assistant blocks flushed ahead of the final reply. The legacy boolean streaming and the flat blockStreaming / blockStreamingCoalesce / chunkMode keys are migrated to this nested form through openclaw doctor --fix.
When a reply carries controls, native cards are used for command buttons and HTTP(S) links, even with streaming turned off. Reply text lives on the card; attachments are still delivered as separate messages. Controls that are unsupported, and cards that go past Feishu's size limits, keep their full labels in a readable fallback. If a later reply streams, that fallback stays a separate message. An active streaming preview is replaced by a final controls reply without resending the preview text; error controls that follow a completed answer stay separate. Where Feishu cannot delete or clear a replaced preview, delivery is reported as failed and the original message receipt is kept.
Quota optimization
Two optional flags cut down on Feishu/Lark API calls:
typingIndicator(defaulttrue): settingfalseskips typing reaction callsresolveSenderNames(defaulttrue): settingfalseskips sender profile lookups
{
channels: {
feishu: {
typingIndicator: false,
resolveSenderNames: false,
},
},
}
Group session scope and topic threads
How group messages map onto agent sessions is governed by channels.feishu.groupSessionScope (top-level, per account, or per group):
| Value | Session |
|---|---|
"group" (default) | One session per group chat |
"group_sender" | One session per (group + sender) |
"group_topic" | One session per topic thread; falls back to the group session |
"group_topic_sender" | One session per (topic + sender); falls back to (group + sender) |
Under the topic scopes, the event thread_id (omt_*) serves as the topic session key for native Feishu/Lark topic groups. When a native topic starter event has no thread_id, OpenClaw fills it in from Feishu before routing the turn. For normal group replies that OpenClaw converts into threads, the reply root message ID (om_*) remains the key, keeping the first turn and later turns in one session.
Setting replyInThread: "enabled" (top-level or per group) makes bot replies start or continue a Feishu topic thread rather than answering inline. topicSessionMode is the deprecated predecessor of groupSessionScope, so use groupSessionScope instead.
Feishu workspace tools
Agent tools for Feishu documents, chats, knowledge base, cloud storage, permissions, and Bitable come bundled with the plugin, along with matching skills (feishu-doc, feishu-drive, feishu-perm, feishu-wiki). Which tool families are available is gated by channels.feishu.tools:
| Key | Tools | Default |
|---|---|---|
tools.doc | feishu_doc document operations | true |
tools.chat | feishu_chat chat info + member queries | true |
tools.wiki | feishu_wiki knowledge base (requires doc) | true |
tools.drive | feishu_drive cloud storage | true |
tools.perm | feishu_perm permission management | false (sensitive) |
tools.scopes | feishu_app_scopes app scope diagnostics | true |
tools.bitable | feishu_bitable_* Bitable/Base operations | true |
Gates that apply per account are found under accounts.<id>.tools.
Once a configuration change takes effect, subsequent agent turns pick up the new account selection and tool gates, and the Gateway does not need a restart. Any tools that were already created for a turn still in progress retain the configuration they started with.
For Bitable operations, the application token comes from a /base/ URL or from a returned app_token, and never from the node token found in a /wiki/ URL. Should application creation succeed while table metadata retrieval does not, retain the returned app_token and URL. Examine that existing application instead of making a new one; a missing table_id is not evidence that creation failed.
Bitable metadata discovery iterates over all table and field pages. When the application lookup succeeds but table discovery then fails, the result retains the validated application token and name and reports tables_error; either retry discovery or use a table ID you already know. Listing of records is still paginated by the caller.
Documents produced by feishu_doc contain only a title. To supply Markdown content, pass the returned document_id as doc_token in a separate write action. Any create request that carries content fails, and no empty document is created.
Unless the app already holds the full drive:drive scope, grant drive:drive.metadata:readonly so that feishu_drive info lookups can run directly outside the root directory. With neither scope present, info continues to offer the legacy root-directory lookup via drive:drive:readonly.
ACP sessions
ACP is available in Feishu/Lark for DMs and for group thread messages. Because Feishu/Lark ACP is driven by text commands, no native slash-command menus exist; send /acp ... messages straight into the conversation.
Persistent ACP binding
{
agents: {
entries: {
codex: {
runtime: {
type: "acp",
acp: {
agent: "codex",
backend: "acpx",
mode: "persistent",
cwd: "/workspace/openclaw",
},
},
},
},
},
bindings: [
{
type: "acp",
agentId: "codex",
match: {
channel: "feishu",
accountId: "default",
peer: { kind: "direct", id: "ou_1234567890" },
},
},
{
type: "acp",
agentId: "codex",
match: {
channel: "feishu",
accountId: "default",
peer: { kind: "group", id: "oc_group_chat:topic:om_topic_root" },
},
acp: { label: "codex-feishu-topic" },
},
],
}
Spawn ACP from chat
Inside a Feishu/Lark DM or thread:
/acp spawn codex --thread here
Both DMs and Feishu/Lark thread messages are handled by --thread here. Later messages sent in the bound conversation are routed straight to that ACP session.
Multi-agent routing
To send Feishu/Lark DMs or groups to different agents, use bindings.
{
agents: {
ownership: "explicit",
defaults: {
systemAgent: { agentId: "main" },
},
entries: {
main: { workspace: "~/.openclaw/workspace" },
"agent-a": { workspace: "/home/user/agent-a" },
"agent-b": { workspace: "/home/user/agent-b" },
},
},
bindings: [
{
agentId: "agent-a",
match: {
channel: "feishu",
peer: { kind: "direct", id: "ou_xxx" },
},
},
{
agentId: "agent-b",
match: {
channel: "feishu",
peer: { kind: "group", id: "oc_zzz" },
},
},
{ agentId: "main", match: { channel: "feishu", accountId: "*" } },
],
talk: { agentId: "main" },
}
Routing fields:
match.channel:"feishu"match.peer.kind:"direct"(DM) or"group"(group chat)match.peer.id: user Open ID (ou_xxx) or group ID (oc_xxx)
For tips on looking these up, see Get group/user IDs.