State Management Documentation
Overview
TunaCode employs a multi-layered state management strategy that combines a central global session store with localized component states and explicit state machines for agent behavior. This architecture provides both centralized truth and localized control where appropriate, while supporting session persistence.
1. State Stores and Global State
1.1 Primary State Store: StateManager
Location: src/tunacode/core/state.py
The StateManager class is the central application state orchestrator. It holds a SessionState object and is instantiated as a global singleton in <repo_root>/src/tunacode/ui/main.py.
state_manager = StateManager()
SessionState Dataclass
The SessionState class encapsulates the entire user session runtime state:
- Conversation State:
conversation(messages, thoughts, token counts) - Task State:
task(todos, original query) - Runtime State:
runtime(iteration counters, request_id, tool registry, streaming flags) - Usage State:
usage(per-call and cumulative metrics) - Configuration:
user_config(merged defaults + user settings) - Tool State:
runtime.tool_registry - UI State:
runtime.operation_cancelled,runtime.is_streaming_active - Metadata:
session_id,project_id,created_at,last_modified,working_directory
The state_manager.session object is the primary shared mutable state passed across agent components, tools, and UI elements.
1.2 Agent Orchestration State
Location: <repo_root>/src/tunacode/core/agents/main.py
Several state classes manage agent behavior:
AgentConfig(dataclass): Defines agent behavior configuration (e.g.,max_iterations)RequestContext(dataclass): Holds request-specific context (e.g.,request_id)EmptyResponseHandler: Manages state for consecutive empty responsesIterationManager: Tracks agent iteration progressRequestOrchestrator: Composes and manages the above state classes
1.3 UI State Containers
Location: <repo_root>/src/tunacode/ui/app.py
TextualReplApp: Core UI state including:request_queue: Asynchronous event queue- Streaming flags:
_streaming_paused,_stream_buffer,current_stream_text - Task references:
_current_request_task
Widget-specific state:
Editor(<repo_root>/src/tunacode/ui/widgets/editor.py): Input editor state (_placeholder_cleared,_was_pasted,_pasted_content)ResourceBar(<repo_root>/src/tunacode/ui/widgets/resource_bar.py): Resource display state (_tokens,_model,_cost,_lsp_enabled)StatusBar(<repo_root>/src/tunacode/ui/widgets/status_bar.py): Status bar state (_edited_files,_location_text)ShellRunner(<repo_root>/src/tunacode/ui/shell_runner.py): External process state (_task,_process)
2. Caching Strategies
2.1 Module-Level In-Memory Caches
Location: <repo_root>/src/tunacode/core/agents/agent_components/agent_config.py
Three module-level dictionaries serve as global in-memory caches:
_TUNACODE_CACHE: dict[str, tuple[str, float]] = {}
_AGENT_CACHE: dict[ModelName, PydanticAgent] = {}
_AGENT_CACHE_VERSION: dict[ModelName, int] = {}
_TUNACODE_CACHE: CachesAGENTS.mdcontent with modification time_AGENT_CACHE: StoresPydanticAgentinstances across requests_AGENT_CACHE_VERSION: Manages cache versioning for invalidation
Note: System prompts are now composed from section files via
SectionLoaderinsrc/tunacode/core/prompting/loader.py, which uses instance-level caching.
2.2 Models Registry Cache
Location: <repo_root>/src/tunacode/configuration/models.py
_models_registry_cache: dict | None = None
def load_models_registry() -> dict:
global _models_registry_cache
if _models_registry_cache is not None:
return _models_registry_cache
# ... loads from file ...
_models_registry_cache = json.load(f)
return _models_registry_cache
The models registry is cached in memory to avoid repeated file reads.
2.3 Token Counter Heuristic
Location: <repo_root>/src/tunacode/utils/messaging/token_counter.py
Uses a lightweight character heuristic:
CHARS_PER_TOKEN: int = 4
def estimate_tokens(text: str) -> int:
if not text:
return 0
return len(text) // CHARS_PER_TOKEN
2.4 Tool Buffer
Location: <repo_root>/src/tunacode/core/agents/agent_components/tool_buffer.py
ToolBuffer: Buffers read-only tool calls (self.read_only_tasks) for parallel execution- Acts as a transient data store for tool calls awaiting batched execution
2.5 Progress Tracker
Location: <repo_root>/src/tunacode/core/agents/research_agent.py
ProgressTracker: Tracksoperation_countfor subagent tool execution
3. Configuration Management
3.1 Configuration Hierarchy
Default Configuration:
Location: <repo_root>/src/tunacode/configuration/defaults.py
DEFAULT_USER_CONFIG = {
"default_model": "openrouter:openai/gpt-4.1",
"env": {
"ANTHROPIC_API_KEY": "",
"OPENAI_API_KEY": "",
"OPENROUTER_API_KEY": ""
},
"settings": {
"max_retries": 3,
"max_iterations": 10,
"global_request_timeout": 120,
"theme": "dark",
# ... tool-specific settings
}
}
User Configuration:
Location: <repo_root>/src/tunacode/utils/config/user_configuration.py
load_config(): Reads user-specific~/.config/tunacode.json, merging withDEFAULT_USER_CONFIG(user values take precedence)save_config(state_manager): Persists current configuration to disk- Includes caching for performance
Application Settings:
Location: <repo_root>/src/tunacode/configuration/settings.py
PathConfig: Specifies configuration file pathApplicationSettings: Manages application-wide metadata and paths- Instantiated as global singleton
app_settingsin<repo_root>/src/tunacode/ui/main.py
Model Configuration:
Location: <repo_root>/src/tunacode/configuration/models.py
- Loads model metadata from
models_registry.json - Provides:
get_provider_env_var(): Provider-specific environment variable namesget_model_context_window(): Model context window sizes
3.2 Environment Variable Handling
Configuration Resolution:
Location: <repo_root>/src/tunacode/core/agents/agent_components/agent_config.py
def _create_model_with_retry(state_manager: StateManager):
# Retrieves API keys from user_config["env"]
api_keys = state_manager.session.user_config.get("env", {})
# Resolves base_url: per-provider config > registry default
provider_settings = settings.get("providers", {}).get(provider_name, {})
base_url = provider_settings.get("base_url") or registry_config.api
# For OpenAI-compatible providers: OPENAI_BASE_URL as escape hatch
if provider_name != "anthropic":
env_base_url = env.get("OPENAI_BASE_URL")
if env_base_url:
base_url = env_base_url
Tool Execution:
Location: <repo_root>/src/tunacode/tools/bash.py
exec_env = os.environ.copy()
if env:
exec_env.update(env)
The bash tool copies the current process environment and allows custom variables per command.
4. Session Management
4.1 Session Lifecycle
Session Creation:
- Each session gets a unique
session_id(UUID) - Associated with a
project_id(generated from Git or CWD viaget_project_id()) - Tracked in
SessionStatemetadata fields
Session Persistence:
Location: <repo_root>/src/tunacode/core/state.py
def save_session():
# Serializes SessionState to JSON
# Stored in platform-specific session directory
# ~/.local/share/tunacode/sessions/ on Linux
Session Loading:
def load_session(session_id):
# Deserializes JSON to SessionState
# Restores application state
Serialization Helpers:
_serialize_messages(): Converts Pydantic-AI message objects to JSON_deserialize_messages(): Restores messages from JSON
Auto-Save:
Location: <repo_root>/src/tunacode/ui/app.py
save_session()called automatically:- On application unmount
- After each user request
- Ensures state persistence across interactions and restarts
4.2 Session Storage
Location: <repo_root>/src/tunacode/utils/system/paths.py
get_session_storage_dir(): Platform-agnostic session storage locationget_session_dir(): Session-specific directory pathget_project_id(): Generates unique project identifier
4.3 Session Commands
Location: <repo_root>/src/tunacode/ui/commands/__init__.py
ResumeCommand: List, load, and delete previous sessions- Enables users to resume work where they left off
5. State Machines
5.1 Agent State Machine
AgentState Enum:
Location: <repo_root>/src/tunacode/types/dataclasses.py
class AgentState(Enum):
USER_INPUT = "user_input"
ASSISTANT = "assistant"
TOOL_EXECUTION = "tool_execution"
RESPONSE = "response"
State Machine Implementation:
Location: <repo_root>/src/tunacode/core/agents/agent_components/state_transition.py
AgentStateMachine: Thread-safe state machine managingAgentStatetransitionsStateTransitionRules: Defines valid transitions between statesAGENT_TRANSITION_RULES: Global instance defining allowed agent processing flow
ResponseState Interface:
Location: <repo_root>/src/tunacode/core/agents/agent_components/response_state.py
@dataclass
class ResponseState:
state_machine: AgentStateMachine
# Provides interface for agent state and completion tracking
# Maintains backward compatibility through boolean flags
The state machine ensures controlled, validated transitions through agent processing stages.
6. User Preferences and Settings
6.1 Preference Storage
Location: <repo_root>/src/tunacode/core/state.py
All user-customizable settings are stored in:
SessionState.user_config
This makes preferences accessible throughout the application.
6.2 Runtime Preference Modification
Location: <repo_root>/src/tunacode/ui/commands/__init__.py
CLI commands allow runtime preference changes:
/model: Reloads config from disk, updatesuser_config["default_model"], and invalidates the agent cache/theme: Updatesuser_config["settings"]["theme"]
Summary
TunaCode's state management is characterized by:
- Centralized Session Store:
StateManager.sessionas the single source of truth - Layered Caching: Module-level caches for prompts, agents, and models
- Explicit State Machines: Controlled agent lifecycle through
AgentStateMachine - Persistent Sessions: JSON-based session save/load with automatic persistence
- Flexible Configuration: Merged defaults + user config with runtime modification
- Component-Level State: Encapsulated state in UI widgets and agent components
- Environment-Aware: Dynamic API key and base URL resolution
This architecture balances performance (caching), safety (state machines), and usability (session persistence, runtime configuration).