deer-agent-framework 0.2a1__tar.gz → 0.2.3__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 (69) hide show
  1. {deer_agent_framework-0.2a1/deer_agent_framework.egg-info → deer_agent_framework-0.2.3}/PKG-INFO +1 -1
  2. {deer_agent_framework-0.2a1/deer/builtins/agents → deer_agent_framework-0.2.3/deer/builtins_agents}/__init__.py +1 -1
  3. {deer_agent_framework-0.2a1/deer/builtins/agents → deer_agent_framework-0.2.3/deer/builtins_agents}/operating_system.py +1 -1
  4. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/cli/main.py +6 -8
  5. deer_agent_framework-0.2.3/deer/cli/repl.py +368 -0
  6. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/core/agent.py +50 -9
  7. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/core/executor.py +1 -1
  8. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/core/planner.py +2 -2
  9. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/core/validator.py +1 -1
  10. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/drivers/base.py +6 -4
  11. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/drivers/ollama.py +8 -5
  12. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/memory/vector.py +1 -1
  13. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/models.py +3 -3
  14. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3/deer_agent_framework.egg-info}/PKG-INFO +1 -1
  15. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer_agent_framework.egg-info/SOURCES.txt +3 -3
  16. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/pyproject.toml +1 -1
  17. deer_agent_framework-0.2a1/deer/cli/repl.py +0 -263
  18. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/LICENSE +0 -0
  19. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/README.md +0 -0
  20. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/__init__.py +0 -0
  21. {deer_agent_framework-0.2a1/deer/builtins/agents → deer_agent_framework-0.2.3/deer/builtins_agents}/python_architect.py +0 -0
  22. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/cli/__init__.py +0 -0
  23. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/cli/parser.py +0 -0
  24. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/core/__init__.py +0 -0
  25. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/drivers/__init__.py +0 -0
  26. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/drivers/azure.py +0 -0
  27. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/drivers/gemini.py +0 -0
  28. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/drivers/openai.py +0 -0
  29. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/evals/__init__.py +0 -0
  30. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/evals/runner.py +0 -0
  31. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/memory/__init__.py +0 -0
  32. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/tools/__init__.py +0 -0
  33. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/tools/base.py +0 -0
  34. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/tools/decorators.py +0 -0
  35. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/tools/presets.py +0 -0
  36. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/tools/providers/__init__.py +0 -0
  37. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/tools/providers/code_searcher.py +0 -0
  38. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/tools/providers/csv_manager.py +0 -0
  39. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/tools/providers/dependency_analyzer.py +0 -0
  40. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/tools/providers/file_manager.py +0 -0
  41. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/tools/providers/git_manager.py +0 -0
  42. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/tools/providers/http_client.py +0 -0
  43. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/tools/providers/json_editor.py +0 -0
  44. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/tools/providers/logic_provider.py +0 -0
  45. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/tools/providers/process_manager.py +0 -0
  46. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/tools/providers/python_struct_editor.py +0 -0
  47. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/tools/providers/runtime_manager.py +0 -0
  48. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/tools/providers/sqlite_manager.py +0 -0
  49. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/tools/providers/system_observer.py +0 -0
  50. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/tools/providers/toml_editor.py +0 -0
  51. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/tools/providers/xml_editor.py +0 -0
  52. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/tools/providers/yaml_editor.py +0 -0
  53. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/tools/registry.py +0 -0
  54. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/tools/schemas.py +0 -0
  55. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/utils/__init__.py +0 -0
  56. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/utils/console.py +0 -0
  57. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/utils/plots/__init__.py +0 -0
  58. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer/utils/plots/plot_traces.py +0 -0
  59. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer_agent_framework.egg-info/dependency_links.txt +0 -0
  60. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer_agent_framework.egg-info/entry_points.txt +0 -0
  61. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer_agent_framework.egg-info/requires.txt +0 -0
  62. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/deer_agent_framework.egg-info/top_level.txt +0 -0
  63. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/setup.cfg +0 -0
  64. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/tests/test_driver.py +0 -0
  65. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/tests/test_executor.py +0 -0
  66. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/tests/test_planner.py +0 -0
  67. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/tests/test_tools.py +0 -0
  68. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/tests/test_tools_definition.py +0 -0
  69. {deer_agent_framework-0.2a1 → deer_agent_framework-0.2.3}/tests/test_vector_memory.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deer-agent-framework
3
- Version: 0.2a1
3
+ Version: 0.2.3
4
4
  Summary: DEER - Deterministic Executable Engine for Runtime-agents
5
5
  Author: Yeison
6
6
  License: BSD-2-Clause
@@ -1,6 +1,6 @@
1
1
  import os
2
2
 
