Skip to content

Add router mode support to ramalama sandbox#2822

Open
bmahabirbu wants to merge 3 commits into
containers:mainfrom
bmahabirbu:sandbox-router
Open

Add router mode support to ramalama sandbox#2822
bmahabirbu wants to merge 3 commits into
containers:mainfrom
bmahabirbu:sandbox-router

Conversation

@bmahabirbu

@bmahabirbu bmahabirbu commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Allow sandbox subcommands (pi, goose, opencode) to run with zero or multiple models using llama.cpp router mode
    • ramalama sandbox pi (no model) — serves all GGUF models from the store
    • ramalama sandbox pi model1 model2 — serves specified models
    • ramalama sandbox pi model1 — unchanged single-model behavior
  • Refactor _serve_router into _build_router_engine + serve_router_nonblocking for sandbox reuse
  • Convert positional ARGS to --prompt named option (required since MODEL is now nargs="*")
  • Add --models-max option to sandbox subparsers
  • Fix health check to handle both models/data keys and name/id fields in /models response
  • After server is healthy, query /v1/models to discover available model IDs and configure agents accordingly
Screencast.From.2026-06-30.00-26-20.mp4

This works best with the pi agent with the build in support for llama.cpp new router mode. if you want to test with this use container_build.sh build pi-agent so that u can have the container to test

right now the container isnt built to test yet

Signed-off-by: Brian brian@fedora

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds non-blocking router startup and model discovery in llama_cpp, updates sandbox execution for zero, one, or many models, and refreshes CLI docs and tests for list-based model parsing and --prompt.

Changes

Multi-model router sandbox

Layer / File(s) Summary
llama_cpp router engine builder and nonblocking serve
ramalama/plugins/runtimes/inference/llama_cpp.py
/models readiness parsing accepts models or data, router engine construction moves into _build_router_engine, and router startup gains a nonblocking path with subprocess.Popen.
Sandbox CLI multi-model args and shared helpers
ramalama/sandbox.py
Sandbox subcommands accept optional positional models, shared --workdir and --prompt helpers are wired in, and router-only --models-max is added.
Agent env/arg conditional model injection
ramalama/sandbox.py
Goose only sets GOOSE_MODEL when present, OpenCode builds config from router model IDs or a single model name, and Pi only appends --model when a model exists.
Single-model vs router orchestration in run_sandbox
ramalama/sandbox.py
run_sandbox() branches on model count, the single-model path waits for health before running the agent, and the router path starts the router, queries /v1/models, stores router model IDs, runs the agent, and stops containers in finally.
Sandbox option and manpage updates
docs/options/*.md, docs/ramalama-sandbox-*.1.md.in
models-max and prompt option docs expand to sandbox commands, and the sandbox man pages update synopsis, router-mode descriptions, examples, and non-interactive usage text.
Updated sandbox CLI and router-mode tests
test/unit/test_sandbox_cmd.py, test/e2e/test_sandbox.py
MODEL parsing now yields lists, router-mode assertions cover empty-model agent construction, --prompt parsing tests check the absent case, and e2e sandbox runs pass prompts via --prompt.

Sequence Diagram(s)

sequenceDiagram
  participant run_sandbox
  participant LlamaCppPlugin
  participant RouterServer
  participant Agent

  run_sandbox->>LlamaCppPlugin: start router runtime
  run_sandbox->>RouterServer: wait_for_healthy()
  run_sandbox->>RouterServer: GET /v1/models
  RouterServer-->>run_sandbox: router_model_ids
  run_sandbox->>Agent: run selected agent
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • containers/ramalama#2563: Both PRs change ramalama/plugins/runtimes/inference/llama_cpp.py around router-mode serving and model discovery.
  • containers/ramalama#2812: Both PRs touch ramalama/sandbox.py and sandbox tests around argument handling and Pi sandbox behavior.

Suggested reviewers

  • maxamillion
  • jhjaggars
  • cgruver
  • engelmi
  • swarajpande5
  • olliewalsh

Poem

🐇 Hop, hop—router lights the way,
One model, many models, all can stay.
--prompt whispers through the dusk,
While sandbox bunnies do their work.
IDs float by, containers hum,
And merry little hops are done.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 78.13% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding router mode support to ramalama sandbox.
Description check ✅ Passed The description is directly related to the changeset and accurately summarizes the router-mode sandbox updates.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for multi-model router mode in the sandbox environment using the llama.cpp runtime, allowing sandboxes to run with zero or multiple models. It adds CLI arguments like --prompt and --models-max, and updates the Goose, OpenCode, and Pi agents to handle router-mode configurations. The review feedback highlights two important improvements: first, initializing the connection variable to None in _query_router_models to prevent an UnboundLocalError in the finally block if connection setup fails; second, appending engine.relabel() to the bind mount options in llama_cpp.py to ensure compatibility with SELinux-enabled systems.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread ramalama/sandbox.py Outdated
Comment on lines +341 to +354
def _query_router_models(port) -> list[str]:
"""Query the llama.cpp router server for available model IDs."""
try:
conn = HTTPConnection("127.0.0.1", int(port), timeout=5)
conn.request("GET", "/v1/models")
resp = conn.getresponse()
if resp.status != 200:
return []
body = json.loads(resp.read())
return [m.get("id", "") for m in body.get("data", []) if m.get("id")]
except Exception:
return []
finally:
conn.close()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

In _query_router_models, if HTTPConnection initialization raises an exception (for example, if int(port) raises a ValueError), the local variable conn will not be bound. When the finally block executes, calling conn.close() will raise an UnboundLocalError, which propagates and masks the original exception. To prevent this, initialize conn = None before the try block and check if conn is not None before calling conn.close() in the finally block.

Suggested change
def _query_router_models(port) -> list[str]:
"""Query the llama.cpp router server for available model IDs."""
try:
conn = HTTPConnection("127.0.0.1", int(port), timeout=5)
conn.request("GET", "/v1/models")
resp = conn.getresponse()
if resp.status != 200:
return []
body = json.loads(resp.read())
return [m.get("id", "") for m in body.get("data", []) if m.get("id")]
except Exception:
return []
finally:
conn.close()
def _query_router_models(port) -> list[str]:
"""Query the llama.cpp router server for available model IDs."""
conn = None
try:
conn = HTTPConnection("127.0.0.1", int(port), timeout=5)
conn.request("GET", "/v1/models")
resp = conn.getresponse()
if resp.status != 200:
return []
body = json.loads(resp.read())
return [m.get("id", "") for m in body.get("data", []) if m.get("id")]
except Exception:
return []
finally:
if conn:
conn.close()
References
  1. An HTTPConnection object is not a context manager and cannot be used in a with statement. Use a try...finally block to ensure the connection is closed.

@@ -510,12 +512,29 @@ def _serve_router(self, args: argparse.Namespace) -> None:
engine.add([f"--mount=type=bind,src={container_host_path},destination={mount_path},ro"])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When mounting models in _build_router_engine, the bind mount options do not include engine.relabel(). On systems with SELinux enabled (such as Fedora or RHEL) and when args.selinux is set to True, this will lead to permission denied errors when the container tries to access the mounted models. Append engine.relabel() to the mount options to ensure proper SELinux labeling, consistent with other bind mounts in the codebase.

Suggested change
engine.add([f"--mount=type=bind,src={container_host_path},destination={mount_path},ro"])
engine.add([f"--mount=type=bind,src={container_host_path},destination={mount_path},ro{engine.relabel()}"])

@bmahabirbu

Copy link
Copy Markdown
Collaborator Author

@mikebonnet had to change ARGS to --prompt for sandbox wondering if thats ok open to suggestion how how to better fix the 2 greedy arguments which are now serve then args

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
ramalama/sandbox.py (1)

317-357: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add missing type hints on the new helpers.

These new helper signatures are missing return/parameter annotations, which weakens mypy coverage on the router path. As per coding guidelines, use type hints in Python code and ensure mypy compatibility.

Proposed fix
-def _run_sandbox_single_model(args: SandboxEngineArgsType, agent_cls: type[Agent]):
+def _run_sandbox_single_model(args: SandboxEngineArgsType, agent_cls: type[Agent]) -> None:
     """Run sandbox with a single model (original behavior)."""
@@
-def _query_router_models(port) -> list[str]:
+def _query_router_models(port: int | str) -> list[str]:
     """Query the llama.cpp router server for available model IDs."""
@@
-def _run_sandbox_router(args: SandboxEngineArgsType, agent_cls: type[Agent]):
+def _run_sandbox_router(args: SandboxEngineArgsType, agent_cls: type[Agent]) -> None:
     """Run sandbox in router mode (zero or multiple models)."""
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ramalama/sandbox.py` around lines 317 - 357, The new helper signatures in
ramalama.sandbox are missing explicit annotations, which weakens mypy coverage
on the router path. Add type hints to _run_sandbox_single_model,
_query_router_models, and _run_sandbox_router by annotating all parameters and
return types, and keep the existing Agent/SandboxEngineArgsType references so
the helpers remain mypy-compatible and consistent with the surrounding code.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ramalama/plugins/runtimes/inference/llama_cpp.py`:
- Around line 498-501: The router-mode image resolution in llama_cpp.py is
overwriting user CLI/runtime overrides by always using ActiveConfig values.
Update the logic around ActiveConfig, accel_image(), and ensure_image() so it
preserves args.image and args.engine when they are provided, and only falls back
to the config-derived image/engine when args.image is absent. Keep the existing
pattern used elsewhere in this file so Engine(args) continues to receive the
intended runtime selection.

In `@ramalama/sandbox.py`:
- Around line 50-55: The `--models-max` argument help in `sandbox.py` is
repeating the default value even though `ramalama/cli.py.add_argument` already
appends it automatically from `default=4`. Update the `parser.add_argument` help
text for `models_max` to remove the manual “(default: 4)” wording so the
rendered help only shows the default once.
- Around line 365-370: Handle the `--dryrun` path before invoking
`serve_router(...)` in `sandbox.py` so the router is never started for dry runs.
Move the dry-run check ahead of the router startup in the main flow around
`serve_router`, `agent_cls`, and `agent.engine.dryrun()`, and keep the router
startup only for non-dryrun execution. This ensures dry-run exits without
leaving any router process/container running.
- Around line 341-354: The _query_router_models helper can raise
UnboundLocalError in its finally block if int(port) or HTTPConnection(...) fails
before conn is assigned. Update _query_router_models to initialize conn before
the try or otherwise guard the conn.close() call in the finally block so
failures still return an empty list without masking the original error path.

In `@test/unit/test_sandbox_cmd.py`:
- Around line 430-445: The current test only exercises the empty-model fallback
by constructing OpenCode without router model IDs, so it does not verify the
real router flow. Update test_opencode_router_mode to pass args.router_model_ids
and assert the OpenCode.add_env_options path emits a provider.ramalama.models
map for those discovered models, while still omitting a top-level model entry.
Use the OpenCode and add_env_options symbols to locate the router-specific
config assembly and make the assertions match the actual /v1/models discovery
contract.

---

Outside diff comments:
In `@ramalama/sandbox.py`:
- Around line 317-357: The new helper signatures in ramalama.sandbox are missing
explicit annotations, which weakens mypy coverage on the router path. Add type
hints to _run_sandbox_single_model, _query_router_models, and
_run_sandbox_router by annotating all parameters and return types, and keep the
existing Agent/SandboxEngineArgsType references so the helpers remain
mypy-compatible and consistent with the surrounding code.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: f8a62c54-3915-4b09-9fcf-fb46f1210816

📥 Commits

Reviewing files that changed from the base of the PR and between 16fe7a4 and 0008790.

📒 Files selected for processing (3)
  • ramalama/plugins/runtimes/inference/llama_cpp.py
  • ramalama/sandbox.py
  • test/unit/test_sandbox_cmd.py

Comment thread ramalama/plugins/runtimes/inference/llama_cpp.py Outdated
Comment thread ramalama/sandbox.py Outdated
Comment thread ramalama/sandbox.py Outdated
Comment thread ramalama/sandbox.py
Comment on lines +365 to +370
serve_router(cast(argparse.Namespace, args))

if args.dryrun:
agent = agent_cls(args, "")
agent.engine.dryrun()
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Do not start the router before handling --dryrun.

Router mode calls serve_router(...) before the dry-run branch, then returns without the cleanup finally; if serve_router_nonblocking starts the server, --dryrun mutates local state and can leave the router container/process running.

Proposed fix
-    serve_router(cast(argparse.Namespace, args))
-
     if args.dryrun:
         agent = agent_cls(args, "")
         agent.engine.dryrun()
         return
 
+    serve_router(cast(argparse.Namespace, args))
+
     try:
         wait_for_healthy(args, partial(is_healthy))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
serve_router(cast(argparse.Namespace, args))
if args.dryrun:
agent = agent_cls(args, "")
agent.engine.dryrun()
return
if args.dryrun:
agent = agent_cls(args, "")
agent.engine.dryrun()
return
serve_router(cast(argparse.Namespace, args))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ramalama/sandbox.py` around lines 365 - 370, Handle the `--dryrun` path
before invoking `serve_router(...)` in `sandbox.py` so the router is never
started for dry runs. Move the dry-run check ahead of the router startup in the
main flow around `serve_router`, `agent_cls`, and `agent.engine.dryrun()`, and
keep the router startup only for non-dryrun execution. This ensures dry-run
exits without leaving any router process/container running.

Comment thread test/unit/test_sandbox_cmd.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
ramalama/sandbox.py (1)

317-341: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add complete annotations for the new helpers.

The new/changed helpers are missing return annotations, and _query_router_models() leaves port untyped. As per coding guidelines, **/*.py: “Use type hints in Python code and ensure mypy compatibility”.

