feat: attach/detach identities route on blocks and agents, move archives attach/detach routes to agents [LET-4428] (#5708)

* deprecate ids for identity endpoints in favor of attach

* move archive attach/detach to agent

* new identities routes

* overrides for path

---------

Co-authored-by: Ari Webb <ari@letta.com>
This commit is contained in:
Ari Webb
2025-10-23 16:40:40 -07:00
committed by Caren Thomas
parent 705bb9d958
commit 272f055b4a
7 changed files with 642 additions and 180 deletions

View File

@@ -687,6 +687,74 @@ paths:
required: true
schema:
type: string
/v1/agents/{agent_id}/archives/attach/{archive_id}:
patch:
x-fern-sdk-group-name:
- agents
- archives
x-fern-sdk-method-name: attach
parameters:
- name: agent_id
in: path
required: true
schema:
type: string
- name: archive_id
in: path
required: true
schema:
type: string
/v1/agents/{agent_id}/archives/detach/{archive_id}:
patch:
x-fern-sdk-group-name:
- agents
- archives
x-fern-sdk-method-name: detach
parameters:
- name: agent_id
in: path
required: true
schema:
type: string
- name: archive_id
in: path
required: true
schema:
type: string
/v1/agents/{agent_id}/identities/attach/{identity_id}:
patch:
x-fern-sdk-group-name:
- agents
- identities
x-fern-sdk-method-name: attach
parameters:
- name: agent_id
in: path
required: true
schema:
type: string
- name: identity_id
in: path
required: true
schema:
type: string
/v1/agents/{agent_id}/identities/detach/{identity_id}:
patch:
x-fern-sdk-group-name:
- agents
- identities
x-fern-sdk-method-name: detach
parameters:
- name: agent_id
in: path
required: true
schema:
type: string
- name: identity_id
in: path
required: true
schema:
type: string
/v1/agents/import:
post:
x-fern-sdk-group-name:
@@ -767,6 +835,40 @@ paths:
- blocks
- agents
x-fern-sdk-method-name: list
/v1/blocks/{block_id}/identities/attach/{identity_id}:
patch:
x-fern-sdk-group-name:
- blocks
- identities
x-fern-sdk-method-name: attach
parameters:
- name: block_id
in: path
required: true
schema:
type: string
- name: identity_id
in: path
required: true
schema:
type: string
/v1/blocks/{block_id}/identities/detach/{identity_id}:
patch:
x-fern-sdk-group-name:
- blocks
- identities
x-fern-sdk-method-name: detach
parameters:
- name: block_id
in: path
required: true
schema:
type: string
- name: identity_id
in: path
required: true
schema:
type: string
/v1/jobs/:
get:
x-fern-sdk-group-name:

View File

@@ -346,130 +346,6 @@
}
}
},
"/v1/archives/{archive_id}/attach/{agent_id}": {
"patch": {
"tags": ["archives"],
"summary": "Attach Agent To Archive",
"description": "Attach an agent to an archive.",
"operationId": "attach_agent_to_archive",
"parameters": [
{
"name": "archive_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"minLength": 44,
"maxLength": 44,
"pattern": "^archive-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
"description": "The ID of the archive in the format 'archive-<uuid4>'",
"examples": ["archive-123e4567-e89b-42d3-8456-426614174000"],
"title": "Archive Id"
},
"description": "The ID of the archive in the format 'archive-<uuid4>'"
},
{
"name": "agent_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"minLength": 42,
"maxLength": 42,
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
"description": "The ID of the agent in the format 'agent-<uuid4>'",
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
"title": "Agent Id"
},
"description": "The ID of the agent in the format 'agent-<uuid4>'"
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Archive"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/v1/archives/{archive_id}/detach/{agent_id}": {
"patch": {
"tags": ["archives"],
"summary": "Detach Agent From Archive",
"description": "Detach an agent from an archive.",
"operationId": "detach_agent_from_archive",
"parameters": [
{
"name": "archive_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"minLength": 44,
"maxLength": 44,
"pattern": "^archive-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
"description": "The ID of the archive in the format 'archive-<uuid4>'",
"examples": ["archive-123e4567-e89b-42d3-8456-426614174000"],
"title": "Archive Id"
},
"description": "The ID of the archive in the format 'archive-<uuid4>'"
},
{
"name": "agent_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"minLength": 42,
"maxLength": 42,
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
"description": "The ID of the agent in the format 'agent-<uuid4>'",
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
"title": "Agent Id"
},
"description": "The ID of the agent in the format 'agent-<uuid4>'"
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Archive"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/v1/archives/{archive_id}/agents": {
"get": {
"tags": ["archives"],
@@ -6325,6 +6201,230 @@
}
}
},
"/v1/agents/{agent_id}/archives/attach/{archive_id}": {
"patch": {
"tags": ["agents"],
"summary": "Attach Archive To Agent",
"description": "Attach an archive to an agent.",
"operationId": "attach_archive_to_agent",
"parameters": [
{
"name": "archive_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Archive Id"
}
},
{
"name": "agent_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"minLength": 42,
"maxLength": 42,
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
"description": "The ID of the agent in the format 'agent-<uuid4>'",
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
"title": "Agent Id"
},
"description": "The ID of the agent in the format 'agent-<uuid4>'"
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AgentState"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/v1/agents/{agent_id}/archives/detach/{archive_id}": {
"patch": {
"tags": ["agents"],
"summary": "Detach Archive From Agent",
"description": "Detach an archive from an agent.",
"operationId": "detach_archive_from_agent",
"parameters": [
{
"name": "archive_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Archive Id"
}
},
{
"name": "agent_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"minLength": 42,
"maxLength": 42,
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
"description": "The ID of the agent in the format 'agent-<uuid4>'",
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
"title": "Agent Id"
},
"description": "The ID of the agent in the format 'agent-<uuid4>'"
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AgentState"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/v1/agents/{agent_id}/identities/attach/{identity_id}": {
"patch": {
"tags": ["agents"],
"summary": "Attach Identity To Agent",
"description": "Attach an identity to an agent.",
"operationId": "attach_identity_to_agent",
"parameters": [
{
"name": "identity_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Identity Id"
}
},
{
"name": "agent_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"minLength": 42,
"maxLength": 42,
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
"description": "The ID of the agent in the format 'agent-<uuid4>'",
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
"title": "Agent Id"
},
"description": "The ID of the agent in the format 'agent-<uuid4>'"
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AgentState"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/v1/agents/{agent_id}/identities/detach/{identity_id}": {
"patch": {
"tags": ["agents"],
"summary": "Detach Identity From Agent",
"description": "Detach an identity from an agent.",
"operationId": "detach_identity_from_agent",
"parameters": [
{
"name": "identity_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Identity Id"
}
},
{
"name": "agent_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"minLength": 42,
"maxLength": 42,
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
"description": "The ID of the agent in the format 'agent-<uuid4>'",
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
"title": "Agent Id"
},
"description": "The ID of the agent in the format 'agent-<uuid4>'"
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AgentState"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/v1/agents/{agent_id}/archival-memory": {
"get": {
"tags": ["agents"],
@@ -10810,6 +10910,118 @@
}
}
},
"/v1/blocks/{block_id}/identities/attach/{identity_id}": {
"patch": {
"tags": ["blocks"],
"summary": "Attach Identity To Block",
"description": "Attach an identity to a block.",
"operationId": "attach_identity_to_block",
"parameters": [
{
"name": "identity_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Identity Id"
}
},
{
"name": "block_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"minLength": 42,
"maxLength": 42,
"pattern": "^block-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
"description": "The ID of the block in the format 'block-<uuid4>'",
"examples": ["block-123e4567-e89b-42d3-8456-426614174000"],
"title": "Block Id"
},
"description": "The ID of the block in the format 'block-<uuid4>'"
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Block"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/v1/blocks/{block_id}/identities/detach/{identity_id}": {
"patch": {
"tags": ["blocks"],
"summary": "Detach Identity From Block",
"description": "Detach an identity from a block.",
"operationId": "detach_identity_from_block",
"parameters": [
{
"name": "identity_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Identity Id"
}
},
{
"name": "block_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"minLength": 42,
"maxLength": 42,
"pattern": "^block-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
"description": "The ID of the block in the format 'block-<uuid4>'",
"examples": ["block-123e4567-e89b-42d3-8456-426614174000"],
"title": "Block Id"
},
"description": "The ID of the block in the format 'block-<uuid4>'"
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Block"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/v1/jobs/": {
"get": {
"tags": ["jobs"],
@@ -23342,7 +23554,8 @@
}
],
"title": "Agent Ids",
"description": "The agent ids that are associated with the identity."
"description": "The agent ids that are associated with the identity.",
"deprecated": true
},
"block_ids": {
"anyOf": [
@@ -23357,7 +23570,8 @@
}
],
"title": "Block Ids",
"description": "The IDs of the blocks associated with the identity."
"description": "The IDs of the blocks associated with the identity.",
"deprecated": true
},
"properties": {
"anyOf": [
@@ -23482,7 +23696,8 @@
}
],
"title": "Agent Ids",
"description": "The agent ids that are associated with the identity."
"description": "The agent ids that are associated with the identity.",
"deprecated": true
},
"block_ids": {
"anyOf": [
@@ -23497,7 +23712,8 @@
}
],
"title": "Block Ids",
"description": "The IDs of the blocks associated with the identity."
"description": "The IDs of the blocks associated with the identity.",
"deprecated": true
},
"properties": {
"anyOf": [
@@ -23560,7 +23776,8 @@
}
],
"title": "Agent Ids",
"description": "The agent ids that are associated with the identity."
"description": "The agent ids that are associated with the identity.",
"deprecated": true
},
"block_ids": {
"anyOf": [
@@ -23575,7 +23792,8 @@
}
],
"title": "Block Ids",
"description": "The IDs of the blocks associated with the identity."
"description": "The IDs of the blocks associated with the identity.",
"deprecated": true
},
"properties": {
"anyOf": [

View File

@@ -57,8 +57,8 @@ class IdentityCreate(LettaBase):
name: str = Field(..., description="The name of the identity.")
identity_type: IdentityType = Field(..., description="The type of the identity.")
project_id: Optional[str] = Field(None, description="The project id of the identity, if applicable.")
agent_ids: Optional[List[str]] = Field(None, description="The agent ids that are associated with the identity.")
block_ids: Optional[List[str]] = Field(None, description="The IDs of the blocks associated with the identity.")
agent_ids: Optional[List[str]] = Field(None, description="The agent ids that are associated with the identity.", deprecated=True)
block_ids: Optional[List[str]] = Field(None, description="The IDs of the blocks associated with the identity.", deprecated=True)
properties: Optional[List[IdentityProperty]] = Field(None, description="List of properties associated with the identity.")
@@ -67,8 +67,8 @@ class IdentityUpsert(LettaBase):
name: str = Field(..., description="The name of the identity.")
identity_type: IdentityType = Field(..., description="The type of the identity.")
project_id: Optional[str] = Field(None, description="The project id of the identity, if applicable.")
agent_ids: Optional[List[str]] = Field(None, description="The agent ids that are associated with the identity.")
block_ids: Optional[List[str]] = Field(None, description="The IDs of the blocks associated with the identity.")
agent_ids: Optional[List[str]] = Field(None, description="The agent ids that are associated with the identity.", deprecated=True)
block_ids: Optional[List[str]] = Field(None, description="The IDs of the blocks associated with the identity.", deprecated=True)
properties: Optional[List[IdentityProperty]] = Field(None, description="List of properties associated with the identity.")
@@ -76,6 +76,6 @@ class IdentityUpdate(LettaBase):
identifier_key: Optional[str] = Field(None, description="External, user-generated identifier key of the identity.")
name: Optional[str] = Field(None, description="The name of the identity.")
identity_type: Optional[IdentityType] = Field(None, description="The type of the identity.")
agent_ids: Optional[List[str]] = Field(None, description="The agent ids that are associated with the identity.")
block_ids: Optional[List[str]] = Field(None, description="The IDs of the blocks associated with the identity.")
agent_ids: Optional[List[str]] = Field(None, description="The agent ids that are associated with the identity.", deprecated=True)
block_ids: Optional[List[str]] = Field(None, description="The IDs of the blocks associated with the identity.", deprecated=True)
properties: Optional[List[IdentityProperty]] = Field(None, description="List of properties associated with the identity.")

View File

@@ -1005,6 +1005,82 @@ async def detach_block_from_agent(
return await server.agent_manager.detach_block_async(agent_id=agent_id, block_id=block_id, actor=actor)
@router.patch("/{agent_id}/archives/attach/{archive_id}", response_model=AgentState, operation_id="attach_archive_to_agent")
async def attach_archive_to_agent(
archive_id: str,
agent_id: AgentId,
server: "SyncServer" = Depends(get_letta_server),
headers: HeaderParams = Depends(get_headers),
):
"""
Attach an archive to an agent.
"""
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
await server.archive_manager.attach_agent_to_archive_async(
agent_id=agent_id,
archive_id=archive_id,
actor=actor,
)
return await server.agent_manager.get_agent_by_id_async(agent_id=agent_id, actor=actor)
@router.patch("/{agent_id}/archives/detach/{archive_id}", response_model=AgentState, operation_id="detach_archive_from_agent")
async def detach_archive_from_agent(
archive_id: str,
agent_id: AgentId,
server: "SyncServer" = Depends(get_letta_server),
headers: HeaderParams = Depends(get_headers),
):
"""
Detach an archive from an agent.
"""
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
await server.archive_manager.detach_agent_from_archive_async(
agent_id=agent_id,
archive_id=archive_id,
actor=actor,
)
return await server.agent_manager.get_agent_by_id_async(agent_id=agent_id, actor=actor)
@router.patch("/{agent_id}/identities/attach/{identity_id}", response_model=AgentState, operation_id="attach_identity_to_agent")
async def attach_identity_to_agent(
identity_id: str,
agent_id: AgentId,
server: "SyncServer" = Depends(get_letta_server),
headers: HeaderParams = Depends(get_headers),
):
"""
Attach an identity to an agent.
"""
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
await server.identity_manager.attach_agent_async(
identity_id=identity_id,
agent_id=agent_id,
actor=actor,
)
return await server.agent_manager.get_agent_by_id_async(agent_id=agent_id, actor=actor)
@router.patch("/{agent_id}/identities/detach/{identity_id}", response_model=AgentState, operation_id="detach_identity_from_agent")
async def detach_identity_from_agent(
identity_id: str,
agent_id: AgentId,
server: "SyncServer" = Depends(get_letta_server),
headers: HeaderParams = Depends(get_headers),
):
"""
Detach an identity from an agent.
"""
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
await server.identity_manager.detach_agent_async(
identity_id=identity_id,
agent_id=agent_id,
actor=actor,
)
return await server.agent_manager.get_agent_by_id_async(agent_id=agent_id, actor=actor)
@router.get("/{agent_id}/archival-memory", response_model=list[Passage], operation_id="list_passages", deprecated=True)
async def list_passages(
agent_id: AgentId,

View File

@@ -136,50 +136,6 @@ async def delete_archive(
)
@router.patch("/{archive_id}/attach/{agent_id}", response_model=PydanticArchive, operation_id="attach_agent_to_archive")
async def attach_agent_to_archive(
archive_id: ArchiveId,
agent_id: AgentId,
server: "SyncServer" = Depends(get_letta_server),
headers: HeaderParams = Depends(get_headers),
):
"""
Attach an agent to an archive.
"""
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
await server.archive_manager.attach_agent_to_archive_async(
agent_id=agent_id,
archive_id=archive_id,
actor=actor,
)
return await server.archive_manager.get_archive_by_id_async(
archive_id=archive_id,
actor=actor,
)
@router.patch("/{archive_id}/detach/{agent_id}", response_model=PydanticArchive, operation_id="detach_agent_from_archive")
async def detach_agent_from_archive(
archive_id: ArchiveId,
agent_id: AgentId,
server: "SyncServer" = Depends(get_letta_server),
headers: HeaderParams = Depends(get_headers),
):
"""
Detach an agent from an archive.
"""
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
await server.archive_manager.detach_agent_from_archive_async(
agent_id=agent_id,
archive_id=archive_id,
actor=actor,
)
return await server.archive_manager.get_archive_by_id_async(
archive_id=archive_id,
actor=actor,
)
@router.get("/{archive_id}/agents", response_model=List[AgentState], operation_id="list_agents_for_archive")
async def list_agents_for_archive(
archive_id: ArchiveId,

View File

@@ -212,3 +212,41 @@ async def list_agents_for_block(
actor=actor,
)
return agents
@router.patch("/{block_id}/identities/attach/{identity_id}", response_model=Block, operation_id="attach_identity_to_block")
async def attach_identity_to_block(
identity_id: str,
block_id: BlockId,
server: SyncServer = Depends(get_letta_server),
headers: HeaderParams = Depends(get_headers),
):
"""
Attach an identity to a block.
"""
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
await server.identity_manager.attach_block_async(
identity_id=identity_id,
block_id=block_id,
actor=actor,
)
return await server.block_manager.get_block_by_id_async(block_id=block_id, actor=actor)
@router.patch("/{block_id}/identities/detach/{identity_id}", response_model=Block, operation_id="detach_identity_from_block")
async def detach_identity_from_block(
identity_id: str,
block_id: BlockId,
server: SyncServer = Depends(get_letta_server),
headers: HeaderParams = Depends(get_headers),
):
"""
Detach an identity from a block.
"""
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
await server.identity_manager.detach_block_async(
identity_id=identity_id,
block_id=block_id,
actor=actor,
)
return await server.block_manager.get_block_by_id_async(block_id=block_id, actor=actor)

View File

@@ -348,3 +348,75 @@ class IdentityManager:
identity_id=identity.id,
)
return [block.to_pydantic() for block in blocks]
@enforce_types
@trace_method
@raise_on_invalid_id(param_name="identity_id", expected_prefix=PrimitiveType.IDENTITY)
@raise_on_invalid_id(param_name="agent_id", expected_prefix=PrimitiveType.AGENT)
async def attach_agent_async(self, identity_id: str, agent_id: str, actor: PydanticUser) -> None:
"""
Attach an agent to an identity.
"""
async with db_registry.async_session() as session:
identity = await IdentityModel.read_async(db_session=session, identifier=identity_id, actor=actor)
agent = await AgentModel.read_async(db_session=session, identifier=agent_id, actor=actor)
# Add agent to identity if not already attached
if agent not in identity.agents:
identity.agents.append(agent)
await identity.update_async(db_session=session, actor=actor)
@enforce_types
@trace_method
@raise_on_invalid_id(param_name="identity_id", expected_prefix=PrimitiveType.IDENTITY)
@raise_on_invalid_id(param_name="agent_id", expected_prefix=PrimitiveType.AGENT)
async def detach_agent_async(self, identity_id: str, agent_id: str, actor: PydanticUser) -> None:
"""
Detach an agent from an identity.
"""
async with db_registry.async_session() as session:
identity = await IdentityModel.read_async(db_session=session, identifier=identity_id, actor=actor)
agent = await AgentModel.read_async(db_session=session, identifier=agent_id, actor=actor)
# Remove agent from identity if attached
if agent in identity.agents:
identity.agents.remove(agent)
await identity.update_async(db_session=session, actor=actor)
@enforce_types
@trace_method
@raise_on_invalid_id(param_name="identity_id", expected_prefix=PrimitiveType.IDENTITY)
@raise_on_invalid_id(param_name="block_id", expected_prefix=PrimitiveType.BLOCK)
async def attach_block_async(self, identity_id: str, block_id: str, actor: PydanticUser) -> None:
"""
Attach a block to an identity.
"""
async with db_registry.async_session() as session:
identity = await IdentityModel.read_async(db_session=session, identifier=identity_id, actor=actor)
block = await BlockModel.read_async(db_session=session, identifier=block_id, actor=actor)
# Add block to identity if not already attached
if block not in identity.blocks:
identity.blocks.append(block)
await identity.update_async(db_session=session, actor=actor)
@enforce_types
@trace_method
@raise_on_invalid_id(param_name="identity_id", expected_prefix=PrimitiveType.IDENTITY)
@raise_on_invalid_id(param_name="block_id", expected_prefix=PrimitiveType.BLOCK)
async def detach_block_async(self, identity_id: str, block_id: str, actor: PydanticUser) -> None:
"""
Detach a block from an identity.
"""
async with db_registry.async_session() as session:
identity = await IdentityModel.read_async(db_session=session, identifier=identity_id, actor=actor)
block = await BlockModel.read_async(db_session=session, identifier=block_id, actor=actor)
# Remove block from identity if attached
if block in identity.blocks:
identity.blocks.remove(block)
await identity.update_async(db_session=session, actor=actor)