mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-15 23:32:46 +00:00
docs: align events strategy with RunEvent simplification
Update terminology (RunEventEnvelope → RunEvent), consumer guidance (match on body instead of .event/.properties), and "Adding A New Event" steps to reflect the direct Event → EventBody construction. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
76e1da8b35
commit
c32705f12f
1 changed files with 36 additions and 24 deletions
|
|
@ -11,24 +11,25 @@ Detached runs rely on this distinction. If something needs to be visible after r
|
|||
```text
|
||||
Engine/Handler -> Event -> Emitter::emit()
|
||||
|- trace(raw event)
|
||||
|- canonicalize -> RunEventEnvelope
|
||||
`- on_event(&RunEventEnvelope)
|
||||
|- canonicalize -> RunEvent
|
||||
`- on_event(&RunEvent)
|
||||
|- progress.jsonl + live.json
|
||||
|- run store
|
||||
|- SSE
|
||||
`- CLI / tests / metrics listeners
|
||||
```
|
||||
|
||||
The canonical envelope is built exactly once in `fabro-workflow/src/event.rs`.
|
||||
The canonical `RunEvent` is built exactly once in `fabro-workflow/src/event.rs`.
|
||||
|
||||
- `Event` remains the internal typed source of truth.
|
||||
- `Emitter` owns an immutable `run_id` and converts typed events into `RunEventEnvelope`.
|
||||
- Every listener receives `&RunEventEnvelope`, not `&Event`.
|
||||
- Bypass paths that cannot go through the emitter must call `canonicalize_event()` once and reuse the same envelope for every sink.
|
||||
- `Event` (in `fabro-workflow`) is the internal typed event emitted by engine and handlers.
|
||||
- `Emitter` owns an immutable `run_id` and converts `Event` into `RunEvent` via `to_run_event_at()`.
|
||||
- `RunEvent` (in `fabro-types`) holds envelope metadata plus a typed `body: EventBody`. It has no cached JSON fields; the wire format is produced only during serialization.
|
||||
- Every listener receives `&RunEvent`, not `&Event`.
|
||||
- Bypass paths that cannot go through the emitter must call `to_run_event()` once and reuse the same `RunEvent` for every sink.
|
||||
|
||||
## Canonical Envelope
|
||||
|
||||
Each line in `progress.jsonl` is a `RunEventEnvelope`:
|
||||
Each line in `progress.jsonl` is a serialized `RunEvent`:
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
@ -102,11 +103,11 @@ Agent events now use explicit session links:
|
|||
|
||||
Most events flow through `Emitter::emit()`. The remaining direct-write paths must use:
|
||||
|
||||
1. `canonicalize_event(run_id, event)`
|
||||
1. `to_run_event(run_id, event)`
|
||||
2. Serialize and redact once
|
||||
3. Reuse that exact serialized envelope for every sink
|
||||
3. Reuse that exact `RunEvent` for every sink
|
||||
|
||||
Never canonicalize the same logical event twice if multiple sinks receive it.
|
||||
Never build the same `RunEvent` twice if multiple sinks receive it.
|
||||
|
||||
## Adding A New Event
|
||||
|
||||
|
|
@ -122,21 +123,26 @@ Extend `Event::trace()` so the raw event is observable in tracing output.
|
|||
|
||||
Extend `event_name()` with the new lowercase dot-notation string.
|
||||
|
||||
### 4. Map envelope fields
|
||||
### 4. Add the `EventBody` variant
|
||||
|
||||
Update `extract_envelope_fields()`:
|
||||
Add a variant to `EventBody` in `fabro-types/src/run_event/mod.rs` with a corresponding props struct. Use `#[serde(rename = "dotted.name")]` matching the external name from step 3.
|
||||
|
||||
### 5. Map envelope fields and construct `EventBody`
|
||||
|
||||
Update `stored_event_fields()` and `event_body_from_event()` in `fabro-workflow/src/event.rs`:
|
||||
|
||||
- Move `node_id`, `node_label`, `session_id`, and `parent_session_id` into the envelope when appropriate.
|
||||
- Keep event-specific data in `properties`.
|
||||
- Flatten structured failure details into explicit property keys when needed.
|
||||
- Construct the `EventBody` variant directly from the `Event` fields.
|
||||
- For `Event::Agent` sub-variants, merge `visit` into the inner props and lift `stage` to `node_id`.
|
||||
- For `Event::Sandbox` sub-variants, unwrap and flatten into the corresponding `EventBody` variant.
|
||||
|
||||
### 5. Emit it
|
||||
### 6. Emit it
|
||||
|
||||
Prefer `Emitter::emit(&Event::...)`.
|
||||
|
||||
Use `canonicalize_event()` only for true bypass paths.
|
||||
Use `to_run_event()` only for true bypass paths.
|
||||
|
||||
### 6. Update consumers
|
||||
### 7. Update consumers
|
||||
|
||||
Check:
|
||||
|
||||
|
|
@ -148,14 +154,20 @@ Check:
|
|||
|
||||
## Consumer Guidance
|
||||
|
||||
When writing listeners:
|
||||
When writing Rust consumers (listeners, store projections, CLI progress):
|
||||
|
||||
- Match on `envelope.event`, not Rust variant names.
|
||||
- Read event payload from `envelope.properties`.
|
||||
- Read stage/branch identity from `node_id` and `node_label`.
|
||||
- Read agent hierarchy from `session_id` and `parent_session_id`.
|
||||
- Match on `event.body` using `EventBody::*` variants. This gives you typed access to event-specific fields.
|
||||
- Use `event.node_id`, `event.node_label`, `event.session_id`, and `event.parent_session_id` for envelope metadata.
|
||||
- Only use `event.event_name()` or `event.properties()` for generic/display purposes (logging, forwarding). These involve serialization and should not be used on hot paths.
|
||||
|
||||
Do not rebuild or mutate the envelope in downstream listeners.
|
||||
When writing external JSON consumers (SSE clients, JSONL parsers):
|
||||
|
||||
- Match on the `"event"` field for the dot-notation event name.
|
||||
- Read event-specific data from `"properties"`.
|
||||
- Read stage/branch identity from `"node_id"` and `"node_label"`.
|
||||
- Read agent hierarchy from `"session_id"` and `"parent_session_id"`.
|
||||
|
||||
Do not rebuild or mutate the `RunEvent` in downstream listeners.
|
||||
|
||||
## Bypass And Persistence Guarantees
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue