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