Back to Blog

테스트

Prompt Combo started with one simple idea: "put prompts into a queue and let them finish on their own." It sounded trivial on paper. In practice, it forced three separate architecture rewrites.

At the start, the queue was nothing more than an array plus a for...of loop. Each prompt ran, returned a response, and appended output to a log. That was enough for a first prototype. It stopped being enough as soon as real usage showed up.

First version: sequential execution

The earliest shape looked like this:

[Queue]
Prompt A -> Claude -> Response A
Prompt B -> Claude -> Response B
Prompt C -> Claude -> Response C

It worked, but the first real user request broke the assumption immediately:

"Can this prompt run on GPT-4o while the next one runs on Claude?"

A queue that assumes one model is not really a workflow engine. From that moment on, every queue item needed to carry its own model, parameters, and execution context.

Second version: model-aware routing

The next step was to enrich each queue item:

interface QueueItem {
  id: string
  prompt: string
  model: 'claude' | 'gpt' | 'gemini'
  params: ModelParams
  status: 'pending' | 'running' | 'done' | 'error'
}

Now the runner could inspect item.model and send work to the correct client. That solved model choice, but it surfaced the next constraint almost immediately: concurrency.

The question turned into:

"While Claude is thinking, can GPT work on something else at the same time?"

Technically, yes. Operationally, that means rate-limit awareness, independent failure handling, and a viewer that can make multiple active streams readable at once.

Third version: worker pool plus queue manager

That is where the current design came from. The queue stopped being a single runner and became a scheduler with workers.

------------------------------------------------------------
|                     Queue Manager                        |
|        priority sorting + dependency resolve            |
|-----------|-----------|-----------|-----------|
            |           |           |
         Worker 1    Worker 2    Worker 3
         (Claude)      (GPT)      (Gemini)
            |           |           |
            |------ merged streams -----------|
                          |
                   Log Aggregator

Three ideas made this version hold:

  1. Workers are generic execution units. They are not tied to a single model.
  2. The queue manager decides routing. It combines priority, dependencies, and rate limits before dispatching.
  3. The log aggregator merges streams into one timeline. Without that layer, the terminal viewer becomes noise.

Dependencies change the problem completely

The most valuable addition in this design was explicit dependencies between prompts.

For example:

  • Prompt A: "Analyze this codebase" -> Claude
  • Prompt B: "Write tests based on A" -> GPT
  • Prompt C: "Summarize A and B into docs" -> Claude

B cannot start until A finishes. C cannot start until both A and B finish. At that point, the queue is no longer just a queue. It behaves more like a job scheduler over a DAG.

function getNextExecutable(queue, completed) {
  return queue
    .filter(item => item.status === 'pending')
    .filter(item => item.dependsOn.every(depId => completed.has(depId)))
    .sort((a, b) => a.priority - b.priority)
}

Once you add circular dependency detection, retry rules, and failure propagation, the problem is operational design, not just iteration order.

What this taught us

The most important lesson was not about workers or graphs. It was about the user mental model.

Users think in terms of "a list of prompts." Internally, Prompt Combo may be running a worker pool, dependency resolver, and merged log pipeline. None of that should leak into the surface unless it helps.

The UI should let people drag items, connect dependencies, and understand what is blocked or done. The internal complexity is justified only if the product still feels simpler than doing the work manually.

That is the bar we ended up optimizing for.