mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
fix(learning): never broadcast agent learnings to human roles (#250)
Every recorded learning was sent as a knowledge-share notification to all agents, and the recipient query included the human / human-driven roles (CEO, prompter, secretary) — so the CEO's inbox filled with agent learnings. Exclude those roles from the recipient query. The human-role set is resolved from the foundation enum at import (a module constant) so a test that patches the models.base AgentRole alias can't break it. Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
@@ -18,6 +18,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
|
||||
- **A failed PR review no longer looks green.** On a task's detail page, the "PR Reviewer Notes" card was painted a fixed teal/green background regardless of the review verdict, so a `Failed` review — red badge and all — sat inside a green card and could read as passing at a glance. The card background now mirrors the verdict the way the QA Notes card already does: red on a failed review, green on approved/passed, amber on changes-requested, and neutral before a verdict is in.
|
||||
|
||||
- **The CEO and other human roles no longer get spammed with agent "learnings."** Whenever an agent recorded a learning, RoboCo broadcast it as a knowledge-share notification — and the recipient query swept in the human roles too (the CEO, plus the human-driven prompter and secretary). Agent knowledge-sharing is a signal for *agents*; in a human's inbox it is just noise. Those roles are now excluded from learning broadcasts.
|
||||
|
||||
## [0.10.0] - 2026-06-23
|
||||
|
||||
### Added
|
||||
|
||||
@@ -16,8 +16,16 @@ from uuid import UUID
|
||||
|
||||
import structlog
|
||||
|
||||
from roboco.foundation.identity import Role as _Role
|
||||
from roboco.models.optimal import IndexType, SearchResult
|
||||
|
||||
# Human / human-driven roles are never agent learning recipients: the CEO is the
|
||||
# human operator, and the prompter (intake) + secretary act only under direct CEO
|
||||
# command, so a knowledge-share ping to them is inbox noise, not an agent signal.
|
||||
# Resolved from the foundation enum at import time so a test that patches the
|
||||
# models.base AgentRole alias can't break this constant.
|
||||
_HUMAN_ONLY_ROLES = (_Role.CEO, _Role.PROMPTER, _Role.SECRETARY)
|
||||
|
||||
logger = structlog.get_logger()
|
||||
|
||||
|
||||
@@ -201,18 +209,21 @@ class LearningPropagationService:
|
||||
from roboco.db.base import get_db_context
|
||||
from roboco.db.tables import AgentTable
|
||||
from roboco.models import NotificationPriority, NotificationType
|
||||
from roboco.models.base import AgentRole
|
||||
from roboco.models.notification import CreateNotificationParams
|
||||
from roboco.services.notification import NotificationService
|
||||
|
||||
try:
|
||||
async with get_db_context() as db:
|
||||
# Build query based on scope
|
||||
query = select(AgentTable).where(AgentTable.id != learning.agent_id)
|
||||
# Build query based on scope; never the author, never a human role.
|
||||
query = (
|
||||
select(AgentTable)
|
||||
.where(AgentTable.id != learning.agent_id)
|
||||
.where(AgentTable.role.notin_(_HUMAN_ONLY_ROLES))
|
||||
)
|
||||
|
||||
if learning.scope == LearningScope.TEAM:
|
||||
# Only notify agents with the same role
|
||||
from roboco.models.base import AgentRole
|
||||
|
||||
try:
|
||||
role_enum = AgentRole(learning.agent_role.upper())
|
||||
query = query.where(AgentTable.role == role_enum)
|
||||
|
||||
@@ -120,6 +120,41 @@ async def test_team_scope_role_uppercase_via_agent_role_enum(
|
||||
assert other_role.id in notified_ids
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_org_scope_excludes_human_roles(
|
||||
shared_session: AsyncSession,
|
||||
) -> None:
|
||||
"""Human / human-driven roles are never learning recipients, even at ORG
|
||||
scope: knowledge-share is a signal for agents, not noise in the human's inbox.
|
||||
"""
|
||||
author = _make_agent(AgentRole.DEVELOPER)
|
||||
peer = _make_agent(AgentRole.QA)
|
||||
ceo = _make_agent(AgentRole.CEO)
|
||||
secretary = _make_agent(AgentRole.SECRETARY)
|
||||
shared_session.add_all([author, peer, ceo, secretary])
|
||||
await shared_session.flush()
|
||||
|
||||
svc = LearningPropagationService()
|
||||
await svc.initialize(_StubOptimal())
|
||||
learning = await svc.record_learning(
|
||||
RecordLearningParams(
|
||||
agent_id=cast("uuid.UUID", author.id),
|
||||
agent_role="developer",
|
||||
content="A useful pattern worth sharing org-wide",
|
||||
learning_type=LearningType.PATTERN,
|
||||
scope=LearningScope.ORG,
|
||||
)
|
||||
)
|
||||
notified_ids = {
|
||||
n.target_agent_id
|
||||
for n in svc._notification_queue
|
||||
if n.learning_id == learning.learning_id
|
||||
}
|
||||
assert peer.id in notified_ids
|
||||
assert ceo.id not in notified_ids
|
||||
assert secretary.id not in notified_ids
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_team_scope_invalid_role_skips_filter(
|
||||
shared_session: AsyncSession,
|
||||
|
||||
Reference in New Issue
Block a user