EvoScientist 0.0.1.dev2__tar.gz → 0.0.1.dev3__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 (129) hide show
  1. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/EvoScientist.py +45 -13
  2. evoscientist-0.0.1.dev3/EvoScientist/cli.py +427 -0
  3. evoscientist-0.0.1.dev3/EvoScientist/memory.py +715 -0
  4. evoscientist-0.0.1.dev3/EvoScientist/middleware.py +80 -0
  5. evoscientist-0.0.1.dev3/EvoScientist/paths.py +45 -0
  6. evoscientist-0.0.1.dev3/EvoScientist/skills_manager.py +392 -0
  7. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/stream/__init__.py +25 -0
  8. evoscientist-0.0.1.dev3/EvoScientist/stream/display.py +604 -0
  9. evoscientist-0.0.1.dev3/EvoScientist/stream/events.py +415 -0
  10. evoscientist-0.0.1.dev3/EvoScientist/stream/state.py +343 -0
  11. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/stream/utils.py +23 -16
  12. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/tools.py +64 -0
  13. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist.egg-info/PKG-INFO +97 -3
  14. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist.egg-info/SOURCES.txt +7 -0
  15. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist.egg-info/requires.txt +1 -0
  16. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/PKG-INFO +97 -3
  17. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/README.md +96 -3
  18. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/pyproject.toml +2 -1
  19. evoscientist-0.0.1.dev3/tests/test_skills_manager.py +345 -0
  20. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/tests/test_stream_utils.py +19 -2
  21. evoscientist-0.0.1.dev2/EvoScientist/cli.py +0 -1553
  22. evoscientist-0.0.1.dev2/EvoScientist/middleware.py +0 -35
  23. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/__init__.py +0 -0
  24. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/__main__.py +0 -0
  25. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/backends.py +0 -0
  26. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/prompts.py +0 -0
  27. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/accelerate/SKILL.md +0 -0
  28. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/accelerate/references/custom-plugins.md +0 -0
  29. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/accelerate/references/megatron-integration.md +0 -0
  30. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/accelerate/references/performance.md +0 -0
  31. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/bitsandbytes/SKILL.md +0 -0
  32. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/bitsandbytes/references/memory-optimization.md +0 -0
  33. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/bitsandbytes/references/qlora-training.md +0 -0
  34. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/bitsandbytes/references/quantization-formats.md +0 -0
  35. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/find-skills/SKILL.md +0 -0
  36. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/find-skills/scripts/install_skill.py +0 -0
  37. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/flash-attention/SKILL.md +0 -0
  38. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/flash-attention/references/benchmarks.md +0 -0
  39. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/flash-attention/references/transformers-integration.md +0 -0
  40. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/llama-cpp/SKILL.md +0 -0
  41. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/llama-cpp/references/optimization.md +0 -0
  42. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/llama-cpp/references/quantization.md +0 -0
  43. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/llama-cpp/references/server.md +0 -0
  44. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/lm-evaluation-harness/SKILL.md +0 -0
  45. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/lm-evaluation-harness/references/api-evaluation.md +0 -0
  46. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/lm-evaluation-harness/references/benchmark-guide.md +0 -0
  47. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/lm-evaluation-harness/references/custom-tasks.md +0 -0
  48. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/lm-evaluation-harness/references/distributed-eval.md +0 -0
  49. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/SKILL.md +0 -0
  50. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/references/checklists.md +0 -0
  51. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/references/citation-workflow.md +0 -0
  52. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/references/reviewer-guidelines.md +0 -0
  53. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/references/sources.md +0 -0
  54. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/references/writing-guide.md +0 -0
  55. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/README.md +0 -0
  56. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/aaai2026/README.md +0 -0
  57. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/aaai2026/aaai2026-unified-supp.tex +0 -0
  58. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/aaai2026/aaai2026-unified-template.tex +0 -0
  59. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/aaai2026/aaai2026.bib +0 -0
  60. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/aaai2026/aaai2026.bst +0 -0
  61. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/aaai2026/aaai2026.sty +0 -0
  62. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/acl/README.md +0 -0
  63. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/acl/acl.sty +0 -0
  64. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/acl/acl_latex.tex +0 -0
  65. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/acl/acl_lualatex.tex +0 -0
  66. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/acl/acl_natbib.bst +0 -0
  67. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/acl/anthology.bib.txt +0 -0
  68. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/acl/custom.bib +0 -0
  69. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/acl/formatting.md +0 -0
  70. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/colm2025/README.md +0 -0
  71. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/colm2025/colm2025_conference.bib +0 -0
  72. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/colm2025/colm2025_conference.bst +0 -0
  73. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/colm2025/colm2025_conference.pdf +0 -0
  74. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/colm2025/colm2025_conference.sty +0 -0
  75. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/colm2025/colm2025_conference.tex +0 -0
  76. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/colm2025/fancyhdr.sty +0 -0
  77. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/colm2025/math_commands.tex +0 -0
  78. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/colm2025/natbib.sty +0 -0
  79. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/iclr2026/fancyhdr.sty +0 -0
  80. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/iclr2026/iclr2026_conference.bib +0 -0
  81. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/iclr2026/iclr2026_conference.bst +0 -0
  82. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/iclr2026/iclr2026_conference.pdf +0 -0
  83. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/iclr2026/iclr2026_conference.sty +0 -0
  84. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/iclr2026/iclr2026_conference.tex +0 -0
  85. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/iclr2026/math_commands.tex +0 -0
  86. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/iclr2026/natbib.sty +0 -0
  87. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/icml2026/algorithm.sty +0 -0
  88. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/icml2026/algorithmic.sty +0 -0
  89. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/icml2026/example_paper.bib +0 -0
  90. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/icml2026/example_paper.pdf +0 -0
  91. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/icml2026/example_paper.tex +0 -0
  92. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/icml2026/fancyhdr.sty +0 -0
  93. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/icml2026/icml2026.bst +0 -0
  94. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/icml2026/icml2026.sty +0 -0
  95. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/icml2026/icml_numpapers.pdf +0 -0
  96. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/neurips2025/Makefile +0 -0
  97. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/neurips2025/extra_pkgs.tex +0 -0
  98. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/neurips2025/main.tex +0 -0
  99. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ml-paper-writing/templates/neurips2025/neurips.sty +0 -0
  100. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/peft/SKILL.md +0 -0
  101. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/peft/references/advanced-usage.md +0 -0
  102. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/peft/references/troubleshooting.md +0 -0
  103. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ray-data/SKILL.md +0 -0
  104. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ray-data/references/integration.md +0 -0
  105. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/ray-data/references/transformations.md +0 -0
  106. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/skill-creator/LICENSE.txt +0 -0
  107. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/skill-creator/SKILL.md +0 -0
  108. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/skill-creator/references/output-patterns.md +0 -0
  109. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/skill-creator/references/workflows.md +0 -0
  110. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/skill-creator/scripts/init_skill.py +0 -0
  111. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/skill-creator/scripts/package_skill.py +0 -0
  112. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/skills/skill-creator/scripts/quick_validate.py +0 -0
  113. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/stream/emitter.py +0 -0
  114. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/stream/formatter.py +0 -0
  115. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/stream/tracker.py +0 -0
  116. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/subagent.yaml +0 -0
  117. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist/utils.py +0 -0
  118. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist.egg-info/dependency_links.txt +0 -0
  119. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist.egg-info/entry_points.txt +0 -0
  120. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/EvoScientist.egg-info/top_level.txt +0 -0
  121. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/LICENSE +0 -0
  122. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/setup.cfg +0 -0
  123. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/tests/test_backends.py +0 -0
  124. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/tests/test_imports.py +0 -0
  125. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/tests/test_prompts.py +0 -0
  126. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/tests/test_stream_emitter.py +0 -0
  127. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/tests/test_stream_state.py +0 -0
  128. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/tests/test_stream_tracker.py +0 -0
  129. {evoscientist-0.0.1.dev2 → evoscientist-0.0.1.dev3}/tests/test_tools.py +0 -0
