Skip to content

Event Log has no workspace dimension: one process's stream mixes every workspace it has opened #5966

Description

@YellowSnnowmann

Split out of a review thread on #5955 (CodeRabbit, src/core/jsonrpc.rs), where it was raised against the MCP supervisor events that PR adds. It is not specific to them, which is why it is not fixed there.

What

The developer Event Log (GET /events/domain → Settings → Developer → Event Log) is a single process-wide stream. One process serves more than one workspace over its life — mcp::host's own module doc says so, and HOSTS is insert-only, so a workspace switch leaves the old one open — but the SSE envelope carries only {domain, event, agent, detail, timestamp}. A row from a workspace the user has switched away from is indistinguishable from one belonging to the workspace they are in.

Several DomainEvent variants already know which workspace they belong to and the envelope discards it:

Why it was not fixed in #5955

Correction (2026-09-02). This section originally claimed there was "no live active-workspace signal". That was wrong, and the correction is what made #5955's related P1 fixable rather than deferrable. Config::load_or_init re-resolves the workspace on every call — resolve_config_dirs_ignoring_env reads active_user.toml from disk with no caching — and #5955 exposes that as config::active_workspace_dir(). What is missing is a cached signal, not any signal. The distinction matters here specifically: a marker read per decision is fine (#5955 pays it for the handful of events a supervisor tick produces), a marker read per streamed event is not.

  1. There is no signal cheap enough for the SSE hot path. config::active_workspace_dir() is a disk read; the Event Log streams every domain event in the process. This needs an in-memory value updated when the workspace changes, which does not exist yet.

  2. The identity cannot simply be put on the wire as-is. workspace_dir is an absolute path under the user's home directory, and this envelope feeds a settings panel and its NDJSON download. Forwarding it would print the user's home directory into a shared, exportable surface. An opaque, stable workspace handle would be needed instead.

  3. Doing it for one event family alone would make those the only workspace-scoped rows in a panel whose other rows are not — a more confusing contract than the current one.

Sketch

  • A cheap in-memory active-workspace signal, updated wherever the workspace changes, that config::active_workspace_dir() can read through instead of hitting disk each time. This is the load-bearing piece and is useful beyond the Event Log — feat(mcp): surface reconnect-supervisor outcomes in the Event Log and notifications (#5931) #5955's notification bridge pays a marker read per decision precisely because this does not exist.
  • An opaque per-workspace handle on the envelope (not the path) for the variants that carry one.
  • A panel control: current workspace only (default) vs. all, so the process-wide view stays available for debugging.

Acceptance criteria

  • An in-memory active-workspace signal that reflects a switch without a per-read disk hit, with the existing on-disk resolution as its source of truth
  • Workspace-bound events carry an opaque workspace handle in the /events/domain envelope; no filesystem path reaches the client or the NDJSON export
  • Event Log defaults to the active workspace and can be switched to all workspaces
  • Applied uniformly to the channel, artifact and MCP-supervisor families, not one of them
  • Regression test across a workspace switch: rows from the previous workspace are excluded by default and included when 'all' is selected

Activity

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

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions