akseljoonas HF Staff commited on
Commit
275319a
Β·
1 Parent(s): fdc5e27

Add ml-agent CLI entry point and update README

Browse files

- Add [project.scripts] entry point so ml-agent can be installed globally
via uv tool install and run from any directory
- Extract cli() function from __main__ block for the entry point
- Fix load_dotenv to also check project root .env when running from
other directories
- Rewrite README: installation, usage, architecture diagrams, events
list to match current codebase

Files changed (4) hide show
  1. README.md +131 -114
  2. agent/config.py +8 -2
  3. agent/main.py +6 -1
  4. pyproject.toml +3 -0
README.md CHANGED
@@ -21,38 +21,60 @@ hf_oauth_scopes:
21
 
22
  An MLE agent CLI with MCP (Model Context Protocol) integration and built-in tool support.
23
 
24
-
25
  ## Quick Start
26
 
27
  ### Installation
28
 
29
  ```bash
30
- # Clone the repository
31
  git clone git@github.com:huggingface/hf_agent.git
32
  cd hf_agent
 
 
33
  ```
34
 
35
- #### Install recommended dependencies
 
36
  ```bash
37
- uv sync --extra agent # or uv sync --extra all
 
 
 
38
  ```
39
 
40
- ### Interactive CLI
 
 
41
 
42
  ```bash
43
- uv run python -m agent.main
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  ```
45
- This starts an interactive chat session with the agent. Type your messages and the agent will respond, using tools as needed.
46
 
47
- The agent will automatically discover and register all tools from configured MCP servers.
 
 
 
 
 
 
48
 
 
49
 
50
- ### Env Setup
51
  ```bash
52
- ANTHROPIC_API_KEY=<one-key-to-rule-them-all>
53
- HF_TOKEN=<hf-token-to-access-the-hub>
54
- GITHUB_TOKEN=<gh-pat-key-for-not-reinventing-the-wheel>
55
- HF_NAMESPACE=<hf-namespace-to-use>
56
  ```
57
 
58
  ## Architecture
@@ -63,60 +85,68 @@ HF_NAMESPACE=<hf-namespace-to-use>
63
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
64
  β”‚ User/CLI β”‚
65
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
66
- β”‚ User request β”‚ Events
67
- ↓ ↑
68
- submission_queue event_queue
69
- β”‚ β”‚
70
- ↓ β”‚
71
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
72
- β”‚ submission_loop (agent_loop.py) β”‚ β”‚
73
- β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚
74
- β”‚ β”‚ 1. Receive Operation from queue β”‚ β”‚ β”‚
75
- β”‚ β”‚ 2. Route to Handler (run_agent/compact/...) β”‚ β”‚ β”‚
76
- β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
77
- β”‚ ↓ β”‚ β”‚
78
- β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚
79
- β”‚ β”‚ Handlers.run_agent() β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
80
- β”‚ β”‚ β”‚ β”‚ Emit β”‚
81
- β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ Events β”‚
82
- β”‚ β”‚ β”‚ Agentic Loop (max 10 iterations) β”‚ β”‚ β”‚ β”‚
83
- β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
84
- β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ β”‚
85
- β”‚ β”‚ β”‚ β”‚ Session β”‚ β”‚ β”‚ β”‚ β”‚
86
- β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ β”‚ β”‚
87
- β”‚ β”‚ β”‚ β”‚ β”‚ ContextManager β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
88
- β”‚ β”‚ β”‚ β”‚ β”‚ β€’ Message history β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
89
- β”‚ β”‚ β”‚ β”‚ β”‚ (litellm.Message[]) β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
90
- β”‚ β”‚ β”‚ β”‚ β”‚ β€’ Auto-compaction (180k) β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
91
- β”‚ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”‚
92
- β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
93
- β”‚ β”‚ οΏ½οΏ½οΏ½ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ β”‚ β”‚
94
- β”‚ β”‚ β”‚ β”‚ β”‚ ToolRouter β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
95
- β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€ explore_hf_docs β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
96
- β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€ fetch_hf_docs β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
97
- β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€ find_hf_api β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
98
- β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€ plan_tool β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
99
- β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€ hf_jobs* β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
100
- β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€ hf_private_repos* β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
101
- β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€ github_* (3 tools) β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
102
- β”‚ β”‚ β”‚ β”‚ β”‚ └─ MCP tools (e.g., β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
103
- β”‚ β”‚ β”‚ β”‚ β”‚ model_search, etc.) β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
104
- β”‚ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”‚
105
- β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚
106
- β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
107
- β”‚ β”‚ β”‚ Loop: β”‚ β”‚ β”‚ β”‚
108
- β”‚ β”‚ β”‚ 1. LLM call (litellm.acompletion) β”‚ β”‚ β”‚ β”‚
109
- β”‚ β”‚ β”‚ ↓ β”‚ β”‚ β”‚ β”‚
110
- β”‚ β”‚ β”‚ 2. Parse tool_calls[] β”‚ β”‚ β”‚ β”‚
111
- β”‚ β”‚ β”‚ ↓ β”‚ β”‚ β”‚ β”‚
112
- β”‚ β”‚ β”‚ 3. Execute via ToolRouter β”‚ β”‚ β”‚ β”‚
113
- β”‚ β”‚ β”‚ ↓ β”‚ β”‚ β”‚ β”‚
114
- β”‚ β”‚ β”‚ 4. Add results to ContextManager β”‚ β”‚ β”‚ β”‚
115
- β”‚ β”‚ β”‚ ↓ β”‚ β”‚ β”‚ β”‚
116
- β”‚ β”‚ β”‚ 5. Repeat if tool_calls exist β”‚ β”‚ β”‚ β”‚
117
- β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚
118
- β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
119
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
 
 
 
 
 
 
 
 
120
  ```
121
 
122
  ### Agentic Loop Flow
@@ -126,61 +156,48 @@ User Message
126
  ↓
127
  [Add to ContextManager]
128
  ↓
129
- ╔═══════════════════════════════════════╗
130
- β•‘ Iteration Loop (max 10) β•‘
131
- β•‘ β•‘
132
- β•‘ Get messages + tool specs β•‘
133
- β•‘ ↓ β•‘
134
- β•‘ litellm.acompletion() β•‘
135
- β•‘ ↓ β•‘
136
- β•‘ Has tool_calls? ──No──> Done β•‘
137
- β•‘ β”‚ β•‘
138
- β•‘ Yes β•‘
139
- β•‘ ↓ β•‘
140
- β•‘ Add assistant msg (with tool_calls) β•‘
141
- β•‘ ↓ β•‘
142
- β•‘ For each tool_call: β•‘
143
- β•‘ β€’ ToolRouter.execute_tool() β•‘
144
- β•‘ β€’ Add result to ContextManager β•‘
145
- β•‘ ↓ β•‘
146
- β•‘ Continue loop ─────────────────┐ β•‘
147
- β•‘ ↑ β”‚ β•‘
148
- β•šβ•β•β•β•β•β•β•β•β•β•§β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•§β•β•β•β•β•β•
 
 
 
 
 
 
149
  ```
150
 
151
- ## Project Structure
152
-
153
- ```
154
- agent/
155
- β”œβ”€β”€ config.py # Configuration models
156
- β”œβ”€β”€ main.py # Interactive CLI entry point
157
- β”œβ”€β”€ prompts/
158
- β”‚ └── system_prompt.yaml # Agent behavior and personality
159
- β”œβ”€β”€ context_manager/
160
- β”‚ └── manager.py # Message history & auto-compaction
161
- └── core/
162
- β”œβ”€β”€ agent_loop.py # Main agent loop and handlers
163
- β”œβ”€β”€ session.py # Session management
164
- β”œβ”€β”€ mcp_client.py # MCP SDK integration
165
- └── tools.py # ToolRouter and built-in tools
166
-
167
- configs/
168
- └── main_agent_config.json # Model and MCP server configuration
169
-
170
- tests/ # Integration and unit tests
171
- eval/ # Evaluation suite (see eval/README.md)
172
- ```
173
-
174
-
175
  ## Events
176
 
177
  The agent emits the following events via `event_queue`:
178
 
179
  - `processing` - Starting to process user input
180
- - `assistant_message` - LLM response text
 
 
 
181
  - `tool_call` - Tool being called with arguments
182
  - `tool_output` - Tool execution result
183
- - `approval_request` - Requesting user approval for sensitive operations
 
 
184
  - `turn_complete` - Agent finished processing
185
  - `error` - Error occurred during processing
186
  - `interrupted` - Agent was interrupted
@@ -232,4 +249,4 @@ Edit `configs/main_agent_config.json`:
232
  }
233
  ```
234
 
235
- Note: Environment variables like `${YOUR_TOKEN}` are auto-substituted from `.env`.
 
21
 
22
  An MLE agent CLI with MCP (Model Context Protocol) integration and built-in tool support.
23
 
 
24
  ## Quick Start
25
 
26
  ### Installation
27
 
28
  ```bash
 
29
  git clone git@github.com:huggingface/hf_agent.git
30
  cd hf_agent
31
+ uv sync
32
+ uv tool install -e .
33
  ```
34
 
35
+ Create a `.env` file in the project root (or export these in your shell):
36
+
37
  ```bash
38
+ ANTHROPIC_API_KEY=<your-anthropic-api-key> # if using anthropic models
39
+ HF_TOKEN=<your-hugging-face-token>
40
+ GITHUB_TOKEN=<github-personal-access-token>
41
+ HF_NAMESPACE=<your-hf-namespace>
42
  ```
43
 
44
+ If no `HF_TOKEN` is set, the CLI will prompt you to paste one on first launch.
45
+
46
+ #### That's it. Now `ml-agent` works from any directory:
47
 
48
  ```bash
49
+ ml-agent
50
+ ```
51
+
52
+ ### Usage
53
+
54
+ **Interactive mode** (start a chat session):
55
+
56
+ ```bash
57
+ ml-agent
58
+ ```
59
+
60
+ **Headless mode** (single prompt, auto-approve):
61
+
62
+ ```bash
63
+ ml-agent "fine-tune llama on my dataset"
64
  ```
 
65
 
66
+ **Options:**
67
+
68
+ ```bash
69
+ ml-agent --model anthropic/claude-opus-4-6 "your prompt"
70
+ ml-agent --max-iterations 100 "your prompt"
71
+ ml-agent --no-stream "your prompt"
72
+ ```
73
 
74
+ If you haven't installed globally, you can still run from the project directory:
75
 
 
76
  ```bash
77
+ uv run python -m agent.main
 
 
 
