EvoScientist 0.0.1.dev4__tar.gz → 0.0.1.dev6__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 (166) hide show
  1. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/EvoScientist/EvoScientist.py +107 -35
  2. evoscientist-0.0.1.dev6/EvoScientist/__init__.py +64 -0
  3. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/EvoScientist/backends.py +39 -101
  4. evoscientist-0.0.1.dev6/EvoScientist/channels/__init__.py +9 -0
  5. evoscientist-0.0.1.dev6/EvoScientist/channels/base.py +110 -0
  6. evoscientist-0.0.1.dev6/EvoScientist/channels/imessage/__init__.py +33 -0
  7. evoscientist-0.0.1.dev6/EvoScientist/channels/imessage/channel_rpc.py +427 -0
  8. evoscientist-0.0.1.dev6/EvoScientist/channels/imessage/probe.py +106 -0
  9. evoscientist-0.0.1.dev6/EvoScientist/channels/imessage/rpc_client.py +235 -0
  10. evoscientist-0.0.1.dev6/EvoScientist/channels/imessage/serve.py +428 -0
  11. evoscientist-0.0.1.dev6/EvoScientist/channels/imessage/targets.py +232 -0
  12. evoscientist-0.0.1.dev6/EvoScientist/cli/__init__.py +26 -0
  13. evoscientist-0.0.1.dev6/EvoScientist/cli/_app.py +47 -0
  14. evoscientist-0.0.1.dev6/EvoScientist/cli/agent.py +60 -0
  15. evoscientist-0.0.1.dev6/EvoScientist/cli/channel.py +289 -0
  16. evoscientist-0.0.1.dev6/EvoScientist/cli/commands.py +470 -0
  17. evoscientist-0.0.1.dev6/EvoScientist/cli/interactive.py +708 -0
  18. evoscientist-0.0.1.dev6/EvoScientist/cli/mcp_ui.py +282 -0
  19. evoscientist-0.0.1.dev6/EvoScientist/cli/skills_cmd.py +73 -0
  20. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/EvoScientist/config.py +12 -1
  21. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/EvoScientist/llm/models.py +20 -4
  22. evoscientist-0.0.1.dev6/EvoScientist/mcp/__init__.py +32 -0
  23. evoscientist-0.0.1.dev6/EvoScientist/mcp/client.py +667 -0
  24. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/EvoScientist/memory.py +24 -46
  25. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/EvoScientist/middleware.py +7 -21
  26. evoscientist-0.0.1.dev6/EvoScientist/onboard.py +1331 -0
  27. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/EvoScientist/paths.py +16 -0
  28. evoscientist-0.0.1.dev6/EvoScientist/sessions.py +332 -0
  29. evoscientist-0.0.1.dev6/EvoScientist/skills/find-skills/SKILL.md +76 -0
  30. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/EvoScientist/skills/skill-creator/SKILL.md +64 -58
  31. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/EvoScientist/stream/__init__.py +2 -0
  32. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/EvoScientist/stream/display.py +217 -8
  33. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/EvoScientist/stream/events.py +59 -4
  34. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/EvoScientist/stream/formatter.py +4 -1
  35. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/EvoScientist/stream/utils.py +11 -2
  36. evoscientist-0.0.1.dev6/EvoScientist/tools/__init__.py +16 -0
  37. evoscientist-0.0.1.dev6/EvoScientist/tools/search.py +115 -0
  38. evoscientist-0.0.1.dev6/EvoScientist/tools/skill_manager.py +120 -0
  39. {evoscientist-0.0.1.dev4/EvoScientist → evoscientist-0.0.1.dev6/EvoScientist/tools}/skills_manager.py +105 -26
  40. evoscientist-0.0.1.dev6/EvoScientist/tools/think.py +32 -0
  41. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/EvoScientist/utils.py +3 -0
  42. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/EvoScientist.egg-info/PKG-INFO +176 -18
  43. evoscientist-0.0.1.dev6/EvoScientist.egg-info/SOURCES.txt +79 -0
  44. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/EvoScientist.egg-info/requires.txt +7 -3
  45. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/PKG-INFO +176 -18
  46. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/README.md +168 -14
  47. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/pyproject.toml +8 -4
  48. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/tests/test_backends.py +88 -0
  49. evoscientist-0.0.1.dev6/tests/test_cli_run_name.py +38 -0
  50. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/tests/test_config.py +19 -0
  51. evoscientist-0.0.1.dev6/tests/test_event_loop.py +200 -0
  52. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/tests/test_imports.py +0 -15
  53. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/tests/test_llm.py +20 -4
  54. evoscientist-0.0.1.dev6/tests/test_mcp_client.py +539 -0
  55. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/tests/test_onboard.py +309 -14
  56. evoscientist-0.0.1.dev6/tests/test_sessions.py +234 -0
  57. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/tests/test_skills_manager.py +7 -7
  58. evoscientist-0.0.1.dev6/tests/test_stream_events.py +87 -0
  59. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/tests/test_stream_state.py +161 -11
  60. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/tests/test_stream_utils.py +28 -4
  61. evoscientist-0.0.1.dev4/EvoScientist/__init__.py +0 -43
  62. evoscientist-0.0.1.dev4/EvoScientist/cli.py +0 -668
  63. evoscientist-0.0.1.dev4/EvoScientist/onboard.py +0 -725
  64. evoscientist-0.0.1.dev4/EvoScientist/skills/accelerate/SKILL.md +0 -332
  65. evoscientist-0.0.1.dev4/EvoScientist/skills/accelerate/references/custom-plugins.md +0 -453
  66. evoscientist-0.0.1.dev4/EvoScientist/skills/accelerate/references/megatron-integration.md +0 -489
  67. evoscientist-0.0.1.dev4/EvoScientist/skills/accelerate/references/performance.md +0 -525
  68. evoscientist-0.0.1.dev4/EvoScientist/skills/bitsandbytes/SKILL.md +0 -411
  69. evoscientist-0.0.1.dev4/EvoScientist/skills/bitsandbytes/references/memory-optimization.md +0 -521
  70. evoscientist-0.0.1.dev4/EvoScientist/skills/bitsandbytes/references/qlora-training.md +0 -521
  71. evoscientist-0.0.1.dev4/EvoScientist/skills/bitsandbytes/references/quantization-formats.md +0 -447
  72. evoscientist-0.0.1.dev4/EvoScientist/skills/find-skills/SKILL.md +0 -133
  73. evoscientist-0.0.1.dev4/EvoScientist/skills/find-skills/scripts/install_skill.py +0 -211
  74. evoscientist-0.0.1.dev4/EvoScientist/skills/flash-attention/SKILL.md +0 -367
  75. evoscientist-0.0.1.dev4/EvoScientist/skills/flash-attention/references/benchmarks.md +0 -215
  76. evoscientist-0.0.1.dev4/EvoScientist/skills/flash-attention/references/transformers-integration.md +0 -293
  77. evoscientist-0.0.1.dev4/EvoScientist/skills/llama-cpp/SKILL.md +0 -258
  78. evoscientist-0.0.1.dev4/EvoScientist/skills/llama-cpp/references/optimization.md +0 -89
  79. evoscientist-0.0.1.dev4/EvoScientist/skills/llama-cpp/references/quantization.md +0 -213
  80. evoscientist-0.0.1.dev4/EvoScientist/skills/llama-cpp/references/server.md +0 -125
  81. evoscientist-0.0.1.dev4/EvoScientist/skills/lm-evaluation-harness/SKILL.md +0 -490
  82. evoscientist-0.0.1.dev4/EvoScientist/skills/lm-evaluation-harness/references/api-evaluation.md +0 -490
  83. evoscientist-0.0.1.dev4/EvoScientist/skills/lm-evaluation-harness/references/benchmark-guide.md +0 -488
  84. evoscientist-0.0.1.dev4/EvoScientist/skills/lm-evaluation-harness/references/custom-tasks.md +0 -602
  85. evoscientist-0.0.1.dev4/EvoScientist/skills/lm-evaluation-harness/references/distributed-eval.md +0 -519
  86. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/SKILL.md +0 -937
  87. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/references/checklists.md +0 -361
  88. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/references/citation-workflow.md +0 -562
  89. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/references/reviewer-guidelines.md +0 -367
  90. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/references/sources.md +0 -159
  91. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/references/writing-guide.md +0 -476
  92. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/README.md +0 -251
  93. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/aaai2026/README.md +0 -534
  94. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/aaai2026/aaai2026-unified-supp.tex +0 -144
  95. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/aaai2026/aaai2026-unified-template.tex +0 -952
  96. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/aaai2026/aaai2026.bib +0 -111
  97. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/aaai2026/aaai2026.bst +0 -1493
  98. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/aaai2026/aaai2026.sty +0 -315
  99. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/acl/README.md +0 -50
  100. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/acl/acl.sty +0 -312
  101. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/acl/acl_latex.tex +0 -377
  102. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/acl/acl_lualatex.tex +0 -101
  103. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/acl/acl_natbib.bst +0 -1940
  104. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/acl/anthology.bib.txt +0 -26
  105. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/acl/custom.bib +0 -70
  106. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/acl/formatting.md +0 -326
  107. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/colm2025/README.md +0 -3
  108. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/colm2025/colm2025_conference.bib +0 -11
  109. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/colm2025/colm2025_conference.bst +0 -1440
  110. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/colm2025/colm2025_conference.pdf +0 -0
  111. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/colm2025/colm2025_conference.sty +0 -218
  112. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/colm2025/colm2025_conference.tex +0 -305
  113. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/colm2025/fancyhdr.sty +0 -485
  114. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/colm2025/math_commands.tex +0 -508
  115. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/colm2025/natbib.sty +0 -1246
  116. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/iclr2026/fancyhdr.sty +0 -485
  117. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/iclr2026/iclr2026_conference.bib +0 -24
  118. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/iclr2026/iclr2026_conference.bst +0 -1440
  119. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/iclr2026/iclr2026_conference.pdf +0 -0
  120. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/iclr2026/iclr2026_conference.sty +0 -246
  121. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/iclr2026/iclr2026_conference.tex +0 -414
  122. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/iclr2026/math_commands.tex +0 -508
  123. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/iclr2026/natbib.sty +0 -1246
  124. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/icml2026/algorithm.sty +0 -79
  125. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/icml2026/algorithmic.sty +0 -201
  126. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/icml2026/example_paper.bib +0 -75
  127. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/icml2026/example_paper.pdf +0 -0
  128. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/icml2026/example_paper.tex +0 -662
  129. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/icml2026/fancyhdr.sty +0 -864
  130. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/icml2026/icml2026.bst +0 -1443
  131. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/icml2026/icml2026.sty +0 -767
  132. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/icml2026/icml_numpapers.pdf +0 -0
  133. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/neurips2025/Makefile +0 -36
  134. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/neurips2025/extra_pkgs.tex +0 -53
  135. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/neurips2025/main.tex +0 -38
  136. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/neurips2025/neurips.sty +0 -382
  137. evoscientist-0.0.1.dev4/EvoScientist/skills/peft/SKILL.md +0 -431
  138. evoscientist-0.0.1.dev4/EvoScientist/skills/peft/references/advanced-usage.md +0 -514
  139. evoscientist-0.0.1.dev4/EvoScientist/skills/peft/references/troubleshooting.md +0 -480
  140. evoscientist-0.0.1.dev4/EvoScientist/skills/ray-data/SKILL.md +0 -326
  141. evoscientist-0.0.1.dev4/EvoScientist/skills/ray-data/references/integration.md +0 -82
  142. evoscientist-0.0.1.dev4/EvoScientist/skills/ray-data/references/transformations.md +0 -83
  143. evoscientist-0.0.1.dev4/EvoScientist/skills/skill-creator/LICENSE.txt +0 -202
  144. evoscientist-0.0.1.dev4/EvoScientist/tools.py +0 -208
  145. evoscientist-0.0.1.dev4/EvoScientist.egg-info/SOURCES.txt +0 -132
  146. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/EvoScientist/__main__.py +0 -0
  147. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/EvoScientist/llm/__init__.py +0 -0
  148. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/EvoScientist/prompts.py +0 -0
  149. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/EvoScientist/skills/skill-creator/references/output-patterns.md +0 -0
  150. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/EvoScientist/skills/skill-creator/references/workflows.md +0 -0
  151. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/EvoScientist/skills/skill-creator/scripts/init_skill.py +0 -0
  152. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/EvoScientist/skills/skill-creator/scripts/package_skill.py +0 -0
  153. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/EvoScientist/skills/skill-creator/scripts/quick_validate.py +0 -0
  154. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/EvoScientist/stream/emitter.py +0 -0
  155. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/EvoScientist/stream/state.py +0 -0
  156. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/EvoScientist/stream/tracker.py +0 -0
  157. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/EvoScientist/subagent.yaml +0 -0
  158. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/EvoScientist.egg-info/dependency_links.txt +0 -0
  159. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/EvoScientist.egg-info/entry_points.txt +0 -0
  160. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/EvoScientist.egg-info/top_level.txt +0 -0
  161. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/LICENSE +0 -0
  162. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/setup.cfg +0 -0
  163. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/tests/test_prompts.py +0 -0
  164. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/tests/test_stream_emitter.py +0 -0
  165. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/tests/test_stream_tracker.py +0 -0
  166. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev6}/tests/test_tools.py +0 -0
