Skip to content
Letta Platform Letta Platform Letta Docs
Sign up
Tutorials
Multi-agent patterns

The producer-reviewer pattern

Iteratively refine content using a producer agent that creates and a reviewer agent that evaluates with feedback loops.

A producer agent creates content while a reviewer agent evaluates it. The reviewer accepts or rejects the content. If rejected, the reviewer provides feedback, and the producer revises based on that feedback. This loop continues until the reviewer approves or a maximum iteration limit is reached.

The producer-reviewer pattern works well when you need to:

  • Enforce approval workflows before content is finalized
  • Separate creation from evaluation responsibilities
  • Iteratively refine outputs based on expert feedback
  • Create content that meets specific quality standards

A marketing team needs social media posts that match brand guidelines. A producer agent drafts posts, while a reviewer agent evaluates them against tone, length, and messaging requirements. The reviewer rejects drafts that don’t meet its standards and provides specific feedback. The producer revises based on this feedback until the reviewer approves or the maximum revision count is reached.

The pattern uses four key components:

  1. The producer agent creates content based on requirements and feedback
  2. A shared memory block stores the current draft for review
  3. The reviewer agent evaluates the content against criteria, then accepts or rejects it
  4. Client orchestration manages the feedback loop and iteration count

Create a memory block where the producer stores drafts and the reviewer reads them:

from letta_client import Letta
import os
client = Letta(api_key=os.getenv("LETTA_API_KEY"))
# Shared memory block for current draft
draft_block = client.blocks.create(
label="current_draft",
description="The current content draft under review",
value="" # Producer will update this
)

Create the producer with access to the shared draft block:

producer = client.agents.create(
name="content_producer",
model="anthropic/claude-sonnet-4-5-20250929",
memory_blocks=[{
"label": "persona",
"value": "I create marketing content. I store my drafts in the current_draft memory block and revise based on feedback."
}],
block_ids=[draft_block.id],
tools=["core_memory_replace"]
)

The producer needs core_memory_replace to update the draft block with each revision.

Create the reviewer with evaluation criteria:

reviewer = client.agents.create(
name="content_reviewer",
model="anthropic/claude-sonnet-4-5-20250929",
memory_blocks=[{
"label": "persona",
"value": "I review marketing content against brand guidelines. I evaluate tone, length (max 280 characters), and messaging. I respond with ACCEPT or REJECT followed by specific feedback."
}],
block_ids=[draft_block.id]
)

The reviewer reads the shared draft block and provides a verdict (ACCEPT or REJECT) with feedback.

Orchestrate the iterative refinement process:

# Configuration
max_iterations = 5
request = "Write a social media post announcing our new AI agent framework launch."
# Initial production
response = client.agents.messages.create(
agent_id=producer.id,
messages=[{
"role": "user",
"content": f"{request} Store your draft in the current_draft memory block."
}]
)
# Feedback loop
for iteration in range(max_iterations):
print(f"\n--- Iteration {iteration + 1}/{max_iterations} ---")
# Get current draft from shared memory
draft_block_updated = client.blocks.retrieve(draft_block.id)
current_draft = draft_block_updated.value
# Reviewer evaluates the draft
review_response = client.agents.messages.create(
agent_id=reviewer.id,
messages=[{
"role": "user",
"content": f"Review this draft from current_draft memory:\n\n{current_draft}\n\nRespond with ACCEPT or REJECT followed by your feedback."
}]
)
# Extract reviewer's verdict
reviewer_message = review_response.messages[-1].content
# Check if approved
if "ACCEPT" in reviewer_message.upper():
print("Draft approved!")
break
# Extract feedback and send to producer for revision
response = client.agents.messages.create(
agent_id=producer.id,
messages=[{
"role": "user",
"content": f"The reviewer provided this feedback:\n\n{reviewer_message}\n\nPlease revise your draft and update the current_draft memory block."
}]
)
# Get final approved draft
final_draft_block = client.blocks.retrieve(draft_block.id)
final_draft = final_draft_block.value
print(f"\nFinal draft:\n{final_draft}")
from letta_client import Letta
import os
client = Letta(api_key=os.getenv("LETTA_API_KEY"))
# Create shared draft block
draft_block = client.blocks.create(
label="current_draft",
value=""
)
# Create producer
producer = client.agents.create(
model="anthropic/claude-sonnet-4-5-20250929",
memory_blocks=[{
"label": "persona",
"value": "I write content and store drafts in current_draft block."
}],
block_ids=[draft_block.id],
tools=["core_memory_replace"]
)
# Create reviewer
reviewer = client.agents.create(
model="anthropic/claude-sonnet-4-5-20250929",
memory_blocks=[{
"label": "persona",
"value": "I review content. Max 50 characters. Respond with ACCEPT or REJECT plus feedback."
}],
block_ids=[draft_block.id]
)
# Initial production
client.agents.messages.create(
agent_id=producer.id,
messages=[{
"role": "user",
"content": "Write a product tagline. Store in current_draft block."
}]
)
# Feedback loop
max_iterations = 3
for iteration in range(max_iterations):
# Get draft
draft_block_updated = client.blocks.retrieve(draft_block.id)
current_draft = draft_block_updated.value
# Review
review_response = client.agents.messages.create(
agent_id=reviewer.id,
messages=[{
"role": "user",
"content": f"Review: {current_draft}"
}]
)
verdict = review_response.messages[-1].content
if "ACCEPT" in verdict.upper():
print(f"Approved: {current_draft}")
break
# Revise
client.agents.messages.create(
agent_id=producer.id,
messages=[{
"role": "user",
"content": f"Feedback: {verdict}\n\nRevise and update current_draft."
}]
)
# Cleanup
client.agents.delete(producer.id)
client.agents.delete(reviewer.id)
client.blocks.delete(draft_block.id)
  • Use explicit verdict format: Train the reviewer to respond with clear keywords (ACCEPT/REJECT) for reliable parsing
  • Set appropriate max iterations: 3-5 iterations work well for most content creation tasks. Balance quality refinement with cost.
  • Store criteria in reviewer persona: Put specific evaluation rules (character limits, tone requirements, brand guidelines) in the reviewer’s persona for consistent evaluation
  • Provide actionable feedback: The reviewer should give specific, actionable feedback. Vague feedback (“this could be better”) leads to unproductive iterations.
  • Monitor iteration patterns: Track how many iterations typically occur. If most content requires the maximum, the criteria may be too strict.
  • Consider archival memory for history: For complex projects, attach a shared archive where both agents can store draft history and feedback records