EvoScientist 0.0.1.dev4__tar.gz → 0.0.1.dev5__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 (157) hide show
  1. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/EvoScientist/EvoScientist.py +85 -32
  2. evoscientist-0.0.1.dev5/EvoScientist/__init__.py +60 -0
  3. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/EvoScientist/backends.py +39 -32
  4. evoscientist-0.0.1.dev5/EvoScientist/channels/__init__.py +9 -0
  5. evoscientist-0.0.1.dev5/EvoScientist/channels/base.py +110 -0
  6. evoscientist-0.0.1.dev5/EvoScientist/channels/imessage/__init__.py +33 -0
  7. evoscientist-0.0.1.dev5/EvoScientist/channels/imessage/channel_rpc.py +427 -0
  8. evoscientist-0.0.1.dev5/EvoScientist/channels/imessage/probe.py +106 -0
  9. evoscientist-0.0.1.dev5/EvoScientist/channels/imessage/rpc_client.py +235 -0
  10. evoscientist-0.0.1.dev5/EvoScientist/channels/imessage/serve.py +428 -0
  11. evoscientist-0.0.1.dev5/EvoScientist/channels/imessage/targets.py +232 -0
  12. evoscientist-0.0.1.dev5/EvoScientist/cli.py +1653 -0
  13. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/EvoScientist/config.py +11 -1
  14. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/EvoScientist/llm/models.py +20 -4
  15. evoscientist-0.0.1.dev5/EvoScientist/mcp/__init__.py +29 -0
  16. evoscientist-0.0.1.dev5/EvoScientist/mcp/client.py +591 -0
  17. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/EvoScientist/middleware.py +7 -21
  18. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/EvoScientist/onboard.py +603 -3
  19. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/EvoScientist/paths.py +16 -0
  20. evoscientist-0.0.1.dev5/EvoScientist/skills/find-skills/SKILL.md +76 -0
  21. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/EvoScientist/skills/skill-creator/SKILL.md +64 -58
  22. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/EvoScientist/stream/__init__.py +2 -0
  23. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/EvoScientist/stream/display.py +209 -5
  24. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/EvoScientist/stream/formatter.py +4 -1
  25. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/EvoScientist/stream/utils.py +11 -2
  26. evoscientist-0.0.1.dev5/EvoScientist/tools/__init__.py +18 -0
  27. evoscientist-0.0.1.dev5/EvoScientist/tools/image.py +74 -0
  28. evoscientist-0.0.1.dev5/EvoScientist/tools/search.py +115 -0
  29. evoscientist-0.0.1.dev5/EvoScientist/tools/skill_manager.py +120 -0
  30. {evoscientist-0.0.1.dev4/EvoScientist → evoscientist-0.0.1.dev5/EvoScientist/tools}/skills_manager.py +105 -26
  31. evoscientist-0.0.1.dev5/EvoScientist/tools/think.py +32 -0
  32. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/EvoScientist.egg-info/PKG-INFO +162 -7
  33. evoscientist-0.0.1.dev5/EvoScientist.egg-info/SOURCES.txt +70 -0
  34. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/EvoScientist.egg-info/requires.txt +3 -0
  35. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/PKG-INFO +162 -7
  36. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/README.md +158 -6
  37. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/pyproject.toml +4 -1
  38. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/tests/test_backends.py +88 -0
  39. evoscientist-0.0.1.dev5/tests/test_cli_run_name.py +38 -0
  40. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/tests/test_config.py +19 -0
  41. evoscientist-0.0.1.dev5/tests/test_event_loop.py +200 -0
  42. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/tests/test_imports.py +0 -15
  43. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/tests/test_llm.py +20 -4
  44. evoscientist-0.0.1.dev5/tests/test_mcp_client.py +539 -0
  45. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/tests/test_onboard.py +294 -14
  46. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/tests/test_skills_manager.py +7 -7
  47. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/tests/test_stream_state.py +161 -11
  48. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/tests/test_stream_utils.py +28 -4
  49. evoscientist-0.0.1.dev5/tests/test_tools.py +107 -0
  50. evoscientist-0.0.1.dev4/EvoScientist/__init__.py +0 -43
  51. evoscientist-0.0.1.dev4/EvoScientist/cli.py +0 -668
  52. evoscientist-0.0.1.dev4/EvoScientist/skills/accelerate/SKILL.md +0 -332
  53. evoscientist-0.0.1.dev4/EvoScientist/skills/accelerate/references/custom-plugins.md +0 -453
  54. evoscientist-0.0.1.dev4/EvoScientist/skills/accelerate/references/megatron-integration.md +0 -489
  55. evoscientist-0.0.1.dev4/EvoScientist/skills/accelerate/references/performance.md +0 -525
  56. evoscientist-0.0.1.dev4/EvoScientist/skills/bitsandbytes/SKILL.md +0 -411
  57. evoscientist-0.0.1.dev4/EvoScientist/skills/bitsandbytes/references/memory-optimization.md +0 -521
  58. evoscientist-0.0.1.dev4/EvoScientist/skills/bitsandbytes/references/qlora-training.md +0 -521
  59. evoscientist-0.0.1.dev4/EvoScientist/skills/bitsandbytes/references/quantization-formats.md +0 -447
  60. evoscientist-0.0.1.dev4/EvoScientist/skills/find-skills/SKILL.md +0 -133
  61. evoscientist-0.0.1.dev4/EvoScientist/skills/find-skills/scripts/install_skill.py +0 -211
  62. evoscientist-0.0.1.dev4/EvoScientist/skills/flash-attention/SKILL.md +0 -367
  63. evoscientist-0.0.1.dev4/EvoScientist/skills/flash-attention/references/benchmarks.md +0 -215
  64. evoscientist-0.0.1.dev4/EvoScientist/skills/flash-attention/references/transformers-integration.md +0 -293
  65. evoscientist-0.0.1.dev4/EvoScientist/skills/llama-cpp/SKILL.md +0 -258
  66. evoscientist-0.0.1.dev4/EvoScientist/skills/llama-cpp/references/optimization.md +0 -89
  67. evoscientist-0.0.1.dev4/EvoScientist/skills/llama-cpp/references/quantization.md +0 -213
  68. evoscientist-0.0.1.dev4/EvoScientist/skills/llama-cpp/references/server.md +0 -125
  69. evoscientist-0.0.1.dev4/EvoScientist/skills/lm-evaluation-harness/SKILL.md +0 -490
  70. evoscientist-0.0.1.dev4/EvoScientist/skills/lm-evaluation-harness/references/api-evaluation.md +0 -490
  71. evoscientist-0.0.1.dev4/EvoScientist/skills/lm-evaluation-harness/references/benchmark-guide.md +0 -488
  72. evoscientist-0.0.1.dev4/EvoScientist/skills/lm-evaluation-harness/references/custom-tasks.md +0 -602
  73. evoscientist-0.0.1.dev4/EvoScientist/skills/lm-evaluation-harness/references/distributed-eval.md +0 -519
  74. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/SKILL.md +0 -937
  75. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/references/checklists.md +0 -361
  76. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/references/citation-workflow.md +0 -562
  77. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/references/reviewer-guidelines.md +0 -367
  78. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/references/sources.md +0 -159
  79. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/references/writing-guide.md +0 -476
  80. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/README.md +0 -251
  81. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/aaai2026/README.md +0 -534
  82. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/aaai2026/aaai2026-unified-supp.tex +0 -144
  83. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/aaai2026/aaai2026-unified-template.tex +0 -952
  84. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/aaai2026/aaai2026.bib +0 -111
  85. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/aaai2026/aaai2026.bst +0 -1493
  86. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/aaai2026/aaai2026.sty +0 -315
  87. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/acl/README.md +0 -50
  88. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/acl/acl.sty +0 -312
  89. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/acl/acl_latex.tex +0 -377
  90. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/acl/acl_lualatex.tex +0 -101
  91. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/acl/acl_natbib.bst +0 -1940
  92. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/acl/anthology.bib.txt +0 -26
  93. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/acl/custom.bib +0 -70
  94. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/acl/formatting.md +0 -326
  95. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/colm2025/README.md +0 -3
  96. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/colm2025/colm2025_conference.bib +0 -11
  97. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/colm2025/colm2025_conference.bst +0 -1440
  98. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/colm2025/colm2025_conference.pdf +0 -0
  99. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/colm2025/colm2025_conference.sty +0 -218
  100. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/colm2025/colm2025_conference.tex +0 -305
  101. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/colm2025/fancyhdr.sty +0 -485
  102. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/colm2025/math_commands.tex +0 -508
  103. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/colm2025/natbib.sty +0 -1246
  104. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/iclr2026/fancyhdr.sty +0 -485
  105. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/iclr2026/iclr2026_conference.bib +0 -24
  106. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/iclr2026/iclr2026_conference.bst +0 -1440
  107. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/iclr2026/iclr2026_conference.pdf +0 -0
  108. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/iclr2026/iclr2026_conference.sty +0 -246
  109. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/iclr2026/iclr2026_conference.tex +0 -414
  110. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/iclr2026/math_commands.tex +0 -508
  111. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/iclr2026/natbib.sty +0 -1246
  112. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/icml2026/algorithm.sty +0 -79
  113. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/icml2026/algorithmic.sty +0 -201
  114. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/icml2026/example_paper.bib +0 -75
  115. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/icml2026/example_paper.pdf +0 -0
  116. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/icml2026/example_paper.tex +0 -662
  117. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/icml2026/fancyhdr.sty +0 -864
  118. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/icml2026/icml2026.bst +0 -1443
  119. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/icml2026/icml2026.sty +0 -767
  120. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/icml2026/icml_numpapers.pdf +0 -0
  121. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/neurips2025/Makefile +0 -36
  122. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/neurips2025/extra_pkgs.tex +0 -53
  123. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/neurips2025/main.tex +0 -38
  124. evoscientist-0.0.1.dev4/EvoScientist/skills/ml-paper-writing/templates/neurips2025/neurips.sty +0 -382
  125. evoscientist-0.0.1.dev4/EvoScientist/skills/peft/SKILL.md +0 -431
  126. evoscientist-0.0.1.dev4/EvoScientist/skills/peft/references/advanced-usage.md +0 -514
  127. evoscientist-0.0.1.dev4/EvoScientist/skills/peft/references/troubleshooting.md +0 -480
  128. evoscientist-0.0.1.dev4/EvoScientist/skills/ray-data/SKILL.md +0 -326
  129. evoscientist-0.0.1.dev4/EvoScientist/skills/ray-data/references/integration.md +0 -82
  130. evoscientist-0.0.1.dev4/EvoScientist/skills/ray-data/references/transformations.md +0 -83
  131. evoscientist-0.0.1.dev4/EvoScientist/skills/skill-creator/LICENSE.txt +0 -202
  132. evoscientist-0.0.1.dev4/EvoScientist/tools.py +0 -208
  133. evoscientist-0.0.1.dev4/EvoScientist.egg-info/SOURCES.txt +0 -132
  134. evoscientist-0.0.1.dev4/tests/test_tools.py +0 -18
  135. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/EvoScientist/__main__.py +0 -0
  136. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/EvoScientist/llm/__init__.py +0 -0
  137. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/EvoScientist/memory.py +0 -0
  138. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/EvoScientist/prompts.py +0 -0
  139. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/EvoScientist/skills/skill-creator/references/output-patterns.md +0 -0
  140. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/EvoScientist/skills/skill-creator/references/workflows.md +0 -0
  141. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/EvoScientist/skills/skill-creator/scripts/init_skill.py +0 -0
  142. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/EvoScientist/skills/skill-creator/scripts/package_skill.py +0 -0
  143. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/EvoScientist/skills/skill-creator/scripts/quick_validate.py +0 -0
  144. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/EvoScientist/stream/emitter.py +0 -0
  145. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/EvoScientist/stream/events.py +0 -0
  146. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/EvoScientist/stream/state.py +0 -0
  147. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/EvoScientist/stream/tracker.py +0 -0
  148. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/EvoScientist/subagent.yaml +0 -0
  149. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/EvoScientist/utils.py +0 -0
  150. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/EvoScientist.egg-info/dependency_links.txt +0 -0
  151. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/EvoScientist.egg-info/entry_points.txt +0 -0
  152. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/EvoScientist.egg-info/top_level.txt +0 -0
  153. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/LICENSE +0 -0
  154. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/setup.cfg +0 -0
  155. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/tests/test_prompts.py +0 -0
  156. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/tests/test_stream_emitter.py +0 -0
  157. {evoscientist-0.0.1.dev4 → evoscientist-0.0.1.dev5}/tests/test_stream_tracker.py +0 -0
