docs(phase-5): research employee design phase
This commit is contained in:
532
.planning/phases/05-employee-design/05-RESEARCH.md
Normal file
532
.planning/phases/05-employee-design/05-RESEARCH.md
Normal file
@@ -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>
|
||||||
|
## 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
|
||||||
|
</user_constraints>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<phase_requirements>
|
||||||
|
## 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 |
|
||||||
|
</phase_requirements>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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 `<AgentDesigner>` 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<Partial<WizardData>>({});
|
||||||
|
```
|
||||||
|
|
||||||
|
The wizard page itself is a server component that renders `<EmployeeWizard tenantId={tenantId} initialStep={step} />` 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<WizardData>) => 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)
|
||||||
Reference in New Issue
Block a user