78
  ```
79
 
80
  ## Architecture
 
85
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
86
  β”‚ User/CLI β”‚
87
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
88
+ β”‚ Operations β”‚ Events
89
+ ↓ (user_input, exec_approval, ↑
90
+ submission_queue interrupt, compact, ...) event_queue
91
+ β”‚ β”‚
92
+ ↓ β”‚
93
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
94
+ β”‚ submission_loop (agent_loop.py) β”‚ β”‚
95
+ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚
96
+ β”‚ β”‚ 1. Receive Operation from queue β”‚ β”‚ β”‚
97
+ β”‚ β”‚ 2. Route to handler (run_agent/compact/...) β”‚ β”‚ β”‚
98
+ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
99
+ β”‚ ↓ β”‚ β”‚
100
+ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚
101
+ β”‚ β”‚ Handlers.run_agent() β”‚ β”œβ”€β”€β”€
102
+ β”‚ β”‚ β”‚ β”‚ β”‚
103
+ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚
104
+ β”‚ β”‚ β”‚ Agentic Loop (max 300 iterations) β”‚ β”‚ β”‚ β”‚
105
+ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
106
+ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ β”‚
107
+ β”‚ β”‚ β”‚ β”‚ Session β”‚ β”‚ β”‚ β”‚ β”‚
108
+ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ β”‚ β”‚
109
+ β”‚ β”‚ β”‚ β”‚ β”‚ ContextManager β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
110
+ β”‚ β”‚ β”‚ β”‚ β”‚ β€’ Message history β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
111
+ β”‚ β”‚ β”‚ β”‚ β”‚ (litellm.Message[]) β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
112
+ β”‚ β”‚ β”‚ β”‚ β”‚ β€’ Auto-compaction (170k) β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
113
+ β”‚ β”‚ β”‚ β”‚ β”‚ β€’ Session upload to HF β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
114
+ β”‚ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”‚
115
+ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
116
+ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ β”‚ β”‚
117
+ β”‚ β”‚ β”‚ β”‚ β”‚ ToolRouter β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
118
+ β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€ HF docs & research β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
119
+ β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€ HF repos, datasets, β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
120
+ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ jobs, papers β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
121
+ β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€ GitHub code search β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
122
+ β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€ Sandbox & local tools β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
123
+ β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€ Planning β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
124
+ β”‚ β”‚ β”‚ β”‚ β”‚ └─ MCP server tools β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
125
+ β”‚ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”‚
126
+ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚
127
+ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
128
+ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ β”‚
129
+ β”‚ β”‚ β”‚ β”‚ Doom Loop Detector β”‚ β”‚ β”‚ β”‚ β”‚
130
+ β”‚ β”‚ β”‚ β”‚ β€’ Detects repeated tool patterns β”‚ β”‚ β”‚ β”‚ β”‚
131
+ β”‚ β”‚ β”‚ β”‚ β€’ Injects corrective prompts β”‚ β”‚ β”‚ β”‚ β”‚
132
+ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚
133
+ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
134
+ β”‚ β”‚ β”‚ Loop: β”‚ β”‚ β”‚ β”‚
135
+ β”‚ β”‚ β”‚ 1. LLM call (litellm.acompletion) β”‚ β”‚ β”‚ β”‚
136
+ β”‚ β”‚ β”‚ ↓ β”‚ β”‚ β”‚ β”‚
137
+ β”‚ β”‚ β”‚ 2. Parse tool_calls[] β”‚ β”‚ β”‚ β”‚
138
+ β”‚ β”‚ β”‚ ↓ β”‚ β”‚ β”‚ β”‚
139
+ β”‚ β”‚ β”‚ 3. Approval check β”‚ β”‚ β”‚ β”‚
140
+ β”‚ β”‚ β”‚ (jobs, sandbox, destructive ops) β”‚ β”‚ β”‚ β”‚
141
+ β”‚ β”‚ β”‚ ↓ β”‚ β”‚ β”‚ β”‚
142
+ β”‚ β”‚ β”‚ 4. Execute via ToolRouter β”‚ β”‚ β”‚ β”‚
143
+ β”‚ β”‚ β”‚ ↓ β”‚ β”‚ β”‚ β”‚
144
+ β”‚ β”‚ β”‚ 5. Add results to ContextManager β”‚ β”‚ β”‚ β”‚
145
+ β”‚ β”‚ β”‚ ↓ β”‚ β”‚ β”‚ β”‚
146
+ β”‚ β”‚ β”‚ 6. Repeat if tool_calls exist β”‚ β”‚ β”‚ β”‚
147
+ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚
148
+ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
149
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”˜
150
  ```
151
 
152
  ### Agentic Loop Flow
 
156
  ↓
157
  [Add to ContextManager]
158
  ↓
159
+ ╔═══════════════════════════════════════════╗
160
+ β•‘ Iteration Loop (max 300) β•‘
161
+ β•‘ β•‘
162
+ β•‘ Get messages + tool specs β•‘
163
+ β•‘ ↓ β•‘
164
+ β•‘ litellm.acompletion() β•‘
165
+ β•‘ ↓ β•‘
166
+ β•‘ Has tool_calls? ──No──> Done β•‘
167
+ β•‘ β”‚ β•‘
168
+ β•‘ Yes β•‘
169
+ β•‘ ↓ β•‘
170
+ β•‘ Add assistant msg (with tool_calls) β•‘
171
+ β•‘ ↓ β•‘
172
+ β•‘ Doom loop check β•‘
173
+ β•‘ ↓ β•‘
174
+ β•‘ For each tool_call: β•‘
175
+ β•‘ β€’ Needs approval? ──Yes──> Wait for β•‘
176
+ β•‘ β”‚ user confirm β•‘
177
+ β•‘ No β•‘
178
+ β•‘ ↓ β•‘
179
+ β•‘ β€’ ToolRouter.execute_tool() β•‘
180
+ β•‘ β€’ Add result to ContextManager β•‘
181
+ β•‘ ↓ β•‘
182
+ β•‘ Continue loop ─────────────────┐ β•‘
183
+ β•‘ ↑ β”‚ β•‘
184
+ β•šβ•β•β•β•β•β•β•β•β•β•§β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•§β•β•β•β•β•β•β•β•β•β•
185
  ```
186
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
  ## Events
188
 
189
  The agent emits the following events via `event_queue`:
190
 
191
  - `processing` - Starting to process user input
192
+ - `ready` - Agent is ready for input
193
+ - `assistant_chunk` - Streaming token chunk
194
+ - `assistant_message` - Complete LLM response text
195
+ - `assistant_stream_end` - Token stream finished
196
  - `tool_call` - Tool being called with arguments
197
  - `tool_output` - Tool execution result
198
+ - `tool_log` - Informational tool log message
199
+ - `tool_state_change` - Tool execution state transition
200
+ - `approval_required` - Requesting user approval for sensitive operations
201
  - `turn_complete` - Agent finished processing
202
  - `error` - Error occurred during processing
203
  - `interrupted` - Agent was interrupted
 
249
  }
250
  ```
