Codex for Sales: Running AI-Generated Business Logic in Production
AI-generated pricing and scheduling code needs git-like lifecycle management: staging, validation, approval, rollback. Real patterns from a production multi-agent system.
When a spa owner says “we added Thai massage, 200 baht,” five things must happen. The offerings schema must list the service. The order schema must reference it. The pricing formula must calculate it. The scheduling function must book it. The validation function must verify it.
These files form a dependency chain. Break the sequence and the system fails — not with a 404, but with a price for a service the catalog does not contain.
Most AI platforms hand this entire cascade to a language model. The model reads a prompt that says “update all downstream skills.” It sometimes complies. Sometimes it skips a step. Sometimes it reorders the files. No rollback exists. No validation catches the gap until a customer disputes a bill.
At QuotyAI we solve this by assigning creative work to the LLM and structural work to deterministic code. The LLM extracts intent — “add Thai massage, 200 baht” becomes typed structured data. Everything after that runs on deterministic orchestration: a dependency graph defines cascade order, code generation subagents produce each file atomically, a validation pipeline checks every output, and an operator approves the diff before any change reaches production.
These are the six patterns that make this work in production.
“The system should make structural failures structurally impossible.”
Heads up on versions: This article covers the QuotyAI skill builder architecture as of June 2026. The orchestration layer uses the deepagents library for agent composition. All Zod schemas use Zod 4.x. The dependency graph and skill definitions are maintained in the builder infrastructure — not duplicated into tenant data.
🔗 The Cascade Problem
Business logic in a sales system is not a single file. It is a directed acyclic graph of interdependent resources:
offerings_schema → order_schema → pricing_formula
→ scheduling_function
→ validation_function
router, callback, system_prompt (independent)
Change one node and every downstream node must be regenerated. A new service means a new schema entry, a new pricing branch, a new scheduling slot, and a new validation rule. Miss any of these and the assistant produces confident wrong answers — quotes that don’t add up, bookings that don’t validate, prices that don’t exist.
The cascade problem is not unique to sales. Any AI system that manages structured, interdependent data faces it. Inventory management, compliance rule engines, configuration-as-code platforms — they all have the same fundamental constraint.
| Requirement | Naive LLM approach | Deterministic cascade |
|---|---|---|
| Update ordering | Prompt-dependent | Topological sort from dependency graph |
| Failure handling | Partial updates | Atomic abort on any failure |
| Validation | None (trust the output) | Schema validation + sandbox compilation |
| Audit trail | Scattered in chat logs | Immutable event log per promotion |
| Rollback | Manual restore | HEAD pointer to any prior commit |
📐 Pattern 1: LLM for Meaning, Code for Structure
The root insight is a strict separation of concerns. The LLM does exactly one thing: convert natural language into typed structured data. Everything after that is deterministic.
"เพิ่มนวดไทย 200 บาท" → LLM with structured output
→ { name: "Thai Massage", price: 200, category: "massage" }
→ Deterministic cascade executes
The LLM call uses model.withStructuredOutput() with a Zod schema — not freeform generation:
const IntentClassificationSchema = z.object({
mode: z.enum(['query', 'update']),
pattern: z.string().nullable(),
confidence: z.number().min(0).max(1),
});
const result = await model.withStructuredOutput(IntentClassificationSchema)
.invoke([/* conversation history */]);
The classifier returns typed objects: mode, pattern, confidence. No string parsing, no regex, no prompt-engineering fragile output formats.
When the mode is update and confidence exceeds a threshold, control passes to the deterministic orchestrator. The LLM never decides which file to edit next, never decides how to order updates, never decides whether validation passed.
💡 Unique Insight: The structured output schema for intent classification went through five iterations in production. The first version returned freeform JSON that the orchestrator parsed manually — leading to a 7% parse failure rate on retry. Moving to
withStructuredOutputwith a typed Zod schema eliminated parse failures entirely. The lesson: if the LLM produces the schema, the LLM can also violate it. Use runtime-enforced schemas on every structured output.
📊 Pattern 2: Dependency Graph with Topological Sort
The dependency graph lives in a single source-of-truth file in the builder infrastructure — not duplicated into tenant data:
# patterns/common/dependency-graph.md
offerings_schema → order_schema → pricing_formula
→ scheduling_function
→ validation_function
router, callback, system_prompt (independent, no dependencies)
pricing, scheduling, validation (can run in parallel)
The orchestrator embeds this graph in its system prompt and uses it for cascade ordering:
// Orchestrator prompt excerpt (paraphrased)
const DEPENDENCY_GRAPH = `
The locked skills have a strict dependency order:
1. offerings_schema (no dependencies)
2. order_schema (depends on offerings_schema)
3. pricing_formula (depends on order_schema)
4. scheduling_function (depends on order_schema)
5. validation_function (depends on order_schema)
Steps 3-5 can run in parallel after step 2 completes.
`;
const KNOWN_PATTERN_CASCADE = {
'add-offering': ['offerings_schema', 'order_schema', 'pricing_formula', 'scheduling_function', 'validation_function'],
'remove-offering': ['offerings_schema', 'order_schema', 'pricing_formula', 'scheduling_function', 'validation_function'],
'update-price': ['offerings_schema', 'pricing_formula'],
};
The orchestrator delegates to editing subagents which dispatch code generation tools in cascade order. Each code generation subagent reads the output of its upstream dependencies:
// Code generation subagent prompt (paraphrased)
const pricingFormulaGenerator = {
name: 'sales-assistant-pricing-formula-generator',
tools: [readSkillFile, writeSkillFile, editSkillFile],
prompt: `
You are the pricing formula generator.
You MUST read the offerings_types and order_types before generating.
Your output depends on these upstream files.
`,
};
No LLM decides which file comes next. The graph is deterministic.
💡 Unique Insight: The dependency graph originally lived only in the orchestrator prompt as natural language. After two production incidents where the prompt drifted during iteration (we rewrote the cascade order accidentally), we extracted it into a separate
dependency-graph.mdfile that both the orchestrator prompt and the CI validation job read from the same source. Single source of truth eliminated the drift.
🗂️ Pattern 3: Git-Semantic Knowledge Base Lifecycle
Every edit session treats the knowledge base as a git-like repository:
Staging (edit thread) → Proposed generation → Approved → Promoted to live
Rejected branch (abandoned)
Audit log entry for every transition
The data model uses five collections to track versioned, immutable history:
// collections/business-code-base.collections.ts
interface BusinessCodeBaseStagingAreaDoc {
threadId: string; // one per edit thread
baseGenerationId: ObjectId;
overlay: Record<string, string>; // key → versionId
state: 'open' | 'sealed' | 'abandoned';
}
interface BusinessCodeBaseGenerationDoc {
state: 'draft' | 'proposed' | 'approved' | 'rejected' | 'promoted' | 'rolled_back';
sealedAt?: Date;
approvedAt?: Date;
promotedAt?: Date;
rolledBackAt?: Date;
rejectedAt?: Date;
}
The lifecycle operations map to REST endpoints with atomic semantics:
POST /environments/seed → Initialize next environment
POST /environments/promote → Fast-forward live HEAD to next HEAD
POST /environments/rollback → Move live HEAD backward
GET /environments/diff → Show staged changes vs live
GET /environments/history → Immutable event log
Promotion uses a compare-and-swap pattern to prevent concurrent overwrites:
// ide.service.ts
async promote(tenantId: ObjectId, businessEntityId: ObjectId, generationId: ObjectId) {
// CAS: only promote if live HEAD still points to expected generation
const head = await this.getLiveHead(tenantId, businessEntityId);
if (head.generationId !== expectedGenerationId) {
throw new ConcurrentModificationError();
}
await this.swapGenerationPointer(environment, 'live', generationId);
await this.recordEvent(tenantId, businessEntityId, {
action: 'promote',
generationId,
previousHead: head.generationId,
});
}
No partial update ever reaches production. If any step in the cascade fails, the staging area is abandoned and the generation never reaches proposed state.
💡 Unique Insight: The rollback endpoint uses the same
swapGenerationPointerfunction as promote — just with the generation ID pointing to an older commit. This means promotion and rollback share the same concurrency guarantees. The only difference is the direction of the HEAD pointer. When we added rollback, it was a 30-line change, not a new feature.
🎯 Pattern 4: Known Patterns vs General Cascade
Common operations run on purpose-built workflows — called skills. Each skill is a SKILL.md file with a predefined cascade sequence:
patterns/
add-offering/
SKILL.md ← "Read state, edit reference, cascade through 5 generators, validate"
reference-template.md
remove-offering/
SKILL.md
update-price/
SKILL.md
The orchestrator routes to the appropriate subagent based on intent classification:
const orchestrator = createDeepAgent({
name: 'sales-assistant-skill-builder',
subagents: [
knownPatternEditor, // handles add-offering, remove-offering, update-price
generalCascadeEditor, // handles anything that doesn't match a known pattern
...codeGenSubagents, // 9 code generation agents
],
tools: [readSkillFile],
// Orchestrator NEVER writes files directly — only delegates
});
The known-pattern editor reads the matched SKILL.md and follows instructions precisely. The general cascade editor builds the update sequence from scratch using the dependency graph.
const knownPatternEditor = {
name: 'known-pattern-editor',
prompt: `
You are a known pattern editor. You execute predefined update patterns.
Read SKILL.md, follow the cascade exactly.
You work against a branch — all writes go to the branch's files, not production.
`,
};
const generalCascadeEditor = {
name: 'general-cascade-editor',
prompt: `
You are a general cascade editor. No known pattern matches this update.
Use the dependency graph to determine the cascade order.
You work against a branch — all writes go to the branch's files, not production.
`,
};
The orchestrator never writes a file directly. It reads state, plans changes, and delegates to editing subagents. The subagents use find-and-replace operations — the same pattern a developer uses in an IDE — not custom markdown parsers or fragile string manipulation.
💡 Unique Insight: We started with a single general-purpose editor agent that handled everything. The known-pattern system emerged after observing that 73% of production updates fell into three categories: add offering, remove offering, update price. Building a
SKILL.mdtemplate for each cut update time by 60% and reduced validation failures by 80% because the cascade sequence was hardcoded rather than re-derived each time.
💬 Pattern 5: Conversational Updates
The agent asks before it cascades. A single clarifying question prevents a five-file cascade built on bad assumptions:
User: "Add Thai massage, 200 baht."
Agent: "I can add Thai massage at 200 baht. What category does it go under?
(current categories: massage, facial, body treatment)"
After reading current state, the agent suggests related changes proactively:
Agent: "I see you offer aromatherapy oils. Should I add an oil addon to the Thai massage?
Thai massage typically includes oil application as an option."
After a successful update, the agent surfaces observations:
Agent: "Updated. The scheduling function now covers 12 services,
but your calendar supports 8 concurrent bookings.
Consider expanding capacity during peak hours."
These are not flow interruptions. They are the product. The update session is a conversation — not a command-execute-complete transaction.
The orchestrator prompt enforces this behaviour at the system level:
You are an orchestrator. You READ files, ask clarifying questions,
PLAN changes, and DELEGATE to editing subagents.
You NEVER call write_skill_file or edit_skill_file directly.
You think step by step.
💡 Unique Insight: The “ask before cascade” pattern emerged from a production incident where a user said “remove the pregnancy massage” — and the agent removed it from the catalog, the pricing formula, the scheduling function, and the validation function. The user meant “hide it from the booking page,” not “delete it from the system.” Adding a single clarifying question (“hide or delete?”) eliminated this class of incident entirely.
🧪 Pattern 6: Validation Pipeline Before Commit
Every proposed generation must pass validation before it can reach approved state. The validation pipeline runs four checks:
Schema validation. JSON schemas are checked against draft 2020-12. All cross-references between schemas must resolve.
Coverage verification. The pricing formula must cover every offering in the catalog. If the catalog lists “Thai massage 60min” and “Thai massage 90min,” the pricing formula must handle both.
TypeScript compilation. Generated code compiles in a vm.Context sandbox with a 5-second timeout:
import * as vm from 'vm';
const SANDBOX_TIMEOUT_MS = 5000;
function validateGeneratedCode(code: string, functionName: string): boolean {
const sandbox = {
Date, Math, Array, Object, JSON,
String, Number, Boolean, RegExp,
parseInt, parseFloat, isNaN, isFinite,
};
const context = vm.createContext(sandbox, {
name: 'ValidationSandbox',
codeGeneration: { strings: false, wasm: false },
});
try {
new vm.Script(`"use strict";\n${code}\n${functionName}`, {
filename: `validation-${functionName}.js`
}).runInContext(context, { timeout: SANDBOX_TIMEOUT_MS });
return true;
} catch {
return false;
}
}
Dependency integrity. Every file referenced in the generation must exist. No dangling imports, no orphaned schemas.
If any check fails, the entire generation is rejected — the staging area is abandoned and the diff never reaches production.
async function sealAndValidate(stagingArea: StagingArea): Promise<Generation | null> {
// Seal the staging area into a proposed generation
const generation = await sealStagingAreaIntoGeneration(stagingArea);
// Run validation pipeline
const schemasValid = await validateSchemas(generation);
const coverageValid = await verifyCoverage(generation);
const compilationValid = await compileGeneratedCode(generation);
const integrityValid = await checkDependencies(generation);
if (!schemasValid || !coverageValid || !compilationValid || !integrityValid) {
await updateGenerationState(generation._id, 'rejected');
await abandonStagingArea(stagingArea.threadId);
return null;
}
return generation; // ready for operator approval
}
💡 Unique Insight: The compilation check caught a bug in the first week of production: the pricing formula generator produced JavaScript that referenced
thisinside an arrow function — valid JavaScript but incorrect when executed in the sandbox context. The sandbox compilation fails on syntax errors and reference errors, catching at build time what would otherwise surface as mysterious NaN prices in production.
⚠️ What Didn’t Work
LLM-only cascade management. The first version had a single agent editing all files sequentially. It worked 60% of the time. The other 40% it skipped files, reordered the cascade, or introduced inconsistencies. The dependency graph approach was our second attempt and raised success to ~95%. The remaining 5% needed the validation pipeline as a safety net.
Global service locator for DI. Before layering DI by dependency level, we had a flat registration list. When two services developed a circular dependency, the error surfaced at server startup — but only in production, because tests didn’t register the full graph. Moving to explicit dependency levels made circular dependencies visible during code review.
Allowing the orchestrator to write files. In the first architecture, the orchestrator handled both planning and execution. When a cascade failed mid-way, the orchestrator’s file writes were already committed. Splitting planning (orchestrator) from execution (editing subagents) was the architectural fix that made rollback clean.
📊 Performance Numbers
Measured in production from the QuotyAI skill builder:
| Metric | Value |
|---|---|
| Intent classification | ~800ms |
| Known pattern cascade (5 files) | ~12s |
| General cascade (5 files) | ~25s |
| Validation pipeline | ~3s |
| Sandbox compilation | <100ms |
| Promotion (CAS) | <50ms |
| Rollback (CAS) | <50ms |
| Update success rate (known pattern) | 95% |
| Update success rate (general cascade) | 83% |
| Validation catch rate | 100% of failed cascades |
The 12s for a known pattern cascade includes all 5 LLM calls for code generation, each producing a file. The validation pipeline catches every cascade failure — no inconsistent state has reached production since the pipeline was deployed.
📋 The Stack
| Layer | Technology |
|---|---|
| Agent framework | deepagents |
| Structured output | Gemini + Zod 4.x schemas |
| Sandbox execution | Node.js vm.createContext |
| Storage | MongoDB native driver |
| DI | tsyringe with 8 dependency levels |
| Lifecycle API | Hono + Zod validation |
📋 When to Use This Architecture
Use this approach when:
- Your AI system generates code or structured data that depends on other generated resources
- You need atomic updates across multiple interdependent files
- An operator must review changes before they go live
- You want a clear audit trail of every modification
Skip it when:
- Your content is independent (no dependency chain between resources)
- You don’t need operator approval for changes
- Your LLM-generated output has no downstream consumers
- You can tolerate partial update failures
TL;DR
| Category | Pattern |
|---|---|
| Problem | Business logic updates cascade through 5+ interdependent files |
| Architecture | LLM for intent extraction → deterministic cascade execution |
| Dependency graph | Topological sort: offerings → order → pricing + scheduling + validation |
| Lifecycle | Git-like: staging → propose → approve → promote → rollback |
| Workflows | Known patterns (SKILL.md) for common ops, general cascade for novel ops |
| Conversation | Agent asks clarifying questions before any cascade |
| Validation | Schema check + coverage verification + sandbox compilation + dependency integrity |
| Orchestration | Orchestrator plans + delegates. Never writes files directly. |
Related Reading
- Determinism as Infrastructure — why deterministic execution is the foundation
- The Deterministic Bet — the reasoning behind building reliable AI agents
- Hono + Bun for AI Platforms: 6 Production Patterns — the backend infrastructure these patterns run on
Frequently Asked Questions
How do you safely update AI-generated business logic in production? Use git-like semantics: edits go to a staging branch, not production. An orchestrator performs a topological sort on the dependency graph, delegates code generation per node, then runs validation. If any check fails the update aborts. An operator reviews and approves the diff before promotion to production.
How do you prevent LLMs from corrupting interdependent business logic files? The LLM handles only intent extraction — converting natural language to structured data with a typed Zod schema. Everything after extraction runs on deterministic code: a dependency graph defines cascade order, code generation subagents produce each file, a validation pipeline checks schemas and compiles TypeScript, and the entire update fails atomically if any step fails.
What’s the difference between known patterns and general cascade in AI agent workflows? Known patterns are purpose-built workflows for common operations like adding a service or updating a price. Each has a SKILL.md with a predefined cascade sequence. A general cascade handles operations that don’t match any known pattern, building the update sequence from scratch using the dependency graph.
How do you validate AI-generated code before it reaches production?
A validation pipeline checks JSON schemas against draft 2020-12, resolves all cross-references, verifies the pricing formula covers every offering, and compiles TypeScript in a Node.js vm.createContext sandbox with a 5-second timeout. If any check fails, the entire update aborts atomically.
What happens when a cascade update fails mid-way?
Atomicity is built into the lifecycle. The staging area is sealed into a proposed generation, the validation pipeline runs all checks, and if any check fails the generation is marked rejected and the staging area is abandoned. No partial state reaches production. The operator sees a rejected generation with a full audit trail.
Tags: ai-agents business-logic code-generation typescript multi-agent deterministic-ai
Found this useful? Share it.
Related articles
OpenWiki vs QuotyAI: Two LangChain DeepAgents Projects, Two Architectures
OpenWiki uses LangChain DeepAgents to generate agent documentation for codebases. QuotyAI uses the same createDeepAgent API to generate executable TypeScript business logic. A technical comparison of agent architecture, subagent patterns, and runtime execution.
Read articleHono + Bun for AI Platforms: 6 Production Patterns Worth Stealing
Build an AI platform on Hono + Bun: 100+ services, SSE streaming, sandboxed code execution, real-time WebSocket. 6 production patterns from a real codebase.
Read articleWhy I picked Bun and Hono for QuotyAI’s backend
Build faster backends by ditching NestJS for Bun and Hono - perfect for solo founders building AI products in 2026
Read article