Data Model: Interactive Init Wizard¶
Feature Branch: 011-interactive-init-wizard
Date: 2025-11-29
Entity Overview¶
┌─────────────────────┐ ┌─────────────────────┐
│ WizardState │──────│ WizardResult │
│ (runtime tracking) │ │ (final selections) │
└─────────────────────┘ └─────────────────────┘
│ │
│ ▼
│ ┌─────────────────────┐
│ │ ProjectInitInput │
│ │ (existing, extended)│
│ └─────────────────────┘
│
▼
┌─────────────────────┐ ┌─────────────────────┐
│ LLMProviderChoice │ │ VectorStoreChoice │
│ (wizard option) │ │ (wizard option) │
└─────────────────────┘ └─────────────────────┘
┌─────────────────────┐ ┌─────────────────────┐
│ EvalChoice │ │ MCPServerChoice │
│ (wizard option) │ │ (wizard option) │
└─────────────────────┘ └─────────────────────┘
Entities¶
1. WizardState¶
Purpose: Tracks user progress through the wizard and accumulates selections.
Location: src/holodeck/models/wizard_config.py
from enum import Enum
from pydantic import BaseModel, Field
class WizardStep(str, Enum):
"""Current step in the wizard flow."""
AGENT_NAME = "agent_name"
LLM_PROVIDER = "llm_provider"
VECTOR_STORE = "vector_store"
EVALS = "evals"
MCP_SERVERS = "mcp_servers"
COMPLETE = "complete"
class WizardState(BaseModel):
"""Runtime state tracking for interactive wizard.
Tracks current step and accumulated selections as user
progresses through the wizard flow.
"""
current_step: WizardStep = Field(
default=WizardStep.AGENT_NAME,
description="Current wizard step"
)
agent_name: str | None = Field(
default=None,
description="Agent name entered by user"
)
llm_provider: str | None = Field(
default=None,
description="Selected LLM provider"
)
vector_store: str | None = Field(
default=None,
description="Selected vector store"
)
evals: list[str] = Field(
default_factory=list,
description="Selected evaluation metrics"
)
mcp_servers: list[str] = Field(
default_factory=list,
description="Selected MCP server identifiers"
)
is_cancelled: bool = Field(
default=False,
description="Whether wizard was cancelled by user"
)
State Transitions:
AGENT_NAME → (input) → LLM_PROVIDER → (selection) → VECTOR_STORE → (selection) → EVALS → (selection) → MCP_SERVERS → (selection) → COMPLETE
↓ ↓ ↓ ↓ ↓
(cancel) (cancel) (cancel) (cancel) (cancel)
↓ ↓ ↓ ↓ ↓
[is_cancelled=True, abort without file creation]
2. WizardResult¶
Purpose: Final validated selections from wizard, ready for project initialization.
Location: src/holodeck/models/wizard_config.py
from pydantic import BaseModel, Field, model_validator
class WizardResult(BaseModel):
"""Final selections from interactive wizard.
All fields are required after wizard completion.
Validated before passing to ProjectInitializer.
"""
agent_name: str = Field(
...,
description="Agent name (alphanumeric, hyphens, underscores)"
)
llm_provider: str = Field(
...,
description="Selected LLM provider: ollama, openai, azure_openai, anthropic"
)
vector_store: str = Field(
...,
description="Selected vector store: chromadb, redis, in-memory"
)
evals: list[str] = Field(
...,
description="Selected evaluation metrics"
)
mcp_servers: list[str] = Field(
...,
description="Selected MCP server identifiers"
)
@model_validator(mode="after")
def validate_selections(self) -> "WizardResult":
"""Validate all selections are from allowed values."""
import re
valid_providers = {"ollama", "openai", "azure_openai", "anthropic"}
valid_stores = {"chromadb", "redis", "in-memory"}
valid_evals = {"rag-faithfulness", "rag-answer_relevancy", "rag-context_precision", "rag-context_recall"}
valid_mcp = {"brave-search", "memory", "sequentialthinking", "filesystem", "github", "postgres"}
# Validate agent name format
if not re.match(r"^[a-zA-Z0-9_-]+$", self.agent_name):
raise ValueError(f"Invalid agent name: {self.agent_name}. Use alphanumeric, hyphens, underscores only.")
if self.llm_provider not in valid_providers:
raise ValueError(f"Invalid LLM provider: {self.llm_provider}")
if self.vector_store not in valid_stores:
raise ValueError(f"Invalid vector store: {self.vector_store}")
for eval_metric in self.evals:
if eval_metric not in valid_evals:
raise ValueError(f"Invalid eval metric: {eval_metric}")
for mcp_server in self.mcp_servers:
if mcp_server not in valid_mcp:
raise ValueError(f"Invalid MCP server: {mcp_server}")
return self
Validation Rules:
agent_name: Alphanumeric, hyphens, underscores onlyllm_provider: Must be one of: ollama, openai, azure_openai, anthropicvector_store: Must be one of: chromadb, redis, in-memoryevals: Must be from allowed eval metricsmcp_servers: Must be from allowed MCP servers (list can be empty)
3. LLMProviderChoice¶
Purpose: Defines a selectable LLM provider option for the wizard.
Location: src/holodeck/models/wizard_config.py
from pydantic import BaseModel, Field
class LLMProviderChoice(BaseModel):
"""LLM provider option for wizard selection.
Provides display information and configuration hints
for each supported LLM provider.
"""
value: str = Field(..., description="Provider identifier for config")
display_name: str = Field(..., description="Human-readable name")
description: str = Field(..., description="Brief capability description")
is_default: bool = Field(default=False, description="Whether this is the default selection")
default_model: str = Field(..., description="Default model for this provider")
requires_api_key: bool = Field(default=True, description="Whether API key is needed")
api_key_env_var: str | None = Field(default=None, description="Environment variable for API key")
requires_endpoint: bool = Field(default=False, description="Whether endpoint URL is needed")
# Predefined choices
LLM_PROVIDER_CHOICES = [
LLMProviderChoice(
value="ollama",
display_name="Ollama (local)",
description="Local LLM inference, no API key required",
is_default=True,
default_model="gpt-oss:20b",
requires_api_key=False,
requires_endpoint=False,
),
LLMProviderChoice(
value="openai",
display_name="OpenAI",
description="GPT-4, GPT-3.5-turbo via OpenAI API",
default_model="gpt-4o",
requires_api_key=True,
api_key_env_var="OPENAI_API_KEY",
),
LLMProviderChoice(
value="azure_openai",
display_name="Azure OpenAI",
description="OpenAI models via Azure deployment",
default_model="gpt-4o",
requires_api_key=True,
api_key_env_var="AZURE_OPENAI_API_KEY",
requires_endpoint=True,
),
LLMProviderChoice(
value="anthropic",
display_name="Anthropic Claude",
description="Claude 3.5, Claude 3 via Anthropic API",
default_model="claude-3-5-sonnet-20241022",
requires_api_key=True,
api_key_env_var="ANTHROPIC_API_KEY",
),
]
4. VectorStoreChoice¶
Purpose: Defines a selectable vector store option for the wizard.
Location: src/holodeck/models/wizard_config.py
from pydantic import BaseModel, Field
class VectorStoreChoice(BaseModel):
"""Vector store option for wizard selection.
Provides display information and configuration hints
for each supported vector store.
"""
value: str = Field(..., description="Store identifier for config")
display_name: str = Field(..., description="Human-readable name")
description: str = Field(..., description="Brief capability description")
is_default: bool = Field(default=False, description="Whether this is the default selection")
default_endpoint: str | None = Field(default=None, description="Default connection endpoint")
persistence: str = Field(..., description="Data persistence model")
connection_required: bool = Field(default=False, description="Whether connection string needed")
# Predefined choices
VECTOR_STORE_CHOICES = [
VectorStoreChoice(
value="chromadb",
display_name="ChromaDB (default)",
description="Embedded vector database with local persistence",
is_default=True,
default_endpoint="http://localhost:8000",
persistence="local file",
connection_required=False,
),
VectorStoreChoice(
value="redis",
display_name="Redis",
description="Production-grade vector store with Redis Stack",
default_endpoint="redis://localhost:6379",
persistence="remote server",
connection_required=True,
),
VectorStoreChoice(
value="in-memory",
display_name="In-Memory",
description="Ephemeral storage for development/testing",
default_endpoint=None,
persistence="none (lost on restart)",
connection_required=False,
),
]
Warning for In-Memory: Per spec acceptance scenario 3.3, warn user about data loss on restart.
5. EvalChoice¶
Purpose: Defines a selectable evaluation metric option for the wizard.
Location: src/holodeck/models/wizard_config.py
from pydantic import BaseModel, Field
class EvalChoice(BaseModel):
"""Evaluation metric option for wizard selection.
Provides display information for each supported evaluation metric.
"""
value: str = Field(..., description="Eval metric identifier for config")
display_name: str = Field(..., description="Human-readable name")
description: str = Field(..., description="What this metric measures")
is_default: bool = Field(default=False, description="Whether pre-selected by default")
metric_type: str = Field(default="ai", description="Type of metric: ai or nlp")
# Predefined choices
EVAL_CHOICES = [
EvalChoice(
value="rag-faithfulness",
display_name="RAG Faithfulness",
description="Measures if response is grounded in retrieved context",
is_default=True,
metric_type="ai",
),
EvalChoice(
value="rag-answer_relevancy",
display_name="RAG Answer Relevancy",
description="Measures if response answers the question",
is_default=True,
metric_type="ai",
),
EvalChoice(
value="rag-context_precision",
display_name="RAG Context Precision",
description="Measures precision of retrieved context",
is_default=False,
metric_type="ai",
),
EvalChoice(
value="rag-context_recall",
display_name="RAG Context Recall",
description="Measures recall of retrieved context",
is_default=False,
metric_type="ai",
),
]
6. MCPServerChoice¶
Purpose: Defines a selectable MCP server option for the wizard.
Location: src/holodeck/models/wizard_config.py
from pydantic import BaseModel, Field
class MCPServerChoice(BaseModel):
"""MCP server option for wizard selection.
Provides display information and package details for each MCP server.
"""
value: str = Field(..., description="Short name for config")
display_name: str = Field(..., description="Human-readable name")
description: str = Field(..., description="Brief capability description")
is_default: bool = Field(default=False, description="Whether pre-selected by default")
package_identifier: str = Field(..., description="NPM package identifier")
command: str = Field(default="npx", description="Command to run the server")
# Predefined choices
MCP_SERVER_CHOICES = [
MCPServerChoice(
value="brave-search",
display_name="Brave Search",
description="Web search capabilities",
is_default=True,
package_identifier="@anthropic/mcp-server-brave-search",
),
MCPServerChoice(
value="memory",
display_name="Memory",
description="Key-value memory storage",
is_default=True,
package_identifier="@modelcontextprotocol/server-memory",
),
MCPServerChoice(
value="sequentialthinking",
display_name="Sequential Thinking",
description="Structured reasoning chains",
is_default=True,
package_identifier="@modelcontextprotocol/server-sequentialthinking",
),
MCPServerChoice(
value="filesystem",
display_name="Filesystem",
description="File system access",
is_default=False,
package_identifier="@modelcontextprotocol/server-filesystem",
),
MCPServerChoice(
value="github",
display_name="GitHub",
description="GitHub repository access",
is_default=False,
package_identifier="@modelcontextprotocol/server-github",
),
MCPServerChoice(
value="postgres",
display_name="PostgreSQL",
description="PostgreSQL database access",
is_default=False,
package_identifier="@modelcontextprotocol/server-postgres",
),
]
7. Extended ProjectInitInput¶
Purpose: Extend existing model to include wizard selections.
Location: src/holodeck/models/project_config.py (existing file, add fields)
# Add to existing ProjectInitInput model
class ProjectInitInput(BaseModel):
"""Extended with wizard configuration selections."""
# ... existing fields ...
# New wizard-related fields
agent_name: str = Field(
default="my-agent",
description="Agent name from wizard input"
)
llm_provider: str = Field(
default="ollama",
description="LLM provider from wizard selection"
)
vector_store: str = Field(
default="chromadb",
description="Vector store from wizard selection"
)
evals: list[str] = Field(
default_factory=lambda: [
"rag-faithfulness",
"rag-answer_relevancy",
],
description="Evaluation metrics from wizard selection"
)
mcp_servers: list[str] = Field(
default_factory=lambda: [
"brave-search",
"memory",
"sequentialthinking",
],
description="MCP servers from wizard selection"
)
Relationships¶
| From | To | Relationship | Description |
|---|---|---|---|
| WizardState | WizardResult | Transforms to | Final state becomes result |
| WizardResult | ProjectInitInput | Merged into | Wizard selections added to init input |
Validation Summary¶
| Entity | Validation Rule | Error Behavior |
|---|---|---|
| WizardResult | Agent name format (alphanumeric, -, _) | Raise ValueError |
| WizardResult | Provider in allowed set | Raise ValueError |
| WizardResult | Vector store in allowed set | Raise ValueError |
| WizardResult | Evals in allowed set | Raise ValueError |
| WizardResult | MCP servers in allowed set | Raise ValueError |
| ProjectInitInput | Template exists | Raise ValidationError |
State Diagram¶
[Start]
│
▼
┌─────────────────────────────┐
│ WizardState │
│ step = AGENT_NAME │
│ agent_name = None │
└─────────────────────────────┘
│ User enters name
▼
┌─────────────────────────────┐
│ WizardState │
│ step = LLM_PROVIDER │
│ agent_name = "my-agent" │
└─────────────────────────────┘
│ User selects provider
▼
┌─────────────────────────────┐
│ WizardState │
│ step = VECTOR_STORE │
│ llm_provider = "ollama" │
└─────────────────────────────┘
│ User selects store
▼
┌─────────────────────────────┐
│ WizardState │
│ step = EVALS │
│ vector_store = "chromadb" │
└─────────────────────────────┘
│ User selects evals
▼
┌─────────────────────────────┐
│ WizardState │
│ step = MCP_SERVERS │
│ evals = [...] │
└─────────────────────────────┘
│ User selects servers
▼
┌─────────────────────────────┐
│ WizardState │
│ step = COMPLETE │
│ mcp_servers = [...] │
└─────────────────────────────┘
│ Convert to WizardResult
▼
┌─────────────────────────────┐
│ WizardResult │
│ (all fields required) │
└─────────────────────────────┘
│ Merge with ProjectInitInput
▼
┌─────────────────────────────┐
│ ProjectInitializer │
│ Creates project files │
└─────────────────────────────┘
│
▼
[End: Project Created]