3
- available_agents = {
3
+ agents = {
4
4
  "python_architect": os.path.join(os.path.dirname(__file__), "python_architect.py"),
5
5
  "operating_system": os.path.join(os.path.dirname(__file__), "operating_system.py"),
6
6
  }
@@ -15,7 +15,7 @@ registry = ToolRegistry(Preset.SYSTEM_ADMIN | {HTTPClient})
15
15
  working_dir = get_path_from_parser() or Path.cwd()
16
16
 
17
17
  agent = DeterministicAgent(
18
- description="OS Agent",
18
+ description="Advanced OS Agent for secure system management.",
19
19
  identity=(
20
20
  "You are a capable Operating System Agent. "
21
21
  "Your goal is to assist the user by managing the system, executing tasks, and solving problems efficiently. "
@@ -1,13 +1,13 @@
1
1
  from rich.text import Text
2
2
  from deer.utils.console import console, error, info
3
- from deer.builtins.agents import available_agents
3
+ from deer.builtins_agents import agents
4
4
  import importlib.util
5
5
  import sys
6
6
  import logging
7
7
 
8
8
  from .parser import drivers_parser
9
9
 
10
- logger = logging.getLogger("DEER")
10
+ logger = logging.getLogger(f"DEER.{__name__}")
11
11
 
12
12
 
13
13
  def title():
@@ -28,13 +28,13 @@ def title():
28
28
  def agents_list():
29
29
  console.print()
30
30
  console.print("[bold]Available agents[/bold]")
31
- for name in available_agents:
31
+ for name in agents:
32
32
  console.print(f" • [green]{name}[/green]")
33
33
  console.print()
34
34
 
35
35
 
36
36
  def example():
37
- console.print(f"[dim]Example:[/dim] deer {list(available_agents.keys())[0]}")
37
+ console.print(f"[dim]Example:[/dim] deer {list(agents.keys())[0]}")
38
38
 
39
39
 
40
40
  def run_agent_in_process(agent_path, backend=None, model=None):
@@ -64,11 +64,9 @@ def main():
64
64
 
65
65
  if selected_agent := args.agent:
66
66
 
67
- if selected_agent in available_agents:
67
+ if selected_agent in agents:
68
68
  info(f"Launching agent '{selected_agent}'")
69
- run_agent_in_process(
70
- available_agents[selected_agent], args.backend, args.model
71
- )
69
+ run_agent_in_process(agents[selected_agent], args.backend, args.model)
72
70
  sys.exit(0)
73
71
 
74
72
  error(f"Unknown agent '{selected_agent}'")
@@ -0,0 +1,368 @@
1
+ import sys
2
+ import pickle
3
+ import logging
4
+ import subprocess
5
+
6
+ from rich.console import Console
7
+ from rich.markdown import Markdown
8
+ from rich.table import Table
9
+ from rich.text import Text
10
+ from prompt_toolkit import prompt, PromptSession
11
+ from prompt_toolkit.formatted_text import HTML
12
+ from prompt_toolkit.history import InMemoryHistory
13
+ from prompt_toolkit.completion import Completer, WordCompleter
14
+
15
+ from deer.core import DeterministicAgent
16
+ from deer import __version__
17
+
18
+ logger = logging.getLogger(f"DEER.{__name__}")
19
+
20
+ COMMANDS = {
21
+ "/tools": "Show tools and capabilities",
22
+ "/clear": "Clear the console and reset the session",
23
+ "/exit": "Terminate the session",
24
+ "/rollback": "Revert the system to the last stable state",
25
+ "/trace": "Display the detailed execution trace and variable resolution of the last plan.",
26
+ "/history": "Display the history of messages and commands",
27
+ "/clearhistory": "Purge all session history and reset memory state.",
28
+ # "/vhistory": "Display the history of verifier messages and commands",
29
+ "/infocontext": "Displays context memory usage information.",
30
+ "/loadcontext": "Restore a saved session context",
31
+ "/savecontext": "Save the current session context",
32
+ }
33
+
34
+ WELCOME_MESSAGE = """
35
+ # DEER - Deterministic Executable Engine for Runtime Agents - v{version}
36
+
37
+ An LLM agent orchestration framework built for:
38
+ - Strict runtime control
39
+ - Enforced tool contracts
40
+ - Full execution traceability
41
+ - Guaranteed reproducibility
42
+
43
+ ## Runtime Pipeline
44
+
45
+ 1. Plan → Generate a structured execution strategy
46
+ 2. Validate → Verify contracts, dependencies, and safety
47
+ 3. Execute → Run approved tools and workflows
48
+ 4. Verify → Validate outputs against expected objectives
49
+
50
+ ## System Guarantees
51
+
52
+ - Deterministic execution flow
53
+ - Typed tool interfaces
54
+ - Structured validation layers
55
+ - Full execution traceability
56
+ - Backend-governed orchestration
57
+
58
+ ## Commands
59
+
60
+ {commands}
61
+
62
+ ## System Escapes
63
+
64
+ - `!<command>` → Execute an OS shell command (e.g., `!ls -la` or `!pwd`)
65
+
66
+ ## Shortcuts
67
+
68
+ - `Tab` → Auto-complete commands and navigate suggestions
69
+ - `Ctrl+C` → Stop processing the current task
70
+ """
71
+
72
+
73
+ class SmartCommandCompleter(Completer):
74
+ def __init__(self, commands):
75
+ self.base_completer = WordCompleter(commands, ignore_case=False)
76
+
77
+ def get_completions(self, document, complete_event):
78
+ word_before_cursor = document.get_word_before_cursor()
79
+ if not word_before_cursor:
80
+ return []
81
+ return self.base_completer.get_completions(document, complete_event)
82
+
83
+
84
+ class AgentREPL:
85
+
86
+ def __init__(self, agent: DeterministicAgent):
87
+ self.agent = agent
88
+
89
+ self.prompt_history: InMemoryHistory
90
+ self.load_prompt_history()
91
+
92
+ self.console = Console()
93
+
94
+ @property
95
+ def completer(self):
96
+ internal_commands = list(COMMANDS.keys())
97
+ return SmartCommandCompleter(internal_commands)
98
+
99
+ def load_prompt_history(self):
100
+ history_file = self.agent.agent_dir / "prompt_history"
101
+
102
+ if not history_file.exists():
103
+ self.prompt_history = InMemoryHistory()
104
+ return
105
+
106
+ with open(history_file, "rb") as f:
107
+ self.prompt_history = pickle.load(f)
108
+ return
109
+
110
+ def save_prompt_history(self):
111
+ history_file = self.agent.agent_dir / "prompt_history"
112
+ with open(history_file, "wb") as f:
113
+ pickle.dump(self.prompt_history, f)
114
+
115
+ def clear_history(self):
116
+ self.history = []
117
+ self.trace = []
118
+
119
+ def pretty_print(self, out: str):
120
+ self.console.print(Markdown(out))
121
+
122
+ def show_identity(self):
123
+ self.pretty_print(
124
+ f">**Agent Profile** \n"
125
+ f"*{self.agent.description}* \n"
126
+ f"root: {self.agent.tool_registry.jail_path} \n"
127
+ f"{self.agent.driver}: {self.agent.driver.model_name} \n"
128
+ )
129
+ print("")
130
+
131
+ def show_welcome(self):
132
+ commands_formated = ""
133
+ for command in COMMANDS:
134
+ commands_formated += (
135
+ f"- {('`'+command+'`').ljust(12, ' ')} → {COMMANDS[command]}\n "
136
+ )
137
+
138
+ self.console.clear()
139
+ self.pretty_print(
140
+ WELCOME_MESSAGE.format(version=__version__, commands=commands_formated)
141
+ )
142
+ self.show_identity()
143
+
144
+ def run_command(self, command):
145
+ match command:
146
+ case "/exit":
147
+ self.agent.persist_state()
148
+ self.console.print(
149
+ "[dim]Context state persisted to disk.[/dim]\n",
150
+ Markdown("----"),
151
+ )
152
+ sys.exit(0)
153
+
154
+ case "/clear":
155
+ self.console.clear()
156
+ self.show_identity()
157
+ self.console.print(
158
+ "[dim]Console buffer cleared. Session state persists. Use /clearhistory for a full purge.[/dim]\n",
159
+ )
160
+
161
+ case "/clearhistory":
162
+ self.clear_history()
163
+ self.console.clear()
164
+ self.agent.clear_agent_history()
165
+ self.agent.clear_traces()
166
+ self.agent.persist_state()
167
+ self.show_identity()
168
+ self.console.print(
169
+ "[dim]Session history purged. Memory state reset.[/dim]\n",
170
+ )
171
+
172
+ case "/tools":
173
+ tools_description = self.agent.tool_registry.describe()
174
+ self.console.print(
175
+ Markdown(tools_description),
176
+ "\n",
177
+ f"[dim]Tool registry: {len(self.agent.tool_registry._tools)} modules active.[/dim]\n",
178
+ f"[dim]Registry description payload: {self.agent.format_bytes(len(tools_description))}[/dim]\n",
179
+ sep="",
180
+ )
181
+
182
+ case "/rollback":
183
+ self.console.print(
184
+ "[dim][bold]Rollback executed.[/bold] System reverted to the last stable state.[/dim]\n",
185
+ )
186
+
187
+ case "/trace":
188
+ trace = self.agent.traces["solution"]
189
+ for i, step in enumerate(trace):
190
+ self.console.print(f"[bold yellow]Trace {i+1}:[/bold yellow]")
191
+ print(f"{step}\n")
192
+
193
+ case "/history":
194
+ for i, chat in enumerate(self.agent.agent_history[1:]):
195
+ self.console.print(f"[bold yellow]Chat {i+1}:[/bold yellow]")
196
+ print(f"{chat}\n")
197
+ self.console.print(
198
+ f"[dim]Session history payload: {self.agent.format_bytes(len(str(self.agent.agent_history)))}[/dim]\n",
199
+ )
200
+
201
+ # case "/vhistory":
202
+ # for i, chat in enumerate(self.agent.verificator_history[1:]):
203
+ # self.console.print(f"[bold yellow]Chat {i+1}:[/bold yellow]")
204
+ # print(f"{chat}\n")
205
+
206
+ case "/infocontext":
207
+ bytes = sum(len(str(item)) for item in self.agent.agent_history)
208
+ self.console.print(
209
+ f"[dim][bold]History:[/bold] {len(self.agent.agent_history)} messages[/dim]\n",
210
+ f"[dim][bold]Context size:[/bold] ~{self.agent.format_bytes(bytes)}[/dim]\n",
211
+ sep="",
212
+ )
213
+
214
+ case "/savecontext":
215
+ try:
216
+ self.agent.persist_state()
217
+ self.console.print("[dim]Context state persisted to disk.[/dim]\n")
218
+ except Exception as e:
219
+ self.console.print(
220
+ f"[bold red]Context persistence error:[/bold red] {e}\n"
221
+ )
222
+
223
+ case "/loadcontext":
224
+ try:
225
+ self.agent.restore_state()
226
+ self.console.print(
227
+ "[dim]Context recovered. Session state synchronized.[/dim]\n"
228
+ )
229
+ except Exception as e:
230
+ self.console.print(
231
+ f"[bold red]Context restoration error:[/bold red] {e}\n"
232
+ )
233
+
234
+ self.console.print(
235
+ Markdown("----"),
236
+ end="",
237
+ )
238
+
239
+ def repl(self):
240
+ logger.setLevel(logging.CRITICAL)
241
+ self.show_welcome()
242
+ self.pretty_print(f"----")
243
+
244
+ session = PromptSession(
245
+ erase_when_done=True,
246
+ history=self.prompt_history,
247
+ completer=self.completer,
248
+ mouse_support=True,
249
+ )
250
+
251
+ def as_table(prompt, message="", color="cyan"):
252
+ table = Table.grid(expand=True)
253
+ table.add_column(width=len(prompt) + 1)
254
+ table.add_column()
255
+
256
+ table.add_row(
257
+ Text(prompt, style=f"bold {color}"),
258
+ Markdown(message),
259
+ )
260
+ return table
261
+
262
+ input_prompt = "&gt;&gt;&gt; " # >>>
263
+ input_prompt_history = "USER |"
264
+ output_prompt_history = "DEER |"
265
+
266
+ while True:
267
+ try:
268
+ msg = session.prompt(
269
+ HTML(f"<ansicyan><b>{input_prompt}</b></ansicyan>")
270
+ )
271
+ self.save_prompt_history()
272
+
273
+ if self.agent.load_context:
274
+ self.agent.persist_state()
275
+
276
+ except KeyboardInterrupt:
277
+ continue
278
+ except EOFError:
279
+ break
280
+
281
+ msg = msg.strip()
282
+ if not msg:
283
+ continue
284
+
285
+ match msg:
286
+ case command if command in COMMANDS:
287
+ self.console.print(
288
+ as_table(f"[{command}]", color="cyan"),
289
+ Text("\n"),
290
+ end="",
291
+ )
292
+ self.run_command(command)
293
+
294
+ case command if command.startswith("!"):
295
+ # Remove the '!' from the start to get only the command
296
+ cmd_to_run = command[1:].strip()
297
+
298
+ self.console.print(
299
+ as_table(f"[{cmd_to_run}]", color="cyan"),
300
+ Text("\n"),
301
+ end="",
302
+ )
303
+
304
+ try:
305
+ # Execute the command
306
+ result = subprocess.run(
307
+ cmd_to_run,
308
+ shell=True,
309
+ capture_output=True,
310
+ text=True,
311
+ check=True,
312
+ )
313
+ self.console.print(
314
+ Text(result.stdout),
315
+ Markdown("----"),
316
+ sep="",
317
+ )
318
+
319
+ except subprocess.CalledProcessError as e:
320
+ self.console.print(
321
+ f"[dim]Error executing command: {e.stderr}[/dim]",
322
+ Markdown("----"),
323
+ sep="",
324
+ )
325
+ except Exception as e:
326
+ self.console.print(
327
+ f"[dim]An unexpected error occurred: {e}[/dim]",
328
+ Markdown("----"),
329
+ sep="",
330
+ )
331
+
332
+ case _:
333
+ if msg.startswith("/"):
334
+ self.pretty_print(
335
+ f"Command `{msg}` not recognized. Did you mean one of these: {list(COMMANDS)}"
336
+ )
337
+ continue
338
+ with self.console.status(
339
+ "[dim]processing request[/]",
340
+ spinner="point",
341
+ spinner_style="dim",
342
+ ):
343
+ try:
344
+ self.console.print(
345
+ as_table(input_prompt_history, msg),
346
+ Text("\n"),
347
+ end="",
348
+ )
349
+
350
+ # Call the LLM model
351
+ response = self.agent.run(msg)
352
+
353
+ self.console.print(
354
+ as_table(output_prompt_history, response),
355
+ Text("\n"),
356
+ Markdown("----"),
357
+ end="",
358
+ )
359
+
360
+ except KeyboardInterrupt:
361
+ self.console.print(
362
+ "[dim]Command aborted by user[/dim]\n",
363
+ Markdown("----"),
364
+ sep="",
365
+ )
366
+ continue
367
+ except EOFError:
368
+ break
@@ -17,7 +17,7 @@ from .validator import PlanValidator
17
17
 
18
18
  import pickle
19
19
 
20
- logger = logging.getLogger("DEER")
20
+ logger = logging.getLogger(f"DEER.{__name__}")
21
21
 
22
22
 
23
23
  class DeterministicAgent:
@@ -32,6 +32,7 @@ class DeterministicAgent:
32
32
  vector_memory: Optional[VectorMemory] = None,
33
33
  max_attempts: int = 3,
34
34
  enable_verification: bool = True,
35
+ load_context: bool = False,
35
36
  ):
36
37
 
37
38
  # Context
@@ -41,6 +42,7 @@ class DeterministicAgent:
41
42
  self.agent_dir = (working_dir / ".deer").resolve()
42
43
  self.working_dir = working_dir.resolve()
43
44
  self.enable_verification = enable_verification
45
+ self.contex_file = self.agent_dir / "context"
44
46
 
45
47
  # LLMDriver
46
48
  self.driver = driver
@@ -78,13 +80,18 @@ class DeterministicAgent:
78
80
  # Pipeline Executer
79
81
  self.pipeline_executor = PipelineExecutor(tool_registry=self.tool_registry)
80
82
 
81
- # Agent History
82
- self.agent_history: list[ChatMessage] = []
83
- self.clear_agent_history()
83
+ self.load_context = load_context
84
+ if load_context:
85
+ self.persist_state()
84
86
 
85
- # Validator History
86
- self.verificator_history: list[ChatMessage] = []
87
- self.clear_verificator_history()
87
+ else:
88
+ # Agent History
89
+ self.agent_history: list[ChatMessage] = []
90
+ self.clear_agent_history()
91
+
92
+ # Validator History
93
+ self.verificator_history: list[ChatMessage] = []
94
+ self.clear_verificator_history()
88
95
 
89
96
  # Traces
90
97
  self.traces: dict[Literal["solution", "verification"], list[Any]] = {}
@@ -116,6 +123,31 @@ class DeterministicAgent:
116
123
  )
117
124
  ]
