---
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"
---

<objective>
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.
</objective>

<execution_context>
@/home/adelorenzo/.claude/get-shit-done/workflows/execute-plan.md
@/home/adelorenzo/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.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

<interfaces>
<!-- Key types and contracts the executor needs. Extracted from codebase. -->

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)
</interfaces>
</context>

<tasks>

<task type="auto" tdd="true">
  <name>Task 1: AgentTemplate model, migration 007, system prompt builder, and tests</name>
  <files>
    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
  </files>
  <behavior>
    - 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
  </behavior>
  <action>
    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
  </action>
  <verify>
    <automated>cd /home/adelorenzo/repos/konstruct && python -m pytest tests/unit/test_system_prompt_builder.py -x -v</automated>
  </verify>
  <done>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</done>
</task>

<task type="auto">
  <name>Task 2: Template API endpoints (list, detail, deploy) with RBAC and integration tests</name>
  <files>
    packages/shared/shared/api/templates.py,
    packages/gateway/main.py,
    tests/integration/test_templates.py
  </files>
  <action>
    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)
  </action>
  <verify>
    <automated>cd /home/adelorenzo/repos/konstruct && python -m pytest tests/integration/test_templates.py -x -v</automated>
  </verify>
  <done>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</done>
</task>

</tasks>

<verification>
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)
</verification>

<success_criteria>
- 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
</success_criteria>

<output>
After completion, create `.planning/phases/05-employee-design/05-01-SUMMARY.md`
</output>
