Data groups: a provider's models read only their own group's data

Every connection is in a data group. Its models are handed, and can find,
only that group's memories, notes, skills, knowledge, reports and
personality -- by search and by id. A chat stays in the group it was
started in: switching its model, the endpoint fallback, the crowd, friends,
bases and the @ menu all stay inside it, and a chat whose model has moved
is refused rather than sent. A group may name its own embedder and image
reviewer. data.manage lets a person make personal groups, remap
connections for themselves and move their own records.

Also: a search no longer mixes two embedders of the same width.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-29 16:07:55 +00:00
co-authored by Claude Opus 5.5
parent e65ea90fe6
commit 9970bb43c6
61 changed files with 3595 additions and 200 deletions
+14
View File
@@ -30,6 +30,7 @@ import logging
from datetime import UTC, datetime
from lembas.db.models import (
DEFAULT_GROUP,
ROLE_ASSISTANT,
TARGET_CHAT,
TARGET_MESSAGES,
@@ -187,6 +188,7 @@ async def deliver(schedule_id: str, message_id: str, *, since: datetime) -> None
source_id=chat_id,
schedule_id=schedule.id,
error="The run did not produce a reply.",
group=schedule.data_group_id or DEFAULT_GROUP,
)
return
reports_service.create(
@@ -198,6 +200,7 @@ async def deliver(schedule_id: str, message_id: str, *, since: datetime) -> None
source_id=chat_id,
schedule_id=schedule.id,
model_id=message.model_id or "",
group=schedule.data_group_id or DEFAULT_GROUP,
)
return
@@ -213,7 +216,18 @@ async def deliver(schedule_id: str, message_id: str, *, since: datetime) -> None
# not write it, and the bubble should not imply they did.
from lembas.services import chat as chat_service
from lembas.services import messages as messages_service
from lembas.services import schedules as schedules_service
# Checked when the schedule was saved, and again here: the person may
# have moved a connection or remapped a group since, and a turn in
# Messages is read by Messages' model on every later reply.
refused = schedules_service.messages_refusal(
db, owner, schedule.data_group_id or ""
)
if refused:
schedule.last_error = refused
db.commit()
return
conversation = messages_service.for_user(db, owner)
chat_service.create_message(
db,