Parent: #34
Priority: P1 (live data flow — graff-memd needs this to react in real time)
Branch base: release/0.1.53
Why
Today TrajectoryRecorder writes to SQLite. graff-memd needs to subscribe to those events live without polling. Hermes does this in-process; we need a broadcast channel that crosses the N-API boundary.
Scope
Rust side
TrajectoryRecorder gets a tokio::sync::broadcast::Sender<TrajectoryEvent> alongside its repo write path. Every recorded event is also broadcast.
- New API on
forge_api::ForgeAPI: subscribe_trajectory(conversation_id: Option<ConversationId>) -> Receiver<TrajectoryEvent>.
SDK side (sdk/typescript/src/lib.rs + wire.rs)
#[napi] method on GraffApi: subscribeTrajectory(conversationId?: string): TrajectoryStream.
TrajectoryStream exposes .next() returning a JSON-serialized event (mirror ChatStreamHandle).
- Public TS:
graff.trajectory.subscribe(conversationId?: string): AsyncIterable<TrajectoryEvent>
Acceptance
Parent: #34
Priority: P1 (live data flow — graff-memd needs this to react in real time)
Branch base:
release/0.1.53Why
Today
TrajectoryRecorderwrites to SQLite. graff-memd needs to subscribe to those events live without polling. Hermes does this in-process; we need a broadcast channel that crosses the N-API boundary.Scope
Rust side
TrajectoryRecordergets atokio::sync::broadcast::Sender<TrajectoryEvent>alongside its repo write path. Every recorded event is also broadcast.forge_api::ForgeAPI:subscribe_trajectory(conversation_id: Option<ConversationId>) -> Receiver<TrajectoryEvent>.SDK side (
sdk/typescript/src/lib.rs+wire.rs)#[napi]method onGraffApi:subscribeTrajectory(conversationId?: string): TrajectoryStream.TrajectoryStreamexposes.next()returning a JSON-serialized event (mirrorChatStreamHandle).Acceptance
RecvError::Laggedhandling — emit alagevent and continue)for await ... breakcancels cleanly (no hanging tokio task)examples/trajectory-subscribe.ts