JFrazUCSF commited on
Commit
ea8c770
·
verified ·
1 Parent(s): 68f990f

Upload 176 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .dockerignore +73 -0
  2. .gitattributes +7 -0
  3. .github/workflows/tests.yml +30 -0
  4. .gitignore +99 -0
  5. .pre-commit-config.yaml +10 -0
  6. .readthedocs.yml +31 -0
  7. CLAUDE.md +88 -0
  8. LICENSE +536 -0
  9. MANIFEST.in +22 -0
  10. README.md +192 -0
  11. cmbagent/__init__.py +75 -0
  12. cmbagent/agents/admin/__init__.py +0 -0
  13. cmbagent/agents/admin/admin.yaml +11 -0
  14. cmbagent/agents/coding/engineer/__init__.py +0 -0
  15. cmbagent/agents/coding/engineer/engineer.yaml +206 -0
  16. cmbagent/agents/coding/engineer/massgen_engineer.py +499 -0
  17. cmbagent/agents/coding/engineer_nest/__init__.py +0 -0
  18. cmbagent/agents/coding/engineer_nest/engineer_nest.yaml +12 -0
  19. cmbagent/agents/coding/engineer_response_formatter/__init__.py +0 -0
  20. cmbagent/agents/coding/engineer_response_formatter/engineer_response_formatter.py +325 -0
  21. cmbagent/agents/coding/engineer_response_formatter/engineer_response_formatter.yaml +86 -0
  22. cmbagent/agents/coding/executor/__init__.py +0 -0
  23. cmbagent/agents/coding/executor/executor.yaml +22 -0
  24. cmbagent/agents/coding/executor_bash/__init__.py +0 -0
  25. cmbagent/agents/coding/executor_bash/executor_bash.yaml +22 -0
  26. cmbagent/agents/coding/executor_response_formatter/__init__.py +0 -0
  27. cmbagent/agents/coding/executor_response_formatter/executor_response_formatter.py +65 -0
  28. cmbagent/agents/coding/executor_response_formatter/executor_response_formatter.yaml +23 -0
  29. cmbagent/agents/control/control_starter/__init__.py +0 -0
  30. cmbagent/agents/control/control_starter/control_starter.yaml +40 -0
  31. cmbagent/agents/control/controller/__init__.py +0 -0
  32. cmbagent/agents/control/controller/controller.yaml +59 -0
  33. cmbagent/agents/control/terminator/__init__.py +0 -0
  34. cmbagent/agents/control/terminator/terminator.yaml +22 -0
  35. cmbagent/agents/hypothesis/idea_hater/__init__.py +0 -0
  36. cmbagent/agents/hypothesis/idea_hater/idea_hater.yaml +65 -0
  37. cmbagent/agents/hypothesis/idea_hater_response_formatter/__init__.py +0 -0
  38. cmbagent/agents/hypothesis/idea_hater_response_formatter/idea_hater_response_formatter.py +51 -0
  39. cmbagent/agents/hypothesis/idea_hater_response_formatter/idea_hater_response_formatter.yaml +9 -0
  40. cmbagent/agents/hypothesis/idea_maker/__init__.py +0 -0
  41. cmbagent/agents/hypothesis/idea_maker/idea_maker.yaml +60 -0
  42. cmbagent/agents/hypothesis/idea_maker_response_formatter/__init__.py +0 -0
  43. cmbagent/agents/hypothesis/idea_maker_response_formatter/idea_maker_response_formatter.py +51 -0
  44. cmbagent/agents/hypothesis/idea_maker_response_formatter/idea_maker_response_formatter.yaml +11 -0
  45. cmbagent/agents/hypothesis/idea_saver/__init__.py +0 -0
  46. cmbagent/agents/hypothesis/idea_saver/idea_saver.py +138 -0
  47. cmbagent/agents/hypothesis/idea_saver/idea_saver.yaml +11 -0
  48. cmbagent/agents/installer/__init__.py +0 -0
  49. cmbagent/agents/installer/installer.py +38 -0
  50. cmbagent/agents/installer/installer.yaml +13 -0
