How to Achieve Parallel Scene Content Generation in OpenMAIC: A Complete Technical Guide
OpenMAIC enables parallel scene content generation by dispatching independent async generation jobs for each scene through a three-layer architecture—UI triggers, per-scene Zustand state tracking, and server-side concurrent job queues—allowing multiple LLM/image model calls to execute simultaneously while streaming results back to the client.
OpenMAIC is an open-source framework for multi-modal AI content creation that handles resource-intensive generation tasks across multiple scenes concurrently. This article explains how to implement parallel scene content generation by leveraging the repository's fire-and-forget pipeline, which decouples UI requests from server-side execution to maximize throughput.
Understanding the Three-Layer Architecture
OpenMAIC separates concerns into three distinct layers to handle concurrency safely and efficiently:
- UI Trigger Layer: Components like
components/generation/interactive-mode-button.tsxinitiate generation without blocking the main thread. - Client Store Layer: The Zustand store in
lib/store/media-generation.tsmaintains isolated state for everysceneIdusingMediaGenerationTaskobjects. - Server Worker Layer: The
lib/server/agent-runtime/generation-tools.tsfile defines therunGenerationfunction that executes inside a configurable job queue, processing many requests simultaneously up toMAX_CONCURRENT_JOBS.
Dispatching Fire-and-Forget Requests from the UI
To initiate parallel generation, the UI maps over a list of scene IDs and invokes the generation function for each without awaiting individual results. This pattern prevents serialization bottlenecks.
When a user clicks the generate button, the component fires off all requests immediately:
// components/generation/interactive-mode-button.tsx
import { generateScene } from '@/lib/server/agent-runtime/generation-tools';
const startParallel = async (sceneIds: string[]) => {
// Create an array of promises, one per scene
const promises = sceneIds.map(id => generateScene(id));
// Optionally await all completions for a final "complete" signal
await Promise.all(promises);
};
Each call to generateScene returns immediately after registering the task, allowing the loop to proceed to the next scene without waiting for the model to finish.
Tracking Per-Scene State with Zustand
The client-side store isolates each generation task by indexing state with sceneId. This prevents collisions when multiple scenes update concurrently.
In lib/store/media-generation.ts, the store creates a MediaGenerationTask for every request:
// lib/store/media-generation.ts
import { create } from 'zustand';
type MediaGenerationTask = {
token: string;
status: 'open' | 'complete' | 'error';
prompt: string;
};
type MediaGenState = {
tasks: Record<string, MediaGenerationTask>;
startGeneration: (opts: { sceneId: string; prompt: string }) => void;
};
export const useMediaGenerationStore = create<MediaGenState>(set => ({
tasks: {},
startGeneration: ({ sceneId, prompt }) => {
const token = crypto.randomUUID();
set(state => ({
tasks: {
...state.tasks,
[sceneId]: { token, status: 'open', prompt }
},
}));
// Dispatch to server without awaiting
api.runGeneration({ sceneId, token, prompt });
},
}));
Because the tasks object uses sceneId as the key, updates to one scene never overwrite another, enabling safe parallel state mutations.
Executing Concurrent Jobs on the Server
The server processes generation requests through an async job queue that respects concurrency limits. Each runGeneration call runs in its own execution context, allowing the event loop to handle multiple model inferences simultaneously.
According to the OpenMAIC source code in lib/server/agent-runtime/generation-tools.ts, the server implementation looks like this:
// lib/server/agent-runtime/generation-tools.ts
import { MAX_CONCURRENT_JOBS } from '@/configs/concurrency';
export async function runGeneration({
sceneId,
token,
prompt,
}: { sceneId: string; token: string; prompt: string }) {
// The job queue manages concurrency automatically
await jobQueue.add(async () => {
const result = await model.generate(prompt);
// Stream partial or final results back to client
pushUpdate(sceneId, { token, content: result });
});
}
The MAX_CONCURRENT_JOBS configuration variable controls how many of these async jobs run at once. Once a job completes, the queue automatically pulls the next waiting task, maintaining optimal throughput without overwhelming the GPU or API rate limits.
Streaming Results to the Stage Store
As the server generates content, it pushes updates via WebSocket or Server-Sent Events (SSE). The client listens for these updates and patches the corresponding scene in the stage store.
The media generation store listens for generation:update events and routes them to the correct scene:
// lib/store/media-generation.ts
socket.on('generation:update', ({ sceneId, delta }) => {
useStageStore.getState().updateSceneContent(sceneId, delta);
});
The stage store (lib/store/stage.ts) holds the canonical scene data. Because updates are keyed by sceneId, the UI components—such as those in components/stage/scene-renderer.tsx—re-render only when their specific scene content changes, ensuring efficient DOM updates across many parallel generations.
Complete Implementation Example
Here is a minimal end-to-end implementation demonstrating parallel generation for a batch of scenes:
// components/generation/batch-generator.tsx
import { generateScene } from '@/lib/server/agent-runtime/generation-tools';
import { useMediaGenerationStore } from '@/lib/store/media-generation';
import { Button } from '@/components/ui/button';
export const BatchGenerator = ({ sceneIds }: { sceneIds: string[] }) => {
const startBatch = async () => {
// Fire all generation requests simultaneously
const jobs = sceneIds.map(id =>
generateScene(id)
);
// Wait for all to settle (optional, for "Export" or "Next" gating)
await Promise.all(jobs);
};
return <Button onClick={startBatch}>Generate All Scenes</Button>;
};
This button triggers parallel execution, while the underlying stores and server queue handle the complexity of concurrency management and state isolation.
Summary
- Parallel dispatch is achieved by mapping scene IDs to generation promises without sequential awaiting, as implemented in
components/generation/interactive-mode-button.tsx. - State isolation relies on Zustand stores keyed by
sceneIdinlib/store/media-generation.ts, usingMediaGenerationTaskobjects to track individual progress. - Server concurrency is managed through an async job queue in
lib/server/agent-runtime/generation-tools.ts, respecting theMAX_CONCURRENT_JOBSlimit while running multiplerunGenerationcalls simultaneously. - Live synchronization occurs via WebSocket updates that patch the stage store (
lib/store/stage.ts), ensuring each scene updates independently without UI blocking.
Frequently Asked Questions
How does OpenMAIC prevent race conditions when updating scene content?
OpenMAIC prevents race conditions by indexing all generation state and scene content by sceneId. The Zustand store in lib/store/media-generation.ts uses a dictionary structure where each scene has its own MediaGenerationTask entry, and the stage store applies updates only to the matching sceneId. This isolation ensures that concurrent updates for different scenes never corrupt each other's data.
What configuration controls the maximum number of concurrent generations?
The MAX_CONCURRENT_JOBS variable in the concurrency configuration file controls server-side parallelism. This setting defines how many runGeneration jobs can execute simultaneously in the job queue defined in lib/server/agent-runtime/generation-tools.ts. Adjusting this value allows you to optimize for your hardware capacity or API rate limits.
How does the UI know when all parallel generations are complete?
The UI can track completion by awaiting Promise.all on the array of generation promises returned by generateScene, as shown in components/generation/interactive-mode-button.tsx. Alternatively, components can subscribe to the useMediaGenerationStore and check that all relevant sceneId entries have a status of complete or error before enabling downstream actions like navigation or export.
Can different scene types be generated in parallel?
Yes, the architecture supports heterogeneous scene types because each generation job is context-independent. Whether generating slides, images, or video segments, the runGeneration function in lib/server/agent-runtime/generation-tools.ts executes each request in its own async context with a unique token. The components/stage/scene-renderer.tsx component then dispatches to the appropriate renderer based on the scene type once content arrives.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →