Clean up nested duplicate: v2/v2/engine/session.py
Browse files- v2/v2/engine/session.py +0 -82
v2/v2/engine/session.py
DELETED
|
@@ -1,82 +0,0 @@
|
|
| 1 |
-
"""Per-user sessions with evidence tracking.
|
| 2 |
-
|
| 3 |
-
Confidence in Rivet is earned, not asserted. Every file a chip actually
|
| 4 |
-
reads during a plan is recorded here; the discipline gate grades the
|
| 5 |
-
final answer against this record:
|
| 6 |
-
|
| 7 |
-
HIGH the answer's claims are backed by source files read this session
|
| 8 |
-
MEDIUM backed by architecture map / audit / pack knowledge only
|
| 9 |
-
LOW reasoning from architecture with no matching source at all
|
| 10 |
-
|
| 11 |
-
Sessions are isolated per user (faculty-wide deployment requirement —
|
| 12 |
-
no context bleed between users).
|
| 13 |
-
"""
|
| 14 |
-
|
| 15 |
-
import time
|
| 16 |
-
from dataclasses import dataclass, field
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
@dataclass
|
| 20 |
-
class Session:
|
| 21 |
-
user_id: str
|
| 22 |
-
created_at: float = field(default_factory=time.time)
|
| 23 |
-
last_active: float = field(default_factory=time.time)
|
| 24 |
-
history: list = field(default_factory=list) # [{role, content}]
|
| 25 |
-
files_read: set = field(default_factory=set) # repo-relative paths
|
| 26 |
-
evidence: list = field(default_factory=list) # [{kind, ref, chip}]
|
| 27 |
-
request_count: int = 0
|
| 28 |
-
|
| 29 |
-
def touch(self) -> None:
|
| 30 |
-
self.last_active = time.time()
|
| 31 |
-
|
| 32 |
-
def record_file_read(self, path: str, chip: str) -> None:
|
| 33 |
-
self.files_read.add(path)
|
| 34 |
-
self.evidence.append({"kind": "source_file", "ref": path, "chip": chip})
|
| 35 |
-
|
| 36 |
-
def record_evidence(self, kind: str, ref: str, chip: str) -> None:
|
| 37 |
-
self.evidence.append({"kind": kind, "ref": ref, "chip": chip})
|
| 38 |
-
|
| 39 |
-
def add_turn(self, role: str, content: str, max_turns: int = 20) -> None:
|
| 40 |
-
self.history.append({"role": role, "content": content})
|
| 41 |
-
if len(self.history) > max_turns:
|
| 42 |
-
self.history = self.history[-max_turns:]
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
class SessionManager:
|
| 46 |
-
def __init__(self, ttl_seconds: int = 3600, rate_limit_per_hour: int = 60):
|
| 47 |
-
self.ttl = ttl_seconds
|
| 48 |
-
self.rate_limit = rate_limit_per_hour
|
| 49 |
-
self._sessions: dict[str, Session] = {}
|
| 50 |
-
self._request_log: dict[str, list] = {}
|
| 51 |
-
|
| 52 |
-
def get(self, user_id: str) -> Session:
|
| 53 |
-
self._expire()
|
| 54 |
-
session = self._sessions.get(user_id)
|
| 55 |
-
if session is None:
|
| 56 |
-
session = Session(user_id=user_id)
|
| 57 |
-
self._sessions[user_id] = session
|
| 58 |
-
session.touch()
|
| 59 |
-
return session
|
| 60 |
-
|
| 61 |
-
def check_rate_limit(self, user_id: str) -> bool:
|
| 62 |
-
"""True if the user is within their hourly budget."""
|
| 63 |
-
now = time.time()
|
| 64 |
-
log = [t for t in self._request_log.get(user_id, []) if now - t < 3600]
|
| 65 |
-
self._request_log[user_id] = log
|
| 66 |
-
if len(log) >= self.rate_limit:
|
| 67 |
-
return False
|
| 68 |
-
log.append(now)
|
| 69 |
-
return True
|
| 70 |
-
|
| 71 |
-
def _expire(self) -> None:
|
| 72 |
-
now = time.time()
|
| 73 |
-
stale = [uid for uid, s in self._sessions.items()
|
| 74 |
-
if now - s.last_active > self.ttl]
|
| 75 |
-
for uid in stale:
|
| 76 |
-
del self._sessions[uid]
|
| 77 |
-
|
| 78 |
-
def stats(self) -> dict:
|
| 79 |
-
return {
|
| 80 |
-
"active_sessions": len(self._sessions),
|
| 81 |
-
"users": sorted(self._sessions.keys()),
|
| 82 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|