Self-hosted coding-assistant backend
Power an internal Cursor / Continue / Aider deployment with Bigwave, with a feedback loop that fine-tunes a code model on the team's own corpus.
The problem
Your engineers want Cursor's productivity. AppSec said no — source code can't leave the perimeter. Your options are:
- Buy enterprise Cursor. Long procurement, still some data-handling concessions, expensive at scale.
- Switch everyone to a self-host-friendly tool (Continue.dev, Aider, Tabby). The IDE side works; the back-end stack is bring your own.
The third option is the gap Bigwave fills: be the OpenAI-compatible back-end behind whichever IDE-side tool your team picks, with a feedback loop that turns thumbs / regenerate / reject signals into a DPO-tuned model that knows your team's idioms.
The Bigwave shape
| Component | Role |
|---|---|
ServingEngine.ollama (or vLLM roadmap) | Serves a code-capable model: qwen2.5-coder:7b, deepseek-coder:6.7b, or your own |
OpenAI-compatible /v1/chat/completions | What Continue.dev / Cursor / Aider talk to |
FeedbackStore | Captures thumbs from the IDE-side tool's feedback widget |
CompletionProvenanceCache | Resolves completion_id → (model, adapter, workspace) so feedback rows are linkable |
dataset_builder | Turns thumbs into preference pairs |
TrainingBackend.unsloth (DPO recipe) | Fine-tunes the code model on the team's preferences |
EvalGate | Refuses regressed adapters at activation |
AuditLogRepository | Tells AppSec what data was used and when |
End-to-end walkthrough
1. Pick a code-capable model
Code models matter more than chat models here. Defaults that work well on consumer hardware:
| Model | VRAM (Q4) | Strengths |
|---|---|---|
qwen2.5-coder:7b | ~6 GB | Permissive licence, strong on Python/JS/TS, good context window |
deepseek-coder:6.7b | ~5 GB | Fast, focused on coding, lower-latency completions |
codellama:13b | ~10 GB | Older but well-understood |
./install.sh --non-interactive \
--non-interactive \
--model=qwen2.5-coder:7b \
--port=80802. Wire Continue.dev
Continue.dev is the easiest IDE integration today. Install the extension in VS Code / JetBrains, then point its config.json at Bigwave:
// ~/.continue/config.json
{
"models": [
{
"title": "Bigwave (Qwen Coder 7B)",
"provider": "openai",
"model": "qwen2.5-coder:7b",
"apiBase": "http://bigwave.internal:8000/v1",
"apiKey": "BIGWAVE_BEARER_HERE"
}
],
"tabAutocompleteModel": {
"title": "Bigwave Autocomplete",
"provider": "openai",
"model": "qwen2.5-coder:7b",
"apiBase": "http://bigwave.internal:8000/v1",
"apiKey": "BIGWAVE_BEARER_HERE"
}
}That's it. Continue.dev's chat panel, autocomplete, and command palette now route through bigwave. Source code stays on your network.
3. Capture feedback
Continue.dev posts an acceptance event when a developer accepts or rejects a completion. Bigwave's feedback endpoint accepts this signal:
import requests
# Map Continue.dev's "accepted" / "rejected" to bigwave's rating.
rating = 1 if accepted else -1
requests.post(
f"http://bigwave.internal:8000/v1/chat/completions/{completion_id}/feedback",
headers={"Authorization": f"Bearer {bearer}"},
json={"rating": rating, "label": "completion-accepted" if accepted else "completion-rejected"},
)Where completion_id comes from the chat completion response's id field (which Bigwave stamps on every response).
A small Continue.dev plugin / userscript bridges acceptance → bigwave-feedback. You can also instrument this in your IDE-side extension if you've forked Continue.
4. Wait for signal
The DPO loop wants ~500–2000 preference pairs to produce a measurable delta. With an active engineering team this lands within a couple of weeks. The Loop dashboard at http://bigwave.internal:8080 shows the current rollup.
A useful interim ritual: weekly review of the highest-disagreement buckets (where the model's accepted-rate is lowest) — those are your fine-tune targets.
5. Build the preference dataset
curl -H "Authorization: Bearer $BIGWAVE_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
"http://bigwave.internal:8000/admin/feedback/build-dataset" \
-d '{
"strategy": "thumbs_pair",
"since": "2026-04-01T00:00:00Z",
"model": "qwen2.5-coder:7b",
"min_pairs": 500
}'
# {"dataset_id": "ds-prefs-...", "n_pairs": 1247, ...}Or via the Web UI: Feedback dashboard → pick thumbs_pair strategy → Build dataset → Train DPO →. The Training screen prefills with recipe="dpo".
6. Train DPO
curl -H "Authorization: Bearer $BIGWAVE_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
"http://bigwave.internal:8000/admin/training/jobs" \
-d '{
"base_model": "qwen2.5-coder:7b",
"dataset_id": "ds-prefs-...",
"recipe": "dpo",
"hparams": {"lr": 5e-6, "beta": 0.1, "epochs": 1},
"eval_split": 0.1
}'DPO is a single-pass fine-tune with low learning rate; the recipe is conservative by default to avoid clobbering the base model's general capability.
7. Eval against the team's golden set
A code-team golden set is best assembled organically: 50–200 representative prompts the team agrees the model should do well on. Examples:
- "Write a Python decorator that caches a function's result for N seconds." (style + correctness)
- "Add error handling to this fetch wrapper." (code-context understanding)
- "Refactor this React component to use the new hooks API." (idiom matching)
Score the candidate:
# Submit eval run against the candidate
curl -H "Authorization: Bearer $BIGWAVE_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
"http://bigwave.internal:8000/admin/evals/runs" \
-d @./team-golden-eval.jsonFor code, exact-match is usually too strict. Wire an LLM-as-judge scorer or compile/test-pass scorers (these are adjacent capabilities).
8. Activate
curl -H "Authorization: Bearer $BIGWAVE_ADMIN_TOKEN" \
"http://bigwave.internal:8000/admin/models/qwen2.5-coder:7b/adapters" \
-d '{"adapter_id": "adp-dpo-..."}'The drain-and-swap activation drops zero in-flight requests. Anyone using Continue.dev right now sees their next completion come from the new adapter; their current request finishes against the old one.
9. Verify the delta
The Loop dashboard shows the active adapter and the latest eval pass-rate. Drive a few representative prompts through both the base and the DPO'd adapter:
def chat(adapter_id, prompt):
return openai.OpenAI(...).chat.completions.create(
model="qwen2.5-coder:7b",
messages=[{"role": "user", "content": prompt}],
extra_body={"bigwave_adapter_id": adapter_id},
).choices[0].message.content
baseline = chat(None, "Write a context manager for a database transaction.")
tuned = chat("adp-dpo-...", "Write a context manager for a database transaction.")For a fair before/after, run both against your golden set and compare pass-rates. Document the delta.
What you'll have at the end
- A self-hosted code-completion back-end your engineers actually use, that AppSec signed off on.
- A DPO'd adapter measurably better than the base on your team's prompts.
- A continuous feedback loop where every accept/reject from the IDE becomes future training signal.
- An audit log of every dataset, training job, and adapter activation — useful when AppSec asks "what data did you train on."
What's not covered here
- Tabby integration. The Tabby completion server has a different API; Bigwave's OpenAI-compat surface fits Continue.dev / Aider / Cursor better. Tabby integration is on the roadmap roadmap.
- Embedding inline-completion latency below ~100ms. Cursor's edge is sub-100ms first-token; on a single-GPU Bigwave host you should expect 200–500ms. Acceptable for chat panel, not for every-keystroke autocomplete on a large codebase.
- Multi-tenant per-team adapters. The workspace fence supports this, but you need a routing layer in front of Bigwave to direct team-A's IDE traffic to adapter-A and team-B's to adapter-B.
- Code-quality eval scorers. Compile-rate / test-pass-rate scorers are roadmap; for now you'll be using LLM-as-judge or exact-match.
AppSec talking points
You'll be asked. The honest answers:
| Question | Answer |
|---|---|
| Does any code leave our network? | No. The ServingEngine.ollama adapter speaks to a local Ollama; the gateway makes no outbound paid-API call. Verify with curl --max-time 5 https://api.openai.com/ from inside the API container — it should fail. |
| What about model downloads? | Ollama pulls the base model once at install time from https://registry.ollama.ai/. After that, no outbound. You can mirror this internally and remove egress entirely. |
| Where is fine-tuned data stored? | In the configured BIGWAVE_DATA_DIR on the GPU host. WorkspaceFence keeps team-A's data invisible to team-B. |
| What about supply-chain? | License-check CI runs on every commit; GPL/AGPL are blocked from the runtime path. |
See also
- The loop concept — the full picture
- Eval-as-a-gate concept — how the gate protects against regression