EvoScientist 0.0.1.dev3__tar.gz → 0.0.1.dev4__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 (134) hide show
  1. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/EvoScientist.py +13 -9
  2. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/__init__.py +19 -0
  3. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/cli.py +264 -23
  4. evoscientist-0.0.1.dev4/EvoScientist/config.py +274 -0
  5. evoscientist-0.0.1.dev4/EvoScientist/llm/__init__.py +21 -0
  6. evoscientist-0.0.1.dev4/EvoScientist/llm/models.py +99 -0
  7. evoscientist-0.0.1.dev4/EvoScientist/onboard.py +725 -0
  8. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/paths.py +2 -3
  9. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills_manager.py +1 -2
  10. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/stream/display.py +1 -1
  11. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/tools.py +11 -2
  12. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist.egg-info/PKG-INFO +68 -22
  13. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist.egg-info/SOURCES.txt +7 -0
  14. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist.egg-info/requires.txt +5 -1
  15. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/PKG-INFO +68 -22
  16. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/README.md +62 -20
  17. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/pyproject.toml +13 -3
  18. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/tests/test_backends.py +0 -3
  19. evoscientist-0.0.1.dev4/tests/test_config.py +345 -0
  20. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/tests/test_imports.py +4 -12
  21. evoscientist-0.0.1.dev4/tests/test_llm.py +226 -0
  22. evoscientist-0.0.1.dev4/tests/test_onboard.py +408 -0
  23. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/tests/test_skills_manager.py +0 -4
  24. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/tests/test_stream_tracker.py +1 -1
  25. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/__main__.py +0 -0
  26. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/backends.py +0 -0
  27. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/memory.py +0 -0
  28. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/middleware.py +0 -0
  29. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/prompts.py +0 -0
  30. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/accelerate/SKILL.md +0 -0
  31. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/accelerate/references/custom-plugins.md +0 -0
  32. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/accelerate/references/megatron-integration.md +0 -0
  33. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/accelerate/references/performance.md +0 -0
  34. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/bitsandbytes/SKILL.md +0 -0
  35. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/bitsandbytes/references/memory-optimization.md +0 -0
  36. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/bitsandbytes/references/qlora-training.md +0 -0
  37. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/bitsandbytes/references/quantization-formats.md +0 -0
  38. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/find-skills/SKILL.md +0 -0
  39. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/find-skills/scripts/install_skill.py +0 -0
  40. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/flash-attention/SKILL.md +0 -0
  41. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/flash-attention/references/benchmarks.md +0 -0
  42. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/flash-attention/references/transformers-integration.md +0 -0
  43. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/llama-cpp/SKILL.md +0 -0
  44. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/llama-cpp/references/optimization.md +0 -0
  45. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/llama-cpp/references/quantization.md +0 -0
  46. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/llama-cpp/references/server.md +0 -0
  47. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/lm-evaluation-harness/SKILL.md +0 -0
  48. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/lm-evaluation-harness/references/api-evaluation.md +0 -0
  49. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/lm-evaluation-harness/references/benchmark-guide.md +0 -0
  50. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/lm-evaluation-harness/references/custom-tasks.md +0 -0
  51. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/lm-evaluation-harness/references/distributed-eval.md +0 -0
  52. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/SKILL.md +0 -0
  53. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/references/checklists.md +0 -0
  54. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/references/citation-workflow.md +0 -0
  55. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/references/reviewer-guidelines.md +0 -0
  56. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/references/sources.md +0 -0
  57. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/references/writing-guide.md +0 -0
  58. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/README.md +0 -0
  59. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/aaai2026/README.md +0 -0
  60. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/aaai2026/aaai2026-unified-supp.tex +0 -0
  61. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/aaai2026/aaai2026-unified-template.tex +0 -0
  62. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/aaai2026/aaai2026.bib +0 -0
  63. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/aaai2026/aaai2026.bst +0 -0
  64. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/aaai2026/aaai2026.sty +0 -0
  65. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/acl/README.md +0 -0
  66. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/acl/acl.sty +0 -0
  67. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/acl/acl_latex.tex +0 -0
  68. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/acl/acl_lualatex.tex +0 -0
  69. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/acl/acl_natbib.bst +0 -0
  70. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/acl/anthology.bib.txt +0 -0
  71. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/acl/custom.bib +0 -0
  72. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/acl/formatting.md +0 -0
  73. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/colm2025/README.md +0 -0
  74. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/colm2025/colm2025_conference.bib +0 -0
  75. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/colm2025/colm2025_conference.bst +0 -0
  76. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/colm2025/colm2025_conference.pdf +0 -0
  77. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/colm2025/colm2025_conference.sty +0 -0
  78. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/colm2025/colm2025_conference.tex +0 -0
  79. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/colm2025/fancyhdr.sty +0 -0
  80. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/colm2025/math_commands.tex +0 -0
  81. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/colm2025/natbib.sty +0 -0
  82. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/iclr2026/fancyhdr.sty +0 -0
  83. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/iclr2026/iclr2026_conference.bib +0 -0
  84. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/iclr2026/iclr2026_conference.bst +0 -0
  85. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/iclr2026/iclr2026_conference.pdf +0 -0
  86. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/iclr2026/iclr2026_conference.sty +0 -0
  87. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/iclr2026/iclr2026_conference.tex +0 -0
  88. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/iclr2026/math_commands.tex +0 -0
  89. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/iclr2026/natbib.sty +0 -0
  90. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/icml2026/algorithm.sty +0 -0
  91. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/icml2026/algorithmic.sty +0 -0
  92. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/icml2026/example_paper.bib +0 -0
  93. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/icml2026/example_paper.pdf +0 -0
  94. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/icml2026/example_paper.tex +0 -0
  95. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/icml2026/fancyhdr.sty +0 -0
  96. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/icml2026/icml2026.bst +0 -0
  97. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/icml2026/icml2026.sty +0 -0
  98. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/icml2026/icml_numpapers.pdf +0 -0
  99. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/neurips2025/Makefile +0 -0
  100. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/neurips2025/extra_pkgs.tex +0 -0
  101. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/neurips2025/main.tex +0 -0
  102. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ml-paper-writing/templates/neurips2025/neurips.sty +0 -0
  103. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/peft/SKILL.md +0 -0
  104. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/peft/references/advanced-usage.md +0 -0
  105. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/peft/references/troubleshooting.md +0 -0
  106. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ray-data/SKILL.md +0 -0
  107. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ray-data/references/integration.md +0 -0
  108. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/ray-data/references/transformations.md +0 -0
  109. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/skill-creator/LICENSE.txt +0 -0
  110. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/skill-creator/SKILL.md +0 -0
  111. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/skill-creator/references/output-patterns.md +0 -0
  112. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/skill-creator/references/workflows.md +0 -0
  113. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/skill-creator/scripts/init_skill.py +0 -0
  114. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/skill-creator/scripts/package_skill.py +0 -0
  115. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/skills/skill-creator/scripts/quick_validate.py +0 -0
  116. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/stream/__init__.py +0 -0
  117. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/stream/emitter.py +0 -0
  118. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/stream/events.py +0 -0
  119. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/stream/formatter.py +0 -0
  120. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/stream/state.py +0 -0
  121. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/stream/tracker.py +0 -0
  122. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/stream/utils.py +0 -0
  123. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/subagent.yaml +0 -0
  124. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist/utils.py +0 -0
  125. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist.egg-info/dependency_links.txt +0 -0
  126. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist.egg-info/entry_points.txt +0 -0
  127. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/EvoScientist.egg-info/top_level.txt +0 -0
  128. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/LICENSE +0 -0
  129. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/setup.cfg +0 -0
  130. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/tests/test_prompts.py +0 -0
  131. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/tests/test_stream_emitter.py +0 -0
  132. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/tests/test_stream_state.py +0 -0
  133. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/tests/test_stream_utils.py +0 -0
  134. {evoscientist-0.0.1.dev3 → evoscientist-0.0.1.dev4}/tests/test_tools.py +0 -0
@@ -13,15 +13,15 @@ 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
25
  from .middleware import create_skills_middleware, create_memory_middleware
26
26
  from .prompts import RESEARCHER_INSTRUCTIONS, get_system_prompt
27
27
  from .utils import load_subagents
@@ -37,12 +37,16 @@ from .paths import (
37
37
  # Configuration
38
38
  # =============================================================================
39
39
 
40
+ # Load configuration from file/env/defaults
41
+ _config = get_effective_config()
42
+ apply_config_to_env(_config)
43
+
40
44
  # Backend mode: "sandbox" (with execute) or "filesystem" (read/write only)
41
45
  BACKEND_MODE = "sandbox"
42
46
 
43
- # Research limits
44
- MAX_CONCURRENT = 3 # Max parallel sub-agents
45
- MAX_ITERATIONS = 3 # Max delegation rounds
47
+ # Research limits (from config)
48
+ MAX_CONCURRENT = _config.max_concurrent
49
+ MAX_ITERATIONS = _config.max_iterations
46
50
 
47
51
  # Workspace settings
48
52
  ensure_dirs()
@@ -65,10 +69,10 @@ SYSTEM_PROMPT = get_system_prompt(
65
69
  max_iterations=MAX_ITERATIONS,
66
70
  )
67
71
 
68
- # Initialize chat model
69
- chat_model = init_chat_model(
70
- model="claude-sonnet-4-5-20250929",
71
- model_provider="anthropic",
72
+ # Initialize chat model using the LLM module (respects config settings)
73
+ chat_model = get_chat_model(
74
+ model=_config.model,
75
+ provider=_config.provider,
72
76
  # thinking={"type": "enabled", "budget_tokens": 2000},
73
77
  )
74
78
 
@@ -1,6 +1,14 @@
1
1
  """EvoScientist Agent — AI-powered research & code execution."""
2
2
 
3
3
  from .backends import CustomSandboxBackend, ReadOnlyFilesystemBackend
4
+ from .config import (
5
+ EvoScientistConfig,
6
+ load_config,
7
+ save_config,
8
+ get_effective_config,
9
+ get_config_path,
10
+ )
11
+ from .llm import get_chat_model, MODELS, list_models, DEFAULT_MODEL
4
12
  from .middleware import create_skills_middleware
5
13
  from .prompts import get_system_prompt, RESEARCHER_INSTRUCTIONS
6
14
  from .tools import tavily_search, think_tool
@@ -13,6 +21,17 @@ __all__ = [
13
21
  # Backends
14
22
  "CustomSandboxBackend",
15
23
  "ReadOnlyFilesystemBackend",
24
+ # Configuration
25
+ "EvoScientistConfig",
26
+ "load_config",
27
+ "save_config",
28
+ "get_effective_config",
29
+ "get_config_path",
30
+ # LLM
31
+ "get_chat_model",
32
+ "MODELS",
33
+ "list_models",
34
+ "DEFAULT_MODEL",
16
35
  # Middleware
17
36
  "create_skills_middleware",
18
37
  # Prompts
@@ -10,6 +10,7 @@ Features:
10
10
  - Response panel (green) - shows final response
11
11
  - Thread ID support for multi-turn conversations
12
12
  - Interactive mode with prompt_toolkit
13
+ - Configuration management (onboard, config commands)
13
14
  """
14
15
 
15
16
  import logging
@@ -25,11 +26,12 @@ from prompt_toolkit.history import FileHistory # type: ignore[import-untyped]
25
26
  from prompt_toolkit.auto_suggest import AutoSuggestFromHistory # type: ignore[import-untyped]
26
27
  from prompt_toolkit.formatted_text import HTML # type: ignore[import-untyped]
27
28
  from rich.text import Text # type: ignore[import-untyped]
29
+ from rich.table import Table # type: ignore[import-untyped]
28
30
 
29
31
  # Backward-compat re-exports (tests import these from EvoScientist.cli)
30
32
  from .stream.state import SubAgentState, StreamState, _parse_todo_items, _build_todo_stats # noqa: F401
31
33
  from .stream.display import console, _run_streaming
32
- from .paths import ensure_dirs, new_run_dir
34
+ from .paths import ensure_dirs, new_run_dir, default_workspace_dir
33
35
 
34
36
 
35
37
  def _shorten_path(path: str) -> str:
@@ -69,8 +71,11 @@ def print_banner(
69
71
  thread_id: str,
70
72
  workspace_dir: str | None = None,
71
73
  memory_dir: str | None = None,
74
+ mode: str | None = None,
75
+ model: str | None = None,
76
+ provider: str | None = None,
72
77
  ):
73
- """Print welcome banner with ASCII art logo, thread ID, and workspace path."""
78
+ """Print welcome banner with ASCII art logo, thread ID, workspace path, and mode."""
74
79
  for line, color in zip(EVOSCIENTIST_ASCII_LINES, _GRADIENT_COLORS):
75
80
  console.print(Text(line, style=f"{color} bold"))
76
81
  info = Text()
@@ -83,6 +88,20 @@ def print_banner(
83
88
  trimmed = memory_dir.rstrip("/").rstrip("\\")
84
89
  info.append("\n Memory dir: ", style="dim")
85
90
  info.append(_shorten_path(trimmed), style="cyan")
91
+ if model or provider or mode:
92
+ info.append("\n ", style="dim")
93
+ parts = []
94
+ if model:
95
+ parts.append(("Model: ", model))
96
+ if provider:
97
+ parts.append(("Provider: ", provider))
98
+ if mode:
99
+ parts.append(("Mode: ", mode))
100
+ for i, (label, value) in enumerate(parts):
101
+ if i > 0:
102
+ info.append(" ", style="dim")
103
+ info.append(label, style="dim")
104
+ info.append(value, style="magenta")
86
105
  info.append("\n Commands: ", style="dim")
87
106
  info.append("/exit", style="bold")
88
107
  info.append(", ", style="dim")
@@ -113,7 +132,7 @@ def _cmd_list_skills() -> None:
113
132
 
114
133
  if not skills:
115
134
  console.print("[dim]No user skills installed.[/dim]")
116
- console.print(f"[dim]Install with:[/dim] /install-skill <path-or-url>")
135
+ console.print("[dim]Install with:[/dim] /install-skill <path-or-url>")
117
136
  console.print(f"[dim]Skills directory:[/dim] [cyan]{_shorten_path(str(USER_SKILLS_DIR))}[/cyan]")
118
137
  console.print()
119
138
  return
@@ -182,6 +201,9 @@ def cmd_interactive(
182
201
  show_thinking: bool = True,
183
202
  workspace_dir: str | None = None,
184
203
  workspace_fixed: bool = False,
204
+ mode: str | None = None,
205
+ model: str | None = None,
206
+ provider: str | None = None,
185
207
  ) -> None:
186
208
  """Interactive conversation mode with streaming output.
187
209
 
@@ -190,11 +212,14 @@ def cmd_interactive(
190
212
  show_thinking: Whether to display thinking panels
191
213
  workspace_dir: Per-session workspace directory path
192
214
  workspace_fixed: If True, /new keeps the same workspace directory
215
+ mode: Workspace mode ('daemon' or 'run'), displayed in banner
216
+ model: Model name to display in banner
217
+ provider: LLM provider name to display in banner
193
218
  """
194
219
  thread_id = str(uuid.uuid4())
195
220
  from .EvoScientist import MEMORY_DIR
196
221
  memory_dir = MEMORY_DIR
197
- print_banner(thread_id, workspace_dir, memory_dir)
222
+ print_banner(thread_id, workspace_dir, memory_dir, mode, model, provider)
198
223
 
199
224
  history_file = str(os.path.expanduser("~/.EvoScientist_history"))
200
225
  session = PromptSession(
@@ -272,7 +297,13 @@ def cmd_interactive(
272
297
  console.print("\n[dim]Goodbye![/dim]")
273
298
  break
274
299
  except Exception as e:
275
- console.print(f"[red]Error: {e}[/red]")
300
+ error_msg = str(e)
301
+ if "authentication" in error_msg.lower() or "api_key" in error_msg.lower():
302
+ console.print("[red]Error: API key not configured.[/red]")
303
+ console.print("[dim]Run [bold]EvoSci onboard[/bold] to set up your API key.[/dim]")
304
+ break
305
+ else:
306
+ console.print(f"[red]Error: {e}[/red]")
276
307
 
277
308
 
278
309
  def cmd_run(agent: Any, prompt: str, thread_id: str | None = None, show_thinking: bool = True, workspace_dir: str | None = None) -> None:
@@ -300,8 +331,14 @@ def cmd_run(agent: Any, prompt: str, thread_id: str | None = None, show_thinking
300
331
  try:
301
332
  _run_streaming(agent, prompt, thread_id, show_thinking, interactive=False)
302
333
  except Exception as e:
303
- console.print(f"[red]Error: {e}[/red]")
304
- raise
334
+ error_msg = str(e)
335
+ if "authentication" in error_msg.lower() or "api_key" in error_msg.lower():
336
+ console.print("[red]Error: API key not configured.[/red]")
337
+ console.print("[dim]Run [bold]EvoSci onboard[/bold] to set up your API key.[/dim]")
338
+ raise typer.Exit(1)
339
+ else:
340
+ console.print(f"[red]Error: {e}[/red]")
341
+ raise
305
342
 
306
343
 
307
344
  # =============================================================================
@@ -332,29 +369,200 @@ def _load_agent(workspace_dir: str | None = None):
332
369
 
333
370
  app = typer.Typer(no_args_is_help=False, add_completion=False)
334
371
 
372
+ # Config subcommand group
373
+ config_app = typer.Typer(help="Configuration management commands", invoke_without_command=True)
374
+ app.add_typer(config_app, name="config")
375
+
376
+
377
+ # =============================================================================
378
+ # Onboard command
379
+ # =============================================================================
380
+
381
+ @app.command()
382
+ def onboard(
383
+ skip_validation: bool = typer.Option(
384
+ False,
385
+ "--skip-validation",
386
+ help="Skip API key validation during setup"
387
+ ),
388
+ ):
389
+ """Interactive setup wizard for EvoScientist.
390
+
391
+ Guides you through configuring API keys, model selection,
392
+ workspace settings, and agent parameters.
393
+ """
394
+ from .onboard import run_onboard
395
+ run_onboard(skip_validation=skip_validation)
396
+
397
+
398
+ # =============================================================================
399
+ # Config commands
400
+ # =============================================================================
401
+
402
+ @config_app.callback(invoke_without_command=True)
403
+ def config_callback(ctx: typer.Context):
404
+ """Configuration management commands."""
405
+ if ctx.invoked_subcommand is None:
406
+ config_list()
407
+
408
+
409
+ @config_app.command("list")
410
+ def config_list():
411
+ """List all configuration values."""
412
+ from .config import list_config, get_config_path
413
+
414
+ config_data = list_config()
415
+
416
+ table = Table(title="EvoScientist Configuration", show_header=True)
417
+ table.add_column("Setting", style="cyan")
418
+ table.add_column("Value")
419
+
420
+ # Mask API keys
421
+ def format_value(key: str, value: Any) -> str:
422
+ if "api_key" in key and value:
423
+ return "***" + str(value)[-4:] if len(str(value)) > 4 else "***"
424
+ if value == "":
425
+ return "[dim](not set)[/dim]"
426
+ return str(value)
427
+
428
+ for key, value in config_data.items():
429
+ table.add_row(key, format_value(key, value))
430
+
431
+ console.print(table)
432
+ console.print(f"\n[dim]Config file: {get_config_path()}[/dim]")
433
+
434
+
435
+ @config_app.command("get")
436
+ def config_get(key: str = typer.Argument(..., help="Configuration key to get")):
437
+ """Get a single configuration value."""
438
+ from .config import get_config_value
439
+
440
+ value = get_config_value(key)
441
+ if value is None:
442
+ console.print(f"[red]Unknown key: {key}[/red]")
443
+ raise typer.Exit(1)
444
+
445
+ # Mask API keys
446
+ if "api_key" in key and value:
447
+ display_value = "***" + str(value)[-4:] if len(str(value)) > 4 else "***"
448
+ elif value == "":
449
+ display_value = "(not set)"
450
+ else:
451
+ display_value = str(value)
452
+
453
+ console.print(f"[cyan]{key}[/cyan]: {display_value}")
454
+
455
+
456
+ @config_app.command("set")
457
+ def config_set(
458
+ key: str = typer.Argument(..., help="Configuration key to set"),
459
+ value: str = typer.Argument(..., help="New value"),
460
+ ):
461
+ """Set a single configuration value."""
462
+ from .config import set_config_value
463
+
464
+ if set_config_value(key, value):
465
+ console.print(f"[green]Set {key}[/green]")
466
+ else:
467
+ console.print(f"[red]Invalid key: {key}[/red]")
468
+ raise typer.Exit(1)
469
+
470
+
471
+ @config_app.command("reset")
472
+ def config_reset(
473
+ yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt"),
474
+ ):
475
+ """Reset configuration to defaults."""
476
+ from .config import reset_config, get_config_path
477
+
478
+ config_path = get_config_path()
479
+
480
+ if not config_path.exists():
481
+ console.print("[yellow]No config file to reset.[/yellow]")
482
+ return
483
+
484
+ if not yes:
485
+ confirm = typer.confirm("Reset configuration to defaults?")
486
+ if not confirm:
487
+ console.print("[dim]Cancelled.[/dim]")
488
+ return
489
+
490
+ reset_config()
491
+ console.print("[green]Configuration reset to defaults.[/green]")
492
+
493
+
494
+ @config_app.command("path")
495
+ def config_path():
496
+ """Show the configuration file path."""
497
+ from .config import get_config_path
498
+
499
+ path = get_config_path()
500
+ exists = path.exists()
501
+ status = "[green]exists[/green]" if exists else "[dim]not created yet[/dim]"
502
+ console.print(f"{path} ({status})")
503
+
504
+
505
+ # =============================================================================
506
+ # Main callback (default behavior)
507
+ # =============================================================================
335
508
 
336
509
  @app.callback(invoke_without_command=True)
337
510
  def _main_callback(
338
511
  ctx: typer.Context,
339
- prompt: Optional[str] = typer.Argument(None, help="Query to execute (single-shot mode)"),
340
- interactive: bool = typer.Option(False, "-i", "--interactive", help="Interactive conversation mode"),
512
+ prompt: Optional[str] = typer.Option(None, "-p", "--prompt", help="Query to execute (single-shot mode)"),
341
513
  thread_id: Optional[str] = typer.Option(None, "--thread-id", help="Thread ID for conversation persistence"),
342
514
  no_thinking: bool = typer.Option(False, "--no-thinking", help="Disable thinking display"),
343
515
  workdir: Optional[str] = typer.Option(None, "--workdir", help="Override workspace directory for this session"),
344
516
  use_cwd: bool = typer.Option(False, "--use-cwd", help="Use current working directory as workspace"),
517
+ mode: Optional[str] = typer.Option(
518
+ None,
519
+ "--mode",
520
+ help="Workspace mode: 'daemon' (persistent, default) or 'run' (isolated per-session)"
521
+ ),
345
522
  ):
346
523
  """EvoScientist Agent - AI-powered research & code execution CLI."""
347
- from dotenv import load_dotenv # type: ignore[import-untyped]
348
- load_dotenv(override=True)
524
+ # If a subcommand was invoked, don't run the default behavior
525
+ if ctx.invoked_subcommand is not None:
526
+ return
527
+
528
+ from dotenv import load_dotenv, find_dotenv # type: ignore[import-untyped]
529
+ # find_dotenv() traverses up the directory tree to locate .env
530
+ load_dotenv(find_dotenv(), override=True)
349
531
 
350
- show_thinking = not no_thinking
532
+ # Load and apply configuration
533
+ from .config import get_effective_config, apply_config_to_env
351
534
 
535
+ # Build CLI overrides dict
536
+ cli_overrides = {}
537
+ if mode:
538
+ cli_overrides["default_mode"] = mode
539
+ if workdir:
540
+ cli_overrides["default_workdir"] = workdir
541
+ if no_thinking:
542
+ cli_overrides["show_thinking"] = False
543
+
544
+ config = get_effective_config(cli_overrides)
545
+ apply_config_to_env(config)
546
+
547
+ show_thinking = config.show_thinking if not no_thinking else False
548
+
549
+ # Validate mutually exclusive options
352
550
  if workdir and use_cwd:
353
551
  raise typer.BadParameter("Use either --workdir or --use-cwd, not both.")
354
552
 
553
+ if mode and (workdir or use_cwd):
554
+ raise typer.BadParameter("--mode cannot be combined with --workdir or --use-cwd")
555
+
556
+ if mode and mode not in ("run", "daemon"):
557
+ raise typer.BadParameter("--mode must be 'run' or 'daemon'")
558
+
355
559
  ensure_dirs()
356
560
 
561
+ # Resolve effective mode from config (CLI mode already applied via overrides)
562
+ effective_mode: str | None = None # None means explicit --workdir/--use-cwd was used
563
+
357
564
  # Resolve workspace directory for this session
565
+ # Priority: --use-cwd > --workdir > --mode (explicit) > default_workdir > default_mode
358
566
  if use_cwd:
359
567
  workspace_dir = os.getcwd()
360
568
  workspace_fixed = True
@@ -362,30 +570,60 @@ def _main_callback(
362
570
  workspace_dir = os.path.abspath(os.path.expanduser(workdir))
363
571
  os.makedirs(workspace_dir, exist_ok=True)
364
572
  workspace_fixed = True
573
+ elif mode:
574
+ # Explicit --mode overrides default_workdir
575
+ effective_mode = mode
576
+ workspace_root = config.default_workdir or str(default_workspace_dir())
577
+ workspace_root = os.path.abspath(os.path.expanduser(workspace_root))
578
+ if effective_mode == "run":
579
+ session_id = datetime.now().strftime("%Y%m%d_%H%M%S")
580
+ workspace_dir = os.path.join(workspace_root, "runs", session_id)
581
+ os.makedirs(workspace_dir, exist_ok=True)
582
+ workspace_fixed = False
583
+ else: # daemon
584
+ workspace_dir = workspace_root
585
+ os.makedirs(workspace_dir, exist_ok=True)
586
+ workspace_fixed = True
587
+ elif config.default_workdir:
588
+ # Use configured default workdir with configured mode
589
+ workspace_root = os.path.abspath(os.path.expanduser(config.default_workdir))
590
+ effective_mode = config.default_mode
591
+ if effective_mode == "run":
592
+ session_id = datetime.now().strftime("%Y%m%d_%H%M%S")
593
+ workspace_dir = os.path.join(workspace_root, "runs", session_id)
594
+ os.makedirs(workspace_dir, exist_ok=True)
595
+ workspace_fixed = False
596
+ else: # daemon
597
+ workspace_dir = workspace_root
598
+ os.makedirs(workspace_dir, exist_ok=True)
599
+ workspace_fixed = True
365
600
  else:
366
- workspace_dir = _create_session_workspace()
367
- workspace_fixed = False
601
+ effective_mode = config.default_mode
602
+ if effective_mode == "run":
603
+ workspace_dir = _create_session_workspace()
604
+ workspace_fixed = False
605
+ else: # daemon mode (default)
606
+ workspace_dir = str(default_workspace_dir())
607
+ os.makedirs(workspace_dir, exist_ok=True)
608
+ workspace_fixed = True
368
609
 
369
610
  # Load agent with session workspace
370
611
  console.print("[dim]Loading agent...[/dim]")
371
612
  agent = _load_agent(workspace_dir=workspace_dir)
372
613
 
373
- if interactive:
374
- cmd_interactive(
375
- agent,
376
- show_thinking=show_thinking,
377
- workspace_dir=workspace_dir,
378
- workspace_fixed=workspace_fixed,
379
- )
380
- elif prompt:
614
+ if prompt:
615
+ # Single-shot mode: execute query and exit
381
616
  cmd_run(agent, prompt, thread_id=thread_id, show_thinking=show_thinking, workspace_dir=workspace_dir)
382
617
  else:
383
- # Default: interactive mode
618
+ # Interactive mode (default)
384
619
  cmd_interactive(
385
620
  agent,
386
621
  show_thinking=show_thinking,
387
622
  workspace_dir=workspace_dir,
388
623
  workspace_fixed=workspace_fixed,
624
+ mode=effective_mode,
625
+ model=config.model,
626
+ provider=config.provider,
389
627
  )
390
628
 
391
629
 
@@ -419,6 +657,9 @@ def _configure_logging():
419
657
 
420
658
  def main():
421
659
  """CLI entry point — delegates to the Typer app."""
660
+ import warnings
661
+ warnings.filterwarnings("ignore", message=".*not known to support tools.*")
662
+ warnings.filterwarnings("ignore", message=".*type is unknown and inference may fail.*")
422
663
  _configure_logging()
423
664
  app()
424
665