Proposed fix
-from typing import Optional, cast
+from typing import Optional, Union, cast
@@
-def _run_sandbox_single_model(args: SandboxEngineArgsType, agent_cls: type[Agent]):
+def _run_sandbox_single_model(args: SandboxEngineArgsType, agent_cls: type[Agent]) -> None:
@@
-def _query_router_models(port) -> list[str]:
+def _query_router_models(port: Union[int, str]) -> list[str]:
@@
-def _run_sandbox_router(args: SandboxEngineArgsType, agent_cls: type[Agent]):
+def _run_sandbox_router(args: SandboxEngineArgsType, agent_cls: type[Agent]) -> None:

Also applies to: 359-359

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ramalama/sandbox.py` around lines 317 - 341, Add explicit type hints to the
helper definitions to match the mypy/type-hinting guidelines. In
_run_sandbox_single_model, annotate the return type as None since it only
performs side effects, and in _query_router_models, add a concrete type
annotation for port (for example, the integer port value it accepts). Keep the
existing behavior unchanged while updating the function signatures of
_run_sandbox_single_model and _query_router_models.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ramalama/sandbox.py`:
- Line 53: The sandbox argument parsing for --models-max currently allows 0 or
negative values, unlike the positive validation used by the llama.cpp serve
parser. Update the option handling in the sandbox parser path around the
models-max argument to validate that the value is strictly positive before
router mode uses it, and reject invalid input early with the same behavior as
the existing serve-side validator.
- Around line 377-381: The router startup path in _build_router_engine() should
not continue when _query_router_models() returns no IDs, because that indicates
model discovery failed rather than a valid empty state. Add an early failure
after assigning args.router_model_ids, before creating the agent_cls instance,
so startup exits consistently when model_ids is empty instead of passing an
unusable first_model.

