--- phase: 05-employee-design plan: 01 type: execute wave: 1 depends_on: [] files_modified: - packages/shared/shared/models/tenant.py - packages/shared/shared/api/templates.py - packages/shared/shared/prompts/system_prompt_builder.py - migrations/versions/007_agent_templates.py - packages/gateway/main.py - tests/unit/test_system_prompt_builder.py - tests/integration/test_templates.py autonomous: true requirements: [EMPL-02, EMPL-03, EMPL-04] must_haves: truths: - "GET /api/portal/templates returns a list of 7+ pre-built agent templates" - "POST /api/portal/templates/{id}/deploy creates an independent agent snapshot with is_active=True" - "Template deploy is blocked for customer_operator (403)" - "System prompt builder produces a coherent prompt including AI transparency clause" artifacts: - path: "packages/shared/shared/models/tenant.py" provides: "AgentTemplate ORM model" contains: "class AgentTemplate" - path: "packages/shared/shared/api/templates.py" provides: "Template list + deploy endpoints" exports: ["templates_router"] - path: "packages/shared/shared/prompts/system_prompt_builder.py" provides: "System prompt auto-generation from wizard inputs" exports: ["build_system_prompt"] - path: "migrations/versions/007_agent_templates.py" provides: "agent_templates table with 7 seed templates" contains: "agent_templates" - path: "tests/unit/test_system_prompt_builder.py" provides: "Unit tests for prompt builder" - path: "tests/integration/test_templates.py" provides: "Integration tests for template API" key_links: - from: "packages/shared/shared/api/templates.py" to: "packages/shared/shared/models/tenant.py" via: "AgentTemplate ORM model import" pattern: "from shared\\.models\\.tenant import.*AgentTemplate" - from: "packages/shared/shared/api/templates.py" to: "packages/shared/shared/models/tenant.py" via: "Agent ORM model for deploy snapshot" pattern: "Agent\\(" - from: "packages/gateway/main.py" to: "packages/shared/shared/api/templates.py" via: "Router mount" pattern: "templates_router" --- Backend foundation for the Employee Design phase: AgentTemplate ORM model, database migration with 7 seed templates, template list + deploy API endpoints, system prompt builder function, and comprehensive tests. Purpose: Provide the API layer that the frontend template gallery and wizard will consume. Templates are global (not tenant-scoped), readable by all authenticated users, deployable by tenant admins only. Output: Working API at /api/portal/templates (GET list, GET detail, POST deploy), system prompt builder module, migration 007, unit + integration tests. @/home/adelorenzo/.claude/get-shit-done/workflows/execute-plan.md @/home/adelorenzo/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/05-employee-design/05-CONTEXT.md @.planning/phases/05-employee-design/05-RESEARCH.md @.planning/phases/05-employee-design/05-VALIDATION.md From packages/shared/shared/models/tenant.py: ```python class Base(DeclarativeBase): pass class Agent(Base): __tablename__ = "agents" id: Mapped[uuid.UUID] tenant_id: Mapped[uuid.UUID] # FK to tenants.id name: Mapped[str] role: Mapped[str] persona: Mapped[str] system_prompt: Mapped[str] model_preference: Mapped[str] # quality | balanced | economy | local tool_assignments: Mapped[list[Any]] # JSON escalation_rules: Mapped[list[Any]] # JSON escalation_assignee: Mapped[str | None] natural_language_escalation: Mapped[bool] is_active: Mapped[bool] # default True budget_limit_usd: Mapped[float | None] created_at: Mapped[datetime] updated_at: Mapped[datetime] ``` From packages/shared/shared/api/portal.py: ```python portal_router = APIRouter(prefix="/api/portal", tags=["portal"]) class AgentCreate(BaseModel): name: str role: str persona: str = "" system_prompt: str = "" model_preference: str = "quality" tool_assignments: list[str] = [] escalation_rules: list[dict] = [] is_active: bool = True class AgentResponse(BaseModel): id: str; tenant_id: str; name: str; role: str; persona: str system_prompt: str; model_preference: str; tool_assignments: list[str] escalation_rules: list[dict]; is_active: bool; budget_limit_usd: float | None created_at: datetime; updated_at: datetime ``` From packages/shared/shared/api/rbac.py: ```python async def require_platform_admin(caller: PortalCaller) -> PortalCaller async def require_tenant_admin(caller: PortalCaller) -> PortalCaller async def require_tenant_member(caller: PortalCaller) -> PortalCaller ``` From packages/gateway/main.py (router mounting pattern): ```python from shared.api.portal import portal_router app.include_router(portal_router) # All Phase 3 routers mounted similarly ``` Latest migration: 006_rbac_roles.py (next is 007) Task 1: AgentTemplate model, migration 007, system prompt builder, and tests packages/shared/shared/models/tenant.py, migrations/versions/007_agent_templates.py, packages/shared/shared/prompts/__init__.py, packages/shared/shared/prompts/system_prompt_builder.py, tests/unit/test_system_prompt_builder.py - build_system_prompt({name: "Mara", role: "Customer Support", persona: "Friendly and helpful", tool_assignments: ["knowledge_base_search"], escalation_rules: [{"condition": "billing_dispute AND attempts > 2", "action": "handoff_human"}]}) produces a string containing "You are Mara", "Customer Support", "Friendly and helpful", "knowledge_base_search", the escalation rule text, AND the AI transparency clause "When directly asked if you are an AI, always disclose that you are an AI assistant." - build_system_prompt with empty tools and empty escalation_rules omits those sections (no "tools:" or "Escalation" text) - build_system_prompt with only name and role still produces valid prompt with AI transparency clause 1. Add `AgentTemplate` ORM model to `packages/shared/shared/models/tenant.py`: - NOT tenant-scoped (no tenant_id, no RLS) - Fields: id (UUID PK), name (String 255), role (String 255), description (Text, shown in card preview), category (String 100, default "general"), persona (Text), system_prompt (Text), model_preference (String 50, default "quality"), tool_assignments (JSON, default []), escalation_rules (JSON, default []), is_active (Boolean, default True), sort_order (Integer, default 0), created_at (DateTime tz, server_default now()) - Add `__repr__` method 2. Create migration `007_agent_templates.py`: - down_revision = "006" - Create `agent_templates` table matching the ORM model - Seed 7 templates with INSERT. Each template needs: name, role, description (2-3 sentences for the card preview), category, persona (paragraph describing communication style), system_prompt (full system prompt with AI transparency clause), model_preference "quality", tool_assignments (JSON array of relevant tool names), escalation_rules (JSON array of {condition, action} objects) - Templates: a. Customer Support Rep — category "support", tools: knowledge_base_search, zendesk_ticket_lookup, zendesk_ticket_create. Escalation: billing_dispute AND attempts > 2 -> handoff_human, sentiment < -0.7 -> handoff_human b. Sales Assistant — category "sales", tools: knowledge_base_search, calendar_book. Escalation: pricing_negotiation AND attempts > 3 -> handoff_human c. Office Manager — category "operations", tools: knowledge_base_search, calendar_book. Escalation: hr_complaint -> handoff_human d. Project Coordinator — category "operations", tools: knowledge_base_search. Escalation: deadline_missed -> handoff_human e. Financial Manager — category "finance", tools: knowledge_base_search. Escalation: large_transaction AND amount > threshold -> handoff_human f. Controller — category "finance", tools: knowledge_base_search. Escalation: budget_exceeded -> handoff_human g. Accountant — category "finance", tools: knowledge_base_search. Escalation: invoice_discrepancy AND amount > threshold -> handoff_human 3. Create `packages/shared/shared/prompts/__init__.py` (empty) and `system_prompt_builder.py`: - Function `build_system_prompt(name: str, role: str, persona: str = "", tool_assignments: list[str] | None = None, escalation_rules: list[dict] | None = None) -> str` - Assembles: "You are {name}, {role}.\n\n{persona}" + optional tools section + optional escalation section + ALWAYS the AI transparency clause - AI transparency clause (non-negotiable, per Phase 1 decision): "\n\nWhen directly asked if you are an AI, always disclose that you are an AI assistant." 4. Write unit tests in `tests/unit/test_system_prompt_builder.py`: - Test full prompt with all fields populated (name, role, persona, tools, escalation) - Test minimal prompt (name + role only) still includes AI clause - Test empty tools/escalation omit those sections - Test AI transparency clause is always present cd /home/adelorenzo/repos/konstruct && python -m pytest tests/unit/test_system_prompt_builder.py -x -v AgentTemplate ORM model exists in tenant.py, migration 007 creates table with 7 seed templates, build_system_prompt function passes all unit tests including AI transparency clause verification Task 2: Template API endpoints (list, detail, deploy) with RBAC and integration tests packages/shared/shared/api/templates.py, packages/gateway/main.py, tests/integration/test_templates.py 1. Create `packages/shared/shared/api/templates.py` with a new `templates_router = APIRouter(prefix="/api/portal", tags=["templates"])`: Pydantic schemas: - `TemplateResponse(BaseModel)`: id (str), name, role, description, category, persona, system_prompt, model_preference, tool_assignments (list[str]), escalation_rules (list[dict]), is_active (bool), sort_order (int), created_at (datetime) - `TemplateDeployRequest(BaseModel)`: tenant_id (str, UUID format) - `TemplateDeployResponse(BaseModel)`: agent (AgentResponse from portal.py) — the newly created agent Endpoints: a. `GET /api/portal/templates` — returns list[TemplateResponse]. Guard: `require_tenant_member` (any authenticated portal user can browse). Query: `SELECT * FROM agent_templates WHERE is_active = True ORDER BY sort_order, name`. No RLS needed (templates are global). b. `GET /api/portal/templates/{template_id}` — returns TemplateResponse. Guard: `require_tenant_member`. 404 if not found or inactive. c. `POST /api/portal/templates/{template_id}/deploy` — Guard: `require_tenant_admin` (per EMPL-04). Body: TemplateDeployRequest with tenant_id. - Fetch template by ID (404 if not found) - Set RLS context to body.tenant_id (same pattern as create_agent in portal.py) - Create Agent from template fields: name, role, persona, system_prompt, model_preference, tool_assignments, escalation_rules, is_active=True - Commit and return TemplateDeployResponse with the new agent 2. Mount `templates_router` in `packages/gateway/main.py` alongside existing routers: ```python from shared.api.templates import templates_router app.include_router(templates_router) ``` 3. Write integration tests in `tests/integration/test_templates.py`: - `test_list_templates` — GET returns 200 with 7+ templates (seeded by migration) - `test_get_template_detail` — GET single template returns correct fields - `test_deploy_template` — POST deploy returns 201 with new agent, agent has is_active=True, agent fields match template (snapshot) - `test_deploy_template_rbac` — POST deploy as customer_operator returns 403 - `test_deploy_template_not_found` — POST deploy with invalid UUID returns 404 - Follow the existing test pattern from `tests/integration/test_portal_rbac.py` for RBAC header injection (X-Portal-User-Id, X-Portal-User-Role, X-Portal-Tenant-Id headers) cd /home/adelorenzo/repos/konstruct && python -m pytest tests/integration/test_templates.py -x -v GET /api/portal/templates returns 7 seeded templates, POST deploy creates an independent agent snapshot with is_active=True, customer_operator gets 403 on deploy, all integration tests pass 1. `pytest tests/unit/test_system_prompt_builder.py -x` passes 2. `pytest tests/integration/test_templates.py -x` passes 3. `pytest tests/ -x` full suite still green (no regressions) - AgentTemplate table exists with 7 seed templates - GET /api/portal/templates returns all active templates - POST /api/portal/templates/{id}/deploy creates agent snapshot with is_active=True - Deploy blocked for customer_operator (403) - System prompt builder produces valid prompts with AI transparency clause - All unit and integration tests pass After completion, create `.planning/phases/05-employee-design/05-01-SUMMARY.md`