Model rules: who a chat's model may bring into a conversation

Rules read from the main model decide who it is offered as a crowd member,
a friend and on its roster; any-to-any with denies by default, or
none-to-none with allows. The crowd picker names what it holds back and
why, and a model held back only by a person's own rule -- or by anything,
with the new rules.override -- can still be added by hand. Another data
group is now a deny that an explicit rule opens. Admin -> Model rules and
a card in Settings, each with a matrix drawn by the enforcing function.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-29 17:20:40 +00:00
co-authored by Claude Opus 5.5
parent 9970bb43c6
commit c27fe47d4d
24 changed files with 1324 additions and 29 deletions
+19
View File
@@ -33,6 +33,7 @@ IMAGES = "images"
SCHEDULES = "schedules"
SUBAGENTS = "subagents"
CROWD = "crowd"
RULES = "rules"
BRANDING = "branding"
EXTRACTION = "extraction"
@@ -349,6 +350,16 @@ def _schedules_defaults() -> dict[str, Any]:
}
def _rules_defaults() -> dict[str, Any]:
"""Who may talk to whom, instance-wide. See services/talk.py.
`open` is any model to any model, with deny rules; `closed` is none to none,
with allow rules. Open by default, so an instance that never looks behaves
exactly as it did before rules existed.
"""
return {"mode": "open"}
def _crowd_defaults() -> dict[str, Any]:
"""Several models answering one turn, in order, then again in reverse.
@@ -431,6 +442,7 @@ _DEFAULTS: dict[str, Any] = {
SCHEDULES: _schedules_defaults,
SUBAGENTS: _subagents_defaults,
CROWD: _crowd_defaults,
RULES: _rules_defaults,
# Whose instance this is. The defaults live in `services/branding.py`
# beside the code that reads them, because every one of them is paired with
# a label and a hint for the admin page and splitting the three across two
@@ -726,6 +738,13 @@ def crowd(db: DBSession) -> dict[str, Any]:
return values
def rules(db: DBSession) -> dict[str, Any]:
"""The talk-rules group, with the mode clamped to the two that exist."""
values = get_group(db, RULES)
values["mode"] = "closed" if values.get("mode") == "closed" else "open"
return values
def images_ready(db: DBSession) -> bool:
"""Whether image generation can actually happen.