@@ -22,6 +22,7 @@ from deepagents.backends import FilesystemBackend, CompositeBackend
22
22
  from .backends import CustomSandboxBackend, MergedReadOnlyBackend
23
23
  from .config import get_effective_config, apply_config_to_env
24
24
  from .llm import get_chat_model
25
+ from .mcp import load_mcp_tools
25
26
  from .middleware import create_skills_middleware, create_memory_middleware
26
27
  from .prompts import RESEARCHER_INSTRUCTIONS, get_system_prompt
27
28
  from .utils import load_subagents
@@ -29,6 +30,7 @@ from .tools import tavily_search, think_tool, skill_manager
29
30
  from .paths import (
30
31
  ensure_dirs,
31
32
  default_workspace_dir,
33
+ set_active_workspace,
32
34
  MEMORY_DIR as _MEMORY_DIR_PATH,
33
35
  USER_SKILLS_DIR as _USER_SKILLS_DIR_PATH,
34
36
  )
@@ -51,6 +53,7 @@ MAX_ITERATIONS = _config.max_iterations
51
53
  # Workspace settings
52
54
  ensure_dirs()
53
55
  WORKSPACE_DIR = str(default_workspace_dir())
56
+ set_active_workspace(WORKSPACE_DIR)
54
57
  MEMORY_DIR = str(_MEMORY_DIR_PATH) # Shared across sessions (not per-session)
55
58
  SKILLS_DIR = str(Path(__file__).parent / "skills")
56
59
  USER_SKILLS_DIR = str(_USER_SKILLS_DIR_PATH)
@@ -73,7 +76,6 @@ SYSTEM_PROMPT = get_system_prompt(
73
76
  chat_model = get_chat_model(
74
77
  model=_config.model,
75
78
  provider=_config.provider,
76
- # thinking={"type": "enabled", "budget_tokens": 2000},
77
79
  )
78
80
 
79
81
  # Initialize workspace backend based on mode
@@ -115,45 +117,116 @@ tool_registry = {
115
117
  "tavily_search": tavily_search,
116
118
  }
117
119
 
120
+ # Base tools that every agent variant gets (before MCP)
121
+ BASE_TOOLS = [think_tool, skill_manager]
122
+
123
+
124
+ def _build_base_kwargs(base_backend, base_middleware):
125
+ """Build agent kwargs *without* MCP (fast, no subprocess spawning)."""
126
+ subs = load_subagents(
127
+ SUBAGENTS_CONFIG,
128
+ tool_registry=tool_registry,
129
+ prompt_refs=prompt_refs,
130
+ )
131
+ return dict(
132
+ name="EvoScientist",
133
+ model=chat_model,
134
+ tools=list(BASE_TOOLS),
135
+ backend=base_backend,
136
+ subagents=subs,
137
+ middleware=base_middleware,
138
+ system_prompt=SYSTEM_PROMPT,
139
+ )
140
+
141
+
142
+ def load_mcp_and_build_kwargs(base_backend, base_middleware):
143
+ """(Re-)load MCP tools and build agent kwargs.
144
+
145
+ Called on every ``create_cli_agent()`` call so that ``/new`` picks up
146
+ MCP config changes. Falls back to base kwargs if no MCP configured.
147
+ """
148
+ mcp_by_agent = load_mcp_tools()
149
+ if not mcp_by_agent:
150
+ return _build_base_kwargs(base_backend, base_middleware)
151
+
152
+ # Fresh tool registry — start from base tools + MCP tools
153
+ registry = dict(tool_registry)
154
+ for tools in mcp_by_agent.values():
155
+ for t in tools:
156
+ registry[t.name] = t
157
+
158
+ mcp_main = mcp_by_agent.pop("main", [])
159
+
160
+ subs = load_subagents(
161
+ SUBAGENTS_CONFIG,
162
+ tool_registry=registry,
163
+ prompt_refs=prompt_refs,
164
+ )
165
+
166
+ # Inject MCP tools into subagents by name
167
+ for sa in subs:
168
+ if sa_tools := mcp_by_agent.get(sa["name"], []):
169
+ sa.setdefault("tools", []).extend(sa_tools)
170
+
171
+ return dict(
172
+ name="EvoScientist",
173
+ model=chat_model,
174
+ tools=BASE_TOOLS + mcp_main,
175
+ backend=base_backend,
176
+ subagents=subs,
177
+ middleware=base_middleware,
178
+ system_prompt=SYSTEM_PROMPT,
179
+ )
180
+
181
+
118
182
  prompt_refs = {
119
183
  "RESEARCHER_INSTRUCTIONS": RESEARCHER_INSTRUCTIONS.format(date=current_date),
120
184
  }
121
185
 
122
- subagents = load_subagents(
123
- SUBAGENTS_CONFIG,
124
- tool_registry=tool_registry,
125
- prompt_refs=prompt_refs,
126
- )
186
+ base_middleware = [
187
+ create_memory_middleware(MEMORY_DIR, extraction_model=chat_model),
188
+ create_skills_middleware(backend),
189
+ ]
190
+
191
+ # Default agent (no checkpointer) — used by langgraph dev / LangSmith / notebooks.
192
+ # Lazily constructed on first access so MCP tools are included without
193
+ # spawning subprocesses at import time.
194
+ _EvoScientist_agent = None
127
195
 
128
- # Shared kwargs for agent creation
129
- _AGENT_KWARGS = dict(
130
- name="EvoScientist",
131
- model=chat_model,
132
- tools=[think_tool, skill_manager],
133
- backend=backend,
134
- subagents=subagents,
135
- middleware=[
136
- create_memory_middleware(MEMORY_DIR, extraction_model=chat_model),
137
- create_skills_middleware(SKILLS_DIR, user_skills_dir=USER_SKILLS_DIR),
138
- ],
139
- system_prompt=SYSTEM_PROMPT,
140
- )
141
196
 
142
- # Default agent (no checkpointer) — used by langgraph dev / LangSmith / notebooks
143
- EvoScientist_agent = create_deep_agent(**_AGENT_KWARGS).with_config({"recursion_limit": 500})
197
+ def _get_default_agent():
198
+ """Build the default agent (with MCP, no checkpointer) on first access."""
199
+ global _EvoScientist_agent
200
+ if _EvoScientist_agent is None:
201
+ kwargs = load_mcp_and_build_kwargs(backend, base_middleware)
202
+ _EvoScientist_agent = create_deep_agent(**kwargs).with_config(
203
+ {"recursion_limit": 500}
204
+ )
205
+ return _EvoScientist_agent
206
+
144
207
 
208
+ def __getattr__(name: str):
209
+ if name == "EvoScientist_agent":
210
+ return _get_default_agent()
211
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
145
212
 
146
- def create_cli_agent(workspace_dir: str | None = None):
147
- """Create agent with InMemorySaver checkpointer for CLI multi-turn support.
213
+
214
+ def create_cli_agent(workspace_dir: str | None = None, checkpointer=None):
215
+ """Create agent with checkpointer for CLI multi-turn support.
148
216
 
149
217
  Args:
150
218
  workspace_dir: Optional per-session workspace directory. If provided,
151
219
  creates a fresh backend rooted at this path. If None, uses the
152
220
  module-level default backend (./workspace).
221
+ checkpointer: Optional LangGraph checkpointer. If None, falls back
222
+ to ``InMemorySaver`` (non-persistent).
153
223
  """
154
- from langgraph.checkpoint.memory import InMemorySaver # type: ignore[import-untyped]
224
+ if checkpointer is None:
225
+ from langgraph.checkpoint.memory import InMemorySaver # type: ignore[import-untyped]
226
+ checkpointer = InMemorySaver()
155
227
 
156
228
  if workspace_dir:
229
+ set_active_workspace(workspace_dir)
157
230
  ws_backend = CustomSandboxBackend(
158
231
  root_dir=workspace_dir,
159
232
  virtual_mode=True,
@@ -175,19 +248,18 @@ def create_cli_agent(workspace_dir: str | None = None):
175
248
  "/memory/": mem_backend,
176
249
  },
177
250
  )
178
- mw = [
179
- create_memory_middleware(MEMORY_DIR, extraction_model=chat_model),
180
- create_skills_middleware(SKILLS_DIR, user_skills_dir=USER_SKILLS_DIR),
181
- ]
182
- kwargs = dict(
183
- _AGENT_KWARGS,
184
- backend=be,
185
- middleware=mw,
186
- )
187
251
  else:
188
- kwargs = dict(_AGENT_KWARGS)
252
+ be = backend
253
+
254
+ mw = [
255
+ create_memory_middleware(MEMORY_DIR, extraction_model=chat_model),
256
+ create_skills_middleware(be),
257
+ ]
258
+
259
+ # Re-load MCP tools from current config (picks up /mcp add changes)
260
+ kwargs = load_mcp_and_build_kwargs(be, mw)
189
261
 
190
262
  return create_deep_agent(
191
263
  **kwargs,
192
- checkpointer=InMemorySaver(),
264
+ checkpointer=checkpointer,
193
265
  ).with_config({"recursion_limit": 500})
@@ -0,0 +1,64 @@
1
+ """EvoScientist Agent - AI-powered research and code execution.
2
+
3
+ This package exposes a convenience API at the package root while keeping
4
+ imports lazy, so lightweight modules (for example config helpers) can be used
5
+ without importing heavy runtime dependencies.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from importlib import import_module
11
+
12
+
13
+ _EXPORTS: dict[str, tuple[str, str]] = {
14
+ # Agent graph (lazy to avoid expensive initialization at import time)
15
+ "EvoScientist_agent": (".EvoScientist", "EvoScientist_agent"),
16
+ "create_cli_agent": (".EvoScientist", "create_cli_agent"),
17
+ # Backends
18
+ "CustomSandboxBackend": (".backends", "CustomSandboxBackend"),
19
+ "ReadOnlyFilesystemBackend": (".backends", "ReadOnlyFilesystemBackend"),
20
+ # Configuration
21
+ "EvoScientistConfig": (".config", "EvoScientistConfig"),
22
+ "load_config": (".config", "load_config"),
23
+ "save_config": (".config", "save_config"),
24
+ "get_effective_config": (".config", "get_effective_config"),
25
+ "get_config_path": (".config", "get_config_path"),
26
+ # LLM
27
+ "get_chat_model": (".llm", "get_chat_model"),
28
+ "MODELS": (".llm", "MODELS"),
29
+ "list_models": (".llm", "list_models"),
30
+ "DEFAULT_MODEL": (".llm", "DEFAULT_MODEL"),
31
+ # Middleware
32
+ "create_skills_middleware": (".middleware", "create_skills_middleware"),
33
+ # Prompts
34
+ "get_system_prompt": (".prompts", "get_system_prompt"),
35
+ "RESEARCHER_INSTRUCTIONS": (".prompts", "RESEARCHER_INSTRUCTIONS"),
36
+ # Tools
37
+ "tavily_search": (".tools", "tavily_search"),
38
+ "think_tool": (".tools", "think_tool"),
39
+ # Sessions
40
+ "get_checkpointer": (".sessions", "get_checkpointer"),
41
+ "generate_thread_id": (".sessions", "generate_thread_id"),
42
+ "list_threads": (".sessions", "list_threads"),
43
+ "delete_thread": (".sessions", "delete_thread"),
44
+ }
45
+
46
+
47
+ def __getattr__(name: str):
48
+ target = _EXPORTS.get(name)
49
+ if target is None:
50
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
51
+
52
+ module_name, attr_name = target
53
+ module = import_module(module_name, package=__name__)
54
+ value = getattr(module, attr_name)
55
+ # Cache after first load to avoid repeated import lookups.
56
+ globals()[name] = value
57
+ return value
58
+
59
+
60
+ def __dir__() -> list[str]:
61
+ return sorted(set(globals()) | set(_EXPORTS))
62
+
63
+
64
+ __all__ = list(_EXPORTS)
@@ -2,16 +2,16 @@
2
2
 
3
3
  import os
4
4
  import re
5
- import subprocess
5
+ import uuid
6
6
  from pathlib import Path
7
7
 
8
- from deepagents.backends import FilesystemBackend
8
+ from deepagents.backends import FilesystemBackend, LocalShellBackend
9
9
  from deepagents.backends.filesystem import WriteResult, EditResult
10
10
  from deepagents.backends.protocol import (
11
+ BackendProtocol,
11
12
  ExecuteResponse,
12
13
  FileDownloadResponse,
13
14
  FileUploadResponse,
14
- SandboxBackendProtocol,
15
15
  )
16
16
 
17
17
  # System path prefixes that should never appear in virtual paths.
@@ -132,7 +132,7 @@ class ReadOnlyFilesystemBackend(FilesystemBackend):
132
132
  )
133
133
 
134
134
 
135
- class MergedReadOnlyBackend:
135
+ class MergedReadOnlyBackend(BackendProtocol):
136
136
  """Read-only backend that merges two directories.
137
137
 
138
138
  Reads from *primary* first (user skills in workspace/skills/),
@@ -205,27 +205,7 @@ class MergedReadOnlyBackend:
205
205
  error="This directory is read-only. Edit operations are not permitted here."
206
206
  )
207
207
 
208
- # -- async variants (required by middleware) --
209
-
210
- async def aread(self, file_path: str, offset: int = 0, limit: int = 2000) -> str:
211
- return self.read(file_path, offset, limit)
212
-
213
- async def als_info(self, path: str = "/") -> list:
214
- return self.ls_info(path)
215
-
216
- async def agrep_raw(self, pattern: str, path: str | None = None, glob: str | None = None) -> list:
217
- return self.grep_raw(pattern, path, glob)
218
-
219
- async def aglob_info(self, pattern: str, path: str = "/") -> list:
220
- return self.glob_info(pattern, path)
221
-
222
- async def awrite(self, file_path: str, content: str) -> WriteResult:
223
- return self.write(file_path, content)
224
-
225
- async def aedit(self, file_path: str, old_string: str, new_string: str, replace_all: bool = False) -> EditResult:
226
- return self.edit(file_path, old_string, new_string, replace_all)
227
-
228
- # -- download / upload (required by BackendProtocol) --
208
+ # -- download / upload --
229
209
 
230
210
  def download_files(self, paths: list[str]) -> list[FileDownloadResponse]:
231
211
  """Download files, trying primary then secondary."""
@@ -237,38 +217,34 @@ class MergedReadOnlyBackend:
237
217
  responses.append(resp)
238
218
  return responses
239
219
 
240
- async def adownload_files(self, paths: list[str]) -> list[FileDownloadResponse]:
241
- return self.download_files(paths)
242
-
243
220
  def upload_files(self, files: list[tuple[str, bytes]]) -> list[FileUploadResponse]:
244
221
  return [
245
222
  FileUploadResponse(path=path, error="permission_denied")
246
223
  for path, _ in files
247
224
  ]
248
225
 
249
- async def aupload_files(self, files: list[tuple[str, bytes]]) -> list[FileUploadResponse]:
250
- return self.upload_files(files)
251
-
252
226
 
253
- class CustomSandboxBackend(FilesystemBackend, SandboxBackendProtocol):
227
+ class CustomSandboxBackend(LocalShellBackend):
254
228
  """
255
- Custom sandbox backend - inherits FilesystemBackend and implements execute method.
229
+ Custom sandbox backend - inherits LocalShellBackend with added safety.
256
230
 
257
231
  Features:
258
232
  - Inherits all file operations (ls, read, write, edit, grep, glob)
259
- - Adds shell command execution capability
260
- - Command validation prevents directory traversal and dangerous operations
261
- - Runs commands in specified working directory
233
+ - Inherits shell command execution with output truncation and timeout
234
+ - Adds command validation to prevent directory traversal and dangerous operations
235
+ - Adds path sanitization to auto-correct common LLM path mistakes
262
236
  - Compatible with LangGraph checkpointer (no thread locks)
263
237
  """
264
238
 
265
239
  def __init__(
266
240
  self,
267
241
  root_dir: str = ".",
242
+ *,
268
243
  virtual_mode: bool = True,
269
- working_dir: str | None = None,
270
244
  timeout: int = 300,
271
- shell: str = "/bin/bash",
245
+ max_output_bytes: int = 100_000,
246
+ env: dict[str, str] | None = None,
247
+ inherit_env: bool = True,
272
248
  ):
273
249
  """
274
250
  Initialize custom sandbox backend.
@@ -276,19 +252,23 @@ class CustomSandboxBackend(FilesystemBackend, SandboxBackendProtocol):
276
252
  Args:
