Dreaming and reflection

Use the dream hook to consolidate an agent's working memory into durable long-term facts after each conversation ends, keeping the long-term memory store clean and deduplicated.

When a conversation ends, the agent’s working memory (every message, tool call, and tool result) disappears with the execution. Facts worth keeping have to be written explicitly with store_memory. dream automates that process: it fires an asynchronous workflow after the agent completes, reviews the conversation history, and updates long-term memory without blocking the agent’s response.

What dreaming does

dream returns an on_complete hook. You attach it to an agent via the on_complete parameter. After the agent returns its final answer, Flux fires every hook in that list asynchronously. The dream hook submits a background Flux workflow (agent_dream), passing a snapshot of working memory and the long-term memory configuration.

agent_dream runs four phases in sequence, each handled by a dedicated sub-agent:

  1. Orient reads all existing long-term memory keys to build a picture of what is already stored.
  2. Gather signal scans the working memory snapshot for corrections (where the user or agent reversed a prior statement), decisions (technology choices, configuration changes), and repeated facts (entities referenced across three or more messages).
  3. Consolidate merges duplicates, resolves contradictions (preferring facts from the most recent execution), converts relative time expressions to absolute dates, and writes new facts from the signal report.
  4. Prune removes stale entries, condenses verbose values, and enforces a cap of 100 memory keys, then logs what changed: entries before and after, and what was pruned.

All four phases use the same recall_memory, store_memory, forget_memory, and list_memory_keys tools that the main agent uses. The dream workflow does not call the main agent again; it only touches long-term memory.

Attaching the dream hook

from flux import workflow, ExecutionContext
from flux.tasks.ai import agent
from flux.tasks.ai.dreaming import dream
from flux.tasks.ai.memory import working_memory, long_term_memory, sqlite

@workflow
async def research_assistant(ctx: ExecutionContext):
    wm = working_memory(max_tokens=50_000)
    ltm = long_term_memory(
        provider=sqlite("research.db"),
        agent="research_assistant",
        scope="default",
    )

    assistant = await agent(
        "You are a research assistant. Check long-term memory first for context. "
        "Store important facts you learn with store_memory.",
        model="ollama/llama3.2",
        working_memory=wm,
        long_term_memory=ltm,
        on_complete=[dream(working_memory=wm, long_term_memory=ltm)],
    )

    return await assistant(ctx.input["question"])

The dream() call captures references to wm and ltm at construction time. When the hook fires, it calls wm.recall() to snapshot the current conversation history and submits the agent_dream workflow with the snapshot and the long-term memory configuration (scope, provider_type). The dream workflow runs independently; the original workflow execution has already completed and returned its result by then.

dream() signature

from flux.tasks.ai.dreaming import dream

dream(
    *,
    working_memory: WorkingMemory,
    long_term_memory: LongTermMemory,
    model: str | None = None,
    workflow: str = "agent_dream",
)

All parameters are keyword-only. working_memory and long_term_memory are required. model defaults to "ollama/llama3.2" inside agent_dream when you do not specify it here. workflow lets you substitute a custom dream workflow by name.

Multi-turn conversations

Dreaming works with multi-turn pause/resume workflows. The hook fires on each turn when the agent returns, including turns that end with a pause. To fire only on agent completion (not on pause), use on_complete instead of on_pause. Both accept the same list of callables.

assistant = await agent(
    "...",
    model="ollama/llama3.2",
    working_memory=wm,
    long_term_memory=ltm,
    on_complete=[dream(working_memory=wm, long_term_memory=ltm)],
    on_pause=[dream(working_memory=wm, long_term_memory=ltm)],
)

Attaching the hook to both on_complete and on_pause means the dream workflow runs after every turn, not only the final one. This matters for long conversations where you want facts consolidated before the session ends.

Registering the dream workflow

agent_dream is a regular Flux workflow defined in flux/tasks/ai/dreaming.py. In a distributed setup (server + workers), you must register it before the first dream hook fires:

flux workflow register flux/tasks/ai/dreaming.py

In inline mode (workflows run directly via .run()), Flux auto-registers workflows the first time they are invoked, so no explicit registration step is needed.

Failure handling

The dream hook is fire-and-forget. Any exception during hook execution is logged at WARNING level and discarded; it does not affect the agent’s return value or the calling workflow’s status.

Inside agent_dream, a consecutive-failure gate protects long-term memory from repeated broken runs. After three consecutive failures for a given scope, the workflow skips processing and returns {"status": "skipped"}. The counter resets on the next successful run. To change the threshold, replace the default agent_dream workflow with a custom one.

Performance considerations

The dream workflow makes one LLM call per phase. With a small model like llama3.2, expect roughly 30 to 60 seconds of background processing after each conversation. The main workflow is already finished before any of this runs, so users see no latency increase.

The built-in agent_dream sets max_tool_calls conservatively: 20 for orient, 10 for gather signal, 30 for consolidate, and 20 for prune. Large long-term memory stores may approach these limits. If orient or consolidate agents hit the cap before finishing, pass a custom workflow with higher limits.

# Provide more tool-call budget for large memory stores
dream(
    working_memory=wm,
    long_term_memory=ltm,
    model="ollama/llama3.2",
    workflow="my_custom_dream",  # registered workflow with higher limits
)

When to use dreaming

Use dream when an agent accumulates knowledge across many conversations and you want that knowledge to persist without writing manual store_memory calls in the system prompt or workflow code.

It works best when conversations are substantive enough that the agent makes decisions or discovers facts worth keeping, and when the same agent runs repeatedly against the same scope (a user, a project, a codebase). The pruning and deduplication phases also pay off as the long-term memory store grows; without them, store_memory calls pile up unchecked.

Skip it for short-lived agents that do not need cross-session memory, or when every conversation is stateless and repeatable. Four consolidation phases per session carry real LLM cost.