> ## Documentation Index
> Fetch the complete documentation index at: https://prism.ntecdev.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# ADR-35: ADR-35

> # Title Two-session model: Work Session (User-level) + Agent Session (process-level)  # Decision Introduce two distinct session concepts in the schema and verb

<Info>**Status:** `accepted` · **ADR-35** · Filed 2026-04-30</Info>

# Title

Two-session model: Work Session (User-level) + Agent Session (process-level)

# Decision

Introduce two distinct session concepts in the schema and verb surface:

1. **Work Session** — a User-level day-of-work boundary. One row per user per logical work-day. Auto-created on first agent\_session of the day for a given user; ends explicitly via `prism_work_session_end` or rolls over at midnight. May span multiple machines (Frank on mini1 morning + mini3 afternoon = same Work Session). Used for daily roll-up analytics, retros, signal history grouping. **Not a routing filter.**

2. **Agent Session** — a process-level binding for one agent on one machine. One row per `prism_start` → `prism_wrap` cycle. Bound to a specific machine + process. Heartbeat-tracked. Backend uses for master election, current-delivery target lookup, audit trail. **Not a routing filter.**

`agent_sessions.work_session_id` FK ties each process-level session to the user's day-of-work.

The legacy `controller_registrations` table is renamed `agent_sessions` and gains the `work_session_id` FK plus the `agent_id` FK (per ADR on agent\_id normalization).

# Rationale

The legacy model conflated three concepts under one `session_id`:

* The agent's identity (which agent is this signal for? — now agent\_id)
* The current process binding (which mcp-node should I push to? — now agent\_session\_id)
* The user's day-of-work grouping (whose day's-work is this? — now work\_session\_id)

Three pressures forced separation:

1. **Routing continuity across restarts.** Solved by agent\_id keying (separate ADR). Process-level session can churn freely without losing the routing target.
2. **Daily roll-up boundary.** No clean way to ask "show me all signals from Frank's day" today — agent\_sessions span midnight in messy ways and don't compose across machines. Work Session is the natural unit.
3. **Cross-machine continuity.** Frank moving from mini1 to mini3 mid-day should preserve the work-day's audit context even though every agent gets a new agent\_session\_id on restart. Work Session FK gives this for free.

The work\_session does not affect routing — channel naming uses agent\_id; subscribe authorization uses tenant\_id/org\_id/project\_id. Work Session is a higher-level grouping for human consumption, like a calendar day vs an HTTP request. Treating them as the same thing was the bug.

The verb surface is intentionally minimal in v1: `prism_work_session_start` (explicit override; auto-created if not called), `prism_work_session_end` (explicit close; auto-rolls at midnight if not called). No reach into project lifecycle, no required UX. The column exists, the FK exists, the auto-creation runs — analytics consumers can build on it later.

# Alternatives Considered

1. **One session concept (today's `session_id`), keep conflating.** Status quo; surfaced as broken via the SPEC-054 port miss + bell+count regression. Rejected.
2. **Three explicit sessions: Day Session + Process Session + Connection Session.** Adds a Connection layer between WS lifecycle and process lifetime. Considered; rejected — the WS reconnect cycle is short-lived and process-bounded, no need for its own row.
3. **Work Session as a label/tag on agent\_sessions, not a separate table.** Smaller schema delta but loses the ability to attach explicit `started_at` / `ended_at` / `notes`. Rejected; the entity is real enough to deserve its own row.

# Status

accepted

# References

* SPEC-056 §Two-session model
* Memory: `feedback_heartbeat_out_of_band` (related — separate channels for separate concerns)
