Skip to content
Hydra Engine

How it works

One daemon, shared fairly

Every view on this page is rendered from a data file. Each says whether it is measured (from a real run or the engine's README) or a sample trace (a deterministic scenario that follows the engine's documented rules). Samples are marked as such everywhere they appear.

Many agents, one daemon

Quick questions never wait behind long work

Requests take a slot in one of two lanes before any lock: quick (decompile, functions, list, open, rename) and long (analyze, import, script, save). Long work can never take the slots quick queries need. When a lane is full, waiting requests are admitted round-robin by client, so one agent sending a hundred requests cannot starve one that sends one.

sample trace

Seven agents, six programs, one daemon (maxQuick 4, maxLong 2)

A deterministic simulation of the scheduler's documented rules (two lanes, fixed slots, round-robin by client, per-client cancel, release on death). Durations are illustrative: cold opens and first decompiles use the README's order of magnitude, warm decompiles about 7 ms. Lane sizes are small so the queues are visible; the daemon's defaults are one quick slot per CPU and a quarter of the CPUs for long work.

data/agents.json · scripts/gen-traces.ts

0 quick and 0 long running · 0 waiting for a slot

0s1s2s3s4s5s6s7s8s9s10s11s12sagent-ap1:/server.exeagent-bp2:/client.dllexporterp3:/driver.sysagent-dp4:/updater.exeagent-ep5:/libcore.soagent-fp6:/firmware.binagent-gp1:/server.exeopen_program: ok, ran 847 ms — cold open of an analysed programopen_program: ok, ran 412 ms — writableopen_program: ok, ran 380 msopen_project: ok, ran 41 ms — writable working projectdecompile: ok, ran 231 msopen_program: ok, ran 298 msopen_program: ok, ran 505 ms — writable, one ownerimport: ok, ran 6000 ms — updater.exe, analyze: trueimportdecompile: ok, ran 256 ms — first decompile: a decompiler startsscript: cancelled, ran 3323 ms — ExportJsonl.java, artifact out.jsonl✕scriptdecompile: ok, ran 238 msfunctions: ok, ran 48 msanalyze: ok, ran 5200 ms, waited 2972 ms — auto-analysis of a rangeanalyzedecompile: ok, ran 8 msdecompile: ok, ran 9 msdecompile: ok, ran 7 msrename: ok, ran 4 msdecompile: ok, ran 7 mscomment: ok, ran 2 msfunctions: ok, ran 45 msdecompile: ok, ran 7 msrename: ok, ran 4 msdecompile: ok, ran 6 msdecompile: ok, ran 8 mscomment: ok, ran 4 msdecompile: ok, ran 9 msdecompile: ok, ran 8 msrename: ok, ran 4 msfunctions: ok, ran 38 msfunctions: ok, ran 47 mscomment: ok, ran 2 msdecompile: ok, ran 8 msdecompile: ok, ran 6 msrename: ok, ran 2 mslist: ok, ran 14 msdecompile: ok, ran 7 msdecompile: ok, ran 8 mscomment: ok, ran 3 msfunctions: ok, ran 34 msrename: ok, ran 3 msdecompile: ok, ran 8 msdecompile: ok, ran 8 msdecompile: ok, ran 6 mscomment: ok, ran 4 msdecompile (parallel): ok, ran 9 ms — a burst of 96 decompiles, parallel: truedecompile (parallel): ok, ran 11 msdecompile (parallel): ok, ran 13 msdecompile (parallel): ok, ran 11 msdecompile (parallel): ok, ran 11 ms, waited 8 msdecompile (parallel): ok, ran 11 ms, waited 9 msdecompile (parallel): ok, ran 10 ms, waited 9 msdecompile (parallel): ok, ran 11 ms, waited 11 msdecompile (parallel): ok, ran 9 ms, waited 18 msdecompile (parallel): ok, ran 12 ms, waited 19 msdecompile (parallel): ok, ran 14 ms, waited 20 msdecompile (parallel): ok, ran 11 ms, waited 24 msdecompile (parallel): ok, ran 9 ms, waited 25 msdecompile (parallel): ok, ran 11 ms, waited 29 msdecompile (parallel): ok, ran 11 ms, waited 33 msdecompile (parallel): ok, ran 13 ms, waited 33 msdecompile (parallel): ok, ran 13 ms, waited 33 msdecompile (parallel): ok, ran 10 ms, waited 39 msdecompile (parallel): ok, ran 12 ms, waited 45 msdecompile (parallel): ok, ran 9 ms, waited 45 msdecompile (parallel): ok, ran 11 ms, waited 48 msdecompile (parallel): ok, ran 13 ms, waited 49 msdecompile (parallel): ok, ran 9 ms, waited 52 msdecompile (parallel): ok, ran 14 ms, waited 55 msdecompile (parallel): ok, ran 9 ms, waited 58 msdecompile (parallel): ok, ran 13 ms, waited 60 msdecompile (parallel): ok, ran 12 ms, waited 60 msdecompile (parallel): ok, ran 11 ms, waited 65 msdecompile (parallel): ok, ran 11 ms, waited 66 msdecompile (parallel): ok, ran 13 ms, waited 71 msdecompile (parallel): ok, ran 13 ms, waited 71 msdecompile (parallel): ok, ran 13 ms, waited 74 msdecompile (parallel): ok, ran 13 ms, waited 76 msdecompile (parallel): ok, ran 12 ms, waited 82 msdecompile (parallel): ok, ran 13 ms, waited 82 msdecompile (parallel): ok, ran 12 ms, waited 85 msdecompile (parallel): ok, ran 10 ms, waited 87 msdecompile (parallel): ok, ran 13 ms, waited 92 msdecompile (parallel): ok, ran 10 ms, waited 94 msdecompile (parallel): ok, ran 10 ms, waited 95 msdecompile (parallel): ok, ran 11 ms, waited 95 msdecompile (parallel): ok, ran 9 ms, waited 102 msdecompile (parallel): ok, ran 14 ms, waited 103 msdecompile (parallel): ok, ran 10 ms, waited 103 msdecompile: ok, ran 9 ms, waited 2 msdecompile (parallel): ok, ran 9 ms, waited 104 msdecompile (parallel): ok, ran 10 ms, waited 109 msdecompile (parallel): ok, ran 9 ms, waited 112 msdecompile (parallel): ok, ran 11 ms, waited 112 msdecompile (parallel): ok, ran 9 ms, waited 115 msdecompile (parallel): ok, ran 9 ms, waited 118 msdecompile (parallel): ok, ran 11 ms, waited 119 msdecompile (parallel): ok, ran 11 ms, waited 121 msdecompile (parallel): ok, ran 12 ms, waited 122 msdecompile (parallel): ok, ran 12 ms, waited 125 msdecompile (parallel): ok, ran 12 ms, waited 128 msdecompile (parallel): ok, ran 14 ms, waited 130 msdecompile (parallel): ok, ran 9 ms, waited 133 msdecompile (parallel): ok, ran 13 ms, waited 136 msdecompile (parallel): ok, ran 11 ms, waited 138 msdecompile (parallel): ok, ran 9 ms, waited 140 msdecompile (parallel): ok, ran 11 ms, waited 142 msdecompile (parallel): ok, ran 13 ms, waited 147 msdecompile (parallel): ok, ran 12 ms, waited 148 msdecompile (parallel): ok, ran 13 ms, waited 148 msdecompile (parallel): ok, ran 14 ms, waited 151 msdecompile (parallel): ok, ran 11 ms, waited 158 msdecompile (parallel): ok, ran 11 ms, waited 159 msdecompile (parallel): ok, ran 10 ms, waited 159 msdecompile (parallel): ok, ran 10 ms, waited 163 msdecompile (parallel): ok, ran 11 ms, waited 167 msdecompile (parallel): ok, ran 12 ms, waited 168 msdecompile (parallel): ok, ran 10 ms, waited 167 msdecompile (parallel): ok, ran 12 ms, waited 172 msdecompile (parallel): ok, ran 8 ms, waited 176 msdecompile (parallel): ok, ran 13 ms, waited 176 msdecompile (parallel): ok, ran 13 ms, waited 177 msdecompile (parallel): ok, ran 13 ms, waited 182 msdecompile (parallel): ok, ran 12 ms, waited 183 msdecompile (parallel): ok, ran 9 ms, waited 187 msdecompile (parallel): ok, ran 8 ms, waited 189 msdecompile (parallel): ok, ran 12 ms, waited 193 msdecompile (parallel): ok, ran 12 ms, waited 193 msdecompile (parallel): ok, ran 10 ms, waited 195 msdecompile (parallel): ok, ran 9 ms, waited 196 msdecompile (parallel): ok, ran 9 ms, waited 204 msdecompile (parallel): ok, ran 8 ms, waited 203 msdecompile (parallel): ok, ran 8 ms, waited 203 msdecompile (parallel): ok, ran 8 ms, waited 203 msdecompile (parallel): ok, ran 14 ms, waited 210 msdecompile (parallel): ok, ran 13 ms, waited 210 msdecompile (parallel): ok, ran 9 ms, waited 210 msdecompile (parallel): ok, ran 8 ms, waited 210 msdecompile (parallel): ok, ran 9 ms, waited 218 msdecompile (parallel): ok, ran 14 ms, waited 217 msdecompile (parallel): ok, ran 13 ms, waited 221 msdecompile (parallel): ok, ran 12 ms, waited 221 msopen_program: ok, ran 1 ms, waited 5 ms — already open and warm: joins itdecompile: ok, ran 7 ms, waited 3 ms — warm decompilerrename: ok, ran 2 msdecompile: ok, ran 7 msfunctions: ok, ran 49 mscomment: ok, ran 4 msdecompile: ok, ran 8 msdecompile: ok, ran 8 msdecompile: ok, ran 6 msdecompile: ok, ran 262 msdecompile: ok, ran 7 msscript: ok, ran 880 ms — a short report scriptdecompile: ok, ran 7 msrename: ok, ran 3 msdecompile: ok, ran 6 msdecompile: ok, ran 7 msfunctions: ok, ran 54 msanalyze: cancelled, ran 424 ms, waited 480 ms — re-analysis after renames✕decompile: ok, ran 6 mscomment: ok, ran 3 msfunctions: ok, ran 32 msdecompile: ok, ran 8 msdecompile: ok, ran 7 msdecompile: ok, ran 8 msdecompile: ok, ran 7 msdecompile: ok, ran 8 msdecompile: ok, ran 7 msdecompile: ok, ran 9 msfunctions: ok, ran 44 msdecompile: ok, ran 9 msdecompile: ok, ran 7 msfunctions: ok, ran 52 msdecompile: ok, ran 8 msfunctions: ok, ran 40 msdecompile: ok, ran 7 msfunctions: ok, ran 47 msdecompile: ok, ran 9 msdecompile: ok, ran 7 msdecompile: ok, ran 9 msdecompile: ok, ran 8 msdecompile: ok, ran 6 msdecompile: ok, ran 7 msdecompile: ok, ran 7 msdecompile: ok, ran 8 msdecompile: ok, ran 7 msdecompile: ok, ran 6 msdecompile: ok, ran 7 msfunctions: ok, ran 45 msdecompile: ok, ran 7 msdecompile: ok, ran 7 msdecompile: ok, ran 9 msfunctions: ok, ran 31 msdecompile: ok, ran 6 msdecompile: ok, ran 9 msfunctions: ok, ran 52 msdecompile: ok, ran 7 msfunctions: ok, ran 50 msdecompile: ok, ran 6 msfunctions: ok, ran 42 msdecompile: ok, ran 6 msdecompile: ok, ran 7 msdecompile: ok, ran 9 msdecompile: ok, ran 8 msdecompile: ok, ran 6 msdecompile: ok, ran 9 msdecompile: ok, ran 7 msfunctions: ok, ran 36 msdecompile: ok, ran 8 msdecompile: ok, ran 8 msdecompile: ok, ran 6 msfunctions: ok, ran 43 msdecompile: ok, ran 9 msdecompile: ok, ran 9 msfunctions: ok, ran 38 msfunctions: ok, ran 44 msdecompile: ok, ran 7 msdecompile: ok, ran 7 mscancel✂ client diedquick lane4 slots busy at mostlong lane2 slots busy at most

What happened

  1. 4.20 sexporter sends cancel {target}: the export stops, its long slot goes to the next waiting request
  2. 6.00 sagent-e sends 96 decompiles at once; the quick lane admits other clients' requests between them (round-robin)
  3. 8.30 sagent-f's process dies: its analyze is cancelled, its unsaved renames are discarded, firmware.bin is read-only again
  4. ·7 clients connected; each bar's tooltip has its command, wait and run time.

Round-robin, up close

Quick-lane admissions in order, from 6.00 s: 96 of one client's burst (agent-e) and 3 requests from the others, which do not queue behind the burst.

  • agent-a decompile: waited 2.4 ms for a slot
  • agent-g open_program: waited 5.2 ms for a slot
  • agent-g decompile: waited 3.4 ms for a slot

The rules: The shared daemon.

Who holds what

Readers, owners, writers, forks and overlays

Each program has a fair read/write lock: queries share it, edits and analysis hold it alone. One client owns a writable program unless it is opened shared; everyone else reads it, annotates a private overlay, or forks the project. Step through a scenario and watch the board change.

sample trace

A scripted scenario: six agents, four programs, three projects

Each step follows the module README's rules ("Several clients", "Who can do what at the same time"). Request and answer lines are shortened; fields not shown are elided with ... . Steps marked next use features on main that are not in a release yet.

