DEV Community

Cover image for My supervisor-worker agent pattern fixed the dumbest bug in my automation stack at 1:13 AM
Lars Winstand
Lars Winstand

Posted on Originally published at standardcompute.com

My supervisor-worker agent pattern fixed the dumbest bug in my automation stack at 1:13 AM

I caught the bug because a refund email draft existed before the Stripe eligibility check finished.

Not the wrong tools.

The wrong order.

That was the whole failure mode.

The agent had access to Stripe, Notion, Gmail, and internal customer data. It called the right systems. It even wrote a decent response with GPT-5.

It just did everything in a chaotic sequence:

  • draft the refund email
  • then check billing
  • then validate policy
  • then re-draft
  • then fetch customer history
  • then hit the CRM again for no good reason

I had built it as one giant tool-calling prompt.

That felt elegant for about 48 hours.

After that, it felt like giving one engineer 20 browser tabs, 14 integrations, and no ticket boundaries.

So I stopped trying to prompt my way out of sequencing bugs and rebuilt the flow as a supervisor plus a few workers.

That change fixed more than the refund bug. It made the whole automation stack easier to reason about.

The real problem was architecture, not the model

This is the part people skip when they talk about agent reliability:

A lot of agent failures are orchestration failures, not model failures.

I kept tweaking prompts because I thought I had an LLM tool-use problem.

I didn’t.

I had hidden planning, routing, sequencing, validation, and execution inside a single prompt and then acted surprised when debugging was miserable.

If you’re running automations in n8n, Make, Zapier, OpenClaw, or custom Python code, this is a very normal trap. One giant agent works just long enough to feel smart.

Then you add one more branch:

  • only refund if Stripe says eligible
  • only send if policy validation passes
  • only escalate if account health is below threshold
  • only draft if enrichment is complete

Now the workflow has real state and real prerequisites.

That’s where the one-big-agent pattern starts to crack.

What changed when I split the agent

I moved from one general-purpose agent to this:

  • supervisor
  • enrichment worker
  • validation worker
  • drafting worker
  • execution worker

The supervisor had exactly three jobs:

  1. read the incoming task
  2. decide which worker acts next
  3. enforce prerequisites before the next handoff

The workers were intentionally narrow.

Worker responsibilities

  • Enrichment worker: fetch Stripe, HubSpot, Postgres, or CRM context
  • Validation worker: check refund policy, required fields, confidence thresholds
  • Drafting worker: write the email, note, or response using GPT-5 or Claude Sonnet 4.6
  • Execution worker: send Gmail, update Airtable, post to Slack, or apply the side effect

That “narrow on purpose” part matters.

When a worker fails, I want to know why in one glance.

Why this pattern is better under pressure

Here’s the tradeoff as I’ve seen it in production:

Approach What actually happens under pressure
One giant tool-calling agent Fast to build, but step ordering is fuzzy and routing failures are painful to debug
Supervisor-worker agent pattern Explicit handoffs, better traces, easier to enforce sequencing constraints
Deterministic graph in n8n, Make, or LangGraph Strongest control over order of operations, less flexible than free-form agents

My opinion: if your workflow touches money, customer comms, or any external side effect, the one-big-agent pattern is overrated.

It’s not useless.

It’s just fragile in exactly the ways that hurt most in production.

The simplest useful mental model

I stopped asking:

“Can the agent solve this?”

And started asking:

“Who is allowed to do what, and when?”

That one question cleaned up a lot of bad design.

Because once you frame the problem that way, the architecture gets obvious:

  • routing belongs to the supervisor
  • domain checks belong to validation
  • side effects belong at the end
  • drafting should not happen before prerequisites are satisfied

This sounds boring.

Boring is good.

Boring systems are the ones you can trust at 1:13 AM.

A minimal supervisor-worker sketch in Python

If you’re building your own orchestration layer, the pattern can be very small.

class Supervisor:
    def run(self, task, state):
        if not state.get("enriched"):
            return "enrichment"
        if not state.get("validated"):
            return "validation"
        if not state.get("drafted"):
            return "drafting"
        if state.get("approved_for_execution"):
            return "execution"
        return "stop"
Enter fullscreen mode Exit fullscreen mode

Then your workers stay focused:

def enrichment_worker(task, state):
    stripe_data = get_stripe_customer(task["customer_id"])
    state["stripe"] = stripe_data
    state["enriched"] = True
    return state


def validation_worker(task, state):
    eligible = check_refund_policy(state["stripe"], task)
    state["refund_eligible"] = eligible
    state["validated"] = True
    state["approved_for_execution"] = eligible
    return state


def drafting_worker(task, state, llm):
    if not state.get("refund_eligible"):
        state["draft"] = "Refund not eligible. Draft a policy explanation instead."
    else:
        state["draft"] = llm.generate("Write a refund approval email")
    state["drafted"] = True
    return state
Enter fullscreen mode Exit fullscreen mode

