Claude Code commited on
Commit
c4b8552
·
1 Parent(s): 590f843

Claude Code: Target:

Browse files

Implement a simple state ma

.openclaw/agents/__pycache__/brain_minimal.cpython-311.pyc CHANGED
Binary files a/.openclaw/agents/__pycache__/brain_minimal.cpython-311.pyc and b/.openclaw/agents/__pycache__/brain_minimal.cpython-311.pyc differ
 
.openclaw/agents/__pycache__/rbac.cpython-311.pyc CHANGED
Binary files a/.openclaw/agents/__pycache__/rbac.cpython-311.pyc and b/.openclaw/agents/__pycache__/rbac.cpython-311.pyc differ
 
.openclaw/agents/brain_minimal.py CHANGED
@@ -14,6 +14,13 @@ from typing import Dict, List, Optional, Any, Callable
14
  from pathlib import Path
15
  from datetime import datetime
16
  from enum import Enum
 
 
 
 
 
 
 
17
 
18
 
19
  # Import RBAC system
@@ -43,6 +50,149 @@ except ImportError:
43
  print("[Brain] Warning: RBAC system not available, running in legacy mode")
44
 
45
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  class ToolCategory(Enum):
47
  """Categories of tools for organization"""
48
  INFRASTRUCTURE = "infrastructure"
@@ -125,8 +275,9 @@ class BrainMinimal:
125
  self.current_role = None
126
  self.legacy_mode = True
127
 
128
- # Brain state
129
- self.state = BrainState.IDLE
 
130
  self.tools: Dict[str, Tool] = {}
131
  self.memory: Dict[str, Any] = {}
132
  self.last_action = None
@@ -379,19 +530,36 @@ class BrainMinimal:
379
  }
380
 
381
  try:
382
- self.state = BrainState.EXECUTING
 
 
 
383
  result = self.tools[tool_name].execute(*args, **kwargs)
384
  self.last_action = tool_name
385
  self.last_action_time = datetime.utcnow().isoformat()
386
- self.state = BrainState.IDLE
 
 
 
 
 
 
 
387
  return result
388
  except Exception as e:
389
- self.state = BrainState.ERROR
 
 
390
  return {
391
  "success": False,
392
  "error": str(e),
393
  "tool": tool_name
394
  }
 
 
 
 
 
395
 
396
  # ========== Default Tool Implementations ==========
397
 
