Quick start
From an unzipped install to decompiled C in three steps, then a second client joining the same warm daemon and running into the first one's program.
Three steps: make a small project, get decompiled C out of it from Python, then add a second client and see what sharing one daemon changes. Everything below ran on Windows 11 while this page was written. It assumes the install and its $G and $GS variables.
1. Make a project
The daemon opens projects; it does not create them. The quickest way to get one is Ghidra's own headless analyser, which ships in the install:
New-Item -ItemType Directory -Force C:\re | Out-Null
& $G\support\analyzeHeadless.bat C:\re Demo -import C:\Windows\System32\choice.exechoice.exe is small: 174 functions, imported and analysed in 21 s here. If you already have an analysed project, skip this step and use its .gpr.
2. Ask a question
Save this as first.py:
import ghidra_session as gs
# joins the running daemon, or starts one
with gs.connect(client="first", harness="shell") as s:
s.call("open_project", path=r"C:\re\Demo.gpr") # read-only: no lock
info = s.call("open_program", path="choice.exe")
print(info["key"], info["functions"], "functions")
print(s.call("decompile", name="entry")["c"])Put the client on the path and run it:
$env:PYTHONPATH = "$G\Ghidra\Features\HeadlessSession\support"
python first.pyp1:/choice.exe 174 functions
void entry(void)
{
FUN_140001734();
FUN_140001140();
return;
}Run it a second time. The new process finds the daemon through its endpoint file, and the project, the program and a decompiler are already open: 40 ms end to end here, against 1.3 s for the first run. The first run paid for the open; nobody pays for it again while the program stays warm (an hour by default).
3. Add a second client
Two agents now want the same program. Save this as two.py:
import ghidra_session as gs
a = gs.connect(client="agent-a", harness="claude-code")
b = gs.connect(client="agent-b", harness="codex")
# agent-a opens the program writable: it owns it now
a.call("open_project", path=r"C:\re\Demo.gpr", writable=True)
a.call("open_program", path="choice.exe", writable=True)
# agent-b can still read it
b.call("open_project", path=r"C:\re\Demo.gpr")
b.call("open_program", path="choice.exe")
fn = b.call("functions", limit=1)["functions"][0]["address"]
try:
b.call("rename", address=fn, name="parse_args")
except gs.SessionError as e:
who = e.details["holderInfo"]
print(e.code, "held by", who["name"], "doing", who["command"],
e.details["alternatives"])
# take the ready-made request: a private overlay for agent-b
b.call(**e.details["overlaySuggestion"])
b.call("rename", address=fn, name="parse_args", overlay="agent-b")
def first_name(s, **kw):
return s.call("functions", limit=1, **kw)["functions"][0]["name"]
print("agent-b, overlay:", first_name(b, overlay="agent-b"))
print("agent-a: ", first_name(a))
b.close(); a.close()not_owner held by agent-a doing open_program writable ['overlay', 'fork_project']
agent-b, overlay: parse_args
agent-a: FUN_140001010What happened:
- One program in memory. Both clients read the same open program, each decompile with its own pooled decompiler.
- No silent wait. The refused rename says who holds the program and what they are doing, and offers two ways out, each as a request you can send as is.
- A private overlay. agent-b's name exists only in its overlay. agent-a, and everyone else, still see
FUN_140001010, and nothing is saved.
If agent-b's process had died instead, its requests would have been cancelled and its unsaved changes dropped, and nobody else would have noticed. The overlay, the fork and the shared mode are covered in Several writers. on main · 12.3-hydra.2
Next
- The shared daemon: lanes, locks, labels, and what happens when a client dies.
- Integrate: writing an MCP server or a harness against it.
- Reference: every command, field and error code.
Checked against the engine on 2026-10-26. Every detail is in the README on GitHub.