277
253
  root_dir: File system root directory
278
254
  virtual_mode: Whether to enable virtual path mode
279
- working_dir: Working directory for command execution (defaults to root_dir)
280
255
  timeout: Command execution timeout in seconds
281
- shell: Shell program to use
256
+ max_output_bytes: Max output size before truncation (default 100KB)
257
+ env: Extra environment variables for subprocess
258
+ inherit_env: Whether to inherit parent process env (default True)
282
259
  """
283
- super().__init__(root_dir=root_dir, virtual_mode=virtual_mode)
284
-
285
- self.working_dir = working_dir or root_dir
286
- self.timeout = timeout
287
- self.shell = shell
288
- self.virtual_mode = virtual_mode
289
-
260
+ super().__init__(
261
+ root_dir=root_dir,
262
+ virtual_mode=virtual_mode,
263
+ timeout=timeout,
264
+ max_output_bytes=max_output_bytes,
265
+ env=env,
266
+ inherit_env=inherit_env,
267
+ )
268
+ # Override parent's "local-" prefix with our own
269
+ self._sandbox_id = f"evosci-{uuid.uuid4().hex[:8]}"
290
270
  # Ensure working directory exists
291
- os.makedirs(self.working_dir, exist_ok=True)
271
+ os.makedirs(str(self.cwd), exist_ok=True)
292
272
 
293
273
  def _resolve_path(self, key: str) -> Path:
294
274
  """Resolve path with sanitization to prevent nested directories.
