# Event Bus

The `EventBus` is how plugins coordinate without holding references to each other. It is typed, priority-ordered,
and supports both fire-and-forget events and typed request/response. This page is the 
model: envelope, priority direction, dispatch order, scope routing.

For the patterns that go on top (designing an event type, the pre-commit interception pattern,
etc...), see [Event Patterns](https://plugin-kit.saad-ardati.dev/concepts/events/).

## The smallest possible event

Define an event type. Emit it. Subscribe from somewhere that has no idea who the emitter is.

```dart
extractRegion(eventBusSnippets, 'event-bus-on-emit')
```

That is the whole happy path. Emit fires every matching handler in priority order. Emitters
and subscribers never have to know about each other.

## Every handler gets an envelope

There is one subscription API: `on<T>()`. Every handler you register receives an `EventEnvelope<T>`, not the raw payload. The envelope carries:

- `e.event`: the payload (mutable; downstream handlers see changes).
- `e.identifier`: the optional scope this event was emitted under.
- `e.stop(value)`: halt the cascade and pin the final payload to `value`.

A handler can read the payload, mutate it for downstream handlers, or call `e.stop(...)` to end the cascade. [Event Patterns](https://plugin-kit.saad-ardati.dev/concepts/events/) covers the practical shapes (observers, mutators, the pre-commit interception pattern) from inside plugin code; this page sticks to the model.

## Priority

Event handlers run in descending priority order. Higher number runs first.

```dart
extractRegion(eventBusSnippets, 'event-bus-priority')
```

The highest-priority handler can mutate the payload so later handlers see the new value, or call `e.stop(...)` to halt
the cascade and pin the final result. Lower-priority handlers only run if no earlier one stopped. Default is `Priority.normal`; same convention and same default as the [service registry](https://plugin-kit.saad-ardati.dev/concepts/service-registry/#priority).

## Globally observing all events

`bind(...)` registers a callback that sees every non-internal event on the bus before typed handlers run.

```dart
extractRegion(eventBusSnippets, 'event-bus-bind')
```

Use it for tracing or analytics. It is not where business logic lives.
Think of it as a bus-wide tap that sees the pre-dispatch view of every event.
`bind` callbacks cannot stop the cascade; they only observe.

## Request and response

The same bus also does typed request/response.

```dart
extractRegion(eventBusSnippets, 'event-bus-request-response')
```

The dispatch model:

1. Merge general handlers with handlers scoped to the request's identifier (if any).
2. Try them in descending priority order (highest first).
3. The first non-null response wins.

That is why `Response` types are usually nullable. `return null` means "I concede, let the next handler try." 
Returning a value short-circuits the rest.

For strictly synchronous dispatch, use `onRequestSync` / `requestSync`.

For callers who prefer `null` over an exception when no handler responds, `maybeRequest` and `maybeRequestSync` exist too.

For a worked LLM-shaped example of priority-based request dispatch (provider routing, capability
matching, fallback cascades), see [the model_embassy tour](https://plugin-kit.saad-ardati.dev/examples/model-embassy/).

## Identifier scoping

Handlers and emissions can be scoped to an identifier. This is how you multiplex one bus across many
logical targets (tools, agents, panels, departments, per-document channels) without
needing a separate bus for each.

```dart
extractRegion(eventBusSnippets, 'event-bus-identifier-scoping')
```

When you emit with an identifier, the bus merges general handlers for that type with handlers
registered for that exact identifier. Handlers scoped to a different identifier do not see the event at all.

## Global bus and session buses

Plugin Kit has two event scopes:

- One **global bus** owned by the runtime.
- One **session bus** per active session.

The two are isolated. Events emitted on the global bus do not automatically reach session buses,
and session events do not escape their session. Cross-scope communication is always explicit.

- Global plugin → global plugins: `context.bus.emit(...)`
- Session plugin → same-session plugins: `context.bus.emit(...)`
- Session plugin → global scope: `context.globalBus.emit(...)`
- Global plugin → every active session: `context.sessions.emit<T>(event)` 
(via the [`SessionBroadcast`](https://plugin-kit.saad-ardati.dev/concepts/sessions/) extension on `GlobalPluginContext.sessions`)

That isolation is intentional. It keeps event routing auditable, which matters once you have more
than a handful of plugins across the two scopes.

See [Sessions](https://plugin-kit.saad-ardati.dev/concepts/sessions/) for the full story on cross-scope communication.

## `emit` returns the envelope

`emit` returns `EventEnvelope<T>`, not `Future<void>`. If you do not care about interception, ignore the return value and move on. If you do care:

```dart
extractRegion(eventBusSnippets, 'event-bus-emit-envelope')
```

The envelope carries whatever the handler chain ended up with: the possibly-mutated payload, 
whether it was stopped, and (if so) the final value.

## Internal events

`emitInternal(...)` dispatches to handlers but skips `bind` observers. Most application code
never needs this. It exists for system-level signals you want typed handlers to react to
without cluttering analytics and logging taps.

## Errors propagate

The bus does not swallow handler exceptions. If a handler throws during
`emit`, `request`, or `requestSync`, the caller sees the exception. That is almost always the right
tradeoff: silent failure in an event pipeline is much harder to debug a month later than a loud,
unambiguous exception at the call site.

## Related reading

[Event Patterns](https://plugin-kit.saad-ardati.dev/concepts/events/)
  [Plugins](https://plugin-kit.saad-ardati.dev/concepts/plugins/)
  [Plugin Services](https://plugin-kit.saad-ardati.dev/concepts/plugin-services/)
  [Sessions](https://plugin-kit.saad-ardati.dev/concepts/sessions/)