brew services start colima
March 24, 2026 ยท View on GitHub
Debug Mode
HAgent provides a debug mode that enables detailed logging and verbose output for troubleshooting and development.
Enabling Debug Mode
MCP Server:
# Run MCP server with debug logging
uv run python hagent/mcp/hagent-mcp-server.py --debug
CLI Tools:
# Run mcp_build.py with debug mode
uv run python hagent/mcp/mcp_build.py --debug --api compile --name gcd
What Debug Mode Does
When debug mode is enabled:
- Logging Level: Changes from INFO to DEBUG, showing all debug messages
- Command Preview: Logs the actual command that will be executed (with
[DEBUG]prefix) - Verbose Output: Builder runs with
quiet=Falsefor detailed execution output - Environment Variable: Sets
HAGENT_MCP_DEBUG=1so all tools can detect debug mode - Log Files: All debug output goes to
logs/hagent_mcp_server.log
Debug Output Examples
When debug mode is active, you'll see messages like:
[DEBUG] Getting command for debugging...
[DEBUG] Command to execute:
docker run --rm -v /path/to/repo:/code/workspace/repo ... compile_command
[DEBUG] Retrying with profile query as title_query
Automatic Debug Propagation
When the MCP server runs with --debug, all tools called through MCP automatically inherit debug mode - no need to pass it explicitly. The HAGENT_MCP_DEBUG=1 environment variable ensures consistent debug behavior across all components.
Log File Locations
logs/hagent_mcp_server.log- Main server and tool debug messageslogs/hagent_mcp_server_io.log- Raw MCP protocol I/O (debug mode only)logs/hagent_mcp_hagent_build.log- Transaction logs for build commands
Execution Mode Configuration
HAgent supports two execution modes: local and docker. The mode is controlled by environment variables:
Local Mode
For local execution (debugging, single tasks), you must set all required environment variables:
# Required for local mode (don't set HAGENT_DOCKER)
unset HAGENT_DOCKER
export HAGENT_REPO_DIR=/path/to/your/git/repository # Git repository root
export HAGENT_BUILD_DIR=/path/to/your/build/directory # Build output directory
export HAGENT_CACHE_DIR=/path/to/your/cache/directory # HAgent cache directory
# Run a step locally
uv run python hagent/step/trivial/trivial.py hagent/step/trivial/tests/input1.yaml -o output.yaml
Docker Mode (Recommended)
For Docker execution (production, complex builds), set HAGENT_DOCKER to your docker image:
Docker images are recommended to be derived from hagent-builer like hagent-simplechisel from https://github.com/masc-ucsc/docker-images
# Required for Docker mode - set HAGENT_DOCKER to activate docker mode
export HAGENT_DOCKER=mascucsc/hagent-simplechisel:2026.02
# Optional: Mount host directories into container
export HAGENT_REPO_DIR=/path/to/your/git/repository # Will be mounted to /code/workspace/repo
export HAGENT_BUILD_DIR=/path/to/your/build/directory # Will be mounted to /code/workspace/build
# Run the same step in Docker - identical command!
uv run python hagent/step/trivial/trivial.py hagent/step/trivial/tests/input1.yaml -o output.yaml
Key Benefits:
- Same command works for both modes - only environment setup differs
- Docker mode automatically handles container setup and path mounting
- Local mode provides direct access for debugging and development
Path Management
- Pipes should use Builder: All pipes should use the Builder pattern and access files through
runner.filesystem - No direct path_manager/runner usage: Pipes should not directly use
path_managerorrunner- use Builder instead - File operations: Use
runner.filesystem.read_file()andrunner.filesystem.write_file()instead ofrun_cmdwithcatfor copying files, especially for Verilog files with special characters - Docker/Local mode: Builder and filesystem automatically handle path translation between Docker (
/code/workspace/...) and local modes - Avoid hard-coded
/code/workspace/...paths outsidehagent/inou/orhagent/mcp/
Structure
HAgent code is divided into four key components:
-
core: Contains multiple Python files with shared functionality across steps
- When common functionality is used by several steps or tools, the logic is to push the code to core as a Python library
-
step: Has a subdirectory for each HAgent compiler step
- Each step has a stand-alone Python executable that matches the directory name
- Each step only reads and writes YAML files. There may also exist a log file that matches the output YAML name
- Each step should be hermetic, relying only on the input YAML files or calling tools
- Each step inherits from a core Step class and provides basic functionality
- Examples:
trivial,get_spec_io
-
tool: Has a subdirectory for each external tool
- Each tool requires a different Python library
- Each tool inherits from a core tool class and provides a common interface to check tool existence, calling, handling warnings and errors, and other common APIs shared across most tools
- Examples:
yosys,slang,chisel
-
pipe: Has a subdirectory for each end-user HAgent compiler
- Each pipe has a stand-alone Python executable that matches the directory name
- Each pipe combines several HAgent steps and is intended to be called by a user
- Each pipe can have multiple iterative sets of step calls
- Some differences between pipe and step:
- A step is supposed to be simple and can have multiple running in parallel
- A pipe can have command line options and non-YAML inputs or outputs
Contributing
Contributions are welcome! Please open an issue or submit a pull request on GitHub.
Before any commit or pull request, ruff and pytest should pass:
uv run ruff format hagent
uv run ruff check hagent
uv run pytest -v
Or better, run ./scripts/push_check.sh before each github push.
Maintenance Checks
- Run
bash scripts/code_check.shfor heuristic maintenance hints (unused methods, env var/path conventions, etc.). - Methods starting with
_are considered internal; public-but-internal methods should generally be renamed with a leading_.
If you want to work on this project, reach out to Jose Renau. Most steps and pipes have a different code owner.
When contributors submit changes, feel free to add yourself to the CREDITS.txt file that each step, tool, core, and pipe has.
Support
If you encounter any issues or have questions, please open an issue on GitHub.
Troubleshooting
Dependencies out of sync
If you encounter import errors:
# Clean and reinstall
uv sync --reinstall
# Or update lock file
uv lock --upgrade
uv sync
Perfetto Stats
Each generated YAML file has a tracer entry. This can be extracted to create a Perfetto trace. Put all the YAML files in a directory (inputs/outputs). For example:
uv run ./hagent/step/trivial/trivial.py ./hagent/step/trivial/tests/input1.yaml -o out.yaml
uv run ./hagent/step/trivial/trivial.py out.yaml -o out2.yaml
Then run this to generate perfetto.json:
uv run ./scripts/build_perfetto_trace.py -i .
To see the results, upload the perfetto.json to https://ui.perfetto.dev/
Docker Issues
If you use macOS and Colima, you may get a "docker-credential-desktop not installed" issue. Most likely, you need to delete the "credStore" entry from your config.json at ~/.docker/config.json
If you run Colima, you need to use virtiofs as mount point, and 32GB for XiangShan. This may require reinstalling Colima:
# brew install colima lima
colima stop -f
rm -rf ~/.colima
colima start --mount-type virtiofs --vm-type=vz --memory 32
# brew services start colima
Try in the command line that you can do the following (fix Docker/Colima first, or it will not work):
docker pull ubuntu:latest
docker run -it --rm ubuntu:latest
For Docker/Colima, you need to have enough memory. In macOS Colima, the default is 2GB. This is fine for small runs, but not for things like XiangShan runs. Check that you have at least 32GB of memory.
docker run --rm busybox cat /proc/meminfo | grep MemTotal
Sometimes you need to clean up leftover Docker containers, run ./scripts/cleanup.sh
Docker Container Management
HAgent uses a modernized container management system that automatically handles Docker setup and execution. Key features include:
Automatic Container Setup:
- Containers are automatically created and configured when
HAGENT_DOCKERis set - Host directories are mounted to standard container paths (
/code/workspace/repo,/code/workspace/build) - Environment variables are automatically set inside containers
Path Translation:
- Host paths are transparently translated to container paths
- File tracking works consistently across local and Docker modes
- No manual path configuration required
Container Lifecycle:
- Containers are automatically cleaned up after execution
- Resource management is handled transparently
- No manual Docker commands needed
Example Docker Usage:
# Set Docker mode by setting HAGENT_DOCKER
export HAGENT_DOCKER=mascucsc/hagent-simplechisel:2026.02
# Optionally mount your project directory
export HAGENT_REPO_DIR=/path/to/your/project
# Run any HAgent step - Docker is handled automatically
uv run python hagent/step/trivial/trivial.py input.yaml -o output.yaml
Manual Docker Debugging with scripts/docker_args.sh
For debugging and experimentation, you can manually start a container using the same arguments that Runner.py would use. The scripts/docker_args.sh script reads the HAGENT_* environment variables and prints a docker run command with the correct mounts:
export HAGENT_DOCKER=mascucsc/hagent-simplechisel:2026.02
export HAGENT_REPO_DIR=/path/to/your/git/repository
export HAGENT_BUILD_DIR=/path/to/your/build/directory
export HAGENT_CACHE_DIR=/path/to/your/cache/directory
# Preview the docker command that matches Runner.py
./scripts/docker_args.sh
# Open an interactive shell in the container with the same mounts
$(./scripts/docker_args.sh) /bin/bash
This is useful to quickly inspect the container environment, verify mounts, or reproduce issues interactively while using the exact same Docker layout as normal HAgent runs.
The container management system eliminates the need for manual Docker commands while providing the benefits of isolated, reproducible execution environments.