@@ -22,10 +22,16 @@ from deepagents.backends import FilesystemBackend, CompositeBackend
22
22
  from langchain.chat_models import init_chat_model
23
23
 
24
24
  from .backends import CustomSandboxBackend, MergedReadOnlyBackend
25
- from .middleware import create_skills_middleware
25
+ from .middleware import create_skills_middleware, create_memory_middleware
26
26
  from .prompts import RESEARCHER_INSTRUCTIONS, get_system_prompt
27
27
  from .utils import load_subagents
28
- from .tools import tavily_search, think_tool
28
+ from .tools import tavily_search, think_tool, skill_manager
29
+ from .paths import (
30
+ ensure_dirs,
31
+ default_workspace_dir,
32
+ MEMORY_DIR as _MEMORY_DIR_PATH,
33
+ USER_SKILLS_DIR as _USER_SKILLS_DIR_PATH,
34
+ )
29
35
 
30
36
  # =============================================================================
31
37
  # Configuration
@@ -39,8 +45,11 @@ MAX_CONCURRENT = 3 # Max parallel sub-agents
39
45
  MAX_ITERATIONS = 3 # Max delegation rounds
40
46
 
41
47
  # Workspace settings
42
- WORKSPACE_DIR = "./workspace/"
48
+ ensure_dirs()
49
+ WORKSPACE_DIR = str(default_workspace_dir())
50
+ MEMORY_DIR = str(_MEMORY_DIR_PATH) # Shared across sessions (not per-session)
43
51
  SKILLS_DIR = str(Path(__file__).parent / "skills")
