# How to bind modules between tasks?

**URL:** https://discourse.julialang.org/t/how-to-bind-modules-between-tasks/135772
**Category:** General Usage
**Created:** [February 22, 2026, 9:39am UTC](https://discourse.julialang.org/t/how-to-bind-modules-between-tasks/135772 "2026-02-22T09:39:05Z")
**Posts on this page:** 7
**Page:** 1

<div class="post-metadata">

### Author: ![ubaldot](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ubaldot/32/220916_2.png) [@ubaldot](https://discourse.julialang.org/u/ubaldot)
#### Post date: [February 22, 2026, 9:39am UTC](https://discourse.julialang.org/t/how-to-bind-modules-between-tasks/135772/1 "2026-02-22T09:39:05Z")

</div>

I am trying to communicate to the repl remotely, but I am having problems in binding the `Main` module used by the repl and the `Main` module used by the server and I don’t know how to fix it.

I prepared a couple of scripts to reproduce the issue.

# Setup

### Server side

Create a `test_module.jl` with the following content:

```julia
module MyModule

using Sockets

# -------------------------------
# Server configuration
# -------------------------------
const _HOST = ip"127.0.0.1"
const _PORT = 8765
const _server_running = Ref(true)

# -------------------------------
# Handlers
# -------------------------------
function get_repl_variables(conn, id, params)
  println("active_module server: ", Base.active_module())
  println("Main objectid server: ", objectid(Main))
  println(names(Main, all=true))
end

# -------------------------------
# Client handler
# -------------------------------
function handle_client(conn::TCPSocket, addr)
  println("Client connected from $addr")
  try
    msg = read(conn, 1)
    get_repl_variables(conn, nothing, nothing)
  catch e
    println("Client error: $e")
  finally
    close(conn)
  end
end

# -------------------------------
# Server loop
# -------------------------------
function start_server()
  server = listen(_HOST,_PORT)
  println("Julia TCP server running on $_HOST:$_PORT")

  while _server_running[]
    try
      conn = accept(server)
      handle_client(conn,getpeername(conn))
    catch e
      println("Accept error: $e")
    end
  end

  println("Server stopped")
end

# -------------------------------
# Run server in background
# -------------------------------
@async start_server()

# End of module
end

```

The module is quite easy: it create a server that is supposed to interact with the repl.  
When a message is received, then the server calls the function `get_repl_variables()` that shall print some information about the `Main` module.

### Client side

Next, we should send some message to the server and see what happens in the repl.  
You can create a `send_request.sh` script with the following content (the script use zsh, but I think you can just change the shebang to use it with bash or similar).

```bash
#!/usr/bin/env zsh

HOST="127.0.0.1"
PORT=8765

# Send just a few bytes to trigger the server
echo -n "ping" | nc $HOST $PORT

```

Make the above script executable with `chmod +x send_request.sh`.

# Basic test

At this point, run `julia -i test_module.jl`. This should start the repl and create the server.  
Then, open another terminal windows and run `./send_request.sh`.

In the repl, you should see the following:

```julia-auto
active_module server: Main
Main objectid server: 8699491554302981441
[Symbol("##meta#60"), :Base, :Core, :Main, :MyModule, :eval, :include]

```

Now, from the repl, run `println("active_module repl: ", Base.active_module())` followed by `println("Main object id repl: ", objectid(Main))` and you should see the following

```julia-auto
active_module repl: Main
Main object id repl: 8699491554302981441

```

Finally, from the repl run `println(names(Main, all=true))` and you should see the following:

```julia-auto
[Symbol("##meta#60"), :Base, :Core, :Main, :MyModule, :eval, :include]

```

So far so good.

# Here comes the issue

Now, from the repl, type `A = 10`, followed by `println(names(Main, all=true))`. You should see the following:

```julia-auto
[Symbol("##meta#60"), :A, :Base, :Core, :Main, :MyModule, :eval, :include]

```

As you see, `A` is in the `Main` scope.

Next, run `./send_request.sh`. You get the following:

```julia-auto
[Symbol("##meta#60"), :Base, :Core, :Main, :MyModule, :eval, :include]

```

BOOM! The variable `A` is not included.

It seems that either the subprocess has its own `Main` that does not update when the repl `Main` is updated - which is strange given that both the `Main`:s have the same object\_id and in both cases is the active module - or that the `Main` used in the server side is somehow frozen or it reads from some cached values.

Interestingly enough, by running `MyModule.get_repl_variables(1,2,3)` from the repl, you get:

```julia-auto
active_module server: Main
Main objectid server: 8699491554302981441
[Symbol("##meta#60"), :A, :Base, :Core, :Main, :MyModule, :eval, :include]

```

I have no idea why it does not work when you go through the server, and TBH I don’t know if this is a bug or not. Perhaps the repl namespace has some dedicated area that I fail to see and from where I should pick up info instead of using `names(Main, all=true)` from the server side?  
Perhaps from the server I should query something like `names(REPLSecretPlace, all=true)` to get all the variables defined in the current interactive repl?

Or perhaps I should bind the repl `Main` with the server `Main` in some way? If so, I have no idea how to do that and any suggestion would be highly appreciated.

---

<div class="post-metadata">

### Author: ![technocrat](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/technocrat/32/220947_2.png) [@technocrat](https://discourse.julialang.org/u/technocrat)
#### Post date: [February 22, 2026, 10:56am UTC](https://discourse.julialang.org/t/how-to-bind-modules-between-tasks/135772/2 "2026-02-22T10:56:30Z")

</div>

Just to surface the obvious question: Why REPL for remote rather than Pluto? (I’m sure you have your reasons—just hoping to get it settled in any one else’s head.)

---

<div class="post-metadata">

### Author: ![ubaldot](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ubaldot/32/220916_2.png) [@ubaldot](https://discourse.julialang.org/u/ubaldot)
#### Post date: [February 22, 2026, 11:15am UTC](https://discourse.julialang.org/t/how-to-bind-modules-between-tasks/135772/3 "2026-02-22T11:15:19Z")

</div>

Well, for the sake of generality. 🙂

I think you can potentially create any simple frontend to connect to a Julia backend through very simple protocols while enjoying an interactive experience at the same time.

You would not be constrained to use off-the-shelf Pluto, Jupyter, and so on, but you can create your own.

For the record, I created a frontend using Vim9 and the only missing part is accessing variables defined in the REPL space, as discussed in the thread.

But regardless of the specific usage, in a more general setting: not being able to access the REPL namespace from a subprocess is just… strange.

---

<div class="post-metadata">

### Author: ![ubaldot](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ubaldot/32/220916_2.png) [@ubaldot](https://discourse.julialang.org/u/ubaldot)
#### Post date: [February 22, 2026, 11:57am UTC](https://discourse.julialang.org/t/how-to-bind-modules-between-tasks/135772/4 "2026-02-22T11:57:37Z")

</div>

FYI: This seems to work:

```julia-auto
function get_repl_variables(conn, id, params)
  println(@eval Main names(Main, all=true))
end

```

Is should be because I am using` @eval` I am forcing runtime evaluation whereas using just `names(Main, all=true)` happens at compile time.

---

<div class="post-metadata">

### Author: ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)
#### Post date: [February 22, 2026, 6:07pm UTC](https://discourse.julialang.org/t/how-to-bind-modules-between-tasks/135772/5 "2026-02-22T18:07:51Z")

</div>

> [@ubaldot](#):
>
> which is strange given that both the `Main`:s have the same object\_id and in both cases is the active module - or that the `Main` used in the server side is somehow frozen

My gut reaction would be a task being stuck in an older world age, specifically the initial state of methods and global variables where `A` didn’t exist. Try the internal `Base.tls_world_age()` in `get_repl_variables`. If those `UInt`s differ, `@invokelatest` can let the task access the latest world age, but there is probably a better way of handling a remote REPL without messing with world age.

EDIT: Looking at it again, I’m more certain that’s the reason. `@async start_server()` calls a function that calls `start_server`, which loops calls of `handle_client` that calls `get_repl_variables` that accesses `Main`. The top-level call’s duration is stuck in a world age, including `Main`’s state. From the [docs](https://docs.julialang.org/en/v1/manual/worldage/):

> In addition, each [`Task`](https://docs.julialang.org/en/v1/base/parallel/#Core.Task) stores a local world age that determines which modifications to the global binding and method tables are currently visible to the running task. The world age of the running task will never exceed the global world age counter, but may run arbitrarily behind it.

The subsequent code example of a stuck world age is in a `begin` block, not a function call or any of the functions and macros documented in Asynchronous Programming, so I’m not sure exactly what a `Task` means there.

`@eval` executes an expression in the global scope where the world age is updated, explaining your workaround. `@invokelatest` should work too, but it’s documented with a subtle note that may affect higher-order functions, `f` meaning the callable:

> If `f` is a global, it will be resolved consistently in the (latest) world as the call target. However, all other arguments (as well as `f` itself if it is not a literal global) will be evaluated in the current world age.

---

<div class="post-metadata">

### Author: ![ubaldot](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ubaldot/32/220916_2.png) [@ubaldot](https://discourse.julialang.org/u/ubaldot)
#### Post date: [February 23, 2026, 8:50am UTC](https://discourse.julialang.org/t/how-to-bind-modules-between-tasks/135772/6 "2026-02-23T08:50:25Z")

</div>

This.

I am new in Julia, and I was not aware of the world age mechanism (but now I am 😃 ).  
Nevertheless, at the very beginning I had the feeling that each task have its own `Main` scope and therefore I was looking for a way to bind one each other. But then, I read (and admittedly asked AI) that the `Main` is common, so the only option left to justify such a behaviior is that the repl and the server task were looking at different “snapshot” in time of the same `Main`.  
Hence, I thought that in this case you may have two different snapshots of `Main`: one at compile time and one at runtime, where the server task is looking at the former and the repl at the latter. I didn’t land too far away. 🙂

Now, I learned that in reality you have `N` “snapshots” of `Main` and the sever task was simply pointing to an old snapshot. Hence, I think that the utilization of `@invokelatest` is the most accurate way to solve the issue given the mental model of time-spaced `Main` snapshots. Yet, for this use-case, and by following a different mental model, `@eval` is not wrong either given that its purpose is for runtime evaluation, and in this case `@eval` would most likely pick the latest and greatest `Main` shapshot.

Note that here, the `Main` used by the server task is a snapshot at compile time (note that at runtime it enters an infinite loop, so its `Main` snapshot must be the one at compile time) and therefore a `names(Main, all=true)` would return what is defined in the `Main` snapshot at compile time.

Does it make sense?

Nevertheless, I cannot test if `@invokelatest` work at the moment, but I will give it a shot this evening (I live in central Europe). But the reproducible code is at the top of this thread and you can just copy/paste: if someone wants to give it a shot in the meantime, please go ahead! 🙂

---

<div class="post-metadata">

### Author: ![ubaldot](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ubaldot/32/220916_2.png) [@ubaldot](https://discourse.julialang.org/u/ubaldot)
#### Post date: [March 1, 2026, 3:54pm UTC](https://discourse.julialang.org/t/how-to-bind-modules-between-tasks/135772/7 "2026-03-01T15:54:44Z")

</div>

> [@ubaldot](#):
>
> `./send_request.sh`

I have finally found some time to test the solution with `@invokelatest` and it works fine.
