From 1ccb0282fe70dd0e91938ccd72574cc196699661 Mon Sep 17 00:00:00 2001 From: Roberto Date: Fri, 15 May 2026 23:52:29 +0200 Subject: [PATCH] refactor(models): rename Agent classes to Scout Co-Authored-By: Claude Sonnet 4.6 --- app/api/routes/agents.py | 24 ++++++------- app/api/routes/device_ws.py | 10 +++--- app/core/agent_runner.py | 20 +++++------ app/models.py | 66 +++++++++++++++++------------------ tests/test_agent_runner_v2.py | 16 ++++----- tests/test_device_ws.py | 10 +++--- 6 files changed, 73 insertions(+), 73 deletions(-) diff --git a/app/api/routes/agents.py b/app/api/routes/agents.py index 4bc2eed..20426cb 100644 --- a/app/api/routes/agents.py +++ b/app/api/routes/agents.py @@ -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.note_summarizer import generate_note_summary from app.db import get_session -from app.models import AgentRunLog, LocalAgentConfig +from app.models import ScoutRunLog, LocalScoutConfig from app.schemas import ( AgentCatalogItem, AgentCreationCheckRequest, @@ -70,11 +70,11 @@ def _to_data_types(values: list[str]) -> list[str]: return result -def _to_run_log_response(log: AgentRunLog) -> AgentRunLogResponse: +def _to_run_log_response(log: ScoutRunLog) -> AgentRunLogResponse: return AgentRunLogResponse( id=log.id, - agent_id=log.agent_id, - agent_type=log.agent_type, # type: ignore[arg-type] + agent_id=log.scout_id, + agent_type=log.scout_type, # type: ignore[arg-type] status=log.status, # type: ignore[arg-type] items_processed=log.items_processed, items_created=log.items_created, @@ -108,9 +108,9 @@ async def _enforce_run_frequency( hour=0, minute=0, second=0, microsecond=0 ) result = await db.execute( - select(func.count(AgentRunLog.id)).where( - AgentRunLog.user_id == user_id, - AgentRunLog.started_at >= today_start, + select(func.count(ScoutRunLog.id)).where( + ScoutRunLog.user_id == user_id, + ScoutRunLog.started_at >= today_start, ) ) runs_today: int = result.scalar_one() @@ -188,7 +188,7 @@ async def trigger_agent_run( if body.last_run_at else None ) - config = LocalAgentConfig( + config = LocalScoutConfig( id=str(uuid.uuid4()), user_id=current_user.id, device_id=body.device_id, @@ -196,7 +196,7 @@ async def trigger_agent_run( directory_paths=[body.directory], data_types=_to_data_types(body.what_to_extract), prompt_template=body.custom_agent_prompt or "", - agent_config=body.agent_config, + scout_config=body.agent_config, file_extensions=[], schedule_cron=body.batch_interval, 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.", ) - run_log = AgentRunLog( - agent_id=stable_agent_id, - agent_type="local", + run_log = ScoutRunLog( + scout_id=stable_agent_id, + scout_type="local", user_id=current_user.id, status="running", ) diff --git a/app/api/routes/device_ws.py b/app/api/routes/device_ws.py index 2231b7a..943a496 100644 --- a/app/api/routes/device_ws.py +++ b/app/api/routes/device_ws.py @@ -51,7 +51,7 @@ from app.core.memory_middleware import MemoryMiddleware from app.core.output_formatter import StreamFormatter from app.core.ws_context import clear_client_executor, set_client_executor 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.contextual import ContextualScope, render_scope_block @@ -822,14 +822,14 @@ async def _heartbeat_loop(websocket: WebSocket) -> None: # ── Disconnect cleanup ──────────────────────────────────────────────── 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: async with async_session() as db: await db.execute( - update(AgentRunLog) + update(ScoutRunLog) .where( - AgentRunLog.user_id == user_id, - AgentRunLog.status == "running", + ScoutRunLog.user_id == user_id, + ScoutRunLog.status == "running", ) .values( status="error", diff --git a/app/core/agent_runner.py b/app/core/agent_runner.py index c2d6507..82b1679 100644 --- a/app/core/agent_runner.py +++ b/app/core/agent_runner.py @@ -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.ws_context import clear_client_executor, execute_on_client, set_client_executor from app.db import async_session -from app.models import AgentRunLog, CloudAgentConfig, LocalAgentConfig +from app.models import ScoutRunLog, CloudScoutConfig, LocalScoutConfig logger = logging.getLogger(__name__) @@ -555,8 +555,8 @@ def _get_no_match_behavior(agent_config: dict) -> str: async def run_local_agent( user_id: str, - config: LocalAgentConfig, - run_log: AgentRunLog, + config: LocalScoutConfig, + run_log: ScoutRunLog, device_mgr: DeviceConnectionManager, run_context: dict | None = None, ) -> None: @@ -605,7 +605,7 @@ async def run_local_agent( errors: list[str] = [] items_processed = 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) try: @@ -773,8 +773,8 @@ _CLOUD_DEFAULT_LOOKBACK_DAYS: int = 7 async def run_cloud_agent( user_id: str, - config: CloudAgentConfig, - run_log: AgentRunLog, + config: CloudScoutConfig, + run_log: ScoutRunLog, device_mgr: DeviceConnectionManager, ) -> None: """Execute a cloud connector agent run end-to-end. @@ -941,7 +941,7 @@ async def run_cloud_agent( new_encrypted = encrypt_token(refreshed) async with async_session() as db: 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() if cfg_row: @@ -1007,7 +1007,7 @@ async def trigger_pending_runs( async def _finalize_run( - run_log: AgentRunLog, + run_log: ScoutRunLog, *, status: str, items_processed: int = 0, @@ -1031,14 +1031,14 @@ async def _finalize_run( if update_config_last_run and config_id: if config_type == "local": 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() if cfg: cfg.last_run_at = now elif config_type == "cloud": 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() if cfg: diff --git a/app/models.py b/app/models.py index a2031d8..840b859 100644 --- a/app/models.py +++ b/app/models.py @@ -1,15 +1,15 @@ """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. Table inventory: users — account credentials + tier refresh_tokens — hashed refresh token store subscriptions — Stripe subscription records - local_agent_configs — per-device batch agent configs - cloud_agent_configs — OAuth-backed cloud agent configs - agent_run_logs — execution history for all agents + local_scout_configs — per-device batch scout configs + cloud_scout_configs — OAuth-backed cloud scout configs + scout_run_logs — execution history for all scouts memory_core — per-user persistent key/value preferences (encrypted) memory_associative — per-user semantic memory with embeddings (encrypted) memory_episodic — per-user session summaries (encrypted) @@ -158,8 +158,8 @@ class Subscription(Base): user: Mapped[User] = relationship(back_populates="subscription") -class LocalAgentConfig(Base): - __tablename__ = "local_agent_configs" +class LocalScoutConfig(Base): + __tablename__ = "local_scout_configs" id: Mapped[str] = mapped_column( 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) data_types: Mapped[list] = mapped_column(JSON, nullable=False, default=list) 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) schedule_cron: Mapped[str] = mapped_column(String(100), nullable=False, default="0 */6 * * *") 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() ) - run_logs: Mapped[list[AgentRunLog]] = relationship( - back_populates="local_agent", - primaryjoin="and_(AgentRunLog.agent_id == LocalAgentConfig.id, AgentRunLog.agent_type == 'local')", - foreign_keys="AgentRunLog.agent_id", + run_logs: Mapped[list["ScoutRunLog"]] = relationship( + back_populates="local_scout", + primaryjoin="and_(ScoutRunLog.scout_id == LocalScoutConfig.id, ScoutRunLog.scout_type == 'local')", + foreign_keys="ScoutRunLog.scout_id", cascade="all, delete-orphan", - overlaps="run_logs,cloud_agent", + overlaps="run_logs,cloud_scout", ) -class CloudAgentConfig(Base): - __tablename__ = "cloud_agent_configs" +class CloudScoutConfig(Base): + __tablename__ = "cloud_scout_configs" id: Mapped[str] = mapped_column( 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() ) - run_logs: Mapped[list[AgentRunLog]] = relationship( - back_populates="cloud_agent", - primaryjoin="and_(AgentRunLog.agent_id == CloudAgentConfig.id, AgentRunLog.agent_type == 'cloud')", - foreign_keys="AgentRunLog.agent_id", + run_logs: Mapped[list["ScoutRunLog"]] = relationship( + back_populates="cloud_scout", + primaryjoin="and_(ScoutRunLog.scout_id == CloudScoutConfig.id, ScoutRunLog.scout_type == 'cloud')", + foreign_keys="ScoutRunLog.scout_id", cascade="all, delete-orphan", - overlaps="run_logs,local_agent", + overlaps="run_logs,local_scout", ) -class AgentRunLog(Base): - __tablename__ = "agent_run_logs" +class ScoutRunLog(Base): + __tablename__ = "scout_run_logs" id: Mapped[str] = mapped_column( 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 - # depending on agent_type. Query by (agent_id, agent_type) to locate the source config. - agent_id: Mapped[str] = mapped_column(String(255), nullable=False, index=True) - agent_type: Mapped[str] = mapped_column(AgentTypeEnum, nullable=False) + # Plain string — not a FK because it references either local_scout_configs or cloud_scout_configs + # depending on scout_type. Query by (scout_id, scout_type) to locate the source config. + scout_id: Mapped[str] = mapped_column(String(255), nullable=False, index=True) + scout_type: Mapped[str] = mapped_column(AgentTypeEnum, nullable=False) user_id: Mapped[str] = mapped_column( 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) - local_agent: Mapped[LocalAgentConfig | None] = relationship( + local_scout: Mapped["LocalScoutConfig | None"] = relationship( back_populates="run_logs", - primaryjoin="and_(AgentRunLog.agent_id == LocalAgentConfig.id, AgentRunLog.agent_type == 'local')", - foreign_keys="AgentRunLog.agent_id", - overlaps="run_logs,cloud_agent", + primaryjoin="and_(ScoutRunLog.scout_id == LocalScoutConfig.id, ScoutRunLog.scout_type == 'local')", + foreign_keys="ScoutRunLog.scout_id", + overlaps="run_logs,cloud_scout", ) - cloud_agent: Mapped[CloudAgentConfig | None] = relationship( + cloud_scout: Mapped["CloudScoutConfig | None"] = relationship( back_populates="run_logs", - primaryjoin="and_(AgentRunLog.agent_id == CloudAgentConfig.id, AgentRunLog.agent_type == 'cloud')", - foreign_keys="AgentRunLog.agent_id", - overlaps="run_logs,local_agent", + primaryjoin="and_(ScoutRunLog.scout_id == CloudScoutConfig.id, ScoutRunLog.scout_type == 'cloud')", + foreign_keys="ScoutRunLog.scout_id", + overlaps="run_logs,local_scout", ) diff --git a/tests/test_agent_runner_v2.py b/tests/test_agent_runner_v2.py index fc3ab85..346433a 100644 --- a/tests/test_agent_runner_v2.py +++ b/tests/test_agent_runner_v2.py @@ -44,7 +44,7 @@ from app.core.agent_runner import ( ) from app.core.device_manager import DeviceConnectionManager 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 # ── Constants ───────────────────────────────────────────────────────────── @@ -127,8 +127,8 @@ def _make_config( agent_config: dict | None = None, directory: str = "/emails", device_id: str = "dev-001", -) -> LocalAgentConfig: - return LocalAgentConfig( +) -> LocalScoutConfig: + return LocalScoutConfig( id=str(uuid.uuid4()), user_id=_USER_ID, device_id=device_id, @@ -136,7 +136,7 @@ def _make_config( directory_paths=[directory], data_types=["tasks", "notes", "timelines"], prompt_template="", - agent_config=agent_config or _AGENT_CONFIG, + scout_config=agent_config or _AGENT_CONFIG, file_extensions=[".html", ".eml"], schedule_cron="0 */6 * * *", enabled=True, @@ -144,11 +144,11 @@ def _make_config( ) -def _make_run_log(agent_id: str) -> AgentRunLog: - return AgentRunLog( +def _make_run_log(agent_id: str) -> ScoutRunLog: + return ScoutRunLog( id=str(uuid.uuid4()), - agent_id=agent_id, - agent_type="local", + scout_id=agent_id, + scout_type="local", user_id=_USER_ID, status="running", started_at=datetime.now(timezone.utc), diff --git a/tests/test_device_ws.py b/tests/test_device_ws.py index b0307c3..638f2cc 100644 --- a/tests/test_device_ws.py +++ b/tests/test_device_ws.py @@ -22,7 +22,7 @@ import pytest from app.core.device_manager import DeviceConnectionManager from app.db import get_session from app.main import app -from app.models import AgentRunLog +from app.models import ScoutRunLog 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"] - run_log = AgentRunLog( + run_log = ScoutRunLog( id=str(uuid.uuid4()), - agent_id=str(uuid.uuid4()), - agent_type="local", + scout_id=str(uuid.uuid4()), + scout_type="local", user_id=user_id, status="running", 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. async with _TestSessionLocal() as s: 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()