# State Management Patterns in BettaFish Agents: 7 Core Implementation Strategies

> Discover 7 core BettaFish state management patterns for AI agents. Learn immutable containers, hierarchical composition, and explicit mutation for predictable workflows.

- Repository: [BaiFu/bettafish](https://github.com/666ghj/bettafish)
- Tags: deep-dive
- Published: 2026-02-23

---

**BettaFish implements a consistent, dataclass-driven state management pattern across all its agents (QueryEngine, MediaEngine, InsightEngine, and ReportEngine) that combines immutable containers, hierarchical composition, and explicit mutation methods to create predictable, resumable AI workflows.**

The open-source BettaFish repository (666ghj/bettafish) orchestrates complex multi-agent research workflows where state integrity directly impacts result reliability. Understanding the state management patterns used across agents reveals how the system maintains data integrity while supporting progress tracking, serialization, and workflow resumption.

## Dataclass-Based State Containers

Every piece of mutable state in BettaFish is represented by a Python `@dataclass` with sensible defaults via `field(default_factory=...)`. This pattern appears consistently across all agent modules to create lightweight, type-safe containers.

In [`QueryEngine/state/state.py`](https://github.com/666ghj/bettafish/blob/main/QueryEngine/state/state.py), [`MediaEngine/state/state.py`](https://github.com/666ghj/bettafish/blob/main/MediaEngine/state/state.py), and [`InsightEngine/state/state.py`](https://github.com/666ghj/bettafish/blob/main/InsightEngine/state/state.py), the system defines core classes including `Search`, `Research`, `Paragraph`, and `State`. The `ReportEngine` uses analogous structures in [`ReportEngine/state/state.py`](https://github.com/666ghj/bettafish/blob/main/ReportEngine/state/state.py) with `ReportMetadata` and `ReportState`. These dataclasses provide immutable-ish semantics where default values ensure valid initialization without boilerplate constructors.

## Hierarchical Composition of State Objects

BettaFish employs hierarchical composition to mirror the logical flow of deep-search tasks. Smaller state objects nest within larger ones, creating a tree structure that reflects the research workflow from granular searches to complete reports.

The composition hierarchy follows this pattern: `State` contains a list of `Paragraph` objects, each `Paragraph` contains a `Research` object, and each `Research` object tracks multiple `Search` instances. This is implemented in [`QueryEngine/state/state.py`](https://github.com/666ghj/bettafish/blob/main/QueryEngine/state/state.py) through the type-hinted field `State.paragraphs: List[Paragraph]`.

The `ReportEngine` extends this aggregation pattern in [`ReportEngine/state/state.py`](https://github.com/666ghj/bettafish/blob/main/ReportEngine/state/state.py) where `ReportState` composes results from lower-level engines through fields like `query_engine_report` and `media_engine_report`, enabling high-level orchestration while maintaining clean domain boundaries.

## Explicit Mutation Methods

Rather than exposing raw attributes for direct modification, each state class provides explicit methods to update data and synchronize related fields like timestamps and progress counters.

The `Research` class in [`QueryEngine/state/state.py`](https://github.com/666ghj/bettafish/blob/main/QueryEngine/state/state.py) implements `add_search_results()` to append search results while maintaining internal consistency. The `Paragraph` class provides status checks via `is_completed()`, while `ReportState` offers `mark_processing()` and `mark_completed()` methods that manage both status flags and timing data atomically.

This approach ensures that state transitions remain predictable and testable, preventing invalid intermediate states where a paragraph might be marked complete while containing incomplete research.

## Progress Reporting and Status Tracking

Agents expose progress snapshots through dedicated methods that aggregate completion statistics for CLI and Streamlit interfaces without exposing internal data structures.

The `State` class in [`QueryEngine/state/state.py`](https://github.com/666ghj/bettafish/blob/main/QueryEngine/state/state.py) implements `get_progress_summary()`, which returns a dictionary containing `completed_paragraphs`, `total_paragraphs`, and `progress_percentage`. The `ReportEngine` provides similar functionality through `ReportState.get_progress()` in [`ReportEngine/state/state.py`](https://github.com/666ghj/bettafish/blob/main/ReportEngine/state/state.py), returning a float representing completion percentage from 0.0 to 100.0.

These methods enable real-time feedback during long-running research jobs while maintaining encapsulation of the underlying state tree.

## Serialization and Checkpointing

All state objects support JSON-compatible serialization through `to_dict()` methods and file persistence via `save_to_file()` and `load_from_file()`. This enables checkpointing, debugging, and resumption of interrupted workflows.

In [`QueryEngine/state/state.py`](https://github.com/666ghj/bettafish/blob/main/QueryEngine/state/state.py), the `State` class implements both serialization directions, allowing agents to write progress to disk and resume later. The `ReportState` class in [`ReportEngine/state/state.py`](https://github.com/666ghj/bettafish/blob/main/ReportEngine/state/state.py) mirrors this capability, ensuring that lengthy report generation can survive process interruptions.

The serialization methods handle datetime conversion automatically, preserving timestamp data across save/load cycles.

## Automatic Timestamping for Audit Trails

Every mutable record automatically captures creation and update timestamps using `field(default_factory=lambda: datetime.now().isoformat())` to provide chronological ordering and debugging capabilities.

Fields like `Search.timestamp`, `ReportMetadata.timestamp`, and `State.created_at`/`updated_at` appear consistently across [`QueryEngine/state/state.py`](https://github.com/666ghj/bettafish/blob/main/QueryEngine/state/state.py) and sibling modules. This pattern proves essential for debugging search sequences and verifying the freshness of cached results across all agent types.

## Separation of Concerns Across Agent Modules

Each BettaFish agent maintains its own `state` module, preventing cross-contamination between QueryEngine, MediaEngine, InsightEngine, and ReportEngine concerns while allowing independent evolution of state representations.

The high-level orchestration in [`QueryEngine/agent.py`](https://github.com/666ghj/bettafish/blob/main/QueryEngine/agent.py) demonstrates this pattern clearly: the `DeepSearchAgent` class initializes a single `State` instance in `__init__` and passes it to node-level mutators like `ReportStructureNode` and `FirstSearchNode` through the `node.mutate_state(state, ...)` interface.

## Practical Implementation Examples

### Initializing and Mutating State in QueryEngine

The following example demonstrates creating a research state, adding paragraphs, recording search results, and persisting the checkpoint:

```python
from bettafish.main.QueryEngine.state import State

# Initialise empty state for a new query

state = State(query="AI impact on finance")

# Add a paragraph that will later be researched

idx = state.add_paragraph(
    title="Market Trends",
    content="Describe recent AI‑driven market trends."
)

# Record a search result inside the paragraph's research history

search_result = {
    "url": "https://example.com/article",
    "title": "AI in Finance 2024",
    "content": "Article body …",
    "score": 0.92,
}
state.paragraphs[idx].research.add_search_results(
    query="AI in finance market trends",
    results=[search_result]
)

# Mark the paragraph as finished and update the whole report status

state.paragraphs[idx].research.mark_completed()
state.mark_completed()

# Persist the state for later inspection

state.save_to_file("tmp/state_checkpoint.json")

```

### Resuming Work and Checking Progress in MediaEngine

State persistence enables workflow resumption across sessions:

```python
from bettafish.main.MediaEngine.state import State

state = State.load_from_file("tmp/state_checkpoint.json")
progress = state.get_progress_summary()
print(f"Completed {progress['completed_paragraphs']} / {progress['total_paragraphs']} "
      f"({progress['progress_percentage']:.1f}%)")

```

### Managing ReportEngine Lifecycle States

The ReportEngine uses simplified status flags for high-level report generation tracking:

```python
from bettafish.main.ReportEngine.state import ReportState

report_state = ReportState(query="Future of renewable energy")
report_state.mark_processing()   # agent started heavy computation

# ... later when HTML is ready

report_state.html_content = "<h1>Report</h1>"
report_state.mark_completed()
print(report_state.get_progress())   # returns 100.0

```

## Summary

- **Dataclass containers**: All agents use `@dataclass` definitions in dedicated [`state.py`](https://github.com/666ghj/bettafish/blob/main/state.py) modules to create lightweight, type-safe state objects with default field factories.

- **Hierarchical composition**: State objects nest logically (State → Paragraph → Research → Search) to mirror research workflows, implemented consistently across QueryEngine, MediaEngine, and InsightEngine.

- **Controlled mutations**: Explicit methods like `add_search_results()`, `mark_completed()`, and `mark_processing()` enforce valid state transitions and synchronize related metadata.

- **Progress visibility**: Methods such as `get_progress_summary()` and `get_progress()` provide real-time completion statistics without exposing internal data structures.

- **Full serialization**: Every state object supports `to_dict()`, `save_to_file()`, and `load_from_file()` operations, enabling checkpointing and workflow resumption in [`QueryEngine/state/state.py`](https://github.com/666ghj/bettafish/blob/main/QueryEngine/state/state.py) and [`ReportEngine/state/state.py`](https://github.com/666ghj/bettafish/blob/main/ReportEngine/state/state.py).

- **Temporal tracking**: Automatic ISO-formatted timestamp fields provide audit trails for search operations and state modifications.

- **Modular architecture**: Each agent (`QueryEngine`, `MediaEngine`, `InsightEngine`, `ReportEngine`) maintains independent state modules, with high-level orchestration passing state instances to node mutators as shown in [`QueryEngine/agent.py`](https://github.com/666ghj/bettafish/blob/main/QueryEngine/agent.py).

## Frequently Asked Questions

### How does BettaFish ensure state consistency across long-running research tasks?

BettaFish ensures state consistency through explicit mutation methods that synchronize related fields atomically. Rather than allowing direct attribute modification, classes like `Research` and `ReportState` provide methods such as `add_search_results()` and `mark_completed()` that update both the primary data and associated timestamps or counters simultaneously. This prevents partial updates that could leave the system in an inconsistent state.

### Can BettaFish research workflows be resumed after an interruption?

Yes, all state objects implement serialization methods (`to_dict`, `from_dict`) and file operations (`save_to_file`, `load_from_file`) that enable full checkpointing. The `State` class in [`QueryEngine/state/state.py`](https://github.com/666ghj/bettafish/blob/main/QueryEngine/state/state.py) and `ReportState` in [`ReportEngine/state/state.py`](https://github.com/666ghj/bettafish/blob/main/ReportEngine/state/state.py) can persist their entire structure to JSON and rehydrate later, allowing agents to resume long-running research jobs from the exact point of interruption.

### What is the relationship between DeepSearchAgent and state objects in the QueryEngine?

The `DeepSearchAgent` class in [`QueryEngine/agent.py`](https://github.com/666ghj/bettafish/blob/main/QueryEngine/agent.py) acts as an orchestrator that holds a single `State` instance initialized during `__init__`. This agent passes the state object to various processing nodes (like `ReportStructureNode` and `FirstSearchNode`) through the `mutate_state(state, ...)` interface. This separation of concerns keeps state management logic in the `state` module while the agent focuses on workflow orchestration.

### How does the ReportEngine state differ from QueryEngine state?

While QueryEngine, MediaEngine, and InsightEngine use hierarchical state structures with `Search`, `Research`, and `Paragraph` classes in their respective [`state.py`](https://github.com/666ghj/bettafish/blob/main/state.py) files, the ReportEngine uses a simplified model in [`ReportEngine/state/state.py`](https://github.com/666ghj/bettafish/blob/main/ReportEngine/state/state.py). The `ReportState` class focuses on high-level aggregation (storing references to sub-engine reports) and status flags (`mark_processing`, `mark_completed`) rather than granular search tracking, reflecting its role as a final output generator rather than a research executor.