Skip to main content

Contributing

Thank you for contributing to Minerva Agent! This guide covers setting up your dev environment, understanding the codebase, and getting your PR merged.

Contribution Priorities​

We value contributions in this order:

  1. Bug fixes — crashes, incorrect behavior, data loss
  2. Cross-platform compatibility — macOS, different Linux distros, WSL2
  3. Security hardening — shell injection, prompt injection, path traversal
  4. Performance and robustness — retry logic, error handling, graceful degradation
  5. New skills — broadly useful ones (see Creating Skills)
  6. New tools — rarely needed; most capabilities should be skills
  7. Documentation — fixes, clarifications, new examples

Common contribution paths​

Development Setup​

Prerequisites​

RequirementNotes
GitWith the git-lfs extension installed
Python 3.11–3.13uv will install it if missing
uvFast Python package manager (install)
Node.js 22.22+, 24.11+ or 26+Optional — needed for browser tools (matches root package.json engines)

Install with the standard installer​

For most contributors, the best development bootstrap is the same path users take: run the standard installer, then work inside the repository it cloned. The installer creates the Minerva venv, wires the minerva command, stamps the install method for minerva update, and, with --source git (which needs access to the private repository), clones the full git project into $MINERVA_HOME/minerva-agent (usually ~/.minerva/minerva-agent). That keeps your development environment on the same layout the CLI, updater, lazy dependency installer, gateway, and docs assume. Without --source git the installer downloads the published release archive, which is not a git checkout.

curl -fsSL https://tryimagent.com/install.sh | bash -s -- --source git
cd "${MINERVA_HOME:-$HOME/.minerva}/minerva-agent"

# Add dev/test extras on top of the standard install.
uv pip install -e ".[all,dev]"

# Optional: browser tools / docs site dependencies.
npm install

After that, create branches and run tests from that checkout:

git checkout -b fix/description
scripts/run_tests.sh

You can also run a fully isolated Minerva instance (throwaway MINERVA_HOME, separate Electron userData, distinct Electron app name to avoid the single-instance lock):

scripts/dev-sandbox.sh python -m minerva_cli.main
scripts/dev-sandbox.sh --persistent python -m minerva_cli.main desktop # state survives restarts, but lives in the worktree :)

Manual clone fallback​

Use this only if you intentionally do not want Minerva's managed install layout (for example, a throwaway clone inside a container or CI job). If you install this way, make sure you run the minerva entrypoint from this venv; running the system python3 -m minerva_cli.main can pick up unrelated system Python packages.

Create the venv outside the cloned source tree. A venv that lives inside the directory the agent operates from can be wiped by a relative-path command the agent runs against its own checkout (rm -rf venv, uv venv venv, etc.), which silently destroys the running runtime mid-session. Keeping it outside the tree means no relative path from the workspace resolves to it.

The source repository is private. Collaborators clone it; everyone else starts from the source archive published with each release:

mkdir minerva && cd minerva
curl -fsSL "$(curl -fsSL https://tryimagent.com/releases/latest/latest.json \
| python3 -c 'import json, sys; print(json.load(sys.stdin)["archives"]["tar.gz"]["url"])')" \
| tar xz --strip-components=1

# Create venv with Python 3.11, OUTSIDE the source tree
uv venv ~/.minerva/venvs/minerva-dev --python 3.11
export VIRTUAL_ENV="$HOME/.minerva/venvs/minerva-dev"
export PATH="$VIRTUAL_ENV/bin:$PATH"

# Install with all extras (messaging, cron, CLI menus, dev tools)
uv pip install -e ".[all,dev]"

# Optional: browser tools
npm install

Configure for Development​

mkdir -p ~/.minerva/{cron,sessions,logs,memories,skills}
cp cli-config.yaml.example ~/.minerva/config.yaml
touch ~/.minerva/.env

# Add at minimum an LLM provider key:
echo 'OPENROUTER_API_KEY=sk-or-v1-your-key' >> ~/.minerva/.env

Run​

# The standard installer already put `minerva` on PATH.
minerva doctor
minerva chat -q "Hello"

If you used the manual clone fallback, run ./minerva from the checkout or symlink the venv's entrypoint explicitly:

mkdir -p ~/.local/bin
ln -sf "$HOME/.minerva/venvs/minerva-dev/bin/minerva" ~/.local/bin/minerva

Run Tests​

scripts/run_tests.sh

Code Style​

  • PEP 8 with practical exceptions (no strict line length enforcement)
  • Comments: Only when explaining non-obvious intent, trade-offs, or API quirks
  • Error handling: Catch specific exceptions. Use logger.warning()/logger.error() with exc_info=True for unexpected errors
  • Cross-platform: Never assume Unix (see below)
  • Profile-safe paths: Never hardcode ~/.minerva — use get_minerva_home() from minerva_constants for code paths and display_minerva_home() for user-facing messages. The root AGENTS.md in the source tree has the full rules.

Cross-Platform Compatibility​

See Platform Support. Native Windows uses Git Bash (from Git for Windows) for shell commands. If you're doing Windows-heavy dev, run the Windows-footgun lint (scripts/check-windows-footguns.py) before pushing.

