Critic agent should optionally read the style guide to avoid degrading aesthetics across rounds

Author: camwolfordCreated Apr 27, 2026Updated Apr 27, 2026

Observation

When running the demo_full (or demo_planner_critic) pipeline with multiple Critic rounds, the first-round image is consistently the most visually coherent, and successive Critic rounds progressively degrade the aesthetic — re-rendered images become more cluttered, lose colour discipline, and drift away from the Stylist's choices.

Setting --max_critic_rounds=0 (or 1) and skipping the Critic gives the best output for many use cases. This suggests the issue isn't the Critic per se but a missing input to it.

Diagnosis

The Stylist agent (agents/stylist_agent.py) and Polish agent (agents/polish_agent.py) both load style_guides/{prefix}_{task}_style_guide.md and feed it to their model calls. The Critic agent (agents/critic_agent.py) does not — it only sees the raw content + caption + image and is prompted to identify fidelity gaps ("which named components are missing or wrong in the rendered figure?").

This means:

  1. The Stylist makes deliberate aesthetic choices guided by the style guide (palette, typography, shape language, whitespace, illustration register).
  2. The Critic, blind to those choices, flags every named-but-not-prominent component as a fidelity violation and asks the Visualizer to add it back.
  3. The Visualizer's revision is applied destructively and erodes the aesthetic the Stylist set up.
  4. After 2–3 rounds the figure is "more faithful" by the Critic's metric but visibly worse by the Stylist's.

Reproducible test

  1. Generate a figure in demo_full mode with max_critic_rounds=3.
  2. Inspect the per-round images saved to disk: target_{task}_stylist_desc0_base64_jpg (round 0, post-Stylist) vs. target_{task}_critic_desc0_base64_jpg, target_{task}_critic_desc1_base64_jpg, etc.
  3. The post-Stylist image is typically cleaner and more on-brand than the post-Critic ones, especially for icon-rich diagrams or editorial-style illustrations where label density should be intentionally low.

Proposed fix

Have the Critic load the same style guide the Stylist did and prepend it to the system prompt. The Critic's job description shifts from "find fidelity gaps" to "find fidelity gaps that don't break the style guide" — its suggestions reinforce the Stylist's choices instead of fighting them.

Concrete sketch (mirrors the existing pattern in agents/stylist_agent.py:62):

python
# agents/critic_agent.py
import os
...
class CriticAgent(BaseAgent):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.model_name = self.exp_config.main_model_name

        style_prefix = os.environ.get("PAPERBANANA_STYLE_GUIDE_PREFIX", "neurips2025")
        task = "plot" if self.exp_config.task_name == "plot" else "diagram"
        guide_path = self.exp_config.work_dir / f"style_guides/{style_prefix}_{task}_style_guide.md"
        try:
            with open(guide_path, "r", encoding="utf-8") as f:
                self.style_guide = f.read()
        except FileNotFoundError:
            self.style_guide = ""
        ...

Then include self.style_guide in the user prompt the Critic sends to the model, with instructions like:

Before suggesting changes, verify each suggestion would not violate the style guide below. If a fidelity gap can only be closed by violating the style guide (e.g., by adding more text labels than the guide allows), prefer to leave the figure as-is.

Why this is worth doing upstream

  • It's a small, contained change (one agent, ~30 lines) that doesn't break the existing API.
  • The Critic-vs-Stylist conflict is structural — anyone using demo_full with non-default style guides will hit it.
  • It removes a class of "the Critic made my figure worse" complaints that currently can only be worked around by lowering max_critic_rounds to 0–1.
  • Optional: gate it behind a flag (--critic-uses-style-guide) for backwards compatibility.

Adjacent observations

While digging into this, two other small things in the same area:

  1. The Streamlit UI in demo.py has min_value=1 on the Max Critic Rounds input, which prevents users from setting it to 0 even though the pipeline supports it (for round_idx in range(max_rounds) with max_rounds=0 is a clean no-op). Lowering the min would give users an immediate workaround.
  2. The Critic loop has no quality regression check — it only rolls back if the Visualizer fails to return an image (utils/paperviz_processor.py:88-97). It assumes monotonic improvement across rounds, which the above issue undermines. A "did the new image actually score better than the previous?" gate (using e.g. CLIP similarity to the Stylist's first render or a separate VLM eval) would let the loop self-correct.

Happy to send a PR for the style guide change if helpful — let me know if the maintainers would prefer a particular approach (always-on vs. flag-gated, prompt wording, etc.) before I start.