Agent Tunnels

When a tunnel is started inside a space, the knot agent can own it for you so it keeps running even after the command that launched it exits. This is distinct from desktop tunnels, which run as a foreground process on your local machine for the life of that command.

Agent-owned tunnels live for the lifetime of the knot agent — they are not persisted, so they stop if the space (and therefore the agent) is stopped or restarted.

You can manage them either inside the space (knot tunnel ...) or remotely from the desktop (knot space tunnel ...). Both operate on the same agent-owned registry, so a tunnel started one way is visible to the other.


Prerequisites

  • The knot server must be configured for tunnels.
  • Inside a space: run knot tunnel ... in a terminal in the space. The --daemon, stop, and list subcommands require the knot agent.
  • From the desktop: run knot space tunnel ... (the space must be running). This drives the space’s agent remotely via the knot server.

Starting a Tunnel

Foreground (default)

knot tunnel http 8080 test1

This opens an HTTP tunnel exposing local port 8080 as <user>--test1.<tunnel_domain>. The tunnel stays active until you press Ctrl-C (or the process is killed), exactly as before.

By default the tunnel is created on the server that owns the space. A tunnel can instead be created on any other knot server — see Targeting Another Knot Server.

Use https instead of http for an HTTPS tunnel:

knot tunnel https 8443 secure1

Daemon mode

Add --daemon to hand the tunnel to the knot agent and return immediately:

knot tunnel http 8080 test1 --daemon
  • The command prints the tunnel URL and exits.
  • The tunnel is owned by the agent and runs until the agent exits, or until you stop it explicitly.
  • The agent uses its own server credentials, so no --server / --token flags are needed for a tunnel on the space’s own server — a different server can be targeted too, see Targeting Another Knot Server.

Targeting Another Knot Server

A tunnel — foreground or --daemon — can be created on any knot server this space can reach, not just the one that owns the space. Pass the target server and an API token valid on it:

knot tunnel http 8080 test1 --server https://other.knot.internal --token <api-token>

A Tunnels-only scoped token is enough — and the right key to hand a machine that should do nothing but expose a port; see API Tokens.

With --daemon the agent owns the tunnel like any other: it survives the launching command exiting, appears in knot tunnel list, and is stopped with knot tunnel stop.

Aliases work the same way. Configure them in the space’s config file (knot.toml in the current directory, ~/knot.toml, or ~/.config/knot/knot.toml) using the same layout knot connect writes on the desktop:

[client.connection.staging]
server = "https://staging.knot.internal"
token = "<api-token>"

[client.connection.prod]
server = "https://prod.knot.internal"
token = "<api-token>"
knot tunnel http 8080 web1 -a staging --daemon
knot tunnel http 8081 web2 -a prod --daemon

The --tunnel-server / --tunnel-token / --tunnel-alias spellings are accepted as synonyms of --server / --token / --alias, so the same flags work here and on knot space tunnel from the desktop.

Each tunnel is independent, so several can run at once against different servers and ports, mixing foreground and daemon freely.

The same works remotely from the desktop with knot space tunnel, using the --tunnel-* flags (named differently from -s / -t / -a because on knot space commands those select the server the CLI itself talks to):

knot space tunnel http myspace 8080 web1 --tunnel-server https://other.knot.internal --tunnel-token <api-token>
knot space tunnel http myspace 8081 web2 --tunnel-alias prod

Notes:

  • Daemon tunnels to other servers are not persisted, like all daemon tunnels: they stop when the space (and therefore the agent) stops or restarts.
  • The tunnel address is built from your username on the target server, and the tunnel counts against that server’s tunnel quota and permissions.
  • Without --server / --token / --alias (or the --tunnel-* equivalents) the tunnel is created on the server that owns the space, as before.
  • The space must be able to reach the target server over the network.
  • knot tunnel list inside the space and knot space tunnel list from the desktop share one registry, so both list and stop tunnels started against any server.
  • Scripts get the same via knot.space.tunnel_start(space, protocol, port, name, server, token) from the knot.space library.

Stopping a Tunnel

Stop a daemon-owned tunnel by its name:

knot tunnel stop test1

This only affects tunnels owned by the agent. A foreground tunnel started without --daemon is stopped by exiting that process (e.g. Ctrl-C).


Listing Tunnels

List all tunnels currently owned by the agent:

knot tunnel list

Example output:

Active tunnels:
  test1  8080  http  https://alice--test1.tunnels.knot.internal

A tunnel’s URL shows the domain of the server it runs on, so tunnels on other servers are distinguishable at a glance.


Remote Management

You can also manage a space’s agent-owned tunnels from the desktop with knot space tunnel, without opening a terminal in the space. These commands drive the same agent-owned registry through the knot server, so a tunnel started from the desktop appears in knot tunnel list inside the space, and vice versa. The space must be running.

Starting a tunnel

knot space tunnel http myspace 8080 test1

Daemon mode is implied — the command prints the tunnel URL and exits, and the tunnel is owned by the space’s agent. https is also supported.

A different knot server can be targeted with --tunnel-server / --tunnel-token or --tunnel-alias — see Targeting Another Knot Server.

Listing tunnels

knot space tunnel list myspace

Stopping a tunnel

knot space tunnel stop myspace test1

Scripting

Agent-owned tunnels can be managed from scripts via knot.space, which is available in all scriptling environments (startup scripts, MCP tools, and standalone scripts). The space must be running.

import knot.space as space

# Start a tunnel — returns the public URL
url = space.tunnel_start("myspace", "http", 8080, "myapp")
print(url)

# Start a tunnel on another knot server (token must be valid there)
url = space.tunnel_start("myspace", "http", 8081, "web1",
                         server="https://other.knot.internal", token="<api-token>")

# List active tunnels — each URL's domain shows which server it runs on
for t in space.tunnel_list("myspace"):
    print(t["name"], t["url"])

# Stop a tunnel by name
space.tunnel_stop("myspace", "myapp")

These call the same server API as the knot space tunnel CLI commands, so a tunnel started from a script is visible to knot tunnel list inside the space.


Behaviour Notes

  • Not persistent: agent-owned tunnels are not stored anywhere. If the space is stopped or restarted, all daemon tunnels are removed and must be started again.
  • One tunnel per name: starting a daemon tunnel with a name that already has one is rejected. Use knot tunnel stop <name> first.
  • Identity: the tunnel name is combined with the space owner’s username to form <user>--<name>.<domain>, just like desktop tunnels.