Tensor LabsTENSORLABS

Self-Host Laguna S 2.1 as an OpenAI-Compatible Coding API

On July 21, 2026, poolside released Laguna S 2.1, a 118-billion-parameter Mixture-of-Experts coding model with open weights on Hugging Face under the OpenMDW-1.1 license.

August 3, 20268 min read11 sectionsBy Ahmed Abdullah
Self-Host Laguna S 2.1 as an OpenAI-Compatible Coding API

Introduction

On July 21, 2026, poolside released Laguna S 2.1, a 118-billion-parameter Mixture-of-Experts coding model with open weights on Hugging Face under the OpenMDW-1.1 license. It scores 70.2% on Terminal-Bench 2.1, ahead of open models several times its size, and poolside published the evaluation harness alongside the score. In this tutorial you will stand Laguna S 2.1 up on vLLM, expose it through an OpenAI-compatible API, wire a coding agent loop on top of it, and end with a working PR-diff reviewer that never sends a byte of your code to a third party.

What poolside Laguna S 2.1 is

Laguna S 2.1 is a sparse Mixture-of-Experts model: 118B total parameters, roughly 8B activated per token. That ratio is the whole trick. You pay for the memory of a 118B model but the per-token compute of an 8B one, which is what makes single-node serving realistic. poolside ships it with a context window of one million tokens, aimed squarely at agentic coding: tool calls, terminal work, multi-file edits. The OpenMDW-1.1 license permits commercial use and self-hosting, which is the property this entire tutorial depends on.

Two numbers matter for capacity planning. In BF16 the weights are roughly 236 GB, which means multi-GPU. With FP8 quantization the footprint roughly halves, which is how poolside's own line about running on a single NVIDIA DGX Spark works out. If you have two 141 GB H200s, or four 80 GB A100s, you are in business (estimates, not gospel: measure on your own rack before promising anyone anything).

What vLLM gives you

vLLM is an inference server that batches concurrent requests, pages the KV cache so long contexts do not fragment memory, and puts an OpenAI-compatible surface over the top. That last part is the strategic bit. Every client library, agent framework, and internal tool that speaks to api.openai.com will speak to your Laguna S 2.1 endpoint by changing one base URL. The model becomes a swappable part instead of an integration

Serve Laguna S 2.1 on vLLM

Install vLLM and start the server. The first launch downloads the weights off Hugging Face, so give the box disk and patience: the download is measured in hundreds of gigabytes.

code
pip install vllm
# Two-GPU serving with FP8 quantization and a working context cap.
# Raise --max-model-len only when a task actually needs it; KV cache
# for 1M tokens is its own capacity project.
vllm serve poolside/Laguna-S-2.1 \
--quantization fp8 \
--tensor-parallel-size 2 \
--max-model-len 131072 \
--enable-auto-tool-choice \
--tool-call-parser hermes \
--port 8000

Verify it answers before building anything on top:

bash
curl -s http://localhost:8000/v1/models | python3 -m json.tool

If the model list comes back, you have a private coding endpoint. Everything from here is client-side.

Point an OpenAI client at your own hardware

The client code is deliberately boring. That is the point of the OpenAI-compatible surface: no SDK migration, no new abstractions to learn.

python
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="local")
response = client.chat.completions.create(
model="poolside/Laguna-S-2.1",
messages=[
{"role": "system", "content": "You are a precise coding assistant."},
{"role": "user", "content": "Write a Python function that parses "
"RFC 3339 timestamps without external dependencies."},
],
temperature=0.2,
max_tokens=1024,
)
print(response.choices[0].message.content)

Run it and check latency while you are here. Tokens per second on your hardware is the number that decides whether this endpoint backs an interactive tool or a batch queue.

Give the endpoint repo tools

A coding model becomes a coding agent when it can read files and run commands. Define both as tools, and keep the definitions honest: the model only sees what the schema describes.

python
import json
import subprocess
from pathlib import Path
REPO_ROOT = Path("/srv/repos/target-repo").resolve()
TOOLS = [
{
"type": "function",
"function": {
"name": "read_file",
"description": "Read a file from the repository.",
"parameters": {
"type": "object",
"properties": {"path": {"type": "string"}},
"required": ["path"],
},
},
},
{
"type": "function",
"function": {
"name": "run_command",
"description": "Run a read-only git or test command.",
"parameters": {
"type": "object",
"properties": {"command": {"type": "string"}},
"required": ["command"],
},
},
},
]
ALLOWED_PREFIXES = ("git diff", "git log", "git show", "pytest", "python -m pytest")
def read_file(path: str) -> str:
target = (REPO_ROOT / path).resolve()
if not target.is_relative_to(REPO_ROOT):
return "ERROR: path escapes the repository root"
return target.read_text()[:20000]
def run_command(command: str) -> str:
if not command.startswith(ALLOWED_PREFIXES):
return f"ERROR: command not in allowlist: {command}" 
result = subprocess.run(
command.split(), cwd=REPO_ROOT, capture_output=True, text=True, timeout=120
)
return (result.stdout + result.stderr)[:20000]

The allowlist is doing more work than the model here. Laguna S 2.1 decides what it wants to run; your prefixes decide what actually runs. Keep the two decisions separate and the blast stays contained when the model reasons its way somewhere strange.

Close the agent loop

Now the loop: send the conversation, execute any tool calls, feed results back, stop when the model stops asking or the step budget runs out.