---

Outside diff comments:
In `@ramalama/sandbox.py`:
- Around line 317-341: Add explicit type hints to the helper definitions to
match the mypy/type-hinting guidelines. In _run_sandbox_single_model, annotate
the return type as None since it only performs side effects, and in
_query_router_models, add a concrete type annotation for port (for example, the
integer port value it accepts). Keep the existing behavior unchanged while
updating the function signatures of _run_sandbox_single_model and
_query_router_models.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 03308acd-fa38-4cab-a933-f955c6d1da01

📥 Commits

Reviewing files that changed from the base of the PR and between 0008790 and f15cfa9.

📒 Files selected for processing (8)
  • docs/options/models-max.md
  • docs/options/prompt.md
  • docs/ramalama-sandbox-goose.1.md.in
  • docs/ramalama-sandbox-opencode.1.md.in
  • docs/ramalama-sandbox-pi.1.md.in
  • ramalama/plugins/runtimes/inference/llama_cpp.py
  • ramalama/sandbox.py
  • test/unit/test_sandbox_cmd.py
✅ Files skipped from review due to trivial changes (4)
  • docs/options/prompt.md
  • docs/options/models-max.md
  • docs/ramalama-sandbox-goose.1.md.in
  • docs/ramalama-sandbox-pi.1.md.in
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/unit/test_sandbox_cmd.py

