Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ One Go binary is both the CLI client and the background daemon (auto-started on
- This tool is the minimal slice: one dependency-free binary, a plain text protocol — trading rich MIME output for simplicity and zero setup.
- Model Context Protocol (MCP) Server is less composable than shell tools and infeasible to use from outside AI sessions.
- [mcp-repl](https://github.com/posit-dev/mcp-repl) for persistent Python/R with sandboxing, inline plot images, curated oversized output.
- [julia-mcp](https://github.com/aplavin/julia-mcp?tab=readme-ov-file) for Julia
- [julia-mcp](https://github.com/aplavin/julia-mcp), [AgentREPL.jl](https://github.com/samtalki/AgentREPL.jl) (Malt.jl workers), [Kaimon.jl](https://github.com/kahliburke/Kaimon.jl) (attaches to running Julia processes, debugging/introspection tools), [MCPRepl.jl](https://discourse.julialang.org/t/ann-mcprepl-jl-share-your-repl-with-your-ai-agent/130536) (shares your own REPL) for Julia
- Julia
- [DaemonMode.jl](https://github.com/dmolina/DaemonMode.jl) is the closest, but the client is itself a `julia` process and the server is started manually per port. [pi-julia](https://pi.dev/packages/pi-julia) wraps it for the pi agent.
- [DaemonicCabal.jl](https://github.com/tecosaur/DaemonicCabal.jl) only runs on Linux
- [Malt.jl](https://github.com/JuliaPluto/Malt.jl) manages isolated Julia worker processes _from within Julia_ (used by Pluto). Both run code in persistent, crash-isolated subprocesses, but Malt is a Julia library: its driver must be Julia, and it returns native typed values over Julia's serialization. This tool targets non-Julia callers — a single dependency-free binary speaking a text protocol, so any language/shell/agent can drive it and interpreter versions can be mixed freely.
4 changes: 3 additions & 1 deletion go/julia/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,13 @@ func (Adapter) EvalFileStmt(path string, args []string) string {
for i, a := range args {
argElems[i] = fmt.Sprintf(`String(hex2bytes("%s"))`, hex.EncodeToString([]byte(a)))
}
// Fresh module per run: scripts only share loaded packages and compiled code,
// evaluated inside a throwaway parent so no binding accumulates in Main.
return fmt.Sprintf(`let _p = String(hex2bytes("%s")), _old = copy(ARGS), _oldpf = Base.PROGRAM_FILE
empty!(ARGS); append!(ARGS, AbstractString[%s])
Base.PROGRAM_FILE = _p
try
Base.include(Main, _p)
Base.include(Core.eval(Module(), Expr(:module, true, :Script, Expr(:block))), _p)
finally
empty!(ARGS); append!(ARGS, _old)
Base.PROGRAM_FILE = _oldpf
Expand Down
2 changes: 1 addition & 1 deletion go/julia/runtime.jl
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ function run(hex_code, print_result)
flush(stdout)
_write_control("OK")
else
short, smart, full = _render_error(err, bt)
short, smart, full = Base.invokelatest(_render_error, err, bt)
flush(stdout)
_write_control(string("ERR ", _hex(short), " ", _hex(smart), " ", _hex(full)))
end
Expand Down
17 changes: 9 additions & 8 deletions go/julia_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -401,18 +401,19 @@ func TestJuliaFileEvalArgsAndState(t *testing.T) {
cwd := sharedJuliaCwd(t)
script := filepath.Join(t.TempDir(), "script.jl")
write := func(body string) { require.NoError(t, os.WriteFile(script, []byte(body), 0644)) }
write("println(join(ARGS, \",\"))\nfile_marker = 11\n")
helper := filepath.Join(filepath.Dir(script), "helper.jl")
require.NoError(t, os.WriteFile(helper, []byte("helper_marker = 7\n"), 0644))
write("println(join(ARGS, \",\"))\nstruct FileS; a::Int; end\nfile_marker = 11\ninclude(\"helper.jl\")\nprintln(helper_marker)\n")

res := repldOK(t, socketPath, cwd, "julia", script, "a", "b")
require.Equal(t, "a,b\n", res.stdout)
check := repldOK(t, socketPath, cwd, "julia", "-e", "println(file_marker)")
require.Equal(t, "11\n", check.stdout)
check = repldOK(t, socketPath, cwd, "julia", "-e", "println(length(ARGS))")
require.Equal(t, "0\n", check.stdout)
require.Equal(t, "a,b\n7\n", res.stdout)
check := repldOK(t, socketPath, cwd, "julia", "-e", "println(isdefined(Main, :file_marker), length(ARGS))")
require.Equal(t, "false0\n", check.stdout)

write("println(\"edited\")\n")
// Globals from a previous run are gone; a changed struct redefines cleanly.
write("println(isdefined(@__MODULE__, :file_marker))\nstruct FileS; a::Float64; end\n")
res = repldOK(t, socketPath, cwd, "julia", script)
require.Equal(t, "edited\n", res.stdout)
require.Equal(t, "false\n", res.stdout)
}

// A request queued behind another eval must not interrupt running eval when client disconnects.
Expand Down
5 changes: 3 additions & 2 deletions skills/repld/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: repld
description: "Evaluate Julia/Python/R/Wolfram in long-lived background sessions so imports, variables, and project state persist across calls. Use for iterative work — package development, REPL-style experiments, tests, benchmarks — where starting fresh wastes tokens and time. Use prudently for reproducible research."
description: "Evaluate Julia/Python/R/Wolfram in long-lived background sessions so imports, variables, and project state persist across calls. Use for iterative work — package development, REPL-style experiments, tests, benchmarks — where starting fresh wastes tokens and time. Prefer file-based execution for reproducible research."
---

## Preferred workflow
Expand All @@ -24,7 +24,8 @@ repld --session scratch julia -E 'x = 1' # named session across directories
cd /tmp && repld --session scratch julia -E 'x' # reuse existing named session

# File mode: write/update scripts, then eval them in the warm session
repld julia analysis.jl
repld julia analysis.jl # fresh module per run: packages persist, globals don't
repld julia -e 'include("analysis.jl")' # keep globals in Main
repld python3 train.py 50 # 50 → sys.argv[1]
```

Expand Down
Loading