Hydra Engine

CLI

One Python file: launcher, daemon manager and client.

ghidra_session.py replaces Ghidra's .bat and .sh launchers for the daemon. It finds the JDK, builds the classpath, starts the JVM detached, and talks to it. It needs Python 3.10 and nothing outside the standard library.

Commands

CommandDoes
ensureStarts the daemon unless it's running; prints its endpoint, without the token.
statusProjects, programs and who uses them, clients, lanes, memory.
call CMD key=value...Sends one request as a new client that disconnects afterwards.
stop [--force]Shuts the daemon down; refused while other clients are connected, unless --force.
killKills the daemon's JVM outright, for a wedged daemon.
versionClient version, protocol and release. Needs no daemon.
configThe effective configuration, JDK and install.
serveRuns the daemon in the foreground, for a service manager.
stdioA private session on this process's stdin and stdout: no daemon, no socket.
aotBuilds the AOT class cache (JDK 25+).
commandPrints the Java command line it would run.

One-off requests

call turns key=value pairs into a request. Values that parse as JSON are sent as JSON, so writable=true is a boolean and limit=20 a number.

bash
1python $GS call ping2python $GS call projects

Each call is its own client, and a client's open projects and programs close when it leaves. For anything that takes more than one request, keep one connection: from Python, gs.connect(); from a shell, stdio below.

A private session over stdio

stdio runs a session inside this process and reads requests from stdin, one JSON object per line. The first line out is the ready event. Nothing is shared and the JVM starts cold, so it suits scripts and CI more than agents.

bash
1python $GS stdio <<'EOF'2{"id": 1, "cmd": "open_project", "path": "/re/MyProject"}3{"id": 2, "cmd": "open_program", "path": "target.exe"}4{"id": 3, "cmd": "decompile", "name": "main"}5EOF

Global options

OptionSets
--ghidraThe install, when the script isn't inside one.
--stateThe state directory (endpoint, locks, logs, caches).
--instanceThe daemon's name; run several side by side with different names.
--javaThe JDK home.
--max-heapThe JVM's -Xmx.
--gcg1, zgc, parallel or default.
--modefast (default classpath and AOT cache) or standard (Ghidra's own class loader).
--tmpdirWhere Ghidra keeps unsaved database buffers.

Each option also has a config.json key and most have an environment variable; see Configuration.

Lifecycle

A daemon stays up until stop, kill, or idleTimeout seconds without clients (0, the default, means stay up). It starts detached from the client that launched it, so it outlives the harness that happened to start it.

Exit codes worth knowing: 3 when a second daemon of the same instance starts (it exits at once), 4 when the client refuses an engine it can't drive.