@@ -22,13 +22,15 @@ 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
28
- from .tools import tavily_search, think_tool, skill_manager
29
+ from .tools import tavily_search, think_tool, skill_manager, view_image
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
@@ -113,33 +115,84 @@ backend = CompositeBackend(
113
115
  tool_registry = {
114
116
  "think_tool": think_tool,
115
117
  "tavily_search": tavily_search,
118
+ "view_image": view_image,
116
119
  }
117
120
 
121
+ # Base tools that every agent variant gets (before MCP)
122
+ BASE_TOOLS = [think_tool, skill_manager, view_image]
123
+
124
+
125
+ def _build_base_kwargs(base_backend, base_middleware):
126
+ """Build agent kwargs *without* MCP (fast, no subprocess spawning)."""
127
+ subs = load_subagents(
128
+ SUBAGENTS_CONFIG,
129
+ tool_registry=tool_registry,
130
+ prompt_refs=prompt_refs,
131
+ )
132
+ return dict(
133
+ name="EvoScientist",
134
+ model=chat_model,
135
+ tools=list(BASE_TOOLS),
136
+ backend=base_backend,
137
+ subagents=subs,
138
+ middleware=base_middleware,
139
+ system_prompt=SYSTEM_PROMPT,
140
+ )
141
+
142
+
143
+ def load_mcp_and_build_kwargs(base_backend, base_middleware):
144
+ """(Re-)load MCP tools and build agent kwargs.
145
+
146
+ Called on every ``create_cli_agent()`` call so that ``/new`` picks up
147
+ MCP config changes. Falls back to base kwargs if no MCP configured.
148
+ """
149
+ mcp_by_agent = load_mcp_tools()
150
+ if not mcp_by_agent:
151
+ return _build_base_kwargs(base_backend, base_middleware)
152
+
153
+ # Fresh tool registry — start from base tools + MCP tools
154
+ registry = dict(tool_registry)
155
+ for tools in mcp_by_agent.values():
156
+ for t in tools:
157
+ registry[t.name] = t
158
+
159
+ mcp_main = mcp_by_agent.pop("main", [])
160
+
161
+ subs = load_subagents(
162
+ SUBAGENTS_CONFIG,
163
+ tool_registry=registry,
164
+ prompt_refs=prompt_refs,
165
+ )
166
+
167
+ # Inject MCP tools into subagents by name
168
+ for sa in subs:
169
+ if sa_tools := mcp_by_agent.get(sa["name"], []):
170
+ sa.setdefault("tools", []).extend(sa_tools)
171
+
172
+ return dict(
173
+ name="EvoScientist",
174
+ model=chat_model,
175
+ tools=BASE_TOOLS + mcp_main,
176
+ backend=base_backend,
177
+ subagents=subs,
178
+ middleware=base_middleware,
179
+ system_prompt=SYSTEM_PROMPT,
180
+ )
181
+
182
+
118
183
  prompt_refs = {
119
184
  "RESEARCHER_INSTRUCTIONS": RESEARCHER_INSTRUCTIONS.format(date=current_date),
120
185
  }
121
186
 
122
- subagents = load_subagents(
123
- SUBAGENTS_CONFIG,
124
- tool_registry=tool_registry,
125
- prompt_refs=prompt_refs,
126
- )
127
-
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
- )
187
+ base_middleware = [
188
+ create_memory_middleware(MEMORY_DIR, extraction_model=chat_model),
189
+ create_skills_middleware(backend),
190
+ ]
141
191
 
