Skip to Content

Reflexion

from loomflow.architecture import Reflexion

Reflexion retries a task and learns from each failed attempt. After each attempt an evaluator scores the output; if the score is below the threshold, a reflector writes a one-sentence “lesson” that the agent reads on its next attempt. Use it when you can score results reliably (tests pass, a schema validates, an answer matches ground truth) and retries are cheaper than wrong answers. Based on Shinn et al. 2023, Reflexion: Language Agents with Verbal Reinforcement Learning.

For the conceptual page see Reflexion.


Class signature

class Reflexion: name: str = "reflexion" def __init__( self, *, base: Architecture | None = None, max_attempts: int = 3, threshold: float = 0.8, evaluator_prompt: str | None = None, reflector_prompt: str | None = None, lessons_block_name: str = "reflexion_lessons", lesson_store: VectorStore | None = None, top_k_lessons: int = 5, evaluator_model: str | Model | None = None, reflector_model: str | Model | None = None, max_lessons: int = 10, ) -> None: ...

Constructor parameters

base

TypeArchitecture | None
DefaultNone (resolves to ReAct())

The architecture used for each attempt. Defaults to ReAct. Wrapping a Supervisor, Reflexion(base=Supervisor(workers=...)), gives a team that learns better delegation across attempts. See Recursive composition.

max_attempts

Typeint
Default3

Maximum attempts before giving up. Each attempt runs the base architecture; below-threshold attempts trigger a lesson. Must be >= 1.

threshold

Typefloat
Default0.8

Evaluator score (in [0.0, 1.0]) at or above which the loop terminates. Lower for permissive evaluation, higher for strict.

evaluator_prompt

Typestr | None
DefaultNone (uses built-in default)

Override the evaluator’s system prompt. The default asks for a score: <0..1> line plus a brief justification. Provide your own when you have a domain-specific rubric (test-pass rate, factual accuracy against ground truth, etc.).

The reply is read with parse_score, which understands scales: score: 7/10 is 0.7, score: 40% is 0.4, and a bare score: 7 is read as out of 10. A reply with no score counts as 0.0, a failed attempt.

reflector_prompt

Typestr | None
DefaultNone (uses built-in default)

Override the reflector’s system prompt. The default asks for a single-sentence lesson the agent can apply on its next attempt. The reflector is shown the task, the attempt’s actions and observations (its tool calls and their results, most recent last) and its output. Lessons are kept short on purpose: long lessons use up the context that makes Reflexion cheaper than a plain retry.

lessons_block_name

Typestr
Default"reflexion_lessons"

Name of the working-memory block lessons are written to in monotonic block mode (when no lesson_store is wired), one per line, in the run’s own user’s memory. The agent’s seed messages include this block on every later attempt.

lesson_store

TypeVectorStore | None
DefaultNone

When provided, switches Reflexion to selective recall mode: lessons are stored as embedded chunks in the vector store, and before each attempt the top-k most relevant lessons for the current prompt are retrieved and surfaced. This avoids context bloat as lessons accumulate.

from loomflow import HashEmbedder from loomflow.architecture import Reflexion from loomflow.vectorstore import InMemoryVectorStore architecture = Reflexion( lesson_store=InMemoryVectorStore(embedder=HashEmbedder()), top_k_lessons=5, )

For cross-process learning, swap InMemoryVectorStore for PostgresVectorStore or any other persistent backend.

Each lesson is stored with the run’s user_id in its metadata ("" for anonymous runs), and a run recalls only its own user’s lessons plus any tagged user_id="*" (SHARED_LESSON). Lessons quote the failing prompt, so they stay with their user. Seed curated lessons for everyone with that tag:

