forkflux-api 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. forkflux_api-0.1.0/.gitignore +35 -0
  2. forkflux_api-0.1.0/PKG-INFO +64 -0
  3. forkflux_api-0.1.0/README.md +33 -0
  4. forkflux_api-0.1.0/forkflux_api/__init__.py +0 -0
  5. forkflux_api-0.1.0/forkflux_api/agents/dependencies.py +21 -0
  6. forkflux_api-0.1.0/forkflux_api/agents/dto.py +19 -0
  7. forkflux_api-0.1.0/forkflux_api/agents/exceptions.py +28 -0
  8. forkflux_api-0.1.0/forkflux_api/agents/handlers.py +19 -0
  9. forkflux_api-0.1.0/forkflux_api/agents/models.py +38 -0
  10. forkflux_api-0.1.0/forkflux_api/agents/respositories.py +150 -0
  11. forkflux_api-0.1.0/forkflux_api/agents/schemas.py +17 -0
  12. forkflux_api-0.1.0/forkflux_api/agents/services.py +117 -0
  13. forkflux_api-0.1.0/forkflux_api/cli.py +208 -0
  14. forkflux_api-0.1.0/forkflux_api/config.py +67 -0
  15. forkflux_api-0.1.0/forkflux_api/database.py +70 -0
  16. forkflux_api-0.1.0/forkflux_api/dependencies.py +87 -0
  17. forkflux_api-0.1.0/forkflux_api/exceptions.py +11 -0
  18. forkflux_api-0.1.0/forkflux_api/jobs/api_exceptions.py +26 -0
  19. forkflux_api-0.1.0/forkflux_api/jobs/constants.py +28 -0
  20. forkflux_api-0.1.0/forkflux_api/jobs/dependencies.py +85 -0
  21. forkflux_api-0.1.0/forkflux_api/jobs/dto.py +56 -0
  22. forkflux_api-0.1.0/forkflux_api/jobs/exceptions.py +18 -0
  23. forkflux_api-0.1.0/forkflux_api/jobs/handlers.py +117 -0
  24. forkflux_api-0.1.0/forkflux_api/jobs/helpers.py +40 -0
  25. forkflux_api-0.1.0/forkflux_api/jobs/models.py +109 -0
  26. forkflux_api-0.1.0/forkflux_api/jobs/repositories.py +234 -0
  27. forkflux_api-0.1.0/forkflux_api/jobs/schemas.py +80 -0
  28. forkflux_api-0.1.0/forkflux_api/jobs/services.py +222 -0
  29. forkflux_api-0.1.0/forkflux_api/main.py +43 -0
  30. forkflux_api-0.1.0/forkflux_api/migrations/env.py +102 -0
  31. forkflux_api-0.1.0/forkflux_api/migrations/script.py.mako +28 -0
  32. forkflux_api-0.1.0/forkflux_api/migrations/versions/2026_06_05_2100-7421e85348dc_.py +77 -0
  33. forkflux_api-0.1.0/forkflux_api/migrations/versions/2026_06_07_1708-ef0279dd14c3_.py +182 -0
  34. forkflux_api-0.1.0/pyproject.toml +78 -0