142
- # Default agent (no checkpointer) — used by langgraph dev / LangSmith / notebooks
192
+ # Default agent (no checkpointer) — used by langgraph dev / LangSmith / notebooks.
193
+ # Built WITHOUT MCP at import time to avoid spawning subprocesses on every import.
194
+ # MCP tools are loaded on-demand in create_cli_agent().
195
+ _AGENT_KWARGS = _build_base_kwargs(backend, base_middleware)
143
196
  EvoScientist_agent = create_deep_agent(**_AGENT_KWARGS).with_config({"recursion_limit": 500})
144
197
 
145
198
 
@@ -154,6 +207,7 @@ def create_cli_agent(workspace_dir: str | None = None):
154
207
  from langgraph.checkpoint.memory import InMemorySaver # type: ignore[import-untyped]
155
208
 
156
209
  if workspace_dir:
210
+ set_active_workspace(workspace_dir)
157
211
  ws_backend = CustomSandboxBackend(
158
212
  root_dir=workspace_dir,
159
213
  virtual_mode=True,
@@ -175,17 +229,16 @@ def create_cli_agent(workspace_dir: str | None = None):
175
229
  "/memory/": mem_backend,
176
230
  },
177
231
  )
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
232
  else:
188
- kwargs = dict(_AGENT_KWARGS)
233
+ be = backend
234
+
235
+ mw = [
236
+ create_memory_middleware(MEMORY_DIR, extraction_model=chat_model),
237
+ create_skills_middleware(be),
238
+ ]
239
+
240
+ # Re-load MCP tools from current config (picks up /mcp add changes)
241
+ kwargs = load_mcp_and_build_kwargs(be, mw)
189
242
 
