# Resource Events (Per-Workspace Subscription API) ## Overview The Resources layer exposes a per-workspace in-process subscription API for status-change events. This API allows server-side callers to register one handler and receive a unified stream of resource status changes for a specific workspace. This is currently an internal TypeScript API (not an HTTP endpoint). ## API Surface Entry point: - `server.resources.getWorkspaceResources(workspaceId)` Subscription method on `WorkspaceResources`: ```ts subscribe(handler: (event: ResourceEvent) => void): () => void ``` Behavior: - Registers `handler` for resource events in one workspace. - Returns an unsubscribe function that removes all registrations created by this call. - Throws `NotFoundError` when `workspaceId` does not exist. ## Event Shape `ResourceEvent` payload: ```ts type ResourceEvent = { group: string; version: string; resource: string; namespace: string; name: string; status: T; }; ``` Fields identify the changed Kubernetes resource (`group`, `version`, `resource`, `namespace`, `name`) and carry the latest `status` snapshot. ## Current Wiring Workspace-level `subscribe` currently aggregates these resource modules: - `models` - `tools` - `kbs` - `documents` - `agents` `stacks` and `secrets` expose their own `subscribe(...)` methods but are not included in the workspace-level aggregate subscription yet. ## Emission Semantics Underlying emission is implemented by `K8sResource` and follows these rules: - Events are emitted only for update cycles where a resource already seen before changes `status`. - Initial add/list population does not emit an event. - Delete events are currently ignored. - Change detection is based on JSON string comparison of `status` (`JSON.stringify(status ?? null)`). - Handlers are called synchronously in registration order. ## Initialization and Lifecycle - Subscriptions are attached to informer-backed resources. - Informers are lazily initialized on first resource access in each module. - The workspace-level subscribe function is lightweight and simply composes per-module unsubscribe callbacks. ## Error and Concurrency Model - Registering/unregistering handlers is in-memory and synchronous. - Event dispatch is best-effort in-process delivery; there is no persistence, replay, cursor, or backpressure protocol. - If one handler throws, the exception bubbles from the dispatch loop (handlers should not throw). ## Scope and Non-Goals This API is intentionally scoped to server-internal observers. Out of scope in current implementation: - cross-process fanout - durable event log or replay - transport protocol (SSE/WebSocket) - delivery guarantees (at-least-once/exactly-once)