Files
OpenShell/examples/inference
Piotr Mlocek f869182d8a feat(sandbox): move inference execution to sandbox-local routing (!79) (!52)
> **🏗️ build-from-issue-agent**

Closes #79

## Summary

Replaces the gateway-proxied inference model (`ProxyInference` RPC) with sandbox-local execution. The sandbox now resolves routes from either a standalone YAML route file or a cluster bundle fetched via the new `GetSandboxInferenceBundle` RPC, then forwards requests directly to inference backends using `navigator-router`.

### Benefits of removing the gRPC hop

Moving inference execution from the gateway to the sandbox eliminates the gRPC round-trip for every inference request:

- **No payload size limits**: Direct HTTP forwarding replaces protobuf-over-gRPC serialization.
- **Streaming-ready**: Preserves connection semantics for future SSE support (e.g., `stream: true`).
- **Lower latency**: Direct sandbox → backend path instead of sandbox → gateway → backend round-trip.
- **Reduced gateway load**: Gateway only serves the control-plane bundle delivery RPC.
- **Simpler error model**: HTTP status codes flow through directly without gRPC wrapping.

## Changes Made

- **Proto**: Removed `ProxyInference` RPC. Added `GetSandboxInferenceBundle` RPC for route bundle delivery.
- **Gateway**: Replaced `proxy_inference` with `get_sandbox_inference_bundle`. Removed `Router` and `navigator-router` dependency — gateway is now control-plane only for inference.
- **Router**: Switched config file format from TOML to YAML. Added `Debug` impl that redacts API keys.
- **Sandbox**: New `InferenceContext` with local `Router` + route cache. Routes loaded from `--inference-routes` file (standalone) or cluster bundle via gRPC, with 30s background refresh. New `--inference-routes` / `NAVIGATOR_INFERENCE_ROUTES` CLI arg.
- **Dev sandbox**: Added `inference-routes.yaml` at repo root with default NVIDIA NIM route. `mise run sandbox` now mounts it automatically and supports `-e` flag to forward env vars (e.g., `mise run sandbox`).
- **Example**: Added standalone example (`examples/inference/routes.yaml`) and rewrote README to cover both standalone and cluster workflows.
- **E2E tests**: Updated existing test names/docs. Added tests for Anthropic messages protocol and multi-route policy filtering.

## Tests Added

- **Unit (21 new):** `router_error_to_http` (6 variants), `load_from_file` YAML round-trip (5), `resolve_sandbox_inference_bundle` gRPC handler (6), `build_inference_context` route loading (4)
- **E2E (2 new):** Anthropic messages protocol routing, route filtering by `allowed_routes`
- **Total:** 188 unit tests passing across 3 crates

## Documentation Updated

- `architecture/gateway.md`: Removed router, updated Inference Service
- `architecture/sandbox.md`: Updated orchestration flow, InferenceContext, route loading
- `architecture/inference-routing.md`: Complete rewrite for sandbox-local architecture

## Verification

- [x] All unit tests passing (188 across 3 crates)
- [x] Pre-commit Rust checks passing (fmt, clippy, tests)
- [x] Architecture documentation updated
- [x] E2E tests updated with new test cases
- [x] Standalone example and documentation added
- [x] Default inference-routes.yaml with env passthrough for mise run sandbox
2026-02-25 09:57:42 -08:00
..

Inference Routing Example

This example demonstrates Navigator's inference interception and routing. A sandbox process makes standard API calls (OpenAI, Anthropic, etc.) and Navigator transparently intercepts, enforces policy, and reroutes them to a configured backend — without any code changes in the sandboxed application.

How It Works

  1. The sandbox proxy intercepts outgoing HTTPS connections.
  2. OPA policy determines the action: if the binary has no explicit network policy but inference routing is configured, the connection is inspected.
  3. The proxy TLS-terminates, parses the HTTP request, and detects known inference patterns (e.g., POST /v1/chat/completions).
  4. Matching requests are forwarded directly to the policy-allowed inference backend via the sandbox's local router. Non-inference requests are denied.

Files

File Description
inference.py Python script that calls the OpenAI SDK — works unmodified inside a sandbox
sandbox-policy.yaml Sandbox policy with inference routing enabled (route hint: local)
routes.yaml Example YAML route file for standalone (no-cluster) mode

Quick Start

There are two ways to run inference routing: with a cluster (managed routes, multi-sandbox) or standalone (single sandbox, routes from a file).

Standalone (no cluster)

Run the sandbox binary directly with a route file — no Navigator cluster needed:

# 1. Edit routes.yaml to point at your local LLM (e.g. LM Studio on :1234)
#    See examples/inference/routes.yaml

# 2. Run the sandbox with --inference-routes
navigator-sandbox \
  --inference-routes examples/inference/routes.yaml \
  --policy-rules <your-policy.rego> \
  --policy-data examples/inference/sandbox-policy.yaml \
  -- python examples/inference/inference.py

The sandbox loads routes from the YAML file at startup and routes inference requests locally — no gRPC server or cluster required.

With a cluster

1. Start a Navigator cluster

mise run cluster
navigator cluster status

2. Create an inference route

Point the route at any OpenAI-compatible endpoint (local or remote):

# Local model (e.g., LM Studio, Ollama, vLLM)
navigator inference create \
  --routing-hint local \
  --base-url http://<HOST>:<PORT> \
  --model-id <MODEL_NAME>

# Remote provider (e.g., OpenAI, NVIDIA NIM)
navigator inference create \
  --routing-hint local \
  --base-url https://api.openai.com \
  --api-key sk-... \
  --model-id gpt-4o-mini

If --protocol is omitted, Navigator auto-detects supported protocols by probing the endpoint (sends minimal requests with max_tokens: 1).

Verify the route:

navigator inference list

3. Run the example inside a sandbox

navigator sandbox create \
  --policy examples/inference/sandbox-policy.yaml \
  --keep \
  --name inference-demo \
  -- python examples/inference/inference.py

The script targets https://api.openai.com by default, but Navigator intercepts the connection and routes it to whatever backend the local route points at.

Expected output:

model=<backend model name>
content=NAV_OK

4. (Optional) Interactive session

navigator sandbox connect inference-demo
# Inside the sandbox:
python examples/inference/inference.py

5. Cleanup

navigator sandbox delete inference-demo
navigator inference delete <route-name>

Customizing the Policy

Edit sandbox-policy.yaml to control which routes are available:

inference:
  allowed_routes:
    - local          # matches the --routing-hint used in step 2
    - production     # add more route hints as needed

The allowed_routes list determines which inference routes a sandbox can use. Routes are matched by their routing_hint field.

Supported Protocols

Navigator detects and routes the following inference API patterns:

Pattern Protocol Kind
POST /v1/chat/completions openai_chat_completions Chat completion
POST /v1/completions openai_completions Text completion
POST /v1/responses openai_responses Responses API
POST /v1/messages anthropic_messages Anthropic messages