190
243
  return create_deep_agent(
191
244
  **kwargs,
@@ -0,0 +1,60 @@
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
+ "view_image": (".tools", "view_image"),
40
+ }
41
+
42
+
43
+ def __getattr__(name: str):
44
+ target = _EXPORTS.get(name)
45
+ if target is None:
46
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
47
+
48
+ module_name, attr_name = target
49
+ module = import_module(module_name, package=__name__)
50
+ value = getattr(module, attr_name)
51
+ # Cache after first load to avoid repeated import lookups.
52
+ globals()[name] = value
53
+ return value
54
+
55
+
56
+ def __dir__() -> list[str]:
57
+ return sorted(set(globals()) | set(_EXPORTS))
58
+
59
+
60
+ __all__ = list(_EXPORTS)
@@ -3,11 +3,13 @@
3
3
  import os
4
4
  import re
5
5
  import subprocess
6
+ import uuid
6
7
  from pathlib import Path
7
8
 
8
9
  from deepagents.backends import FilesystemBackend
9
10
  from deepagents.backends.filesystem import WriteResult, EditResult
10
11
  from deepagents.backends.protocol import (
12
+ BackendProtocol,
11
13
  ExecuteResponse,
12
14
  FileDownloadResponse,
13
15
  FileUploadResponse,
@@ -132,7 +134,7 @@ class ReadOnlyFilesystemBackend(FilesystemBackend):
132
134
  )
