feat(wiring): OpenAI-compatible local-model adapter (Ollama / LM Studio / vLLM / …) - #7
Merged
Merged
Conversation
Add createOpenAICompatibleCaller + createOpenAICompatibleCodexCaller to wiring.ts. Targets any HTTP server speaking /v1/chat/completions — Ollama, LM Studio, vLLM, llama.cpp server, OpenRouter, Azure OpenAI. One adapter, many backends. Env switching: - ASIL_LLM_BASE_URL → use the local adapter for the primary LLM - ASIL_LLM_MODEL → model id passed verbatim to the server - ASIL_LLM_API_KEY → optional Bearer (many local servers ignore auth) - ASIL_CODEX_BASE_URL / ASIL_CODEX_API_KEY → same for adversarial gate - Defaults to cloud Anthropic + OpenAI when env vars are unset loadEnv() now skips the ANTHROPIC_API_KEY requirement when ASIL_LLM_BASE_URL is set. Air-gapped / regulated deployments aren't forced to set a dummy key. Cost-controller graceful for non-tier model ids: calculateCallCost returns Decimal(0) when the model isn't in the pricing table. Token caps still enforce — wire cost is $0 in local mode but token counts flow through (via server's `usage` field, or chars/4 estimate when omitted). run-a.ts and run-b.ts switch adapters automatically based on ASIL_LLM_BASE_URL / ASIL_CODEX_BASE_URL. Tests: +8 in asil-runners covering the new adapter (request shape, auth header presence/absence, token estimation fallback, custom estimator, trailing-slash normalization, error handling) + the CodexCaller variant + loadEnv's local-mode skip. Total project tests: 363 → 371. Docs: new examples/local-llm.md walkthrough (Ollama / LM Studio / vLLM / mixed-mode recipes + cost-accounting + when-NOT-to-use). Root README updated with three subsections (analyzer, local models, Python profile) flagging the new surfaces.
This was referenced Jun 17, 2026
telivity-otaip
added a commit
that referenced
this pull request
Jun 17, 2026
#2) (#9) The cost-controller only recorded executor token spend; self-review (3 persona calls) and the adversarial gate were invisible to both the budget cap and the reported total — System A could spend ~3x what the checkpoint saw, and the kill switch couldn't fire mid-task. System B (papa) recorded an aggregate only AFTER the thinker fan-out, with no pre-flight check. Changes: - CodexCaller contract gains optional token fields; createCodexCaller parses OpenAI's usage block, createOpenAICompatibleCodexCaller estimates chars/4 when the server omits usage. The adversarial gate's spend was previously untracked AT THE SOURCE (the interface returned only { content }). - SelfReviewResult + AdversarialReviewResult carry tokenUsage. selfReview aggregates the three persona calls; adversarialReview surfaces the codex call's tokens. - loop.ts records every stage against the checkpoint: forceCheck before the self-review fan-out, recordAndCheck after self-review and after adversarial, kill→budget-exceeded on any over-budget result. The reported totalTokenUsage now sums executor + review + adversarial on ALL outcome paths (success and failure). - papa.ts forceChecks before the thinker fan-out so a request already at the ceiling never launches N parallel calls. The codex model id is free-form (not a ModelTier); cost-estimator returns $0 for ids absent from the pricing table (PR #7), so tokens are tracked even though the codex wire-cost line is $0. Adding codex pricing is a follow-on. Also adds docs/design/2026-06-17-criticals-sandbox-and-budget.md — the design for this fix (#2) AND critical #1 (sandbox hardening, Level 1 chosen). #1 ships as a separate follow-on PR. Tests: 380 → 384. New: cloud + local codex token surfacing, loop total-token accounting includes review+adversarial. No skips. Refs CODEX_REVIEW.md (#2).
telivity-otaip
added a commit
that referenced
this pull request
Jun 17, 2026
…) (#11) #7 — DomainAnswerStore keyed answers by question TEXT only, so two different files asking the same question (e.g. "What is the grace period?") collapsed to one answer; answering it in file A silently unblocked file B with A's answer. hashQuestion now takes (filePath, text) and hashes the composite. Same wording in a different file is a distinct question again. #9 — Captured transcripts wrote full prompts/responses to disk unredacted; they can carry pulled-in source and secrets. New redactSecrets() masks known token shapes (sk-ant-, sk-/sk-proj-, ghp_/gho_/…, github_pat_, AKIA/ASIA, AIza, xox*, Bearer values, and SECRET_NAME=value assignments). instrumentLLMCaller/CodexCaller redact by default (opt out with { redact: false }); the events file is created mode 0600. Tests: 387 → 400. New: hashQuestion file-scoping + path-normalization; redactSecrets per token shape + non-secret passthrough; instrumented callers redact by default and honor redact:false. No skips. Refs Codex review #7, #9 (private KB).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
One adapter, many backends.
createOpenAICompatibleCallertargets any HTTP server speaking the OpenAI-compatible/v1/chat/completionsAPI — Ollama, LM Studio, vLLM, llama.cpp server, OpenRouter, Azure OpenAI. This unlocks ASIL for regulated industries (healthcare, gov, finance, defense) that can't send code to cloud APIs.What ships
Adapter (in `packages/asil-runners/src/wiring.ts`):
```ts
createOpenAICompatibleCaller({
baseUrl: 'http://localhost:11434/v1',
apiKey: 'optional',
modelId: 'llama3.1:8b-instruct-q4_K_M',
estimateTokens: t => Math.ceil(t.length / 4), // optional override
}): LLMCaller
createOpenAICompatibleCodexCaller({ ... }): CodexCaller // for the adversarial gate
```
Shares a small internal POST helper with the existing `createCodexCaller` — same fetch pattern, same error-shape.
Env switching (run-a + run-b):
When `ASIL_LLM_BASE_URL` is unset, behaviour is unchanged from cloud-only deployments.
loadEnv relaxation: `ANTHROPIC_API_KEY` is no longer required when `ASIL_LLM_BASE_URL` is set. Air-gapped users don't have to set a dummy value.
Cost-controller graceful: `calculateCallCost` returns `Decimal(0)` when the model id isn't in the pricing table. Token caps still bite — wire cost is $0 in local mode, but token counts (from the server's `usage` field, or chars/4 fallback) still flow through.
What this changes for cloud users
Nothing. Without the env vars set, run-a and run-b construct the same Anthropic + OpenAI callers they always did. The adapter additions are pure-add.
Walkthrough
New `examples/local-llm.md` covers:
Tests
Files
Modified:
New:
Updated docs:
Closes the four-PR sequence
This is the fourth of four merged PRs from the planning session:
🤖 Generated with Claude Code