118
125
 
126
+ def format_bytes(self, size):
127
+ for unit in ["B", "KB", "MB", "GB"]:
128
+ if size < 1024:
129
+ return f"{size:.1f} {unit}"
130
+ size /= 1024
131
+
132
+ def persist_state(self):
133
+ context = {
134
+ "agent_history": self.agent_history,
135
+ "verificator_history": self.verificator_history,
136
+ }
137
+ with self.contex_file.open("wb") as f:
138
+ pickle.dump(context, f)
139
+
140
+ def restore_state(self):
141
+ if not self.contex_file.exists():
142
+ return
143
+
144
+ with self.contex_file.open("rb") as f:
145
+ context = pickle.load(f)
146
+ self.agent_history = context.get("agent_history", self.agent_history)
147
+ self.verificator_history = context.get(
148
+ "verificator_history", self.verificator_history
149
+ )
150
+
119
151
  def save_trace(self):
120
152
  obj = {
121
153
  "tools": list(self.tool_registry.list_tools()),
@@ -222,11 +254,20 @@ class DeterministicAgent:
222
254
  trace_verification = self.run_verification(goal)
223
255
  self.save_trace_verification(trace_verification)
224
256
 
257
+ if (
258
+ not trace_solution.steps
259
+ and not self.enable_verification
260
+ and trace_solution.response
261
+ ):
262
+ # Return a response without validation when the request does not have steps
263
+ return trace_solution.response
264
+
225
265
  # Conclusion
266
+ # The request has steps and validation is requested
226
267
  conclusion = self.generate_conclusion(goal, trace_solution, trace_verification)
227
268
 
228
269
  if conclusion.goal_achieved:
229
- # self.clear_verificator_history()
270
+ # The conclusion finds that the goal has been achieved
230
271
  return conclusion.user_message
231
272
  else:
232
273
  if iteration >= self.max_attempts:
@@ -267,7 +308,7 @@ class DeterministicAgent:
267
308
  goal: str,
268
309
  trace_solution: ExecutionTrace,
269
310
  trace_verification: ExecutionTrace,
270
- ) -> str:
311
+ ) -> AgentConclusion:
271
312
 