Comment thread ramalama/sandbox.py
parser.add_argument(
"--models-max",
dest="models_max",
type=int,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Validate --models-max as positive.

Sandbox currently accepts 0 or negative values, unlike the llama.cpp serve parser’s positive validator. Reject invalid values before passing them into router mode.

Proposed fix
+def _positive_int(value: str) -> int:
+    parsed = int(value)
+    if parsed <= 0:
+        raise argparse.ArgumentTypeError("--models-max must be a positive integer")
+    return parsed
+
+
 def _add_router_args(parser: argparse.ArgumentParser) -> None:
@@
-        type=int,
+        type=_positive_int,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
type=int,
def _positive_int(value: str) -> int:
parsed = int(value)
if parsed <= 0:
raise argparse.ArgumentTypeError("--models-max must be a positive integer")
return parsed
def _add_router_args(parser: argparse.ArgumentParser) -> None:
...
type=_positive_int,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ramalama/sandbox.py` at line 53, The sandbox argument parsing for
--models-max currently allows 0 or negative values, unlike the positive
validation used by the llama.cpp serve parser. Update the option handling in the
sandbox parser path around the models-max argument to validate that the value is
strictly positive before router mode uses it, and reject invalid input early
with the same behavior as the existing serve-side validator.

Comment thread ramalama/sandbox.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
ramalama/plugins/runtimes/inference/llama_cpp.py (1)

275-288: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Require usable model IDs before marking router mode ready.

If /models returns entries without name or id, model_names becomes [""], and router mode is marked ready because the list is non-empty. Filter invalid entries first.

Proposed fix
         if models_list is None:
             logger.debug(f"{self.name} {container_name} /models does not include a model list in the response")
             return False
 
-        model_names = [m.get("name") or m.get("id", "") for m in models_list]
+        if not isinstance(models_list, list):
+            logger.debug(f"{self.name} {container_name} /models model list is not an array")
+            return False
+
+        model_names = []
+        for model in models_list:
+            if not isinstance(model, dict):
+                continue
+            model_id = model.get("name") or model.get("id")
+            if model_id:
+                model_names.append(model_id)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ramalama/plugins/runtimes/inference/llama_cpp.py` around lines 275 - 288, The
readiness check in the llama_cpp model listing logic is treating `/models`
entries with missing `name` and `id` as usable, which lets router mode become
ready with invalid model data. Update the model collection in `llama_cpp.py`
around the `models_list` and `model_names` handling so invalid entries are
filtered out before readiness is evaluated, and make the `self.name` router-mode
branch return False unless at least one non-empty model identifier is present.
Keep the fix localized to the existing model discovery path used by the
`New(...).model_alias` and router-ready logging logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@ramalama/plugins/runtimes/inference/llama_cpp.py`:
- Around line 275-288: The readiness check in the llama_cpp model listing logic
is treating `/models` entries with missing `name` and `id` as usable, which lets
router mode become ready with invalid model data. Update the model collection in
`llama_cpp.py` around the `models_list` and `model_names` handling so invalid
entries are filtered out before readiness is evaluated, and make the `self.name`
router-mode branch return False unless at least one non-empty model identifier
is present. Keep the fix localized to the existing model discovery path used by
the `New(...).model_alias` and router-ready logging logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7061b73d-75ec-4ee0-bfa9-e0091b2abfbb

📥 Commits

Reviewing files that changed from the base of the PR and between f15cfa9 and d9eb1eb.

📒 Files selected for processing (8)
  • docs/options/models-max.md
  • docs/options/prompt.md
  • docs/ramalama-sandbox-goose.1.md.in
  • docs/ramalama-sandbox-opencode.1.md.in
  • docs/ramalama-sandbox-pi.1.md.in
  • ramalama/plugins/runtimes/inference/llama_cpp.py
  • ramalama/sandbox.py
  • test/unit/test_sandbox_cmd.py
✅ Files skipped from review due to trivial changes (4)
  • docs/options/prompt.md
  • docs/ramalama-sandbox-goose.1.md.in
  • docs/ramalama-sandbox-opencode.1.md.in
  • docs/ramalama-sandbox-pi.1.md.in
🚧 Files skipped from review as they are similar to previous changes (2)
  • docs/options/models-max.md
  • test/unit/test_sandbox_cmd.py

@mikebonnet

mikebonnet commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

@mikebonnet had to change ARGS to --prompt for sandbox wondering if thats ok open to suggestion how how to better fix the 2 greedy arguments which are now serve then args

Sure, that sounds reasonable. It's probably not necessary to make --prompt greedy, the whole prompt could be passed as a single argument, which seems more intuitive.

You'll need to update test/e2e/test_sandbox.py to use the new argument format.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ramalama/plugins/runtimes/inference/llama_cpp.py`:
- Line 536: The sandbox startup path in llama_cpp.py is leaking detached
container IDs because subprocess.Popen(engine.exec_args) inherits stdout; update
this call so stdout is redirected away while stderr remains visible. Keep the
behavior inside the runtime launch path unchanged otherwise, and use the
existing engine.exec_args invocation in the sandbox startup flow to locate the
fix.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d2bfd553-decc-48e2-81a3-6a75cc036dc1

📥 Commits

Reviewing files that changed from the base of the PR and between 31394cd and 863b4d7.

📒 Files selected for processing (9)
  • docs/options/models-max.md
  • docs/options/prompt.md
  • docs/ramalama-sandbox-goose.1.md.in
  • docs/ramalama-sandbox-opencode.1.md.in
  • docs/ramalama-sandbox-pi.1.md.in
  • ramalama/plugins/runtimes/inference/llama_cpp.py
  • ramalama/sandbox.py
  • test/e2e/test_sandbox.py
  • test/unit/test_sandbox_cmd.py
✅ Files skipped from review due to trivial changes (3)
  • docs/ramalama-sandbox-goose.1.md.in
  • docs/ramalama-sandbox-opencode.1.md.in
  • docs/ramalama-sandbox-pi.1.md.in

engine.dryrun()
return

subprocess.Popen(engine.exec_args)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Suppress detached container IDs from sandbox startup.

podman/docker run -d writes the container ID to stdout; because Popen inherits stdout here, ramalama sandbox can print that ID before the agent UI. Redirect stdout while keeping stderr visible.

Proposed fix
-        subprocess.Popen(engine.exec_args)
+        subprocess.Popen(engine.exec_args, stdout=subprocess.DEVNULL)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
subprocess.Popen(engine.exec_args)
subprocess.Popen(engine.exec_args, stdout=subprocess.DEVNULL)
🧰 Tools
🪛 Ruff (0.15.20)

[error] 536-536: subprocess call: check for execution of untrusted input

(S603)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ramalama/plugins/runtimes/inference/llama_cpp.py` at line 536, The sandbox
startup path in llama_cpp.py is leaking detached container IDs because
subprocess.Popen(engine.exec_args) inherits stdout; update this call so stdout
is redirected away while stderr remains visible. Keep the behavior inside the
runtime launch path unchanged otherwise, and use the existing engine.exec_args
invocation in the sandbox startup flow to locate the fix.

@bmahabirbu

Copy link
Copy Markdown
Collaborator Author

@mikebonnet @olliewalsh ready-to-test if you have the time! @rhatdan too I think this one is pretty cool cause it has the baked in model spawing loading + pi already does the chat summarizations as well

@bmahabirbu

bmahabirbu commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

look at am17an/pi-llama-server#2

resolved so this pr is ready-to-test @mikebonnet @olliewalsh! Thanks @am17an for the changes on the pi extension

Comment thread container-images/pi-agent/Containerfile Outdated

RUN npm install -g --ignore-scripts @earendil-works/pi-coding-agent@${PI_VERSION} \
&& pi install npm:pi-llama-cpp@${PI_LLAMA_CPP_VERSION} \
&& pi install npm:pi-llama-server@1.1.0 \

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to define the pi-llama-server version as an ARG for consistency with the rest of the component versions.

Comment thread ramalama/sandbox.py Outdated
self.engine.add_env_option(f"OPENAI_HOST=http://localhost:{args.port}")
self.engine.add_env_option("OPENAI_API_KEY=ramalama")
self.engine.add_env_option(f"GOOSE_MODEL={self.model_name}")
if self.model_name:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

model_name is always defined and non-empty (except in the dryrun case). I'd suggest removing the conditionals around model_name to avoid confusion.

Comment thread ramalama/sandbox.py Outdated
serve_router(cast(argparse.Namespace, args))

if args.dryrun:
agent = agent_cls(args, "")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second arg here should be a non-empty string (dummy or something) to match the non-dryrun behavior.

Comment thread ramalama/sandbox.py Outdated
raise ValueError("Router mode requires a resolved serving port")
model_ids = _query_router_models(args.port)
args.router_model_ids = model_ids # type: ignore[attr-defined]
first_model = model_ids[0] if model_ids else ""

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be an assert that model_ids is non-empty (as enforced by _build_router_engine()), then an unconditional reference to model_ids[0].

@bmahabirbu

Copy link
Copy Markdown
Collaborator Author

@mikebonnet updated!

@mikebonnet mikebonnet left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is working great for me, really nice work!

@mikebonnet
mikebonnet enabled auto-merge July 21, 2026 16:02
bmahabirbu and others added 3 commits July 21, 2026 12:02
Allow sandbox subcommands (pi, goose, opencode) to run without a model
or with multiple models, using llama.cpp's router mode to dynamically
load/unload models via --models-dir.

- ramalama sandbox pi (no model) serves all store GGUF models
- ramalama sandbox pi model1 model2 serves specified models
- ramalama sandbox pi model1 works as before (single model)

Changes:
- Refactor _serve_router into _build_router_engine + serve_router_nonblocking
  for reuse from sandbox
- Change sandbox MODEL arg from single positional to nargs="*"
- Convert sandbox ARGS positional to --prompt named option
- Add --models-max option to sandbox subparsers
- Update agent classes (Pi, Goose, OpenCode) to handle empty model_name
  in router mode

Signed-off-by: Brian <brian@fedora>
Signed-off-by: Brian <bmahabir@bu.edu>
Replace pi-llama-cpp with Arman's pi-llama-server@1.0.4 extension which
handles provider registration internally via LLAMA_SERVER_URL. The
provider id is now just "llama-server" instead of "llama-server=<url>".

Also set --models-max default to 1 so the router unloads the current
model before loading a new one, freeing GPU memory.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Brian Mahabir <56164556+bmahabirbu@users.noreply.github.com>
Signed-off-by: Brian Mahabir <56164556+bmahabirbu@users.noreply.github.com>
@bmahabirbu
bmahabirbu deployed to macos-installer July 21, 2026 16:02 — with GitHub Actions Active
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants