Skip to content

jx4e/hotwire

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 

Repository files navigation

hotwire

Native agent + MCP server that exposes a running HotSpot JVM to LLMs. See plan/plan.md for the full design.

Status

  • M0 — minimal Java target (target/Target.java) — done
  • M1 — JVMTI agent that mutates a static field from C++ — done
  • M2 — JSON-RPC over unix socket, driven by nc — done
  • M3 — MCP server fronting the agent, registered with Claude Code — done
  • M4 — instance handles, find_instances, get/set_field, invoke, release, list_classes, describe_class — done
  • M4.5 — constructor support in invoke, object handles in set_*_field — done
  • M5-minimal — define_class, redefine_class — done (ASM helper jar deferred)
  • Limitation pass — full modifier surface, char/byte/short types, array tools (array_length/get/set) — done
  • M6-M8 — see plan

Prereqs (macOS)

brew install openjdk@21 cmake
echo 'export PATH="/opt/homebrew/opt/openjdk@21/bin:$PATH"' >> ~/.zshrc

Build

# Java target
( cd target && javac Target.java )

# Native payload
( cd payload && JAVA_HOME=/opt/homebrew/opt/openjdk@21 cmake -S . -B build && cmake --build build )

Produces payload/build/libhotwire_payload.dylib (Linux: .so, Windows: .dll).

Run M2

Start the target with the agent:

java -agentpath:$PWD/../payload/build/libhotwire_payload.dylib -cp $PWD Target

The agent prints listening on /tmp/hotwire-<pid>.sock. Drive it from another terminal with nc -U:

SOCK=/tmp/hotwire-$(pgrep -f 'Target$').sock

# smoke test
echo '{"id":1,"method":"ping"}' | nc -U "$SOCK" -w 1

# read counter
echo '{"id":2,"method":"get_static_field","params":{"class":"Target","field":"counter","type":"I"}}' | nc -U "$SOCK" -w 1

# overwrite counter
echo '{"id":3,"method":"set_static_field","params":{"class":"Target","field":"counter","type":"I","value":99999}}' | nc -U "$SOCK" -w 1

# tell the target to exit
echo '{"id":4,"method":"set_static_field","params":{"class":"Target","field":"running","type":"Z","value":false}}' | nc -U "$SOCK" -w 1

Each nc -U ... -w 1 opens a fresh connection and times out after 1s of silence. Multiple requests on a single connection are supported (one per line) but nc makes that awkward; the dedicated CLI client comes in M3.

RPC methods (v1)

method params result
ping "pong"
get_static_field {class, field, type} field value (matches type)
set_static_field {class, field, type, value} "ok"

type is a JNI signature: I int, J long, Z boolean, F float, D double. class uses internal slash-form (java/lang/String, not java.lang.String).

Layout

target/        Minimal Java target used for M0/M1
payload/       C++ JVMTI agent (the meat)
mcp/           TypeScript MCP server (M3+)
demo-game/     JavaFX game used as the headline demo target (M6+)
plan/          Design doc

About

Live JVM introspection and patching for LLM agents over MCP. Attach to any running java process, mutate state, invoke methods, and hot-redefine bytecode.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors