Phase 3 of the AI-workforce engine (TODO-agent-2): - engine/manager.py: ManagerTurns.chat (CREATE_TASK/PAUSE/RESUME/CANCEL, HIRE/UPDATE_EMPLOYEE/FIRE, KNOWLEDGE), triage (ASSIGN/REJECT/ASK_USER), final_review (COMPLETE/REVISE +cycle), error_review (RESOLVE_ERROR/CANCEL), bootstrap_team; post_user_message now delegates to it. - One billing path: call_model/BillingScope in runner.py is the single model entry point (fixes the agybot chat-billing bug). - Team changes capped at 3/turn, employee cap, last manager cannot be fired, hires pick a granted model of the requested tier strictly. - container_engine.py wires the real WorkforceBootstrapPort. - Fixes: REVISE is a body tag (prompt and parser now agree); ASSIGN employee="" means pick-for-me; the runner's protocol-error retry is capped by engine_max_protocol_retries (was unbounded until the turn timeout). - 20 new manager tests + regression tests; report in docs/plan/reports/agent-2-phase-3.md.
74 lines
2.8 KiB
Python
74 lines
2.8 KiB
Python
"""Wiring for the AI-workforce workstream (team bootstrap).
|
|
|
|
The org wizard calls ``build_workforce_bootstrap(session)`` after it commits a
|
|
new organization, then ``bootstrap_team`` creates the starting team. The team
|
|
is built on the *caller's* session, so the employees and the "waiting for a
|
|
model" note land in the same transaction as the organization.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.application.ports.workforce_bootstrap import WorkforceBootstrapPort
|
|
|
|
|
|
class _EngineWorkforceBootstrap:
|
|
"""The real bootstrap: one manager, planner, developer and tester.
|
|
|
|
Everything is built here, lazily, from the session: ``container.py`` is a
|
|
frozen composition root and the engine must not widen it, so the repos and
|
|
ports the manager needs are assembled where they are used.
|
|
"""
|
|
|
|
def __init__(self, session: AsyncSession) -> None:
|
|
self.session = session
|
|
|
|
async def bootstrap_team(self, *, organization_id: str, project_id: str) -> None:
|
|
from app.application.container import get_model_provider
|
|
from app.application.container_org import build_memory, build_model_grants
|
|
from app.application.engine.manager import ManagerTurns
|
|
from app.infrastructure.repositories.ai_control import (
|
|
AiModelRepository,
|
|
ModelCredentialRepository,
|
|
RateCardRepository,
|
|
)
|
|
from app.infrastructure.repositories.audit import UsageLedgerRepository
|
|
from app.infrastructure.repositories.engine import (
|
|
EmployeeProfileRepository,
|
|
PipelineStateRepository,
|
|
)
|
|
from app.infrastructure.repositories.workforce import (
|
|
EmployeeRepository,
|
|
MessageRepository,
|
|
TaskRepository,
|
|
ThreadRepository,
|
|
)
|
|
|
|
session = self.session
|
|
manager = ManagerTurns(
|
|
provider=get_model_provider(),
|
|
employees=EmployeeRepository(session),
|
|
messages=MessageRepository(session),
|
|
tasks=TaskRepository(session),
|
|
threads=ThreadRepository(session),
|
|
models=AiModelRepository(session),
|
|
credentials=ModelCredentialRepository(session),
|
|
rate_cards=RateCardRepository(session),
|
|
ledger=UsageLedgerRepository(session),
|
|
grants=build_model_grants(session),
|
|
memory=build_memory(session),
|
|
profiles=EmployeeProfileRepository(session),
|
|
states=PipelineStateRepository(session),
|
|
)
|
|
await manager.bootstrap_team(
|
|
organization_id=organization_id, project_id=project_id
|
|
)
|
|
|
|
|
|
def build_workforce_bootstrap(session: AsyncSession) -> WorkforceBootstrapPort:
|
|
return _EngineWorkforceBootstrap(session)
|
|
|
|
|
|
__all__ = ["build_workforce_bootstrap"]
|