Skip to content

Plan to add external events plan#217

Open
archandatta wants to merge 3 commits intomainfrom
archand/plan/externel-events-stream
Open

Plan to add external events plan#217
archandatta wants to merge 3 commits intomainfrom
archand/plan/externel-events-stream

Conversation

@archandatta
Copy link
Copy Markdown
Contributor

@archandatta archandatta commented Apr 14, 2026

Note

Low Risk
Low risk because this PR only adds a planning document and does not change runtime code or APIs.

Overview
Adds plans/external-events.md, a detailed plan proposing new endpoints POST /events/publish (ingest external events.Event into the active capture session with enforced source.kind) and GET /events/stream (SSE streaming with Last-Event-ID resume, keepalives, and multi-client readers).

The plan also outlines follow-up behavioral changes like clearing capture-session state on stop via a session_ended marker, adding CaptureSession.Active(), and documenting both endpoints in server/openapi.yaml.

Reviewed by Cursor Bugbot for commit 6a06e95. Bugbot is set up for automated code reviews on this repo. Configure here.

@firetiger-agent
Copy link
Copy Markdown

Firetiger deploy monitoring skipped

This PR didn't match the auto-monitor filter configured on your GitHub connection:

Any PR that changes the kernel API. Monitor changes to API endpoints (packages/api/cmd/api/) and Temporal workflows (packages/api/lib/temporal) in the kernel repo

Reason: PR title and empty body provide insufficient information to determine if kernel API endpoints or Temporal workflows are being modified; please add details about what files are changed.

To monitor this PR anyway, reply with @firetiger monitor this.

@archandatta archandatta force-pushed the archand/plan/externel-events-stream branch from a19154d to fc419d0 Compare April 14, 2026 17:24
@archandatta archandatta requested review from Sayan- and rgarcia April 14, 2026 17:25
Comment thread plans/external-events.md Outdated
@@ -0,0 +1,155 @@
# External Event Ingestion + SSE Streaming — Plan

**Scope:** Two new HTTP endpoints layered on top of the merged CDP base: `POST /events/capture_session/publish` (external event ingestion) and `GET /events/capture_session/stream` (SSE live stream), both wired into the existing resource-style `CaptureSession`.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't think the url here should be nested under capture_session. in my mind the capture session is a singleton producer the API runs / users can configure that publishes to a shared event stream that others can publish into. it doesn't own this stream

so either POST /events or POST /events/publish and GET /events or GET /evenst/stream makes more sense to me

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't we want the /events/publish tied to a capture_session_id for traceability?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The endpoint changed to events/publish but made it so that the a capture session is required so it goes through the pipeline

Comment thread plans/external-events.md Outdated
Comment thread plans/external-events.md Outdated
| POST | `/events/capture_session/publish` | `ApiService.PublishEvent` | `publishEvent` |
| GET | `/events/capture_session/stream` | `ApiService.StreamCaptureSession` (SSE) | `streamCaptureSession` |

The stream endpoint follows the same singleton pattern as the other `/events/capture_session` routes. Handlers reference `s.captureSession` directly; the endpoint returns 404 when no session is active.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't think the stream endpoint should be stateful or a singleton. Many API users should be able to call this and request events starting after some time or sequence number. It's not tied to capture session, in fact capture session might not even be running

Copy link
Copy Markdown
Contributor Author

@archandatta archandatta Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay understood.

So then diving into the lifecycle of the stream:

Client                              Pipeline
  │                                    │
  │  GET /events/stream?after_seq=N    │
  │───────────────────────────────────▶│
  │                                    │
  │      SSE connection opens          │
  │◀───────────────────────────────────│
  │                                    │
  │         event (seq=N+1)            │
  │◀───────────────────────────────────│
  │         event (seq=N+2)            │
  │◀───────────────────────────────────│
  │              ...                   │
  │                                    │
  ├──── Client disconnects ────────────┤
  │     OR                             │
  ├──── Server shuts down ─────────────┤
  │                                    │
  │      Connection closes             │
  │◀ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
  • if we did decouple the events/publish from the capture session, then would we leave the stream open?
    • since this is opt-in, we'd need to figure out the use cases to leave the stream connection open/closed

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once a capture session is active, multiple streams will be able to connect/disconnect either at the oldest record or a certain seq

@archandatta archandatta requested a review from rgarcia April 15, 2026 13:07
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 2bbe5a3. Configure here.

Comment thread plans/external-events.md Outdated
Copy link
Copy Markdown
Contributor

@Sayan- Sayan- left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

overall semantics look good to me!

Comment thread plans/external-events.md

Accepts a JSON `events.Event` body and publishes it into the currently active `CaptureSession`.

**Publish requires an active capture session.** If no session is active the handler returns `400` — the caller must `POST /events/start` first. The session is a precondition for publishing, not an implicitly-created resource.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems reasonable

Comment thread plans/external-events.md
- `400` when `type` is empty.
- `200` on successful publish.

The handler unconditionally sets `source.kind = KindKernelAPI` **after** decoding, overwriting whatever the caller supplied. This is not a default — it is an enforcement. A caller that sends `"source": {"kind": "cdp"}` must not be able to forge CDP provenance; `source.kind` is documented as the fan-out key (§3.2) and its value for this endpoint is not negotiable.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm is kind an enum or known set of strings? for example could we simpler interpolate the provided kind with something like event_publish? would that be more or less confusing downstream?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah kind is coming from the types set in event.go

type SourceKind string

const (
	KindCDP          SourceKind = "cdp"
	KindKernelAPI    SourceKind = "kernel_api"
	KindExtension    SourceKind = "extension"
	KindLocalProcess SourceKind = "local_process"
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm. how would we ever get events of type KindExtension or KindLocalProcess?

Comment thread plans/external-events.md

The handler does not take `monitorMu` — `CaptureSession.Publish` is serialised internally and guarantees monotonic seq delivery. This creates a narrow TOCTOU: a concurrent `StopCapture` can clear the session ID between the `Active()` check and the `Publish()` call, causing the event to be written to the ring buffer with an empty `captureSessionID` (after the `session_ended` marker). This is accepted; grabbing `monitorMu` on every publish would serialize all external events behind CDP monitor start/stop.

### 2.3 `GET /events/stream`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems reasonable!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants