Skip to content
skyl

Running the sandbox

Flags, mounts, credentials, and wiring each adapter.

One command, four wire protocols, no credentials. The sandbox binds to loopback by default because it is not a security boundary.

Reference#

Terminal
go run ./cmd/skyl-sandbox [-addr host:port] [-api-key KEY]
FlagDefaultPurpose
-addr127.0.0.1:8099Listen address. Loopback by default, deliberately.
-api-keysandbox-keyThe credential every mount accepts.

Pass -addr 127.0.0.1:0 to let the OS choose a free port — which is what you want when starting it from tests, so parallel packages cannot collide.

The mounts#

MountBase URLAuth headerModels
anthropichttp://127.0.0.1:8099/anthropicx-api-keyclaude-opus-5, claude-sonnet-5, claude-haiku-4-5
openaihttp://127.0.0.1:8099/openai/v1Authorization: Bearergpt-5.6, gpt-5.4-nano
geminihttp://127.0.0.1:8099/gemini/v1betax-goog-api-keygemini-3.6-flash, gemini-3.6-pro
openaicompathttp://127.0.0.1:8099/compat/v1Authorization: Bearer (or none)gpt-5.6, gpt-5.4-nano

Each mount checks the header its real counterpart uses, so the adapters' credential handling is genuinely exercised rather than bypassed. The OpenAI-compatible mount also accepts no credential at all, because that is how Ollama, LM Studio and llama.cpp behave.

Wiring each adapter#

goCompiles
p := anthropic.New("sandbox-key",
	anthropic.WithBaseURL("http://127.0.0.1:8099/anthropic"))
// models: claude-opus-5, claude-sonnet-5, claude-haiku-4-5
p := anthropic.New("sandbox-key",
	anthropic.WithBaseURL("http://127.0.0.1:8099/anthropic"))
// models: claude-opus-5, claude-sonnet-5, claude-haiku-4-5

The model catalogue#

Each mount serves a small fixed list and rejects anything else with that provider's own 404.

Starting it from tests#

goCompiles
func startSandbox(t *testing.T) string {
	t.Helper()

	// :0 asks the OS for a free port, so parallel packages cannot collide.
	cmd := exec.Command("go", "run",
		"github.com/BAGOMBEKA-JOB-DEV/skyl/cmd/skyl-sandbox", "-addr", "127.0.0.1:0")
	stderr, err := cmd.StderrPipe()
	if err != nil {
		t.Fatal(err)
	}
	if err := cmd.Start(); err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { _ = cmd.Process.Kill() })

	sc := bufio.NewScanner(stderr)
	for sc.Scan() {
		if addr := parseAddr(sc.Text()); addr != "" {
			return addr
		}
	}
	t.Fatal("sandbox did not report a listen address")
	return ""
}
func startSandbox(t *testing.T) string {
	t.Helper()

	// :0 asks the OS for a free port, so parallel packages cannot collide.
	cmd := exec.Command("go", "run",
		"github.com/BAGOMBEKA-JOB-DEV/skyl/cmd/skyl-sandbox", "-addr", "127.0.0.1:0")
	stderr, err := cmd.StderrPipe()
	if err != nil {
		t.Fatal(err)
	}
	if err := cmd.Start(); err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { _ = cmd.Process.Kill() })

	sc := bufio.NewScanner(stderr)
	for sc.Scan() {
		if addr := parseAddr(sc.Text()); addr != "" {
			return addr
		}
	}
	t.Fatal("sandbox did not report a listen address")
	return ""
}

Troubleshooting#

404 on a model I expected

The catalogue is deliberately small. Use one of the IDs listed above, or a sandbox-* fault model.

401 from a mount

Each mount checks the header its real counterpart uses — x-api-key, Authorization: Bearer, x-goog-api-key. If you built the provider by hand, check you are using the matching adapter for the mount.

Can I run it on 0.0.0.0?

You can, with -addr. Do not. It authenticates nothing meaningfully, and loopback is the default for that reason.

Edit this page on GitHub