# agents.md — drasi-core/lib/src/channels
## FFI Boundary Warning
Several types in this directory cross the dynamic plugin FFI boundary. They are passed
as opaque pointers inside `#[repr(C)]` structs or have their fields extracted into
FFI-safe representations.
### Types that cross FFI
| `SourceEventWrapper` | `events.rs` | Opaque pointer (`Box<SourceEventWrapper>` → `*mut c_void`) | `FfiSourceEvent.opaque` |
| `BootstrapEvent` | `events.rs` | Opaque pointer (`Box<BootstrapEvent>` → `*mut c_void`) | `FfiBootstrapEvent.opaque` |
| `QueryResult` | `events.rs` | Opaque pointer (`Box<QueryResult>` → `*mut c_void`) | `ReactionVtable.enqueue_query_result_fn` arg |
| `SubscriptionResponse` | `events.rs` | Converted field-by-field to `FfiSubscriptionResponse` | `FfiSubscriptionResponse` |
| `ComponentStatus` | `events.rs` | Mapped to `FfiComponentStatus` enum | `FfiComponentStatus` |
| `DispatchMode` | `dispatcher.rs` | Mapped to `FfiDispatchMode` enum | `FfiDispatchMode` |
### What to update when changing these types
#### Adding/removing fields on `SourceEventWrapper` or `BootstrapEvent`
These cross as opaque pointers, so field changes don't break the FFI envelope layout.
However, you must still:
1. Bump `FFI_SDK_VERSION` in `components/plugin-sdk/src/ffi/metadata.rs` — the host and
plugin must agree on the struct layout since they both cast the same `*mut c_void`.
2. If metadata fields are extracted (e.g., `source_id` from `SourceEventWrapper`), update
the extraction in `components/plugin-sdk/src/ffi/vtable_gen.rs`.
#### Adding/removing fields on `QueryResult`
`QueryResult` crosses FFI as an opaque pointer via `ReactionVtable.enqueue_query_result_fn`.
The host boxes the value (`Box::into_raw`) and the plugin takes ownership (`Box::from_raw`).
1. Bump `FFI_SDK_VERSION` in `components/plugin-sdk/src/ffi/metadata.rs` — both sides
cast the same `*mut c_void` pointer.
2. **Plugin side** — `components/plugin-sdk/src/ffi/vtable_gen.rs` — the
`enqueue_query_result_fn` function in both `build_reaction_vtable()` and
`build_reaction_vtable_from_boxed()` reconstructs the `QueryResult`.
3. **Host side** — `components/host-sdk/src/proxies/reaction.rs` — `ReactionProxy`
boxes the `QueryResult` and passes the raw pointer.
#### Adding variants to `ComponentStatus` or `DispatchMode`
1. **FFI enum** — Add the variant to the corresponding `#[repr(C)]` enum in
`components/plugin-sdk/src/ffi/types.rs` (`FfiComponentStatus` or `FfiDispatchMode`)
2. **Plugin SDK mapping** — Update the match arms in
`components/plugin-sdk/src/ffi/vtable_gen.rs` that convert between the Rust enum
and the FFI enum
3. **Host SDK mapping** — Update the match arms in the corresponding proxy files:
- `ComponentStatus`: `components/host-sdk/src/proxies/source.rs` and
`components/host-sdk/src/proxies/reaction.rs` (both have `status()` methods)
- `DispatchMode`: `components/host-sdk/src/proxies/source.rs` (`dispatch_mode()`)
4. **Version bump** — Bump `FFI_SDK_VERSION` minor version
#### Changing `SubscriptionResponse`
This struct is deconstructed into FFI parts on the plugin side and reconstructed on
the host side:
1. **Plugin side** — `components/plugin-sdk/src/ffi/vtable_gen.rs` — look for
`FfiSubscriptionResponse` construction (the `subscribe` vtable function)
2. **FFI struct** — `components/plugin-sdk/src/ffi/vtables.rs` — update
`FfiSubscriptionResponse` definition
3. **Host side** — `components/host-sdk/src/proxies/source.rs` — the `subscribe()`
method on `SourceProxy` reconstructs `SubscriptionResponse` from FFI parts
4. **Host side receivers** — `components/host-sdk/src/proxies/change_receiver.rs` —
`ChangeReceiverProxy` and `BootstrapReceiverProxy` reconstruct the channel types