Skip to content

Commit e7b8003

Browse files
authored
Merge pull request #2822 from bmahabirbu/sandbox-router
Add router mode support to ramalama sandbox
2 parents 6395e43 + 851b157 commit e7b8003

10 files changed

Lines changed: 377 additions & 102 deletions

File tree

container-images/pi-agent/Containerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ FROM quay.io/fedora/fedora:44
22

33
ARG PI_VERSION=0.80.7
44
ARG NODE_VERSION=26.5.0
5-
ARG PI_LLAMA_CPP_VERSION=0.8.1
5+
ARG PI_LLAMA_SERVER_VERSION=1.1.0
66
ARG PI_WEB_ACCESS_VERSION=0.13.0
77
ARG TARGETARCH
88

@@ -43,7 +43,7 @@ RUN echo "Include /home/node/.ssh/config" >> /etc/ssh/ssh_config \
4343
&& echo "StrictHostKeyChecking accept-new" >> /etc/ssh/ssh_config
4444

4545
RUN npm install -g --ignore-scripts @earendil-works/pi-coding-agent@${PI_VERSION} \
46-
&& pi install npm:pi-llama-cpp@${PI_LLAMA_CPP_VERSION} \
46+
&& pi install npm:pi-llama-server@${PI_LLAMA_SERVER_VERSION} \
4747
&& pi install npm:pi-web-access@${PI_WEB_ACCESS_VERSION} \
4848
&& npm cache clean --force \
4949
&& echo '{"workflow":"none"}' > /home/node/.pi/web-search.json \

docs/options/models-max.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
####> This option file is used in:
2-
####> ramalama serve
2+
####> ramalama sandbox goose, ramalama sandbox opencode, ramalama sandbox pi, ramalama serve
33
####> If this file is edited, make sure the changes
44
####> are applicable to all of those.
55
#### **--models-max**=*integer*
66
Maximum number of models to load concurrently in router mode (default: 4).
7-
Only used when `ramalama serve` is invoked in router mode (zero or multiple models).
7+
Only used when invoked in router mode (zero or multiple models).

docs/options/prompt.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
####> This option file is used in:
2+
####> ramalama sandbox goose, ramalama sandbox opencode, ramalama sandbox pi
3+
####> If this file is edited, make sure the changes
4+
####> are applicable to all of those.
5+
#### **--prompt**
6+
Instructions for the sandbox agent to process non-interactively.

docs/ramalama-sandbox-goose.1.md.in

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,19 @@
44
ramalama\-sandbox\-goose - run Goose in a sandbox, backed by a local AI Model
55

66
## SYNOPSIS
7-
**ramalama sandbox goose** [*options*] *model* [arg ...]
7+
**ramalama sandbox goose** [*options*] [*model* ...]
88

99
## DESCRIPTION
1010
Run Goose in a container, connected to a local model server also running
1111
in a container. Goose uses the model for reasoning and tool calling.
1212

13-
When run with no arguments after the model, an interactive session is
14-
launched. If one or more arguments are provided, they are passed to the agent
15-
as instructions to process non-interactively. Commands may also be passed via
16-
stdin.
13+
When a single model is specified, the server loads that model directly.
14+
When zero or multiple models are specified, the server starts in router mode
15+
using llama.cpp's --models-dir, dynamically loading and unloading models.
16+
With no models, all GGUF models in the store are served.
17+
18+
Use **--prompt** to pass instructions for non-interactive processing.
19+
Commands may also be passed via stdin.
1720

1821
If the url is not given then two containers are started: a model server (llama-server) and the agent
1922
container. They communicate via container networking. When the agent session
@@ -54,6 +57,8 @@ Otherwise only the agent container starts using the given url for the openai com
5457

5558
@@option model-draft
5659

60+
@@option models-max
61+
5762
@@option name
5863

5964
@@option ncmoe
@@ -68,6 +73,8 @@ Otherwise only the agent container starts using the given url for the openai com
6873

6974
@@option privileged
7075

76+
@@option prompt
77+
7178
@@option pull
7279

7380
@@option runtime-args
@@ -122,7 +129,7 @@ ramalama sandbox goose -w ./src qwen3:4b
122129

123130
Request the agent to perform actions non-interactively:
124131
```
125-
ramalama sandbox goose -w ./src qwen3:4b Please analyze the source code in the current directory
132+
ramalama sandbox goose -w ./src qwen3:4b --prompt "Please analyze the source code in the current directory"
126133
```
127134

128135
Send instructions to the agent via stdin:

docs/ramalama-sandbox-opencode.1.md.in

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,19 @@
44
ramalama\-sandbox\-opencode - run OpenCode in a sandbox, backed by a local AI Model
55

66
## SYNOPSIS
7-
**ramalama sandbox opencode** [*options*] *model* [arg ...]
7+
**ramalama sandbox opencode** [*options*] [*model* ...]
88

99
## DESCRIPTION
1010
Run OpenCode in a container, connected to a local model server also running
1111
in a container. OpenCode uses the model for reasoning and tool calling.
1212

13-
When run with no arguments after the model, an interactive TUI session is
14-
launched. If one or more arguments are provided, they are passed to the agent
15-
as instructions to process non-interactively. Commands may also be passed via
16-
stdin.
13+
When a single model is specified, the server loads that model directly.
14+
When zero or multiple models are specified, the server starts in router mode
15+
using llama.cpp's --models-dir, dynamically loading and unloading models.
16+
With no models, all GGUF models in the store are served.
17+
18+
Use **--prompt** to pass instructions for non-interactive processing.
19+
Commands may also be passed via stdin.
1720

1821
If the url is not given then two containers are started: a model server (llama-server) and the agent
1922
container. They communicate via container networking. When the agent session
@@ -52,6 +55,8 @@ Otherwise only the agent container starts using the given url for the openai com
5255

5356
@@option model-draft
5457

58+
@@option models-max
59+
5560
@@option name
5661

5762
@@option ncmoe
@@ -68,6 +73,8 @@ Otherwise only the agent container starts using the given url for the openai com
6873

6974
@@option privileged
7075

76+
@@option prompt
77+
7178
@@option pull
7279

7380
@@option runtime-args
@@ -122,7 +129,7 @@ ramalama sandbox opencode -w ./src qwen3:4b
122129

123130
Request the agent to perform actions non-interactively:
124131
```
125-
ramalama sandbox opencode -w ./src qwen3:4b Please analyze the source code in the current directory
132+
ramalama sandbox opencode -w ./src qwen3:4b --prompt "Please analyze the source code in the current directory"
126133
```
127134

128135
Send instructions to the agent via stdin:

docs/ramalama-sandbox-pi.1.md.in

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,19 @@
44
ramalama\-sandbox\-pi - run Pi in a sandbox, backed by a local AI Model
55

66
## SYNOPSIS
7-
**ramalama sandbox pi** [*options*] *model* [arg ...]
7+
**ramalama sandbox pi** [*options*] [*model* ...]
88

99
## DESCRIPTION
1010
Run Pi in a container, connected to a local model server also running
1111
in a container. Pi uses the model for reasoning and tool calling.
1212

13-
When run with no arguments after the model, an interactive session is
14-
launched. If one or more arguments are provided, they are passed to the agent
15-
as instructions to process non-interactively. Commands may also be passed via
16-
stdin.
13+
When a single model is specified, the server loads that model directly.
14+
When zero or multiple models are specified, the server starts in router mode
15+
using llama.cpp's --models-dir, dynamically loading and unloading models.
16+
With no models, all GGUF models in the store are served.
17+
18+
Use **--prompt** to pass instructions for non-interactive processing.
19+
Commands may also be passed via stdin.
1720

1821
Two containers are started: a model server (llama-server) and the agent
1922
container. They communicate via container networking. When the agent session
@@ -56,6 +59,8 @@ Otherwise only the agent container starts using the given url for the openai com
5659

5760
@@option model-draft
5861

62+
@@option models-max
63+
5964
@@option name
6065

6166
@@option ncmoe
@@ -72,6 +77,8 @@ Otherwise only the agent container starts using the given url for the openai com
7277

7378
@@option privileged
7479

80+
@@option prompt
81+
7582
@@option pull
7683

