Tutorial: Closing the Feedback Loop

On this page

This tutorial shows how to let real usage data adjust routing behavior over time via FeedbackEngine.

1. Enable feedback with a low sample threshold (for demonstration)

Python · 11 lines
from zerda.router import HierarchicalRouter, RouterConfig, FeedbackConfig

config = RouterConfig(
    feedback=FeedbackConfig(
        enabled=True,
        min_samples_to_adapt=5,          # adapt quickly for this demo
        persist_path="demo_feedback.json",
    ),
)
router = HierarchicalRouter(config=config)
# ... register groups/routes as usual ...

2. Simulate routing + outcome feedback

In production, "outcome" typically comes from a user signal (thumbs up/down, a follow-up clarifying question, task completion). For this tutorial, we simulate it:

Python · 9 lines
import random

queries = ["what's the refund policy?"] * 20

for q in queries:
    result = router.route_query(q)
    # Simulate: this route is actually good 90% of the time
    was_useful = random.random() < 0.9
    router.feedback(route_name=result.route_name, success=was_useful, score=0.8)

3. Inspect the learned bias

Python · 4 lines
summary = router.feedback_summary()
print(summary["states"]["docs_qa"])
# {'route_name': 'docs_qa', 'ema': 0.87, 'n_samples': 20, 'n_success': 18,
#  'n_failure': 2, 'success_rate': 0.9, 'bias': 0.222}

The bias value (clipped to [-0.3, +0.3]) is now live on router.get_route("docs_qa").score_bias, and will be added into every future scoring pass for that route — see Architecture → Feedback loop.

4. Persist and restore across restarts

Python · 6 lines
router._feedback.save()  # or use `with HierarchicalRouter(...) as router:` for auto-save

# On next process start, constructing FeedbackEngine with the same persist_path
# automatically restores prior learned biases:
router2 = HierarchicalRouter(config=config)
print(router2.get_route("docs_qa").score_bias)  # reflects the persisted bias, once routes are registered

[!WARNING] Bias only applies once you register the route again Persisted feedback state lives in FeedbackEngine, keyed by route name — it isn't automatically re-applied to a Route object until apply_biases(route_map) runs again (which happens on the next router.feedback(...) call or _post_route hook). If you need biases applied immediately after restoring persisted state and re-registering routes, call router._feedback.apply_biases(router._route_map) explicitly.

Best practices

  • Start with a conservative min_samples_to_adapt (the default is 10) in production — the demo above lowers it only to make the effect visible quickly.
  • Remember FeedbackConfig.score_decay does not control the EMA smoothing rate — see the caveat in FeedbackEngine if you need to tune adaptation speed.
  • Combine automatic feedback (from _post_route's result.success) with manual, higher-quality feedback (router.feedback(...) after a user signal) — both write to the same underlying state.

Next steps

Related pages