initial commit
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
# ADR-0011: Go integration — the `Foreman` interface
|
||||
|
||||
**Status:** Accepted — 2026-05-23
|
||||
|
||||
## Context
|
||||
|
||||
The ultimate goal: use the M1 Pro **simply as a target for `go-llm`**.
|
||||
|
||||
**Verified (`v2/constructors.go`, `v2/ollama/ollama.go`):** `llm.OllamaCloud(key,
|
||||
WithBaseURL(...))` already targets "a private Ollama deployment that requires
|
||||
auth" — native `/api/chat` + `Authorization: Bearer <key>` against any base URL.
|
||||
foreman is exactly that on the wire (ADR-0003). So integration needs **no new
|
||||
provider** — only a clean, intent-revealing seam so call sites say "foreman," not
|
||||
"Ollama."
|
||||
|
||||
`go-llm`'s provider contract (`v2/provider`) is two methods, `Complete` and
|
||||
`Stream`; a future dedicated provider would implement them.
|
||||
|
||||
## Decision
|
||||
|
||||
Add a `llm.Foreman(baseURL, apiKey, opts...)` constructor to go-llm that delegates
|
||||
to the ollama native provider — the ollama translation happens behind the scenes:
|
||||
|
||||
```go
|
||||
func Foreman(baseURL, apiKey string, opts ...ClientOption) *Client {
|
||||
cfg := &clientConfig{}
|
||||
for _, opt := range opts {
|
||||
opt(cfg)
|
||||
}
|
||||
if cfg.baseURL != "" {
|
||||
baseURL = cfg.baseURL
|
||||
}
|
||||
return NewClient(ollamaProvider.New(apiKey, baseURL))
|
||||
}
|
||||
|
||||
// model := llm.Foreman("http://foreman.orgrimmar:PORT", token).Model("qwen3.6:35b")
|
||||
```
|
||||
|
||||
`baseURL` is required (foreman has no default public address). This is a
|
||||
deliberate **seam**: v1 is a pass-through to the `ollama` provider; a dedicated
|
||||
foreman provider can later replace the delegate to surface job IDs / async state
|
||||
without changing call sites.
|
||||
|
||||
### Three escalating levels
|
||||
|
||||
- **Level 0 — `llm.Foreman(...)` (now, the headline goal).** Transparent,
|
||||
synchronous, full native tool-calling / `think:false` / streaming. Queueing and
|
||||
model-swap management happen invisibly inside the daemon. Zero provider code.
|
||||
- **Level 1 — `foreman` client package (when an orchestration caller needs it).**
|
||||
A synchronous facade over the async `/jobs` surface: given messages, it manages
|
||||
an ephemeral webhook receiver, blocks until `done`, and returns result +
|
||||
artifacts (falling back to `GET /jobs/{id}` polling if it can't receive
|
||||
callbacks). For callers wanting async semantics — surfaced job IDs, no
|
||||
long-held connection — with a synchronous call signature.
|
||||
- **Level 2 — dedicated `provider.Provider` (only if needed).** Wraps Level 1 so
|
||||
foreman is a first-class go-llm backend exposing job IDs / state / artifacts the
|
||||
plain ollama provider can't. Built only if Level 0 proves insufficient.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Headline goal met with one constructor and no provider code.
|
||||
- Call sites are foreman-named and future-proofed by the seam.
|
||||
- Async ergonomics are available later without forcing webhook plumbing on
|
||||
callers, and without touching Level-0 users.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- **Just tell users to call `OllamaCloud` with a base URL.** Works identically
|
||||
today, but leaks the implementation ("it's Ollama") and offers no seam for
|
||||
future foreman-specific behavior. The named constructor is the requested
|
||||
"foreman interface."
|
||||
- **Ship a dedicated provider from day one (Level 2 first).** More code; bypasses
|
||||
the zero-friction win. Deferred.
|
||||
Reference in New Issue
Block a user