133
135
 
134
136
 
135
- class MergedReadOnlyBackend:
137
+ class MergedReadOnlyBackend(BackendProtocol):
136
138
  """Read-only backend that merges two directories.
137
139
 
138
140
  Reads from *primary* first (user skills in workspace/skills/),
@@ -205,27 +207,7 @@ class MergedReadOnlyBackend:
205
207
  error="This directory is read-only. Edit operations are not permitted here."
206
208
  )
207
209
 
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) --
210
+ # -- download / upload --
229
211
 
230
212
  def download_files(self, paths: list[str]) -> list[FileDownloadResponse]:
231
213
  """Download files, trying primary then secondary."""
@@ -237,18 +219,12 @@ class MergedReadOnlyBackend:
237
219
  responses.append(resp)
238
220
  return responses
239
221
 
240
- async def adownload_files(self, paths: list[str]) -> list[FileDownloadResponse]:
241
- return self.download_files(paths)
242
-
243
222
  def upload_files(self, files: list[tuple[str, bytes]]) -> list[FileUploadResponse]:
244
223
  return [
245
224
  FileUploadResponse(path=path, error="permission_denied")
246
225
  for path, _ in files
247
226
  ]
248
227
 
249
- async def aupload_files(self, files: list[tuple[str, bytes]]) -> list[FileUploadResponse]:
250
- return self.upload_files(files)
251
-
252
228
 
253
229
  class CustomSandboxBackend(FilesystemBackend, SandboxBackendProtocol):
254
230
  """