@@ -498,7 +666,7 @@ class BrainMinimal:
498
  return {
499
  "success": True,
500
  "status": "healthy",
501
- "state": self.state.value,
502
  "agent": self.agent_name,
503
  "role": self.current_role.value if self.current_role else "unknown",
504
  "tools_count": len(self.get_allowed_tools())
@@ -510,7 +678,8 @@ class BrainMinimal:
510
  "success": True,
511
  "agent": self.agent_name,
512
  "role": self.current_role.value if self.current_role else "unknown",
513
- "state": self.state.value,
 
514
  "last_action": self.last_action,
515
  "last_action_time": self.last_action_time,
516
  "legacy_mode": self.legacy_mode
@@ -538,7 +707,7 @@ class BrainMinimal:
538
  Returns:
539
  Name of the tool to execute
540
  """
541
- self.state = BrainState.THINKING
542
 
543
  # Simple decision logic
544
  action = context.get("action", "")
@@ -557,7 +726,7 @@ class BrainMinimal:
557
  else:
558
  tool = "get_status"
559
 
560
- self.state = BrainState.IDLE
561
  return tool
562
 
563
  def execute_decision(self, context: Dict[str, Any]) -> Any:
@@ -573,6 +742,38 @@ class BrainMinimal:
573
  tool_name = self.think(context)
574
  return self.execute_tool(tool_name, **context)
575
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
576
  # ========== Utility Methods ==========
577
 
578
  def get_info(self) -> Dict[str, Any]:
@@ -580,7 +781,8 @@ class BrainMinimal:
580
  return {
581
  "agent": self.agent_name,
582
  "role": self.current_role.value if self.current_role else "unknown",
583
- "state": self.state.value,
 
584
  "legacy_mode": self.legacy_mode,
585
  "tools_registered": len(self.tools),
586
  "tools_allowed": len(self.get_allowed_tools()),
 
14
  from pathlib import Path
15
  from datetime import datetime
16
  from enum import Enum
17
+ import threading
18
+
19
+
20
+ # OpenClaw agents directory
21
+ OPENCLAW_AGENTS_DIR = Path(__file__).parent
22
+ LOGS_DIR = OPENCLAW_AGENTS_DIR / "logs"
23
+ STATUS_FILE = OPENCLAW_AGENTS_DIR / "cain_status.json"
24
 
25
 
26
  # Import RBAC system
 
50
  print("[Brain] Warning: RBAC system not available, running in legacy mode")
51
 
52
 
53
+ # State Machine for Agent State Management
54
+ class AgentState(Enum):
55
+ """Agent operational states"""
56
+ IDLE = "idle"
57
+ PROCESSING = "processing"
58
+ SUCCESS = "success"
59
+ ERROR = "error"
60
+
61
+
62
+ class AgentStateMachine:
63
+ """
64
+ Simple state machine for tracking agent states with persistence and logging.
65
+
66
+ States:
67
+ - IDLE: Agent is idle, waiting for tasks
68
+ - PROCESSING: Agent is actively processing a task
69
+ - SUCCESS: Agent completed a task successfully
70
+ - ERROR: Agent encountered an error
71
+
72
+ Every state transition is:
73
+ - Written to cain_status.json (current_state, last_updated)
74
+ - Logged to session-archive.jsonl
75
+ """
76
+
77
+ def __init__(self, agent_name: str = "cain"):
78
+ """
79
+ Initialize the state machine.
80
+
81
+ Args:
82
+ agent_name: Name of the agent for logging purposes
83
+ """
84
+ self._state = AgentState.IDLE
85
+ self._agent_name = agent_name
86
+ self._lock = threading.Lock()
87
+ self._status_file = STATUS_FILE
88
+
89
+ # Initialize log file and status file
90
+ self._ensure_log_file_exists()
91
+ self._update_status_file()
92
+
93
+ def _ensure_log_file_exists(self):
94
+ """Ensure the log file exists."""
95
+ LOGS_DIR.mkdir(parents=True, exist_ok=True)
96
+
97
+ def _log_state_change(self, new_state: AgentState):
98
+ """
99
+ Log state change to session-archive.jsonl.
100
+
101
+ Args:
102
+ new_state: The new state being transitioned to
103
+ """
104
+ log_entry = {
105
+ "type": "state_change",
106
+ "state": new_state.value,
107
+ "timestamp": datetime.utcnow().isoformat() + "+00:00",
108
+ "agent": self._agent_name
109
+ }
110
+
111
+ log_file = LOGS_DIR / "session-archive.jsonl"
112
+ try:
113
+ with open(log_file, "a") as f:
114
+ f.write(json.dumps(log_entry) + "\n")
115
+ except Exception as e:
116
+ print(f"[AgentStateMachine] Failed to write log: {e}")
117
+
118
+ def _update_status_file(self):
119
+ """Update cain_status.json with current state and timestamp."""
120
+ status_data = {
121
+ "current_state": self._state.value,
122
+ "last_updated": datetime.utcnow().isoformat() + "+00:00",
123
+ "agent": self._agent_name
124
+ }
125
+
126
+ try:
127
+ with open(self._status_file, "w") as f:
128
+ json.dump(status_data, f, indent=2)
129
+ except Exception as e:
130
+ print(f"[AgentStateMachine] Failed to update status file: {e}")
131
+
132
+ def transition_to(self, new_state: AgentState) -> bool:
133
+ """
134
+ Transition to a new state.
135
+
136
+ Args:
137
+ new_state: The state to transition to
138
+
139
+ Returns:
140
+ True if transition was successful, False otherwise
141
+ """
142
+ with self._lock:
143
+ if new_state == self._state:
144
+ return True # Already in this state
145
+
146
+ old_state = self._state
147
+ self._state = new_state
148
+
149
+ # Log the state change
150
+ self._log_state_change(new_state)
151
+
152
+ # Update status file
153
+ self._update_status_file()
154
+
155
+ return True
156
+
157
+ def get_state(self) -> str:
158
+ """
159
+ Get the current state as a string.
160
+
161
+ Returns:
162
+ Current state value as a string
163
+ """
164
+ return self._state.value
165
+
166
+ def get_state_enum(self) -> AgentState:
167
+ """
168
+ Get the current state as an enum.
169
+
170
+ Returns:
171
+ Current AgentState enum
172
+ """
173
+ return self._state
174
+
175
+ def is_idle(self) -> bool:
176
+ """Check if agent is in IDLE state."""
177
+ return self._state == AgentState.IDLE
178
+
179
+ def is_processing(self) -> bool:
180
+ """Check if agent is in PROCESSING state."""
181
+ return self._state == AgentState.PROCESSING
182
+
183
+ def is_success(self) -> bool:
184
+ """Check if agent is in SUCCESS state."""
185
+ return self._state == AgentState.SUCCESS
186
+
187
+ def is_error(self) -> bool:
188
+ """Check if agent is in ERROR state."""
189
+ return self._state == AgentState.ERROR
190
+
191
+ def reset(self) -> None:
192
+ """Reset state to IDLE."""
193
+ self.transition_to(AgentState.IDLE)
194
+
195
+
196
  class ToolCategory(Enum):
197
  """Categories of tools for organization"""
198
  INFRASTRUCTURE = "infrastructure"
 
275
  self.current_role = None
276
  self.legacy_mode = True
277
 
278
+ # Brain state - use AgentStateMachine for state management
279
+ self.brain_state = BrainState.IDLE # Legacy brain state
280
+ self.state_machine = AgentStateMachine(agent_name or "cain") # New state machine
281
  self.tools: Dict[str, Tool] = {}
282
  self.memory: Dict[str, Any] = {}
283
  self.last_action = None
 
530
  }
531
 
532
  try:
533
+ # Transition to PROCESSING state
534
+ self.state_machine.transition_to(AgentState.PROCESSING)
535
+ self.brain_state = BrainState.EXECUTING
536
+
537
  result = self.tools[tool_name].execute(*args, **kwargs)
538
  self.last_action = tool_name
539
  self.last_action_time = datetime.utcnow().isoformat()
540
+
541
+ # Transition to SUCCESS state
542
+ self.state_machine.transition_to(AgentState.SUCCESS)
543
+
544
+ # Return to IDLE after successful completion
545
+ self.state_machine.transition_to(AgentState.IDLE)
546
+ self.brain_state = BrainState.IDLE
547
+
548
  return result
549
  except Exception as e:
550
+ # Transition to ERROR state
551
+ self.state_machine.transition_to(AgentState.ERROR)
552
+ self.brain_state = BrainState.ERROR
553
  return {
554
  "success": False,
555
  "error": str(e),
556
  "tool": tool_name
557
  }
558
+ finally:
559
+ # Ensure we return to IDLE after any operation
560
+ if self.state_machine.get_state_enum() not in (AgentState.ERROR,):
561
+ self.state_machine.transition_to(AgentState.IDLE)
562
+ self.brain_state = BrainState.IDLE
563
 
564
  # ========== Default Tool Implementations ==========
565
 
 
666
  return {
667
  "success": True,
668
  "status": "healthy",
669
+ "state": self.brain_state.value,
670
  "agent": self.agent_name,
671
  "role": self.current_role.value if self.current_role else "unknown",
672
  "tools_count": len(self.get_allowed_tools())
 
678
  "success": True,
679
  "agent": self.agent_name,
680
  "role": self.current_role.value if self.current_role else "unknown",
681
+ "brain_state": self.brain_state.value,
682
+ "agent_state": self.state_machine.get_state(),
683
  "last_action": self.last_action,
684
  "last_action_time": self.last_action_time,
685
  "legacy_mode": self.legacy_mode
 
707
  Returns:
708
  Name of the tool to execute
709
  """
710
+ self.brain_state = BrainState.THINKING
711
 
712
  # Simple decision logic
713
  action = context.get("action", "")
 
726
  else:
727
  tool = "get_status"
728
 
729
+ self.brain_state = BrainState.IDLE
730
  return tool
731
 
732
  def execute_decision(self, context: Dict[str, Any]) -> Any:
 
742
  tool_name = self.think(context)
743
  return self.execute_tool(tool_name, **context)
744
 
745
+ # ========== State Machine Methods ==========
746
+
747
+ def get_state(self) -> str:
748
+ """
749
+ Get the current agent state as a string.
750
+
751
+ Returns:
752
+ Current state value (idle, processing, success, error)
753
+ """
754
+ return self.state_machine.get_state()
755
+
756
+ def get_state_enum(self) -> AgentState:
757
+ """
758
+ Get the current agent state as an enum.
759
+
760
+ Returns:
761
+ Current AgentState enum
762
+ """
763
+ return self.state_machine.get_state_enum()
764
+
765
+ def transition_to(self, state: AgentState) -> bool:
766
+ """
767
+ Manually transition to a specific state.
768
+
769
+ Args:
770
+ state: The AgentState to transition to
771
+
772
+ Returns:
773
+ True if transition was successful
774
+ """
775
+ return self.state_machine.transition_to(state)
776
+
777
  # ========== Utility Methods ==========
778
 
779
  def get_info(self) -> Dict[str, Any]:
 
781
  return {
782
  "agent": self.agent_name,
783
  "role": self.current_role.value if self.current_role else "unknown",
784
+ "brain_state": self.brain_state.value,
785
+ "agent_state": self.state_machine.get_state(),
786
  "legacy_mode": self.legacy_mode,
787
  "tools_registered": len(self.tools),
788
  "tools_allowed": len(self.get_allowed_tools()),
.openclaw/agents/cain_status.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {
2
+ "current_state": "idle",
3
+ "last_updated": "2026-03-14T16:15:44.947651+00:00",
4
+ "agent": "cain"
5
+ }
.openclaw/agents/logs/session-archive.jsonl CHANGED
@@ -1,2 +1,15 @@
1
  {"timestamp": "2026-03-14T04:45:10.399081+00:00", "threshold_days": 7, "dry_run": true, "action": "archive_sessions", "total_files": 0, "archived_count": 0, "skipped_count": 0, "error_count": 0, "status": "no_sessions_found"}
2
  {"timestamp": "2026-03-14T04:45:28.188361+00:00", "threshold_days": 7, "dry_run": true, "action": "archive_sessions", "total_files": 0, "archived_count": 0, "skipped_count": 0, "error_count": 0, "status": "no_sessions_found"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  {"timestamp": "2026-03-14T04:45:10.399081+00:00", "threshold_days": 7, "dry_run": true, "action": "archive_sessions", "total_files": 0, "archived_count": 0, "skipped_count": 0, "error_count": 0, "status": "no_sessions_found"}
2
  {"timestamp": "2026-03-14T04:45:28.188361+00:00", "threshold_days": 7, "dry_run": true, "action": "archive_sessions", "total_files": 0, "archived_count": 0, "skipped_count": 0, "error_count": 0, "status": "no_sessions_found"}
3
+ {"type": "state_change", "state": "processing", "timestamp": "2026-03-14T16:15:25.366226+00:00", "agent": "test_agent"}
4
+ {"type": "state_change", "state": "success", "timestamp": "2026-03-14T16:15:25.366381+00:00", "agent": "test_agent"}
5
+ {"type": "state_change", "state": "error", "timestamp": "2026-03-14T16:15:25.367350+00:00", "agent": "test_agent"}
6
+ {"type": "state_change", "state": "idle", "timestamp": "2026-03-14T16:15:25.368319+00:00", "agent": "test_agent"}
7
+ {"type": "state_change", "state": "processing", "timestamp": "2026-03-14T16:15:25.370340+00:00", "agent": "cain"}
8
+ {"type": "state_change", "state": "error", "timestamp": "2026-03-14T16:15:25.371351+00:00", "agent": "cain"}
9
+ {"type": "state_change", "state": "processing", "timestamp": "2026-03-14T16:15:44.940447+00:00", "agent": "test_agent"}
10
+ {"type": "state_change", "state": "success", "timestamp": "2026-03-14T16:15:44.941499+00:00", "agent": "test_agent"}
11
+ {"type": "state_change", "state": "error", "timestamp": "2026-03-14T16:15:44.942554+00:00", "agent": "test_agent"}
12
+ {"type": "state_change", "state": "idle", "timestamp": "2026-03-14T16:15:44.943556+00:00", "agent": "test_agent"}
13
+ {"type": "state_change", "state": "processing", "timestamp": "2026-03-14T16:15:44.945500+00:00", "agent": "cain"}
14
+ {"type": "state_change", "state": "success", "timestamp": "2026-03-14T16:15:44.946525+00:00", "agent": "cain"}
15
+ {"type": "state_change", "state": "idle", "timestamp": "2026-03-14T16:15:44.947587+00:00", "agent": "cain"}