python
def run_agent(task: str, max_steps: int = 12) -> str:
messages = [
{"role": "system", "content": (
"You are a code reviewer. Use read_file and run_command to inspect "
"the repository before answering. Cite file paths and line numbers."
)},
{"role": "user", "content": task},
]
for _ in range(max_steps):
response = client.chat.completions.create(
model="poolside/Laguna-S-2.1",
messages=messages,
tools=TOOLS,
temperature=0.2,
)
msg = response.choices[0].message
if not msg.tool_calls:
return msg.content
messages.append(msg)
for call in msg.tool_calls:
args = json.loads(call.function.arguments)
output = (read_file(**args) if call.function.name == "read_file"
else run_command(**args))
messages.append({
"role": "tool", "tool_call_id": call.id, "content": output,
})
return "ERROR: step budget exhausted before the agent finished"
print(run_agent("Review the diff on the current branch: git diff main...HEAD. "
"Flag correctness issues only, ranked by severity."))

Twelve steps is not a magic number. It is a budget, and budgets exist to make runaway loops a configuration error instead of a GPU bill.

Laguna S 2.1 self-hosted vs a hosted coding API

| | Self-hosted Laguna S 2.1 | Hosted frontier API | |---|---|---| | Code privacy | Never leaves your network | Provider-dependent, contract-dependent | | Cost shape | Fixed hardware, near-zero marginal | Zero fixed, per-token forever | | Terminal-Bench 2.1 | 70.2% | Frontier models score higher | | Latency control | Yours to tune | Whatever the provider is doing today | | Ops burden | Yours: CUDA, OOMs, upgrades | None | | Model updates | You choose when weights change | Silent upstream changes |

The honest reading of that table: hosted APIs still win on raw capability and convenience, and self-hosting wins on privacy, cost-at-volume, and control. Pick based on which column your constraints live in.

What this setup does not do

- It does not match the top hosted frontier coders on capability. 70.2% on Terminal-Bench 2.1 is remarkable for open weights; it is not the ceiling of the leaderboard.

- It does not manage multi-tenant auth, rate limiting, or observability. vLLM serves tokens; the platform around it is your job.

- It does not make the 1M-token context free. Long contexts eat KV cache, and KV cache is GPU memory you also wanted for batching.

- It does not absolve you from evals. If you adopt the endpoint without a task-level test set, you have swapped a measured dependency for an unmeasured one, and that is a downgrade wearing a cost-saving costume.

The final working example

The assembled version, one file, ready to run:

python
"""PR-diff reviewer on self-hosted poolside Laguna S 2.1 via vLLM.
Start the server first:
vllm serve poolside/Laguna-S-2.1 --quantization fp8 \
--tensor-parallel-size 2 --max-model-len 131072 \
--enable-auto-tool-choice --tool-call-parser hermes --port 8000
"""
import json
import subprocess
from pathlib import Path
from openai import OpenAI
REPO_ROOT = Path("/srv/repos/target-repo").resolve()
ALLOWED_PREFIXES = ("git diff", "git log", "git show", "pytest", "python -m pytest")
client = OpenAI(base_url="http://localhost:8000/v1", api_key="local")
TOOLS = [
{"type": "function", "function": {
"name": "read_file",
"description": "Read a file from the repository.",
"parameters": {"type": "object",
"properties": {"path": {"type": "string"}},
"required": ["path"]}}},
{"type": "function", "function": {
"name": "run_command",
"description": "Run a read-only git or test command.",
"parameters": {"type": "object",
"properties": {"command": {"type": "string"}},
"required": ["command"]}}},
]
def read_file(path: str) -> str:
target = (REPO_ROOT / path).resolve()
if not target.is_relative_to(REPO_ROOT):
return "ERROR: path escapes the repository root"
return target.read_text()[:20000]
def run_command(command: str) -> str:
if not command.startswith(ALLOWED_PREFIXES):
return f"ERROR: command not in allowlist: {command}"
result = subprocess.run(command.split(), cwd=REPO_ROOT,
capture_output=True, text=True, timeout=120)
return (result.stdout + result.stderr)[:20000]
def run_agent(task: str, max_steps: int = 12) -> str:
messages = [
{"role": "system", "content": (
"You are a code reviewer. Use read_file and run_command to inspect "
"the repository before answering. Cite file paths and line numbers.")},
{"role": "user", "content": task},
]
for _ in range(max_steps):
response = client.chat.completions.create(
model="poolside/Laguna-S-2.1", messages=messages,
tools=TOOLS, temperature=0.2)
msg = response.choices[0].message
if not msg.tool_calls:
return msg.content
messages.append(msg)
for call in msg.tool_calls:
args = json.loads(call.function.arguments)
output = (read_file(**args) if call.function.name == "read_file"
else run_command(**args))
messages.append({"role": "tool", "tool_call_id": call.id,
"content": output})
return "ERROR: step budget exhausted before the agent finished"
if __name__ == "__main__":
print(run_agent("Review the diff on the current branch: "
"git diff main...HEAD. Flag correctness issues only, "
"ranked by severity."))

When to self-host Laguna S 2.1

Self-host when the code cannot leave the building, when your monthly token bill already reads like a salary, or when you need the model version pinned while you build evals around it. Stay on hosted APIs when volume is low, when you need frontier-ceiling capability, or when nobody on the team wants to own a GPU pager. Laguna S 2.1 moves the line because it is the first open-weight coder in this capability band that fits on hardware a mid-size team can actually rack. The line will keep moving. Re-run the math each time it does.