diff --git a/alembic/versions/15b577c62f3f_add_hidden_property_to_agents.py b/alembic/versions/15b577c62f3f_add_hidden_property_to_agents.py new file mode 100644 index 00000000..bfd99e39 --- /dev/null +++ b/alembic/versions/15b577c62f3f_add_hidden_property_to_agents.py @@ -0,0 +1,31 @@ +"""Add hidden property to agents + +Revision ID: 15b577c62f3f +Revises: 4c6c9ef0387d +Create Date: 2025-07-30 13:19:15.213121 + +""" + +from typing import Sequence, Union + +import sqlalchemy as sa + +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "15b577c62f3f" +down_revision: Union[str, None] = "4c6c9ef0387d" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.add_column("agents", sa.Column("hidden", sa.Boolean(), nullable=True)) + + # Set hidden=true for existing agents with project names starting with "templates" + connection = op.get_bind() + connection.execute(sa.text("UPDATE agents SET hidden = true WHERE project_id LIKE 'templates-%'")) + + +def downgrade() -> None: + op.drop_column("agents", "hidden") diff --git a/letta/orm/agent.py b/letta/orm/agent.py index a0128f87..2b8b5f1f 100644 --- a/letta/orm/agent.py +++ b/letta/orm/agent.py @@ -100,6 +100,9 @@ class Agent(SqlalchemyBase, OrganizationMixin, ProjectMixin, AsyncAttrs): Integer, nullable=True, doc="The per-file view window character limit for this agent." ) + # indexing controls + hidden: Mapped[Optional[bool]] = mapped_column(Boolean, nullable=True, default=None, doc="If set to True, the agent will be hidden.") + # relationships organization: Mapped["Organization"] = relationship("Organization", back_populates="agents", lazy="raise") tool_exec_environment_variables: Mapped[List["AgentEnvironmentVariable"]] = relationship( @@ -210,6 +213,7 @@ class Agent(SqlalchemyBase, OrganizationMixin, ProjectMixin, AsyncAttrs): "timezone": self.timezone, "max_files_open": self.max_files_open, "per_file_view_window_char_limit": self.per_file_view_window_char_limit, + "hidden": self.hidden, # optional field defaults "tags": [], "tools": [], @@ -297,6 +301,7 @@ class Agent(SqlalchemyBase, OrganizationMixin, ProjectMixin, AsyncAttrs): "last_run_duration_ms": self.last_run_duration_ms, "max_files_open": self.max_files_open, "per_file_view_window_char_limit": self.per_file_view_window_char_limit, + "hidden": self.hidden, } optional_fields = { "tags": [], diff --git a/letta/schemas/agent.py b/letta/schemas/agent.py index bdc36083..46bce4a7 100644 --- a/letta/schemas/agent.py +++ b/letta/schemas/agent.py @@ -122,6 +122,12 @@ class AgentState(OrmMetadataBase, validate_assignment=True): description="The per-file view window character limit for this agent. Setting this too high may exceed the context window, which will break the agent.", ) + # indexing controls + hidden: Optional[bool] = Field( + None, + description="If set to True, the agent will be hidden.", + ) + def get_agent_env_vars_as_dict(self) -> Dict[str, str]: # Get environment variables for this agent specifically per_agent_env_vars = {} @@ -236,6 +242,10 @@ class CreateAgent(BaseModel, validate_assignment=True): # None, description="The per-file view window character limit for this agent. Setting this too high may exceed the context window, which will break the agent.", ) + hidden: Optional[bool] = Field( + None, + description="If set to True, the agent will be hidden.", + ) @field_validator("name") @classmethod @@ -338,6 +348,10 @@ class UpdateAgent(BaseModel): None, description="The per-file view window character limit for this agent. Setting this too high may exceed the context window, which will break the agent.", ) + hidden: Optional[bool] = Field( + None, + description="If set to True, the agent will be hidden.", + ) class Config: extra = "ignore" # Ignores extra fields diff --git a/letta/server/rest_api/routers/v1/agents.py b/letta/server/rest_api/routers/v1/agents.py index b146585d..420439d6 100644 --- a/letta/server/rest_api/routers/v1/agents.py +++ b/letta/server/rest_api/routers/v1/agents.py @@ -86,6 +86,11 @@ async def list_agents( "created_at", description="Field to sort by. Options: 'created_at' (default), 'last_run_completion'", ), + show_hidden_agents: bool | None = Query( + False, + include_in_schema=False, + description="If set to True, include agents marked as hidden in the results.", + ), ): """ List all agents associated with a given user. @@ -115,6 +120,7 @@ async def list_agents( include_relationships=include_relationships, ascending=ascending, sort_by=sort_by, + show_hidden_agents=show_hidden_agents, ) diff --git a/letta/services/agent_manager.py b/letta/services/agent_manager.py index fed81a5b..7220af38 100644 --- a/letta/services/agent_manager.py +++ b/letta/services/agent_manager.py @@ -1034,6 +1034,7 @@ class AgentManager: include_relationships: Optional[List[str]] = None, ascending: bool = True, sort_by: Optional[str] = "created_at", + show_hidden_agents: Optional[bool] = None, ) -> List[PydanticAgentState]: """ Retrieves agents with optimized filtering and optional field selection. @@ -1055,6 +1056,7 @@ class AgentManager: include_relationships (Optional[List[str]]): List of fields to load for performance optimization. ascending (bool): Sort agents in ascending order. sort_by (Optional[str]): Sort agents by this field. + show_hidden_agents (bool): If True, include agents marked as hidden in the results. Returns: List[PydanticAgentState]: The filtered list of matching agents. @@ -1068,6 +1070,10 @@ class AgentManager: query = _apply_identity_filters(query, identity_id, identifier_keys) query = _apply_tag_filter(query, tags, match_all_tags) query = _apply_relationship_filters(query, include_relationships) + + # Apply hidden filter + if not show_hidden_agents: + query = query.where((AgentModel.hidden.is_(None)) | (AgentModel.hidden == False)) query = await _apply_pagination_async(query, before, after, session, ascending=ascending, sort_by=sort_by) if limit: