| name | langbot-eba-adapter-dev |
| description | Build, refactor, and test LangBot platform adapters for the Event-Based Agents architecture. Use when adding or migrating Telegram, Discord, or other messaging platform adapters to the EBA adapter layout, validating unified event/message conversion, writing live adapter probes, or using standalone plugin runtime plus Computer Use for end-to-end platform testing. |
LangBot EBA Adapter Development
Use this skill when implementing or reviewing a LangBot platform adapter under the Event-Based Agents architecture.
Controlling a running instance via MCP
Beyond writing code, you can drive a live LangBot instance over MCP — no raw
HTTP needed. Two MCP servers exist (both reuse existing API keys; see AGENTS.md):
- LangBot instance —
http://<host>:5300/mcp (auth: web-UI lbk_ key or the
api.global_api_key from config.yaml). Manage bots, pipelines, models,
knowledge bases, and skills. See the langbot-mcp-ops skill.
- LangBot Space marketplace —
https://space.langbot.app/mcp (auth: Personal
Access Token). Search plugins / MCP servers / skills. See the
langbot-space-ops skill.
Any change to an agent-accessible HTTP API endpoint must keep the matching MCP
tool and these skills in sync.
Core Rule
Do not let platform-native event or message shapes leak into LangBot's common path. Each adapter must convert incoming SDK objects into unified EBA entities before dispatch:
- Events:
langbot_plugin.api.entities.builtin.platform.events
- Message chains:
langbot_plugin.api.entities.builtin.platform.message.MessageChain
- Users/groups/members:
langbot_plugin.api.entities.builtin.platform.entities
- Raw platform objects may remain only in
source_platform_object for debugging or platform-specific escape hatches.
Start Here
- Read the EBA design docs in
LangBot/docs/event-based-agents/.
- Read the architecture-level acceptance checklist before writing or validating code:
LangBot/docs/event-based-agents/adapters/acceptance-checklist.md
- Read the current reference adapter before writing code. Prefer Telegram first:
LangBot/src/langbot/pkg/platform/adapters/telegram/
LangBot/docs/event-based-agents/adapters/telegram.md
- Read the legacy source adapter for the target platform:
LangBot/src/langbot/pkg/platform/sources/<platform>.py
LangBot/src/langbot/pkg/platform/sources/<platform>.yaml
- Inspect SDK entity definitions in
langbot-plugin-sdk/src/langbot_plugin/api/entities/builtin/platform/.
- Search before assuming APIs. Platform SDKs change often.
Adapter Layout
Create one directory per adapter:
LangBot/src/langbot/pkg/platform/adapters/<platform>/
├── __init__.py
├── adapter.py
├── api_impl.py
├── event_converter.py
├── manifest.yaml
├── message_converter.py
├── platform_api.py
├── types.py
└── <platform>.svg
Add optional helpers such as voice.py only when the platform has a real domain-specific surface.
Ensure pyproject.toml package data includes adapter assets:
package-data = { "langbot" = ["templates/**", "pkg/platform/sources/*", "pkg/platform/adapters/**", ...] }
Implementation Checklist
manifest.yaml declares metadata.name, config schema, supported events, common APIs, and platform-specific APIs.
adapter.py creates the platform client, subscribes to native events, filters self/bot loops where appropriate, calls event_converter.target2yiri(...), then dispatches the EBA event.
event_converter.py maps native events to EBA event classes such as MessageReceivedEvent, MessageEditedEvent, MessageDeletedEvent, MessageReactionEvent, MemberJoinedEvent, BotInvitedToGroupEvent, and PlatformSpecificEvent.
message_converter.py maps native messages to MessageChain, and maps MessageChain back to the platform send format.
api_impl.py implements common EBA APIs: send, reply, edit, delete, forward, user/group/member lookup, moderation, upload/file URL, leave group.
platform_api.py keeps platform-specific calls behind call_platform_api(action, params).
- Unsupported common APIs must raise explicit SDK platform errors such as
NotSupportedError; do not silently no-op.
- Destructive APIs such as kick, ban, leave, delete, or moderation must be gated in live tests and documented.
Conversion Contract
For message events, the common shape should look like this regardless of platform:
platform_events.MessageReceivedEvent(
type="message.received",
adapter_name="<platform>",
message_id=<platform_message_id>,
message_chain=platform_message.MessageChain([...]),
sender=platform_entities.User(...),
chat_type=platform_entities.ChatType.PRIVATE or ChatType.GROUP,
chat_id=<conversation_or_channel_id>,
group=platform_entities.UserGroup(...) or None,
source_platform_object=<raw_object>,
)
Message content should use common components:
Source for original message id/time when available.
Plain for text.
At / AtAll for mentions.
Image, Voice, File for media.
Forward only when the platform can represent or emulate it safely.
If a platform event cannot cleanly map to a common event, emit PlatformSpecificEvent with a compact action and structured data.
Unit Tests
Add focused tests under LangBot/tests/unit_tests/platform/test_<platform>_eba_adapter.py.
Cover at least:
- Manifest supported events match adapter
supported_events().
- Manifest supported APIs match adapter
supported_apis().
- Platform API map matches manifest actions.
- Dispatcher chooses the most specific EBA listener.
- Message converter maps every supported common component both directions where possible:
Source
Plain
At
AtAll
Image
Voice
File
Quote
Face
Forward
Unknown
- mixed chains preserving order
- Event converter maps message received/edited/deleted/reaction, raw uncached gateway events, member events, and bot join/leave events.
- Send/reply methods pass correct platform kwargs and return
MessageResult.
Run the existing reference adapter tests too:
cd LangBot
uv run pytest tests/unit_tests/platform/test_<platform>_eba_adapter.py tests/unit_tests/platform/test_telegram_eba_adapter.py
uv run python -m py_compile tests/e2e/live_<platform>_eba_probe.py
git diff --check
Live Test Workflow
Direct adapter live probes are useful diagnostics, but they are not sufficient acceptance evidence for EBA. Treat tests/e2e/live_<platform>_eba_probe.py as an auxiliary tool only. The final adapter record must distinguish:
plugin-e2e-ui: real SDK plugin through standalone runtime, LangBot core, adapter, and a real/simulator UI action. This can mark an inbound UI item complete.
plugin-e2e-protocol: real SDK plugin through standalone runtime, LangBot core, adapter, and a protocol-boundary injected event. This is useful evidence but must not be claimed as UI coverage.
plugin-e2e-outbound: real SDK plugin calls an API and the bot output is visible in the real/simulator UI. This can mark send/API coverage complete.
adapter-live: direct adapter probe connected to a real/simulator endpoint. This is auxiliary only.
unit: mocked conversion/API-shape coverage. This is auxiliary only.
not-supported: platform protocol or SDK has no equivalent. Must include the reason.
blocked: intended capability could not be verified. This is not complete.
Write a live probe in LangBot/tests/e2e/live_<platform>_eba_probe.py. It should:
- Read token/client ids from environment variables or CLI args.
- Start the adapter directly.
- Register an EBA listener and write JSONL evidence to
LangBot/data/temp/.
- Wait for a real user/platform event instead of fabricating the entrypoint.
- Exercise common APIs and
call_platform_api actions.
- Observe returned gateway events for edit/delete/reaction/member/bot lifecycle where available.
- Print a summary containing passed, failed, skipped, and observed event types.
- Redact or avoid printing secrets.
- Keep destructive operations behind flags and run them last.
Use Computer Use when the user asks for real platform end-to-end coverage. Actually send messages/click reactions in the platform UI or otherwise trigger real user-side events; do not replace that with unit tests.
For media/component acceptance, keep the direction and trigger source explicit:
- Real inbound media only counts when a human-side platform UI or simulator UI sends the image/file/voice to the bot and the plugin JSONL records the corresponding common component.
- Bot outbound media only proves
send_message/adapter send conversion. It does not prove inbound conversion.
- Protocol-boundary injection, such as sending a OneBot event directly into a reverse WebSocket adapter, is useful and should be labelled
plugin-e2e-protocol, but it must not be reported as UI-level end-to-end media upload.
- If the UI cannot send or upload the media, record the item as
blocked with the exact client/simulator limitation.
Standalone Runtime + Plugin Test
When validating the whole LangBot EBA path, test with the SDK standalone runtime and a real test plugin. This is the required acceptance path; direct adapter calls do not prove the EBA architecture path.
The required path is:
Real platform / simulator UI
-> platform SDK native event
-> adapter event converter
-> unified EBA event/entity/message types
-> LangBot core event dispatch
-> standalone SDK runtime
-> real test plugin listener
-> plugin calls platform APIs through SDK
-> LangBot core API dispatch
-> adapter API implementation
-> real platform / simulator UI
Typical shape:
cd langbot-plugin-sdk
uv run python -m langbot_plugin.cli.__init__ rt \
--debug-only \
--ws-control-port 5400 \
--ws-debug-port 5401 \
--skip-deps-check
cd LangBot
export PYTHONPATH=/absolute/path/to/langbot-plugin-sdk/src:${PYTHONPATH:-}
uv run main.py --standalone-runtime
export DEBUG_RUNTIME_WS_URL=ws://127.0.0.1:5401/plugin/ws
export EBA_PROBE_LOG=/absolute/path/to/LangBot/data/temp/<platform>_eba_plugin_probe.jsonl
export EBA_PROBE_API=1
export EBA_PROBE_COMPONENT_SWEEP=1
export EBA_PROBE_PLATFORM_API=1
uv --project /absolute/path/to/langbot-plugin-sdk run python -m langbot_plugin.cli.__init__ run
Use an EBA probe plugin that subscribes to all relevant EBA event classes and runs SDK API calls after the first MessageReceived.
The plugin evidence should be JSONL and include:
- event class and
event.type
- adapter name
- chat type and chat ID
- sender/user/group IDs with secrets redacted
bot_uuid and adapter_name, proving LangBot filled common routing fields before plugin dispatch
- received
message_chain component list
- API action name, input summary, result or error
- unsupported or blocked reason when an item is skipped
For full adapter acceptance, enable both probe sweeps:
EBA_PROBE_COMPONENT_SWEEP=1 sends the required outbound message components through send_message.
EBA_PROBE_PLATFORM_API=1 calls common safe APIs plus selected call_platform_api actions for the adapter.
The SDK must support plugin.call_platform_api(bot_uuid, action, params) for platform-specific acceptance. If the SDK cannot call a platform-specific action from the plugin, the adapter cannot be fully accepted even if direct adapter probes pass.
Required EBA Acceptance Coverage
Before marking an adapter migrated, fill out an adapter record against LangBot/docs/event-based-agents/adapters/acceptance-checklist.md.
At minimum, the record must cover these categories:
- Message receive component tests through
plugin-e2e-ui: Source, Plain, At, AtAll, Image, Voice, File, Quote, Face, Forward, Unknown, and mixed chains where the platform supports them. Protocol-only receive evidence must be labelled plugin-e2e-protocol.
- Message send component tests through
plugin-e2e-outbound: Plain, At, AtAll, Image, Voice, File, Quote, Face, Forward, and mixed chains where the platform supports them.
- Every event declared in
manifest.yaml -> spec.supported_events.
- Every common API declared in
manifest.yaml -> spec.supported_apis.required and optional.
- Every action declared in
manifest.yaml -> spec.platform_specific_apis.
- Compatibility tests for manifest declarations, legacy message listener fallback, EBA listener specificity, bot self-message filtering, and
source_platform_object reply/debug behavior.
Do not declare an event or API in the manifest unless it has an implementation path and an acceptance entry. If a platform or simulator lacks a capability, document it as not-supported or blocked rather than silently omitting the test.
Common Pitfalls
get_bots() may return bot dictionaries, not UUID strings. Probe plugins should select an enabled dict and pass bot["uuid"] to get_bot_info() and send_message().
- Make sure the probe subscribes to every event you claim to verify. Missing
MessageDeleted subscription can make a working adapter look untested.
- Some platforms emit both cached and raw gateway events, producing duplicate evidence for delete/reaction. Count this explicitly; do not treat duplicates as failure unless semantics differ.
- Self-message filtering is platform-specific. Filter bot-originated
message.received loops, but do not accidentally filter edit/delete events needed for bot-owned API probes.
- Reaction events may be filtered for bot self reactions. To test user reaction add/remove, use real UI interaction or a real user token path if permitted.
- File uploads usually happen as message attachments. A standalone
upload_file API may need to be NotSupportedError.
- Live probes should not leak bot tokens through command output, logs, docs, or final answers.
- Discord requires privileged intents for message content and members. Missing intents can look like converter bugs.
- Telegram Bot API exposes only limited member lists; document capability gaps.
- Do not mark moderation APIs verified unless they ran against a disposable target member/bot.
- If
leave_group is tested, run it last because the test bot will be removed from the server/group.
- Restore local LangBot DB/test state after live runs if you enabled temporary bots or changed plugin settings.
Documentation Record
Add or update LangBot/docs/event-based-agents/adapters/<platform>.md in the same style as Telegram:
- Status and adapter directory.
- Configuration table matching manifest fields.
- Supported EBA event list.
- Common API table with support and limitations.
call_platform_api action list.
- Receive component table with evidence level per component.
- Send component table with evidence level per component.
- Event table with evidence level per event.
- Common API table with evidence level per API.
- Platform-specific API table with evidence level per action.
- Live test record with exact date, endpoint/simulator, standalone runtime command, test plugin path/name, JSONL evidence path, channel/group type, observed events, APIs exercised, destructive operations, and skipped items.
Be honest. Put untested or skipped APIs in the document with the reason. Do not imply full parity when a platform cannot provide the same information density.
Before Finishing
- Run unit tests and compile the live probe.
- Run the standalone runtime plugin E2E path for every required acceptance item that the platform supports.
- Run
git diff --check.
- Summarize live JSONL evidence by event type.
- Stop all long-running runtimes and probes.
- Confirm no secrets are staged.
- Leave unrelated untracked files alone.