@@ -331,62 +311,20 @@ class CustomSandboxBackend(FilesystemBackend, SandboxBackendProtocol):
331
311
  - Access to paths outside workspace
332
312
  - Dangerous system commands
333
313
 
334
- Args:
335
- command: Command string to execute
336
-
337
- Returns:
338
- ExecuteResponse containing output, exit_code, and truncated flag
314
+ Then delegates to LocalShellBackend.execute() for actual execution.
339
315
  """
340
- try:
341
- # Validate command safety
342
- error = validate_command(command)
343
- if error:
344
- return ExecuteResponse(
345
- output=error,
346
- exit_code=1,
347
- truncated=False,
348
- )
349
-
350
- # Convert virtual paths to relative paths
351
- if self.virtual_mode:
352
- command = convert_virtual_paths_in_command(command=command)
353
-
354
- result = subprocess.run(
355
- command,
356
- shell=True,
357
- executable=self.shell,
358
- cwd=self.working_dir,
359
- capture_output=True,
360
- text=True,
361
- timeout=self.timeout,
362
- )
363
-
364
- output = ""
365
- if result.stdout:
366
- output += result.stdout
367
- if result.stderr:
368
- output += result.stderr
369
-
316
+ # Validate command safety
317
+ error = validate_command(command)
318
+ if error:
370
319
  return ExecuteResponse(
371
- output=output,
372
- exit_code=result.returncode,
320
+ output=error,
321
+ exit_code=1,
373
322
  truncated=False,
374
323
  )
375
324
 
376
- except subprocess.TimeoutExpired:
377
- return ExecuteResponse(
378
- output=f"Command timed out after {self.timeout} seconds",
379
- exit_code=-1,
380
- truncated=False,
381
- )
382
- except Exception as e:
383
- return ExecuteResponse(
384
- output=f"Error executing command: {str(e)}",
385
- exit_code=-1,
386
- truncated=False,
387
- )
325
+ # Convert virtual paths to relative paths
326
+ if self.virtual_mode:
327
+ command = convert_virtual_paths_in_command(command=command)
388
328
 
389
- async def aexecute(self, command: str) -> ExecuteResponse:
390
- """Async version of execute (runs sync version in thread)."""
391
- import asyncio
392
- return await asyncio.to_thread(self.execute, command)
329
+ # Delegate to parent for subprocess execution
330
+ return super().execute(command)
@@ -0,0 +1,9 @@
1
+ """Communication channels for EvoScientist.
2
+
3
+ This module provides an extensible interface for different messaging channels
4
+ (iMessage, WeChat, etc.) to communicate with the EvoScientist agent.
5
+ """
6
+
7
+ from .base import Channel, IncomingMessage, OutgoingMessage
8
+
9
+ __all__ = ["Channel", "IncomingMessage", "OutgoingMessage"]
@@ -0,0 +1,110 @@
1
+ """Abstract base class for communication channels.
2
+
3
+ This module defines the Channel interface that all messaging channels
4
+ (iMessage, WeChat, etc.) must implement.
5
+ """
6
+
7
+ from abc import ABC, abstractmethod
8
+ from dataclasses import dataclass, field
9
+ from datetime import datetime
10
+ from typing import AsyncIterator
11
+
12
+
13
+ @dataclass
14
+ class IncomingMessage:
15
+ """Represents a message received from a channel."""
16
+
17
+ sender: str # Phone number, email, or unique identifier
18
+ content: str # Message text content
19
+ timestamp: datetime # When the message was sent
20
+ message_id: str # Unique identifier for the message
21
+ metadata: dict = field(default_factory=dict) # Channel-specific metadata
22
+
23
+
24
+ @dataclass
25
+ class OutgoingMessage:
26
+ """Represents a message to be sent through a channel."""
27
+
28
+ recipient: str # Phone number, email, or unique identifier
29
+ content: str # Message text content
30
+ reply_to: str | None = None # Optional message ID being replied to
31
+ metadata: dict = field(default_factory=dict) # Channel-specific metadata
32
+
33
+
34
+ class Channel(ABC):
35
+ """Abstract base class for messaging channels.
36
+
37
+ Subclasses must implement:
38
+ - start(): Initialize the channel (connect, authenticate, etc.)
39
+ - stop(): Clean up resources
40
+ - receive(): Async iterator yielding incoming messages
41
+ - send(): Send a message through the channel
42
+ """
43
+
44
+ @abstractmethod
45
+ async def start(self) -> None:
46
+ """Initialize and start the channel.
47
+
48
+ This method should:
49
+ - Establish connections
50
+ - Verify permissions/authentication
51
+ - Start any background tasks needed
52
+
53
+ Raises:
54
+ ChannelError: If initialization fails
55
+ """
56
+ pass
57
+
58
+ @abstractmethod
59
+ async def stop(self) -> None:
60
+ """Stop the channel and clean up resources.
61
+
62
+ This method should:
63
+ - Close connections
64
+ - Cancel background tasks
65
+ - Release any held resources
66
+ """
67
+ pass
68
+
69
+ @abstractmethod
70
+ async def receive(self) -> AsyncIterator[IncomingMessage]:
71
+ """Async iterator that yields incoming messages.
72
+
73
+ Yields:
74
+ IncomingMessage: Each new message received
75
+
76
+ Example:
77
+ async for msg in channel.receive():
78
+ print(f"From {msg.sender}: {msg.content}")
79
+ """
80
+ pass
81
+
82
+ @abstractmethod
83
+ async def send(self, message: OutgoingMessage) -> bool:
84
+ """Send a message through the channel.
85
+
86
+ Args:
87
+ message: The message to send
88
+
89
+ Returns:
90
+ True if sent successfully, False otherwise
91
+ """
92
+ pass
93
+
94
+
95
+ class ChannelError(Exception):
96
+ """Base exception for channel-related errors."""
97
+
98
+ pass
99
+
100
+
101
+ class ChannelPermissionError(ChannelError):
102
+ """Raised when the channel lacks required permissions."""
103
+
104
+ pass
105
+
106
+
107
+ class ChannelConnectionError(ChannelError):
108
+ """Raised when the channel cannot establish a connection."""
109
+
110
+ pass
@@ -0,0 +1,33 @@
1
+ """iMessage channel implementation for EvoScientist.
2
+
3
+ Uses imsg CLI via JSON-RPC for real-time message streaming.
4
+
5
+ Requirements:
6
+ - macOS only
7
+ - imsg CLI: brew install steipete/tap/imsg
8
+ - Full Disk Access permission
9
+ - Messages.app logged into iCloud
10
+ """
11
+
12
+ from .channel_rpc import IMessageChannelRpc as IMessageChannel
13
+ from .channel_rpc import IMessageConfig
14
+ from .probe import probe_imessage, ProbeResult
15
+ from .targets import (
16
+ parse_target,
17
+ normalize_handle,
18
+ normalize_e164,
19
+ IMessageTarget,
20
+ IMessageService,
21
+ )
22
+
23
+ __all__ = [
24
+ "IMessageChannel",
25
+ "IMessageConfig",
26
+ "probe_imessage",
27
+ "ProbeResult",
28
+ "parse_target",
29
+ "normalize_handle",
30
+ "normalize_e164",
31
+ "IMessageTarget",
32
+ "IMessageService",
33
+ ]