# LCM + Session Manager — status & roadmap Where the lane stands, what's left, and where to plug in. ## Branch model `feat/lcm-sm-contracts` is the **lane integration branch**: each sub-PR below branched off it and merged **into** it. The whole lane lands on `main` as one unit once it's ready. Sub-PRs were reviewed against the integration branch; the eventual lane→main merge is a single cumulative review. ## Done — implementation complete (behind fakes) | Area | What landed | PR | |------|-------------|----| | Skeleton | `backend/` (Go) + `frontend/` (Electron/TS) | #1 (on `main`) | | Contracts + CI | `domain/` + `ports/`; Go + gitleaks workflows | #2 | | Pure DECIDE core | the deciders + anti-flap quarantine + exhaustive truth-table tests | #4 | | LCM — pipeline | `Apply*` pipeline, per-session serialization, store integration, composition rules, detecting-memory lifecycle | #5 | | LCM — reactions | reaction table + escalation engine + real `TickEscalations` | #6 | | Session Manager | spawn / kill / restore / cleanup / list, eager rollback, worktree-remove safety | #7 | `gofmt` / `go build` / `go vet` / `go test -race` all green across `domain`, `domain/decide`, `lifecycle`, and `session`. The `decide` core is at 100% statement coverage; the impl packages cover the load-bearing logic including the error/rollback paths. ### Build & test ``` cd backend gofmt -l . # must print nothing go build ./... go vet ./... go test -race ./... go test -cover ./... ``` ## Not done — the integration phase Everything above runs against **in-memory fakes**. Making it a live system means swapping fakes for real adapters (built by other lanes) behind the existing ports, and resolving the carried-forward items below. ### Carried-forward items (must be addressed as real adapters land) - **`react()` out-of-lock dispatch.** Reactions fire after the per-session lock releases (deliberate, so a busy-waiting send-to-agent doesn't hold the mutex). Under a live daemon with concurrent observers this can dispatch on a stale snapshot / out of order. Give `react()` a per-session ordering (a small react queue) or re-check the triggering state before dispatching. Documented in `lifecycle/reactions.go`. - **`ExpectedRevision` optimistic-concurrency is unused.** The in-process per-session mutex covers a single daemon. Multi-writer or CDC-driven setups must use the `LifecyclePatch.ExpectedRevision` CAS the contract already exposes. - **Store `Seed` + `Get` need a real implementation.** The Session Manager added two record-with-identity methods to `LifecycleStore`; the real persistence layer must implement them (create-with-identity that rejects an existing id; full-record read by id). Documented in `ports/outbound.go`. ### Real adapters needed (other lanes) | Port | Real adapter | Owning lane | |------|--------------|-------------| | `LifecycleStore` | persistence layer (flat-file/KV + atomic write + lock + CDC) | persistence | | `SCMFacts` producer | SCM poller (batch PR/CI/review enrichment) | SCM | | `Runtime` / `Agent` / `Workspace` | tmux runtime, claude-code/codex agent, git-worktree workspace | coding-agents | | `Notifier` | desktop/Slack notifier | notifications | | `AgentMessenger` | tmux inject with busy-detect + delivery verify | coding-agents | | `SessionManager` consumer | backend API (routes/controllers) + OpenAPI | API | ### Open cross-lane contract questions - **SCM facts** — does `SCMFacts` match what the poller can cheaply produce (batch enrichment, CI log tail as a pointer)? - **Persistence** — is `LifecycleStore` + `LifecyclePatch` the right boundary? Per-session lock vs. the `ExpectedRevision` CAS? - **API** — is the `SessionManager` interface + the `Session` read-model OpenAPI-friendly? ### Land the lane → `main` A final cumulative review of `feat/lcm-sm-contracts` vs. `main`, then merge the complete lane in one unit. ## Where to plug in (for someone picking this up) - **Implementing a real adapter?** Write it to satisfy the matching interface in `ports/`, then construct the `lifecycle.Manager` / `session.Manager` with it in place of the fake. Nothing in `domain`/`lifecycle`/`session` should need to change. - **Changing decision behavior?** It lives in `domain/decide` (pure) — add a truth-table case first; nothing there does I/O. - **Adding a reaction?** Extend the table in `lifecycle/reactions.go` and map the triggering status in `reactionEventFor`. - **Don't** persist the display status, conclude death outside the probe pipeline, or `rm -rf` a still-registered worktree — see the invariants in [architecture.md](architecture.md#7-load-bearing-invariants).