deer-agent-framework 0.1a2__tar.gz → 0.1a4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/PKG-INFO +20 -8
  2. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/README.md +13 -5
  3. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/__init__.py +4 -2
  4. deer_agent_framework-0.1a4/deer/builtins/python_manager/agent.py +31 -0
  5. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/core/agent.py +220 -79
  6. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/core/ui.py +11 -5
  7. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/drivers/base_driver.py +82 -14
  8. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/drivers/gemini_driver.py +27 -7
  9. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/drivers/ollama_driver.py +9 -13
  10. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/executor/executor.py +4 -4
  11. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/main.py +28 -17
  12. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/planner/planner.py +5 -3
  13. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/prompts/planner.py +2 -2
  14. deer_agent_framework-0.1a4/deer/states/__init__.py +2 -0
  15. deer_agent_framework-0.1a4/deer/states/base.py +50 -0
  16. deer_agent_framework-0.1a4/deer/states/parallel_state_manager.py +129 -0
  17. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/tools/__init__.py +1 -1
  18. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/tools/base.py +8 -2
  19. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/tools/builtin/__init__.py +6 -0
  20. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/tools/builtin/file_manager.py +45 -17
  21. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/tools/builtin/git_manager.py +11 -10
  22. deer_agent_framework-0.1a4/deer/tools/builtin/json_editor.py +150 -0
  23. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/tools/builtin/memory_manager.py +6 -5
  24. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/tools/builtin/network_manager.py +2 -1
  25. deer_agent_framework-0.1a4/deer/tools/builtin/python_editor.py +552 -0
  26. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/tools/builtin/runtime_manager.py +8 -7
  27. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/tools/builtin/search_manager.py +14 -13
  28. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/tools/builtin/structured_data_inspector.py +6 -6
  29. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/tools/builtin/system_inspector.py +5 -4
  30. deer_agent_framework-0.1a4/deer/tools/builtin/toml_editor.py +122 -0
  31. deer_agent_framework-0.1a4/deer/tools/builtin/xml_editor.py +126 -0
  32. deer_agent_framework-0.1a4/deer/tools/builtin/yaml_editor.py +150 -0
  33. deer_agent_framework-0.1a4/deer/tools/presets.py +71 -0
  34. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/tools/registry.py +2 -1
  35. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/validator/plan_validator.py +4 -4
  36. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/validator/rules.py +4 -4
  37. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer_agent_framework.egg-info/PKG-INFO +20 -8
  38. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer_agent_framework.egg-info/SOURCES.txt +11 -4
  39. deer_agent_framework-0.1a4/deer_agent_framework.egg-info/requires.txt +12 -0
  40. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/pyproject.toml +7 -3
  41. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/tests/test_plan_validator.py +3 -3
  42. deer_agent_framework-0.1a4/tests/test_python_editor.py +68 -0
  43. deer_agent_framework-0.1a2/deer/builtins/python_manager/agent.py +0 -29
  44. deer_agent_framework-0.1a2/deer/builtins/python_manager/tools.py +0 -112
  45. deer_agent_framework-0.1a2/deer_agent_framework.egg-info/requires.txt +0 -8
  46. deer_agent_framework-0.1a2/tests/test_azure_driver.py +0 -56
  47. deer_agent_framework-0.1a2/tests/test_openai_driver.py +0 -69
  48. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/LICENSE +0 -0
  49. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/builtins/__init__.py +0 -0
  50. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/core/__init__.py +0 -0
  51. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/drivers/__init__.py +0 -0
  52. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/drivers/azure_driver.py +0 -0
  53. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/drivers/openai_driver.py +0 -0
  54. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/executor/__init__.py +0 -0
  55. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/executor/logic.py +0 -0
  56. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/executor/logic_secure.py +0 -0
  57. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/planner/__init__.py +0 -0
  58. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/prompts/__init__.py +0 -0
  59. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/prompts/error_explain.py +0 -0
  60. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/prompts/goal_improvement.py +0 -0
  61. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/prompts/goal_validation.py +0 -0
  62. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/prompts/humanizer.py +0 -0
  63. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/prompts/response_improvement.py +0 -0
  64. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/schema/__init__.py +0 -0
  65. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/schema/io.py +0 -0
  66. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/schema/plan.py +0 -0
  67. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/tools/decorators.py +0 -0
  68. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/tracing/__init__.py +0 -0
  69. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/tracing/logging_config.py +0 -0
  70. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/tracing/store.py +0 -0
  71. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/utils/__init__.py +0 -0
  72. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/utils/console.py +0 -0
  73. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/utils/plots/__init__.py +0 -0
  74. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/utils/plots/plot_traces.py +0 -0
  75. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer/validator/__init__.py +0 -0
  76. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer_agent_framework.egg-info/dependency_links.txt +0 -0
  77. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer_agent_framework.egg-info/entry_points.txt +0 -0
  78. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/deer_agent_framework.egg-info/top_level.txt +0 -0
  79. {deer_agent_framework-0.1a2 → deer_agent_framework-0.1a4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deer-agent-framework
3
- Version: 0.1a2
3
+ Version: 0.1a4
4
4
  Summary: DEER - Deterministic Executable Engine for Runtime-agents
5
5
  Author: Yeison
6
6
  License: BSD-2-Clause
@@ -20,8 +20,12 @@ Requires-Python: >=3.12
20
20
  Description-Content-Type: text/markdown
21
21
  License-File: LICENSE
22
22
  Requires-Dist: pydantic>=2.0
23
- Requires-Dist: rich
24
- Requires-Dist: prompt_toolkit
23
+ Requires-Dist: rich>=15.0.0
24
+ Requires-Dist: prompt_toolkit>=3.0.52
25
+ Requires-Dist: libcst>=1.8.6
26
+ Requires-Dist: tomlkit>=0.15.0
27
+ Requires-Dist: ruamel.yaml>=0.19.1
28
+ Requires-Dist: lxml>=6.1.1
25
29
  Provides-Extra: debug
26
30
  Requires-Dist: matplotlib>=3.10.9; extra == "debug"
27
31
  Requires-Dist: pandas>=3.0.3; extra == "debug"
@@ -68,8 +72,9 @@ Forget about telling the AI to "act like a specialist." Define a high-level `Det
68
72
  ```python
69
73
  from pathlib import Path
70
74
  from deer.core.agent import DeterministicAgent
75
+ from deer.states import ParallelGitStateManager
71
76
  from deer.drivers import get_driver_from_parser
72
- from .tools import registry
77
+ from deer.tools import Preset
73
78
 
74
79
  agent = DeterministicAgent(
75
80
  description="Python Architecture Specialist",
@@ -78,10 +83,11 @@ agent = DeterministicAgent(
78
83
  "in advanced module resolution and dependency management."
79
84
  ),
80
85
  driver=get_driver_from_parser(),
81
- registry=registry,
86
+ tool_registry=Preset.CODE_REPAIR | Preset.CODE_EDITOR | Preset.DATA_ANALYST,
82
87
  jail_path=Path.cwd() / "sandbox", # Strict security boundary
83
88
  format_response="markdown",
84
- max_retries=5
89
+ max_retries=5,
90
+ state_manager=ParallelGitStateManager(),
85
91
  )
86
92
 
87
93
  if __name__ == "__main__":
@@ -95,10 +101,10 @@ Combine built-in managers (File, Git, Search) into a single, cohesive unit of ex
95
101
  from deer.tools.registry import ToolRegistry
96
102
  from deer.tools.builtin import FileManager, GitManager, SearchManager
97
103
 
98
- registry = ToolRegistry()
104
+ tool_registry = ToolRegistry()
99
105
 
100
106
  # Add specialized capabilities with zero-config
101
- registry.register(
107
+ tool_registry.register(
102
108
  FileManager(),
103
109
  GitManager(),
104
110
  SearchManager(),
@@ -171,6 +177,12 @@ Every decision, tool call, and validation step is recorded. This allows for deep
171
177
 
172
178
  ## Installation
173
179
 
180
+ From PyPI:
181
+ ```bash
182
+ pip install deer-agent-framework
183
+ ```
184
+
185
+ From GitHub (development version):
174
186
  ```bash
175
187
  pip install git+https://github.com/dunderlab/deer-agent-framework.git
176
188
  ```
@@ -38,8 +38,9 @@ Forget about telling the AI to "act like a specialist." Define a high-level `Det
38
38
  ```python
39
39
  from pathlib import Path
40
40
  from deer.core.agent import DeterministicAgent
41
+ from deer.states import ParallelGitStateManager
41
42
  from deer.drivers import get_driver_from_parser
42
- from .tools import registry
43
+ from deer.tools import Preset
43
44
 
44
45
  agent = DeterministicAgent(
45
46
  description="Python Architecture Specialist",
@@ -48,10 +49,11 @@ agent = DeterministicAgent(
48
49
  "in advanced module resolution and dependency management."
49
50
  ),
50
51
  driver=get_driver_from_parser(),
51
- registry=registry,
52
+ tool_registry=Preset.CODE_REPAIR | Preset.CODE_EDITOR | Preset.DATA_ANALYST,
52
53
  jail_path=Path.cwd() / "sandbox", # Strict security boundary
53
54
  format_response="markdown",
54
- max_retries=5
55
+ max_retries=5,
56
+ state_manager=ParallelGitStateManager(),
55
57
  )
56
58
 
57
59
  if __name__ == "__main__":
@@ -65,10 +67,10 @@ Combine built-in managers (File, Git, Search) into a single, cohesive unit of ex
65
67
  from deer.tools.registry import ToolRegistry
66
68
  from deer.tools.builtin import FileManager, GitManager, SearchManager
67
69
 
68
- registry = ToolRegistry()
70
+ tool_registry = ToolRegistry()
69
71
 
70
72
  # Add specialized capabilities with zero-config
71
- registry.register(
73
+ tool_registry.register(
72
74
  FileManager(),
73
75
  GitManager(),
74
76
  SearchManager(),
@@ -141,6 +143,12 @@ Every decision, tool call, and validation step is recorded. This allows for deep
141
143
 
142
144
  ## Installation
143
145
 
146
+ From PyPI:
147
+ ```bash
148
+ pip install deer-agent-framework
149
+ ```
150
+
151
+ From GitHub (development version):
144
152
  ```bash
145
153
  pip install git+https://github.com/dunderlab/deer-agent-framework.git
146
154
  ```
@@ -1,6 +1,8 @@
1
1
  import os
2
2
  from pathlib import Path
3
3
 
4
+ __version__ = "0.1.9"
5
+
4
6
 
5
7
  def load_env_file(env_path: Path) -> None:
6
8
  """Reads a .env file and loads its variables into os.environ natively."""
@@ -28,9 +30,9 @@ def load_env_file(env_path: Path) -> None:
28
30
 
29
31
  # Your configuration logic remains clean and dependency-free
30
32
  local_env = Path(".env")
31
- user_env = Path.home() / "deer.env"
33
+ user_env = Path.home() / ".deer.env"
32
34
 
33
35
  if local_env.exists():
34
36
  load_env_file(local_env)
35
37
  elif user_env.exists():
36
- load_env_file(user_env)
38
+ load_env_file(user_env)
@@ -0,0 +1,31 @@
1
+ from pathlib import Path
2
+
3
+ from deer.core.agent import DeterministicAgent
4
+ from deer.states import ParallelGitStateManager
5
+ from deer.drivers import get_driver_from_parser
6
+ from deer.tools import Preset
7
+
8
+ agent = DeterministicAgent(
9
+ description="AI specialist in Python architecture, runtime module resolution, and dependency management.",
10
+ identity=(
11
+ "You are an elite AI Agent operating as a Principal Python Architect and Core Ecosystem Specialist. "
12
+ "You provide authoritative, deterministic guidance on advanced module resolution, runtime execution, "
13
+ "dependency isolation, and package distribution. Your expertise spans the entire Python lifecycle: "
14
+ "from engineering scalable code structures to resolving complex import mechanisms and optimizing "
15
+ "deployment pipelines across public or internal repositories."
16
+ ),
17
+ driver=get_driver_from_parser(),
18
+ tool_registry=Preset.CODE_REPAIR | Preset.CODE_EDITOR | Preset.DATA_ANALYST,
19
+ jail_path=Path.cwd(),
20
+ format_response="markdown",
21
+ max_retries=5,
22
+ state_manager=ParallelGitStateManager(),
23
+ )
24
+
25
+
26
+ def main():
27
+ agent.repl()
28
+
29
+
30
+ if __name__ == "__main__":
31
+ main()
@@ -5,10 +5,16 @@ import pickle
5
5
  import sys
6
6
  from typing import Callable
7
7
  from datetime import datetime
8
+ import os
9
+ import subprocess
10
+ from pathlib import Path
8
11
 
9
12
  from prompt_toolkit import prompt
10
13
  from prompt_toolkit.formatted_text import HTML
11
14
  from prompt_toolkit.history import InMemoryHistory
15
+ from prompt_toolkit.completion import WordCompleter
16
+ from rich.console import Console
17
+ from rich.markdown import Markdown
12
18
 
13
19
  from deer.prompts import (
14
20
  RESPONSE_IMPROVEMENT_PROMPT,
@@ -17,6 +23,7 @@ from deer.prompts import (
17
23
  GOAL_VERIFIER_PROMPT,
18
24
  VERIFIER_JUDGE_PROMPT,
19
25
  )
26
+ from deer import __version__
20
27
  from deer.drivers.base_driver import LLMDriver
21
28
  from deer.planner.planner import Planner
22
29
  from deer.validator.plan_validator import PlanValidator
@@ -24,11 +31,19 @@ from deer.executor.executor import Executor
24
31
  from deer.core.ui import WELCOME_MESSAGE
25
32
  from deer.tools.registry import ToolRegistry, default_registry
26
33
  from deer.schema.io import AgentInput, AgentOutput
27
- from rich.console import Console
28
- from rich.markdown import Markdown
34
+ from deer.states import BaseStateManager
29
35
 
30
36
  logger = logging.getLogger("DEER")
31
- prompt_history = InMemoryHistory()
37
+ # prompt_history = InMemoryHistory()
38
+ # prompt_history = '.deer.history'
39
+
40
+
41
+ COMMANDS = {
42
+ "/tools": "Show tools and capabilities",
43
+ "/clear": "Clear the console and reset the session",
44
+ "/exit": "Terminate the session",
45
+ "/rollback": "Revert the system to the last stable state",
46
+ }
32
47
 
33
48
 
34
49
  class DeterministicAgent:
@@ -38,42 +53,53 @@ class DeterministicAgent:
38
53
  identity: str = "DeterministicAgent",
39
54
  max_retries: int = 3,
40
55
  driver: LLMDriver | None = None,
41
- registry: ToolRegistry | None = None,
56
+ tool_registry: ToolRegistry | None = None,
42
57
  format_response: str = "plaintext",
43
- rollback: Callable = None,
58
+ state_manager: BaseStateManager = None,
44
59
  jail_path: str = None,
45
60
  ) -> None:
46
61
  assert format_response in {"markdown", "plaintext"}
47
62
 
63
+ self.history_filename = ".deer.history"
64
+ self.prompt_history = self.load_history()
65
+
48
66
  self.identity = identity
49
67
  self.description = description
50
68
  self.driver = driver
51
- self.registry = registry or default_registry()
69
+
70
+ if isinstance(tool_registry, set):
71
+ tr = ToolRegistry()
72
+ tr.register(*[tool() for tool in tool_registry])
73
+ tool_registry = tr
74
+
75
+ self.tool_registry = tool_registry or default_registry()
76
+
52
77
  self.console = Console()
53
78
 
54
79
  self.executor = Executor(
55
- registry=self.registry,
80
+ tool_registry=self.tool_registry,
56
81
  )
57
82
 
58
83
  self.validator = PlanValidator(
59
- registry=self.registry,
84
+ tool_registry=self.tool_registry,
60
85
  )
61
86
 
62
87
  self.planner = Planner(
63
88
  identity=identity,
64
89
  driver=driver,
65
- registry=self.registry,
90
+ tool_registry=self.tool_registry,
66
91
  format_response=format_response,
67
92
  )
68
93
 
94
+ self.state_manager = state_manager
95
+
69
96
  assert (
70
- max_retries > 0
97
+ max_retries > 0
71
98
  ), f"max_tries_for_plan must be positive, got {max_retries}"
72
99
  self.max_retries = max_retries
73
100
 
74
101
  self.history = []
75
102
  self.trace = []
76
- self.rollback_function = rollback
77
103
 
78
104
  if jail_path:
79
105
  self.set_jail(jail_path)
@@ -84,17 +110,17 @@ class DeterministicAgent:
84
110
  )
85
111
  logger.info(f"Identity: {self.identity}")
86
112
  logger.info(
87
- f"Available tools:\n{self.registry.describe(include_state_modifying=True)}"
113
+ f"Available tools:\n{self.tool_registry.describe(include_state_modifying=True)}"
88
114
  )
89
115
  logger.info(f"Authorized filesystem scope is restricted to: {jail_path}")
90
116
 
91
117
  def set_jail(self, jail_path):
92
- self.registry.set_jail(jail_path)
118
+ self.tool_registry.set_jail(jail_path)
93
119
 
94
120
  def should_verify(self, plan) -> bool:
95
121
  return any(step.tool for step in plan.steps)
96
122
 
97
- def execute_plan(
123
+ def generate_and_execute_plan(
98
124
  self,
99
125
  agent_input: AgentInput,
100
126
  feedback: {},
@@ -113,7 +139,7 @@ class DeterministicAgent:
113
139
  feedback = {"Planning error": str(planning_error)}
114
140
  last_error_message = str(planning_error)
115
141
  return None, feedback, last_error_message, None
116
- logger.info(f"Plan generated")
142
+ logger.info(f"1. Plan generated")
117
143
  logger.debug(f"Plan steps:")
118
144
  for step in plan.steps:
119
145
  logger.debug(f"\tstep: {step.id}")
@@ -129,7 +155,7 @@ class DeterministicAgent:
129
155
  feedback = {"verification error": str(validation_error)}
130
156
  last_error_message = str(validation_error)
131
157
  return None, feedback, last_error_message, plan
132
- logger.info(f"Plan verified")
158
+ logger.info(f"2. Plan verified")
133
159
 
134
160
  try:
135
161
  response = self.executor.execute(
@@ -141,22 +167,28 @@ class DeterministicAgent:
141
167
  feedback = {"execution error": str(execution_error)}
142
168
  last_error_message = str(execution_error)
143
169
  return None, feedback, last_error_message, plan
144
- logger.info(f"Plan executed")
170
+ logger.info(f"3. Plan executed")
145
171
 
146
172
  return response, feedback, last_error_message, plan
147
173
 
148
174
  def run(self, agent_input: AgentInput) -> AgentOutput:
149
175
  feedback = {}
150
176
  run_trace = {}
151
-
152
177
  response_validation = None
178
+ last_error_message = None
179
+
180
+ self.state_snapshot()
153
181
 
154
182
  for attempt_idx in range(self.max_retries):
183
+ self.state_restore()
184
+
155
185
  logger.debug(f"Attempt {attempt_idx + 1} of {self.max_retries}")
156
186
 
157
187
  run_trace[f"Attempt-{attempt_idx + 1}"] = {}
158
188
 
159
189
  for solution_idx in range(self.max_retries):
190
+ self.state_restore()
191
+
160
192
  logger.debug(
161
193
  f"Solution planning {solution_idx + 1} of {self.max_retries}"
162
194
  )
@@ -164,8 +196,8 @@ class DeterministicAgent:
164
196
  f"Solution-{solution_idx + 1}"
165
197
  ] = datetime.now()
166
198
 
167
- response, feedback, last_error_message, plan = self.execute_plan(
168
- agent_input, feedback
199
+ response, feedback, last_error_message, solution_steps = (
200
+ self.generate_and_execute_plan(agent_input, feedback)
169
201
  )
170
202
  if response is None:
171
203
  continue
@@ -176,15 +208,10 @@ class DeterministicAgent:
176
208
  f"Agent failed to produce a response after {self.max_retries} attempts"
177
209
  )
178
210
  if last_error_message:
179
- last_error_message_explained = self.explain_error(
180
- last_error_message
181
- )
182
- return AgentOutput(
183
- result=last_error_message_explained,
184
- trace=self.executor.trace_store.get_trace(),
185
- )
211
+ feedback = {"Previous Error": last_error_message}
212
+ continue
186
213
 
187
- if not self.should_verify(plan):
214
+ if not self.should_verify(solution_steps):
188
215
  break
189
216
 
190
217
  feedback_ = {}
@@ -209,7 +236,7 @@ class DeterministicAgent:
209
236
  agent_verifier_input = AgentInput(
210
237
  goal=goal_verifier_prompt, payload=payload_verifier
211
238
  )
212
- response_validation, feedback_, _, validation_plan = self.execute_plan(
239
+ response_validation, feedback_, _, _ = self.generate_and_execute_plan(
213
240
  agent_verifier_input, feedback_, include_state_modifying=False
214
241
  )
215
242
 
@@ -235,7 +262,16 @@ class DeterministicAgent:
235
262
  if response.validated:
236
263
  break
237
264
 
238
- self.rollback()
265
+ if response is None:
266
+ logger.warning(
267
+ f"Agent failed to produce a response after {self.max_retries} attempts"
268
+ )
269
+ if last_error_message:
270
+ last_error_message_explained = self.explain_error(last_error_message)
271
+ return AgentOutput(
272
+ result=last_error_message_explained,
273
+ trace=self.executor.trace_store.get_trace(),
274
+ )
239
275
 
240
276
  self.history.extend(
241
277
  [
@@ -261,21 +297,29 @@ class DeterministicAgent:
261
297
  )
262
298
 
263
299
  self.planner.goal = agent_input.goal
300
+ self.state_purge_engine()
264
301
  return response
265
302
 
266
- def send(self, msg):
303
+ def send(self, message, print_chat: bool = False):
267
304
  user_input = AgentInput(
268
- goal=msg,
305
+ goal=message,
269
306
  payload={
270
307
  "chat_history": self.history,
271
308
  },
272
309
  )
273
- response = self.run(user_input)
274
310
 
311
+ if print_chat:
312
+ print(f">>> {message}")
313
+
314
+ response = self.run(user_input)
275
315
  if isinstance(response.result, dict):
276
316
  response.result = self.humanize_result(response)
277
317
  else:
278
318
  response.result = self.improve_result(response)
319
+
320
+ if print_chat:
321
+ print(f" {response.result}")
322
+
279
323
  return response
280
324
 
281
325
  def pretty_print(self, out: str):
@@ -285,12 +329,17 @@ class DeterministicAgent:
285
329
  self.history = []
286
330
  self.trace = []
287
331
 
288
- def rollback(self):
289
- logger.debug(f"Rolling back agent")
290
- if self.rollback_function:
291
- self.rollback_function()
292
- else:
293
- logger.debug(f"No rollback function defined")
332
+ def state_snapshot(self):
333
+ if self.state_manager:
334
+ self.state_manager.set_reference_state(self.tool_registry.jail_path)
335
+
336
+ def state_restore(self):
337
+ if self.state_manager:
338
+ self.state_manager.rollback_to_previous_state()
339
+
340
+ def state_purge_engine(self):
341
+ if self.state_manager:
342
+ self.state_manager.purge_engine()
294
343
 
295
344
  def humanize_result(self, output: AgentOutput) -> str:
296
345
  trace_str = ""
@@ -318,7 +367,7 @@ class DeterministicAgent:
318
367
 
319
368
  def explain_error(self, error: str):
320
369
  error_prompt = ERROR_EXPLAIN_PROMPT.format(
321
- error=error, tools=self.registry.describe()
370
+ error=error, tools=self.tool_registry.describe()
322
371
  )
323
372
  error_explained = self.driver.generate_text(error_prompt)
324
373
  return error_explained
@@ -376,7 +425,7 @@ class DeterministicAgent:
376
425
 
377
426
  def save_trace(self, filename: str):
378
427
  obj = {
379
- "tools": list(self.registry.list_tools()),
428
+ "tools": list(self.tool_registry.list_tools()),
380
429
  "trace": self.trace,
381
430
  "history": self.history,
382
431
  }
@@ -388,75 +437,167 @@ class DeterministicAgent:
388
437
  pickle.dump(obj, f)
389
438
 
390
439
  def show_welcome(self):
440
+
441
+ commands_formated = ""
442
+ for command in COMMANDS:
443
+ commands_formated += (
444
+ f"- {('`'+command+'`').ljust(12, ' ')} → {COMMANDS[command]}\n "
445
+ )
446
+
391
447
  self.console.clear()
392
- self.pretty_print(WELCOME_MESSAGE)
448
+ self.pretty_print(
449
+ WELCOME_MESSAGE.format(version=__version__, commands=commands_formated)
450
+ )
393
451
  self.pretty_print(
394
452
  f">**Agent Profile** \n"
395
453
  f"*{self.description}* \n"
396
- f"root: {self.registry.jail_path} \n"
454
+ f"root: {self.tool_registry.jail_path} \n"
397
455
  f"{self.driver}: {self.driver.model_name} \n"
398
456
  )
399
457
  print("\n")
400
458
 
459
+ def run_command(self, command):
460
+ match command:
461
+ case "/exit":
462
+ sys.exit(0)
463
+
464
+ case "/clear":
465
+ self.clear_history()
466
+ self.console.clear()
467
+ self.pretty_print(f">**Agent Profile** \n*{self.description}*")
468
+ print("\n")
469
+
470
+ case "/tools":
471
+ self.pretty_print(
472
+ self.tool_registry.describe(
473
+ include_state_modifying=True, markdown=True
474
+ )
475
+ )
476
+ print("\n")
477
+
478
+ case "/rollback":
479
+ self.state_restore()
480
+ self.pretty_print(
481
+ "**Rollback executed.** System reverted to the last stable state."
482
+ )
483
+ print("\n")
484
+
401
485
  def repl(self):
402
486
  logger.setLevel(logging.CRITICAL)
403
487
  self.show_welcome()
404
488
 
405
489
  while True:
406
- msg = prompt(
407
- HTML("<ansicyan><b>&gt;&gt;&gt; </b></ansicyan>"),
408
- history=prompt_history,
409
- )
410
-
411
- self.console.status("[cyan]Planning...", spinner="dots")
412
-
413
- match msg.strip():
414
- case "exit;":
415
- sys.exit(0)
490
+ try:
491
+ msg = prompt(
492
+ HTML("<ansicyan><b>&gt;&gt;&gt; </b></ansicyan>"),
493
+ history=self.prompt_history,
494
+ completer=self.completer,
495
+ )
496
+ self.save_history()
497
+ except KeyboardInterrupt:
498
+ continue
499
+ except EOFError:
500
+ break
416
501
 
417
- case "clear;":
418
- self.clear_history()
419
- self.console.clear()
420
- self.pretty_print(f">**Agent Profile** \n*{self.description}*")
421
- print("\n")
502
+ msg = msg.strip()
503
+ if not msg:
504
+ continue
422
505
 
423
- case "tools;":
424
- self.pretty_print(
425
- self.registry.describe(
426
- include_state_modifying=True, markdown=True
506
+ match msg:
507
+ case command if command in COMMANDS:
508
+ self.run_command(command)
509
+
510
+ case command if command.startswith("!"):
511
+ # Remove the '!' from the start to get only the command
512
+ cmd_to_run = command[1:].strip()
513
+
514
+ try:
515
+ # Execute the command
516
+ result = subprocess.run(
517
+ cmd_to_run,
518
+ shell=True,
519
+ capture_output=True,
520
+ text=True,
521
+ check=True,
427
522
  )
428
- )
429
- print("\n")
430
-
431
- case "rollback;":
432
- self.rollback()
433
- self.pretty_print(
434
- "**Rollback executed.** System reverted to the last stable state."
435
- )
436
- print("\n")
523
+ print(result.stdout)
524
+ except subprocess.CalledProcessError as e:
525
+ print(f"Error executing command: {e.stderr}")
526
+ except Exception as e:
527
+ print(f"An unexpected error occurred: {e}")
437
528
 
438
529
  case _:
439
- if not msg or msg.endswith(";"):
530
+ if msg.startswith("/"):
531
+ logging.warning(
532
+ f"Command `{msg}` not recognized. Did you mean one of these: {list(COMMANDS)}"
533
+ )
440
534
  continue
441
-
442
535
  with self.console.status(
443
536
  "[dim]processing request[/]",
444
537
  spinner="point",
445
538
  spinner_style="dim",
446
539
  ):
447
- output = self.send(msg)
448
- self.pretty_print(f" {output.text}")
449
- print("\n")
450
-
451
- def iterate_debug(self, chain_messages: list[str], repetitions: int, path: str):
540
+ try:
541
+ output = self.send(msg)
542
+ self.pretty_print(f" {output.text}")
543
+
544
+ print("\n")
545
+ except KeyboardInterrupt:
546
+ self.console.print("[dim]Command aborted by user[/]\n")
547
+ self.state_restore()
548
+ continue
549
+ except EOFError:
550
+ break
551
+
552
+ def iterate_debug(
553
+ self,
554
+ chain_messages: list[str],
555
+ repetitions: int,
556
+ path: str,
557
+ callback: Callable = None,
558
+ ):
452
559
  logger.setLevel(logging.DEBUG)
560
+ os.makedirs(path, exist_ok=True)
453
561
  for i in range(repetitions):
454
562
  self.clear_history()
455
- self.rollback()
456
563
  name = f"{self.driver.model_name}-{datetime.now().timestamp()}"
564
+
565
+ formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s")
566
+ file_handler = logging.FileHandler(f"{path}/{name}.log")
567
+ file_handler.setFormatter(formatter)
568
+ logger.addHandler(file_handler)
569
+ logger.info("Starting debug mode")
570
+ logger.info(f"Model: {self.driver.model_name}")
571
+ logger.info("Tools:")
572
+ logger.info(self.tool_registry.describe(include_state_modifying=True))
573
+ logger.removeHandler(file_handler)
574
+ file_handler.close()
575
+
457
576
  self.generate_chat_log(
458
577
  chain_messages,
459
578
  print_chat=True,
460
579
  save_log=f"{path}/{name}.log",
461
580
  )
462
581
  self.save_trace(f"{path}/{name}.trace")
582
+ if callback:
583
+ callback()
584
+
585
+ def load_history(self):
586
+ history_file = Path.home() / self.history_filename
587
+ if not history_file.exists():
588
+ self.prompt_history = InMemoryHistory()
589
+ return self.prompt_history
590
+
591
+ with open(history_file, "rb") as f:
592
+ self.prompt_history = pickle.load(f)
593
+ return self.prompt_history
594
+
595
+ def save_history(self):
596
+ history_file = Path.home() / self.history_filename
597
+ with open(history_file, "wb") as f:
598
+ pickle.dump(self.prompt_history, f)
599
+
600
+ @property
601
+ def completer(self):
602
+ internal_commands = ["/tools", "/clear", "/exit", "/rollback"]
603
+ return WordCompleter(internal_commands, ignore_case=False)