251
 
252
+ Note: Environment variables like `${YOUR_TOKEN}` are auto-substituted from `.env`.
agent/config.py CHANGED
@@ -1,9 +1,13 @@
1
  import json
2
  import os
3
  import re
 
4
  from typing import Any, Union
5
 
6
  from dotenv import load_dotenv
 
 
 
7
  from fastmcp.mcp_config import (
8
  RemoteMCPServer,
9
  StdioMCPServer,
@@ -74,8 +78,10 @@ def load_config(config_path: str = "config.json") -> Config:
74
  Use ${VAR_NAME} in your JSON for any secret.
75
  Automatically loads from .env file.
76
  """
77
- # Load environment variables from .env file
78
- load_dotenv()
 
 
79
 
80
  with open(config_path, "r") as f:
81
  raw_config = json.load(f)
 
1
  import json
2
  import os
3
  import re
4
+ from pathlib import Path
5
  from typing import Any, Union
6
 
7
  from dotenv import load_dotenv
8
+
9
+ # Project root: two levels up from this file (agent/config.py -> project root)
10
+ _PROJECT_ROOT = Path(__file__).resolve().parent.parent
11
  from fastmcp.mcp_config import (
12
  RemoteMCPServer,
13
  StdioMCPServer,
 
78
  Use ${VAR_NAME} in your JSON for any secret.
79
  Automatically loads from .env file.
80
  """
81
+ # Load .env from project root first (so it works from any directory),
82
+ # then CWD .env can override if present
83
+ load_dotenv(_PROJECT_ROOT / ".env")
84
+ load_dotenv(override=False)
85
 
86
  with open(config_path, "r") as f:
87
  raw_config = json.load(f)
agent/main.py CHANGED
@@ -1026,7 +1026,8 @@ async def headless_main(
1026
  await tool_router.__aexit__(None, None, None)
1027
 
1028
 
1029
- if __name__ == "__main__":
 
1030
  import logging as _logging
1031
  import warnings
1032
  # Suppress aiohttp "Unclosed client session" noise during event loop teardown
@@ -1053,3 +1054,7 @@ if __name__ == "__main__":
1053
  asyncio.run(main())
1054
  except KeyboardInterrupt:
1055
  print("\n\nGoodbye!")
 
 
 
 
 
1026
  await tool_router.__aexit__(None, None, None)
1027
 
1028
 
1029
+ def cli():
1030
+ """Entry point for the ml-agent CLI command."""
1031
  import logging as _logging
1032
  import warnings
1033
  # Suppress aiohttp "Unclosed client session" noise during event loop teardown
 
1054
  asyncio.run(main())
1055
  except KeyboardInterrupt:
1056
  print("\n\nGoodbye!")
1057
+
1058
+
1059
+ if __name__ == "__main__":
1060
+ cli()
pyproject.toml CHANGED
@@ -47,6 +47,9 @@ all = [
47
  "hf-agent[eval,dev]",
48
  ]
49
 
 
 
 
50
  [build-system]
51
  requires = ["setuptools>=64"]
52
  build-backend = "setuptools.build_meta"
 
47
  "hf-agent[eval,dev]",
48
  ]
49
 
50
+ [project.scripts]
51
+ ml-agent = "agent.main:cli"
52
+
53
  [build-system]
54
  requires = ["setuptools>=64"]
55
  build-backend = "setuptools.build_meta"