@@ -269,6 +245,9 @@ class CustomSandboxBackend(FilesystemBackend, SandboxBackendProtocol):
269
245
  working_dir: str | None = None,
270
246
  timeout: int = 300,
271
247
  shell: str = "/bin/bash",
248
+ max_output_bytes: int = 100_000,
249
+ env: dict[str, str] | None = None,
250
+ inherit_env: bool = True,
272
251
  ):
273
252
  """
274
253
  Initialize custom sandbox backend.
@@ -279,17 +258,33 @@ class CustomSandboxBackend(FilesystemBackend, SandboxBackendProtocol):
279
258
  working_dir: Working directory for command execution (defaults to root_dir)
280
259
  timeout: Command execution timeout in seconds
281
260
  shell: Shell program to use
261
+ max_output_bytes: Max output size before truncation (default 100KB)
262
+ env: Extra environment variables for subprocess
263
+ inherit_env: Whether to inherit parent process env (default True)
282
264
  """
283
265
  super().__init__(root_dir=root_dir, virtual_mode=virtual_mode)
284
266
 
267
+ self._sandbox_id = f"evosci-{uuid.uuid4().hex[:8]}"
285
268
  self.working_dir = working_dir or root_dir
286
269
  self.timeout = timeout
287
270
  self.shell = shell
288
271
  self.virtual_mode = virtual_mode
272
+ self._max_output_bytes = max_output_bytes
273
+
274
+ # Build subprocess environment
275
+ if inherit_env:
276
+ self._env = {**os.environ, **(env or {})}
277
+ else:
278
+ self._env = dict(env) if env else {}
289
279
 
290
280
  # Ensure working directory exists
291
281
  os.makedirs(self.working_dir, exist_ok=True)
292
282
 
283
+ @property
284
+ def id(self) -> str:
285
+ """Unique identifier for the sandbox backend instance."""
286
+ return self._sandbox_id
287
+
293
288
  def _resolve_path(self, key: str) -> Path:
294
289
  """Resolve path with sanitization to prevent nested directories.
295
290
 
@@ -359,18 +354,30 @@ class CustomSandboxBackend(FilesystemBackend, SandboxBackendProtocol):
359
354
  capture_output=True,
360
355
  text=True,
361
356
  timeout=self.timeout,
357
+ env=self._env,
362
358
  )
363
359
 
364
- output = ""
360
+ output_parts = []
365
361
  if result.stdout:
366
- output += result.stdout
362
+ output_parts.append(result.stdout)
367
363
  if result.stderr:
368
- output += result.stderr
364
+ stderr_lines = result.stderr.strip().split("\n")
365
+ output_parts.extend(f"[stderr] {line}" for line in stderr_lines)
366
+ output = "\n".join(output_parts) if output_parts else ""
367
+
368
+ if result.returncode != 0:
369
+ output = f"{output.rstrip()}\n\nExit code: {result.returncode}"
370
+
371
+ truncated = False
372
+ if len(output) > self._max_output_bytes:
373
+ output = output[:self._max_output_bytes]
374
+ output += f"\n\n... Output truncated at {self._max_output_bytes} bytes."
375
+ truncated = True
369
376
 
370
377
  return ExecuteResponse(
371
378
  output=output,
372
379
  exit_code=result.returncode,
373
- truncated=False,
380
+ truncated=truncated,
374
381
  )
375
382
 
376
383
  except subprocess.TimeoutExpired:
@@ -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
+ ]