# 103 — Stand up the profile actor skeleton and primary→profile signaling, with no behavior moved yet ## Summary First slice of [[0001-actor-split]]'s step 3 (splitting the single engine actor into reader/primary/profile actors). This ticket is pure plumbing: a second actor task with its own `left_right` write side for profile-owned state, a way for it to read the primary's index (`EngineHandle`'s `IndexReader` is already `Clone`, so no primary-side change is needed to hand out a read-only view), and a `tokio::sync::watch` the primary signals on after each `left_right` sync so the profile actor wakes without busy-polling. No metric computation, alert evaluation, or query routing moves in this ticket — that's [[104-move-metric-computation-to-profile-actor]] and [[105-move-alert-evaluation-to-profile-actor]]. Depends on [[101-extract-metric-computation-into-separate-stage]] (done) for the `ingest_params`/`apply_all_metrics` seam this split will eventually route across two actors. ## Where - `src/engine/handle.rs:33-47` (`IndexWriter`) — `mutate()` calls `self.handle.append(...)` then `self.handle.publish()`; this is where the new watch-sender notification hooks in, right after `publish()`. - `src/engine/handle.rs:48-73` (`IndexReadHandle`/`IndexReader`) — the existing read-side pattern to mirror for the profile actor's own derived state; `IndexReader` is already `Clone` (`handle.rs:61-67` is a template, not something to reuse directly — the profile actor needs its own `left_right` pair for its own state, plus a cloned primary `IndexReader` for read access). - `src/engine/handle.rs:74-78` (`EngineHandle`) and `:119-124` (`spawn`) — the shape to parallel for a new profile-actor spawn function. - `src/engine/snapshot/mod.rs:208-255` (`logs_range`/ `logs_in_sequence_range`) — the read-side primary API the profile actor will use (via its cloned `IndexReader`) to pull newly-visible lines after waking on the watch signal. ## Scope - Add a `ProfileSnapshot` type (new module, e.g. `src/engine/profile.rs`) with its own `left_right` write/read pair, parallel to `EngineSnapshot`'s pattern in `handle.rs` — for now it can be effectively empty (a placeholder struct), since no state moves into it yet. - Add a `tokio::sync::watch` (carrying, e.g., the primary's latest processed sequence) that `IndexWriter::mutate` sends on after `publish()` succeeds. - Add a profile actor task: holds a cloned primary `IndexReader` plus its own `ProfileSnapshot` write side, wakes on the watch channel, and — for this ticket only — reads whatever new lines `logs_in_sequence_range` exposes since its last-seen sequence and does nothing with them (e.g. just advances its own watermark). The goal is proving the wake/read loop works end-to-end, not doing real work with the data yet. - Wire `spawn` (`src/cli/mod.rs:95`) to also spawn the profile actor alongside the primary, but do not yet change what `ApiState` holds or what any API handler talks to — `EngineHandle` and its public API stay exactly as they are today. That's [[106-split-enginehandle-api-for-primary-and-profile]]'s concern. - Out of scope: moving `apply_all_metrics`, metric declarations, or alert evaluation; changing `EngineHandle`'s public surface; eviction coordination beyond what already exists (primary still evicts on its own schedule per [[0001-actor-split]]'s resolved decision — this ticket doesn't need to handle the profile actor falling behind eviction yet, since it isn't consuming anything meaningful).