EvoScientist 0.0.1.dev3__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 (158) hide show
  1. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/EvoScientist/EvoScientist.py +98 -41
  2. evoscientist-0.0.1.dev5/EvoScientist/__init__.py +60 -0
  3. {evoscientist-0.0.1.dev3 → 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.dev5/EvoScientist/config.py +284 -0
  14. evoscientist-0.0.1.dev5/EvoScientist/llm/__init__.py +21 -0
  15. evoscientist-0.0.1.dev5/EvoScientist/llm/models.py +115 -0
  16. evoscientist-0.0.1.dev5/EvoScientist/mcp/__init__.py +29 -0
  17. evoscientist-0.0.1.dev5/EvoScientist/mcp/client.py +591 -0
  18. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/EvoScientist/middleware.py +7 -21
  19. evoscientist-0.0.1.dev5/EvoScientist/onboard.py +1325 -0
  20. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/EvoScientist/paths.py +18 -3
  21. evoscientist-0.0.1.dev5/EvoScientist/skills/find-skills/SKILL.md +76 -0
  22. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/EvoScientist/skills/skill-creator/SKILL.md +64 -58
  23. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/EvoScientist/stream/__init__.py +2 -0
  24. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/EvoScientist/stream/display.py +210 -6
  25. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/EvoScientist/stream/formatter.py +4 -1
  26. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/EvoScientist/stream/utils.py +11 -2
  27. evoscientist-0.0.1.dev5/EvoScientist/tools/__init__.py +18 -0
  28. evoscientist-0.0.1.dev5/EvoScientist/tools/image.py +74 -0
  29. evoscientist-0.0.1.dev5/EvoScientist/tools/search.py +115 -0
  30. evoscientist-0.0.1.dev5/EvoScientist/tools/skill_manager.py +120 -0
  31. {evoscientist-0.0.1.dev3/EvoScientist → evoscientist-0.0.1.dev5/EvoScientist/tools}/skills_manager.py +106 -28
  32. evoscientist-0.0.1.dev5/EvoScientist/tools/think.py +32 -0
  33. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/EvoScientist.egg-info/PKG-INFO +225 -24
  34. evoscientist-0.0.1.dev5/EvoScientist.egg-info/SOURCES.txt +70 -0
  35. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/EvoScientist.egg-info/requires.txt +8 -1
  36. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/PKG-INFO +225 -24
  37. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/README.md +216 -22
  38. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/pyproject.toml +16 -3
  39. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/tests/test_backends.py +88 -3
  40. evoscientist-0.0.1.dev5/tests/test_cli_run_name.py +38 -0
  41. evoscientist-0.0.1.dev5/tests/test_config.py +364 -0
  42. evoscientist-0.0.1.dev5/tests/test_event_loop.py +200 -0
  43. evoscientist-0.0.1.dev5/tests/test_imports.py +36 -0
  44. evoscientist-0.0.1.dev5/tests/test_llm.py +242 -0
  45. evoscientist-0.0.1.dev5/tests/test_mcp_client.py +539 -0
  46. evoscientist-0.0.1.dev5/tests/test_onboard.py +688 -0
  47. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/tests/test_skills_manager.py +7 -11
  48. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/tests/test_stream_state.py +161 -11
  49. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/tests/test_stream_tracker.py +1 -1
  50. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/tests/test_stream_utils.py +28 -4
  51. evoscientist-0.0.1.dev5/tests/test_tools.py +107 -0
  52. evoscientist-0.0.1.dev3/EvoScientist/__init__.py +0 -24
  53. evoscientist-0.0.1.dev3/EvoScientist/cli.py +0 -427
  54. evoscientist-0.0.1.dev3/EvoScientist/skills/accelerate/SKILL.md +0 -332
  55. evoscientist-0.0.1.dev3/EvoScientist/skills/accelerate/references/custom-plugins.md +0 -453
  56. evoscientist-0.0.1.dev3/EvoScientist/skills/accelerate/references/megatron-integration.md +0 -489
  57. evoscientist-0.0.1.dev3/EvoScientist/skills/accelerate/references/performance.md +0 -525
  58. evoscientist-0.0.1.dev3/EvoScientist/skills/bitsandbytes/SKILL.md +0 -411
  59. evoscientist-0.0.1.dev3/EvoScientist/skills/bitsandbytes/references/memory-optimization.md +0 -521
  60. evoscientist-0.0.1.dev3/EvoScientist/skills/bitsandbytes/references/qlora-training.md +0 -521
  61. evoscientist-0.0.1.dev3/EvoScientist/skills/bitsandbytes/references/quantization-formats.md +0 -447
  62. evoscientist-0.0.1.dev3/EvoScientist/skills/find-skills/SKILL.md +0 -133
  63. evoscientist-0.0.1.dev3/EvoScientist/skills/find-skills/scripts/install_skill.py +0 -211
  64. evoscientist-0.0.1.dev3/EvoScientist/skills/flash-attention/SKILL.md +0 -367
  65. evoscientist-0.0.1.dev3/EvoScientist/skills/flash-attention/references/benchmarks.md +0 -215
  66. evoscientist-0.0.1.dev3/EvoScientist/skills/flash-attention/references/transformers-integration.md +0 -293
  67. evoscientist-0.0.1.dev3/EvoScientist/skills/llama-cpp/SKILL.md +0 -258
  68. evoscientist-0.0.1.dev3/EvoScientist/skills/llama-cpp/references/optimization.md +0 -89
  69. evoscientist-0.0.1.dev3/EvoScientist/skills/llama-cpp/references/quantization.md +0 -213
  70. evoscientist-0.0.1.dev3/EvoScientist/skills/llama-cpp/references/server.md +0 -125
  71. evoscientist-0.0.1.dev3/EvoScientist/skills/lm-evaluation-harness/SKILL.md +0 -490
  72. evoscientist-0.0.1.dev3/EvoScientist/skills/lm-evaluation-harness/references/api-evaluation.md +0 -490
  73. evoscientist-0.0.1.dev3/EvoScientist/skills/lm-evaluation-harness/references/benchmark-guide.md +0 -488
  74. evoscientist-0.0.1.dev3/EvoScientist/skills/lm-evaluation-harness/references/custom-tasks.md +0 -602
  75. evoscientist-0.0.1.dev3/EvoScientist/skills/lm-evaluation-harness/references/distributed-eval.md +0 -519
  76. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/SKILL.md +0 -937
  77. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/references/checklists.md +0 -361
  78. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/references/citation-workflow.md +0 -562
  79. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/references/reviewer-guidelines.md +0 -367
  80. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/references/sources.md +0 -159
  81. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/references/writing-guide.md +0 -476
  82. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/README.md +0 -251
  83. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/aaai2026/README.md +0 -534
  84. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/aaai2026/aaai2026-unified-supp.tex +0 -144
  85. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/aaai2026/aaai2026-unified-template.tex +0 -952
  86. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/aaai2026/aaai2026.bib +0 -111
  87. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/aaai2026/aaai2026.bst +0 -1493
  88. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/aaai2026/aaai2026.sty +0 -315
  89. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/acl/README.md +0 -50
  90. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/acl/acl.sty +0 -312
  91. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/acl/acl_latex.tex +0 -377
  92. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/acl/acl_lualatex.tex +0 -101
  93. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/acl/acl_natbib.bst +0 -1940
  94. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/acl/anthology.bib.txt +0 -26
  95. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/acl/custom.bib +0 -70
  96. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/acl/formatting.md +0 -326
  97. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/colm2025/README.md +0 -3
  98. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/colm2025/colm2025_conference.bib +0 -11
  99. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/colm2025/colm2025_conference.bst +0 -1440
  100. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/colm2025/colm2025_conference.pdf +0 -0
  101. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/colm2025/colm2025_conference.sty +0 -218
  102. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/colm2025/colm2025_conference.tex +0 -305
  103. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/colm2025/fancyhdr.sty +0 -485
  104. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/colm2025/math_commands.tex +0 -508
  105. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/colm2025/natbib.sty +0 -1246
  106. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/iclr2026/fancyhdr.sty +0 -485
  107. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/iclr2026/iclr2026_conference.bib +0 -24
  108. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/iclr2026/iclr2026_conference.bst +0 -1440
  109. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/iclr2026/iclr2026_conference.pdf +0 -0
  110. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/iclr2026/iclr2026_conference.sty +0 -246
  111. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/iclr2026/iclr2026_conference.tex +0 -414
  112. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/iclr2026/math_commands.tex +0 -508
  113. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/iclr2026/natbib.sty +0 -1246
  114. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/icml2026/algorithm.sty +0 -79
  115. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/icml2026/algorithmic.sty +0 -201
  116. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/icml2026/example_paper.bib +0 -75
  117. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/icml2026/example_paper.pdf +0 -0
  118. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/icml2026/example_paper.tex +0 -662
  119. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/icml2026/fancyhdr.sty +0 -864
  120. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/icml2026/icml2026.bst +0 -1443
  121. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/icml2026/icml2026.sty +0 -767
  122. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/icml2026/icml_numpapers.pdf +0 -0
  123. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/neurips2025/Makefile +0 -36
  124. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/neurips2025/extra_pkgs.tex +0 -53
  125. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/neurips2025/main.tex +0 -38
  126. evoscientist-0.0.1.dev3/EvoScientist/skills/ml-paper-writing/templates/neurips2025/neurips.sty +0 -382
  127. evoscientist-0.0.1.dev3/EvoScientist/skills/peft/SKILL.md +0 -431
  128. evoscientist-0.0.1.dev3/EvoScientist/skills/peft/references/advanced-usage.md +0 -514
  129. evoscientist-0.0.1.dev3/EvoScientist/skills/peft/references/troubleshooting.md +0 -480
  130. evoscientist-0.0.1.dev3/EvoScientist/skills/ray-data/SKILL.md +0 -326
  131. evoscientist-0.0.1.dev3/EvoScientist/skills/ray-data/references/integration.md +0 -82
  132. evoscientist-0.0.1.dev3/EvoScientist/skills/ray-data/references/transformations.md +0 -83
  133. evoscientist-0.0.1.dev3/EvoScientist/skills/skill-creator/LICENSE.txt +0 -202
  134. evoscientist-0.0.1.dev3/EvoScientist/tools.py +0 -199
  135. evoscientist-0.0.1.dev3/EvoScientist.egg-info/SOURCES.txt +0 -125
  136. evoscientist-0.0.1.dev3/tests/test_imports.py +0 -59
  137. evoscientist-0.0.1.dev3/tests/test_tools.py +0 -18
  138. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/EvoScientist/__main__.py +0 -0
  139. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/EvoScientist/memory.py +0 -0
  140. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/EvoScientist/prompts.py +0 -0
  141. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/EvoScientist/skills/skill-creator/references/output-patterns.md +0 -0
  142. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/EvoScientist/skills/skill-creator/references/workflows.md +0 -0
  143. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/EvoScientist/skills/skill-creator/scripts/init_skill.py +0 -0
  144. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/EvoScientist/skills/skill-creator/scripts/package_skill.py +0 -0
  145. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/EvoScientist/skills/skill-creator/scripts/quick_validate.py +0 -0
  146. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/EvoScientist/stream/emitter.py +0 -0
  147. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/EvoScientist/stream/events.py +0 -0
  148. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/EvoScientist/stream/state.py +0 -0
  149. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/EvoScientist/stream/tracker.py +0 -0
  150. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/EvoScientist/subagent.yaml +0 -0
  151. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/EvoScientist/utils.py +0 -0
  152. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/EvoScientist.egg-info/dependency_links.txt +0 -0
  153. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/EvoScientist.egg-info/entry_points.txt +0 -0
  154. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/EvoScientist.egg-info/top_level.txt +0 -0
  155. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/LICENSE +0 -0
  156. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/setup.cfg +0 -0
  157. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/tests/test_prompts.py +0 -0
  158. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev5}/tests/test_stream_emitter.py +0 -0
