refactor(models): rename Agent classes to Scout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Roberto
2026-05-15 23:52:29 +02:00
parent 1a20c11e86
commit 1ccb0282fe
6 changed files with 73 additions and 73 deletions

View File

@@ -28,7 +28,7 @@ from app.core.agent_runner import is_agent_running, run_local_agent
from app.core.device_manager import device_manager from app.core.device_manager import device_manager
from app.core.note_summarizer import generate_note_summary from app.core.note_summarizer import generate_note_summary
from app.db import get_session from app.db import get_session
from app.models import AgentRunLog, LocalAgentConfig from app.models import ScoutRunLog, LocalScoutConfig
from app.schemas import ( from app.schemas import (
AgentCatalogItem, AgentCatalogItem,
AgentCreationCheckRequest, AgentCreationCheckRequest,
@@ -70,11 +70,11 @@ def _to_data_types(values: list[str]) -> list[str]:
return result return result
def _to_run_log_response(log: AgentRunLog) -> AgentRunLogResponse: def _to_run_log_response(log: ScoutRunLog) -> AgentRunLogResponse:
return AgentRunLogResponse( return AgentRunLogResponse(
id=log.id, id=log.id,
agent_id=log.agent_id, agent_id=log.scout_id,
agent_type=log.agent_type, # type: ignore[arg-type] agent_type=log.scout_type, # type: ignore[arg-type]
status=log.status, # type: ignore[arg-type] status=log.status, # type: ignore[arg-type]
items_processed=log.items_processed, items_processed=log.items_processed,
items_created=log.items_created, items_created=log.items_created,
@@ -108,9 +108,9 @@ async def _enforce_run_frequency(
hour=0, minute=0, second=0, microsecond=0 hour=0, minute=0, second=0, microsecond=0
) )
result = await db.execute( result = await db.execute(
select(func.count(AgentRunLog.id)).where( select(func.count(ScoutRunLog.id)).where(
AgentRunLog.user_id == user_id, ScoutRunLog.user_id == user_id,
AgentRunLog.started_at >= today_start, ScoutRunLog.started_at >= today_start,
) )
) )
runs_today: int = result.scalar_one() runs_today: int = result.scalar_one()
@@ -188,7 +188,7 @@ async def trigger_agent_run(
if body.last_run_at if body.last_run_at
else None else None
) )
config = LocalAgentConfig( config = LocalScoutConfig(
id=str(uuid.uuid4()), id=str(uuid.uuid4()),
user_id=current_user.id, user_id=current_user.id,
device_id=body.device_id, device_id=body.device_id,
@@ -196,7 +196,7 @@ async def trigger_agent_run(
directory_paths=[body.directory], directory_paths=[body.directory],
data_types=_to_data_types(body.what_to_extract), data_types=_to_data_types(body.what_to_extract),
prompt_template=body.custom_agent_prompt or "", prompt_template=body.custom_agent_prompt or "",
agent_config=body.agent_config, scout_config=body.agent_config,
file_extensions=[], file_extensions=[],
schedule_cron=body.batch_interval, schedule_cron=body.batch_interval,
enabled=True, enabled=True,
@@ -212,9 +212,9 @@ async def trigger_agent_run(
detail="Agent is already running. Only one run per agent is allowed at a time.", detail="Agent is already running. Only one run per agent is allowed at a time.",
) )
run_log = AgentRunLog( run_log = ScoutRunLog(
agent_id=stable_agent_id, scout_id=stable_agent_id,
agent_type="local", scout_type="local",
user_id=current_user.id, user_id=current_user.id,
status="running", status="running",
) )

View File

@@ -51,7 +51,7 @@ from app.core.memory_middleware import MemoryMiddleware
from app.core.output_formatter import StreamFormatter from app.core.output_formatter import StreamFormatter
from app.core.ws_context import clear_client_executor, set_client_executor from app.core.ws_context import clear_client_executor, set_client_executor
from app.db import async_session from app.db import async_session
from app.models import AgentRunLog from app.models import ScoutRunLog
from app.schemas import WsFrameType, WsStreamEnd from app.schemas import WsFrameType, WsStreamEnd
from app.schemas.contextual import ContextualScope, render_scope_block from app.schemas.contextual import ContextualScope, render_scope_block
@@ -822,14 +822,14 @@ async def _heartbeat_loop(websocket: WebSocket) -> None:
# ── Disconnect cleanup ──────────────────────────────────────────────── # ── Disconnect cleanup ────────────────────────────────────────────────
async def _mark_runs_disconnected(user_id: str) -> None: async def _mark_runs_disconnected(user_id: str) -> None:
"""Mark all in-progress AgentRunLog rows as 'error' for this user.""" """Mark all in-progress ScoutRunLog rows as 'error' for this user."""
try: try:
async with async_session() as db: async with async_session() as db:
await db.execute( await db.execute(
update(AgentRunLog) update(ScoutRunLog)
.where( .where(
AgentRunLog.user_id == user_id, ScoutRunLog.user_id == user_id,
AgentRunLog.status == "running", ScoutRunLog.status == "running",
) )
.values( .values(
status="error", status="error",

View File

@@ -48,7 +48,7 @@ from app.core.llm import get_agent_llm, model_for_agent
from app.core.preprocessors import detect_content_type, preprocess from app.core.preprocessors import detect_content_type, preprocess
from app.core.ws_context import clear_client_executor, execute_on_client, set_client_executor from app.core.ws_context import clear_client_executor, execute_on_client, set_client_executor
from app.db import async_session from app.db import async_session
from app.models import AgentRunLog, CloudAgentConfig, LocalAgentConfig from app.models import ScoutRunLog, CloudScoutConfig, LocalScoutConfig
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -555,8 +555,8 @@ def _get_no_match_behavior(agent_config: dict) -> str:
async def run_local_agent( async def run_local_agent(
user_id: str, user_id: str,
config: LocalAgentConfig, config: LocalScoutConfig,
run_log: AgentRunLog, run_log: ScoutRunLog,
device_mgr: DeviceConnectionManager, device_mgr: DeviceConnectionManager,
run_context: dict | None = None, run_context: dict | None = None,
) -> None: ) -> None:
@@ -605,7 +605,7 @@ async def run_local_agent(
errors: list[str] = [] errors: list[str] = []
items_processed = 0 items_processed = 0
items_created = 0 items_created = 0
agent_config: dict = config.agent_config or {} agent_config: dict = config.scout_config or {}
processing_tools = _build_processing_tools(config.data_types) processing_tools = _build_processing_tools(config.data_types)
try: try:
@@ -773,8 +773,8 @@ _CLOUD_DEFAULT_LOOKBACK_DAYS: int = 7
async def run_cloud_agent( async def run_cloud_agent(
user_id: str, user_id: str,
config: CloudAgentConfig, config: CloudScoutConfig,
run_log: AgentRunLog, run_log: ScoutRunLog,
device_mgr: DeviceConnectionManager, device_mgr: DeviceConnectionManager,
) -> None: ) -> None:
"""Execute a cloud connector agent run end-to-end. """Execute a cloud connector agent run end-to-end.
@@ -941,7 +941,7 @@ async def run_cloud_agent(
new_encrypted = encrypt_token(refreshed) new_encrypted = encrypt_token(refreshed)
async with async_session() as db: async with async_session() as db:
cfg_result = await db.execute( cfg_result = await db.execute(
select(CloudAgentConfig).where(CloudAgentConfig.id == config.id) select(CloudScoutConfig).where(CloudScoutConfig.id == config.id)
) )
cfg_row = cfg_result.scalar_one_or_none() cfg_row = cfg_result.scalar_one_or_none()
if cfg_row: if cfg_row:
@@ -1007,7 +1007,7 @@ async def trigger_pending_runs(
async def _finalize_run( async def _finalize_run(
run_log: AgentRunLog, run_log: ScoutRunLog,
*, *,
status: str, status: str,
items_processed: int = 0, items_processed: int = 0,
@@ -1031,14 +1031,14 @@ async def _finalize_run(
if update_config_last_run and config_id: if update_config_last_run and config_id:
if config_type == "local": if config_type == "local":
cfg_result = await db.execute( cfg_result = await db.execute(
select(LocalAgentConfig).where(LocalAgentConfig.id == config_id) select(LocalScoutConfig).where(LocalScoutConfig.id == config_id)
) )
cfg = cfg_result.scalar_one_or_none() cfg = cfg_result.scalar_one_or_none()
if cfg: if cfg:
cfg.last_run_at = now cfg.last_run_at = now
elif config_type == "cloud": elif config_type == "cloud":
cfg_result = await db.execute( cfg_result = await db.execute(
select(CloudAgentConfig).where(CloudAgentConfig.id == config_id) select(CloudScoutConfig).where(CloudScoutConfig.id == config_id)
) )
cfg = cfg_result.scalar_one_or_none() cfg = cfg_result.scalar_one_or_none()
if cfg: if cfg:

View File

@@ -1,15 +1,15 @@
"""SQLAlchemy ORM models for all persistent tables. """SQLAlchemy ORM models for all persistent tables.
Only auth, billing, agent config, and memory data live here. Only auth, billing, scout config, and memory data live here.
User content (notes, tasks, etc.) lives exclusively on the client. User content (notes, tasks, etc.) lives exclusively on the client.
Table inventory: Table inventory:
users — account credentials + tier users — account credentials + tier
refresh_tokens — hashed refresh token store refresh_tokens — hashed refresh token store
subscriptions — Stripe subscription records subscriptions — Stripe subscription records
local_agent_configs — per-device batch agent configs local_scout_configs — per-device batch scout configs
cloud_agent_configs — OAuth-backed cloud agent configs cloud_scout_configs — OAuth-backed cloud scout configs
agent_run_logs — execution history for all agents scout_run_logs — execution history for all scouts
memory_core — per-user persistent key/value preferences (encrypted) memory_core — per-user persistent key/value preferences (encrypted)
memory_associative — per-user semantic memory with embeddings (encrypted) memory_associative — per-user semantic memory with embeddings (encrypted)
memory_episodic — per-user session summaries (encrypted) memory_episodic — per-user session summaries (encrypted)
@@ -158,8 +158,8 @@ class Subscription(Base):
user: Mapped[User] = relationship(back_populates="subscription") user: Mapped[User] = relationship(back_populates="subscription")
class LocalAgentConfig(Base): class LocalScoutConfig(Base):
__tablename__ = "local_agent_configs" __tablename__ = "local_scout_configs"
id: Mapped[str] = mapped_column( id: Mapped[str] = mapped_column(
Uuid(as_uuid=False), primary_key=True, default=_uuid Uuid(as_uuid=False), primary_key=True, default=_uuid
@@ -172,7 +172,7 @@ class LocalAgentConfig(Base):
directory_paths: Mapped[list] = mapped_column(JSON, nullable=False, default=list) directory_paths: Mapped[list] = mapped_column(JSON, nullable=False, default=list)
data_types: Mapped[list] = mapped_column(JSON, nullable=False, default=list) data_types: Mapped[list] = mapped_column(JSON, nullable=False, default=list)
prompt_template: Mapped[str] = mapped_column(Text, nullable=False, default="") prompt_template: Mapped[str] = mapped_column(Text, nullable=False, default="")
agent_config: Mapped[dict | None] = mapped_column(JSON, nullable=True) scout_config: Mapped[dict | None] = mapped_column(JSON, nullable=True)
file_extensions: Mapped[list] = mapped_column(JSON, nullable=False, default=list) file_extensions: Mapped[list] = mapped_column(JSON, nullable=False, default=list)
schedule_cron: Mapped[str] = mapped_column(String(100), nullable=False, default="0 */6 * * *") schedule_cron: Mapped[str] = mapped_column(String(100), nullable=False, default="0 */6 * * *")
enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True) enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
@@ -184,17 +184,17 @@ class LocalAgentConfig(Base):
DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now() DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()
) )
run_logs: Mapped[list[AgentRunLog]] = relationship( run_logs: Mapped[list["ScoutRunLog"]] = relationship(
back_populates="local_agent", back_populates="local_scout",
primaryjoin="and_(AgentRunLog.agent_id == LocalAgentConfig.id, AgentRunLog.agent_type == 'local')", primaryjoin="and_(ScoutRunLog.scout_id == LocalScoutConfig.id, ScoutRunLog.scout_type == 'local')",
foreign_keys="AgentRunLog.agent_id", foreign_keys="ScoutRunLog.scout_id",
cascade="all, delete-orphan", cascade="all, delete-orphan",
overlaps="run_logs,cloud_agent", overlaps="run_logs,cloud_scout",
) )
class CloudAgentConfig(Base): class CloudScoutConfig(Base):
__tablename__ = "cloud_agent_configs" __tablename__ = "cloud_scout_configs"
id: Mapped[str] = mapped_column( id: Mapped[str] = mapped_column(
Uuid(as_uuid=False), primary_key=True, default=_uuid Uuid(as_uuid=False), primary_key=True, default=_uuid
@@ -218,25 +218,25 @@ class CloudAgentConfig(Base):
DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now() DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()
) )
run_logs: Mapped[list[AgentRunLog]] = relationship( run_logs: Mapped[list["ScoutRunLog"]] = relationship(
back_populates="cloud_agent", back_populates="cloud_scout",
primaryjoin="and_(AgentRunLog.agent_id == CloudAgentConfig.id, AgentRunLog.agent_type == 'cloud')", primaryjoin="and_(ScoutRunLog.scout_id == CloudScoutConfig.id, ScoutRunLog.scout_type == 'cloud')",
foreign_keys="AgentRunLog.agent_id", foreign_keys="ScoutRunLog.scout_id",
cascade="all, delete-orphan", cascade="all, delete-orphan",
overlaps="run_logs,local_agent", overlaps="run_logs,local_scout",
) )
class AgentRunLog(Base): class ScoutRunLog(Base):
__tablename__ = "agent_run_logs" __tablename__ = "scout_run_logs"
id: Mapped[str] = mapped_column( id: Mapped[str] = mapped_column(
Uuid(as_uuid=False), primary_key=True, default=_uuid Uuid(as_uuid=False), primary_key=True, default=_uuid
) )
# Plain string — not a FK because it references either local_agent_configs or cloud_agent_configs # Plain string — not a FK because it references either local_scout_configs or cloud_scout_configs
# depending on agent_type. Query by (agent_id, agent_type) to locate the source config. # depending on scout_type. Query by (scout_id, scout_type) to locate the source config.
agent_id: Mapped[str] = mapped_column(String(255), nullable=False, index=True) scout_id: Mapped[str] = mapped_column(String(255), nullable=False, index=True)
agent_type: Mapped[str] = mapped_column(AgentTypeEnum, nullable=False) scout_type: Mapped[str] = mapped_column(AgentTypeEnum, nullable=False)
user_id: Mapped[str] = mapped_column( user_id: Mapped[str] = mapped_column(
Uuid(as_uuid=False), ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True Uuid(as_uuid=False), ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True
) )
@@ -250,17 +250,17 @@ class AgentRunLog(Base):
) )
completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
local_agent: Mapped[LocalAgentConfig | None] = relationship( local_scout: Mapped["LocalScoutConfig | None"] = relationship(
back_populates="run_logs", back_populates="run_logs",
primaryjoin="and_(AgentRunLog.agent_id == LocalAgentConfig.id, AgentRunLog.agent_type == 'local')", primaryjoin="and_(ScoutRunLog.scout_id == LocalScoutConfig.id, ScoutRunLog.scout_type == 'local')",
foreign_keys="AgentRunLog.agent_id", foreign_keys="ScoutRunLog.scout_id",
overlaps="run_logs,cloud_agent", overlaps="run_logs,cloud_scout",
) )
cloud_agent: Mapped[CloudAgentConfig | None] = relationship( cloud_scout: Mapped["CloudScoutConfig | None"] = relationship(
back_populates="run_logs", back_populates="run_logs",
primaryjoin="and_(AgentRunLog.agent_id == CloudAgentConfig.id, AgentRunLog.agent_type == 'cloud')", primaryjoin="and_(ScoutRunLog.scout_id == CloudScoutConfig.id, ScoutRunLog.scout_type == 'cloud')",
foreign_keys="AgentRunLog.agent_id", foreign_keys="ScoutRunLog.scout_id",
overlaps="run_logs,local_agent", overlaps="run_logs,local_scout",
) )

View File

@@ -44,7 +44,7 @@ from app.core.agent_runner import (
) )
from app.core.device_manager import DeviceConnectionManager from app.core.device_manager import DeviceConnectionManager
from app.core.langfuse_client import get_langfuse from app.core.langfuse_client import get_langfuse
from app.models import AgentRunLog, LocalAgentConfig from app.models import ScoutRunLog, LocalScoutConfig
from tests.conftest import TEST_USER_IDS from tests.conftest import TEST_USER_IDS
# ── Constants ───────────────────────────────────────────────────────────── # ── Constants ─────────────────────────────────────────────────────────────
@@ -127,8 +127,8 @@ def _make_config(
agent_config: dict | None = None, agent_config: dict | None = None,
directory: str = "/emails", directory: str = "/emails",
device_id: str = "dev-001", device_id: str = "dev-001",
) -> LocalAgentConfig: ) -> LocalScoutConfig:
return LocalAgentConfig( return LocalScoutConfig(
id=str(uuid.uuid4()), id=str(uuid.uuid4()),
user_id=_USER_ID, user_id=_USER_ID,
device_id=device_id, device_id=device_id,
@@ -136,7 +136,7 @@ def _make_config(
directory_paths=[directory], directory_paths=[directory],
data_types=["tasks", "notes", "timelines"], data_types=["tasks", "notes", "timelines"],
prompt_template="", prompt_template="",
agent_config=agent_config or _AGENT_CONFIG, scout_config=agent_config or _AGENT_CONFIG,
file_extensions=[".html", ".eml"], file_extensions=[".html", ".eml"],
schedule_cron="0 */6 * * *", schedule_cron="0 */6 * * *",
enabled=True, enabled=True,
@@ -144,11 +144,11 @@ def _make_config(
) )
def _make_run_log(agent_id: str) -> AgentRunLog: def _make_run_log(agent_id: str) -> ScoutRunLog:
return AgentRunLog( return ScoutRunLog(
id=str(uuid.uuid4()), id=str(uuid.uuid4()),
agent_id=agent_id, scout_id=agent_id,
agent_type="local", scout_type="local",
user_id=_USER_ID, user_id=_USER_ID,
status="running", status="running",
started_at=datetime.now(timezone.utc), started_at=datetime.now(timezone.utc),

View File

@@ -22,7 +22,7 @@ import pytest
from app.core.device_manager import DeviceConnectionManager from app.core.device_manager import DeviceConnectionManager
from app.db import get_session from app.db import get_session
from app.main import app from app.main import app
from app.models import AgentRunLog from app.models import ScoutRunLog
from tests.conftest import TEST_USER_IDS, make_jwt from tests.conftest import TEST_USER_IDS, make_jwt
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -262,10 +262,10 @@ async def test_mark_runs_disconnected_updates_db(db_session):
user_id = TEST_USER_IDS["free"] user_id = TEST_USER_IDS["free"]
run_log = AgentRunLog( run_log = ScoutRunLog(
id=str(uuid.uuid4()), id=str(uuid.uuid4()),
agent_id=str(uuid.uuid4()), scout_id=str(uuid.uuid4()),
agent_type="local", scout_type="local",
user_id=user_id, user_id=user_id,
status="running", status="running",
started_at=datetime.now(timezone.utc), started_at=datetime.now(timezone.utc),
@@ -280,7 +280,7 @@ async def test_mark_runs_disconnected_updates_db(db_session):
# Verify through the same session factory. # Verify through the same session factory.
async with _TestSessionLocal() as s: async with _TestSessionLocal() as s:
result = await s.execute( result = await s.execute(
select(AgentRunLog).where(AgentRunLog.id == run_log.id) select(ScoutRunLog).where(ScoutRunLog.id == run_log.id)
) )
updated = result.scalar_one_or_none() updated = result.scalar_one_or_none()