272
313
  messages = [
273
314
  ChatMessage(
@@ -4,7 +4,7 @@ from typing import Any, Dict
4
4
  from deer.tools import ToolRegistry
5
5
  from deer.models import ExecutionPlan, ExecutionTrace, StepTrace
6
6
 
7
- logger = logging.getLogger("DEER-LLM")
7
+ logger = logging.getLogger(f"DEER.{__name__}")
8
8
 
9
9
 
10
10
  class PipelineExecutor:
@@ -5,7 +5,7 @@ from typing import List, Dict
5
5
  from deer.tools import ToolRegistry
6
6
  from deer.models import ExecutionPlan, ChatMessage, Role
7
7
 
8
- logger = logging.getLogger("DEER-LLM")
8
+ logger = logging.getLogger(f"DEER.{__name__}")
9
9
 
10
10
 
11
11
  class Planner:
@@ -55,7 +55,7 @@ class Planner:
55
55
  "Respond exclusively with the JSON object. Do NOT wrap the response in markdown code blocks or add any introductory text."
56
56
  )
57
57
 
58
- def build_conclusion_prompt(self, goal, trace_solution, trace_verification):
58
+ def build_conclusion_prompt(self, goal, trace_solution, trace_verification) -> str:
59
59
 
60
60
  if not trace_verification:
61
61
  verification = "VERIFICATION NO NEEDED"
@@ -7,7 +7,7 @@ import logging
7
7
  from deer.tools.registry import ToolRegistry
8
8
  from deer.models import ExecutionPlan
9
9
 
10
- logger = logging.getLogger("DEER")
10
+ logger = logging.getLogger(f"DEER.{__name__}")
11
11
 
12
12
 
13
13
  class Rules:
@@ -10,7 +10,7 @@ from deer.models import ChatMessage
10
10
  from pydantic import BaseModel
11
11
 
12
12
  T = TypeVar("T", bound=BaseModel)
13
- logger = logging.getLogger("DEER-LLM")
13
+ logger = logging.getLogger(f"DEER.{__name__}")
14
14
 
15
15
 
16
16
  class LLMDriver(ABC):
@@ -90,8 +90,10 @@ class LLMDriver(ABC):
90
90
 
91
91
  @abstractmethod
92
92
  def generate(
93
- self, messages: list[ChatMessage], response_model: Optional[Type[T]] = None
94
- ) -> Union[str, T]:
93
+ self,
94
+ messages: list[ChatMessage],
95
+ response_model: type[T] | None = None,
96
+ ) -> str | T:
95
97
  pass
