From bc0e1f10bd91571e77b7bfeba964a1e9aa4f396d Mon Sep 17 00:00:00 2001 From: Brian Grinstead Date: Mon, 6 Oct 2025 16:33:44 -0700 Subject: [PATCH 1/3] example using browser-use without submodule --- .gitignore | 3 + agents/browser-use-muzzle/Dockerfile | 43 +++++++++ agents/browser-use-muzzle/README.md | 41 +++++++++ .../browser-use-muzzle/browser_use_server.py | 89 +++++++++++++++++++ 4 files changed, 176 insertions(+) create mode 100644 agents/browser-use-muzzle/Dockerfile create mode 100644 agents/browser-use-muzzle/README.md create mode 100644 agents/browser-use-muzzle/browser_use_server.py diff --git a/.gitignore b/.gitignore index 81d4580..99fdcba 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ __pycache__/ # Local config files *.local.* +# Video recordings +recordings/* + # C extensions *.so diff --git a/agents/browser-use-muzzle/Dockerfile b/agents/browser-use-muzzle/Dockerfile new file mode 100644 index 0000000..f49156b --- /dev/null +++ b/agents/browser-use-muzzle/Dockerfile @@ -0,0 +1,43 @@ +# Use an official Python runtime as a parent image +FROM python:3.12-slim + +# Install system dependencies +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + chromium \ + fonts-unifont \ + fonts-liberation \ + fonts-dejavu-core \ + fonts-freefont-ttf \ + fonts-noto-core \ + ffmpeg \ + ca-certificates \ + curl \ + && rm -rf /var/lib/apt/lists/* \ + && ln -s /usr/bin/chromium /usr/bin/chromium-browser + +# Install the zoo certificate +RUN curl -o /usr/local/share/ca-certificates/zoo-root.crt \ + https://raw.githubusercontent.com/bgrins/the_zoo/refs/heads/main/core/caddy/root.crt \ + && update-ca-certificates + +# Set environment variables for Chromium +ENV CHROME_PATH=/usr/bin/chromium-browser + +# Install browser-use and video dependencies, plus FastAPI and uvicorn +RUN pip install --no-cache-dir "browser-use[video]" fastapi uvicorn + +# Create a working directory +WORKDIR /app + +# Copy your script into the container +COPY browser_use_server.py . + +# Create directory for recordings +RUN mkdir -p /app/tmp/recordings + +# Expose API port +EXPOSE 8000 + +# Set the entrypoint to run your script +CMD ["python", "browser_use_server.py"] diff --git a/agents/browser-use-muzzle/README.md b/agents/browser-use-muzzle/README.md new file mode 100644 index 0000000..050b978 --- /dev/null +++ b/agents/browser-use-muzzle/README.md @@ -0,0 +1,41 @@ +# Browser Use Muzzle Agent + +## Build and run +```bash +docker build -t browser-use-muzzle agents/browser-use-muzzle && +docker run -e OPENAI_API_KEY=$OPENAI_API_KEY \ + -e OPENAI_MODEL=gpt-4o-mini \ + -v $PWD/recordings:/app/tmp/recordings \ + -p 8000:8000 \ + --add-host=host.docker.internal:host-gateway \ + browser-use-muzzle +``` + +## API Usage + +### Health Check +```bash +curl http://localhost:8000/health +``` + +### Execute Task +```bash +curl -X POST http://localhost:8000/task \ + -H "Content-Type: application/json" \ + -d '{"task": "Go to example.zoo & summarize", "max_steps": 10}' +``` + +```bash +curl -X POST http://localhost:8000/task \ + -H "Content-Type: application/json" \ + -d '{"task": "Summarize this page", "max_steps": 10, "starting_url": "https://status.zoo"}' +``` + +"starting_url": "https://example.zoo" +## Environment Variables +- `OPENAI_API_KEY` (required): Your OpenAI API key +- `OPENAI_MODEL` (optional): OpenAI model to use (default: `gpt-4o-mini`) + +## Notes +- Proxy support: Automatically uses proxy at `host.docker.internal:3128` if available +- Recordings are saved to the mounted volume at `./recordings` \ No newline at end of file diff --git a/agents/browser-use-muzzle/browser_use_server.py b/agents/browser-use-muzzle/browser_use_server.py new file mode 100644 index 0000000..b618a64 --- /dev/null +++ b/agents/browser-use-muzzle/browser_use_server.py @@ -0,0 +1,89 @@ +import asyncio +import os +import socket +import time +from pathlib import Path +from typing import Optional + +from fastapi import FastAPI, HTTPException +from pydantic import BaseModel +from browser_use import Agent, Browser, ChatOpenAI +from browser_use.browser import ProxySettings +import uvicorn + +# NOTE: To use this example, install imageio[ffmpeg], e.g. with uv pip install "browser-use[video]" + +app = FastAPI() + +class TaskRequest(BaseModel): + task: str + max_steps: Optional[int] = 10 + starting_url: Optional[str] = None + +def check_proxy_available(host: str, port: int) -> bool: + """Check if proxy is available before attempting to use it.""" + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(2) + result = sock.connect_ex((host, port)) + sock.close() + return result == 0 + except Exception as e: + print(f"Error checking proxy: {e}") + return False + +async def run_agent_task(task: str, max_steps: int = 10, starting_url: Optional[str] = None): + """Run a browser-use agent task with optional proxy.""" + proxy_settings = None + proxy_host = 'host.docker.internal' + proxy_port = 3128 + + if check_proxy_available(proxy_host, proxy_port): + print(f"Proxy available at {proxy_host}:{proxy_port}") + proxy_settings = ProxySettings(server=f'http://{proxy_host}:{proxy_port}') + else: + raise RuntimeError(f"Proxy not available at {proxy_host}:{proxy_port}") + + browser_session = Browser( + record_video_dir=Path('./tmp/recordings'), + proxy=proxy_settings, + args=['--ignore-certificate-errors'] + ) + + # Build initial actions if starting URL is provided + initial_actions = None + if starting_url: + initial_actions = [{'go_to_url': {'url': starting_url}}] + + agent = Agent( + task=task, + llm=ChatOpenAI( + model=os.environ.get('OPENAI_MODEL', 'gpt-4o-mini'), + api_key=os.environ.get('OPENAI_API_KEY') + ), + browser_session=browser_session, + allowed_domains=['*.zoo'], + initial_actions=initial_actions, + ) + + result = await agent.run(max_steps=max_steps) + + print('Agent run finished. Check the ./tmp/recordings directory for the video.') + return result + +@app.post("/task") +async def execute_task(request: TaskRequest): + """Execute a browser automation task.""" + try: + result = await run_agent_task(request.task, request.max_steps, request.starting_url) + return {"status": "success", "result": str(result)} + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + +@app.get("/health") +async def health(): + """Health check endpoint.""" + return {"status": "healthy"} + +if __name__ == '__main__': + uvicorn.run(app, host="0.0.0.0", port=8000) \ No newline at end of file From 3f36bbc539f12c0fb725deac0822880ce54d145c Mon Sep 17 00:00:00 2001 From: Brian Grinstead Date: Wed, 8 Oct 2025 14:18:04 -0700 Subject: [PATCH 2/3] allow passing a named agent in the new format (new docker process for a shared instance via compose --- agents/browser-use-muzzle/Dockerfile | 14 +-- agents/browser-use-muzzle/README.md | 60 ++++++---- .../browser-use-muzzle/browser_use_server.py | 89 -------------- agents/browser-use-muzzle/main.py | 113 ++++++++++++++++++ docker-compose.yml | 13 ++ muzzle/experiment.py | 99 ++++++++++++++- muzzle/orchestrator.py | 11 +- muzzle/utils/configs.py | 10 +- run.py | 15 ++- 9 files changed, 285 insertions(+), 139 deletions(-) delete mode 100644 agents/browser-use-muzzle/browser_use_server.py create mode 100644 agents/browser-use-muzzle/main.py create mode 100644 docker-compose.yml diff --git a/agents/browser-use-muzzle/Dockerfile b/agents/browser-use-muzzle/Dockerfile index f49156b..16b2695 100644 --- a/agents/browser-use-muzzle/Dockerfile +++ b/agents/browser-use-muzzle/Dockerfile @@ -24,20 +24,16 @@ RUN curl -o /usr/local/share/ca-certificates/zoo-root.crt \ # Set environment variables for Chromium ENV CHROME_PATH=/usr/bin/chromium-browser -# Install browser-use and video dependencies, plus FastAPI and uvicorn -RUN pip install --no-cache-dir "browser-use[video]" fastapi uvicorn +RUN pip install --no-cache-dir "browser-use[video]==0.7.10" # Create a working directory WORKDIR /app -# Copy your script into the container -COPY browser_use_server.py . +# Copy worker script into the container +COPY main.py . # Create directory for recordings RUN mkdir -p /app/tmp/recordings -# Expose API port -EXPOSE 8000 - -# Set the entrypoint to run your script -CMD ["python", "browser_use_server.py"] +# Keep container running (users will docker exec to run tasks) +CMD ["tail", "-f", "/dev/null"] diff --git a/agents/browser-use-muzzle/README.md b/agents/browser-use-muzzle/README.md index 050b978..75282c1 100644 --- a/agents/browser-use-muzzle/README.md +++ b/agents/browser-use-muzzle/README.md @@ -1,41 +1,57 @@ # Browser Use Muzzle Agent -## Build and run +A containerized browser automation worker using [browser-use](https://github.com/browser-use/browser-use) for AI-powered web tasks. + +## Setup + +### Start the container ```bash -docker build -t browser-use-muzzle agents/browser-use-muzzle && -docker run -e OPENAI_API_KEY=$OPENAI_API_KEY \ - -e OPENAI_MODEL=gpt-4o-mini \ - -v $PWD/recordings:/app/tmp/recordings \ - -p 8000:8000 \ - --add-host=host.docker.internal:host-gateway \ - browser-use-muzzle +docker-compose up -d browser-use ``` -## API Usage +This starts a persistent container with browser-use installed and ready to execute tasks. + +## Usage -### Health Check +Execute browser automation tasks by running the worker script inside the container: + +### Basic task ```bash -curl http://localhost:8000/health +docker-compose exec browser-use python main.py \ + --task "Go to example.zoo and summarize the contents" ``` -### Execute Task +### With starting URL ```bash -curl -X POST http://localhost:8000/task \ - -H "Content-Type: application/json" \ - -d '{"task": "Go to example.zoo & summarize", "max_steps": 10}' +docker-compose exec browser-use python main.py \ + --task "Summarize this page" \ + --starting-url "https://status.zoo" ``` +### Custom max steps ```bash -curl -X POST http://localhost:8000/task \ - -H "Content-Type: application/json" \ - -d '{"task": "Summarize this page", "max_steps": 10, "starting_url": "https://status.zoo"}' +docker-compose exec browser-use python main.py \ + --task "Navigate to example.zoo, find the contact page, and extract email addresses" \ + --max-steps 15 ``` -"starting_url": "https://example.zoo" ## Environment Variables + +Set these in the `.env` file or manually in your environment + - `OPENAI_API_KEY` (required): Your OpenAI API key - `OPENAI_MODEL` (optional): OpenAI model to use (default: `gpt-4o-mini`) -## Notes -- Proxy support: Automatically uses proxy at `host.docker.internal:3128` if available -- Recordings are saved to the mounted volume at `./recordings` \ No newline at end of file +## Features + +- **Proxy support**: Requires proxy at `host.docker.internal:3128` (fails if unavailable) +- **Domain restrictions**: Only allows navigation to `*.zoo` domains +- **Video recordings**: Saved to `./recordings/` directory +- **SSL bypass**: Ignores certificate errors for self-signed certs + +## Architecture + +- Container runs continuously with `tail -f /dev/null` +- Each task execution is isolated (separate `docker exec` invocation) +- No shared state between task runs +- Worker script exits after each task completion \ No newline at end of file diff --git a/agents/browser-use-muzzle/browser_use_server.py b/agents/browser-use-muzzle/browser_use_server.py deleted file mode 100644 index b618a64..0000000 --- a/agents/browser-use-muzzle/browser_use_server.py +++ /dev/null @@ -1,89 +0,0 @@ -import asyncio -import os -import socket -import time -from pathlib import Path -from typing import Optional - -from fastapi import FastAPI, HTTPException -from pydantic import BaseModel -from browser_use import Agent, Browser, ChatOpenAI -from browser_use.browser import ProxySettings -import uvicorn - -# NOTE: To use this example, install imageio[ffmpeg], e.g. with uv pip install "browser-use[video]" - -app = FastAPI() - -class TaskRequest(BaseModel): - task: str - max_steps: Optional[int] = 10 - starting_url: Optional[str] = None - -def check_proxy_available(host: str, port: int) -> bool: - """Check if proxy is available before attempting to use it.""" - try: - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.settimeout(2) - result = sock.connect_ex((host, port)) - sock.close() - return result == 0 - except Exception as e: - print(f"Error checking proxy: {e}") - return False - -async def run_agent_task(task: str, max_steps: int = 10, starting_url: Optional[str] = None): - """Run a browser-use agent task with optional proxy.""" - proxy_settings = None - proxy_host = 'host.docker.internal' - proxy_port = 3128 - - if check_proxy_available(proxy_host, proxy_port): - print(f"Proxy available at {proxy_host}:{proxy_port}") - proxy_settings = ProxySettings(server=f'http://{proxy_host}:{proxy_port}') - else: - raise RuntimeError(f"Proxy not available at {proxy_host}:{proxy_port}") - - browser_session = Browser( - record_video_dir=Path('./tmp/recordings'), - proxy=proxy_settings, - args=['--ignore-certificate-errors'] - ) - - # Build initial actions if starting URL is provided - initial_actions = None - if starting_url: - initial_actions = [{'go_to_url': {'url': starting_url}}] - - agent = Agent( - task=task, - llm=ChatOpenAI( - model=os.environ.get('OPENAI_MODEL', 'gpt-4o-mini'), - api_key=os.environ.get('OPENAI_API_KEY') - ), - browser_session=browser_session, - allowed_domains=['*.zoo'], - initial_actions=initial_actions, - ) - - result = await agent.run(max_steps=max_steps) - - print('Agent run finished. Check the ./tmp/recordings directory for the video.') - return result - -@app.post("/task") -async def execute_task(request: TaskRequest): - """Execute a browser automation task.""" - try: - result = await run_agent_task(request.task, request.max_steps, request.starting_url) - return {"status": "success", "result": str(result)} - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - -@app.get("/health") -async def health(): - """Health check endpoint.""" - return {"status": "healthy"} - -if __name__ == '__main__': - uvicorn.run(app, host="0.0.0.0", port=8000) \ No newline at end of file diff --git a/agents/browser-use-muzzle/main.py b/agents/browser-use-muzzle/main.py new file mode 100644 index 0000000..c542585 --- /dev/null +++ b/agents/browser-use-muzzle/main.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +""" +Browser-use worker - executes a single browser automation task and exits. +Usage: python browser_use_worker.py --task "Your task here" [--starting-url URL] [--max-steps N] +""" + +import argparse +import asyncio +import os +import socket +import sys +from pathlib import Path +from typing import Optional + +from browser_use import Agent, Browser, ChatOpenAI +from browser_use.browser import ProxySettings + + +def check_proxy_available(host: str, port: int) -> bool: + """Check if proxy is available before attempting to use it.""" + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(2) + result = sock.connect_ex((host, port)) + sock.close() + return result == 0 + except Exception as e: + print(f"Error checking proxy: {e}", file=sys.stderr) + return False + + +async def run_task(task: str, max_steps: int = 10, starting_url: Optional[str] = None): + """Run a browser-use agent task with proxy.""" + proxy_host = 'host.docker.internal' + proxy_port = 3128 + + # Verify proxy is available + if not check_proxy_available(proxy_host, proxy_port): + raise RuntimeError(f"Proxy not available at {proxy_host}:{proxy_port}") + + print(f"Proxy available at {proxy_host}:{proxy_port}") + + # Configure browser session + browser_session = Browser( + record_video_dir=Path('./tmp/recordings'), + proxy=ProxySettings(server=f'http://{proxy_host}:{proxy_port}'), + args=['--ignore-certificate-errors'] + ) + + # Build initial actions if starting URL is provided + initial_actions = None + if starting_url: + initial_actions = [{'go_to_url': {'url': starting_url}}] + print(f"Starting at URL: {starting_url}") + + # Create and run agent + agent = Agent( + task=task, + llm=ChatOpenAI( + model=os.environ.get('OPENAI_MODEL', 'gpt-4o-mini'), + api_key=os.environ.get('OPENAI_API_KEY') + ), + browser_session=browser_session, + allowed_domains=['*.zoo'], + initial_actions=initial_actions, + ) + + print(f"Running task: {task}") + result = await agent.run(max_steps=max_steps) + + print(f"\nTask completed successfully") + print(f"Result: {result}") + + return result + + +def main(): + parser = argparse.ArgumentParser( + description='Execute a browser automation task using browser-use' + ) + parser.add_argument( + '--task', + required=True, + help='Task description for the browser agent' + ) + parser.add_argument( + '--starting-url', + help='Optional starting URL for the task' + ) + parser.add_argument( + '--max-steps', + type=int, + default=10, + help='Maximum number of steps for the agent (default: 10)' + ) + + args = parser.parse_args() + + # Validate required environment variables + if not os.environ.get('OPENAI_API_KEY'): + print("Error: OPENAI_API_KEY environment variable not set", file=sys.stderr) + sys.exit(1) + + # Run the task + try: + asyncio.run(run_task(args.task, args.max_steps, args.starting_url)) + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..c48ac46 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,13 @@ +services: + browser-use: + build: ./agents/browser-use-muzzle + image: browser-use-muzzle + container_name: browser-use-muzzle + environment: + - OPENAI_API_KEY=${OPENAI_API_KEY} + - OPENAI_MODEL=${OPENAI_MODEL:-gpt-4o-mini} + volumes: + - ./recordings:/app/tmp/recordings + extra_hosts: + - "host.docker.internal:host-gateway" + restart: unless-stopped diff --git a/muzzle/experiment.py b/muzzle/experiment.py index d45c5bb..a34bec1 100644 --- a/muzzle/experiment.py +++ b/muzzle/experiment.py @@ -13,11 +13,21 @@ import os class Experiment: - def __init__(self, task_config_path, agent_config_path, browser_config_path, injection_config_path): + def __init__(self, task_config_path, agent_config_path, browser_config_path, injection_config_path, agent_name=None): self.id = str(uuid.uuid4()) - self.browser_config, self.agent_config, self.task_config = get_configs( - browser_config_path, agent_config_path, task_config_path, injection_config_path - ) + self.agent_name = agent_name + + # Only load configs if not using a built-in agent + if agent_name: + # For built-in agents like browser_use, only load task config + self.task_config = get_configs(None, None, task_config_path, injection_config_path)[2] + self.browser_config = None + self.agent_config = None + else: + self.browser_config, self.agent_config, self.task_config = get_configs( + browser_config_path, agent_config_path, task_config_path, injection_config_path + ) + self.logger = logging.getLogger(self.__class__.__name__) self.docker_client = docker.from_env() @@ -133,6 +143,80 @@ def _evaluate_task_completion(self): evaluator = Evaluator() evaluator.evaluate(self.task_config) + # Map of agent names to docker-compose service names + AGENT_SERVICE_MAP = { + "browser_use": "browser-use", + } + + def _run_named_agent_worker(self, agent_name): + """ + Run a named agent worker via docker-compose exec. + + Args: + agent_name (str): Name of the agent (e.g., 'browser_use') + """ + self.logger.info(f"Running {agent_name} worker...") + + # Get service name from map + service_name = self.AGENT_SERVICE_MAP.get(agent_name) + if not service_name: + raise ValueError(f"Unknown agent name: {agent_name}. Supported agents: {list(self.AGENT_SERVICE_MAP.keys())}") + + import subprocess + + # Check if container is running, if not start it + self.logger.info(f"Checking if {service_name} container is running...") + check_cmd = ["docker-compose", "ps", "-q", service_name] + result = subprocess.run(check_cmd, capture_output=True, text=True) + + if not result.stdout.strip(): + self.logger.info(f"Container {service_name} is not running. Starting it now...") + start_cmd = ["docker-compose", "up", "-d", service_name] + start_result = subprocess.run(start_cmd, capture_output=True, text=True) + if start_result.returncode != 0: + raise RuntimeError(f"Failed to start {service_name} container: {start_result.stderr}") + self.logger.info(f"Container {service_name} started successfully") + else: + self.logger.info(f"Container {service_name} is already running") + + # Build the docker-compose exec command + task_description = self.task_config.get("task", "") + # Use default_url from task config as the starting URL + starting_url = self.task_config.get("default_url") + max_steps = self.task_config.get("max_steps", 10) + + cmd = [ + "docker-compose", "exec", "-T", service_name, + "python", "main.py", + "--task", task_description, + "--max-steps", str(max_steps) + ] + + if starting_url: + cmd.extend(["--starting-url", starting_url]) + + self.logger.info(f"Running command: {' '.join(cmd)}") + + # Execute the command and stream output + process = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + bufsize=1 + ) + + # Stream output in real-time + for line in process.stdout: + self.logger.info(line.rstrip()) + + # Wait for completion + return_code = process.wait() + if return_code != 0: + raise RuntimeError(f"{agent_name} worker failed with exit code {return_code}") + + self.logger.info(f"{agent_name} worker completed successfully") + def run(self): """ Run the experiment. @@ -145,8 +229,11 @@ def run(self): # Step 2: Seed the Zoo with the desired content for the experiment self._seed_zoo() - # Step 3: Run the docker container with the desired experiment parameters - self._run_docker_container() + # Step 3: Run the agent (either docker container or named agent worker) + if self.agent_name: + self._run_named_agent_worker(self.agent_name) + else: + self._run_docker_container() # Step 4: Evaluate the task completion self._evaluate_task_completion() \ No newline at end of file diff --git a/muzzle/orchestrator.py b/muzzle/orchestrator.py index 30ed1f0..f709386 100644 --- a/muzzle/orchestrator.py +++ b/muzzle/orchestrator.py @@ -6,7 +6,7 @@ def __init__(self): self._queue = [] self.logger = logging.getLogger(self.__class__.__name__) - def add(self, task_config_path, agent_config_path, browser_config_path, injection_config_path): + def add(self, task_config_path, agent_config_path, browser_config_path, injection_config_path, agent_name=None): """ Add a new experiment to the queue. Args: @@ -14,30 +14,33 @@ def add(self, task_config_path, agent_config_path, browser_config_path, injectio agent_config_path (str): Path to the agent configuration JSON file. browser_config_path (str): Path to the browser configuration JSON file. injection_config_path (str): Path to the injection configuration JSON file. + agent_name (str, optional): Built-in agent name (e.g., 'browser_use') """ e = Experiment( task_config_path=task_config_path, agent_config_path=agent_config_path, browser_config_path=browser_config_path, - injection_config_path=injection_config_path + injection_config_path=injection_config_path, + agent_name=agent_name ) self.logger.info(f"Adding experiment: {e.id}") self._queue.append(e) - def addAll(self, task_configs_folder_path, agent_config_path, browser_config_path, injection_config_path): + def addAll(self, task_configs_folder_path, agent_config_path, browser_config_path, injection_config_path, agent_name=None): """ Add all experiments from a folder to the queue. Args: task_configs_folder_path (str): Path to the folder containing task configuration JSON files. agent_config_path (str): Path to the agent configuration JSON file. browser_config_path (str): Path to the browser configuration JSON file. + agent_name (str, optional): Built-in agent name (e.g., 'browser_use') """ import os task_folder = [f for f in os.listdir(task_configs_folder_path) if f.endswith(".json")] self.logger.info(f"Adding all ({len(task_folder)}) experiments from folder: {task_configs_folder_path}") for filename in task_folder: task_config_path = os.path.join(task_configs_folder_path, filename) - self.add(task_config_path, agent_config_path, browser_config_path, injection_config_path) + self.add(task_config_path, agent_config_path, browser_config_path, injection_config_path, agent_name) def runAll(self): """ diff --git a/muzzle/utils/configs.py b/muzzle/utils/configs.py index 0ca5dbd..5bb8fc8 100644 --- a/muzzle/utils/configs.py +++ b/muzzle/utils/configs.py @@ -7,15 +7,15 @@ def get_configs(browser_config_path, inference_config_path, task_config_path, injection_config_path): """ - Load configurations from JSON files. + Load configurations from JSON files. Args: - browser_config_path (str): Path to the browser configuration JSON file. - inference_config_path (str): Path to the inference configuration JSON file. + browser_config_path (str or None): Path to the browser configuration JSON file. + inference_config_path (str or None): Path to the inference configuration JSON file. task_config_path (str): Path to the task configuration JSON file. injection_config_path (str): Path to the injection configuration JSON file. """ - browser_config = json.load(open(browser_config_path)) - inference_config = json.load(open(inference_config_path)) + browser_config = json.load(open(browser_config_path)) if browser_config_path else None + inference_config = json.load(open(inference_config_path)) if inference_config_path else None injection_config = json.load(open(injection_config_path)) task_config = json.load(open(task_config_path)) # Merge injection config into task config diff --git a/run.py b/run.py index 4c32e12..58073ad 100644 --- a/run.py +++ b/run.py @@ -20,11 +20,16 @@ def main(): parser.add_argument("--run-all", action="store_true", default=False, help="Run all experiments.") parser.add_argument("--task-config", type=str, required=True, help="Path to the task configuration JSON file. If --run-all is set, this should be a folder containing multiple task configuration JSON files.") parser.add_argument("--injection-config", type=str, required=True, help="Path to the injection configuration JSON file.") - parser.add_argument("--agent-config", type=str, required=True, help="Path to the agent configuration JSON file.") - parser.add_argument("--browser-config", type=str, required=True, help="Path to the browser configuration JSON file.") + parser.add_argument("--agent", type=str, help="Built-in agent name (e.g., 'browser_use'). If specified, --agent-config and --browser-config are optional.") + parser.add_argument("--agent-config", type=str, help="Path to the agent configuration JSON file.") + parser.add_argument("--browser-config", type=str, help="Path to the browser configuration JSON file.") parser.add_argument("--refresh-images", action="store_true", default=False, help="Remove existing Docker images before building new ones.") args = parser.parse_args() + # Validate that either --agent or both --agent-config and --browser-config are provided + if not args.agent and (not args.agent_config or not args.browser_config): + parser.error("Either --agent must be specified, or both --agent-config and --browser-config must be provided.") + if not args.zoo_path and not zoo_api.get_zoo_path(): parser.error("Either --zoo-path must be provided or a Zoo path must be set in ~/.muzzle/zoo.yml") @@ -46,13 +51,15 @@ def main(): orchestrator.addAll(task_configs_folder_path=args.task_config, agent_config_path=args.agent_config, browser_config_path=args.browser_config, - injection_config_path=args.injection_config) + injection_config_path=args.injection_config, + agent_name=args.agent) else: # Add a single experiment orchestrator.add(task_config_path=args.task_config, agent_config_path=args.agent_config, browser_config_path=args.browser_config, - injection_config_path=args.injection_config) + injection_config_path=args.injection_config, + agent_name=args.agent) # Run all experiments in the queue orchestrator.runAll() From e60c675a8716d3bcfcb023a7cf404a3a1faf39d4 Mon Sep 17 00:00:00 2001 From: Brian Grinstead Date: Wed, 8 Oct 2025 14:38:37 -0700 Subject: [PATCH 3/3] pass params --- muzzle/experiment.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/muzzle/experiment.py b/muzzle/experiment.py index a34bec1..9f9f9a3 100644 --- a/muzzle/experiment.py +++ b/muzzle/experiment.py @@ -180,21 +180,27 @@ def _run_named_agent_worker(self, agent_name): self.logger.info(f"Container {service_name} is already running") # Build the docker-compose exec command - task_description = self.task_config.get("task", "") - # Use default_url from task config as the starting URL - starting_url = self.task_config.get("default_url") + # Format task with credentials and starting URL like the old agent does + parameters = self.task_config.get("parameters", {}) + + base_task = self.task_config.get("task", "NO TASK PROVIDED") + username = parameters.get("gitlab_username", "user") + password = parameters.get("gitlab_password", "pass") + default_url = self.task_config.get("default_url", "about:blank") max_steps = self.task_config.get("max_steps", 10) + self.logger.info(f"Extracted - username: {username}, password: {password}, default_url: {default_url}") + + task_description = f"Your username is {username} and your password is {password}. FIRST GO TO {default_url}. {base_task}" + cmd = [ "docker-compose", "exec", "-T", service_name, "python", "main.py", "--task", task_description, - "--max-steps", str(max_steps) + "--max-steps", str(max_steps), + "--starting-url", default_url ] - if starting_url: - cmd.extend(["--starting-url", starting_url]) - self.logger.info(f"Running command: {' '.join(cmd)}") # Execute the command and stream output