feat: add multi-channel Devii sessions with docs search mode and channel-aware conversation persistence

This commit is contained in:
2026-06-13 21:37:13 +00:00
parent 873186b274
commit 61c1ae8c5d
25 changed files with 1250 additions and 134 deletions
+16 -7
View File
@@ -30,7 +30,7 @@ class DeviiHub:
"ledger": UsageLedger(),
"turns": TurnAudit(),
}
self._sessions: dict[tuple[str, str], DeviiSession] = {}
self._sessions: dict[tuple[str, str, str], DeviiSession] = {}
@property
def ledger(self) -> UsageLedger:
@@ -44,15 +44,20 @@ class DeviiHub:
api_key: str,
base_url: str,
is_admin: bool = False,
channel: str = "main",
) -> DeviiSession:
key = (owner_kind, owner_id)
key = (owner_kind, owner_id, channel)
session = self._sessions.get(key)
if session is not None:
return session
settings, pricing = self._build(api_key, base_url, owner_kind, is_admin)
llm = LLMClient(settings)
# Persistent, owner-isolated stores for signed-in users; ephemeral in-memory for guests.
owned_db = db if owner_kind == "user" else memory_db()
# The `docs` channel (Docii) is a self-contained documentation assistant: it gets its own
# ephemeral stores so Devii's tasks, lessons, behavior and virtual tools never leak into it
# (and a Docii reflection never pollutes the user's Devii memory). Only the conversation
# thread persists, keyed per channel in the shared ConversationStore.
owned_db = db if (owner_kind == "user" and channel == "main") else memory_db()
task_store = TaskStore(owned_db, owner_kind, owner_id)
lessons = LessonStore(owned_db, owner_kind, owner_id)
virtual_tool_store = VirtualToolStore(owned_db, owner_kind, owner_id)
@@ -70,22 +75,26 @@ class DeviiHub:
behavior_store,
self._stores,
is_admin=is_admin,
channel=channel,
)
if owner_kind == "user":
saved = self._stores["conversations"].load(owner_kind, owner_id)
saved = self._stores["conversations"].load(owner_kind, owner_id, channel)
if saved:
session.restore_history(saved)
self._sessions[key] = session
logger.info(
"Created session %s/%s (total %d)",
"Created session %s/%s [%s] (total %d)",
owner_kind,
owner_id,
channel,
len(self._sessions),
)
return session
def find(self, owner_kind: str, owner_id: str) -> DeviiSession | None:
return self._sessions.get((owner_kind, owner_id))
def find(
self, owner_kind: str, owner_id: str, channel: str = "main"
) -> DeviiSession | None:
return self._sessions.get((owner_kind, owner_id, channel))
def active_sessions(self) -> int:
return len(self._sessions)