52
+ USER_SKILLS_DIR = str(_USER_SKILLS_DIR_PATH)
44
53
  SUBAGENTS_CONFIG = Path(__file__).parent / "subagent.yaml"
45
54
 
46
55
  # =============================================================================
@@ -76,16 +85,25 @@ else:
76
85
  virtual_mode=True,
77
86
  )
78
87
 
79
- # Skills backend: merge user-installed (workspace) and system (package) skills
88
+ # Skills backend: merge user-installed (./skills/) and system (package) skills
80
89
  _skills_backend = MergedReadOnlyBackend(
81
- primary_dir=str(Path(WORKSPACE_DIR) / "skills"), # user-installed, takes priority
90
+ primary_dir=USER_SKILLS_DIR, # user-installed, takes priority
82
91
  secondary_dir=SKILLS_DIR, # package built-in, fallback
83
92
  )
84
93
 
85
- # Composite backend: workspace as default, skills mounted at /skills/
94
+ # Memory backend: persistent filesystem for long-term memory (shared across sessions)
95
+ _memory_backend = FilesystemBackend(
96
+ root_dir=MEMORY_DIR,
97
+ virtual_mode=True,
98
+ )
99
+
100
+ # Composite backend: workspace as default, skills and memory mounted
86
101
  backend = CompositeBackend(
87
102
  default=_workspace_backend,
88
- routes={"/skills/": _skills_backend},
103
+ routes={
104
+ "/skills/": _skills_backend,
105
+ "/memory/": _memory_backend,
106
+ },
89
107
  )
90
108
 