When contributing code, keep these rules in mind:

  • Don't add unguarded signal.SIGKILL references. It's not defined on Windows. Either route through gateway.status.terminate_pid(pid, force=True) (the centralized primitive that does taskkill /T /F on Windows and SIGKILL on POSIX), or fall back with getattr(signal, "SIGKILL", signal.SIGTERM).
  • Catch OSError alongside ProcessLookupError on os.kill(pid, 0) probes. Windows raises OSError (WinError 87, "parameter is incorrect") for an already-gone PID instead of ProcessLookupError.
  • Don't force the terminal to POSIX semantics. os.setsid, os.killpg, os.getpgid, os.fork all raise on Windows — gate them with if sys.platform != "win32": or if os.name != "nt":.
  • Open files with an explicit encoding="utf-8". The Python default on Windows is the system locale (often cp1252), which mojibakes or crashes on non-Latin text.
  • Use pathlib.Path / os.path.join — never manually concat with /. This matters less for strings the OS gives us back and more for strings we construct to hand to subprocesses.

Key patterns:

1. File encoding​

Some environments may save .env files in non-UTF-8 encodings:

try:
load_dotenv(env_path)
except UnicodeDecodeError:
load_dotenv(env_path, encoding="latin-1")

2. Process management​

os.setsid(), os.killpg(), and signal handling differ across platforms:

import platform
if platform.system() != "Windows":
kwargs["preexec_fn"] = os.setsid

3. Path separators​

Use pathlib.Path instead of string concatenation with /.

Security Considerations​

Minerva has terminal access. Security matters.

Existing Protections​

LayerImplementation
Sudo password pipingUses shlex.quote() to prevent shell injection
Dangerous command detectionRegex patterns in tools/approval.py with user approval flow
Cron prompt injectionScanner blocks instruction-override patterns
Write deny listProtected paths resolved via os.path.realpath() to prevent symlink bypass
Skills guardSecurity scanner for hub-installed skills
Code execution sandboxChild process runs with API keys stripped
Container hardeningDocker: all capabilities dropped, no privilege escalation, PID limits

Contributing Security-Sensitive Code​

  • Always use shlex.quote() when interpolating user input into shell commands
  • Resolve symlinks with os.path.realpath() before access control checks
  • Don't log secrets
  • Catch broad exceptions around tool execution
  • Test on all platforms if your change touches file paths or processes

Pull Request Process​

Branch Naming​

fix/description # Bug fixes
feat/description # New features
docs/description # Documentation
test/description # Tests
refactor/description # Code restructuring

Before Submitting​

  1. Run tests: scripts/run_tests.sh for CI-parity. Use direct python -m pytest ... only when the wrapper is unavailable or you are intentionally debugging outside the wrapper.
  2. Test manually: Run minerva and exercise the code path you changed
  3. Check cross-platform impact: Consider macOS, Linux, WSL2, and native Windows. If you touch file I/O, process management, terminal handling, subprocesses, or signals, run scripts/check-windows-footguns.py.
  4. Keep PRs focused: One logical change per PR

PR Description​

Include:

  • What changed and why
  • How to test it
  • What platforms you tested on
  • Reference any related issues

Commit Messages​

We use Conventional Commits:

<type>(<scope>): <description>
TypeUse for
fixBug fixes
featNew features
docsDocumentation
testTests
refactorCode restructuring
choreBuild, CI, dependency updates

Scopes: cli, gateway, tools, skills, agent, install, security

Examples:

fix(cli): prevent crash in save_config_value when model is a string
feat(gateway): add Discord multi-user session isolation
fix(security): prevent shell injection in sudo password piping

Repo-local review checklists: .agents/checks/*.md​

Projects built on (or reviewed by) Minerva can keep reviewer checklists inside the repository under .agents/checks/. Each file is a focused, plain-markdown checklist that an agent loads before reviewing a change touching the matching area:

.agents/
checks/
security.md # e.g. "grep the diff for shell interpolation; check subprocess calls quote args"
migrations.md # e.g. "every schema change ships a backfill and a rollback note"
public-api.md # e.g. "exported signatures changed? flag for semver review"

Conventions that make these work well:

  • One concern per file, named after the concern. Small files get read in full; a monolithic checklist.md gets skimmed.
  • Write checks as verifiable actions ("run X and confirm Y"), not aspirations ("code should be secure").
  • State the trigger at the top — which paths or change types the checklist applies to — so an agent (or human) can skip irrelevant ones cheaply.
  • Keep them in version control next to the code they guard: they evolve with the codebase, and a PR that changes the rules changes the checklist in the same diff.

When you ask Minerva to review a PR in a repository that has .agents/checks/, tell it (or teach it via a skill) to read the relevant checklists first and report against them. This gives review agents the project-specific bar that generic review prompts miss.

Reporting Issues​

  • The issue tracker lives in the private source repository, so issues are filed by collaborators
  • Include: OS, Python version, Minerva version (minerva --version), full error traceback
  • Include steps to reproduce
  • Check existing issues before creating duplicates
  • For security vulnerabilities, please report privately

Community​

  • Skills Hub: Upload specialized skills and share with the community

License​

By contributing, you agree that your contributions will be licensed under the MIT License (the LICENSE file at the root of the source tree).