96
98
 
97
99
  @abstractmethod
@@ -104,7 +106,7 @@ class LLMDriver(ABC):
104
106
  """
105
107
  pass
106
108
 
107
- def extract_json(self, text: str) -> dict:
109
+ def extract_json(self, text: str) -> str:
108
110
  text = text.strip()
109
111
  text = re.sub(r"^```(?:json)?\s*", "", text)
110
112
  text = re.sub(r"^json\s*", "", text)
@@ -1,8 +1,8 @@
1
1
  import json
2
- from typing import TypeVar, Type, Optional, Union, Generator
3
- from pydantic import BaseModel
2
+ from typing import Generator, TypeVar
4
3
  from .base import LLMDriver, logger
5
4
  from deer.models import ChatMessage
5
+ from pydantic import BaseModel
6
6
 
7
7
  T = TypeVar("T", bound=BaseModel)
8
8
 
@@ -26,8 +26,10 @@ class OllamaDriver(LLMDriver):
26
26
  return "Ollama"
27
27
 
28
28
  def generate(
29
- self, messages: list[ChatMessage], response_model: Optional[Type[T]] = None
30
- ) -> Union[str, T]:
29
+ self,
30
+ messages: list[ChatMessage],
31
+ response_model: type[T] | None = None,
32
+ ) -> str | T:
31
33
  """
