State Management Patterns in BettaFish Agents: 7 Core Implementation Strategies
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, MediaEngine/state/state.py, and 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 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 through the type-hinted field State.paragraphs: List[Paragraph].
The ReportEngine extends this aggregation pattern in 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 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 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, 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, 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 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 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 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:
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:
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:
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
@dataclassdefinitions in dedicatedstate.pymodules 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(), andmark_processing()enforce valid state transitions and synchronize related metadata. -
Progress visibility: Methods such as
get_progress_summary()andget_progress()provide real-time completion statistics without exposing internal data structures. -
Full serialization: Every state object supports
to_dict(),save_to_file(), andload_from_file()operations, enabling checkpointing and workflow resumption inQueryEngine/state/state.pyandReportEngine/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 inQueryEngine/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 and ReportState in 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 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 files, the ReportEngine uses a simplified model in 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →