Package: @holoscript/framework, not @holoscript/core. Every name on this page is imported from @holoscript/framework (source: packages/framework/src/ai/). The AI layer moved out of core in A.011.02c; this page stayed behind and still imported from core until 2026-10-07, which led one agent to report that HoloScriptGenerator existed nowhere.
Tests: 131 passing in the 5 files listed under Testing, in packages/framework/src/ai/__tests__/ (run 2026-10-08; see Testing). None of them calls a live model API (they use stand-in adapters or test only configuration), so validation against live APIs is still open (see Next Steps).
Validation: not done today. The parser this layer uses is a stand-in: HoloScriptPlusParser in packages/framework/src/ai/HoloScriptGenerator.ts returns { success: true, errors: [] } for any text. So parseResult.success is always true, validateBatch marks every input valid, and auto-fix never runs. Until that is fixed, check generated code yourself with parseHolo from @holoscript/core or the strict layer in packages/core/strict (codes HS1001-HS1010).
This document is a guide to the AI-guided HoloScript generation API in @holoscript/framework, which turns natural language descriptions into HoloScript code.
Not the same path as the MCP
generate_object/generate_scenetools. Those go through@holoscript/llm-provider(generateHoloScripton a provider adapter), whose system prompt (HOLOSCRIPT_SYSTEM_PROMPT) shows a parse-testedcomposition "Name" { ... }program. Since 2026-10-08 the framework adapters on this page send that same prompt for generate, fix and optimize, and the same knowledge with its "return only code" rule lifted for chat. Explain sends a short "explain clearly" prompt instead: with the long one, Qwen3-4B answered explain requests with code (measured 2026-10-09).
import { HoloScriptGenerator, AnthropicAdapter } from '@holoscript/framework';
// Create generator and session
const generator = new HoloScriptGenerator();
const adapter = new AnthropicAdapter({ apiKey: process.env.ANTHROPIC_API_KEY });
const session = generator.createSession(adapter);
// Generate code from prompt
const result = await generator.generate('Create a blue sphere at origin');
console.log(result.holoScript); // Generated HoloScript code
console.log(result.aiConfidence); // Confidence score (0-1)
console.log(result.parseResult.success); // Parse succeeded?
console.log(result.wasFixed); // Auto-fixed?import {
generateHoloScriptWithAdapter,
generateBatch,
validateBatch,
OpenAIAdapter,
} from '@holoscript/framework';
// Single generation
const result = await generateHoloScriptWithAdapter(
'Create an interactive player controller',
new OpenAIAdapter({ apiKey: 'sk-...' }),
{ maxAttempts: 3, targetPlatform: 'vr' }
);
// Batch generation
const results = await generateBatch(
['Create a player', 'Create an enemy', 'Create a button'],
new OpenAIAdapter({ apiKey: 'sk-...' })
);
// Validate batch
const validation = validateBatch(results.map((r) => r.holoScript));
console.log(`Valid: ${validation.filter((v) => v.valid).length}/${validation.length}`);Two functions, similar names.
generateHoloScriptWithAdapter(prompt, adapter, config?)is the helper above: it takes an adapter and returnsGeneratedCode. The export namedgenerateHoloScript(prompt, options?)is a different function: it uses the adapter registered withregisterAIAdapter/setDefaultAIAdapterand returns the adapter's rawGenerateResult. Passing an adapter togenerateHoloScriptdoes not do what the helper does.
Natural Language Prompt
↓
AI Adapter
↓
Generated Code (HoloScript)
↓
HoloScriptPlusParser
↓
Parse Result (AST)
↓
Valid? ──→ No → Auto-Fix → Re-parse
↓
Yes
↓
Explanation (optional)
↓
Generated Code Result
| Component | Purpose | Status |
|---|---|---|
| AIAdapter | Interface for AI providers | ✅ 9 implementations |
| HoloScriptGenerator | High-level generation API | ✅ Complete |
| HoloScriptPlusParser | Parse & validate generated code | |
| ErrorRecovery | Auto-fix broken code | |
| Sessions | Track generation history | ✅ Implemented |
Main class for AI-guided code generation.
class HoloScriptGenerator {
constructor(enableCache?: boolean); // default: true
}Creates a new generator instance with a built-in parser and, unless enableCache is false, a generation cache.
Create a new generation session.
interface GenerationConfig {
maxAttempts: number; // Default: 3
targetPlatform: 'mobile' | 'desktop' | 'vr' | 'ar'; // Default: 'vr'
autoFix: boolean; // Default: true
minConfidence: number; // Default: 0.7 (0-1)
}
const session = generator.createSession(adapter, {
maxAttempts: 5,
targetPlatform: 'vr',
autoFix: true,
minConfidence: 0.8,
});Generate HoloScript from a natural language prompt.
interface GeneratedCode {
holoScript: string; // The generated code
aiConfidence: number; // AI confidence (0-1)
parseResult: HSPlusCompileResult; // Parser result
wasFixed: boolean; // Auto-fixed?
attempts: number; // Number of attempts
explanation?: string; // Optional explanation
}
const result = await generator.generate(
'Create a glowing red cube that responds to touch',
session
);Behavior:
- Generates code up to
maxAttemptstimes - Checks confidence against
minConfidencethreshold - Auto-fixes if enabled and the parse reports errors, which today it never does
- "Parses" with
HoloScriptPlusParser({ strict: false }), a stand-in that returns success for any text.parseResult.successis therefore always true and says nothing about the code; check it withparseHoloor the strict layer (packages/core/strict) instead. - Fetches explanation if generation succeeds
- Records in session history
Optimize code for a specific platform.
const optimized = await generator.optimize(generatedCode.holoScript, 'mobile', session);Platforms: mobile, desktop, vr, ar
Fix invalid HoloScript code.
const fixed = await generator.fix(invalidCode, session);
if (fixed.parseResult.success) {
console.log('Fixed successfully!');
console.log(fixed.holoScript);
}Get a text explanation of what code does.
const explanation = await generator.explain(code, session);
console.log(explanation); // "This code creates a..."Multi-turn conversation for iterative development.
const history = [
{ role: 'user', content: 'Create a player' },
{ role: 'assistant', content: 'I will create...' },
];
const response = await generator.chat('Now add physics', session, history);Get generation history from session.
const history = generator.getHistory(session);
history.forEach((entry, i) => {
console.log(`[${i}] ${entry.prompt}`);
console.log(` Attempts: ${entry.generated.attempts}`);
console.log(` Confidence: ${entry.generated.aiConfidence}`);
console.log(` Success: ${entry.generated.parseResult.success}`);
});Get statistics for a session.
const stats = generator.getStats(session);
console.log(stats);
// {
// totalGenerations: 5,
// successCount: 4,
// fixedCount: 1,
// avgAttempts: 1.2,
// avgConfidence: 0.87,
// successRate: 0.8
// }Clear session history.
generator.clearHistory(session);| Provider | Class | Status | Auth |
|---|---|---|---|
| OpenAI | OpenAIAdapter |
✅ | API Key |
| Anthropic | AnthropicAdapter |
✅ | API Key |
| Ollama (Local) | OllamaAdapter |
✅ | URL |
| LM Studio | LMStudioAdapter |
✅ | URL |
| Google (Gemini) | GeminiAdapter |
✅ | API Key |
| XAI (Grok) | XAIAdapter |
✅ | API Key |
| Together.ai | TogetherAdapter |
✅ | API Key |
| Fireworks.ai | FireworksAdapter |
✅ | API Key |
| NVIDIA | NVIDIAAdapter |
✅ | API Key |
All adapters implement AIAdapter (packages/framework/src/ai/AIAdapter.ts). Only id,
name and isReady() are required; each capability is optional, so check before calling:
interface AIAdapter {
readonly id: string;
readonly name: string;
isReady(): boolean | Promise<boolean>;
// Generate HoloScript from prompt
generateHoloScript?(prompt: string, options?: GenerateOptions): Promise<GenerateResult>;
// Explain existing code
explainHoloScript?(holoScript: string): Promise<ExplainResult>;
// Optimize for platform
optimizeHoloScript?(
holoScript: string,
target: 'mobile' | 'desktop' | 'vr' | 'ar'
): Promise<OptimizeResult>;
// Fix broken code
fixHoloScript?(holoScript: string, errors: string[]): Promise<FixResult>;
// Code completion at a cursor position
completeHoloScript?(holoScript: string, cursorPosition: number): Promise<string[]>;
// Multi-turn conversation
chat?(
message: string,
holoScript?: string,
history?: Array<{ role: 'user' | 'assistant'; content: string }>
): Promise<string>;
// Generate embeddings
getEmbeddings?(text: string | string[]): Promise<number[][]>;
}
interface GenerateResult {
holoScript: string;
confidence?: number; // becomes GeneratedCode.aiConfidence
objectCount?: number;
warnings?: string[];
metadata?: Record<string, unknown>;
}import {
OpenAIAdapter,
AnthropicAdapter,
GeminiAdapter,
OllamaAdapter,
generateHoloScriptWithAdapter,
} from '@holoscript/framework';
// `model` is optional in every config; each adapter has a default.
// OpenAI
const openai = new OpenAIAdapter({
apiKey: process.env.OPENAI_API_KEY,
model: 'gpt-4o-mini', // the default
});
// Anthropic (Claude)
const anthropic = new AnthropicAdapter({
apiKey: process.env.ANTHROPIC_API_KEY,
});
// Google Gemini
const gemini = new GeminiAdapter({
apiKey: process.env.GEMINI_API_KEY,
});
// Local Ollama
const ollama = new OllamaAdapter({
baseUrl: 'http://localhost:11434',
model: 'mistral',
});
// Generate with different adapters
const results = await Promise.all([
generateHoloScriptWithAdapter(prompt, openai),
generateHoloScriptWithAdapter(prompt, anthropic),
generateHoloScriptWithAdapter(prompt, gemini),
]);Sessions track generation history and configuration:
const adapter = new OpenAIAdapter({ apiKey: '...' });
const session = generator.createSession(adapter, {
maxAttempts: 5,
targetPlatform: 'vr',
autoFix: true,
minConfidence: 0.8,
});
// All operations use this session by default
await generator.generate('Create a player', session);
// Or set as current
const current = generator.getCurrentSession();const session = generator.createSession(adapter, {
// Retry configuration
maxAttempts: 5, // Increase attempts for complex prompts
// Confidence threshold
minConfidence: 0.85, // Stricter validation
// Auto-fix
autoFix: true, // Try to fix broken code automatically
// Target platform
targetPlatform: 'mobile', // Optimize for mobile
});Every generation is recorded:
await generator.generate('Create player', session);
await generator.generate('Create enemy', session);
const history = generator.getHistory(session);
history.forEach((entry) => {
console.log('Prompt:', entry.prompt);
console.log('Code:', entry.generated.holoScript);
console.log('Success:', entry.generated.parseResult.success);
console.log('Timestamp:', entry.timestamp);
});const generator = new HoloScriptGenerator();
const adapter = new AnthropicAdapter({ apiKey: process.env.ANTHROPIC_API_KEY });
const session = generator.createSession(adapter);
const result = await generator.generate(
'Create a blue sphere that the user can grab and throw',
session
);
console.log('Generated:');
console.log(result.holoScript);
console.log('\nConfidence:', result.aiConfidence);
console.log('Valid:', result.parseResult.success);
console.log('Auto-fixed:', result.wasFixed);Example output (a model's exact output varies; this is the shape to expect, one
composition root around the objects). It parses with zero errors under parseHolo and the
strict layer:
composition "Throwable Sphere" {
object "BlueSphere" {
@grabbable
@throwable
@physics
@collidable
geometry: "sphere"
position: [0, 1.5, 0]
scale: 0.3
material: { baseColor: "#0077ff", roughness: 0.4, metallic: 0.1 }
}
}
A real, longer program in the same shape: examples/quickstart/2-red-cube-teal-button.holo.
const prompts = [
'Create a red cube that flashes when clicked',
'Create a green cylinder that rotates slowly',
'Create a yellow torus that glows in the dark',
];
const results = await generateBatch(prompts, new OpenAIAdapter({ apiKey: 'sk-...' }), {
maxAttempts: 3,
autoFix: true,
});
// Validate all results
const validation = validateBatch(results.map((r) => r.holoScript));
console.log(`\nValidation Results:`);
validation.forEach((v, i) => {
console.log(`[${i}] Valid: ${v.valid}, Errors: ${v.errors}`);
});const generator = new HoloScriptGenerator();
const adapter = new OpenAIAdapter({ apiKey: 'sk-...' });
const session = generator.createSession(adapter, { autoFix: true });
// Start with basic prompt
let code = await generator.generate('Create a player controller', session);
console.log('v1:', code.holoScript);
// Refine with fixes
const fixed = await generator.fix(code.holoScript, session);
console.log('v2:', fixed.holoScript);
// Optimize for mobile
const optimized = await generator.optimize(fixed.holoScript, 'mobile', session);
console.log('v3:', optimized.holoScript);
// Get explanation
const explanation = await generator.explain(optimized.holoScript, session);
console.log('\nExplanation:', explanation);const generator = new HoloScriptGenerator();
const adapter = new AnthropicAdapter({ apiKey: '...' });
const session = generator.createSession(adapter);
let history: Array<{ role: 'user' | 'assistant'; content: string }> = [];
// Turn 1
console.log('User: Create a simple game scene');
let response = await generator.chat('Create a simple game scene', session, history);
history.push({ role: 'user', content: 'Create a simple game scene' });
history.push({ role: 'assistant', content: response });
console.log('AI:', response);
// Turn 2
console.log('User: Add a player controller');
response = await generator.chat('Add a player controller', session, history);
history.push({ role: 'user', content: 'Add a player controller' });
history.push({ role: 'assistant', content: response });
console.log('AI:', response);
// Turn 3
console.log('User: Make the player able to jump');
response = await generator.chat('Make the player able to jump', session, history);
console.log('AI:', response);// ✅ Good: Create session once, reuse
const session = generator.createSession(adapter);
const code1 = await generator.generate('prompt 1', session);
const code2 = await generator.generate('prompt 2', session);
// ❌ Avoid: Creating new session for each generation
for (let i = 0; i < 10; i++) {
const s = generator.createSession(adapter); // Don't do this
await generator.generate(`prompt ${i}`, s);
}// ✅ Good: Adjust based on use case
const criticalCode = generator.createSession(adapter, {
minConfidence: 0.95, // High bar for critical code
maxAttempts: 10,
});
const experimentalCode = generator.createSession(adapter, {
minConfidence: 0.7, // Lower bar for exploration
maxAttempts: 3,
});// ✅ Good: Handle generation failures
try {
const result = await generator.generate(prompt, session);
if (result.parseResult.success) {
console.log('Generated successfully');
} else {
console.log('Warnings:', result.parseResult.errors);
}
} catch (error) {
console.error('Generation failed:', error.message);
// Fallback or retry
}// ✅ Good: Optimize upfront
const session = generator.createSession(adapter, {
targetPlatform: 'mobile', // Optimize for target
});
// Or optimize after generation
const optimized = await generator.optimize(generatedCode.holoScript, 'mobile', session);// ✅ Good: Generate in parallel
const results = await Promise.all(prompts.map((p) => generateHoloScriptWithAdapter(p, adapter)));
// Then validate
const validation = validateBatch(results.map((r) => r.holoScript));The tests live in packages/framework/src/ai/__tests__/:
# Run the five AI layer test files counted below
pnpm --filter @holoscript/framework exec vitest run src/ai/__tests__/HoloScriptGenerator.test.ts src/ai/__tests__/AIAdapter.test.ts src/ai/__tests__/AIAdapter.prod.test.ts src/ai/__tests__/adapters.test.ts src/ai/__tests__/adapters.prod.test.ts
# Run every test in the folder (43 files)
pnpm --filter @holoscript/framework exec vitest run src/ai/__tests__/
# Run one file
pnpm --filter @holoscript/framework exec vitest run src/ai/__tests__/HoloScriptGenerator.test.tsCounted from a run on 2026-10-08. Re-run the command above rather than trusting these numbers as they age.
| File | Tests | Status |
|---|---|---|
HoloScriptGenerator.test.ts |
14 | ✅ Passing |
AIAdapter.test.ts |
14 | ✅ Passing |
AIAdapter.prod.test.ts |
23 | ✅ Passing |
adapters.test.ts |
39 | ✅ Passing |
adapters.prod.test.ts |
41 | ✅ Passing |
| Total | 131 | ✅ Passing |
import { describe, it, expect } from 'vitest';
import { HoloScriptGenerator, type AIAdapter } from '@holoscript/framework';
class MockAdapter implements AIAdapter {
readonly id = 'mock';
readonly name = 'Mock';
isReady() {
return true;
}
async generateHoloScript(prompt: string) {
return {
holoScript: `composition "Test" {\n object "Cube" { geometry: "cube" }\n}`,
confidence: 0.95, // surfaces as GeneratedCode.aiConfidence
};
}
}
describe('GenerationLogic', () => {
it('should work with mock adapter', async () => {
const generator = new HoloScriptGenerator();
const session = generator.createSession(new MockAdapter());
const result = await generator.generate('test', session);
expect(result.holoScript).toBeDefined();
expect(result.aiConfidence).toBe(0.95);
});
});- Real adapter validation with live APIs
- End-to-end scenario testing
- Performance benchmarking
- Custom prompt templates
- Generation analytics dashboard
- Fine-tuned models for HoloScript
- Streaming generation for long operations
- Context-aware generation with scene analysis
Last Updated: 2026-10-07 (imports moved to @holoscript/framework, test counts re-run)
Status: Generation works; validation is a stand-in that always says "success" (see the top of this page); tested without live model APIs
Maintainer: AI Development Team