Files
LangBot/docs/event-based-agents/09-event-processors.md
T

223 lines
12 KiB
Markdown

# Event processors and Pipeline plugin compatibility
Status: implemented in the 4.11 development branches of LangBot and the Plugin SDK,
2026-09-08. The Host uv configuration pins the matching SDK commit. Deploy both
revisions together; older SDK releases do not contain this component. Switch the
development source pin to a published SDK release before a stable PyPI release.
This design supersedes the automatic EBA EventListener observer broadcast in
the earlier EBA documents. Existing Pipeline plugin behavior remains supported.
## Product boundary
Three processor types appear together in the Processors area:
| Product | Implementation | Event entry | Flow ownership |
| --- | --- | --- | --- |
| Pipeline | Existing Pipeline stages and configuration | Received messages | Pipeline stages, including legacy plugin hooks |
| Agent | A configured plugin AgentRunner | Supported EBA events | The selected runner |
| Event processor | A configured plugin EventProcessor | Supported EBA events | Plugin Python handlers |
Use **Event processor** as the product label and **EventProcessor** as the SDK
component name. The localized description should explain that the plugin defines
the processing logic. It must not suggest an LLM, prompt, or visual workflow is
required.
An installed component is a reusable implementation. A processor instance is a
user-created configuration of that component. A Bot event binding selects an
instance, not an installed plugin package directly.
## Legacy EventListener contract
EventListener remains a Pipeline extension. Existing plugins retain their import
paths, handler registration syntax, event classes, Query-based APIs, and Pipeline
plugin selection behavior. No conversion of installed listeners into standalone
processor instances takes place.
Compatibility must cover execution behavior, not just successful deserialization:
| Hook | Required behavior |
| --- | --- |
| PersonMessageReceived / GroupMessageReceived | Read the returned EventContext before later stages; retain message edits and default prevention |
| PromptPreProcessing | Preserve timing and apply returned default_prompt and prompt |
| PersonNormalMessageReceived / GroupNormalMessageReceived | Preserve user_message_alter, default prevention, and replacement replies |
| PersonCommandSent / GroupCommandSent | Preserve command stage timing, default prevention, and replacement replies |
| NormalMessageResponded | Preserve response-stage timing, default prevention, and replacement message chains, including streaming behavior |
| All hooks | Preserve plugin ordering, prevent_postorder across installations, bound-plugin filtering, Query identity, and Workspace scope |
RPC responses are new Python objects. Host code must consume returned values
rather than assume mutations reached the original Query by object identity.
Host-only references, including the active Query and raw adapter message, must
remain available for legacy reply APIs without being exposed in serialized
plugin events.
Preserve source event fields across EBA-to-legacy conversion, including group
member permissions, bot group permissions, and member titles. Missing platform
information must be distinguished from fields that were dropped during conversion.
Direct Agent and Event processor execution must not synthesize Pipeline lifecycle
hooks. Those hooks describe actual Pipeline stages.
## EventProcessor SDK contract
Introduce a distinct component kind instead of changing what an existing
EventListener manifest means. One package may contain both kinds; only the legacy
EventListener participates in Pipeline hook dispatch.
Retain the familiar authoring shape:
```python
from langbot_plugin.api.definition.components.event_processor import EventProcessor, EventProcessorContext
from langbot_plugin.api.entities.builtin.platform.events import MemberJoinedEvent
class WelcomeProcessor(EventProcessor):
async def initialize(self):
await super().initialize()
@self.handler(MemberJoinedEvent)
async def on_join(ctx: EventProcessorContext):
await ctx.reply(f"Hello, {ctx.event.member.nickname}")
```
Handlers receive typed EBA events directly. Do not maintain a second, incomplete
mapping into plugin-only EBA wrapper classes. Preserve complete public event
fields; compact log previews must not become the execution payload. Include the
generic platform-specific event contract for adapter-specific events.
The context belongs to one invocation and exposes the event, processor/run
identifiers, instance configuration, logging, and authorized Host APIs. It has no
fabricated Pipeline Query. Reuse Host run tracking, deadlines, installation
authority, platform capabilities, and delivery records where appropriate.
A handler returning normally completes its invocation. There is no implicit LLM
loop, automatic second processor, or hidden retry of side effects. An exception
marks the run failed and retains the associated log. New processing handlers do
not use prevent_default to control another processor; routing has already chosen
the current processor. The legacy methods keep their existing Pipeline meaning.
## Activation and routing
The activation sequence is explicit:
1. Install a plugin containing an EventProcessor component.
2. Create an Event processor in the Processors area.
3. Open its detail page, select a plugin component, and save its configuration.
4. Bind a Bot event to that processor instance in the existing event routing UI.
Installation and processor creation alone do not subscribe to Bot events.
The component declares the event types it handles; Bot bindings select the subset
of supported events to deliver. One package may supply multiple components, and
multiple instances may use the same component with independent configuration.
Extend the existing single-target route arbitration with `event_processor`.
There is no automatic EBA broadcast to installed EventListeners. Keep Pipeline hook dispatch inside the Pipeline path. Existing observer
plugins must explicitly adopt the new component and be bound by the user; do not
create subscriptions during migration.
An unconfigured instance has no supported events and cannot execute. Validate
component availability, event compatibility, Workspace ownership, and instance
identity when configuring the instance and again at invocation. A disabled or
unavailable plugin leaves the instance visible with an actionable unavailable
status. It must not silently fall back to Agent or Pipeline.
## Compact UI
Creation adds a third type next to Agent and Pipeline and asks only for basic
instance information. Select the plugin component in the detail-page header.
Keep component-defined configuration in the adjacent Plugin settings popover.
If no component is installed, show a relevant plugin installation entry point;
installing still does not create a binding.
The detail page shows event debugging on the left and logs on the right without
view-switching tabs. A compact run list shows event type, time, status and known
processing duration. Selecting a row shows that run's identity, input, logs,
actions and outcome below. There is no shared timeline between unrelated runs.
The additive `created_at_ms`, `started_at_ms`, and `finished_at_ms` fields retain
Host lifecycle precision for elapsed-time display.
Keep payloads and error details collapsed until expanded. Distinguish attempted
delivery from confirmed delivery and display the actual destination.
Place component identity, availability, bindings, and configuration in a compact
secondary area. Do not add a prompt editor, model selector, or flow designer.
The plugin implements the processing flow in code.
## Delivery sequence and acceptance
1. Repair and regression-test Pipeline EventContext handling and legacy payload
conversion independently of the new processor feature.
2. Add the SDK component, context, manifest/scaffolding, and explicit invocation
contract; verify registration, event coverage, and process isolation.
3. Add Host instance management, event routing, execution tracking, and matching
HTTP/MCP/skill surfaces. Turn off automatic EBA observer dispatch in this step.
4. Add creation, binding, availability, logs, and delivery trace UI with i18n.
5. Exercise a real packaged plugin through installation, explicit instance
creation, Bot binding, invocation, logging, and reply delivery.
Acceptance must prove that installation alone invokes no handlers; one matching
binding invokes exactly the chosen component; instance configuration and run
history remain separate; all declared EBA events retain their fields; unavailable
components fail visibly; and legacy plugins keep the documented Pipeline hook
order and behavior. Unit tests alone do not establish a successful live plugin
installation or platform delivery.
## Implemented transport and APIs
`lbp comp EventProcessor` scaffolds a component in `components/event_processor`.
Its manifest uses `kind: EventProcessor` and `spec.events`, for example
`[group.member_joined]`. `spec.config` defines instance parameters. A component
that calls `ctx.reply()` declares `spec.permissions.tools: [detail, call]`.
References use `event_processor:author/plugin/component`, separate from
`plugin:author/plugin/runner`. Both kinds share the existing run transport,
installation authorization, deadlines and run ledger. The trusted Host selects
the component kind; the worker invokes only that exact kind and name.
There is no model invocation in the EventProcessor base class.
`EventProcessorContext` provides `event`, `run_id`, `config`, `api`, `log()` and
`reply()`. `api` is the existing run-scoped Host proxy. Use `ctx.config` for instance
parameters; plugin installation configuration remains separate. Handlers may
register the `EBAEvent` base class as a catch-all. An exact typed handler takes
precedence over that fallback. Multiple handlers for the same type run in their
registration order, within one invocation.
HTTP instance management uses `/api/v1/agents` with `kind: event_processor`.
Metadata at `/api/v1/agents/_/metadata` lists installed `event_processors`.
Creation accepts `component_ref` and `parameters`; the Host derives the supported
event patterns from the component. Bot bindings use `target_type: event_processor`
and the created instance UUID as `target_id`.
- `GET /api/v1/agents/{id}/runs?before_id=...` lists this instance's runs.
- `GET /api/v1/agents/{id}/runs/{run_id}/events?after_sequence=...` pages its logs
and action results. A run from another instance or Workspace is rejected.
- The corresponding MCP tools are `get_processor_metadata`, `list_processor_runs`
and `get_processor_run_events`, alongside processor CRUD.
- `/api/v1/agents/{id}/debug` accepts a full typed EBA event in `data`. Platform
actions use Mock; other authorized tools retain their configured behavior.
The detail page polls run updates, keeps payload details collapsed and separates
logs from platform delivery. Completed handlers produce no synthetic reply text.
## Verification (2026-09-08)
- SDK API, scaffolding and Plugin Runtime suites: 684 passed.
- Host runner, service, controller, MCP and adapter regression suites: 971 passed.
- Pipeline and registry regression suites: 243 passed, one environment-dependent skip.
- Frontend unit suite: 74 passed; TypeScript and changed-file lint checks passed.
- Real packaged-plugin tests cover installation, component-kind separation,
invocation, mock platform delivery, instance isolation and persisted logs.
- Authenticated Edge testing created an instance and a loopback OneBot bot, saved
a member-join binding, injected one native notice and received exactly one
`send_group_msg` response. The detail page displayed the completed run,
localized action name, destination and returned message ID. No external IM
account or live model was involved.
- Browser regression covers expanding long payloads, reaching the last log and
pagination without duplication. Eight unrelated existing browser failures were
reproduced against the pre-change commit; the full suite is not green.
- Repository-wide lint also retains the pre-existing duplicate `send_image_msg`
in the WeCom customer-service library and existing formatting failures outside
this change. The i18n check has the same pre-existing diagnostics as its baseline.
Native Agent interaction-resumption is not enabled for EventProcessor bindings;
its input contract is the platform EBA event collection, not a synthetic Agent
continuation. Plugins should handle platform events through their typed handlers.