Even this toy version is better than one giant prompt if sequencing matters.

OpenAI’s handoff model makes this much easier to reason about

One thing I like about the OpenAI Agents SDK is that handoffs are explicit.

That matters because explicit handoffs are debuggable.

A pattern like this is much clearer than burying routing logic inside a giant instruction block:

from agents import Agent, handoff

billing_agent = Agent(name="Billing agent")
refund_agent = Agent(name="Refund agent")

triage_agent = Agent(
    name="Triage agent",
    handoffs=[billing_agent, handoff(refund_agent)],
)
Enter fullscreen mode Exit fullscreen mode

The key improvement is not magic autonomy.

It’s visibility.

When a run goes wrong, I want to see:

  • which agent received the task
  • which handoff happened
  • what state existed at that moment
  • why execution was allowed

That is a much better debugging story than “the model seemed confident.”

How this maps to n8n

In n8n, I’d implement this as a controller plus specialized branches.

Something like:

  1. Webhook receives request
  2. Controller node decides next stage
  3. Enrichment branch fetches Stripe/CRM data
  4. Validation branch checks policy and thresholds
  5. Drafting branch generates response
  6. Execution branch sends email or updates system

The useful part is that execution stays locked until validation passes.

That sounds obvious because it is obvious.

Which is exactly why hiding it inside a prompt was such a mistake.

How this maps to Make

Make already nudges you toward routers, filters, and modules, so this pattern feels natural there.

I’d use the LLM for:

  • classification
  • routing hints
  • drafting
  • summarization

I would not use one LLM call to own the entire workflow lifecycle if the workflow has hard sequencing requirements.

That’s the distinction that matters.

Use the model where judgment helps.

Use the workflow engine where order matters.

When you should not use multi-agent architecture

Sometimes a single agent is enough.

I’d keep it simple when:

  • there are only a few integrations
  • order is flexible
  • failures are cheap
  • auditability doesn’t matter much

But once the workflow can trigger external side effects, I get less sentimental.

If the system can send money-related messages, update records, or trigger downstream automations, I want stronger control.

That usually means either:

  • a supervisor-worker setup
  • a deterministic graph
  • or both

LangGraph is often the right middle ground

If your workflow has fixed stages but still needs LLM judgment inside those stages, LangGraph is a strong fit.

Install looks like this:

pip install langchain_core langchain-anthropic langgraph
Enter fullscreen mode Exit fullscreen mode

Basic model setup:

from langchain_anthropic import ChatAnthropic

llm = ChatAnthropic(model="claude-sonnet-4-6")
Enter fullscreen mode Exit fullscreen mode

Then split the flow into stages you can verify independently:

  • extraction
  • validation
  • decision
  • drafting
  • execution

That structure is less glamorous than “one autonomous agent runs support.”

It is also much easier to operate.

The cost problem nobody likes admitting

Supervisor-worker systems usually increase LLM calls.

You add:

  • routing
  • worker calls
  • validation passes
  • retries
  • traces
  • maybe a second draft

That is usually the right engineering choice.

It is also exactly where per-token pricing gets annoying.

If you’re running customer support triage, lead qualification, or back-office automations 24/7, better architecture often means more inference overhead.

That creates a dumb incentive: ship the cheaper architecture instead of the safer one.

This is one reason flat-cost API access is appealing for agentic systems.

If you’re using a drop-in OpenAI-compatible endpoint like Standard Compute, you can afford to test supervisor routing, worker retries, validation loops, and traces without turning every architecture decision into a billing debate.

That matters more than people admit.

Safer automations usually do more work.

You want pricing that doesn’t punish that.

The rule I use now

Here’s the rule that came out of this bug:

If an agent can cause an external side effect, it should almost never decide both what to do and when to do it inside the same giant prompt.

That single rule has saved me a lot of time.

So my default is now:

  • use one agent for small, low-risk tasks
  • use a supervisor-worker pattern when routing needs judgment but order still matters
  • use a deterministic graph when order matters more than judgment

That’s the whole playbook.

Not fancy.

Just practical.

Actionable takeaway

If your current agent stack feels smart in demos and chaotic in production, try this refactor:

  1. list every external side effect
  2. move those actions to the end of the workflow
  3. split enrichment, validation, drafting, and execution into separate stages
  4. add a supervisor that controls handoffs
  5. trace every handoff explicitly

You do not need more prompt cleverness first.

You probably need clearer orchestration.

That was true for my refund workflow.

And once I saw it, the original design stopped looking autonomous and started looking like gambling.

Top comments (1)

Collapse
 
jo-do profile image
Jo Do

Draft before check is the classic failure, and it's worse than a wrong answer because the artifact exists. Once the email draft is written, every later step is negotiating with a commitment the agent already made. Ordering is underrated as a control mechanism: make the eligibility check a precondition for the draft tool being callable at all, and the dumb bug becomes structurally impossible instead of merely unlikely. A supervisor catches what a prompt only discourages.