@@ -13,22 +13,24 @@ Usage:
13
13
  ...
14
14
  """
15
15
 
16
- import os
17
16
  from datetime import datetime
18
17
  from pathlib import Path
19
18
 
20
19
  from deepagents import create_deep_agent
21
20
  from deepagents.backends import FilesystemBackend, CompositeBackend
22
- from langchain.chat_models import init_chat_model
23
21
 
24
22
  from .backends import CustomSandboxBackend, MergedReadOnlyBackend
23
+ from .config import get_effective_config, apply_config_to_env
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
  )
@@ -37,16 +39,21 @@ from .paths import (
37
39
  # Configuration
38
40
  # =============================================================================
39
41
 
42
+ # Load configuration from file/env/defaults
43
+ _config = get_effective_config()
44
+ apply_config_to_env(_config)
45
+
40
46
  # Backend mode: "sandbox" (with execute) or "filesystem" (read/write only)
41
47
  BACKEND_MODE = "sandbox"
42
48
 
43
- # Research limits
44
- MAX_CONCURRENT = 3 # Max parallel sub-agents
45
- MAX_ITERATIONS = 3 # Max delegation rounds
49
+ # Research limits (from config)
50
+ MAX_CONCURRENT = _config.max_concurrent
51
+ MAX_ITERATIONS = _config.max_iterations
46
52
 
47
53
  # Workspace settings
48
54
  ensure_dirs()
49
55
  WORKSPACE_DIR = str(default_workspace_dir())
56
+ set_active_workspace(WORKSPACE_DIR)
50
57
  MEMORY_DIR = str(_MEMORY_DIR_PATH) # Shared across sessions (not per-session)
51
58
  SKILLS_DIR = str(Path(__file__).parent / "skills")
52
59
  USER_SKILLS_DIR = str(_USER_SKILLS_DIR_PATH)
@@ -65,11 +72,10 @@ SYSTEM_PROMPT = get_system_prompt(
65
72
  max_iterations=MAX_ITERATIONS,
66
73
  )
67
74
 
68
- # Initialize chat model
69
- chat_model = init_chat_model(
70
- model="claude-sonnet-4-5-20250929",
71
- model_provider="anthropic",
72
- # thinking={"type": "enabled", "budget_tokens": 2000},
75
+ # Initialize chat model using the LLM module (respects config settings)
76
+ chat_model = get_chat_model(
77
+ model=_config.model,
78
+ provider=_config.provider,
73
79
  )
74
80
 
75
81
  # Initialize workspace backend based on mode
@@ -109,33 +115,84 @@ backend = CompositeBackend(
109
115
  tool_registry = {
110
116
  "think_tool": think_tool,
111
117
  "tavily_search": tavily_search,
118
+ "view_image": view_image,
112
119
  }
113
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
+
114
183
  prompt_refs = {
115
184
  "RESEARCHER_INSTRUCTIONS": RESEARCHER_INSTRUCTIONS.format(date=current_date),
116
185
  }
117
186
 
118
- subagents = load_subagents(
119
- SUBAGENTS_CONFIG,
120
- tool_registry=tool_registry,
121
- prompt_refs=prompt_refs,
122
- )
187
+ base_middleware = [
188
+ create_memory_middleware(MEMORY_DIR, extraction_model=chat_model),
189
+ create_skills_middleware(backend),
190
+ ]
123
191
 
124
- # Shared kwargs for agent creation
125
- _AGENT_KWARGS = dict(
126
- name="EvoScientist",
127
- model=chat_model,
128
- tools=[think_tool, skill_manager],
129
- backend=backend,
130
- subagents=subagents,
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
- ],
135
- system_prompt=SYSTEM_PROMPT,
136
- )
137
-
138
- # 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)
139
196
  EvoScientist_agent = create_deep_agent(**_AGENT_KWARGS).with_config({"recursion_limit": 500})
140
197
 
141
198
 
@@ -150,6 +207,7 @@ def create_cli_agent(workspace_dir: str | None = None):
150
207
  from langgraph.checkpoint.memory import InMemorySaver # type: ignore[import-untyped]
151
208
 
152
209
  if workspace_dir:
210
+ set_active_workspace(workspace_dir)
153
211
  ws_backend = CustomSandboxBackend(
154
212
  root_dir=workspace_dir,
155
213
  virtual_mode=True,
@@ -171,17 +229,16 @@ def create_cli_agent(workspace_dir: str | None = None):
171
229
  "/memory/": mem_backend,
172
230
  },
173
231
  )
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
- ]
178
- kwargs = dict(
179
- _AGENT_KWARGS,
180
- backend=be,
181
- middleware=mw,
182
- )
183
232
  else:
184
- 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)
185
242
 
186
243
  return create_deep_agent(
187
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
+ ]