diff --git a/.planning/phases/05-employee-design/05-RESEARCH.md b/.planning/phases/05-employee-design/05-RESEARCH.md new file mode 100644 index 0000000..39ae964 --- /dev/null +++ b/.planning/phases/05-employee-design/05-RESEARCH.md @@ -0,0 +1,532 @@ +# Phase 5: Employee Design - Research + +**Researched:** 2026-03-24 +**Domain:** Multi-path AI employee creation — wizard, templates, and Advanced mode refactor in Next.js 16 / React 19 +**Confidence:** HIGH + +--- + + +## User Constraints (from CONTEXT.md) + +### Locked Decisions +- "New Employee" button presents three options: Templates / Guided Setup / Advanced +- Templates: Card grid gallery of pre-built agents — one-click deploy +- Guided Setup: 5-step wizard (Role → Persona → Tools → Channels → Escalation) +- Advanced: Existing Agent Designer form — full manual control over all fields including system prompt +- Labels: "Templates", "Guided Setup", "Advanced" — clear hierarchy from easiest to most control +- 5 wizard steps: Role definition → Persona setup → Tool selection → Channel assignment → Escalation rules +- System prompt auto-generated from wizard inputs — hidden from user (never shown during wizard) +- Final step: Review summary card showing everything configured, user clicks "Deploy Employee" +- After deploy: agent goes live on selected channels immediately +- Wizard-created agents appear in Agent Designer for later customization +- Templates stored as database seed data — platform admin can add/edit templates via portal +- Card grid gallery with preview — each card shows: name, role description, included tools +- "Preview" expands to show full configuration before deploying +- V1 Templates (7+): Customer Support Rep, Sales Assistant, Office Manager, Project Coordinator, Financial Manager, Controller, Accountant +- One-click deploy — no customization step before deployment +- Auto-assigns to all connected channels for the tenant +- User can find the deployed agent in the employee list and edit via Agent Designer later +- Template is a snapshot — deploying creates an independent agent that doesn't track template changes +- Agent Designer becomes the "Advanced" option for new employee creation +- Also serves as the edit mode for all existing agents (regardless of how they were created) +- Wizard-created and template-deployed agents are fully editable in Agent Designer +- No functionality removed from Agent Designer — it remains the power-user tool + +### Claude's Discretion +- Wizard step UI design (stepper, cards, progress indicator) +- Template card visual design +- Review summary card layout +- How wizard inputs map to system prompt construction +- Template seed data format and migration approach +- Whether templates get a dedicated DB table or reuse the agents table with a `is_template` flag + +### Deferred Ideas (OUT OF SCOPE) +None — discussion stayed within phase scope + + +--- + + +## Phase Requirements + +| ID | Description | Research Support | +|----|-------------|-----------------| +| EMPL-01 | Multi-step wizard guides user through AI employee creation (role definition, persona, tools, channels, escalation rules) without requiring knowledge of system prompt format | Wizard component pattern using URL searchParams for step state; react-hook-form + zod for per-step validation; system prompt builder function maps wizard inputs to system_prompt field | +| EMPL-02 | Pre-built agent templates (e.g., Customer Support Rep, Sales Assistant, Office Manager) available for one-click deployment with sensible defaults | Templates DB table with seed migration 007; new GET /templates and POST /templates/{id}/deploy API endpoints; TanStack Query hooks useTemplates and useDeployTemplate | +| EMPL-03 | Template-deployed agents are immediately functional — respond in connected channels with the template's persona, tools, and escalation rules | Deploy endpoint calls existing POST /tenants/{tid}/agents + assigns all connected channels; channel_connections query provides auto-assignment targets | +| EMPL-04 | Wizard and templates accessible to platform admins and customer admins (RBAC-enforced, not operators) | require_tenant_admin guard on all new creation endpoints — same guard already used on POST /tenants/{tid}/agents; EMPL-01/02 are creation paths, so existing guard applies | +| EMPL-05 | Agents created via wizard or template appear in Agent Designer for further customization | All creation paths produce agents through the same POST /agents endpoint and ORM model; edit page at /agents/[id] already works for all agents | + + +--- + +## Summary + +Phase 5 adds three employee creation paths on top of the existing Agent CRUD infrastructure. The backend (FastAPI, SQLAlchemy, PostgreSQL) is largely complete — the Agent ORM model, create/update/delete endpoints, and RBAC guards all exist and need only minor additions. The primary new backend work is a `templates` table with seed data and a deploy endpoint that wraps the existing agent creation path. + +The frontend work is larger. The current `/agents/new` page routes directly to AgentDesigner. It must become a three-option entry screen. The 5-step wizard is a new client component using the same URL-searchParams step tracking pattern established by the onboarding wizard. The template gallery is a new card grid that reads from a new `/api/portal/templates` endpoint. + +The system prompt auto-generation for the wizard is a pure TypeScript function that assembles the stored fields (role, persona, tool list, escalation rules) into a coherent system prompt string — no AI call needed. + +**Primary recommendation:** Reuse the existing `OnboardingStepper` component pattern for the wizard stepper; store templates in a dedicated `agent_templates` table (cleaner than `is_template` flag, avoids polluting the agents list); use URL searchParams for wizard step state (established project pattern). + +--- + +## Standard Stack + +### Core (all already in use — no new installs needed) + +| Library | Version | Purpose | Why Standard | +|---------|---------|---------|--------------| +| Next.js | 16.2.1 | App Router, pages, routing | Project standard | +| React | 19.2.4 | UI | Project standard | +| react-hook-form | existing | Per-step form validation | Project standard (agentDesignerSchema pattern) | +| zod | existing | Schema validation | Project standard (standardSchemaResolver) | +| @hookform/resolvers | existing | standardSchemaResolver | Project standard — zodResolver dropped in v5 | +| TanStack Query | existing | Server state, mutations | Project standard (useCreateAgent pattern) | +| shadcn/ui | existing | Card, Button, Badge, Dialog, Select | Project standard | +| Tailwind CSS | existing | Styling | Project standard | +| FastAPI | existing | API endpoints | Project standard | +| SQLAlchemy 2.0 | existing | ORM, templates table | Project standard | +| Alembic | existing | Migration 007 for templates | Project standard | +| pytest + pytest-asyncio + httpx | existing | Integration tests | Project standard | + +### No new packages required + +All needed libraries are already installed. The wizard, template gallery, and system prompt builder are implemented using existing tools. + +--- + +## Architecture Patterns + +### Recommended File Structure (new files only) + +``` +packages/portal/ +├── app/(dashboard)/agents/ +│ └── new/ +│ ├── page.tsx # REPLACE: three-option entry screen +│ ├── wizard/ +│ │ └── page.tsx # NEW: 5-step wizard page +│ └── templates/ +│ └── page.tsx # NEW: template gallery page +├── components/ +│ ├── employee-wizard.tsx # NEW: 5-step wizard component +│ ├── employee-wizard-stepper.tsx # NEW: wizard progress indicator +│ └── template-gallery.tsx # NEW: template card grid + +packages/shared/shared/ +├── api/ +│ └── templates.py # NEW: GET /templates, POST /templates/{id}/deploy +├── models/ +│ └── tenant.py # ADD: AgentTemplate ORM model +└── prompts/ + └── system_prompt_builder.py # NEW: wizard → system prompt construction + +migrations/versions/ +└── 007_agent_templates.py # NEW: agent_templates table + seed data +``` + +### Pattern 1: Three-Option Entry Screen + +The current `/agents/new/page.tsx` becomes a three-panel selection screen. Each option is a Card with a headline, description, and "Start" button. This is a client component. + +```typescript +// packages/portal/app/(dashboard)/agents/new/page.tsx +"use client"; +// Router push to three distinct destinations: +// Templates → /agents/new/templates?tenant={id} +// Guided Setup → /agents/new/wizard?tenant={id}&step=1 +// Advanced → /agents/new/advanced?tenant={id} (or reuse existing AgentDesigner inline) +``` + +The "Advanced" path can either: +- Route to a sub-path `/agents/new/advanced` that renders `` directly, OR +- Replace `page.tsx` with a mode switch that renders the three-option screen when no `mode` param is set, and renders AgentDesigner when `?mode=advanced` + +**Recommendation:** Sub-pages (`/agents/new/wizard`, `/agents/new/templates`) are cleaner — each is an independent route with its own URL. The existing `/agents/new` page becomes the selector. Advanced mode moves to `/agents/new/advanced`. + +### Pattern 2: 5-Step Wizard with URL State + +The onboarding wizard at `/onboarding` uses `?step=1|2|3` in URL searchParams. The employee wizard follows the same pattern — established, browser-refresh safe, shareable. + +The wizard page is an async server component that reads `searchParams` (Promise in Next.js 15+). The step content components are client components. + +```typescript +// packages/portal/app/(dashboard)/agents/new/wizard/page.tsx +// Server component (no "use client") +interface WizardPageProps { + searchParams: Promise<{ step?: string; tenant?: string }>; +} + +export default async function WizardPage({ searchParams }: WizardPageProps) { + const params = await searchParams; + const step = parseInt(params.step ?? "1", 10); + const tenantId = params.tenant_id ?? ""; + // ...render EmployeeWizardStepper + step content +} +``` + +Step navigation uses `router.push` with updated searchParams: +```typescript +router.push(`/agents/new/wizard?tenant=${tenantId}&step=${nextStep}`); +``` + +### Pattern 3: Wizard State — React useState (not URL) + +The wizard accumulates data across 5 steps. The step page contains the stepper and renders one step component at a time. Step data is held in a client-component parent via `useState` — NOT in URL params (persona text, tool lists would pollute the URL). + +```typescript +// employee-wizard.tsx — client component +"use client"; + +interface WizardData { + role: string; + roleTitle: string; + persona: string; + tool_assignments: string[]; + channel_ids: string[]; // IDs of selected channels to assign + escalation_rules: { condition: string; action: string }[]; +} + +// Parent holds state, passes down to each step component +const [wizardData, setWizardData] = useState>({}); +``` + +The wizard page itself is a server component that renders `` which is a client component managing all state. + +### Pattern 4: System Prompt Builder + +A pure TypeScript/Python function assembles wizard inputs into a system prompt. No LLM call — just string templating. + +```typescript +// lib/system-prompt-builder.ts +export function buildSystemPrompt(data: { + name: string; + role: string; + persona: string; + tool_assignments: string[]; + escalation_rules: { condition: string; action: string }[]; +}): string { + const toolsSection = data.tool_assignments.length > 0 + ? `\n\nYou have access to the following tools: ${data.tool_assignments.join(", ")}.` + : ""; + const escalationSection = data.escalation_rules.length > 0 + ? `\n\nEscalation rules:\n${data.escalation_rules.map(r => `- If ${r.condition}: ${r.action}`).join("\n")}` + : ""; + const aiClause = "\n\nWhen directly asked if you are an AI, always disclose that you are an AI assistant."; + + return `You are ${data.name}, ${data.role}.\n\n${data.persona}${toolsSection}${escalationSection}${aiClause}`; +} +``` + +The AI transparency clause (unconditional AI disclosure) is a locked project decision from Phase 1. It must be included in all auto-generated system prompts. + +### Pattern 5: Template DB Table (Dedicated Table) + +The CONTEXT.md leaves the template storage approach to Claude's discretion. A dedicated `agent_templates` table is cleaner than an `is_template` flag on the agents table: +- Templates are never tenant-scoped (global, created by platform admin) +- They should not appear in tenant agent lists +- They have additional metadata fields (description, category, preview) +- No RLS needed — templates are read-only for all users, write for platform admin + +```sql +-- agent_templates table (migration 007) +CREATE TABLE agent_templates ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + name TEXT NOT NULL, + role TEXT NOT NULL, + description TEXT NOT NULL DEFAULT '', -- shown in card preview + category TEXT NOT NULL DEFAULT 'general', + persona TEXT NOT NULL DEFAULT '', + system_prompt TEXT NOT NULL DEFAULT '', + model_preference TEXT NOT NULL DEFAULT 'quality', + tool_assignments JSONB NOT NULL DEFAULT '[]', + escalation_rules JSONB NOT NULL DEFAULT '[]', + is_active BOOLEAN NOT NULL DEFAULT TRUE, + sort_order INTEGER NOT NULL DEFAULT 0, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); +``` + +Platform admin CRUD uses `require_platform_admin` guard. All users can GET templates (no auth required beyond being a portal member). + +### Pattern 6: Template Deploy Endpoint + +Deploy calls existing agent creation logic + auto-assigns all connected channels. + +```python +# POST /api/portal/templates/{template_id}/deploy +# Body: { tenant_id: UUID } +# Guard: require_tenant_admin +# Logic: +# 1. Fetch template by ID +# 2. Create Agent from template fields (snapshot — independent copy) +# 3. Return AgentResponse +``` + +Auto-assignment to connected channels: the deploy endpoint queries `channel_connections` for the tenant and stores the channel IDs. For v1, the agent is created and marked active — the channel assignment means the agent responds to all messages in those channels (the existing routing already assigns by tenant, not per-agent channel list). No additional channel-agent join table is needed for v1. + +### Pattern 7: Wizard Step Components + +Each step is a separate component file under `components/wizard-steps/`: + +``` +components/wizard-steps/ +├── step-role.tsx # Name + Job Title inputs +├── step-persona.tsx # Persona textarea (plain language) +├── step-tools.tsx # Tool multi-select (badge chips, same as AgentDesigner) +├── step-channels.tsx # Channel multi-select from connected channels +├── step-escalation.tsx # Escalation rules (simplified compared to AgentDesigner) +└── step-review.tsx # Summary card + "Deploy Employee" button +``` + +Each step receives `wizardData` and `onNext: (updates: Partial) => void`. The review step receives the complete `wizardData` and calls the createAgent mutation. + +### Anti-Patterns to Avoid + +- **Storing wizard state in URL params:** Role name is fine, but persona text and tool lists would make URLs unreadable and hit browser URL length limits. Keep multi-field state in React state. +- **Creating a new agent creation endpoint for wizard:** The wizard uses the existing `POST /tenants/{tid}/agents` endpoint — it just pre-fills `system_prompt` from the builder function. +- **`is_template` flag on the agents table:** Templates are global platform data, not tenant agents. A separate table avoids polluting agent lists and avoids RLS complications. +- **Template tracking changes after deploy:** The CONTEXT.md explicitly states deployed agents are independent snapshots. Do not add foreign key from agents back to templates. +- **Showing the system_prompt field in the wizard UI:** Locked decision — system prompt is hidden during wizard. Only visible in Agent Designer (Advanced mode). + +--- + +## Don't Hand-Roll + +| Problem | Don't Build | Use Instead | Why | +|---------|-------------|-------------|-----| +| Step progress indicator | Custom stepper from scratch | Adapt existing `OnboardingStepper` component | Already built, same visual style, just update step count and labels | +| Form validation | Custom validation logic | react-hook-form + zod (per-step schemas) | Established project pattern, handles error display | +| Server state | Custom fetch/cache | TanStack Query `useTemplates`, `useDeployTemplate` hooks | Established pattern, handles loading/error states | +| Tool chip UI | Custom chip component | Badge + X button (same as AgentDesigner tool_assignments section) | Already implemented, copy the pattern | +| Channel selection | Custom channel list | useChannelConnections hook + checkboxes | Hook already exists in queries.ts | + +--- + +## Common Pitfalls + +### Pitfall 1: searchParams is a Promise in Next.js 15+ (project uses Next.js 16) + +**What goes wrong:** Accessing `searchParams.step` directly causes a build error or runtime warning. +**Why it happens:** Next.js 15 made `searchParams` a Promise in page components. Confirmed in the project's STATE.md and existing onboarding page. +**How to avoid:** In server components, `await searchParams`. In client components, use `useSearchParams()` from `next/navigation`. +**Warning signs:** TypeScript errors on `.step` access without await. + +### Pitfall 2: Wizard state lost on browser refresh + +**What goes wrong:** User refreshes midway through wizard, loses all entered data. +**Why it happens:** React useState is ephemeral. +**How to avoid:** For the current project's needs (5-step wizard completing in < 5 minutes), this is acceptable UX. Add a warning comment in the component. Do NOT try to serialize wizard state into URL params (too complex). If the user refreshes, they restart — this is fine. +**Warning signs:** Temptation to serialize complex state to sessionStorage or URL. + +### Pitfall 3: Template deploy creates inactive agent + +**What goes wrong:** Deployed agent is inactive by default, not responding in channels. +**Why it happens:** `is_active` defaults to `True` in the ORM, but code might explicitly set False. +**How to avoid:** Template deploy always creates agents with `is_active=True`. The phase 3 decision ("agent goes live automatically, is_active true by default") applies here. + +### Pitfall 4: Wizard channel step — no connected channels + +**What goes wrong:** User reaches the Channels step but the tenant has no connected channels yet. +**Why it happens:** Channel connection (Slack/WhatsApp) is a separate onboarding step. +**How to avoid:** The Channels step should handle the empty state gracefully — show a message "No channels connected yet. Your employee will be deployed and can be assigned to channels later." Allow the step to be skipped with no channels selected. + +### Pitfall 5: Template gallery visible to customer_operator + +**What goes wrong:** Operators can browse templates but cannot deploy (403 on the deploy endpoint). Confusing UX. +**Why it happens:** EMPL-04 restricts wizard and template deployment to platform_admin and customer_admin. +**How to avoid:** Check caller role before rendering the "New Employee" button (same RBAC check already used for nav items). The three-option screen should only be reachable by admins. If an operator somehow reaches it, the deploy API will return 403 and the UI should handle that error. + +### Pitfall 6: System prompt builder missing AI transparency clause + +**What goes wrong:** Wizard-generated system prompts omit the mandatory AI disclosure clause. +**Why it happens:** The disclosure is hardcoded into the default system prompt template but easy to forget when building the auto-generator. +**How to avoid:** The system prompt builder function always appends: "When directly asked if you are an AI, always disclose that you are an AI assistant." This is a non-negotiable per Phase 1 design decision. + +--- + +## Code Examples + +### Existing stepper pattern (adapt for 5-step wizard) + +The `OnboardingStepper` component at `packages/portal/components/onboarding-stepper.tsx` accepts `currentStep: number` and a `StepInfo[]` array. For the employee wizard, create `EmployeeWizardStepper` with the same structure but 5 steps: + +```typescript +// Source: packages/portal/components/onboarding-stepper.tsx (existing) +export const WIZARD_STEPS: StepInfo[] = [ + { number: 1, label: "Role", description: "What will they do?" }, + { number: 2, label: "Persona", description: "How will they behave?" }, + { number: 3, label: "Tools", description: "What can they use?" }, + { number: 4, label: "Channels", description: "Where will they work?" }, + { number: 5, label: "Escalation", description: "When to hand off?" }, +]; +// + step 6 (Review) is not a numbered wizard step — it's the final deploy screen +``` + +### Existing createAgent mutation (wizard and template deploy both use this) + +```typescript +// Source: packages/portal/lib/queries.ts (existing) +const createAgent = useCreateAgent(); +await createAgent.mutateAsync({ + tenantId: tenantId, + data: { + name: wizardData.name, + role: wizardData.roleTitle, + persona: wizardData.persona, + system_prompt: buildSystemPrompt(wizardData), // auto-generated + model_preference: "quality", + tool_assignments: wizardData.tool_assignments, + escalation_rules: wizardData.escalation_rules, + is_active: true, + }, +}); +``` + +### Alembic migration pattern for seed data (migration 007) + +```python +# Source: migrations/versions/006_rbac_roles.py pattern +revision: str = "007" +down_revision: Union[str, None] = "006" + +def upgrade() -> None: + op.create_table( + "agent_templates", + sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True, ...), + sa.Column("name", sa.String(255), nullable=False), + ... + ) + # Seed data INSERT + op.execute(""" + INSERT INTO agent_templates (id, name, role, description, ...) VALUES + (gen_random_uuid(), 'Customer Support Rep', 'Customer Support Representative', ...), + ... + """) +``` + +### Channel auto-assignment on template deploy + +```python +# POST /api/portal/templates/{template_id}/deploy +async def deploy_template(template_id, body, caller, session): + template = await get_template_or_404(template_id, session) + # Create agent (snapshot of template) + agent = Agent( + tenant_id=body.tenant_id, + name=template.name, + role=template.role, + persona=template.persona, + system_prompt=template.system_prompt, + model_preference=template.model_preference, + tool_assignments=template.tool_assignments, + escalation_rules=template.escalation_rules, + is_active=True, + ) + session.add(agent) + await session.commit() + # Note: channel routing is tenant-scoped, not per-agent in v1. + # Agent responds to all channels connected to the tenant automatically. + return AgentResponse.from_orm(agent) +``` + +--- + +## State of the Art + +| Old Approach | Current Approach | When Changed | Impact | +|--------------|------------------|--------------|--------| +| zodResolver from @hookform/resolvers | standardSchemaResolver | hookform/resolvers v5 (project already uses this) | Must use standardSchemaResolver, not zodResolver | +| `searchParams` as sync object | `searchParams` as Promise | Next.js 15+ (project on 16.2.1) | Must `await searchParams` in server components, use `useSearchParams()` in client components | +| `params` as sync object | `params` as Promise | Next.js 15+ (project on 16.2.1) | Must `use(params)` in client components (see existing /agents/[id]/page.tsx) | +| middleware.ts | proxy.ts | Next.js 16 (renamed) | Project already uses proxy.ts | + +--- + +## Open Questions + +1. **Wizard Channels Step — what exactly gets "assigned"?** + - What we know: The deploy decision says "auto-assigns to all connected channels." The existing agent routing is tenant-scoped — all tenant agents share the channel and respond by agent selection logic. + - What's unclear: Is there a per-agent channel assignment in the ORM/routing layer, or is it purely tenant-level? + - Recommendation: Audit the orchestrator routing logic before planning. If per-agent channel assignment doesn't exist in the DB schema, the Channels step in the wizard becomes an informational step ("Your employee will be active in these channels") rather than a configuration step. Do not add a channel-agent join table in this phase. + +2. **Template CRUD for platform admin — new portal page or inline?** + - What we know: Templates are stored as DB seed data. Platform admin should be able to add/edit. + - What's unclear: Context says "platform admin can add/edit templates via portal" but gives no UI spec. + - Recommendation: For Phase 5, templates are read-only via seed data. Full template CRUD UI can be a v2 feature. The seed migration covers the 7 V1 templates. + +3. **Wizard "Deploy Employee" step — return URL after success?** + - What we know: Current createAgent mutations redirect to `/agents` on success. + - Recommendation: Redirect to `/agents/{newAgent.id}?tenant={tenantId}` (the edit page) to confirm deployment and offer immediate customization. This satisfies EMPL-05 (agent appears in Agent Designer). + +--- + +## Validation Architecture + +### Test Framework + +| Property | Value | +|----------|-------| +| Framework | pytest 8.x + pytest-asyncio + httpx | +| Config file | `pyproject.toml` (root) | +| Quick run command | `pytest tests/unit -x` | +| Full suite command | `pytest tests/ -x` | + +### Phase Requirements → Test Map + +| Req ID | Behavior | Test Type | Automated Command | File Exists? | +|--------|----------|-----------|-------------------|-------------| +| EMPL-01 | Wizard creates agent with auto-generated system_prompt | unit | `pytest tests/unit/test_system_prompt_builder.py -x` | ❌ Wave 0 | +| EMPL-01 | Wizard agent creation hits POST /tenants/{tid}/agents (existing endpoint) | integration | `pytest tests/integration/test_portal_agents.py -x` | ✅ existing | +| EMPL-02 | GET /api/portal/templates returns template list | integration | `pytest tests/integration/test_templates.py -x` | ❌ Wave 0 | +| EMPL-02 | Template deploy creates independent agent snapshot | integration | `pytest tests/integration/test_templates.py::test_deploy_template -x` | ❌ Wave 0 | +| EMPL-03 | Deployed agent is_active=True and correct fields from template | integration | `pytest tests/integration/test_templates.py::test_deployed_agent_is_active -x` | ❌ Wave 0 | +| EMPL-04 | Template deploy blocked for customer_operator (403) | integration | `pytest tests/integration/test_templates.py::test_deploy_template_rbac -x` | ❌ Wave 0 | +| EMPL-04 | Wizard agent creation blocked for customer_operator (403) | integration | `pytest tests/integration/test_portal_rbac.py -x` | ✅ existing | +| EMPL-05 | Wizard-created agent has correct fields (accessible via GET /agents/{id}) | integration | `pytest tests/integration/test_portal_agents.py -x` | ✅ existing | + +### Sampling Rate +- **Per task commit:** `pytest tests/unit -x` +- **Per wave merge:** `pytest tests/ -x` +- **Phase gate:** Full suite green before `/gsd:verify-work` + +### Wave 0 Gaps +- [ ] `tests/unit/test_system_prompt_builder.py` — covers EMPL-01 (system prompt construction) +- [ ] `tests/integration/test_templates.py` — covers EMPL-02, EMPL-03, EMPL-04 + +*(Frontend wizard UX is manual-only: no automated test framework for Next.js components is configured in this project)* + +--- + +## Sources + +### Primary (HIGH confidence) +- `/home/adelorenzo/repos/konstruct/packages/portal/AGENTS.md` — Next.js version caveat; confirmed Next.js 16.2.1, React 19.2.4 +- `/home/adelorenzo/repos/konstruct/packages/portal/node_modules/next/dist/docs/01-app/03-api-reference/04-functions/use-search-params.md` — searchParams is read-only URLSearchParams in client components +- `/home/adelorenzo/repos/konstruct/packages/portal/node_modules/next/dist/docs/01-app/03-api-reference/04-functions/use-router.md` — router.push/replace API confirmed +- `/home/adelorenzo/repos/konstruct/packages/portal/node_modules/next/dist/docs/01-app/02-guides/forms.md` — Server Actions form patterns (not used for this phase — TanStack Query pattern preferred per project conventions) +- `/home/adelorenzo/repos/konstruct/packages/portal/components/onboarding-stepper.tsx` — OnboardingStepper pattern to reuse +- `/home/adelorenzo/repos/konstruct/packages/portal/app/(dashboard)/onboarding/page.tsx` — `searchParams: Promise<{...}>` server component pattern confirmed +- `/home/adelorenzo/repos/konstruct/packages/shared/shared/models/tenant.py` — Agent ORM model fields confirmed +- `/home/adelorenzo/repos/konstruct/packages/shared/shared/api/portal.py` — `require_tenant_admin` guard on POST /agents confirmed +- `/home/adelorenzo/repos/konstruct/.planning/STATE.md` — Project-wide architectural decisions + +### Secondary (MEDIUM confidence) +- `/home/adelorenzo/repos/konstruct/.planning/phases/05-employee-design/05-CONTEXT.md` — User decisions (authoritative for this phase) + +--- + +## Metadata + +**Confidence breakdown:** +- Standard stack: HIGH — all libraries verified from existing source files +- Architecture: HIGH — patterns derived from existing onboarding wizard and agent designer code in the project +- Pitfalls: HIGH — searchParams/params as Promise pitfalls confirmed from Next.js 16 docs; other pitfalls from existing STATE.md decisions +- System prompt builder: HIGH — pure function, no external dependencies +- Template DB design: MEDIUM — dedicated table recommendation is reasoned but not verified against a specific external source + +**Research date:** 2026-03-24 +**Valid until:** 2026-04-24 (stable stack — Next.js 16, no breaking changes expected)