Skip to content
Forward Deployed

Forward Deployed
Lessons and contents
Multi-Agent ArchitecturesLesson 4 of 7
7 lessons
Learn

Supervisor workflow with human review

Source
From the RAG & Agents Engineering course
By
Sourav Banerjee
Last updated
9 Nov 2026

Report an issue

Human review in a supervisor loop

What you'll learn

  • Explain a supervisor loop that grades a draft and routes it to the first failing check.
  • Separate checks code can do exactly from checks that need a model's judgement.
  • Trace a draft through the loop, the pass cap and the single place the approved output is written.
  • Choose between a static pause on every run and a dynamic pause that only some runs take.

Lesson 2 of T11.1 sketched a supervisor with hard-coded routing. This lesson gives it real specialists, a grader and a person at the end.

Larkspur's billing agent drafts refund replies. An annual plan gets a full refund within 30 days of purchase if the account used less than 100 GB of outbound traffic. After day 30 the refund is prorated, minus a 15% early-termination fee. A reply that promises a full refund on day 45 commits Larkspur to money the handbook does not allow. Someone has to read those replies before the customer does. The pattern needs three parts: a loop that keeps fixing a draft until it meets its rules, a cap on that loop, and a pause for review.

The notebook builds the same pattern on news articles. A short transfer rumour ("Lionel Messi will join Real Madrid in 2025") has to become a publishable article. A supervisor, the "news chef", grades the article and routes it to the specialist that can fix what is missing. A person reads the result before it is published.

Grading in code and in a model

An article passes when four checks pass. The notebook splits them by who can check each one reliably:

Check Checked by Specialist that fixes it
About football transfers model, structured output none: an off-topic article ends the run
Mentions the market value model, structured output market_value_researcher (a one-tool agent over a small lookup table)
Mentions the current club model, structured output current_club_researcher (the same shape)
At least 100 words code, by counting words word_count_rewriter (asked for at least 120 words)

Table 1: The four publication checks. Only the word count never reaches a model.

Grade in code what code can check. Topic, club and value need judgement. The word count is exact, and a model asked to count words is a second source of error. Each researcher's first model call is also forced to call its tool and cannot skip the lookup to answer from a guess.

After every specialist, control returns to the news chef, which grades the updated article again:

Diagram: Rumour; News chef grades: first failing check?; market_value_ researcher; current_club_ researcher; word_count_ rewriter; final_article set run pauses; Human reviews, may edit; PublishDiagram: Rumour; News chef grades: first failing check?; market_value_ researcher; current_club_ researcher; word_count_ rewriter; final_article set run pauses; Human reviews, may edit; Publish

Figure 1: The supervisor loop and the review step. Every specialist reports back to the news chef.

The chef routes to the first failing check, in a fixed order: market value, then club, then length. An off-topic article ends the run at once.

Tracing a rumour through the loop

We ran the notebook's workflow graph with the model-backed pieces replaced by stubs: a grader that checks for "join", "€" and "Inter Miami", researchers that return the lookup-table values, and a rewriter that pads the text past 100 words. The routing, the word count and the cap are the notebook's own code:

Pass Checks failing Routed to
1 value, club, length market_value_researcher (adds €50 million)
2 club, length current_club_researcher (adds Inter Miami)
3 length word_count_rewriter
4 none end; final_article set (120 words)

Table 2: The rumour through the loop (stubbed grader and specialists, notebook routing).

Two more inputs complete the picture. "Today in Munich will be 9°C" fails the topic check on pass 1 and ends with no article. A 109-word article that already names the club and the value passes on pass 1, and no specialist runs.

Two guards keep the loop safe to run unattended up to the human step:

  • A pass cap. MAX_PASSES = 5. A live grader sometimes marks a check "no" twice, and the same specialist runs again. A grader that never says "yes" cannot keep the loop going past five passes.
  • One place for the output. Only a full pass copies the article into final_article. The published text is always the version that passed, whether or not any specialist ran. The 109-word article reaches publication unchanged for that reason.

The planted failure here is the stale output: a variable set before the last specialist ran, published after it. Writing the output in exactly one place, on a full pass, rules it out.

Pausing before publication

The workflow becomes one node of an outer graph, followed by a node that publishes. Compiling the outer graph with a checkpointer and interrupt_after=["newsagent_node"] pauses every run after the article is written:

DiagramDiagram

Figure 2: A static pause. The reviewer works on the saved state, and the run resumes from the pause.

The notebook runs three threads to the pause and resumes each one, which we reproduced with the stubbed workflow:

Thread Answer at the pause Reviewer action Published
weather "Article not relevant for news agency" resume as is the same text
messi the four-pass article update_state sets "Fake news!!!" "Fake news!!!"
clean the 109-word article resume as is the article, word for word

Table 3: Three paused threads (notebook review graph, stubbed workflow).

The edited answer reaches the publishing call, not the model's. The checkpointer holds each thread's state by thread_id, and the reviewer can take the threads in any order.

Static or dynamic review

interrupt_after is static: fixed at compile time, it pauses every run at the named node. A dynamic pause calls interrupt(payload) inside a node, which pauses only when the node decides to and shows the reviewer the payload. The run resumes with Command(resume=...).

Static interrupt_after Dynamic interrupt(payload)
Decided at compile time at run time, inside the node
Pauses every run at that node only the runs the node chooses
Reviewer sees the saved state the payload the node sends
Resume invoke(None) after any update_state Command(resume=...)

Table 4: Two ways to put a person in the loop.

Larkspur needs the dynamic kind. A refund inside the 30-day window can go straight to the customer, and one outside it pauses for a person. The check that decides to pause compares the purchase date with today in code, for the same reason the word count does. A static pause would stop every refund reply, including the ones the handbook already allows.

Run the notebooks

The code for this lesson is in these notebooks. Colab runs one in your browser: install the packages its first cell imports and set the API keys it reads. To run every notebook as written, clone the repository and use its uv environment.

Notebook GitHub Colab
Supervisor workflow with human review (optional) Open on GitHub Open in Colab

Key Takeaways

  • Grade in code whatever code can check exactly, such as word count, and use a model with structured output only for checks that need judgement. Route to the first failing check and cap the passes.
  • Write the published output in one place, set only on a full pass, so the text that ships is always the version that passed.
  • A static interrupt_after pauses every run at a node, while a dynamic interrupt(payload) pauses only when the node decides. Use the dynamic kind for a refund outside the 30-day window, and compare the dates in code.

Check yourself

1. Why does the news chef use a model to judge topic, club and market value, but leave the 100-word check to code?

Topic, club and market value need judgement, so a model answers them with structured output. The word count is exact. Code computes it and the model is never asked. A check that code can do exactly should never depend on a model's answer.

2. An article names the player's current club but has no market value and only 80 words. Which specialists run, and in what order?

The news chef routes to the first failing check, in the order market value, club, length. market_value_researcher runs first. Control returns to the chef, which grades again. The club passes, and word_count_rewriter runs next. MAX_PASSES = 5 caps the loop if a check keeps failing.

3. Larkspur wants a person to review only refunds proposed outside the 30-day window. Should it use a static interrupt_after or a dynamic interrupt?

A dynamic interrupt(payload) inside the node. A static interrupt_after is fixed at compile time and pauses every run, while the dynamic one pauses only when the node decides to, and the run resumes with Command(resume=...). The check that decides to pause compares the dates in code.

Rate your confidence and keep notes on the module page.