@@ -0,0 +1,35 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+
12
+ .idea
13
+ .mypy_cache
14
+ .ruff_cache
15
+ .pytest_cache
16
+ .env
17
+ local.yml
18
+ docker_data
19
+
20
+ AGENTS.md
21
+ .aiginore
22
+ .agents
23
+ .clinerules
24
+ .clineignore
25
+ .roorules
26
+ .roo
27
+ .rooignore
28
+ .zoorules
29
+ .zoo
30
+ .zooignore
31
+ .cursor
32
+ .cursorignore
33
+ .claude
34
+ .kilocodeignore
35
+ .kilo
@@ -0,0 +1,64 @@
1
+ Metadata-Version: 2.4
2
+ Name: forkflux-api
3
+ Version: 0.1.0
4
+ Summary: Core API server and coordination bus for cross-device AI agent task handoff.
5
+ Project-URL: Homepage, https://github.com/forkflux/forkflux
6
+ Project-URL: Repository, https://github.com/forkflux/forkflux
7
+ Project-URL: Issues, https://github.com/forkflux/forkflux/issues
8
+ Keywords: agents,ai,coordination,developer-tools,fastapi,handoff,llm,multi-agent,task-delegation
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: Apache Software License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.14
14
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Classifier: Topic :: System :: Distributed Computing
17
+ Requires-Python: >=3.14
18
+ Requires-Dist: aiosqlite==0.22.1
19
+ Requires-Dist: alembic-postgresql-enum==1.10.0
20
+ Requires-Dist: alembic==1.18.4
21
+ Requires-Dist: asyncpg==0.31.0
22
+ Requires-Dist: fastapi==0.137.2
23
+ Requires-Dist: gunicorn==26.0.0
24
+ Requires-Dist: pydantic-settings==2.14.1
25
+ Requires-Dist: pydantic==2.13.4
26
+ Requires-Dist: sqlalchemy==2.0.51
27
+ Requires-Dist: structlog==26.1.0
28
+ Requires-Dist: typer==0.26.7
29
+ Requires-Dist: uvicorn==0.49.0
30
+ Description-Content-Type: text/markdown
31
+
32
+ # ForkFlux API
33
+
34
+ > Core API server and coordination bus for cross-device AI agent task handoff.
35
+
36
+ ForkFlux API is the stateful coordination layer behind ForkFlux. It gives isolated AI agents a shared, machine-readable job pool for publishing work, atomically claiming tasks, transferring context and artifacts, and closing jobs with explicit lifecycle states.
37
+
38
+ Use this package when you need the ForkFlux coordination bus service itself: a FastAPI application backed by PostgreSQL or SQLite, plus a small CLI for registering target roles and agent API tokens.
39
+
40
+ ## What it provides
41
+
42
+ - **Shared handoff queue** for agent-to-agent job delegation.
43
+ - **Atomic claims** so only one agent can own a published job.
44
+ - **Structured context transfer** through job constraints, payloads, and artifacts.
45
+ - **Lifecycle control** for `published` → `in_progress` → `completed` / `failed` / `cancelled`.
46
+ - **Agent identity and role registry** for role-aware routing.
47
+
48
+ ## Package
49
+
50
+ ```bash
51
+ pip install forkflux-api
52
+ ```
53
+
54
+ The installed CLI entry point is:
55
+
56
+ ```bash
57
+ forkflux --help
58
+ ```
59
+
60
+ ## Runtime requirements
61
+
62
+ - Python 3.14+
63
+
64
+ See the main ForkFlux repository for local Docker setup, MCP integration, and end-to-end handoff examples.
@@ -0,0 +1,33 @@
1
+ # ForkFlux API
2
+
3
+ > Core API server and coordination bus for cross-device AI agent task handoff.
4
+
5
+ ForkFlux API is the stateful coordination layer behind ForkFlux. It gives isolated AI agents a shared, machine-readable job pool for publishing work, atomically claiming tasks, transferring context and artifacts, and closing jobs with explicit lifecycle states.
6
+
7
+ Use this package when you need the ForkFlux coordination bus service itself: a FastAPI application backed by PostgreSQL or SQLite, plus a small CLI for registering target roles and agent API tokens.
8
+
9
+ ## What it provides
10
+
11
+ - **Shared handoff queue** for agent-to-agent job delegation.
12
+ - **Atomic claims** so only one agent can own a published job.
13
+ - **Structured context transfer** through job constraints, payloads, and artifacts.
14
+ - **Lifecycle control** for `published` → `in_progress` → `completed` / `failed` / `cancelled`.
15
+ - **Agent identity and role registry** for role-aware routing.
16
+
17
+ ## Package
18
+
19
+ ```bash
20
+ pip install forkflux-api
21
+ ```
22
+
23
+ The installed CLI entry point is:
24
+
25
+ ```bash
26
+ forkflux --help
27
+ ```
28
+
29
+ ## Runtime requirements
30
+
31
+ - Python 3.14+
32
+
33
+ See the main ForkFlux repository for local Docker setup, MCP integration, and end-to-end handoff examples.
File without changes
@@ -0,0 +1,21 @@
1
+ from fastapi import Depends, Request
2
+ from forkflux_api.agents.respositories import TargetRoleRepository
3
+ from forkflux_api.agents.services import TargetRoleService
4
+ from forkflux_api.database import get_session
5
+ from sqlalchemy.ext.asyncio import AsyncSession
6
+
7
+
8
+ def get_trace_id(request: Request) -> str:
9
+ return request.state.trace_id
10
+
11
+
12
+ def get_target_role_repo(
13
+ session: AsyncSession = Depends(get_session), trace_id: str = Depends(get_trace_id)
14
+ ) -> TargetRoleRepository:
15
+ return TargetRoleRepository(session=session, trace_id=trace_id)
16
+
17
+
18
+ def get_target_role_service(
19
+ repository: TargetRoleRepository = Depends(get_target_role_repo), trace_id: str = Depends(get_trace_id)
20
+ ) -> TargetRoleService:
21
+ return TargetRoleService(target_role_repo=repository, trace_id=trace_id)
@@ -0,0 +1,19 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass(slots=True)
5
+ class TargetRoleCreate:
6
+ role_key: str
7
+ role_label: str
8
+
9
+
10
+ @dataclass(slots=True)
11
+ class AgentIdentityCreate:
12
+ agent_label: str
13
+ role_id: int
14
+ tool_family: str | None
15
+
16
+
17
+ @dataclass(slots=True)
18
+ class AgentApiTokenCreate:
19
+ agent_id: int
@@ -0,0 +1,28 @@
1
+ class AgentApiTokenNotFoundError(Exception):
2
+ code = "agent_api_token.not_found"
3
+ msg = "Agent API token not found."
4
+
5
+
6
+ class AgentApiTokenConflictError(Exception):
7
+ code = "agent_api_token.conflict"
8
+ msg = "Agent API token conflicts with existing data constraints."
9
+
10
+
11
+ class AgentIdentityNotFoundError(Exception):
12
+ code = "agent_identity.not_found"
13
+ msg = "Agent identity not found."
14
+
15
+
16
+ class AgentIdentityConflictError(Exception):
17
+ code = "agent_identity.conflict"
18
+ msg = "Agent identity conflicts with existing data constraints."
19
+
20
+
21
+ class TargetRoleConflictError(Exception):
22
+ code = "target_role.conflict"
23
+ msg = "Target role already exists."
24
+
25
+
26
+ class TargetRoleNotFoundError(Exception):
27
+ code = "target_role.not_found"
28
+ msg = "Target role not found."
@@ -0,0 +1,19 @@
1
+ from fastapi import APIRouter, Depends
2
+ from forkflux_api.agents.dependencies import get_target_role_service
3
+ from forkflux_api.agents.models import AgentIdentity
4
+ from forkflux_api.agents.schemas import GetMeResponse, ListRolesResponse
5
+ from forkflux_api.agents.services import TargetRoleService
6
+ from forkflux_api.dependencies import get_current_agent, verify_token
7
+
8
+ router = APIRouter(prefix="/agents", tags=["agents"], dependencies=[Depends(verify_token)])
9
+
10
+
11
+ @router.get("/roles", response_model=list[ListRolesResponse])
12
+ async def list_roles(service: TargetRoleService = Depends(get_target_role_service)):
13
+ roles = await service.get_all_roles()
14
+ return roles
15
+
16
+
17
+ @router.get("/me", response_model=GetMeResponse)
18
+ async def get_me(current_agent: AgentIdentity = Depends(get_current_agent)):
19
+ return current_agent
@@ -0,0 +1,38 @@
1
+ from datetime import datetime
2
+
3
+ from forkflux_api.database import Base, UTCDateTime
4
+ from sqlalchemy import BigInteger, Boolean, ForeignKey, Integer, Text
5
+ from sqlalchemy.orm import Mapped, mapped_column
6
+
7
+ PK_TYPE = BigInteger().with_variant(Integer, "sqlite")
8
+
9
+
10
+ class TargetRole(Base):
11
+ __tablename__ = "target_role"
12
+
13
+ id: Mapped[int] = mapped_column(PK_TYPE, primary_key=True, autoincrement=True)
14
+ role_key: Mapped[str] = mapped_column(Text, nullable=False, unique=True)
15
+ role_label: Mapped[str] = mapped_column(Text, nullable=False)
16
+ created_at: Mapped[datetime] = mapped_column(UTCDateTime(), nullable=False)
17
+
18
+
19
+ class AgentIdentity(Base):
20
+ __tablename__ = "agent_identity"
21
+
22
+ id: Mapped[int] = mapped_column(PK_TYPE, primary_key=True, autoincrement=True)
23
+ agent_label: Mapped[str] = mapped_column(Text, nullable=False)
24
+ role_id: Mapped[int] = mapped_column(ForeignKey("target_role.id"), nullable=False)
25
+ tool_family: Mapped[str | None] = mapped_column(Text, nullable=True)
26
+ created_at: Mapped[datetime] = mapped_column(UTCDateTime(), nullable=False)
27
+
28
+
29
+ class AgentApiToken(Base):
30
+ __tablename__ = "agent_api_token"
31
+
32
+ id: Mapped[int] = mapped_column(PK_TYPE, primary_key=True, autoincrement=True)
33
+ token_hash: Mapped[str] = mapped_column(Text, nullable=False, unique=True)
34
+ agent_id: Mapped[int] = mapped_column(ForeignKey("agent_identity.id"), nullable=False)
35
+ is_active: Mapped[bool] = mapped_column(Boolean, nullable=False)
36
+ created_at: Mapped[datetime] = mapped_column(UTCDateTime(), nullable=False)
37
+ last_used_at: Mapped[datetime | None] = mapped_column(UTCDateTime(), nullable=True)
38
+ revoked_at: Mapped[datetime | None] = mapped_column(UTCDateTime(), nullable=True)
@@ -0,0 +1,150 @@
1
+ from datetime import datetime, timezone
2
+
3
+ import structlog
4
+ from forkflux_api.agents.dto import AgentApiTokenCreate, AgentIdentityCreate, TargetRoleCreate
5
+ from forkflux_api.agents.exceptions import (
6
+ AgentApiTokenConflictError,
7
+ AgentApiTokenNotFoundError,
8
+ AgentIdentityConflictError,
9
+ AgentIdentityNotFoundError,
10
+ TargetRoleConflictError,
11
+ TargetRoleNotFoundError,
12
+ )
13
+ from forkflux_api.agents.models import AgentApiToken, AgentIdentity, TargetRole
14
+ from sqlalchemy import exists, select, update
15
+ from sqlalchemy.exc import IntegrityError
16
+ from sqlalchemy.ext.asyncio import AsyncSession
17
+
18
+
19
+ class TargetRoleRepository:
20
+ def __init__(self, session: AsyncSession, trace_id: str) -> None:
21
+ self._session = session
22
+ self._logger = structlog.get_logger().bind(cls=self.__class__.__name__, trace_id=trace_id)
23
+
24
+ async def list(self) -> list[TargetRole]:
25
+ result = await self._session.execute(select(TargetRole))
26
+ return list(result.scalars().all())
27
+
28
+ async def get_by_role_key(self, role_key: str) -> TargetRole:
29
+ result = await self._session.execute(select(TargetRole).where(TargetRole.role_key == role_key))
30
+ target_role = result.scalar_one_or_none()
31
+ if target_role is None:
32
+ raise TargetRoleNotFoundError
33
+
34
+ return target_role
35
+
36
+ async def exists(self, role_key: str) -> bool:
37
+ log = self._logger.bind(method="exists", role_key=role_key)
38
+ result = await self._session.execute(select(exists().where(TargetRole.role_key == role_key)))
39
+ role_exists = result.scalar_one()
40
+
41
+ if role_exists:
42
+ log.info("target_role_exists_hit")
43
+ else:
44
+ log.info("target_role_exists_miss")
45
+
46
+ return role_exists
47
+
48
+ async def create(self, dto: TargetRoleCreate) -> TargetRole:
49
+ target_role = TargetRole(
50
+ role_key=dto.role_key,
51
+ role_label=dto.role_label,
52
+ created_at=datetime.now(timezone.utc),
53
+ )
54
+
55
+ self._session.add(target_role)
56
+ try:
57
+ await self._session.flush()
58
+ except IntegrityError as err:
59
+ await self._session.rollback()
60
+ raise TargetRoleConflictError from err
61
+
62
+ return target_role
63
+
64
+
65
+ class AgentApiTokenRepository:
66
+ def __init__(self, session: AsyncSession, trace_id: str) -> None:
67
+ self._session = session
68
+ self._logger = structlog.get_logger().bind(cls=self.__class__.__name__, trace_id=trace_id)
69
+
70
+ async def get(self, token_hash: str) -> AgentApiToken:
71
+ result = await self._session.execute(
72
+ select(AgentApiToken).where(
73
+ AgentApiToken.token_hash == token_hash,
74
+ AgentApiToken.is_active.is_(True),
75
+ )
76
+ )
77
+ token = result.scalar_one_or_none()
78
+ if token is None:
79
+ raise AgentApiTokenNotFoundError
80
+
81
+ return token
82
+
83
+ async def create(self, dto: AgentApiTokenCreate, token_hash: str) -> AgentApiToken:
84
+ agent_api_token = AgentApiToken(
85
+ token_hash=token_hash,
86
+ agent_id=dto.agent_id,
87
+ is_active=True,
88
+ created_at=datetime.now(timezone.utc),
89
+ )
90
+
91
+ self._session.add(agent_api_token)
92
+ try:
93
+ await self._session.flush()
94
+ except IntegrityError as err:
95
+ await self._session.rollback()
96
+ raise AgentApiTokenConflictError from err
97
+
98
+ return agent_api_token
99
+
100
+ async def revoke(self, agent_id: int) -> int:
101
+ revoked_at = datetime.now(timezone.utc)
102
+ result = await self._session.execute(
103
+ update(AgentApiToken)
104
+ .where(
105
+ AgentApiToken.agent_id == agent_id,
106
+ AgentApiToken.is_active.is_(True),
107
+ )
108
+ .values(
109
+ is_active=False,
110
+ revoked_at=revoked_at,
111
+ )
112
+ )
113
+ await self._session.flush()
114
+
115
+ return result.rowcount or 0 # type: ignore[attr-defined]
116
+
117
+
118
+ class AgentIdentityRepository:
119
+ def __init__(self, session: AsyncSession, trace_id: str) -> None:
120
+ self._session = session
121
+ self._logger = structlog.get_logger().bind(cls=self.__class__.__name__, trace_id=trace_id)
122
+
123
+ async def list(self) -> list[AgentIdentity]:
124
+ result = await self._session.execute(select(AgentIdentity))
125
+ return list(result.scalars().all())
126
+
127
+ async def get_by_id(self, agent_identity_id: int) -> AgentIdentity:
128
+ result = await self._session.execute(select(AgentIdentity).where(AgentIdentity.id == agent_identity_id))
129
+ agent_identity = result.scalar_one_or_none()
130
+ if agent_identity is None:
131
+ raise AgentIdentityNotFoundError
132
+
133
+ return agent_identity
134
+
135
+ async def create(self, dto: AgentIdentityCreate) -> AgentIdentity:
136
+ agent_identity = AgentIdentity(
137
+ agent_label=dto.agent_label,
138
+ role_id=dto.role_id,
139
+ tool_family=dto.tool_family,
140
+ created_at=datetime.now(timezone.utc),
141
+ )
142
+
143
+ self._session.add(agent_identity)
144
+ try:
145
+ await self._session.flush()
146
+ except IntegrityError as err:
147
+ await self._session.rollback()
148
+ raise AgentIdentityConflictError from err
149
+
150
+ return agent_identity
@@ -0,0 +1,17 @@
1
+ from pydantic import BaseModel, ConfigDict
2
+
3
+
4
+ class ListRolesResponse(BaseModel):
5
+ model_config = ConfigDict(from_attributes=True)
6
+
7
+ role_key: str
8
+ role_label: str
9
+
10
+
11
+ class GetMeResponse(BaseModel):
12
+ model_config = ConfigDict(from_attributes=True)
13
+
14
+ id: int
15
+ agent_label: str
16
+ role_id: int
17
+ tool_family: str | None
@@ -0,0 +1,117 @@
1
+ import hashlib
2
+ import secrets
3
+
4
+ import structlog
5
+ from forkflux_api.agents.dto import AgentApiTokenCreate, AgentIdentityCreate, TargetRoleCreate
6
+ from forkflux_api.agents.models import AgentApiToken, AgentIdentity, TargetRole
7
+ from forkflux_api.agents.respositories import AgentApiTokenRepository, AgentIdentityRepository, TargetRoleRepository
8
+
9
+
10
+ class TargetRoleService:
11
+ def __init__(self, target_role_repo: TargetRoleRepository, trace_id: str) -> None:
12
+ self._logger = structlog.get_logger().bind(cls=self.__class__.__name__, trace_id=trace_id)
13
+ self._target_role_repo = target_role_repo
14
+
15
+ async def get_all_roles(self) -> list[TargetRole]:
16
+ log = self._logger.bind(method="get_all_roles")
17
+ log.info("operation_started")
18
+
19
+ roles = await self._target_role_repo.list()
20
+
21
+ log.info("operation_completed", roles_count=len(roles))
22
+ return roles
23
+
24
+ async def get_by_role_key(self, role_key: str) -> TargetRole:
25
+ log = self._logger.bind(method="get_by_role_key", role_key=role_key)
26
+ log.info("operation_started")
27
+
28
+ role = await self._target_role_repo.get_by_role_key(role_key)
29
+
30
+ log.info("operation_completed")
31
+ return role
32
+
33
+ async def is_role_exists(self, role_key: str) -> bool:
34
+ log = self._logger.bind(method="is_role_exists", role_key=role_key)
35
+ log.info("operation_started")
36
+
37
+ exists = await self._target_role_repo.exists(role_key)
38
+
39
+ log.info("operation_completed", role_exists=exists)
40
+ return exists
41
+
42
+ async def create_role(self, dto: TargetRoleCreate) -> TargetRole:
43
+ log = self._logger.bind(method="create_role", role_key=dto.role_key)
44
+ log.info("operation_started")
45
+
46
+ role = await self._target_role_repo.create(dto)
47
+
48
+ log.info("operation_completed")
49
+ return role
50
+
51
+
52
+ class AgentApiTokenService:
53
+ def __init__(self, agent_api_token_repo: AgentApiTokenRepository, trace_id: str) -> None:
54
+ self._logger = structlog.get_logger().bind(cls=self.__class__.__name__, trace_id=trace_id)
55
+ self._agent_api_token_repo = agent_api_token_repo
56
+
57
+ async def get_token(self, token_hash: str) -> AgentApiToken:
58
+ log = self._logger.bind(method="get_token", token_hash=token_hash)
59
+ log.info("operation_started")
60
+
61
+ token = await self._agent_api_token_repo.get(token_hash)
62
+
63
+ log.info("operation_completed", token_id=token.id, agent_id=token.agent_id)
64
+ return token
65
+
66
+ async def create_token(self, dto: AgentApiTokenCreate) -> str:
67
+ log = self._logger.bind(method="create_token", agent_id=dto.agent_id)
68
+ log.info("operation_started")
69
+
70
+ raw_token = secrets.token_urlsafe(32)
71
+ token_hash = hashlib.sha256(raw_token.encode()).hexdigest()
72
+ await self._agent_api_token_repo.create(dto=dto, token_hash=token_hash)
73
+
74
+ log.info("operation_completed")
75
+ return raw_token
76
+
77
+ async def revoke_token(self, agent_id: int) -> int:
78
+ log = self._logger.bind(method="revoke_token", agent_id=agent_id)
79
+ log.info("operation_started")
80
+
81
+ revoked_count = await self._agent_api_token_repo.revoke(agent_id)
82
+
83
+ log.info("operation_completed", revoked_count=revoked_count)
84
+ return revoked_count
85
+
86
+
87
+ class AgentIdentityService:
88
+ def __init__(self, agent_identity_repo: AgentIdentityRepository, trace_id: str) -> None:
89
+ self._logger = structlog.get_logger().bind(cls=self.__class__.__name__, trace_id=trace_id)
90
+ self._agent_identity_repo = agent_identity_repo
91
+
92
+ async def get_all_agents(self) -> list[AgentIdentity]:
93
+ log = self._logger.bind(method="get_all_agents")
94
+ log.info("operation_started")
95
+
96
+ agents = await self._agent_identity_repo.list()
97
+
98
+ log.info("operation_completed", agents_count=len(agents))
99
+ return agents
100
+
101
+ async def get_by_id(self, agent_identity_id: int) -> AgentIdentity:
102
+ log = self._logger.bind(method="get_by_id", agent_identity_id=agent_identity_id)
103
+ log.info("operation_started")
104
+
105
+ agent = await self._agent_identity_repo.get_by_id(agent_identity_id)
106
+
107
+ log.info("operation_completed")
108
+ return agent
109
+
110
+ async def create_agent(self, dto: AgentIdentityCreate) -> AgentIdentity:
111
+ log = self._logger.bind(method="create_agent", agent_label=dto.agent_label, role_id=dto.role_id)
112
+ log.info("operation_started")
113
+
114
+ agent = await self._agent_identity_repo.create(dto)
115
+
116
+ log.info("operation_completed", agent_identity_id=agent.id)
117
+ return agent