32
34
  Generates a response from Ollama.
33
35
  If response_model is provided, it forces the model to follow the schema.
@@ -72,7 +74,8 @@ class OllamaDriver(LLMDriver):
72
74
  )
73
75
 
74
76
  def generate_stream(
75
- self, messages: list[dict[str, str]]
77
+ self,
78
+ messages: list[ChatMessage],
76
79
  ) -> Generator[str, None, None]:
77
80
  """
78
81
  Implementation of streaming for Ollama.
@@ -6,7 +6,7 @@ import logging
6
6
  from typing import Any, Optional, Union
7
7
  from pathlib import Path
8
8
 
9
- logger = logging.getLogger(__name__)
9
+ logger = logging.getLogger(f"DEER.{__name__}")
10
10
 
11
11
 
12
12
  class VectorMemory:
@@ -26,7 +26,7 @@ class ChatMessage(BaseModel):
26
26
  content: str = Field(..., description="The actual text content of the message.")
27
27
 
28
28
  def __str__(self) -> str:
29
- return f"Role: {self.role.value}\n" f"Content: {self.content}"
29
+ return f"Role: {self.role.value}\n" f"Content: {self.content.strip()}"
30
30
 
31
31
 
32
32
  class ToolStep(BaseModel):
@@ -87,7 +87,7 @@ class StepTrace(BaseModel):
87
87
 
88
88
  def __str__(self):
89
89
  if self.error:
90
- line = f"Error: {self.error}\n"
90
+ line = f"\nError: {self.error}"
91
91
  else:
92
92
  line = ""
93
93
  return (
@@ -95,7 +95,7 @@ class StepTrace(BaseModel):
95
95
  f"Resolved args: {self.resolved_args}\n"
96
96
  f"Output: {self.output}\n"
97
97
  f"Status: {self.status}\n"
98
- f"Execution time: {self.execution_time}\n"
98
+ f"Execution time: {self.execution_time}"
99
99
  f"{line}"
100
100
  )
101
101
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deer-agent-framework
3
- Version: 0.2a1
3
+ Version: 0.2.3
4
4
  Summary: DEER - Deterministic Executable Engine for Runtime-agents
5
5
  Author: Yeison
6
6
  License: BSD-2-Clause
@@ -3,9 +3,9 @@ README.md
3
3
  pyproject.toml
4
4
  deer/__init__.py
5
5
  deer/models.py
6
- deer/builtins/agents/__init__.py
7
- deer/builtins/agents/operating_system.py
8
- deer/builtins/agents/python_architect.py
6
+ deer/builtins_agents/__init__.py
7
+ deer/builtins_agents/operating_system.py
8
+ deer/builtins_agents/python_architect.py
9
9
  deer/cli/__init__.py
10
10
  deer/cli/main.py
11
11
  deer/cli/parser.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "deer-agent-framework"
7
- version = "0.2a1"
7
+ version = "0.2.3"
8
8
  description = "DEER - Deterministic Executable Engine for Runtime-agents"
9
9
  authors = [{ name = "Yeison" }]
10
10
  license = { text = "BSD-2-Clause" }
@@ -1,263 +0,0 @@
1
- import json
2
- import sys
3
- import logging
4
-
5
- from deer.core import DeterministicAgent
6
- import pickle
7
- from prompt_toolkit import prompt
8
- from prompt_toolkit.formatted_text import HTML
9
- from prompt_toolkit.history import InMemoryHistory
10
- from prompt_toolkit.completion import WordCompleter
11
- from rich.console import Console
12
- from rich.markdown import Markdown
13
- import subprocess
14
-
15
- from prompt_toolkit.completion import Completer, WordCompleter
16
-
17
-
18
- from deer import __version__
19
-
20
- logger = logging.getLogger("DEER")
21
-
22
- COMMANDS = {
23
- "/tools": "Show tools and capabilities",
24
- "/clear": "Clear the console and reset the session",
25
- "/exit": "Terminate the session",
26
- "/rollback": "Revert the system to the last stable state",
27
- "/trace": "Display the detailed execution trace and variable resolution of the last plan.",
28
- "/history": "Display the history of messages and commands",
29
- "/vhistory": "Display the history of verifier messages and commands",
30
- }
31
-
32
- WELCOME_MESSAGE = """
33
- # DEER - Deterministic Executable Engine for Runtime Agents - v{version}
34
-
35
- An LLM agent orchestration framework built for:
36
- - Strict runtime control
37
- - Enforced tool contracts
38
- - Full execution traceability
39
- - Guaranteed reproducibility
40
-
41
- ## Runtime Pipeline
42
-
43
- 1. Plan → Generate a structured execution strategy
44
- 2. Validate → Verify contracts, dependencies, and safety
45
- 3. Execute → Run approved tools and workflows
46
- 4. Verify → Validate outputs against expected objectives
47
-
48
- ## System Guarantees
49
-
50
- - Deterministic execution flow
51
- - Typed tool interfaces
52
- - Structured validation layers
53
- - Full execution traceability
54
- - Backend-governed orchestration
55
-
56
- ## Commands
57
-
58
- {commands}
59
-
60
- ## System Escapes
61
-
62
- - `!<command>` → Execute an OS shell command (e.g., `!ls -la` or `!pwd`)
63
-
64
- ## Shortcuts
65
-
66
- - `Tab` → Auto-complete commands and navigate suggestions
67
- - `Ctrl+C` → Stop processing the current task
68
- """
69
-
70
-
71
- class SmartCommandCompleter(Completer):
72
- def __init__(self, commands):
73
- self.base_completer = WordCompleter(commands, ignore_case=False)
74
-
75
- def get_completions(self, document, complete_event):
76
- word_before_cursor = document.get_word_before_cursor()
77
- if not word_before_cursor:
78
- return []
79
- return self.base_completer.get_completions(document, complete_event)
80
-
81
-
82
- class AgentREPL:
83
-
84
- def __init__(self, agent: DeterministicAgent):
85
- self.agent = agent
86
-
87
- self.prompt_history: InMemoryHistory
88
- self.load_prompt_history()
89
-
90
- self.console = Console()
91
-
92
- @property
93
- def completer(self):
94
- internal_commands = list(COMMANDS.keys())
95
- return SmartCommandCompleter(internal_commands)
96
-
97
- def load_prompt_history(self):
98
- history_file = self.agent.agent_dir / "prompt_history"
99
-
100
- if not history_file.exists():
101
- self.prompt_history = InMemoryHistory()
102
- return
103
-
104
- with open(history_file, "rb") as f:
105
- self.prompt_history = pickle.load(f)
106
- return
107
-
108
- def save_history(self):
109
- history_file = self.agent.agent_dir / "prompt_history"
110
- with open(history_file, "wb") as f:
111
- pickle.dump(self.prompt_history, f)
112
-
113
- def clear_history(self):
114
- self.history = []
115
- self.trace = []
116
-
117
- def pretty_print(self, out: str):
118
- self.console.print(Markdown(out))
119
-
120
- def show_welcome(self):
121
- commands_formated = ""
122
- for command in COMMANDS:
123
- commands_formated += (
124
- f"- {('`'+command+'`').ljust(12, ' ')} → {COMMANDS[command]}\n "
125
- )
126
-
127
- self.console.clear()
128
- self.pretty_print(
129
- WELCOME_MESSAGE.format(version=__version__, commands=commands_formated)
130
- )
131
- self.pretty_print(
132
- f">**Agent Profile** \n"
133
- f"*{self.agent.description}* \n"
134
- f"root: {self.agent.tool_registry.jail_path} \n"
135
- f"{self.agent.driver}: {self.agent.driver.model_name} \n"
136
- )
137
- print("\n")
138
-
139
- def run_command(self, command):
140
- match command:
141
- case "/exit":
142
- sys.exit(0)
143
-
144
- case "/clear":
145
- self.clear_history()
146
- self.console.clear()
147
- self.agent.clear_agent_history()
148
- self.agent.clear_traces()
149
- self.pretty_print(f">**Agent Profile** \n*{self.agent.description}*")
150
- print("\n")
151
-
152
- case "/tools":
153
- self.pretty_print(self.agent.tool_registry.describe())
154
- print("\n")
155
-
156
- case "/rollback":
157
- # self.state_restore()
158
- self.pretty_print(
159
- "**Rollback executed.** System reverted to the last stable state."
160
- )
161
- print("\n")
162
-
163
- case "/trace":
164
- trace = self.agent.traces["solution"]
165
- for i, step in enumerate(trace):
166
- self.console.print(f"[bold yellow]Trace {i+1}:[/bold yellow]")
167
- print(f"{step}")
168
- # self.pretty_print(str(step))
169
- print("\n")
170
-
171
- case "/history":
172
- for i, chat in enumerate(self.agent.agent_history[1:]):
173
- self.console.print(f"[bold yellow]Chat {i+1}:[/bold yellow]")
174
- print(f"{chat}")
175
- # self.pretty_print(str(chat))
176
- print("\n")
177
-
178
- case "/vhistory":
179
- for i, chat in enumerate(self.agent.verificator_history[1:]):
180
- self.console.print(f"[bold yellow]Chat {i+1}:[/bold yellow]")
181
- print(f"{chat}")
182
- # self.pretty_print(str(chat))
183
- print("\n")
184
-
185
- def repl(self):
186
- logger.setLevel(logging.CRITICAL)
187
- self.show_welcome()
188
-
189
- while True:
190
- try:
191
- msg = prompt(
192
- HTML("<ansicyan><b>&gt;&gt;&gt; </b></ansicyan>"),
193
- history=self.prompt_history,
194
- completer=self.completer,
195
- # multiline=True,
196
- )
197
- self.save_history()
198
- except KeyboardInterrupt:
199
- continue
200
- except EOFError:
201
- break
202
-
203
- msg = msg.strip()
204
- if not msg:
205
- continue
206
-
207
- match msg:
208
- case command if command in COMMANDS:
209
- self.run_command(command)
210
-
211
- case command if command.startswith("!"):
212
- # Remove the '!' from the start to get only the command
213
- cmd_to_run = command[1:].strip()
214
-
215
- try:
216
- # Execute the command
217
- result = subprocess.run(
218
- cmd_to_run,
219
- shell=True,
220
- capture_output=True,
221
- text=True,
222
- check=True,
223
- )
224
- print(result.stdout)
225
- except subprocess.CalledProcessError as e:
226
- print(f"Error executing command: {e.stderr}")
227
- except Exception as e:
228
- print(f"An unexpected error occurred: {e}")
229
-
230
- case _:
231
- if msg.startswith("/"):
232
- self.pretty_print(
233
- f"Command `{msg}` not recognized. Did you mean one of these: {list(COMMANDS)}"
234
- )
235
- continue
236
- with self.console.status(
237
- "[dim]processing request[/]",
238
- spinner="point",
239
- spinner_style="dim",
240
- ):
241
- try:
242
- output = self.send(msg)
243
- self.pretty_print(f" {output}")
244
-
245
- print("\n")
246
- except KeyboardInterrupt:
247
- self.console.print("[dim]Command aborted by user[/]\n")
248
- # self.state_restore()
249
- continue
250
- except EOFError:
251
- break
252
-
253
- def send(self, message, print_chat: bool = False):
254
-
255
- if print_chat:
256
- print(f">>> {message}")
257
-
258
- response = self.agent.run(message)
259
-
260
- if print_chat:
261
- print(f" {response}")
262
-
263
- return response