91
109
  tool_registry = {
@@ -107,10 +125,13 @@ subagents = load_subagents(
107
125
  _AGENT_KWARGS = dict(
108
126
  name="EvoScientist",
109
127
  model=chat_model,
110
- tools=[think_tool],
128
+ tools=[think_tool, skill_manager],
111
129
  backend=backend,
112
130
  subagents=subagents,
113
- middleware=[create_skills_middleware(SKILLS_DIR, WORKSPACE_DIR)],
131
+ middleware=[
132
+ create_memory_middleware(MEMORY_DIR, extraction_model=chat_model),
133
+ create_skills_middleware(SKILLS_DIR, user_skills_dir=USER_SKILLS_DIR),
134
+ ],
114
135
  system_prompt=SYSTEM_PROMPT,
115
136
  )
116
137
 
@@ -124,7 +145,7 @@ def create_cli_agent(workspace_dir: str | None = None):
124
145
  Args:
125
146
  workspace_dir: Optional per-session workspace directory. If provided,
126
147
  creates a fresh backend rooted at this path. If None, uses the
127
- module-level default backend (./workspace/).
148
+ module-level default backend (./workspace).
128
149
  """
129
150
  from langgraph.checkpoint.memory import InMemorySaver # type: ignore[import-untyped]
130
151
 
@@ -135,14 +156,25 @@ def create_cli_agent(workspace_dir: str | None = None):
135
156
  timeout=300,
136
157
  )
137
158
  sk_backend = MergedReadOnlyBackend(
138
- primary_dir=str(Path(workspace_dir) / "skills"),
159
+ primary_dir=USER_SKILLS_DIR,
139
160
  secondary_dir=SKILLS_DIR,
140
161
  )
162
+ # Memory always uses SHARED directory (not per-session) for cross-session persistence
163
+ mem_backend = FilesystemBackend(
164
+ root_dir=MEMORY_DIR,
165
+ virtual_mode=True,
166
+ )
141
167
  be = CompositeBackend(
142
168
  default=ws_backend,
143
- routes={"/skills/": sk_backend},
169
+ routes={
170
+ "/skills/": sk_backend,
171
+ "/memory/": mem_backend,
172
+ },
144
173
  )
145
- mw = [create_skills_middleware(SKILLS_DIR, workspace_dir)]
174
+ mw = [
175
+ create_memory_middleware(MEMORY_DIR, extraction_model=chat_model),
176
+ create_skills_middleware(SKILLS_DIR, user_skills_dir=USER_SKILLS_DIR),
177
+ ]
146
178
  kwargs = dict(
147
179
  _AGENT_KWARGS,
148
180
  backend=be,
@@ -0,0 +1,427 @@
1
+ """
2
+ EvoScientist Agent CLI
3
+
4
+ Command-line interface with streaming output for the EvoScientist research agent.
5
+
6
+ Features:
7
+ - Thinking panel (blue) - shows model reasoning
8
+ - Tool calls with status indicators (green/yellow/red dots)
9
+ - Tool results in tree format with folding
10
+ - Response panel (green) - shows final response
11
+ - Thread ID support for multi-turn conversations
12
+ - Interactive mode with prompt_toolkit
13
+ """
14
+
15
+ import logging
16
+ import os
17
+ import sys
18
+ import uuid
19
+ from datetime import datetime
20
+ from typing import Any, Optional
21
+
22
+ import typer # type: ignore[import-untyped]
23
+ from prompt_toolkit import PromptSession # type: ignore[import-untyped]
24
+ from prompt_toolkit.history import FileHistory # type: ignore[import-untyped]
25
+ from prompt_toolkit.auto_suggest import AutoSuggestFromHistory # type: ignore[import-untyped]
26
+ from prompt_toolkit.formatted_text import HTML # type: ignore[import-untyped]
27
+ from rich.text import Text # type: ignore[import-untyped]
28
+
29
+ # Backward-compat re-exports (tests import these from EvoScientist.cli)
30
+ from .stream.state import SubAgentState, StreamState, _parse_todo_items, _build_todo_stats # noqa: F401
31
+ from .stream.display import console, _run_streaming
32
+ from .paths import ensure_dirs, new_run_dir
33
+
34
+
35
+ def _shorten_path(path: str) -> str:
36
+ """Shorten absolute path to relative path from current directory."""
37
+ if not path:
38
+ return path
39
+ try:
40
+ cwd = os.getcwd()
41
+ if path.startswith(cwd):
42
+ # Remove cwd prefix, keep the relative part
43
+ rel = path[len(cwd):].lstrip(os.sep)
44
+ # Add current dir name for context
45
+ return os.path.join(os.path.basename(cwd), rel) if rel else os.path.basename(cwd)
46
+ return path
47
+ except Exception:
48
+ return path
49
+
50
+
51
+ # =============================================================================
52
+ # Banner
53
+ # =============================================================================
54
+
55
+ EVOSCIENTIST_ASCII_LINES = [
56
+ r" ███████╗ ██╗ ██╗ ██████╗ ███████╗ ██████╗ ██╗ ███████╗ ███╗ ██╗ ████████╗ ██╗ ███████╗ ████████╗",
57
+ r" ██╔════╝ ██║ ██║ ██╔═══██╗ ██╔════╝ ██╔════╝ ██║ ██╔════╝ ████╗ ██║ ╚══██╔══╝ ██║ ██╔════╝ ╚══██╔══╝",
58
+ r" █████╗ ██║ ██║ ██║ ██║ ███████╗ ██║ ██║ █████╗ ██╔██╗ ██║ ██║ ██║ ███████╗ ██║ ",
59
+ r" ██╔══╝ ╚██╗ ██╔╝ ██║ ██║ ╚════██║ ██║ ██║ ██╔══╝ ██║╚██╗██║ ██║ ██║ ╚════██║ ██║ ",
60
+ r" ███████╗ ╚████╔╝ ╚██████╔╝ ███████║ ╚██████╗ ██║ ███████╗ ██║ ╚████║ ██║ ██║ ███████║ ██║ ",
61
+ r" ╚══════╝ ╚═══╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚══════╝ ╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚══════╝ ╚═╝ ",
62
+ ]
63
+
64
+ # Blue gradient: deep navy -> royal blue -> sky blue -> cyan
65
+ _GRADIENT_COLORS = ["#1a237e", "#1565c0", "#1e88e5", "#42a5f5", "#64b5f6", "#90caf9"]
66
+
67
+
68
+ def print_banner(
69
+ thread_id: str,
70
+ workspace_dir: str | None = None,
71
+ memory_dir: str | None = None,
72
+ ):
73
+ """Print welcome banner with ASCII art logo, thread ID, and workspace path."""
74
+ for line, color in zip(EVOSCIENTIST_ASCII_LINES, _GRADIENT_COLORS):
75
+ console.print(Text(line, style=f"{color} bold"))
76
+ info = Text()
77
+ info.append(" Thread: ", style="dim")
78
+ info.append(thread_id, style="yellow")
79
+ if workspace_dir:
80
+ info.append("\n Workspace: ", style="dim")
81
+ info.append(_shorten_path(workspace_dir), style="cyan")
82
+ if memory_dir:
83
+ trimmed = memory_dir.rstrip("/").rstrip("\\")
84
+ info.append("\n Memory dir: ", style="dim")
85
+ info.append(_shorten_path(trimmed), style="cyan")
86
+ info.append("\n Commands: ", style="dim")
87
+ info.append("/exit", style="bold")
88
+ info.append(", ", style="dim")
89
+ info.append("/new", style="bold")
90
+ info.append(", ", style="dim")
91
+ info.append("/thread", style="bold")
92
+ info.append(", ", style="dim")
93
+ info.append("/skills", style="bold")
94
+ info.append(", ", style="dim")
95
+ info.append("/install-skill", style="bold")
96
+ info.append(", ", style="dim")
97
+ info.append("/uninstall-skill", style="bold")
98
+ console.print(info)
99
+ console.print()
100
+
101
+
102
+ # =============================================================================
103
+ # Skill management commands
104
+ # =============================================================================
105
+
106
+
107
+ def _cmd_list_skills() -> None:
108
+ """List installed user skills."""
109
+ from .skills_manager import list_skills
110
+ from .paths import USER_SKILLS_DIR
111
+
112
+ skills = list_skills(include_system=False)
113
+
114
+ if not skills:
115
+ console.print("[dim]No user skills installed.[/dim]")
116
+ console.print(f"[dim]Install with:[/dim] /install-skill <path-or-url>")
117
+ console.print(f"[dim]Skills directory:[/dim] [cyan]{_shorten_path(str(USER_SKILLS_DIR))}[/cyan]")
118
+ console.print()
119
+ return
120
+
121
+ console.print(f"[bold]Installed Skills[/bold] ({len(skills)}):")
122
+ for skill in skills:
123
+ console.print(f" [green]{skill.name}[/green] - {skill.description}")
124
+ console.print(f"\n[dim]Location:[/dim] [cyan]{_shorten_path(str(USER_SKILLS_DIR))}[/cyan]")
125
+ console.print()
126
+
127
+
128
+ def _cmd_install_skill(source: str) -> None:
129
+ """Install a skill from local path or GitHub URL."""
130
+ from .skills_manager import install_skill
131
+
132
+ if not source:
133
+ console.print("[red]Usage:[/red] /install-skill <path-or-url>")
134
+ console.print("[dim]Examples:[/dim]")
135
+ console.print(" /install-skill ./my-skill")
136
+ console.print(" /install-skill https://github.com/user/repo/tree/main/skill-name")
137
+ console.print(" /install-skill user/repo@skill-name")
138
+ console.print()
139
+ return
140
+
141
+ console.print(f"[dim]Installing skill from:[/dim] {source}")
142
+
143
+ result = install_skill(source)
144
+
145
+ if result["success"]:
146
+ console.print(f"[green]Installed:[/green] {result['name']}")
147
+ console.print(f"[dim]Description:[/dim] {result.get('description', '(none)')}")
148
+ console.print(f"[dim]Path:[/dim] [cyan]{_shorten_path(result['path'])}[/cyan]")
149
+ console.print()
150
+ console.print("[dim]Reload the agent with /new to use the skill.[/dim]")
151
+ else:
152
+ console.print(f"[red]Failed:[/red] {result['error']}")
153
+ console.print()
154
+
155
+
156
+ def _cmd_uninstall_skill(name: str) -> None:
157
+ """Uninstall a user-installed skill."""
158
+ from .skills_manager import uninstall_skill
159
+
160
+ if not name:
161
+ console.print("[red]Usage:[/red] /uninstall-skill <skill-name>")
162
+ console.print("[dim]Use /skills to see installed skills.[/dim]")
163
+ console.print()
164
+ return
165
+
166
+ result = uninstall_skill(name)
167
+
168
+ if result["success"]:
169
+ console.print(f"[green]Uninstalled:[/green] {name}")
170
+ console.print("[dim]Reload the agent with /new to apply changes.[/dim]")
171
+ else:
172
+ console.print(f"[red]Failed:[/red] {result['error']}")
173
+ console.print()
174
+
175
+
176
+ # =============================================================================
177
+ # CLI commands
178
+ # =============================================================================
179
+
180
+ def cmd_interactive(
181
+ agent: Any,
182
+ show_thinking: bool = True,
183
+ workspace_dir: str | None = None,
184
+ workspace_fixed: bool = False,
185
+ ) -> None:
186
+ """Interactive conversation mode with streaming output.
187
+
188
+ Args:
189
+ agent: Compiled agent graph
190
+ show_thinking: Whether to display thinking panels
191
+ workspace_dir: Per-session workspace directory path
192
+ workspace_fixed: If True, /new keeps the same workspace directory
193
+ """
194
+ thread_id = str(uuid.uuid4())
195
+ from .EvoScientist import MEMORY_DIR
196
+ memory_dir = MEMORY_DIR
197
+ print_banner(thread_id, workspace_dir, memory_dir)
198
+
199
+ history_file = str(os.path.expanduser("~/.EvoScientist_history"))
200
+ session = PromptSession(
201
+ history=FileHistory(history_file),
202
+ auto_suggest=AutoSuggestFromHistory(),
203
+ enable_history_search=True,
204
+ )
205
+
206
+ def _print_separator():
207
+ """Print a horizontal separator line spanning the terminal width."""
208
+ width = console.size.width
209
+ console.print(Text("\u2500" * width, style="dim"))
210
+
211
+ _print_separator()
212
+ while True:
213
+ try:
214
+ user_input = session.prompt(
215
+ HTML('<ansiblue><b>&gt;</b></ansiblue> ')
216
+ ).strip()
217
+
218
+ if not user_input:
219
+ # Erase the empty prompt line so it looks like nothing happened
220
+ sys.stdout.write("\033[A\033[2K\r")
221
+ sys.stdout.flush()
222
+ continue
223
+
224
+ _print_separator()
225
+
226
+ # Special commands
227
+ if user_input.lower() in ("/exit", "/quit", "/q"):
228
+ console.print("[dim]Goodbye![/dim]")
229
+ break
230
+
231
+ if user_input.lower() == "/new":
232
+ # New session: new thread; workspace only changes if not fixed
233
+ if not workspace_fixed:
234
+ workspace_dir = _create_session_workspace()
235
+ console.print("[dim]Loading new session...[/dim]")
236
+ agent = _load_agent(workspace_dir=workspace_dir)
237
+ thread_id = str(uuid.uuid4())
238
+ console.print(f"[green]New session:[/green] [yellow]{thread_id}[/yellow]")
239
+ if workspace_dir:
240
+ console.print(f"[dim]Workspace:[/dim] [cyan]{_shorten_path(workspace_dir)}[/cyan]\n")
241
+ continue
242
+
243
+ if user_input.lower() == "/thread":
244
+ console.print(f"[dim]Thread:[/dim] [yellow]{thread_id}[/yellow]")
245
+ if workspace_dir:
246
+ console.print(f"[dim]Workspace:[/dim] [cyan]{_shorten_path(workspace_dir)}[/cyan]")
247
+ if memory_dir:
248
+ console.print(f"[dim]Memory dir:[/dim] [cyan]{_shorten_path(memory_dir)}[/cyan]")
249
+ console.print()
250
+ continue
251
+
252
+ if user_input.lower() == "/skills":
253
+ _cmd_list_skills()
254
+ continue
255
+
256
+ if user_input.lower().startswith("/install-skill"):
257
+ source = user_input[len("/install-skill"):].strip()
258
+ _cmd_install_skill(source)
259
+ continue
260
+
261
+ if user_input.lower().startswith("/uninstall-skill"):
262
+ name = user_input[len("/uninstall-skill"):].strip()
263
+ _cmd_uninstall_skill(name)
264
+ continue
265
+
266
+ # Stream agent response
267
+ console.print()
268
+ _run_streaming(agent, user_input, thread_id, show_thinking, interactive=True)
269
+ _print_separator()
270
+
271
+ except KeyboardInterrupt:
272
+ console.print("\n[dim]Goodbye![/dim]")
273
+ break
274
+ except Exception as e:
275
+ console.print(f"[red]Error: {e}[/red]")
276
+
277
+
278
+ def cmd_run(agent: Any, prompt: str, thread_id: str | None = None, show_thinking: bool = True, workspace_dir: str | None = None) -> None:
279
+ """Single-shot execution with streaming display.
280
+
281
+ Args:
282
+ agent: Compiled agent graph
283
+ prompt: User prompt
284
+ thread_id: Optional thread ID (generates new one if None)
285
+ show_thinking: Whether to display thinking panels
286
+ workspace_dir: Per-session workspace directory path
287
+ """
288
+ thread_id = thread_id or str(uuid.uuid4())
289
+
290
+ width = console.size.width
291
+ sep = Text("\u2500" * width, style="dim")
292
+ console.print(sep)
293
+ console.print(Text(f"> {prompt}"))
294
+ console.print(sep)
295
+ console.print(f"[dim]Thread: {thread_id}[/dim]")
296
+ if workspace_dir:
297
+ console.print(f"[dim]Workspace: {_shorten_path(workspace_dir)}[/dim]")
298
+ console.print()
299
+
300
+ try:
301
+ _run_streaming(agent, prompt, thread_id, show_thinking, interactive=False)
302
+ except Exception as e:
303
+ console.print(f"[red]Error: {e}[/red]")
304
+ raise
305
+
306
+
307
+ # =============================================================================
308
+ # Agent loading helpers
309
+ # =============================================================================
310
+
311
+ def _create_session_workspace() -> str:
312
+ """Create a per-session workspace directory and return its path."""
313
+ session_id = datetime.now().strftime("%Y%m%d_%H%M%S")
314
+ workspace_dir = str(new_run_dir(session_id))
315
+ os.makedirs(workspace_dir, exist_ok=True)
316
+ return workspace_dir
317
+
318
+
319
+ def _load_agent(workspace_dir: str | None = None):
320
+ """Load the CLI agent (with InMemorySaver checkpointer for multi-turn).
321
+
322
+ Args:
323
+ workspace_dir: Optional per-session workspace directory.
324
+ """
325
+ from .EvoScientist import create_cli_agent
326
+ return create_cli_agent(workspace_dir=workspace_dir)
327
+
328
+
329
+ # =============================================================================
330
+ # Typer app
331
+ # =============================================================================
332
+
333
+ app = typer.Typer(no_args_is_help=False, add_completion=False)
334
+
335
+
336
+ @app.callback(invoke_without_command=True)
337
+ def _main_callback(
338
+ ctx: typer.Context,
339
+ prompt: Optional[str] = typer.Argument(None, help="Query to execute (single-shot mode)"),
340
+ interactive: bool = typer.Option(False, "-i", "--interactive", help="Interactive conversation mode"),
341
+ thread_id: Optional[str] = typer.Option(None, "--thread-id", help="Thread ID for conversation persistence"),
342
+ no_thinking: bool = typer.Option(False, "--no-thinking", help="Disable thinking display"),
343
+ workdir: Optional[str] = typer.Option(None, "--workdir", help="Override workspace directory for this session"),
344
+ use_cwd: bool = typer.Option(False, "--use-cwd", help="Use current working directory as workspace"),
345
+ ):
346
+ """EvoScientist Agent - AI-powered research & code execution CLI."""
347
+ from dotenv import load_dotenv # type: ignore[import-untyped]
348
+ load_dotenv(override=True)
349
+
350
+ show_thinking = not no_thinking
351
+
352
+ if workdir and use_cwd:
353
+ raise typer.BadParameter("Use either --workdir or --use-cwd, not both.")
354
+
355
+ ensure_dirs()
356
+
357
+ # Resolve workspace directory for this session
358
+ if use_cwd:
359
+ workspace_dir = os.getcwd()
360
+ workspace_fixed = True
361
+ elif workdir:
362
+ workspace_dir = os.path.abspath(os.path.expanduser(workdir))
363
+ os.makedirs(workspace_dir, exist_ok=True)
364
+ workspace_fixed = True
365
+ else:
366
+ workspace_dir = _create_session_workspace()
367
+ workspace_fixed = False
368
+
369
+ # Load agent with session workspace
370
+ console.print("[dim]Loading agent...[/dim]")
371
+ agent = _load_agent(workspace_dir=workspace_dir)
372
+
373
+ if interactive:
374
+ cmd_interactive(
375
+ agent,
376
+ show_thinking=show_thinking,
377
+ workspace_dir=workspace_dir,
378
+ workspace_fixed=workspace_fixed,
379
+ )
380
+ elif prompt:
381
+ cmd_run(agent, prompt, thread_id=thread_id, show_thinking=show_thinking, workspace_dir=workspace_dir)
382
+ else:
383
+ # Default: interactive mode
384
+ cmd_interactive(
385
+ agent,
386
+ show_thinking=show_thinking,
387
+ workspace_dir=workspace_dir,
388
+ workspace_fixed=workspace_fixed,
389
+ )
390
+
391
+
392
+ def _configure_logging():
393
+ """Configure logging with warning symbols for better visibility."""
394
+ from rich.logging import RichHandler
395
+
396
+ class DimWarningHandler(RichHandler):
397
+ """Custom handler that renders warnings in dim style."""
398
+
399
+ def emit(self, record: logging.LogRecord) -> None:
400
+ if record.levelno == logging.WARNING:
401
+ # Use Rich console to print dim warning
402
+ msg = record.getMessage()
403
+ console.print(f"[dim yellow]\u26a0\ufe0f Warning:[/dim yellow] [dim]{msg}[/dim]")
404
+ else:
405
+ super().emit(record)
406
+
407
+ # Configure root logger to use our handler for WARNING and above
408
+ handler = DimWarningHandler(console=console, show_time=False, show_path=False, show_level=False)
409
+ handler.setLevel(logging.WARNING)
410
+
411
+ # Apply to root logger (catches all loggers including deepagents)
412
+ root_logger = logging.getLogger()
413
+ # Remove existing handlers to avoid duplicate output
414
+ for h in root_logger.handlers[:]:
415
+ root_logger.removeHandler(h)
416
+ root_logger.addHandler(handler)
417
+ root_logger.setLevel(logging.WARNING)
418
+
419
+
420
+ def main():
421
+ """CLI entry point — delegates to the Typer app."""
422
+ _configure_logging()
423
+ app()
424
+
425
+
426
+ if __name__ == "__main__":
427
+ main()