data/locks.json · scripts/gen-traces.ts

1 / 14
okin 12.3-hydra.1

agent-a opens an analysed project the way File > View Project does: no lock file, nothing written.

→ {"cmd": "open_project", "path": "/re/firmware.gpr"}
← {"id": "p1", "writable": false, ...}

p1:/server.exe

new

firmware.gpr · read-only view, no lock

readers
none
owner
none
writers
none
overlays
none
waiting
none
unsaved
none

p1:/libcrypto.dll

new

firmware.gpr · read-only view, no lock

readers
none
owner
none
writers
none
overlays
none
waiting
none
unsaved
none

A question's life

Where an agent's question spends its time

Cold, a question pays for the daemon's start, the program's open and a decompiler's start. Warm, the daemon, the program and its pooled decompilers are already there, for every client. Rows marked measured quote the engine's README; the others are illustrative samples.

sample trace

Where an agent's question spends its time, cold and warm

Rows marked measured quote the engine's README; their phases are the README's own split where it gives one, else one total. Rows marked sample are illustrative.

data/questions.json · scripts/gen-traces.ts

  • start / connect
  • queue wait
  • open program
  • decode / work
  • answer
  • total (not split)
  • bar length: log scale, 0.5 ms to 30 s
  1. “Decompile this function”

    decompile

    quick lane
    cold
    1.52 smeasured
    What this run was ›

    No daemon running: launch until ready 348 ms, open_program 847 ms, first decompile 256 ms; launch until the first answer 1,521 ms (66.7 MB x64 executable, 243,855 functions, read-only; the rest of the 1,521 ms is open_project and the round trips) Split: start / connect 348 ms, open program 847 ms, decode / work 256 ms, answer 70 ms. Source

    warm
    6.7-7.1 msmeasured
    What this run was ›

    Daemon up, program open, decompiler pooled (66.7 MB x64 executable, 243,855 functions, read-only). The README gives the total only. Source

  2. “Decompile, from a second harness that just started”

    connect + hello, open_project, open_program, decompile

    quick lane
    warm
    23.2 msmeasured
    What this run was ›

    Another harness process while the first is connected: connect + hello 14 ms, join the project 0.9 ms, open_program (already open) 0.9 ms, decompile 7.4 ms. Measured on Ghidra 12.1.4. Split: start / connect 14 ms, open program 1.8 ms, decode / work 7.4 ms. Source

  3. “Open an analysed program”

    open_program

    quick lane
    cold
    847 msmeasured
    What this run was ›

    First open in this daemon (66.7 MB x64 executable, 243,855 functions, read-only) Source

    warm
    1.7 msmeasured
    What this run was ›

    A harness that arrives after everyone left, within keepWarm: no reopen. Measured on Ghidra 12.1.4. Source

  4. “What is this binary? (format, language, function count)”

    info

    no slot needed
    warm
    2 msmeasured
    What this run was ›

    Recorded session, 140 KB test program (574 functions) Source

  5. “List the functions whose name contains crypt”

    functions contains=crypt

    quick lane
    cold
    61 mssample trace
    What this run was ›

    Sample: first page of 200 after the program opened Split: queue wait 0.4 ms, decode / work 54 ms, answer 6.6 ms.

    warm
    38 mssample trace
    What this run was ›

    Sample: the same filter again Split: queue wait 0.3 ms, decode / work 33 ms, answer 4.7 ms.

  6. “Who references this address?”

    script (a GhidraScript; there is no xrefs command)

    long lane
    cold
    1.18 ssample trace
    What this run was ›

    Sample: the first script of the session waits for the deferred script compiler (OSGi), then compiles the script Split: queue wait 2 ms, open program 960 ms, decode / work 210 ms, answer 8 ms.

    warm
    64 mssample trace
    What this run was ›

    Sample: script already compiled, program warm Split: queue wait 1 ms, decode / work 58 ms, answer 5 ms.

  7. “Find strings that mention a URL”

    script (a GhidraScript; there is no strings command)

    long lane
    cold
    2.35 ssample trace
    What this run was ›

    Sample: first script of the session, defined data scanned once Split: queue wait 3 ms, open program 940 ms, decode / work 1.39 s, answer 17 ms.

    warm
    1.24 ssample trace
    What this run was ›

    Sample: the scan itself dominates; warmth only saves the start Split: queue wait 2 ms, decode / work 1.23 s, answer 13 ms.

  8. “Rename this function and leave a comment”

    rename + comment (or edit, or apply_overlay)

    quick lane
    cold
    9.5 mssample trace
    What this run was ›

    Sample: owner mode, first change opens a transaction log Split: queue wait 0.5 ms, decode / work 7.6 ms, answer 1.4 ms.

    warm
    1.2 msmeasured
    What this run was ›

    In an overlay of a shared program: 1.2 ms per rename (66.7 MB x64 executable, 243,855 functions) Source

  9. “Import and analyse a new binary”

    import analyze=true

    long lane
    cold
    15.52 smeasured
    What this run was ›

    Recorded session: a 140 KB x64 test program with its PDB, imported and analysed Source

    warm
    4.2 msmeasured
    What this run was ›

    The same file again: refused, its SHA-256 is already in the project Source

  10. “Re-run auto-analysis after my renames”

    analyze

    long lane
    cold
    5.24 ssample trace
    What this run was ›

    Sample: a range of a large program; waits for a long slot Split: queue wait 40 ms, decode / work 5.19 s, answer 10 ms.

    warm
    5.12 ssample trace
    What this run was ›

    Sample: analysis is work, not start-up; warmth barely helps Split: queue wait 2 ms, decode / work 5.11 s, answer 10 ms.