7784
@@option runtime-args
@@ -109,6 +116,16 @@ Run the Pi agent with default settings:
109116
ramalama sandbox pi qwen3:4b
110117
```
111118

119+
Run the Pi agent in router mode (serve all models in the store):
120+
```
121+
ramalama sandbox pi
122+
```
123+
124+
Run the Pi agent with multiple models in router mode:
125+
```
126+
ramalama sandbox pi qwen3:4b granite-code:3b
127+
```
128+
112129
Run the Pi agent with a custom image:
113130
```
114131
ramalama sandbox pi --pi-image myimage:v1 qwen3:4b
@@ -126,7 +143,7 @@ ramalama sandbox pi -w ./src qwen3:4b
126143

127144
Request the agent to perform actions non-interactively:
128145
```
129-
ramalama sandbox pi -w ./src qwen3:4b Please analyze the source code in the current directory
146+
ramalama sandbox pi -w ./src qwen3:4b --prompt "Please analyze the source code in the current directory"
130147
```
131148

132149
Send instructions to the agent via stdin:

ramalama/plugins/runtimes/inference/llama_cpp.py

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import os
77
import platform
88
import shutil
9+
import subprocess
910
import sys
1011
import tempfile
1112
from collections.abc import Mapping
@@ -312,7 +313,6 @@ def service_ready_check(self, conn: HTTPConnection, args: Any, model_name: Optio
312313
except ValueError:
313314
logger.debug(f"{self.name} {container_name} /models does not include a model list in the response")
314315
return False
315-
316316
if not model_name:
317317
if hasattr(args, 'MODEL') and isinstance(args.MODEL, str):
318318
model_name = New(args.MODEL, args).model_alias
@@ -539,13 +539,12 @@ def _serve_handler(self, args: argparse.Namespace) -> None:
539539

540540
super()._serve_handler(args)
541541

542-
def _serve_router(self, args: argparse.Namespace) -> None:
543-
"""Serve multiple models using llama.cpp router mode (container-only)."""
542+
def _build_router_engine(self, args: argparse.Namespace) -> Engine:
543+
"""Resolve models and build an Engine with bind mounts for router mode."""
544544
if not args.container:
545545
sys.exit("Error: multi-model router mode requires a container runtime.")
546546

547547
set_accel_env_vars()
548-
args.port = compute_serving_port(args)
549548
args.router_mode = True
550549

551550
if args.MODEL:
@@ -563,28 +562,46 @@ def _serve_router(self, args: argparse.Namespace) -> None:
563562
if not models:
564563
sys.exit("Error: no GGUF models found in the model store. Pull a model first with: ramalama pull <model>")
565564

566-
if args.container and not args.dryrun:
565+
if not args.dryrun and not getattr(args, "image", None):
567566
config = ActiveConfig()
568567
should_pull = config.pull in ["always", "missing", "newer"]
569568
args.image = ensure_image(config.engine, accel_image(config), should_pull=should_pull)
570569

571-
cmd = assemble_command(args)
570+
cmd = self.handle_subcommand("serve", args)
572571
engine = Engine(args)
573572
name = getattr(args, "name", None) or genname()
573+
args.name = name
574574
engine.add(["--label", "ai.ramalama", "--name", name, "--env=HOME=/tmp", "--init"])
575575

576576
for host_path, container_name in models:
577577
mount_path = f"/mnt/models/{container_name}"
578578
container_host_path = get_container_mount_path(host_path)
579-
engine.add([f"--mount=type=bind,src={container_host_path},destination={mount_path},ro"])
579+
engine.add([f"--mount=type=bind,src={container_host_path},destination={mount_path},ro{engine.relabel()}"])
580580

581581
engine.add([args.image] + cmd)
582+
return engine
583+
584+
def _serve_router(self, args: argparse.Namespace) -> None:
585+
"""Serve multiple models using llama.cpp router mode (container-only)."""
586+
args.port = compute_serving_port(args)
587+
engine = self._build_router_engine(args)
582588

583589
if args.dryrun:
584590
engine.dryrun()
585591
return
586592
engine.exec()
587593

594+
def serve_router_nonblocking(self, args: argparse.Namespace) -> None:
595+
"""Start the router-mode server non-blocking (for sandbox use)."""
596+
args.detach = True
597+
engine = self._build_router_engine(args)
598+
599+
if args.dryrun:
600+
engine.dryrun()
601+
return
602+
603+
subprocess.Popen(engine.exec_args)
604+
588605
@staticmethod
589606
def _migrate_store_ref_files(store: Any) -> None:
590607
"""Migrate any old-format ref files to JSON before enumeration."""

0 commit comments

Comments
 (0)