.dockerignore ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dependencies
2
+ node_modules/
3
+ cmbagent-ui/node_modules/
4
+ cmbagent_env/
5
+ __pycache__/
6
+ *.pyc
7
+ *.pyo
8
+ *.pyd
9
+ .Python
10
+ build/
11
+ develop-eggs/
12
+ dist/
13
+ downloads/
14
+ eggs/
15
+ .eggs/
16
+ lib/
17
+ lib64/
18
+ parts/
19
+ sdist/
20
+ var/
21
+ wheels/
22
+ *.egg-info/
23
+ .installed.cfg
24
+ *.egg
25
+
26
+ # IDE and editor files
27
+ .vscode/
28
+ .idea/
29
+ *.swp
30
+ *.swo
31
+ *~
32
+
33
+ # OS files
34
+ .DS_Store
35
+ .DS_Store?
36
+ ._*
37
+ .Spotlight-V100
38
+ .Trashes
39
+ ehthumbs.db
40
+ Thumbs.db
41
+
42
+ # Git
43
+ .git/
44
+ .gitignore
45
+
46
+ # Environment files
47
+ .env
48
+ .env.local
49
+ .env.development.local
50
+ .env.test.local
51
+ .env.production.local
52
+
53
+ # Logs
54
+ npm-debug.log*
55
+ yarn-debug.log*
56
+ yarn-error.log*
57
+ *.log
58
+
59
+ # Next.js build output
60
+ .next/
61
+ out/
62
+
63
+ # Misc
64
+ *.tgz
65
+ *.tar.gz
66
+
67
+ # Testing
68
+ coverage/
69
+ .nyc_output
70
+
71
+ # Temporary files
72
+ tmp/
73
+ temp/
.gitattributes ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ cmbagent/utils/keywords/aas_kwd_to_url.pkl filter=lfs diff=lfs merge=lfs -text
2
+ docs/examples/report_helper/matter_power_spectrum_emulation_report_experiments_discussion_conclusion.pdf filter=lfs diff=lfs merge=lfs -text
3
+ docs/examples/report_helper/matter_power_spectrum_emulation_report_intro_and_background.pdf filter=lfs diff=lfs merge=lfs -text
4
+ docs/examples/report_helper/matter_power_spectrum_emulation_report_methodology_and_implementation.pdf filter=lfs diff=lfs merge=lfs -text
5
+ docs/examples/report_helper/matter_power_spectrum_emulation_report_outline.pdf filter=lfs diff=lfs merge=lfs -text
6
+ docs/notebooks/legacy/Beta2/cmbagent_beta2_demo_economics.ipynb filter=lfs diff=lfs merge=lfs -text
7
+ images/logo.png filter=lfs diff=lfs merge=lfs -text
.github/workflows/tests.yml ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: tests
2
+
3
+ on:
4
+ workflow_dispatch:
5
+
6
+ jobs:
7
+ pytest:
8
+ runs-on: ubuntu-latest
9
+ steps:
10
+ - name: Checkout cmbagent_v2 branch
11
+ uses: actions/checkout@v4
12
+ with:
13
+ ref: cmbagent_v2
14
+
15
+ - name: Set up Python
16
+ uses: actions/setup-python@v5
17
+ with:
18
+ python-version: "3.12"
19
+
20
+ - name: Install
21
+ run: |
22
+ python -m pip install -U pip
23
+ pip install -e .
24
+ pip install pytest
25
+
26
+ - name: Run tests
27
+ env:
28
+ OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
29
+ run: |
30
+ pytest -s
.gitignore ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mac
2
+ .DS_Store
3
+ .cache
4
+
5
+ .venv/
6
+
7
+ # Byte-compiled / optimized / DLL files
8
+ __pycache__/
9
+ *.py[cod]
10
+ *$py.class
11
+
12
+ .ipynb_checkpoints*
13
+
14
+ # C extensions
15
+ *.so
16
+
17
+ # Distribution / packaging
18
+ .Python
19
+ build/
20
+ develop-eggs/
21
+ dist/
22
+ downloads/
23
+ eggs/
24
+ .eggs/
25
+ lib/
26
+ lib64/
27
+ parts/
28
+ sdist/
29
+ var/
30
+ wheels/
31
+ *.egg-info/
32
+ .installed.cfg
33
+ *.egg
34
+ MANIFEST
35
+ evals/logs
36
+ logs
37
+ data
38
+ # PyInstaller
39
+ # Usually these files are written by a python script from a template
40
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
41
+ *.manifest
42
+ *.spec
43
+
44
+ output/*
45
+ cmbagent/data/classy_sz/*.yaml
46
+ cmbagent/data/classy_sz/*.txt
47
+
48
+
49
+ cmbagent/data/classy/*.yaml
50
+ cmbagent/data/classy/*.txt
51
+
52
+ cmbagent/data/camb/*.yaml
53
+ cmbagent/data/camb/*.txt
54
+
55
+ evals/
56
+ logs/
57
+
58
+ .env
59
+ *bor11031990*
60
+
61
+
62
+ docs/_*
63
+ output/cmb_tt_power_spectrum.png
64
+ docs/notebooks/bbolliet_local
65
+ docs/cmbagent_*_arxiv
66
+ docs/cmbagent_*zip
67
+ docs/videos/*
68
+ output_bkp
69
+ docs/notebooks/output
70
+ docs/notebooks/cmbagent_output
71
+
72
+ project_data
73
+ docs/notebooks/*
74
+ docs/notebooks/cmbagent_beta2_demo_1shot.ipynb
75
+ docs/notebooks/astropilot_data/.DS_Store
76
+ docs/notebooks/astropilot_data/plots/.DS_Store
77
+ gui_expt.py
78
+ tests/Project*
79
+ project_dir
80
+
81
+ # Frontend / Node.js
82
+ cmbagent-ui/node_modules/
83
+ cmbagent-ui/.next/
84
+ cmbagent-ui/out/
85
+ cmbagent-ui/.env.local
86
+ cmbagent-ui/.env.development.local
87
+ cmbagent-ui/.env.test.local
88
+ cmbagent-ui/.env.production.local
89
+
90
+ # Test files
91
+ test_*.py
92
+ tests/testdocs/*
93
+ tests/artifacts/
94
+ tests/legacy/
95
+
96
+ .massgen
97
+
98
+ # PyCharm
99
+ .idea/
.pre-commit-config.yaml ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ repos:
2
+ - repo: https://github.com/kynan/nbstripout
3
+ rev: 0.8.2
4
+ hooks:
5
+ - id: nbstripout
6
+ files: ^notebooks/.*\.ipynb$
7
+ args:
8
+ - --keep-output
9
+ - --extra-keys
10
+ - metadata.kernelspec metadata.language_info
.readthedocs.yml ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # .readthedocs.yml
2
+ # Read the Docs configuration file
3
+ # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
4
+
5
+ # Required
6
+ version: 2
7
+
8
+ # Set the OS, Python version and other tools you might need
9
+ build:
10
+ os: ubuntu-22.04
11
+ tools:
12
+ python: "3.7"
13
+
14
+ # Build documentation in the docs/ directory with Sphinx
15
+ sphinx:
16
+ configuration: docs/conf.py
17
+
18
+ # Build documentation with MkDocs
19
+ #mkdocs:
20
+ # configuration: mkdocs.yml
21
+
22
+ # Optionally build your docs in additional formats such as PDF
23
+ formats:
24
+ - pdf
25
+
26
+ # Optional but recommended, declare the Python requirements required
27
+ # to build your documentation
28
+ # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
29
+ python:
30
+ install:
31
+ - requirements: docs/requirements.txt
CLAUDE.md ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ CMBAgent is a multi-agent system powered by AG2 (formerly AutoGen) for scientific discovery across scientific domains. The system follows a planning and control strategy with specialized agents for different research workflows.
8
+
9
+ The system uses a two-phase approach:
10
+ - **Planning**: A planner and plan reviewer design task execution strategies
11
+ - **Control**: Step-by-step execution where sub-tasks are handed to specialized agents
12
+
13
+ ## Key Components
14
+
15
+ ### Core Python Package (`cmbagent/`)
16
+ - **Main API**: `cmbagent.one_shot()` - primary function for task execution
17
+ - **Agent system**: Specialized agents in `agents/` directory, each with `.py` and `.yaml` configuration
18
+ - **Agent types**: Planning, control, coding (engineer, executor), research (researcher, summarizer), hypothesis generation, keyword extraction, and domain-specific templates
19
+ - **Context management**: Sophisticated context handling via `context.py` and `hand_offs.py`
20
+ - **Remote execution**: `execution/remote_executor.py` - delegates code execution to client
21
+
22
+ ## Common Development Commands
23
+
24
+ ### Python Package
25
+ ```bash
26
+ # Install core only (lightweight)
27
+ pip install -e .
28
+
29
+ # Install with development tools
30
+ pip install -e ".[dev]"
31
+
32
+ # Install with local execution support (if not using remote execution)
33
+ pip install -e ".[local]"
34
+
35
+ # Install everything (all scientific packages)
36
+ pip install -e ".[all]"
37
+
38
+ # Run tests (individual Python scripts in tests/)
39
+ python tests/test_one_shot.py
40
+ python tests/test_engineer.py
41
+ ```
42
+
43
+ ## Architecture Details
44
+
45
+ ### Agent System
46
+ - Each agent has a Python implementation and YAML configuration
47
+ - Agents are specialized for different workflows: engineering, research, planning, execution, hypothesis generation, keyword extraction
48
+ - Response formatters handle output formatting for specific use cases
49
+ - Controller manages workflow orchestration
50
+ - Domain-specific agents in `specialized/` serve as templates for users to add their own
51
+
52
+ ### Multi-Modal Capabilities
53
+ - Plot generation capabilities
54
+ - File browser with inline image viewing
55
+
56
+ ### Research Capabilities
57
+ - Domain-agnostic architecture supporting any scientific field
58
+ - Literature search and keyword extraction (`literature.py`, `aas_keyword_finder`)
59
+ - Example domain packages provided: materials science, biochemistry, astronomy, data science
60
+ - Users can easily add their own domain-specific dependencies in `pyproject.toml`
61
+
62
+ ### Work Directory Structure
63
+ Tasks create organized output directories:
64
+ ```
65
+ project_dir/
66
+ ├── chats/ # Conversation history
67
+ ├── codebase/ # Generated code
68
+ ├── cost/ # Cost analysis
69
+ ├── data/ # Generated data and plots
70
+ ├── time/ # Timing reports
71
+ └── context/ # Context files (.pkl)
72
+ ```
73
+
74
+ ## Testing
75
+ - Tests are individual Python scripts in `tests/` directory
76
+ - Run specific test files directly: `python tests/test_<name>.py`
77
+ - Tests cover various agents and use cases (engineering, research, plotting, etc.)
78
+ - No centralized test runner - tests are designed as standalone demonstrations
79
+
80
+ ## Configuration
81
+ - API keys required: `OPENAI_API_KEY`, optionally `ANTHROPIC_API_KEY`, `GEMINI_API_KEY`
82
+ - Model configurations in `cmbagent/apis/` (JSON files for different providers)
83
+ - Agent configurations in individual `.yaml` files alongside Python implementations
84
+
85
+ ## Key APIs
86
+ - `cmbagent.one_shot(task, agent='engineer', model='gpt-4o', work_dir=...)` - Main execution API
87
+ - Planning and control via specialized agent orchestration
88
+ - WebSocket streaming for real-time UI updates
LICENSE ADDED
@@ -0,0 +1,536 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright 2026 - Boris Bolliet
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
202
+
203
+
204
+
205
+ APACHE HTTP SERVER SUBCOMPONENTS:
206
+
207
+ The Apache HTTP Server includes a number of subcomponents with
208
+ separate copyright notices and license terms. Your use of the source
209
+ code for the these subcomponents is subject to the terms and
210
+ conditions of the following licenses.
211
+
212
+ For the mod_mime_magic component:
213
+
214
+ /*
215
+ * mod_mime_magic: MIME type lookup via file magic numbers
216
+ * Copyright (c) 1996-1997 Cisco Systems, Inc.
217
+ *
218
+ * This software was submitted by Cisco Systems to the Apache Group in July
219
+ * 1997. Future revisions and derivatives of this source code must
220
+ * acknowledge Cisco Systems as the original contributor of this module.
221
+ * All other licensing and usage conditions are those of the Apache Group.
222
+ *
223
+ * Some of this code is derived from the free version of the file command
224
+ * originally posted to comp.sources.unix. Copyright info for that program
225
+ * is included below as required.
226
+ * ---------------------------------------------------------------------------
227
+ * - Copyright (c) Ian F. Darwin, 1987. Written by Ian F. Darwin.
228
+ *
229
+ * This software is not subject to any license of the American Telephone and
230
+ * Telegraph Company or of the Regents of the University of California.
231
+ *
232
+ * Permission is granted to anyone to use this software for any purpose on any
233
+ * computer system, and to alter it and redistribute it freely, subject to
234
+ * the following restrictions:
235
+ *
236
+ * 1. The author is not responsible for the consequences of use of this
237
+ * software, no matter how awful, even if they arise from flaws in it.
238
+ *
239
+ * 2. The origin of this software must not be misrepresented, either by
240
+ * explicit claim or by omission. Since few users ever read sources, credits
241
+ * must appear in the documentation.
242
+ *
243
+ * 3. Altered versions must be plainly marked as such, and must not be
244
+ * misrepresented as being the original software. Since few users ever read
245
+ * sources, credits must appear in the documentation.
246
+ *
247
+ * 4. This notice may not be removed or altered.
248
+ * -------------------------------------------------------------------------
249
+ *
250
+ */
251
+
252
+
253
+ For the modules\mappers\mod_imagemap.c component:
254
+
255
+ "macmartinized" polygon code copyright 1992 by Eric Haines, erich@eye.com
256
+
257
+ For the server\util_md5.c component:
258
+
259
+ /************************************************************************
260
+ * NCSA HTTPd Server
261
+ * Software Development Group
262
+ * National Center for Supercomputing Applications
263
+ * University of Illinois at Urbana-Champaign
264
+ * 605 E. Springfield, Champaign, IL 61820
265
+ * httpd@ncsa.uiuc.edu
266
+ *
267
+ * Copyright (C) 1995, Board of Trustees of the University of Illinois
268
+ *
269
+ ************************************************************************
270
+ *
271
+ * md5.c: NCSA HTTPd code which uses the md5c.c RSA Code
272
+ *
273
+ * Original Code Copyright (C) 1994, Jeff Hostetler, Spyglass, Inc.
274
+ * Portions of Content-MD5 code Copyright (C) 1993, 1994 by Carnegie Mellon
275
+ * University (see Copyright below).
276
+ * Portions of Content-MD5 code Copyright (C) 1991 Bell Communications
277
+ * Research, Inc. (Bellcore) (see Copyright below).
278
+ * Portions extracted from mpack, John G. Myers - jgm+@cmu.edu
279
+ * Content-MD5 Code contributed by Martin Hamilton (martin@net.lut.ac.uk)
280
+ *
281
+ */
282
+
283
+
284
+ /* these portions extracted from mpack, John G. Myers - jgm+@cmu.edu */
285
+ /* (C) Copyright 1993,1994 by Carnegie Mellon University
286
+ * All Rights Reserved.
287
+ *
288
+ * Permission to use, copy, modify, distribute, and sell this software
289
+ * and its documentation for any purpose is hereby granted without
290
+ * fee, provided that the above copyright notice appear in all copies
291
+ * and that both that copyright notice and this permission notice
292
+ * appear in supporting documentation, and that the name of Carnegie
293
+ * Mellon University not be used in advertising or publicity
294
+ * pertaining to distribution of the software without specific,
295
+ * written prior permission. Carnegie Mellon University makes no
296
+ * representations about the suitability of this software for any
297
+ * purpose. It is provided "as is" without express or implied
298
+ * warranty.
299
+ *
300
+ * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
301
+ * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
302
+ * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
303
+ * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
304
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
305
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
306
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
307
+ * SOFTWARE.
308
+ */
309
+
310
+ /*
311
+ * Copyright (c) 1991 Bell Communications Research, Inc. (Bellcore)
312
+ *
313
+ * Permission to use, copy, modify, and distribute this material
314
+ * for any purpose and without fee is hereby granted, provided
315
+ * that the above copyright notice and this permission notice
316
+ * appear in all copies, and that the name of Bellcore not be
317
+ * used in advertising or publicity pertaining to this
318
+ * material without the specific, prior written permission
319
+ * of an authorized representative of Bellcore. BELLCORE
320
+ * MAKES NO REPRESENTATIONS ABOUT THE ACCURACY OR SUITABILITY
321
+ * OF THIS MATERIAL FOR ANY PURPOSE. IT IS PROVIDED "AS IS",
322
+ * WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
323
+ */
324
+
325
+
326
+ For the util_pcre.c and ap_regex.h components:
327
+
328
+ Copyright (c) 1997-2004 University of Cambridge
329
+
330
+ -----------------------------------------------------------------------------
331
+ Redistribution and use in source and binary forms, with or without
332
+ modification, are permitted provided that the following conditions are met:
333
+
334
+ * Redistributions of source code must retain the above copyright notice,
335
+ this list of conditions and the following disclaimer.
336
+
337
+ * Redistributions in binary form must reproduce the above copyright
338
+ notice, this list of conditions and the following disclaimer in the
339
+ documentation and/or other materials provided with the distribution.
340
+
341
+ * Neither the name of the University of Cambridge nor the names of its
342
+ contributors may be used to endorse or promote products derived from
343
+ this software without specific prior written permission.
344
+
345
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
346
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
347
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
348
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
349
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
350
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
351
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
352
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
353
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
354
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
355
+ POSSIBILITY OF SUCH DAMAGE.
356
+ -----------------------------------------------------------------------------
357
+
358
+
359
+ For the srclib\apr\include\apr_md5.h component:
360
+ /*
361
+ * This is work is derived from material Copyright RSA Data Security, Inc.
362
+ *
363
+ * The RSA copyright statement and Licence for that original material is
364
+ * included below. This is followed by the Apache copyright statement and
365
+ * licence for the modifications made to that material.
366
+ */
367
+
368
+ /* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
369
+ rights reserved.
370
+
371
+ License to copy and use this software is granted provided that it
372
+ is identified as the "RSA Data Security, Inc. MD5 Message-Digest
373
+ Algorithm" in all material mentioning or referencing this software
374
+ or this function.
375
+
376
+ License is also granted to make and use derivative works provided
377
+ that such works are identified as "derived from the RSA Data
378
+ Security, Inc. MD5 Message-Digest Algorithm" in all material
379
+ mentioning or referencing the derived work.
380
+
381
+ RSA Data Security, Inc. makes no representations concerning either
382
+ the merchantability of this software or the suitability of this
383
+ software for any particular purpose. It is provided "as is"
384
+ without express or implied warranty of any kind.
385
+
386
+ These notices must be retained in any copies of any part of this
387
+ documentation and/or software.
388
+ */
389
+
390
+ For the srclib\apr\passwd\apr_md5.c component:
391
+
392
+ /*
393
+ * This is work is derived from material Copyright RSA Data Security, Inc.
394
+ *
395
+ * The RSA copyright statement and Licence for that original material is
396
+ * included below. This is followed by the Apache copyright statement and
397
+ * licence for the modifications made to that material.
398
+ */
399
+
400
+ /* MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm
401
+ */
402
+
403
+ /* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
404
+ rights reserved.
405
+
406
+ License to copy and use this software is granted provided that it
407
+ is identified as the "RSA Data Security, Inc. MD5 Message-Digest
408
+ Algorithm" in all material mentioning or referencing this software
409
+ or this function.
410
+
411
+ License is also granted to make and use derivative works provided
412
+ that such works are identified as "derived from the RSA Data
413
+ Security, Inc. MD5 Message-Digest Algorithm" in all material
414
+ mentioning or referencing the derived work.
415
+
416
+ RSA Data Security, Inc. makes no representations concerning either
417
+ the merchantability of this software or the suitability of this
418
+ software for any particular purpose. It is provided "as is"
419
+ without express or implied warranty of any kind.
420
+
421
+ These notices must be retained in any copies of any part of this
422
+ documentation and/or software.
423
+ */
424
+ /*
425
+ * The apr_md5_encode() routine uses much code obtained from the FreeBSD 3.0
426
+ * MD5 crypt() function, which is licenced as follows:
427
+ * ----------------------------------------------------------------------------
428
+ * "THE BEER-WARE LICENSE" (Revision 42):
429
+ * <phk@login.dknet.dk> wrote this file. As long as you retain this notice you
430
+ * can do whatever you want with this stuff. If we meet some day, and you think
431
+ * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
432
+ * ----------------------------------------------------------------------------
433
+ */
434
+
435
+ For the srclib\apr-util\crypto\apr_md4.c component:
436
+
437
+ * This is derived from material copyright RSA Data Security, Inc.
438
+ * Their notice is reproduced below in its entirety.
439
+ *
440
+ * Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
441
+ * rights reserved.
442
+ *
443
+ * License to copy and use this software is granted provided that it
444
+ * is identified as the "RSA Data Security, Inc. MD4 Message-Digest
445
+ * Algorithm" in all material mentioning or referencing this software
446
+ * or this function.
447
+ *
448
+ * License is also granted to make and use derivative works provided
449
+ * that such works are identified as "derived from the RSA Data
450
+ * Security, Inc. MD4 Message-Digest Algorithm" in all material
451
+ * mentioning or referencing the derived work.
452
+ *
453
+ * RSA Data Security, Inc. makes no representations concerning either
454
+ * the merchantability of this software or the suitability of this
455
+ * software for any particular purpose. It is provided "as is"
456
+ * without express or implied warranty of any kind.
457
+ *
458
+ * These notices must be retained in any copies of any part of this
459
+ * documentation and/or software.
460
+ */
461
+
462
+ For the srclib\apr-util\include\apr_md4.h component:
463
+
464
+ *
465
+ * This is derived from material copyright RSA Data Security, Inc.
466
+ * Their notice is reproduced below in its entirety.
467
+ *
468
+ * Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
469
+ * rights reserved.
470
+ *
471
+ * License to copy and use this software is granted provided that it
472
+ * is identified as the "RSA Data Security, Inc. MD4 Message-Digest
473
+ * Algorithm" in all material mentioning or referencing this software
474
+ * or this function.
475
+ *
476
+ * License is also granted to make and use derivative works provided
477
+ * that such works are identified as "derived from the RSA Data
478
+ * Security, Inc. MD4 Message-Digest Algorithm" in all material
479
+ * mentioning or referencing the derived work.
480
+ *
481
+ * RSA Data Security, Inc. makes no representations concerning either
482
+ * the merchantability of this software or the suitability of this
483
+ * software for any particular purpose. It is provided "as is"
484
+ * without express or implied warranty of any kind.
485
+ *
486
+ * These notices must be retained in any copies of any part of this
487
+ * documentation and/or software.
488
+ */
489
+
490
+
491
+ For the srclib\apr-util\test\testmd4.c component:
492
+
493
+ *
494
+ * This is derived from material copyright RSA Data Security, Inc.
495
+ * Their notice is reproduced below in its entirety.
496
+ *
497
+ * Copyright (C) 1990-2, RSA Data Security, Inc. Created 1990. All
498
+ * rights reserved.
499
+ *
500
+ * RSA Data Security, Inc. makes no representations concerning either
501
+ * the merchantability of this software or the suitability of this
502
+ * software for any particular purpose. It is provided "as is"
503
+ * without express or implied warranty of any kind.
504
+ *
505
+ * These notices must be retained in any copies of any part of this
506
+ * documentation and/or software.
507
+ */
508
+
509
+ For the test\zb.c component:
510
+
511
+ /* ZeusBench V1.01
512
+ ===============
513
+
514
+ This program is Copyright (C) Zeus Technology Limited 1996.
515
+
516
+ This program may be used and copied freely providing this copyright notice
517
+ is not removed.
518
+
519
+ This software is provided "as is" and any express or implied warranties,
520
+ including but not limited to, the implied warranties of merchantability and
521
+ fitness for a particular purpose are disclaimed. In no event shall
522
+ Zeus Technology Ltd. be liable for any direct, indirect, incidental, special,
523
+ exemplary, or consequential damaged (including, but not limited to,
524
+ procurement of substitute good or services; loss of use, data, or profits;
525
+ or business interruption) however caused and on theory of liability. Whether
526
+ in contract, strict liability or tort (including negligence or otherwise)
527
+ arising in any way out of the use of this software, even if advised of the
528
+ possibility of such damage.
529
+
530
+ Written by Adam Twiss (adam@zeus.co.uk). March 1996
531
+
532
+ Thanks to the following people for their input:
533
+ Mike Belshe (mbelshe@netscape.com)
534
+ Michael Campanella (campanella@stevms.enet.dec.com)
535
+
536
+ */
MANIFEST.in ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ include LICENSE
2
+ include README.md
3
+ recursive-exclude cmbagent/output *
4
+ recursive-exclude cmbagent/cmbagent/data *
5
+ global-exclude __pycache__/
6
+ global-exclude *.py[cod]
7
+ global-exclude *.egg-info/
8
+ global-exclude .DS_Store
9
+ recursive-exclude cmbagent/docs *
10
+ include cmbagent/apis/*.json
11
+ include cmbagent/planner/*yaml
12
+ include cmbagent/engineer/*yaml
13
+ include cmbagent/executor/*yaml
14
+ include cmbagent/admin/*yaml
15
+
16
+ prune tests
17
+ prune docs
18
+ prune evals
19
+ prune output
20
+ exclude *.ipynb
21
+ exclude *backup*.py
22
+ exclude *.sh
README.md ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # cmbagent
2
+
3
+ [![License](https://img.shields.io/badge/license-Apache%202-blue.svg)](LICENSE) [![arXiv](https://img.shields.io/badge/arXiv-2507.07257-b31b1b.svg)](https://arxiv.org/abs/2507.07257)
4
+ [![PyPI version](https://img.shields.io/pypi/v/cmbagent.svg)](https://pypi.org/project/cmbagent/)
5
+
6
+ <a href="https://www.youtube.com/@cmbagent" target="_blank">
7
+ <img src="https://img.shields.io/badge/YouTube-Subscribe-red?style=flat-square&logo=youtube" alt="Subscribe on YouTube" width="140"/>
8
+ </a>
9
+
10
+ <a href="https://discord.gg/UG47Yb6gHG" target="_blank">
11
+ <img src="https://img.shields.io/badge/Discord-Join%20Chat-5865F2?logo=discord&logoColor=white&style=flat-square" alt="Join us on Discord" width="140"/>
12
+ </a>
13
+
14
+ Autonomous Research System, Powered by [AG2](https://github.com/ag2ai/ag2). Developed by Boris Bolliet for the Open Source Software community since May 2024. Under Apache 2 license, no private or public organizations/individuals other than the copyright holder can claim IP and/or ownership on this software package.
15
+
16
+ CMBAgent is a generalist multi-agent system for autonomous scientific research. Despite the name — a nod to its origins in astrophysics — it is domain-agnostic and works across any scientific field.
17
+
18
+ 🎉 **News**: Cmbagent won a **first place award** at the **NeurIPS 2025 [Fair Universe Competition](https://fair-universe.lbl.gov/)**.
19
+
20
+
21
+ Cmbagent is the autonomous research engine for [Denario](https://astropilot-ai.github.io/DenarioPaperPage/), our end-to-end research system.
22
+
23
+ We are currently deploying cmbagent on the cloud, it will be in production soon!
24
+
25
+ Check our [demo videos](https://www.youtube.com/@cmbagent) on YouTube!
26
+
27
+ Join our [Discord Server](https://discord.gg/UG47Yb6gHG) to ask all your questions!
28
+
29
+ This is open-source research-ready software.
30
+
31
+ - Check the [demo notebooks](https://github.com/CMBAgents/cmbagent/tree/main/docs/notebooks).
32
+
33
+ - Best performances are obtained with [top-scoring models](https://lmarena.ai/?leaderboard).
34
+
35
+ We emphasize that [cmbagent](https://github.com/CMBAgents/cmbagent) is under active development and apologize for any bugs.
36
+
37
+ **The backbone of [cmbagent](https://github.com/CMBAgents/cmbagent) is [AG2](https://github.com/ag2ai/ag2)**. **Please star the [AG2](https://github.com/ag2ai/ag2) repo ⭐ and cite [Wu et al (2023)](https://arxiv.org/abs/2308.08155)!**
38
+
39
+ ## Strategy
40
+
41
+ **Cmbagent** acts according to a **Planning and Control** strategy with **no human-in-the-loop**.
42
+
43
+ You give a task to solve, then:
44
+
45
+ **Planning**
46
+
47
+ - A plan is designed from a conversation between a planner and a plan reviewer.
48
+ - Once the number of feedbacks (reviews) is exhausted the plan is recorded in context and **cmbagent** switches to **control**.
49
+
50
+ **Control**
51
+
52
+ - The plan is executed **step-by-step**.
53
+ - Sub-tasks are handed over to a single agent in each step.
54
+
55
+ ## Installation
56
+
57
+ With Python 3.12 or above:
58
+
59
+ ```bash
60
+ python3 -m venv cmbagent_env
61
+ source cmbagent_env/bin/activate
62
+ pip install cmbagent
63
+ ```
64
+
65
+ ### Optional Dependencies
66
+
67
+ ```bash
68
+ # Development tools (pytest, ipython, jupyter)
69
+ pip install cmbagent[dev]
70
+
71
+ # Local execution (if not using the web UI)
72
+ pip install cmbagent[local]
73
+
74
+ # Domain-specific packages (for specialized agents or local execution)
75
+ pip install cmbagent[materials] # pymatgen, ASE, phonopy, matminer
76
+ pip install cmbagent[biochem] # BioPython, RDKit, MDAnalysis, ProDy
77
+ pip install cmbagent[astro] # CAMB, AstroPy, HEALPix, Cobaya
78
+ pip install cmbagent[data] # scipy, scikit-learn, seaborn, plotly
79
+
80
+ # Everything
81
+ pip install cmbagent[all]
82
+
83
+ # Or combine multiple domains
84
+ pip install cmbagent[materials,biochem,data]
85
+ ```
86
+
87
+ ## Installation for developers
88
+
89
+ ```bash
90
+ git clone https://github.com/CMBAgents/cmbagent.git
91
+ cd cmbagent
92
+ python3 -m venv cmbagent_env
93
+ source cmbagent_env/bin/activate
94
+ pip install -e .
95
+ ```
96
+
97
+ You can then open the folder in your VSCode/Cursor/Emacs/... and work on the source code.
98
+
99
+ To install optional domain-specific packages (examples provided, add your own in `pyproject.toml`):
100
+
101
+ ```bash
102
+ # Single domain
103
+ pip install -e .[materials]
104
+ pip install -e .[biochem]
105
+ pip install -e .[astro]
106
+ pip install -e .[data]
107
+
108
+ # Or combine multiple domains
109
+ pip install -e .[materials,biochem,data]
110
+ ```
111
+
112
+ ## Run
113
+
114
+ We assume you are in the virtual environment where you installed cmbagent.
115
+
116
+ Here is a one-liner you can run in terminal:
117
+
118
+ ```bash
119
+ python -c "import cmbagent; task='''Draw two random numbers and give me their sum'''; results=cmbagent.one_shot(task, agent='engineer', engineer_model='gpt-4o-mini');"
120
+ ```
121
+
122
+ If you want to run the notebooks, first create the ipykernel (assuming your virtual environment is called cmbagent_env):
123
+
124
+ ```bash
125
+ pip install ipykernel jupyterlab
126
+ python -m ipykernel install --user --name cmbagent_env --display-name "Python (cmbagent_env)"
127
+ ```
128
+
129
+ Then launch jupyterlab:
130
+
131
+ ```bash
132
+ jupyter-lab
133
+ ```
134
+
135
+ Select the cmbagent kernel, and run the notebook.
136
+
137
+ ## API Keys
138
+
139
+ Before you can use cmbagent, you need to set your OpenAI API key as an environment variable. Do this in a terminal, before launching Jupyter-lab.
140
+
141
+ For Unix-based systems (Linux, macOS), do:
142
+
143
+ ```bash
144
+ export OPENAI_API_KEY="sk-..." ## mandatory for the RAG agents
145
+ export ANTHROPIC_API_KEY="sk-..." ## optional
146
+ export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account-key.json" ## optional for Vertex AI
147
+ ```
148
+
149
+ (paste in your bashrc or zshrc file, if possible.)
150
+
151
+ For Windows, use [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) and the same command.
152
+
153
+ By default, cmbagent uses models from oai/anthropic/google. If you want to pick different LLMs, just adapt `agent_llm_configs` as above, or the `default_agent_llm_configs` in [utils.py](https://github.com/CMBAgents/cmbagent/blob/main/cmbagent/utils.py).
154
+
155
+ ## References
156
+
157
+ ```bash
158
+
159
+ @software{CMBAGENT_2025,
160
+ author = {Boris Bolliet},
161
+ title = {CMBAGENT: Open-Source Multi-Agent System for Science},
162
+ year = {2025},
163
+ url = {https://github.com/CMBAgents/cmbagent},
164
+ note = {Available at https://github.com/CMBAgents/cmbagent},
165
+ version = {latest}
166
+ }
167
+
168
+ @misc{xu2025opensourceplanning,
169
+ title={Open Source Planning & Control System with Language Agents for Autonomous Scientific Discovery},
170
+ author={Licong Xu and Milind Sarkar and Anto I. Lonappan and Íñigo Zubeldia and Pablo Villanueva-Domingo and Santiago Casas and Christian Fidler and Chetana Amancharla and Ujjwal Tiwari and Adrian Bayer and Chadi Ait Ekiou and Miles Cranmer and Adrian Dimitrov and James Fergusson and Kahaan Gandhi and Sven Krippendorf and Andrew Laverick and Julien Lesgourgues and Antony Lewis and Thomas Meier and Blake Sherwin and Kristen Surrao and Francisco Villaescusa-Navarro and Chi Wang and Xueqing Xu and Boris Bolliet},
171
+ year={2025},
172
+ eprint={2507.07257},
173
+ archivePrefix={arXiv},
174
+ primaryClass={cs.AI},
175
+ url={https://arxiv.org/abs/2507.07257},
176
+ }
177
+
178
+
179
+ @misc{Laverick:2024fyh,
180
+ author = "Laverick, Andrew and Surrao, Kristen and Zubeldia, Inigo and Bolliet, Boris and Cranmer, Miles and Lewis, Antony and Sherwin, Blake and Lesgourgues, Julien",
181
+ title = "{Multi-Agent System for Cosmological Parameter Analysis}",
182
+ eprint = "2412.00431",
183
+ archivePrefix = "arXiv",
184
+ primaryClass = "astro-ph.IM",
185
+ month = "11",
186
+ year = "2024"
187
+ }
188
+ ```
189
+
190
+ ## Acknowledgments
191
+
192
+ Our project is funded by the [Cambridge Centre for Data-Driven Discovery Accelerate Programme](https://science.ai.cam.ac.uk). We are grateful to [Mark Sze](https://github.com/marklysze) for help with [AG2](https://github.com/ag2ai/ag2).
cmbagent/__init__.py ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # import warnings
2
+ # warnings.filterwarnings("ignore", message=r'Field "model_client_cls" in LLMConfigEntry has conflict with protected namespace "model_"')
3
+
4
+ import warnings
5
+
6
+ warnings.filterwarnings(
7
+ "ignore", # action
8
+ message=r"Update function string contains no variables\.", # regex
9
+ category=UserWarning, # same category that's raised
10
+ module=r"autogen\.agentchat\.conversable_agent" # where it comes from
11
+ )
12
+
13
+ # Suppress Vertex AI deprecation warning - preview module internally still uses deprecated code
14
+ warnings.filterwarnings(
15
+ "ignore",
16
+ message=r"This feature is deprecated as of June 24, 2025.*",
17
+ category=UserWarning,
18
+ module=r"vertexai\.generative_models\._generative_models"
19
+ )
20
+
21
+
22
+ from .cmbagent import CMBAgent
23
+ from .version import __version__
24
+ import os
25
+ from IPython.display import Image, display, Markdown
26
+ from autogen.cmbagent_utils import LOGO, IMG_WIDTH, cmbagent_disable_display
27
+
28
+
29
+
30
+ from .cmbagent import planning_and_control, one_shot, human_in_the_loop, control, load_plan, deep_research, work_dir_default
31
+ from .cmbagent import get_keywords, get_keywords_from_aaai, get_keywords_from_string, get_aas_keywords
32
+
33
+ # OCR functionality
34
+ from .utils.ocr import process_single_pdf, process_folder
35
+
36
+ # arXiv downloader functionality
37
+ from .utils.arxiv_downloader import arxiv_filter
38
+
39
+ # Summarization functionality
40
+ from .utils.summarization import preprocess_task, summarize_document, summarize_documents
41
+
42
+
43
+ def print_cmbagent_logo():
44
+ base_dir = os.path.dirname(__file__)
45
+ # logo.png is in the images directory at the project root
46
+ # base_dir is cmbagent/cmbagent/, so go up one level to get to cmbagent/
47
+ project_root = os.path.dirname(base_dir)
48
+ png_path = os.path.join(project_root, "images", "logo.png")
49
+ # print(png_path)
50
+ # print(base_dir)
51
+ if not cmbagent_disable_display:
52
+ display(Image(filename=png_path, width=IMG_WIDTH))
53
+ display(Markdown(LOGO))
54
+ # ANSI hyperlink: Note that support varies by terminal.
55
+ # HTML link for GitHub with icon
56
+ # github_html = '''
57
+ # <a href="https://github.com/CMBAgents/cmbagent" target="_blank" style="text-decoration: none;">
58
+ # <img src="https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png"
59
+ # alt="GitHub Icon" width="20" style="vertical-align:middle; margin-right:5px;">
60
+ # Star me on GitHub
61
+ # </a>
62
+ # '''
63
+ # # HTML link for YouTube with icon
64
+ # youtube_html = '''
65
+ # <a href="https://www.youtube.com/@cmbagent" target="_blank" style="text-decoration: none;">
66
+ # <img src="https://upload.wikimedia.org/wikipedia/commons/4/42/YouTube_icon_%282013-2017%29.png"
67
+ # alt="YouTube Icon" width="20" style="vertical-align:middle; margin-right:5px;">
68
+ # Watch me on YouTube
69
+ # </a>
70
+ # '''
71
+
72
+ # display(HTML(github_html))
73
+ # display(HTML(youtube_html))
74
+
75
+ print_cmbagent_logo()
cmbagent/agents/admin/__init__.py ADDED
File without changes
cmbagent/agents/admin/admin.yaml ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: "admin"
2
+
3
+ instructions: |
4
+ A human admin.
5
+
6
+
7
+ code_execution_config: False
8
+
9
+
10
+
11
+
cmbagent/agents/coding/engineer/__init__.py ADDED
File without changes
cmbagent/agents/coding/engineer/engineer.yaml ADDED
@@ -0,0 +1,206 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: "engineer"
2
+
3
+ instructions: |
4
+
5
+ You are the engineer agent.
6
+
7
+ The following instructions must be followed strictly at all times.
8
+
9
+ You provide single self-consistent Python code, ready to be executed. The code itself should not have any comment lines or explanations in it other than docstrings.
10
+ You must not provide any other text than the Python code (and a concise explanation of the code, including description of modifications provided to fix the previous code if any).
11
+
12
+ --------------------
13
+ **RESPONSE**
14
+
15
+ Your response is structured as follows:
16
+
17
+ **Code Explanation:**
18
+
19
+ <provide a concise explanation of the code>
20
+
21
+ **Modifications:** (Optional)
22
+
23
+ <provide a summary of any modifications made to fix errors from the previous version>
24
+
25
+ **Python Code:**
26
+
27
+ <Python code ready to be executed>
28
+ --------------------
29
+
30
+
31
+ **IMPORTANT**:
32
+
33
+ - Return one and only one Python code block in your response.
34
+ - Focus on one step at a time.
35
+ - Do not suggest incomplete code.
36
+ - Do not produce code blocks that are not intended for execution.
37
+ - Include only one code block per response.
38
+ - When a plot is requested, it must be saved into a png file at high resolution (dpi>=300).
39
+ - The filename for the plots must have the following format: `<plot_name>_<plot_number>_<timestamp>.<format>` and must be saved in the folder `{database_path}`.
40
+ - For plots, add relevant units to the axes labels, where appropriate.
41
+ - Every time a plot is saved, print a concise description of the plot to the console.
42
+ - Do not use '.show()' for plots.
43
+ - Do not check for installed packages.
44
+ - Do not install new packages, another agent will do that. Nonetheless, following a successful package installation, you must report the entire code that you were trying to run and that failed due to the missing package.
45
+ - Write very detailed docstrings for all methods or classes.
46
+ - Avoid f-strings when possible.
47
+ - Strictly avoid comments in the code. The explanations go in the code explanation section.
48
+ - NEVER use .format, use string concatenation instead. Prefer simple strings for your prints.
49
+ - ALWAYS wrap all top-level execution code (everything that is not a function/class definition or import) inside `if __name__ == '__main__':`. This is mandatory because on macOS, Python uses the "spawn" multiprocessing start method, which re-imports the script in child processes. Without this guard, any library that uses multiprocessing internally (memory_profiler, joblib, torch, etc.) will crash with a RuntimeError. It also makes functions safely importable from other steps.
50
+
51
+
52
+ Workflow involving creating several Python files or modules:
53
+ - Only provide code for one file/module at a time.
54
+ - If an issue is found in a specific file/module, regenerate the full code for that file only.
55
+
56
+ Plotting:
57
+ - Never use LaTeX rendering.
58
+ - Avoid multiple dollar signs within one label.
59
+ - Do not use nested math mode.
60
+ - Do not use LaTeX expressions with optional arguments or complex macros.
61
+ - Keep labels as short, clear and simple as possible, convey the details of the information needed.
62
+ - Always set concise and informative titles to plots.
63
+ - Always call `plt.tight_layout()` (or `fig.tight_layout()`) after setting titles and labels, to prevent any overlap.
64
+ - Rotate axis tick labels if needed to avoid overlap, especially if they are long or dense.
65
+ - Increase the figure size if necessary to ensure no elements overlap.
66
+ - Do not return plot code unless all labels, titles, and ticks are fully visible and not overlapping.
67
+
68
+ f-strings:
69
+ - Avoid using f-strings.
70
+
71
+ To print a blank line or add a newline, use "\n" or a triple-quoted string; never hit the physical newline inside a single-quoted r"" string.
72
+
73
+
74
+ NEVER use .format, use string concatenation instead.
75
+
76
+
77
+ Math mode and LaTeX: Never use LateX rendering.
78
+
79
+ For training ML models (e.g., neural networks):
80
+ - Disable Verbose Output for Training: Configure any training routines (e.g., using Keras, TensorFlow, torch, etc.) to disable ongoing progress messages. For instance, in Keras, always set the verbose parameter to 0 in methods like model.fit(), and similar for other frameworks.
81
+ - Suppress Repetitive Status Messages: Remove or deactivate print statements and logging within custom training loops that output redundant progress updates (e.g., "Epoch 1/100", "16/16 ━━━━━━", etc.) to avoid generating large volumes of unnecessary output.
82
+ - Retain Essential Evaluation Metrics: Ensure that important quantitative results, such as the final training score, final loss, and other key numerical evaluations, are clearly printed to provide a concise summary of the training performance.
83
+ - Prevent Unintended Re-enabling of Verbose Logging: Verify that code modifications do not inadvertently re-enable detailed logging during training, keeping the output concise while still reporting critical evaluation information.
84
+ - Suppress verbose output during training (e.g., use `verbose=0` for Keras training).
85
+ - Suppress progress bars and repeated print/log outputs during loops.
86
+ - Retain and print only key evaluation metrics (e.g., final loss, accuracy).
87
+
88
+
89
+ Progress bars:
90
+ - If the code involves training or fitting a model, make sure ALL progress bars are not shown (i.e., silence them).
91
+
92
+ Units:
93
+ - For quantities that are not dimensionless, always include explanation of the units in the code explanation section, or in the docstrings. Never use code annotations.
94
+ - The units of all the quanities and functions should be explained in the code explanation section, or in the docstrings.
95
+
96
+ ALL important numerical results from simulations or analysis must be printed to the console in a detailed manner,
97
+ with concise but precise textual description of the results (without truncation). For this, it may be necessary to change pandas (if using it) display options.
98
+
99
+
100
+
101
+ Further instructions:
102
+ - you should not aim to discuss the results of the code, only to write the code. The discussion should be done by the researcher agent.
103
+ - don't use latex in dictionary keys.
104
+ - When using Exception handling, never provide dummy summaries/solutions. The errors should be printed in full to be addressed properly.
105
+ - Make sure you don't print error message that may appear many times in long loops (just print once).
106
+
107
+
108
+ For projects that require many plots, you should not generate many plots. Instead, you should generate a single plot with multiple subplots, only the ones that are definitely needed to convey the information.
109
+
110
+ Python Error avoidance:
111
+ - Make sure you avoid RuntimeWarning: invalid value encountered in divide.
112
+
113
+
114
+
115
+ **Use Python language only.**
116
+
117
+ When generating code that produces a plot, you must: Save the plot to disk file using the savefig method or similar.
118
+
119
+ For plots, make sure you use detailed labeling and grid lines unless asked otherwise.
120
+
121
+
122
+ Also, make sure you **never use LaTeX rendering**, i.e., set:
123
+ `rcParams['text.usetex'] = False`.
124
+
125
+
126
+ ----- DATA/PROBLEM OF INTEREST------------
127
+ {improved_main_task}
128
+ ------------------------------------------
129
+
130
+ ----Task-specific instructions for coding-----
131
+ {engineer_append_instructions}
132
+ ----------------------------------------------
133
+
134
+ We follow the established plan:
135
+
136
+ <PLAN>
137
+ {final_plan}
138
+ </PLAN>
139
+
140
+ <CURRENT_STEP_IN_PLAN>
141
+ **Current step in plan:**
142
+ {current_plan_step_number}
143
+ </CURRENT_STEP_IN_PLAN>
144
+
145
+ <CURRENT_STATUS>
146
+ **Current status:**
147
+ {current_status}
148
+ </CURRENT_STATUS>
149
+
150
+ <CURRENT_SUB_TASK>
151
+ **Current sub-task:**
152
+ {current_sub_task}
153
+ </CURRENT_SUB_TASK>
154
+
155
+ <CURRENT_INSTRUCTIONS>
156
+ **Current instructions:**
157
+ {current_instructions}
158
+ </CURRENT_INSTRUCTIONS>
159
+
160
+ Your implementation much achieve the best speed in terms of compute. For instance, you make sure all initialization steps are outside of loops.
161
+
162
+ **Saving and Loading Data**
163
+
164
+ CRITICAL - User-provided file paths:
165
+ - If the user provides an absolute file path (e.g., `/path/to/data.xyz`), you MUST use that EXACT path.
166
+ - NEVER modify, relocate, or wrap user-provided absolute paths in variables or fallback logic.
167
+ - NEVER create "possible_paths" lists that include both user paths and other locations.
168
+ - User-provided paths are authoritative - trust them and use them directly.
169
+
170
+ For OUTPUT files (plots, generated data, intermediate results):
171
+ - The output data folder for this task is: `{database_path}`
172
+ - You **must** save generated data (e.g., plots, datasets, `.csv/.npz/.npy` files) under this folder
173
+ - You **must** load data files from previous steps using the SAME folder
174
+ - Use: `data_dir = "{database_path}"` and `filepath = os.path.join(data_dir, "my_data.npy")`
175
+ - While you save extended information, you must also print it to the console in a detailed and concise manner.
176
+
177
+ Summary:
178
+ - INPUT data from user: Use the EXACT path provided by the user (e.g., `/path/to/input.xyz`)
179
+ - OUTPUT data you generate: Save to `{database_path}`
180
+
181
+
182
+
183
+ **AUTOMATIC PREAMBLE**: Every script automatically gets the following lines injected at the top by the system — do NOT include them yourself:
184
+ import sys
185
+ import os
186
+ sys.path.insert(0, os.path.abspath("codebase"))
187
+ This means you can always import from previous step files directly (e.g., `from step_1 import function`).
188
+
189
+ Rather than writing code from scratch, you should prioritize importing functions from the python scripts in the codebase folder if some of them are relevant to the current sub-task (e.g., "from step_1 import function" etc, note that all generated scripts are in the codebase folder).
190
+
191
+ **Context**
192
+ Summary of previous steps execution and python scripts in the codebase folder.
193
+ For relative imports, use the relative path to the file, making sure you use the correct filename (i.e., from step_1 import function, from step_2 import function, etc.).
194
+ <PREVIOUS_STEPS_EXECUTION_SUMMARY>
195
+ {previous_steps_execution_summary}
196
+ -----------------------------------
197
+ </PREVIOUS_STEPS_EXECUTION_SUMMARY>
198
+
199
+
200
+
201
+
202
+
203
+ description: |
204
+ To generate the results and do the computations, plots and key statistics via code pipelines.
205
+
206
+
cmbagent/agents/coding/engineer/massgen_engineer.py ADDED
@@ -0,0 +1,499 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """MassGen-powered engineer agent for multi-agent code generation.
2
+
3
+ This module provides an engineer agent that uses MassGen (multi-agent system)
4
+ for code generation instead of a single LLM. Multiple agents collaborate to
5
+ produce higher-quality code through planning, voting, and consensus.
6
+ """
7
+
8
+ import os
9
+ import asyncio
10
+ import re
11
+ from pathlib import Path
12
+ from typing import Dict, Optional, Union, List, Callable, Any
13
+ from autogen.agentchat import ConversableAgent, UpdateSystemMessage
14
+ from cmbagent.base_agent import CmbAgentSwarmAgent
15
+
16
+ # Lazy import massgen to avoid dependency issues if not installed
17
+ _massgen = None
18
+
19
+ def get_massgen():
20
+ """Lazy import of massgen."""
21
+ global _massgen
22
+ if _massgen is None:
23
+ try:
24
+ import massgen
25
+ _massgen = massgen
26
+ except ImportError:
27
+ raise ImportError(
28
+ "MassGen is required for engineer_backend='massgen'. "
29
+ "Install it with: pip install massgen"
30
+ )
31
+ return _massgen
32
+
33
+
34
+ class MassGenEngineerAgent(CmbAgentSwarmAgent):
35
+ """Hybrid engineer agent that uses MassGen for initial code generation,
36
+ then falls back to standard LLM for debugging.
37
+
38
+ Strategy:
39
+ - First code generation: Use MassGen (multi-agent collaboration)
40
+ - Debugging/retries (after code failures): Use single LLM (fast iteration)
41
+
42
+ This provides quality initial code with efficient debugging cycles.
43
+
44
+ Args:
45
+ name: Agent name
46
+ massgen_config: Path to MassGen YAML configuration file
47
+ extract_code: Whether to extract code from MassGen response (default: True)
48
+ use_massgen_for_retries: If True, use MassGen even for retries (default: False)
49
+ **kwargs: Additional arguments passed to CmbAgentSwarmAgent
50
+ """
51
+
52
+ DEFAULT_MASSGEN_CONFIG = str(
53
+ Path(__file__).parent.parent.parent.parent.parent /
54
+ "massgen_configs" / "engineer_massgen.yaml"
55
+ )
56
+
57
+ def __init__(
58
+ self,
59
+ name: str,
60
+ massgen_config: Optional[str] = None,
61
+ extract_code: bool = True,
62
+ verbose: bool = False,
63
+ enable_logging: bool = True,
64
+ use_massgen_for_retries: bool = False,
65
+ **kwargs
66
+ ):
67
+ super().__init__(name=name, **kwargs)
68
+
69
+ self.massgen_config = massgen_config or self.DEFAULT_MASSGEN_CONFIG
70
+ self.extract_code = extract_code
71
+ self.verbose = verbose
72
+ self.enable_logging = enable_logging
73
+ self.use_massgen_for_retries = use_massgen_for_retries
74
+
75
+ # Track code generation attempts
76
+ self._generation_count = 0
77
+
78
+ # Verify config exists
79
+ if not os.path.exists(self.massgen_config):
80
+ raise FileNotFoundError(
81
+ f"MassGen config not found: {self.massgen_config}\n"
82
+ f"Expected at: {self.DEFAULT_MASSGEN_CONFIG}"
83
+ )
84
+
85
+ print(f"[MassGenEngineer] Initialized (hybrid mode)")
86
+ print(f"[MassGenEngineer] Config: {self.massgen_config}")
87
+ print(f"[MassGenEngineer] Strategy: MassGen for initial, single LLM for retries")
88
+
89
+ # Register MassGen reply function with highest priority
90
+ # This will be called before the default LLM reply function
91
+ self.register_reply(
92
+ trigger=ConversableAgent,
93
+ reply_func=self._massgen_reply_func,
94
+ position=0, # Highest priority
95
+ )
96
+
97
+ def _extract_code_from_response(self, response: str) -> str:
98
+ """Extract Python code from MassGen structured response.
99
+
100
+ Expects format:
101
+ **Code Explanation:**
102
+ <explanation>
103
+
104
+ **Python Code:**
105
+ <code>
106
+ """
107
+ # Try to extract Python Code section
108
+ match = re.search(
109
+ r'\*\*Python Code:\*\*\s*```python\s*(.*?)```',
110
+ response,
111
+ re.DOTALL | re.IGNORECASE
112
+ )
113
+ if match:
114
+ return match.group(1).strip()
115
+
116
+ match = re.search(
117
+ r'\*\*Python Code:\*\*\s*(.*?)(?=\*\*|$)',
118
+ response,
119
+ re.DOTALL | re.IGNORECASE
120
+ )
121
+ if match:
122
+ code = match.group(1).strip()
123
+ # Remove markdown code fences if present
124
+ code = re.sub(r'^```python\s*', '', code)
125
+ code = re.sub(r'```\s*$', '', code)
126
+ return code.strip()
127
+
128
+ # Fallback: try to find any code block
129
+ match = re.search(r'```python\s*(.*?)```', response, re.DOTALL)
130
+ if match:
131
+ return match.group(1).strip()
132
+
133
+ # Last resort: return the response as-is
134
+ return response
135
+
136
+ def _get_context_variables(self, messages: Optional[List[Dict]] = None) -> Dict[str, Any]:
137
+ """Extract context variables from AG2's ContextVariables.
138
+
139
+ These are the variables that the engineer.yaml template expects.
140
+
141
+ Args:
142
+ messages: Message history (unused, but kept for compatibility)
143
+ """
144
+ context = {}
145
+
146
+ # Try to get context from AG2's ContextVariables
147
+ # The context is stored in the agent's state during UpdateSystemMessage
148
+ if hasattr(self, 'context_variables') and self.context_variables:
149
+ # AG2's ContextVariables has a `data` attribute
150
+ if hasattr(self.context_variables, 'data'):
151
+ context = dict(self.context_variables.data)
152
+ elif isinstance(self.context_variables, dict):
153
+ context = self.context_variables.copy()
154
+
155
+ # Also check _agent_state (backup)
156
+ if not context and hasattr(self, '_agent_state') and self._agent_state:
157
+ context = self._agent_state.copy()
158
+
159
+ # Set defaults for required template variables
160
+ defaults = {
161
+ 'improved_main_task': '',
162
+ 'engineer_append_instructions': '',
163
+ 'final_plan': 'No formal plan - one-shot execution',
164
+ 'current_plan_step_number': '1',
165
+ 'current_status': 'Starting task execution',
166
+ 'current_sub_task': '',
167
+ 'current_instructions': 'Execute the task as described',
168
+ 'database_path': './data',
169
+ 'codebase_path': './codebase',
170
+ 'previous_steps_execution_summary': 'No previous steps - this is the first step.',
171
+ }
172
+
173
+ # Merge defaults with any existing context
174
+ for key, value in defaults.items():
175
+ if key not in context:
176
+ context[key] = value
177
+
178
+ return context
179
+
180
+ def _format_engineer_prompt(self, task: str, messages: Optional[List[Dict]] = None) -> str:
181
+ """Format the full engineer prompt with all context variables.
182
+
183
+ This ensures MassGen receives the same detailed instructions as
184
+ the standard engineer would.
185
+
186
+ Args:
187
+ task: The raw task from the conversation
188
+ messages: Message history for context
189
+
190
+ Returns:
191
+ Fully formatted prompt with all context variables
192
+ """
193
+ from cmbagent.utils.yaml import yaml_load_file
194
+ import os
195
+
196
+ # Load engineer template
197
+ engineer_yaml = os.path.join(
198
+ os.path.dirname(__file__),
199
+ "engineer.yaml"
200
+ )
201
+ engineer_info = yaml_load_file(engineer_yaml)
202
+ template = engineer_info['instructions']
203
+
204
+ # Get context variables from AG2's ContextVariables
205
+ context = self._get_context_variables(messages)
206
+
207
+ # Update task-specific context
208
+ # If improved_main_task is not set, use the task
209
+ if not context.get('improved_main_task') or context['improved_main_task'] == '':
210
+ context['improved_main_task'] = task
211
+
212
+ if not context.get('current_sub_task') or context['current_sub_task'] == '':
213
+ context['current_sub_task'] = task
214
+
215
+ if self.verbose:
216
+ print(f"[MassGenEngineer] Context variables:")
217
+ for key in ['improved_main_task', 'final_plan', 'current_plan_step_number',
218
+ 'current_status', 'database_path', 'codebase_path']:
219
+ print(f" {key}: {context.get(key, 'NOT SET')}")
220
+
221
+ # Format the template
222
+ try:
223
+ formatted_prompt = template.format(**context)
224
+ except KeyError as e:
225
+ # Missing variable - log warning and continue with defaults
226
+ print(f"[MassGenEngineer] Warning: Missing context variable {e}")
227
+ print(f"[MassGenEngineer] Using defaults and continuing...")
228
+ # Try again with just the essential variables
229
+ essential_context = {
230
+ 'improved_main_task': task,
231
+ 'engineer_append_instructions': '',
232
+ 'final_plan': 'No formal plan - one-shot execution',
233
+ 'current_plan_step_number': '1',
234
+ 'current_status': 'Starting task execution',
235
+ 'current_sub_task': task,
236
+ 'current_instructions': 'Execute the task as described',
237
+ 'database_path': context.get('database_path', './data'),
238
+ 'codebase_path': context.get('codebase_path', './codebase'),
239
+ 'previous_steps_execution_summary': 'No previous steps - this is the first step.',
240
+ }
241
+ try:
242
+ formatted_prompt = template.format(**essential_context)
243
+ except Exception as e2:
244
+ print(f"[MassGenEngineer] Error formatting template: {e2}")
245
+ print(f"[MassGenEngineer] Falling back to task-only prompt")
246
+ formatted_prompt = task
247
+
248
+ return formatted_prompt
249
+
250
+ async def _call_massgen(self, prompt: str, messages: Optional[List[Dict]] = None) -> str:
251
+ """Call MassGen asynchronously to generate code.
252
+
253
+ Args:
254
+ prompt: The raw task/prompt from the conversation
255
+ messages: Message history for context
256
+
257
+ Returns:
258
+ Formatted response ready for AG2 (includes code block)
259
+ """
260
+ massgen = get_massgen()
261
+
262
+ # Format the full engineer prompt with all context variables
263
+ formatted_prompt = self._format_engineer_prompt(prompt, messages)
264
+
265
+ if self.verbose:
266
+ print(f"\n[MassGenEngineer] Calling MassGen...")
267
+ print(f"[MassGenEngineer] Config: {self.massgen_config}")
268
+ print(f"[MassGenEngineer] Raw prompt length: {len(prompt)} chars")
269
+ print(f"[MassGenEngineer] Formatted prompt length: {len(formatted_prompt)} chars")
270
+ print(f"[MassGenEngineer] Raw prompt: {prompt}")
271
+
272
+ # Call MassGen with the FULL formatted prompt
273
+ result = await massgen.run(
274
+ query=formatted_prompt,
275
+ config=self.massgen_config,
276
+ verbose=self.verbose,
277
+ enable_logging=self.enable_logging
278
+ )
279
+
280
+ if self.verbose:
281
+ print(f"[MassGenEngineer] Selected agent: {result.get('selected_agent', 'N/A')}")
282
+ if result.get('log_directory'):
283
+ print(f"[MassGenEngineer] Logs: {result['log_directory']}")
284
+
285
+ response = result['final_answer']
286
+
287
+ # Format response for AG2 executor
288
+ # The response should include the full explanation + code in a code block
289
+ # This matches what the engineer normally returns
290
+ formatted_response = self._format_response_for_ag2(response)
291
+
292
+ if self.verbose:
293
+ print(f"[MassGenEngineer] Formatted response length: {len(formatted_response)} chars")
294
+
295
+ return formatted_response
296
+
297
+ def _format_response_for_ag2(self, massgen_response: str) -> str:
298
+ """Format MassGen response for AG2 executor.
299
+
300
+ Ensures the response includes code in ```python``` blocks
301
+ which the executor expects.
302
+ """
303
+ # Check if response already has code blocks
304
+ if "```python" in massgen_response:
305
+ # Already formatted properly
306
+ return massgen_response
307
+
308
+ # Try to extract code and wrap it properly
309
+ code = self._extract_code_from_response(massgen_response)
310
+
311
+ # Build formatted response matching engineer's expected format
312
+ formatted = f"""**Code Explanation:**
313
+
314
+ Generated by MassGen multi-agent collaboration.
315
+
316
+ **Python Code:**
317
+
318
+ ```python
319
+ {code}
320
+ ```
321
+ """
322
+ return formatted
323
+
324
+ def _is_retry_attempt(self, messages: List[Dict]) -> bool:
325
+ """Detect if this is a retry/debugging attempt.
326
+
327
+ Returns True if:
328
+ - Code execution errors are present in history
329
+ - Previous code blocks exist (indicating a retry)
330
+ - Error-related keywords in recent messages
331
+ """
332
+ if len(messages) < 2:
333
+ # First message - definitely not a retry
334
+ return False
335
+
336
+ # Check last few messages for error indicators
337
+ recent_messages = messages[-5:] if len(messages) >= 5 else messages
338
+
339
+ for msg in recent_messages:
340
+ content = msg.get("content", "").lower()
341
+
342
+ # Check for execution results or errors
343
+ if any(keyword in content for keyword in [
344
+ "exitcode",
345
+ "traceback",
346
+ "error:",
347
+ "exception",
348
+ "failed",
349
+ "stderr:",
350
+ "exit code",
351
+ "returned non-zero",
352
+ ]):
353
+ return True
354
+
355
+ # Check for code execution output
356
+ if "```python" in content and any(err in content for err in [
357
+ "error", "exception", "failed", "traceback"
358
+ ]):
359
+ return True
360
+
361
+ return False
362
+
363
+ def _massgen_reply_func(
364
+ self,
365
+ recipient: ConversableAgent,
366
+ messages: Optional[List[Dict]] = None,
367
+ sender: Optional[ConversableAgent] = None,
368
+ config: Optional[Any] = None,
369
+ ) -> tuple[bool, Union[str, Dict, None]]:
370
+ """Custom reply function for MassGen hybrid approach.
371
+
372
+ This is called by AG2's reply system. The config parameter may contain
373
+ context_variables from the conversation.
374
+
375
+ Strategy:
376
+ - First code generation: Use MassGen (multi-agent collaboration)
377
+ - Retry/debugging: Use standard LLM (fast, cheap iteration)
378
+
379
+ Returns:
380
+ tuple: (should_reply, reply_content)
381
+ - should_reply: True if this function generated a reply
382
+ - reply_content: The generated reply or None
383
+ """
384
+ if messages is None:
385
+ print("[MassGenEngineer] Warning: messages is None, skipping")
386
+ return False, None
387
+
388
+ if not messages:
389
+ print("[MassGenEngineer] Warning: messages is empty, skipping")
390
+ return False, None
391
+
392
+ # Store context_variables if available (passed through config or recipient)
393
+ if config and isinstance(config, dict) and 'context_variables' in config:
394
+ self.context_variables = config['context_variables']
395
+ elif hasattr(recipient, 'context_variables'):
396
+ self.context_variables = recipient.context_variables
397
+ elif hasattr(sender, 'context_variables'):
398
+ self.context_variables = sender.context_variables
399
+
400
+ # Determine if this is a retry
401
+ is_retry = self._is_retry_attempt(messages)
402
+
403
+ self._generation_count += 1
404
+
405
+ try:
406
+ if is_retry and not self.use_massgen_for_retries:
407
+ # RETRY MODE: Skip to next reply function (standard LLM)
408
+ print(f"\n[MassGenEngineer] Attempt #{self._generation_count} - RETRY MODE (single LLM)")
409
+ print(f"[MassGenEngineer] Delegating to standard LLM for fast debugging")
410
+
411
+ # Return False to let next reply function (default LLM) handle it
412
+ return False, None
413
+
414
+ else:
415
+ # INITIAL MODE: Use MassGen for quality code generation
416
+ print(f"\n[MassGenEngineer] Attempt #{self._generation_count} - INITIAL MODE (MassGen multi-agent)")
417
+ print(f"[MassGenEngineer] Using MassGen for high-quality code generation")
418
+
419
+ last_message = messages[-1]
420
+ prompt = last_message.get("content", "")
421
+
422
+ if not prompt:
423
+ print("[MassGenEngineer] Warning: prompt is empty")
424
+ return False, None
425
+
426
+ # Call MassGen synchronously (AG2 expects sync)
427
+ try:
428
+ loop = asyncio.get_event_loop()
429
+ if loop.is_running():
430
+ # Already in async context, create new loop
431
+ import nest_asyncio
432
+ try:
433
+ nest_asyncio.apply()
434
+ except:
435
+ # nest_asyncio not available, try different approach
436
+ loop = asyncio.new_event_loop()
437
+ asyncio.set_event_loop(loop)
438
+ except RuntimeError:
439
+ loop = asyncio.new_event_loop()
440
+ asyncio.set_event_loop(loop)
441
+
442
+ print(f"[MassGenEngineer] Calling MassGen...")
443
+ response = loop.run_until_complete(self._call_massgen(prompt, messages))
444
+
445
+ if response:
446
+ print(f"[MassGenEngineer] MassGen generated response (length: {len(response)})")
447
+ return True, response # We generated a reply
448
+ else:
449
+ print(f"[MassGenEngineer] Warning: MassGen returned empty response")
450
+ return False, None # Let next reply function try
451
+
452
+ except Exception as e:
453
+ print(f"[MassGenEngineer] Error in _massgen_reply_func: {type(e).__name__}: {e}")
454
+ import traceback
455
+ traceback.print_exc()
456
+ return False, None # Let next reply function try
457
+
458
+
459
+ def create_massgen_engineer_agent(
460
+ name: str,
461
+ llm_config: Dict,
462
+ instructions: str,
463
+ description: str,
464
+ massgen_config: Optional[str] = None,
465
+ verbose: bool = False,
466
+ enable_logging: bool = True,
467
+ use_massgen_for_retries: bool = False,
468
+ **kwargs
469
+ ) -> MassGenEngineerAgent:
470
+ """Factory function to create a hybrid MassGen engineer agent.
471
+
472
+ Args:
473
+ name: Agent name (typically "engineer")
474
+ llm_config: LLM config (used for retry attempts with single LLM)
475
+ instructions: Engineer instructions/system message
476
+ description: Agent description
477
+ massgen_config: Path to MassGen config (optional)
478
+ verbose: Enable verbose output
479
+ enable_logging: Enable MassGen logging
480
+ use_massgen_for_retries: Use MassGen even for retries (default: False)
481
+ **kwargs: Additional arguments
482
+
483
+ Returns:
484
+ Configured MassGenEngineerAgent with hybrid strategy
485
+ """
486
+ agent = MassGenEngineerAgent(
487
+ name=name,
488
+ massgen_config=massgen_config,
489
+ extract_code=True,
490
+ verbose=verbose,
491
+ enable_logging=enable_logging,
492
+ use_massgen_for_retries=use_massgen_for_retries,
493
+ update_agent_state_before_reply=[UpdateSystemMessage(instructions)],
494
+ description=description,
495
+ llm_config=llm_config, # Used for retry attempts
496
+ **kwargs
497
+ )
498
+
499
+ return agent
cmbagent/agents/coding/engineer_nest/__init__.py ADDED
File without changes
cmbagent/agents/coding/engineer_nest/engineer_nest.yaml ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: "engineer_nest"
2
+
3
+ instructions: |
4
+
5
+ You trigger the nested chat for the engineer agent.
6
+
7
+ description: |
8
+ Engineer nest agent, to implement the plan.
9
+
10
+
11
+
12
+
cmbagent/agents/coding/engineer_response_formatter/__init__.py ADDED
File without changes
cmbagent/agents/coding/engineer_response_formatter/engineer_response_formatter.py ADDED
@@ -0,0 +1,325 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ from cmbagent.base_agent import BaseAgent
4
+ from pydantic import BaseModel, Field
5
+ from typing import Optional
6
+ class EngineerResponseFormatterAgent(BaseAgent):
7
+
8
+ def __init__(self, llm_config=None, **kwargs):
9
+
10
+ agent_id = os.path.splitext(os.path.abspath(__file__))[0]
11
+
12
+ llm_config['config_list'][0]['response_format'] = self.EngineerResponse
13
+ # llm_config['config_list'][0]['response_mime_type'] = "application/json"
14
+ # llm_config['config_list'][0]['response_schema'] = list[self.EngineerResponse]
15
+
16
+ super().__init__(llm_config=llm_config, agent_id=agent_id, **kwargs)
17
+
18
+
19
+ def set_agent(self,**kwargs):
20
+
21
+ super().set_assistant_agent(**kwargs)
22
+
23
+
24
+
25
+ class EngineerResponse(BaseModel):
26
+ filename: str = Field(..., description="The name to give to this Python script")
27
+ relative_path: Optional[str] = Field(
28
+ None, description="The relative path to the file (exclude <filename>.py itself)"
29
+ )
30
+ code_explanation: str = Field(
31
+ ..., description="Copy of the engineer's explanation of the Python code provided. Including the docstrings of the methods used."
32
+ )
33
+ modification_summary: Optional[str] = Field(
34
+ None,
35
+ description="Copy of the engineer's summary of any modifications made to fix errors from the previous version."
36
+ )
37
+ python_code: str = Field(
38
+ ..., description="Copy of the engineer's Python code in a form ready to execute. Should not contain anything else than code. Indentation has to be carefully checked, line by line, and fixed."
39
+ )
40
+
41
+ @staticmethod
42
+ def _fix_indentation(code: str, max_attempts: int = 10) -> str:
43
+ """Auto-fix indentation errors caused by LLM structured output.
44
+
45
+ When an LLM generates Python code inside a JSON string field
46
+ (e.g., the ``python_code`` field of a Pydantic structured response),
47
+ it occasionally introduces stray whitespace — typically one extra
48
+ space at the start of a line. This breaks Python's indentation
49
+ rules and causes ``IndentationError`` at execution time.
50
+
51
+ This method uses ``compile()`` to detect such errors and attempts
52
+ to repair them automatically. For each offending line reported by
53
+ the compiler, it builds a set of candidate indentation levels from
54
+ the surrounding context:
55
+
56
+ - **Same level** as the nearest non-empty line above.
57
+ - **One level deeper** if that line opens a block (ends with ``:``,
58
+ e.g., ``for``, ``if``, ``def``, ``while``, ``with``, ...).
59
+ - **One level shallower** (dedent) — for lines that follow the
60
+ end of a block body and return to the parent scope.
61
+ - **Same level** as the nearest non-empty line below.
62
+ - **Zero indent** (module level) as a fallback.
63
+
64
+ Each candidate is tried with ``compile()``; the first one that
65
+ produces valid Python wins. The process repeats up to
66
+ ``max_attempts`` times to handle multiple affected lines.
67
+
68
+ Parameters
69
+ ----------
70
+ code : str
71
+ The full Python source code to check and fix.
72
+ max_attempts : int, optional
73
+ Maximum number of lines to fix in one pass (default 10).
74
+
75
+ Returns
76
+ -------
77
+ str
78
+ The code with indentation errors fixed where possible.
79
+ If the code has non-indentation ``SyntaxError``s, or if no
80
+ candidate fixes compile, the code is returned as-is.
81
+ """
82
+ print("[_fix_indentation] Checking code for indentation errors...")
83
+ for attempt in range(max_attempts):
84
+ try:
85
+ compile(code, "<string>", "exec")
86
+ if attempt == 0:
87
+ print("[_fix_indentation] Code compiles cleanly — no fixes needed.")
88
+ else:
89
+ print("[_fix_indentation] Code now compiles after " + str(attempt) + " fix(es).")
90
+ return code
91
+ except IndentationError as exc:
92
+ if exc.lineno is None:
93
+ print("[_fix_indentation] IndentationError with no line number — cannot fix.")
94
+ return code
95
+ lines = code.splitlines()
96
+ bad_idx = exc.lineno - 1
97
+ if bad_idx < 0 or bad_idx >= len(lines):
98
+ print("[_fix_indentation] Bad line index out of range — cannot fix.")
99
+ return code
100
+
101
+ bad_content = lines[bad_idx].lstrip()
102
+ if not bad_content:
103
+ print("[_fix_indentation] Offending line is empty — cannot fix.")
104
+ return code
105
+
106
+ print("[_fix_indentation] IndentationError on line " + str(exc.lineno) + ": " + repr(lines[bad_idx]))
107
+
108
+ # Collect candidate indentation levels from context
109
+ candidates = []
110
+
111
+ # From nearest non-empty neighbour above
112
+ for i in range(bad_idx - 1, -1, -1):
113
+ above = lines[i]
114
+ stripped = above.lstrip()
115
+ if stripped and not stripped.startswith("#"):
116
+ above_indent = above[: len(above) - len(stripped)]
117
+ # Same level as neighbour above
118
+ candidates.append(above_indent)
119
+ # One level deeper (if neighbour ends with ':')
120
+ if stripped.rstrip().endswith(":"):
121
+ candidates.append(above_indent + " ")
122
+ # One level shallower (dedent)
123
+ if len(above_indent) >= 4:
124
+ candidates.append(above_indent[:-4])
125
+ break
126
+
127
+ # From nearest non-empty neighbour below
128
+ for i in range(bad_idx + 1, len(lines)):
129
+ below = lines[i]
130
+ stripped = below.lstrip()
131
+ if stripped and not stripped.startswith("#"):
132
+ below_indent = below[: len(below) - len(stripped)]
133
+ candidates.append(below_indent)
134
+ break
135
+
136
+ # Always try zero indent (module level)
137
+ candidates.append("")
138
+
139
+ # Deduplicate while preserving order
140
+ seen = set()
141
+ unique_candidates = []
142
+ for c in candidates:
143
+ if c not in seen:
144
+ seen.add(c)
145
+ unique_candidates.append(c)
146
+
147
+ print("[_fix_indentation] Trying " + str(len(unique_candidates)) + " candidate indent(s): " + str([str(len(c)) + " spaces" for c in unique_candidates]))
148
+
149
+ # Try each candidate — use the first one that compiles
150
+ fixed = False
151
+ for candidate in unique_candidates:
152
+ trial_lines = lines[:]
153
+ trial_lines[bad_idx] = candidate + bad_content
154
+ trial_code = "\n".join(trial_lines)
155
+ try:
156
+ compile(trial_code, "<string>", "exec")
157
+ code = trial_code
158
+ fixed = True
159
+ print("[_fix_indentation] Fixed line " + str(exc.lineno) + " -> " + str(len(candidate)) + " spaces indent: " + repr(candidate + bad_content))
160
+ break
161
+ except SyntaxError:
162
+ continue
163
+
164
+ if not fixed:
165
+ # None of the candidates compiled on their own, but
166
+ # picking the best guess still lets the next iteration
167
+ # fix a subsequent line. Prefer the neighbour-below
168
+ # indent if available, otherwise the first candidate.
169
+ best = unique_candidates[-2] if len(unique_candidates) >= 2 else unique_candidates[0]
170
+ lines[bad_idx] = best + bad_content
171
+ code = "\n".join(lines)
172
+ print("[_fix_indentation] No candidate compiled — using best guess (" + str(len(best)) + " spaces) for line " + str(exc.lineno))
173
+
174
+ except SyntaxError:
175
+ # Not an indentation issue — nothing we can auto-fix
176
+ print("[_fix_indentation] Non-indentation SyntaxError — returning code as-is.")
177
+ return code
178
+ print("[_fix_indentation] Reached max attempts (" + str(max_attempts) + ") — returning best effort.")
179
+ return code
180
+
181
+ @staticmethod
182
+ def _fix_data_paths(code: str, database_path: str = "data/") -> str:
183
+ """Fix incorrect data directory paths in generated code.
184
+
185
+ Engineers sometimes use hardcoded paths like "./data", "../data", or
186
+ inconsistent variations instead of the designated database_path.
187
+ This method scans the code and fixes these to ensure all data
188
+ operations use the correct, consistent path.
189
+
190
+ Parameters
191
+ ----------
192
+ code : str
193
+ The Python source code to check and fix.
194
+ database_path : str, optional
195
+ The correct data directory path (default "data/").
196
+
197
+ Returns
198
+ -------
199
+ str
200
+ The code with data paths fixed.
201
+ """
202
+ # Normalize database_path (ensure it doesn't have trailing slash for matching)
203
+ db_path_clean = database_path.rstrip("/")
204
+
205
+ # Track if any fixes were made
206
+ fixes_made = []
207
+
208
+ # Pattern 1: data_dir = "..." or data_dir = '...' assignments
209
+ # Matches variations like: data_dir = "./data", data_dir = "../data", data_dir="data"
210
+ data_dir_pattern = r'''(data_dir\s*=\s*)(['"])(\.{0,2}/?data/?)\2'''
211
+
212
+ def fix_data_dir(match):
213
+ prefix = match.group(1)
214
+ quote = match.group(2)
215
+ old_path = match.group(3)
216
+ if old_path.rstrip("/") != db_path_clean:
217
+ fixes_made.append("data_dir assignment: " + repr(old_path) + " -> " + repr(database_path))
218
+ return prefix + quote + database_path + quote
219
+ return match.group(0)
220
+
221
+ code = re.sub(data_dir_pattern, fix_data_dir, code)
222
+
223
+ # Pattern 2: Standalone path strings in os.path.join or open() calls
224
+ # Match patterns like: os.path.join("./data", ...) or open("../data/file.csv", ...)
225
+ path_in_call_pattern = r'''(os\.path\.join\s*\(\s*|open\s*\(\s*)(['"])(\.{1,2}/data)(/[^'"]*)?(\2)'''
226
+
227
+ def fix_path_in_call(match):
228
+ prefix = match.group(1)
229
+ quote = match.group(2)
230
+ bad_prefix = match.group(3)
231
+ rest = match.group(4) or ""
232
+ end_quote = match.group(5)
233
+ fixes_made.append("path in function call: " + repr(bad_prefix + rest) + " -> " + repr(database_path + rest.lstrip("/")))
234
+ return prefix + quote + database_path + rest.lstrip("/") + end_quote
235
+
236
+ code = re.sub(path_in_call_pattern, fix_path_in_call, code)
237
+
238
+ # Pattern 3: Direct string paths like "../data/" or "./data/" used in concatenation
239
+ # Match: + "../data/" + or + "./data/" +
240
+ concat_pattern = r'''(\+\s*)(['"])(\.{1,2}/data/?)(\2)(\s*\+)'''
241
+
242
+ def fix_concat_path(match):
243
+ pre_plus = match.group(1)
244
+ quote = match.group(2)
245
+ old_path = match.group(3)
246
+ end_quote = match.group(4)
247
+ post_plus = match.group(5)
248
+ fixes_made.append("concatenated path: " + repr(old_path) + " -> " + repr(database_path))
249
+ return pre_plus + quote + database_path + end_quote + post_plus
250
+
251
+ code = re.sub(concat_pattern, fix_concat_path, code)
252
+
253
+ # Pattern 4: Save/load paths starting with incorrect relative paths
254
+ # Match: savefig("./data/...", savefig("../data/..., np.save("./data/...
255
+ save_load_pattern = r'''(savefig|np\.save|np\.load|pd\.to_csv|pd\.read_csv|to_csv|read_csv|save|load)\s*\(\s*(['"])(\.{1,2}/data/)([^'"]+)\2'''
256
+
257
+ def fix_save_load(match):
258
+ func = match.group(1)
259
+ quote = match.group(2)
260
+ bad_prefix = match.group(3)
261
+ filename = match.group(4)
262
+ fixes_made.append(func + " path: " + repr(bad_prefix + filename) + " -> " + repr(database_path + filename))
263
+ return func + "(" + quote + database_path + filename + quote
264
+
265
+ code = re.sub(save_load_pattern, fix_save_load, code)
266
+
267
+ # Log fixes
268
+ if fixes_made:
269
+ print("[_fix_data_paths] Fixed " + str(len(fixes_made)) + " data path issue(s):")
270
+ for fix in fixes_made:
271
+ print(" - " + fix)
272
+ else:
273
+ print("[_fix_data_paths] No data path issues found.")
274
+
275
+ return code
276
+
277
+ def format(self) -> str:
278
+ final_filename = self.filename if self.filename.endswith(".py") else self.filename + ".py"
279
+
280
+ if self.relative_path:
281
+ cleaned_path = self.relative_path.rstrip("/\\")
282
+ full_path = os.path.join(cleaned_path, os.path.basename(final_filename))
283
+ else:
284
+ full_path = final_filename
285
+
286
+ # Preamble: filename comment + sys.path for codebase imports
287
+ preamble_lines = [
288
+ f"# filename: {full_path}",
289
+ "import sys",
290
+ "import os",
291
+ 'sys.path.insert(0, os.path.abspath("codebase"))',
292
+ ]
293
+
294
+ code_lines = self.python_code.splitlines()
295
+
296
+ # Strip any existing preamble the LLM may have included
297
+ while code_lines and code_lines[0].strip() in (
298
+ "", "import sys", "import os",
299
+ 'sys.path.insert(0, os.path.abspath("codebase"))',
300
+ "sys.path.insert(0, os.path.abspath('codebase'))",
301
+ ) or (code_lines and code_lines[0].strip().startswith("# filename:")):
302
+ code_lines.pop(0)
303
+
304
+ updated_python_code = "\n".join(preamble_lines + code_lines)
305
+
306
+ # Fix indentation errors introduced by LLM structured output
307
+ updated_python_code = self._fix_indentation(updated_python_code)
308
+
309
+ # Fix incorrect data directory paths (e.g., "./data" -> "data/")
310
+ updated_python_code = self._fix_data_paths(updated_python_code)
311
+
312
+ response_parts = [f"**Code Explanation:**\n\n{self.code_explanation}"]
313
+
314
+ if self.modification_summary:
315
+ response_parts.append(
316
+ f"**Modifications:**\n\n{self.modification_summary}"
317
+ )
318
+
319
+ response_parts.append(
320
+ f"**Python Code:**\n\n```python\n{updated_python_code}\n```"
321
+ )
322
+
323
+ return "\n\n".join(response_parts)
324
+
325
+
cmbagent/agents/coding/engineer_response_formatter/engineer_response_formatter.yaml ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: "engineer_response_formatter"
2
+
3
+ instructions: |
4
+ You are a formatting agent; you format the response provided by the engineer agent and fix errors in the formatting or Python code.
5
+ You are also responsible for checking the Python code for errors and fixing them.
6
+
7
+ Indentation should be carefully checked, line by line in the python code, and fixed.
8
+
9
+ Carefully check the relative imports across the codebase. Since all files are written in the codebase folder, you should use relative imports (not from codebase.filename import function, but from filename import function)
10
+ Filenames in relative imports are always in the format: step_N.py where N is the step number.
11
+
12
+ **AUTOMATIC PREAMBLE**: The following lines are automatically injected at the top of every script by the system (do NOT include them in the code yourself):
13
+ # filename: codebase/step_N.py
14
+ import sys
15
+ import os
16
+ sys.path.insert(0, os.path.abspath("codebase"))
17
+ If the engineer's code already contains these lines, remove them from python_code — they will be added automatically.
18
+
19
+ You **must not alter the content of the response provided to you by the engineer agent**.
20
+ You **must** not add or remove any information.
21
+ You should only structure the input text into the output response format, in doing so you must preserve the modifications made by the engineer agent when it fixed errors.
22
+
23
+ **MAIN GUARD**: All top-level execution code (anything that is not a function/class definition or import) MUST be inside `if __name__ == '__main__':`. If the engineer forgot this, you must add it. This prevents multiprocessing crashes on macOS and makes functions safely importable from other steps.
24
+
25
+ IMPORTANT ERRORS TO PAY ATTENTION TO: (YOU MUST CHECK ALL OF THIS EACH TIME! SOME CAN OCCUR MULTIPLE TIMES!)
26
+
27
+ NEVER use .format, use string concatenation instead.
28
+
29
+ Make sure your LaTeX strings are written on a single line. Fix LaTeX or raw strings if needed.
30
+ Make sure to escape all backslashes properly (e.g., use '\\'instead of '\') to avoid SyntaxWarning: invalid escape sequence '\<x>' where <x> is any character.
31
+ Make sure you avoid SyntaxWarning: invalid escape sequence '\(' and '\)'.
32
+ Make sure you avoid errors like SyntaxError: closing parenthesis ')' does not match opening parenthesis '['
33
+ If errors are caused by LaTeX, remove the LaTeX rendering.
34
+ Don't use f-strings unless you are sure they are not causing errors.
35
+
36
+
37
+ **IMPORTANT**:
38
+ NEVER use .format, use string concatenation instead.
39
+ Make sure no LaTeX rendering is used.
40
+
41
+
42
+
43
+ If the code involves training or fitting a model, make sure ALL progress bars are not shown (i.e., silence them).
44
+
45
+
46
+
47
+ For setting relative_path, remember that:
48
+ - all generated data (e.g., plots, datasets, CSV files, etc.) is saved in the root folder `{database_path}`
49
+ - the root folder `{codebase_path}` should serve as the relative path to <filename>.py
50
+
51
+ **CRITICAL DATA PATH RULES:**
52
+
53
+ User-provided absolute paths (INPUT data):
54
+ - NEVER modify absolute paths that the user explicitly provided (e.g., `/path/to/data.xyz`)
55
+ - These are paths to the user's external data files and MUST be used exactly as given
56
+ - Do NOT wrap them in variables, fallback lists, or try/except blocks that check other locations
57
+ - If you see the engineer creating "possible_paths" lists that include user paths, remove that logic and use the user path directly
58
+
59
+ Relative paths and output data:
60
+ - If the engineer uses relative paths like `"data"`, `"./data"`, `"../data"` for GENERATED output, fix these to use `{database_path}`
61
+ - Correct pattern for output: `data_dir = "{database_path}"` then `os.path.join(data_dir, filename)`
62
+ - This ensures all generated files are saved to the correct location
63
+
64
+ Summary:
65
+ - Absolute paths from user input: PRESERVE exactly as provided
66
+ - Relative paths for output: FIX to use `{database_path}`
67
+
68
+ **CRITICAL FILENAME CONVENTION:**
69
+ - You MUST set the filename to: step_{current_plan_step_number}.py
70
+ - For example, if current_plan_step_number is 1, the filename should be: step_1.py
71
+ - For example, if current_plan_step_number is 3, the filename should be: step_3.py
72
+ - Do NOT use descriptive names like "calculate_sum.py" or "hello_world.py"
73
+ - ALWAYS use the pattern: step_N.py where N is {current_plan_step_number}
74
+ - The relative_path field MUST be set to `{codebase_path}` so that scripts are saved in the codebase folder
75
+
76
+ You adhere strictly to your response format, which is based on the following template:
77
+ filename: str = Field(..., description="The name to give to this Python script - MUST be step_{current_plan_step_number}.py")
78
+ relative_path: Optional[str] = Field(None, description="The relative path to the file (exclude <filename>.py itself)")
79
+ code_explanation: str = Field(..., description="Copy of the engineer's explanation of the Python code provided. You must fix any LaTeX formatting issues if any.")
80
+ modification_summary: Optional[str] = Field(None, description="Copy of the engineer's summary of any modifications made to fix errors from the previous version.")
81
+ python_code: str = Field(..., description="Copy of the engineer's Python code in a form ready to execute. Should not contain anything else than code.")
82
+
83
+ description: |
84
+ Formatter agent, to format the response provided by the engineer agent.
85
+
86
+
cmbagent/agents/coding/executor/__init__.py ADDED
File without changes
cmbagent/agents/coding/executor/executor.yaml ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: "executor"
2
+
3
+ human_input_mode: "NEVER"
4
+ # conversable_agent.py
5
+ # When "NEVER", the agent will never prompt for human input.
6
+ # Under this mode, the conversation stops
7
+ # when the number of auto reply reaches the max_consecutive_auto_reply or when is_termination_msg is True.
8
+ # "NEVER": "A computer terminal that performs no other action than running Python scripts (provided to it quoted in ```python code blocks), or sh shell scripts (provided to it quoted in ```sh code blocks).",
9
+
10
+
11
+
12
+ max_consecutive_auto_reply: 50
13
+
14
+
15
+ instructions: |
16
+ You execute python code provided to you by the engineer.
17
+
18
+ description: |
19
+ Executes python code provided by the engineer.
20
+
21
+
22
+ timeout: 7200
cmbagent/agents/coding/executor_bash/__init__.py ADDED
File without changes
cmbagent/agents/coding/executor_bash/executor_bash.yaml ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: "executor_bash"
2
+
3
+ human_input_mode: "NEVER"
4
+ # conversable_agent.py
5
+ # When "NEVER", the agent will never prompt for human input.
6
+ # Under this mode, the conversation stops
7
+ # when the number of auto reply reaches the max_consecutive_auto_reply or when is_termination_msg is True.
8
+ # "NEVER": "A computer terminal that performs no other action than running Python scripts (provided to it quoted in ```python code blocks), or sh shell scripts (provided to it quoted in ```sh code blocks).",
9
+
10
+
11
+
12
+ max_consecutive_auto_reply: 50
13
+
14
+
15
+ instructions: |
16
+ You execute bash code provided to you by the installer agent.
17
+
18
+ description: |
19
+ Executes bash code provided by the installer agent.
20
+
21
+
22
+ timeout: 7200
cmbagent/agents/coding/executor_response_formatter/__init__.py ADDED
File without changes
cmbagent/agents/coding/executor_response_formatter/executor_response_formatter.py ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from cmbagent.base_agent import BaseAgent
3
+ from pydantic import BaseModel, Field
4
+ from typing import Literal
5
+
6
+
7
+
8
+
9
+ class ExecutorResponseFormatterAgent(BaseAgent):
10
+
11
+ def __init__(self, llm_config=None, **kwargs):
12
+
13
+ agent_id = os.path.splitext(os.path.abspath(__file__))[0]
14
+
15
+ # llm_config['config_list'][0]['response_format'] = self.ExecutorResponse
16
+
17
+ super().__init__(llm_config=llm_config, agent_id=agent_id, **kwargs)
18
+
19
+
20
+ def set_agent(self,**kwargs):
21
+
22
+ super().set_assistant_agent(**kwargs)
23
+
24
+
25
+ ### currently not used
26
+ # class ExecutorResponse(BaseModel):
27
+ # execution_summary: str = Field(
28
+ # ..., description="Summay of the Execution output"
29
+ # )
30
+ # execution_status: Literal["success", "failure"] = Field(
31
+ # ..., description="Status of the execution."
32
+ # )
33
+ # next_agent_suggestion: Literal["engineer", "control", "installer"] = Field(
34
+ # None, ## default value
35
+ # description=r"""
36
+ # Suggestion for the next agent to call:
37
+ # Suggest the engineer agent if error related to generic Python code.
38
+ # Suggest the installer agent if error related to missing Python modules (i.e., ModuleNotFoundError).
39
+ # Suggest the controller if execution was successful.
40
+ # """
41
+ # )
42
+ # current_step_in_plan: int = Field(
43
+ # ..., description="Current step in plan."
44
+ # )
45
+ # def format(self) -> str:
46
+ # return f"""
47
+ # **Execution Summary:**
48
+ # {self.execution_summary}
49
+
50
+ # **Execution Status:**
51
+ # {self.execution_status}
52
+
53
+ # **Next Agent Suggestion:**
54
+ # {self.next_agent_suggestion}
55
+
56
+ # **Current Step in Plan:**
57
+ # {self.current_step_in_plan}
58
+ # """
59
+
60
+
61
+
62
+
63
+
64
+
65
+
cmbagent/agents/coding/executor_response_formatter/executor_response_formatter.yaml ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: "executor_response_formatter"
2
+
3
+ instructions: |
4
+
5
+ You call your tool post_execution_transfer.
6
+
7
+ **IMPORTANT:** You report the CODE execution status, not whether the step is complete.
8
+ - execution_status="success" means the code/command ran without errors
9
+ - execution_status="failure" means there was an error during execution
10
+
11
+ The CONTROLLER decides if the step goal is achieved, not you.
12
+
13
+ For the next agent suggestion, follow these rules:
14
+
15
+ - Suggest the installer agent if error related to missing Python modules (i.e., ModuleNotFoundError).
16
+ - Suggest the camb_context agent if CAMB documentation should be consulted.
17
+ - Suggest camb_context to fix Python errors related to the camb code.
18
+ - Suggest the engineer agent if error related to generic Python code. Don't prioritize the engineer agent if the error is related to the camb code, in this case suggest camb_context instead.
19
+ - Suggest the controller if execution was successful (controller will evaluate if step is complete).
20
+
21
+
22
+ description: |
23
+ Process the response provided by the executor agent. Reports code execution status to the controller.
cmbagent/agents/control/control_starter/__init__.py ADDED
File without changes
cmbagent/agents/control/control_starter/control_starter.yaml ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: "control_starter"
2
+
3
+ instructions: |
4
+ You are the control_starter agent in the team. You don't respond. You should only call record_status tool.
5
+
6
+ You must call record_status **before** calling the agent in charge of the up-coming sub-task.
7
+
8
+ You follow step-by-step the established plan:
9
+
10
+ {final_plan}
11
+
12
+ The current status of this workflow is:
13
+
14
+ **Current step in plan:**
15
+ {current_plan_step_number}
16
+
17
+ **Current status:**
18
+ {current_status}
19
+
20
+ **Current sub-task:**
21
+ {current_sub_task}
22
+
23
+ **Agent in charge:**
24
+ {agent_for_sub_task}
25
+
26
+ **Instructions:**
27
+ {current_instructions}
28
+
29
+ You must implement the plan step-by-step until the final step and never call the terminator agent unless **ALL** the steps in plan have been fully **successfully** implemented one by one.
30
+
31
+ If a code execution has failed, it must be fixed before moving to subsequent step in the plan!
32
+
33
+
34
+
35
+ description: |
36
+ Control starter agent, to start the control stage.
37
+
38
+
39
+
40
+
cmbagent/agents/control/controller/__init__.py ADDED
File without changes
cmbagent/agents/control/controller/controller.yaml ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: "controller"
2
+
3
+ instructions: |
4
+ You are the controller agent in the team. You don't respond. You should only call record_status tool.
5
+
6
+ You must call record_status **before** calling the agent in charge of the up-coming sub-task.
7
+
8
+ **IMPORTANT: Distinguish between code execution status and step completion:**
9
+ - `code_execution_status`: Whether code/commands ran without errors ("success"/"failure")
10
+ - `step_execution_status`: Whether the plan step GOAL is achieved
11
+
12
+ **Only mark a step as "completed" when the actual step goal is achieved**, not just when code runs successfully!
13
+ - Package installation success → step is NOT complete, continue with main task
14
+ - Debugging/fix success → step is NOT complete, verify the fix works
15
+ - Main task code runs and produces expected output → step IS complete
16
+
17
+ You follow step-by-step the established plan:
18
+
19
+ {final_plan}
20
+
21
+ The current status of this workflow is:
22
+
23
+ **Current step in plan:**
24
+ {current_plan_step_number}
25
+
26
+ **Current status:**
27
+ {current_status}
28
+
29
+ **Code execution status:**
30
+ {code_execution_status}
31
+
32
+ **Current sub-task:**
33
+ {current_sub_task}
34
+
35
+ **Agent in charge:**
36
+ {agent_for_sub_task}
37
+
38
+ **Instructions:**
39
+ {current_instructions}
40
+
41
+ You must implement the plan step-by-step until the final step and never call the terminator agent unless **ALL** the steps in plan have been fully **successfully** implemented one by one.
42
+
43
+ If a code execution has failed, it must be fixed before moving to subsequent step in the plan!
44
+
45
+ **Context**
46
+ Summary of previous steps execution and codebase:
47
+ <PREVIOUS_STEPS_EXECUTION_SUMMARY>
48
+ {previous_steps_execution_summary}
49
+ -----------------------------------
50
+ </PREVIOUS_STEPS_EXECUTION_SUMMARY>
51
+
52
+ After successful installation of a Python package, you must call the engineer agent to check if the code can be run.
53
+
54
+ description: |
55
+ Controller agent, to control the plan implementation.
56
+
57
+
58
+
59
+
cmbagent/agents/control/terminator/__init__.py ADDED
File without changes
cmbagent/agents/control/terminator/terminator.yaml ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: "terminator"
2
+
3
+ human_input_mode: "NEVER"
4
+ # conversable_agent.py
5
+ # When "NEVER", the agent will never prompt for human input.
6
+ # Under this mode, the conversation stops
7
+ # when the number of auto reply reaches the max_consecutive_auto_reply or when is_termination_msg is True.
8
+ # "NEVER": "A computer terminal that performs no other action than running Python scripts (provided to it quoted in ```python code blocks), or sh shell scripts (provided to it quoted in ```sh code blocks).",
9
+
10
+
11
+
12
+ max_consecutive_auto_reply: 50
13
+
14
+
15
+ instructions: |
16
+ Terminate the session.
17
+
18
+ description: |
19
+ Terminates the session.
20
+
21
+
22
+ timeout: 7200
cmbagent/agents/hypothesis/idea_hater/__init__.py ADDED
File without changes
cmbagent/agents/hypothesis/idea_hater/idea_hater.yaml ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: "idea_hater"
2
+
3
+ instructions: |
4
+ You are the idea_hater agent in the team.
5
+
6
+ You must provide a high quality critique of the research project ideas presented to you, offering changes on the ideas.
7
+
8
+ -----DATA/PROBLEM OF INTEREST-------------
9
+ {improved_main_task}
10
+ ------------------------------------------
11
+
12
+ Ideas should be critiques based on their relevance to the data/problem of interest, and feasibility given the data available described above.
13
+ Focus only on project ideas involving the data/problem of interest presented here. No external data sources are available. The research project should be about extracting new information, solely from the data/problem of interest.
14
+
15
+ We are following the established plan:
16
+ <PLAN>
17
+
18
+ {final_plan}
19
+
20
+ **Current step in plan:**
21
+ {current_plan_step_number}
22
+
23
+ **Current status:**
24
+ {current_status}
25
+
26
+ **Current sub-task:**
27
+ {current_sub_task}
28
+
29
+ **Current instructions:**
30
+ {current_instructions}
31
+
32
+ </PLAN>
33
+
34
+
35
+ **Context**
36
+ <PREVIOUS_STEPS_EXECUTION_SUMMARY>
37
+ Summary of previous steps:
38
+ {previous_steps_execution_summary}
39
+ -----------------------------------
40
+ </PREVIOUS_STEPS_EXECUTION_SUMMARY>
41
+
42
+
43
+
44
+ <RESPONSE_FORMAT>
45
+
46
+ Your response is structured as follows:
47
+
48
+ **Thoughts on Ideas:**
49
+ - Idea 1:
50
+ * description of the first idea
51
+ * bullet points explaining what you think about the ideas
52
+ .....
53
+ - Idea N:
54
+ * description of the Nth idea
55
+ * bullet points explaining what you think about the ideas
56
+ .....
57
+ - and so on...
58
+
59
+ </RESPONSE_FORMAT>
60
+
61
+
62
+ description: |
63
+ idea_hater agent, to hate on and critique ideas, suggesting improvements and removing bad ideas.
64
+
65
+
cmbagent/agents/hypothesis/idea_hater_response_formatter/__init__.py ADDED
File without changes
cmbagent/agents/hypothesis/idea_hater_response_formatter/idea_hater_response_formatter.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from cmbagent.base_agent import BaseAgent
3
+ from pydantic import BaseModel, Field
4
+ from typing import List
5
+
6
+
7
+ class Subtasks(BaseModel):
8
+ idea_description: str = Field(..., description="The description of the idea")
9
+ bullet_points: List[str] = Field(
10
+ ..., description="A list of bullet points explaining what you think about the idea"
11
+ )
12
+
13
+ class IdeaHaterResponse(BaseModel):
14
+ # main_task: str = Field(..., description="The exact main task to solve.")
15
+ sub_tasks: List[Subtasks]
16
+
17
+ def format(self) -> str:
18
+ plan_output = ""
19
+ for i, step in enumerate(self.sub_tasks):
20
+ plan_output += f"\n- Idea {i + 1}:\n\t* {step.idea_description}\n"
21
+ if step.bullet_points:
22
+ # plan_output += f"\n\t* :\n"
23
+ for bullet in step.bullet_points:
24
+ plan_output += f"\t\t- {bullet}\n"
25
+ message = f"""
26
+ **IDEA CRITIQUE**
27
+ {plan_output}
28
+ """
29
+ return message
30
+
31
+
32
+ class IdeaHaterResponseFormatterAgent(BaseAgent):
33
+
34
+ def __init__(self, llm_config=None, **kwargs):
35
+
36
+ agent_id = os.path.splitext(os.path.abspath(__file__))[0]
37
+
38
+ llm_config['config_list'][0]['response_format'] = IdeaHaterResponse
39
+
40
+ super().__init__(llm_config=llm_config, agent_id=agent_id, **kwargs)
41
+
42
+
43
+ def set_agent(self,**kwargs):
44
+
45
+ super().set_assistant_agent(**kwargs)
46
+
47
+
48
+
49
+
50
+
51
+
cmbagent/agents/hypothesis/idea_hater_response_formatter/idea_hater_response_formatter.yaml ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ name: "idea_hater_response_formatter"
2
+
3
+ instructions: |
4
+ You are a formatting agent, you format the response provided by the idea_hater agent.
5
+
6
+
7
+ description: |
8
+ Formatting agent, to format the response provided by the idea_hater agent.
9
+
cmbagent/agents/hypothesis/idea_maker/__init__.py ADDED
File without changes
cmbagent/agents/hypothesis/idea_maker/idea_maker.yaml ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: "idea_maker"
2
+
3
+ instructions: |
4
+ You are the idea_maker agent in the team.
5
+
6
+ You must provide a high quality set ideas and update your ideas based on recommendations from the idea_hater agent.
7
+
8
+
9
+ The overall research project is about the following data/problem:
10
+
11
+ -----DATA/PROBLEM OF INTEREST-----
12
+ {improved_main_task}
13
+ ----------------------------------
14
+
15
+ Ideas should be based on the data/problem of interest, and feasibility given the data available described above.
16
+ Focus only on project ideas involving the data/problem of interest presented here. No external data sources are available. The research project should be about extracting new information, solely from the data/problem of interest.
17
+
18
+ <PLAN>
19
+ We are following the established plan:
20
+
21
+ {final_plan}
22
+
23
+ **Current step in plan:**
24
+ {current_plan_step_number}
25
+
26
+ **Current status:**
27
+ {current_status}
28
+
29
+ **Current sub-task:**
30
+ {current_sub_task}
31
+
32
+ **Current instructions:**
33
+ {current_instructions}
34
+
35
+ </PLAN>
36
+
37
+ **Context**
38
+ <PREVIOUS_STEPS_EXECUTION_SUMMARY>
39
+ Summary of previous steps:
40
+ {previous_steps_execution_summary}
41
+ -----------------------------------
42
+ </PREVIOUS_STEPS_EXECUTION_SUMMARY>
43
+
44
+ <RESPONSE_FORMAT>
45
+ Your response must be in the following format:
46
+
47
+ **Ideas:**
48
+ - Idea 1: idea title
49
+ * bullet points describing what the idea is
50
+ * ...
51
+ - Idea 2: idea title
52
+ * bullet points describing what the idea is
53
+ * ...
54
+ - and so on...
55
+
56
+ </RESPONSE_FORMAT>
57
+
58
+
59
+ description: |
60
+ idea_maker agent, to generate ideas.
cmbagent/agents/hypothesis/idea_maker_response_formatter/__init__.py ADDED
File without changes
cmbagent/agents/hypothesis/idea_maker_response_formatter/idea_maker_response_formatter.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from cmbagent.base_agent import BaseAgent
3
+ from pydantic import BaseModel, Field
4
+ from typing import List
5
+
6
+
7
+ class Subtasks(BaseModel):
8
+ idea_description: str = Field(..., description="The idea title, as it is, preserve capitalization and ponctuation.")
9
+ bullet_points: List[str] = Field(
10
+ ..., description="A list of bullet points describing what the idea is, i.e., the idea description. Preserve all the information passed to you, do not alter it."
11
+ )
12
+
13
+ class IdeaMakerResponse(BaseModel):
14
+ # main_task: str = Field(..., description="The exact main task to solve.")
15
+ sub_tasks: List[Subtasks]
16
+
17
+ def format(self) -> str:
18
+ plan_output = ""
19
+ for i, step in enumerate(self.sub_tasks):
20
+ plan_output += f"\n- Idea {i + 1}:\n\t* {step.idea_description}\n"
21
+ if step.bullet_points:
22
+ # plan_output += f"\n\t* bullet points:\n"
23
+ for bullet in step.bullet_points:
24
+ plan_output += f"\t\t- {bullet}\n"
25
+ message = f"""
26
+ **Ideas**
27
+ {plan_output}
28
+ """
29
+ return message
30
+
31
+
32
+ class IdeaMakerResponseFormatterAgent(BaseAgent):
33
+
34
+ def __init__(self, llm_config=None, **kwargs):
35
+
36
+ agent_id = os.path.splitext(os.path.abspath(__file__))[0]
37
+
38
+ llm_config['config_list'][0]['response_format'] = IdeaMakerResponse
39
+
40
+ super().__init__(llm_config=llm_config, agent_id=agent_id, **kwargs)
41
+
42
+
43
+ def set_agent(self,**kwargs):
44
+
45
+ super().set_assistant_agent(**kwargs)
46
+
47
+
48
+
49
+
50
+
51
+
cmbagent/agents/hypothesis/idea_maker_response_formatter/idea_maker_response_formatter.yaml ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: "idea_maker_response_formatter"
2
+
3
+ instructions: |
4
+ You are a formatting agent, you format the response provided by the idea_maker_nest agent.
5
+ You should not select ideas yourself.
6
+ If the idea_maker_nest has not selected any specific ideas, you should not select any ideas yourself and report them all.
7
+
8
+
9
+ description: |
10
+ Formatting agent, to format the response provided by the idea_maker agent.
11
+
cmbagent/agents/hypothesis/idea_saver/__init__.py ADDED
File without changes
cmbagent/agents/hypothesis/idea_saver/idea_saver.py ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Idea Saver Agent - Non-LLM agent that saves ideas to a JSON file.
3
+
4
+ This agent replaces the LLM-based idea_saver with a pure Python implementation.
5
+ It parses the formatted ideas from idea_maker_response_formatter and saves them
6
+ to a JSON file.
7
+ """
8
+
9
+ import os
10
+ import re
11
+ import json
12
+ import datetime
13
+ from cmbagent.base_agent import BaseAgent
14
+ from autogen.agentchat import ConversableAgent
15
+
16
+
17
+ def _parse_ideas_from_formatted_text(text: str) -> list[dict]:
18
+ """
19
+ Parse ideas from the formatted text output of idea_maker_response_formatter.
20
+
21
+ Expected format:
22
+ **Ideas**
23
+ - Idea 1:
24
+ * idea title here
25
+ - bullet point 1
26
+ - bullet point 2
27
+ - Idea 2:
28
+ * another idea title
29
+ - bullet point 1
30
+
31
+ Returns:
32
+ List of dicts with 'idea_description' and 'bullet_points' keys.
33
+ """
34
+ ideas = []
35
+
36
+ # Split by "- Idea N:" pattern
37
+ idea_pattern = r'-\s*Idea\s+\d+:'
38
+ parts = re.split(idea_pattern, text)
39
+
40
+ for part in parts[1:]: # Skip the first part (before "- Idea 1:")
41
+ if not part.strip():
42
+ continue
43
+
44
+ idea = {
45
+ 'idea_description': '',
46
+ 'bullet_points': []
47
+ }
48
+
49
+ lines = part.strip().split('\n')
50
+
51
+ for line in lines:
52
+ stripped = line.strip()
53
+ if not stripped:
54
+ continue
55
+
56
+ # Check if it's the idea title (starts with *)
57
+ if stripped.startswith('* '):
58
+ idea['idea_description'] = stripped[2:].strip()
59
+ # Check if it's a bullet point (starts with -)
60
+ elif stripped.startswith('- '):
61
+ idea['bullet_points'].append(stripped[2:].strip())
62
+
63
+ if idea['idea_description']:
64
+ ideas.append(idea)
65
+
66
+ return ideas
67
+
68
+
69
+ class IdeaSaverAgent(BaseAgent):
70
+ """Non-LLM agent that saves ideas to a JSON file."""
71
+
72
+ def __init__(self, llm_config=None, **kwargs):
73
+ agent_id = os.path.splitext(os.path.abspath(__file__))[0]
74
+ # Pass llm_config to parent but we won't use it
75
+ super().__init__(llm_config=llm_config, agent_id=agent_id, **kwargs)
76
+
77
+ def set_agent(self, **kwargs):
78
+ """Create a non-LLM agent that saves ideas."""
79
+ self.agent = IdeaSaverConversableAgent(
80
+ name=self.name,
81
+ description=self.info.get("description", "Idea saver agent"),
82
+ llm_config=False, # No LLM needed
83
+ human_input_mode="NEVER",
84
+ work_dir=self.work_dir,
85
+ )
86
+
87
+
88
+ class IdeaSaverConversableAgent(ConversableAgent):
89
+ """
90
+ A ConversableAgent that doesn't use LLM.
91
+ Instead, it parses and saves ideas from the formatted text.
92
+ """
93
+
94
+ def __init__(self, work_dir: str = ".", **kwargs):
95
+ self._work_dir = work_dir
96
+ super().__init__(**kwargs)
97
+ # Register our custom reply function
98
+ self.register_reply(
99
+ trigger=lambda sender: True, # Reply to any sender
100
+ reply_func=self._save_ideas_reply,
101
+ position=0, # High priority
102
+ )
103
+
104
+ def _save_ideas_reply(
105
+ self,
106
+ recipient: ConversableAgent,
107
+ messages: list[dict],
108
+ sender: ConversableAgent,
109
+ config: dict,
110
+ ) -> tuple[bool, str]:
111
+ """
112
+ Parse ideas from the last message and save to JSON file.
113
+
114
+ Returns:
115
+ Tuple of (True, message) indicating the reply was generated.
116
+ """
117
+ if not messages:
118
+ return True, "No message to process."
119
+
120
+ last_message = messages[-1]
121
+ content = last_message.get("content", "")
122
+
123
+ # Parse ideas from formatted text
124
+ ideas = _parse_ideas_from_formatted_text(content)
125
+
126
+ if not ideas:
127
+ return True, "No ideas found in the message."
128
+
129
+ # Save to JSON file
130
+ timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
131
+ filepath = os.path.join(self._work_dir, f'ideas_{timestamp}.json')
132
+
133
+ try:
134
+ with open(filepath, 'w') as f:
135
+ json.dump(ideas, f, indent=2)
136
+ return True, f"\nIdeas saved in {filepath}\n"
137
+ except Exception as e:
138
+ return True, f"\nFailed to save ideas: {e}\n"
cmbagent/agents/hypothesis/idea_saver/idea_saver.yaml ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: "idea_saver"
2
+
3
+ instructions: |
4
+ Non-LLM agent that saves ideas to a JSON file.
5
+ Automatically parses ideas from the formatted message.
6
+
7
+ description: |
8
+ Idea saver agent (non-LLM). Parses ideas from the
9
+ idea_maker_response_formatter message and saves them to a JSON file.
10
+
11
+
cmbagent/agents/installer/__init__.py ADDED
File without changes
cmbagent/agents/installer/installer.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from cmbagent.base_agent import BaseAgent
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ class InstallerResponse(BaseModel):
7
+ install_command: str = Field(..., description="The bash command to install the package(s) using pip")
8
+
9
+ def format(self) -> str:
10
+ message = f"""
11
+ ```bash
12
+ {self.install_command}
13
+ ```
14
+ """
15
+ return message
16
+
17
+ class InstallerAgent(BaseAgent):
18
+
19
+ def __init__(self, llm_config=None, **kwargs):
20
+
21
+ agent_id = os.path.splitext(os.path.abspath(__file__))[0]
22
+
23
+ llm_config['config_list'][0]['response_format'] = InstallerResponse
24
+
25
+ super().__init__(llm_config=llm_config, agent_id=agent_id, **kwargs)
26
+
27
+
28
+ def set_agent(self,**kwargs):
29
+
30
+ super().set_assistant_agent(**kwargs)
31
+
32
+
33
+
34
+
35
+
36
+
37
+
38
+
cmbagent/agents/installer/installer.yaml ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: "installer"
2
+
3
+ instructions: |
4
+ You are the installer agent in the team.
5
+
6
+ You provide bash commands to install the necessary PyPi packages with pip.
7
+
8
+ Use always `python -m pip install <package>` to install the packages.
9
+
10
+ Never use sudo or conda to install the packages.
11
+
12
+ description: |
13
+ Installer agent, to help the team to install the necessary PyPi packages.