Source code for atloop.memory.state

"""Agent state data structures."""

from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union

if TYPE_CHECKING:
    pass  # AgentState is defined in this file, no need to import

# Import PlanStep for type hints (avoid circular import)
try:
    from atloop.memory.plan import PlanStep
except ImportError:
    PlanStep = Any  # Fallback for type hints


[docs] @dataclass class LastError: """Last error information.""" summary: str = "" repro_cmd: str = "" raw_stderr_tail: str = "" error_signature: str = "" # Hash of key error lines
[docs] @dataclass class Memory: """Memory for tracking decisions and attempts. Memory is organized into three categories: 1. FACTS (used for LLM context): - created_files, modified_files_content, tool_results_history - These are objective, verifiable facts from tool execution 2. LONG-TERM (used for LLM context): - plan, task_summary, milestones, learnings - These are validated/verified information 3. DEBUG-ONLY (NOT fed back to LLM): - decisions, llm_responses - These contain LLM's interpretations which could cause feedback loops Memory 模块负责: - ✓ 原始数据的存储和管理 - ✓ 各个条目的格式转换和控制输出(考虑约束:单条长度、字符串映射等) - ✓ 提供统一的格式化接口,返回可直接注入 prompt 的字符串 Memory 模块不负责: - ❌ 数据压缩(由独立的 CompressionPolicy 负责) - ❌ 数据重要性评分(由独立的 Scorer 负责,如果使用) """ # ========================================================================= # FACTS - Objective data from tool execution (fed to LLM) # ========================================================================= created_files: List[str] = field(default_factory=list) # Track created files attempts: List[Dict[str, Any]] = field(default_factory=list) # Tool execution attempts key_files: List[Dict[str, Any]] = field(default_factory=list) # Key files identified notes: List[str] = field(default_factory=list) # Factual notes # Store tool execution results history tool_results_history: List[Dict[str, Any]] = field(default_factory=list) # Format: {"step": int, "tool": str, "args": Dict, "result": Dict} # Auto-read file content after modification modified_files_content: List[Dict[str, Any]] = field(default_factory=list) # Format: {"path": str, "content": str, "content_hash": str, ...} # ========================================================================= # PROGRESS TRACKING - For loop detection (fed to LLM as metrics only) # ========================================================================= action_history: List[Dict[str, Any]] = field(default_factory=list) # Format: {"step": int, "tool": str, "category": str, "signature": str, ...} # This is serialized from ProgressTracker for persistence # ========================================================================= # LONG-TERM MEMORY - Validated information (fed to LLM) # ========================================================================= plan: Union[str, List[Any]] = field(default_factory=list) task_summary: str = "" important_decisions: List[Dict[str, Any]] = field(default_factory=list) milestones: List[Dict[str, Any]] = field(default_factory=list) learnings: List[str] = field(default_factory=list) # Skill cache for lazy loading of skill resources # Format: # { # "skill_name": { # "metadata": { # "name": str, # "description": str, # "body": str, # SKILL.md body # "loaded_at_step": int # }, # "resources": { # "scripts": Dict[str, Dict[str, Any]], # {filename: {content, loaded_at_step}} # "references": Dict[str, Dict[str, Any]], # "assets": Dict[str, Dict[str, Any]] # May not cache binary content # } # } # } skill_cache: Dict[str, Dict[str, Any]] = field(default_factory=dict) # ========================================================================= # PARTIALLY VISIBLE - Facts only (NOT fully DEBUG-ONLY) # ========================================================================= # decisions: Partially visible to LLM - only factual information is shown # - ✓ Visible: step, actions_count, tools_used, stop_reason # - ❌ NOT visible: current_step_thoughts, plan, llm_output # - Purpose: Provide context about what was done, without LLM's thinking process decisions: List[Dict[str, Any]] = field(default_factory=list) # NOTE: Contains current_step_thoughts - this field is NOT shown to LLM # Only factual information (tools, actions, stop_reason) is shown in MemorySummary # ========================================================================= # DEBUG-ONLY - LLM interpretations (NOT fed back to LLM) # ========================================================================= # llm_responses: Completely invisible to LLM - only for debugging/logging llm_responses: List[Dict[str, Any]] = field(default_factory=list) # WARNING: Contains current_step_thoughts - DO NOT feed back to LLM # Format: {"step": int, "current_step_thoughts": str, "plan": List[str], ...}
[docs] def get_formatted_context( self, state: "AgentState", task_goal: Optional[str] = None, max_length: Optional[int] = None, format_options: Optional[Dict[str, Any]] = None, tool_registry: Optional[Any] = None, ) -> str: """ 获取格式化后的记忆上下文,可直接注入到 prompt 中。 这是 Memory 模块的主要输出接口,返回格式化的字符串。 Args: state: AgentState 实例(需要访问 memory, last_error, artifacts) task_goal: 任务目标(可选,用于任务概览) max_length: 最大长度限制(可选) format_options: 格式选项(可选,会覆盖配置中的默认值) - tool_results_count: int (默认从 MemoryConfig 读取) - steps_summary_count: int (默认从 MemoryConfig 读取) - include_file_content: bool (默认从 MemoryConfig 读取) - max_file_content_length: int (默认从 MemoryConfig 读取) 注意:所有默认值现在从 MemoryConfig 读取,确保单一数据源。 可以通过 format_options 参数覆盖特定调用的值。 - string_mappings: Dict[str, str] (字符串映射规则) tool_registry: 工具注册表(用于输出限制策略) Returns: 格式化后的字符串,可直接用于 prompt 注入 格式:符合 MEMORY_PROMPT_FORMAT_DEMO.md 中定义的格式 """ from atloop.memory.formatter import MemoryFormatter formatter = MemoryFormatter(tool_registry=tool_registry) # 合并 format_options 和 max_length options = format_options or {} if max_length: options["max_length"] = max_length return formatter.format(state, task_goal=task_goal, format_options=options)
[docs] @dataclass class Artifacts: """Artifacts produced during execution.""" current_diff: str = "" test_results: str = "" verification_success: Optional[bool] = None # Latest verification result dod_result: Optional[Any] = None # DoD check result (for reporting, not stopping)
[docs] @dataclass class BudgetUsed: """Budget usage tracking.""" llm_calls: int = 0 tool_calls: int = 0 wall_time_sec: int = 0
[docs] @dataclass class AgentState: """Agent execution state.""" step: int = 0 phase: str = "DISCOVER" # DISCOVER, PLAN, ACT, VERIFY, DONE, FAIL last_error: LastError = field(default_factory=LastError) memory: Memory = field(default_factory=Memory) artifacts: Artifacts = field(default_factory=Artifacts) budget_used: BudgetUsed = field(default_factory=BudgetUsed)
[docs] def to_dict(self) -> Dict[str, Any]: """Convert to dictionary.""" return { "step": self.step, "phase": self.phase, "last_error": { "summary": self.last_error.summary, "repro_cmd": self.last_error.repro_cmd, "raw_stderr_tail": self.last_error.raw_stderr_tail, "error_signature": self.last_error.error_signature, }, "memory": { # Facts "created_files": self.memory.created_files, "attempts": self.memory.attempts, "key_files": self.memory.key_files, "notes": self.memory.notes, "tool_results_history": self.memory.tool_results_history, "modified_files_content": self.memory.modified_files_content, # Progress tracking "action_history": self.memory.action_history, # Long-term memory "plan": ( [s.to_dict() if hasattr(s, "to_dict") else s for s in self.memory.plan] if isinstance(self.memory.plan, list) else self.memory.plan ), "task_summary": self.memory.task_summary, "important_decisions": self.memory.important_decisions, "milestones": self.memory.milestones, "learnings": self.memory.learnings, # Debug-only (preserved for logging, NOT fed to LLM) "decisions": self.memory.decisions, "llm_responses": self.memory.llm_responses, }, "artifacts": { "current_diff": self.artifacts.current_diff, "test_results": self.artifacts.test_results, "dod_result": ( { "passed": self.artifacts.dod_result.passed, "checks": self.artifacts.dod_result.checks, "message": self.artifacts.dod_result.message, } if self.artifacts.dod_result else None ), }, "budget_used": { "llm_calls": self.budget_used.llm_calls, "tool_calls": self.budget_used.tool_calls, "wall_time_sec": self.budget_used.wall_time_sec, }, }
[docs] @classmethod def from_dict(cls, data: Dict[str, Any]) -> "AgentState": """Create from dictionary.""" last_error = LastError(**data.get("last_error", {})) memory_data = data.get("memory", {}) # Handle plan: can be string or list of dicts (structured) plan_data = memory_data.get("plan", []) if isinstance(plan_data, str): plan = plan_data elif isinstance(plan_data, list): # Structured format: convert dicts to PlanStep objects try: from atloop.memory.plan import PlanStep plan = [PlanStep.from_dict(s) if isinstance(s, dict) else s for s in plan_data] except ImportError: # Fallback: keep as list of dicts plan = plan_data else: plan = [] memory = Memory( # Facts created_files=memory_data.get("created_files", []), attempts=memory_data.get("attempts", []), key_files=memory_data.get("key_files", []), notes=memory_data.get("notes", []), tool_results_history=memory_data.get("tool_results_history", []), modified_files_content=memory_data.get("modified_files_content", []), # Progress tracking action_history=memory_data.get("action_history", []), # Long-term memory plan=plan, task_summary=memory_data.get("task_summary", ""), important_decisions=memory_data.get("important_decisions", []), milestones=memory_data.get("milestones", []), learnings=memory_data.get("learnings", []), # Debug-only decisions=memory_data.get("decisions", []), llm_responses=memory_data.get("llm_responses", []), ) artifacts = Artifacts(**data.get("artifacts", {})) budget_used = BudgetUsed(**data.get("budget_used", {})) return cls( step=data.get("step", 0), phase=data.get("phase", "DISCOVER"), last_error=last_error, memory=memory, artifacts=artifacts, budget_used=budget_used, )