/ website / docs / guides / python-library.md
python-library.md
  1  ---
  2  sidebar_position: 5
  3  title: "Using Hermes as a Python Library"
  4  description: "Embed AIAgent in your own Python scripts, web apps, or automation pipelines — no CLI required"
  5  ---
  6  
  7  # Using Hermes as a Python Library
  8  
  9  Hermes isn't just a CLI tool. You can import `AIAgent` directly and use it programmatically in your own Python scripts, web applications, or automation pipelines. This guide shows you how.
 10  
 11  ---
 12  
 13  ## Installation
 14  
 15  Install Hermes directly from the repository:
 16  
 17  ```bash
 18  pip install git+https://github.com/NousResearch/hermes-agent.git
 19  ```
 20  
 21  Or with [uv](https://docs.astral.sh/uv/):
 22  
 23  ```bash
 24  uv pip install git+https://github.com/NousResearch/hermes-agent.git
 25  ```
 26  
 27  You can also pin it in your `requirements.txt`:
 28  
 29  ```text
 30  hermes-agent @ git+https://github.com/NousResearch/hermes-agent.git
 31  ```
 32  
 33  :::tip
 34  The same environment variables used by the CLI are required when using Hermes as a library. At minimum, set `OPENROUTER_API_KEY` (or `OPENAI_API_KEY` / `ANTHROPIC_API_KEY` if using direct provider access).
 35  :::
 36  
 37  ---
 38  
 39  ## Basic Usage
 40  
 41  The simplest way to use Hermes is the `chat()` method — pass a message, get a string back:
 42  
 43  ```python
 44  from run_agent import AIAgent
 45  
 46  agent = AIAgent(
 47      model="anthropic/claude-sonnet-4",
 48      quiet_mode=True,
 49  )
 50  response = agent.chat("What is the capital of France?")
 51  print(response)
 52  ```
 53  
 54  `chat()` handles the full conversation loop internally — tool calls, retries, everything — and returns just the final text response.
 55  
 56  :::warning
 57  Always set `quiet_mode=True` when embedding Hermes in your own code. Without it, the agent prints CLI spinners, progress indicators, and other terminal output that will clutter your application's output.
 58  :::
 59  
 60  ---
 61  
 62  ## Full Conversation Control
 63  
 64  For more control over the conversation, use `run_conversation()` directly. It returns a dictionary with the full response, message history, and metadata:
 65  
 66  ```python
 67  agent = AIAgent(
 68      model="anthropic/claude-sonnet-4",
 69      quiet_mode=True,
 70  )
 71  
 72  result = agent.run_conversation(
 73      user_message="Search for recent Python 3.13 features",
 74      task_id="my-task-1",
 75  )
 76  
 77  print(result["final_response"])
 78  print(f"Messages exchanged: {len(result['messages'])}")
 79  ```
 80  
 81  The returned dictionary contains:
 82  - **`final_response`** — The agent's final text reply
 83  - **`messages`** — The complete message history (system, user, assistant, tool calls)
 84  - **`task_id`** — The task identifier used for VM isolation
 85  
 86  You can also pass a custom system message that overrides the ephemeral system prompt for that call:
 87  
 88  ```python
 89  result = agent.run_conversation(
 90      user_message="Explain quicksort",
 91      system_message="You are a computer science tutor. Use simple analogies.",
 92  )
 93  ```
 94  
 95  ---
 96  
 97  ## Configuring Tools
 98  
 99  Control which toolsets the agent has access to using `enabled_toolsets` or `disabled_toolsets`:
100  
101  ```python
102  # Only enable web tools (browsing, search)
103  agent = AIAgent(
104      model="anthropic/claude-sonnet-4",
105      enabled_toolsets=["web"],
106      quiet_mode=True,
107  )
108  
109  # Enable everything except terminal access
110  agent = AIAgent(
111      model="anthropic/claude-sonnet-4",
112      disabled_toolsets=["terminal"],
113      quiet_mode=True,
114  )
115  ```
116  
117  :::tip
118  Use `enabled_toolsets` when you want a minimal, locked-down agent (e.g., only web search for a research bot). Use `disabled_toolsets` when you want most capabilities but need to restrict specific ones (e.g., no terminal access in a shared environment).
119  :::
120  
121  ---
122  
123  ## Multi-turn Conversations
124  
125  Maintain conversation state across multiple turns by passing the message history back in:
126  
127  ```python
128  agent = AIAgent(
129      model="anthropic/claude-sonnet-4",
130      quiet_mode=True,
131  )
132  
133  # First turn
134  result1 = agent.run_conversation("My name is Alice")
135  history = result1["messages"]
136  
137  # Second turn — agent remembers the context
138  result2 = agent.run_conversation(
139      "What's my name?",
140      conversation_history=history,
141  )
142  print(result2["final_response"])  # "Your name is Alice."
143  ```
144  
145  The `conversation_history` parameter accepts the `messages` list from a previous result. The agent copies it internally, so your original list is never mutated.
146  
147  ---
148  
149  ## Saving Trajectories
150  
151  Enable trajectory saving to capture conversations in ShareGPT format — useful for generating training data or debugging:
152  
153  ```python
154  agent = AIAgent(
155      model="anthropic/claude-sonnet-4",
156      save_trajectories=True,
157      quiet_mode=True,
158  )
159  
160  agent.chat("Write a Python function to sort a list")
161  # Saves to trajectory_samples.jsonl in ShareGPT format
162  ```
163  
164  Each conversation is appended as a single JSONL line, making it easy to collect datasets from automated runs.
165  
166  ---
167  
168  ## Custom System Prompts
169  
170  Use `ephemeral_system_prompt` to set a custom system prompt that guides the agent's behavior but is **not** saved to trajectory files (keeping your training data clean):
171  
172  ```python
173  agent = AIAgent(
174      model="anthropic/claude-sonnet-4",
175      ephemeral_system_prompt="You are a SQL expert. Only answer database questions.",
176      quiet_mode=True,
177  )
178  
179  response = agent.chat("How do I write a JOIN query?")
180  print(response)
181  ```
182  
183  This is ideal for building specialized agents — a code reviewer, a documentation writer, a SQL assistant — all using the same underlying tooling.
184  
185  ---
186  
187  ## Batch Processing
188  
189  For running many prompts in parallel, Hermes includes `batch_runner.py`. It manages concurrent `AIAgent` instances with proper resource isolation:
190  
191  ```bash
192  python batch_runner.py --input prompts.jsonl --output results.jsonl
193  ```
194  
195  Each prompt gets its own `task_id` and isolated environment. If you need custom batch logic, you can build your own using `AIAgent` directly:
196  
197  ```python
198  import concurrent.futures
199  from run_agent import AIAgent
200  
201  prompts = [
202      "Explain recursion",
203      "What is a hash table?",
204      "How does garbage collection work?",
205  ]
206  
207  def process_prompt(prompt):
208      # Create a fresh agent per task for thread safety
209      agent = AIAgent(
210          model="anthropic/claude-sonnet-4",
211          quiet_mode=True,
212          skip_memory=True,
213      )
214      return agent.chat(prompt)
215  
216  with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
217      results = list(executor.map(process_prompt, prompts))
218  
219  for prompt, result in zip(prompts, results):
220      print(f"Q: {prompt}\nA: {result}\n")
221  ```
222  
223  :::warning
224  Always create a **new `AIAgent` instance per thread or task**. The agent maintains internal state (conversation history, tool sessions, iteration counters) that is not thread-safe to share.
225  :::
226  
227  ---
228  
229  ## Integration Examples
230  
231  ### FastAPI Endpoint
232  
233  ```python
234  from fastapi import FastAPI
235  from pydantic import BaseModel
236  from run_agent import AIAgent
237  
238  app = FastAPI()
239  
240  class ChatRequest(BaseModel):
241      message: str
242      model: str = "anthropic/claude-sonnet-4"
243  
244  @app.post("/chat")
245  async def chat(request: ChatRequest):
246      agent = AIAgent(
247          model=request.model,
248          quiet_mode=True,
249          skip_context_files=True,
250          skip_memory=True,
251      )
252      response = agent.chat(request.message)
253      return {"response": response}
254  ```
255  
256  ### Discord Bot
257  
258  ```python
259  import discord
260  from run_agent import AIAgent
261  
262  client = discord.Client(intents=discord.Intents.default())
263  
264  @client.event
265  async def on_message(message):
266      if message.author == client.user:
267          return
268      if message.content.startswith("!hermes "):
269          query = message.content[8:]
270          agent = AIAgent(
271              model="anthropic/claude-sonnet-4",
272              quiet_mode=True,
273              skip_context_files=True,
274              skip_memory=True,
275              platform="discord",
276          )
277          response = agent.chat(query)
278          await message.channel.send(response[:2000])
279  
280  client.run("YOUR_DISCORD_TOKEN")
281  ```
282  
283  ### CI/CD Pipeline Step
284  
285  ```python
286  #!/usr/bin/env python3
287  """CI step: auto-review a PR diff."""
288  import subprocess
289  from run_agent import AIAgent
290  
291  diff = subprocess.check_output(["git", "diff", "main...HEAD"]).decode()
292  
293  agent = AIAgent(
294      model="anthropic/claude-sonnet-4",
295      quiet_mode=True,
296      skip_context_files=True,
297      skip_memory=True,
298      disabled_toolsets=["terminal", "browser"],
299  )
300  
301  review = agent.chat(
302      f"Review this PR diff for bugs, security issues, and style problems:\n\n{diff}"
303  )
304  print(review)
305  ```
306  
307  ---
308  
309  ## Key Constructor Parameters
310  
311  | Parameter | Type | Default | Description |
312  |-----------|------|---------|-------------|
313  | `model` | `str` | `"anthropic/claude-opus-4.6"` | Model in OpenRouter format |
314  | `quiet_mode` | `bool` | `False` | Suppress CLI output |
315  | `enabled_toolsets` | `List[str]` | `None` | Whitelist specific toolsets |
316  | `disabled_toolsets` | `List[str]` | `None` | Blacklist specific toolsets |
317  | `save_trajectories` | `bool` | `False` | Save conversations to JSONL |
318  | `ephemeral_system_prompt` | `str` | `None` | Custom system prompt (not saved to trajectories) |
319  | `max_iterations` | `int` | `90` | Max tool-calling iterations per conversation |
320  | `skip_context_files` | `bool` | `False` | Skip loading AGENTS.md files |
321  | `skip_memory` | `bool` | `False` | Disable persistent memory read/write |
322  | `api_key` | `str` | `None` | API key (falls back to env vars) |
323  | `base_url` | `str` | `None` | Custom API endpoint URL |
324  | `platform` | `str` | `None` | Platform hint (`"discord"`, `"telegram"`, etc.) |
325  
326  ---
327  
328  ## Important Notes
329  
330  :::tip
331  - Set **`skip_context_files=True`** if you don't want `AGENTS.md` files from the working directory loaded into the system prompt.
332  - Set **`skip_memory=True`** to prevent the agent from reading or writing persistent memory — recommended for stateless API endpoints.
333  - The `platform` parameter (e.g., `"discord"`, `"telegram"`) injects platform-specific formatting hints so the agent adapts its output style.
334  :::
335  
336  :::warning
337  - **Thread safety**: Create one `AIAgent` per thread or task. Never share an instance across concurrent calls.
338  - **Resource cleanup**: The agent automatically cleans up resources (terminal sessions, browser instances) when a conversation ends. If you're running in a long-lived process, ensure each conversation completes normally.
339  - **Iteration limits**: The default `max_iterations=90` is generous. For simple Q&A use cases, consider lowering it (e.g., `max_iterations=10`) to prevent runaway tool-calling loops and control costs.
340  :::