schedule 11 min read

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.

translate
Available in:

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 withStructuredOutput with 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.md file that both the orchestrator prompt and the CI validation job read from the same source. Single source of truth eliminated the drift.

Dependency graph diagram showing business logic files organized as a directed acyclic graph: offerings_schema is the root, order_schema depends on it, then pricing_formula, scheduling_function, and validation_function all depend on order_schema and can run in parallel

🗂️ 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 swapGenerationPointer function 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.md template 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 this inside 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.

Architecture diagram showing the orchestrator deepagent routing intent classification (query vs update, known pattern vs general) to editing subagents, which dispatch to 9 code generation subagents in cascade order, followed by a 4-stage validation pipeline before operator approval

⚠️ 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.


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

Thanks for reading!
Read more articles