from loomflow import HashEmbedder from loomflow.architecture import Reflexion from loomflow.architecture.reflexion import SHARED_LESSON from loomflow.loader import Chunk from loomflow.vectorstore import InMemoryVectorStore store = InMemoryVectorStore(embedder=HashEmbedder()) await store.add([ Chunk( content="Normalize dates to ISO 8601 before answering.", metadata={"user_id": SHARED_LESSON}, ), ]) architecture = Reflexion(lesson_store=store)

Lessons without a user_id tag, as written before 0.17, are no longer recalled.

top_k_lessons

Typeint
Default5

Number of relevant lessons surfaced per attempt when lesson_store is wired. Must be >= 1. Ignored in monotonic-block mode (see max_lessons).

max_lessons

Typeint
Default10

How many lessons the monotonic block keeps; the oldest are dropped first so the prompt doesn’t grow without bound. Must be >= 1. Ignored in selective-recall mode.

evaluator_model / reflector_model

Typestr | Model | None
DefaultNone (use the agent’s model)

Use a different model for scoring or for writing lessons, for example a cheap model for the evaluator. Accepts a model string or a Model instance; role models are not wrapped with the agent’s retry policy. A model string is resolved with the agent’s secrets= backend, like the main model.


Methods

declared_workers

Returns {}. Workers of the base architecture are reported by the base’s own declared_workers (for Reflexion(base=Supervisor(...)), ask the supervisor).

run

For each attempt:

  1. Check the budget (once per attempt), then emit reflexion.attempt_started.
  2. Selective recall (when lesson_store is wired): query for lessons relevant to THIS prompt, from this user or shared, and write the top-k into the working memory block. Emit reflexion.lessons_recalled.
  3. Reset session messages so the base rebuilds its starting context, which picks up lessons from memory.working().
  4. Run base.run(session, deps, prompt), forwarding all events.
  5. If the base ran out of turns (its max_turns) and attempts remain, the attempt counts as a failed trial: score 0, no evaluator call, reflect and retry. Any other interruption (a budget, a guardrail), or running out of turns on the last attempt, stops.
  6. Evaluate. One model call (reflexion.evaluated). An attempt that ran out of turns skips the call and scores 0 (reflexion.evaluated with reason="out_of_turns"). If score ≥ threshold, stop (reflexion.threshold_met).
  7. Reflect. One model call producing a lesson from the attempt’s actions, observations and output (reflexion.lesson_produced). Persist it to lesson_store (selective mode) or append it to the monotonic block (reflexion.lesson_persisted).

When the last attempt scores below the threshold, Reflexion emits reflexion.max_attempts_reached. When the last attempt runs out of turns, the run ends interrupted (interruption_reason="max_turns_exceeded") without that event.


Cost model

Each attempt: base + 1 (evaluator) + 1 (reflector if below threshold). For a 3-attempt run: roughly 1.5–3× base cost depending on how often the evaluator passes. Typical use case (test pass/fail evaluator): 1.5× cost when passing on attempt 2, 3× when needing all 3 attempts.


When Reflexion pays off

  • Tasks with a clear evaluator signal (test pass/fail, JSON schema match, factual correctness against ground truth).
  • Repeated runs of the same problem class, where lessons compound.
  • Cases where the alternative is shipping wrong answers to users, so the extra calls pay for themselves.

Example

from loomflow import Agent, HashEmbedder from loomflow.architecture import Reflexion, Supervisor from loomflow.vectorstore import InMemoryVectorStore agent = Agent( "Manage the article pipeline.", model="claude-opus-4-7", architecture=Reflexion( base=Supervisor(workers={ "researcher": researcher, "writer": writer, "reviewer": reviewer, }), max_attempts=3, threshold=0.85, lesson_store=InMemoryVectorStore(embedder=HashEmbedder()), ), )

Source

loomflow/architecture/reflexion.py

Without an evaluator, Reflexion does nothing useful. The lesson generator only fires when the evaluator’s score < threshold. Provide an evaluator_prompt that returns a meaningful score for your domain (test runner, regex match, structured-output validity, etc.) or the loop just retries without learning.

Last updated on