15 KiB
Local Backend Convenience Implementation Plan
Status: Complete.
Purpose
This document is the decision-complete implementation plan for local backend convenience. It is written for a coding agent that will implement each stage in order.
The feature roadmap owns the motivation, consumer paths, policy choices, compatibility requirements, non-goals, and target end state. This document owns the exact proposed API, file-level changes, implementation sequence, test ownership, documentation work, validation commands, and completion gates.
Implementation Rules
- Complete the stages in order. Keep the root package compiling and its focused tests passing at every stage boundary.
- Preserve unrelated working-tree changes. The feature roadmap may already be uncommitted when implementation begins; retain it.
- Follow every policy under
docs/policy/, the task-specific reading guide indocs/development.md, and the accepted behavior inlocal-backend.md. - Keep the API in the root
promptkitpackage. Do not add a public or internal package for this feature. - Implement the helper as a transparent constructor for the existing
Backendtype. Do not add a second backend representation or bypassWithBackend. - Leave normalization, validation, copying, registry construction, capacity defaulting, and duplicate detection in their existing owners.
- Do not add environment-variable discovery, package-global registration, implicit local defaults, model selection, profile construction, or support for non-OpenAI-compatible transports.
- Keep tests lean and behavior-focused. Do not duplicate the existing registry and concurrency test matrices merely because the constructor reaches those mechanisms.
- Update exact GoDoc with the exported declarations. Update current-state consumer guidance only after the corresponding API is implemented.
- Do not update release notes, create a release, change a module version, or tag a commit as part of this work.
Fixed Design
Exported API
Add these declarations to backends.go in package promptkit:
// BackendLocal is the conventional ID used by LocalBackend. It is not a
// built-in or reserved backend and must be registered with WithBackend.
const BackendLocal = "local"
// LocalBackend returns a Backend for a conventional local OpenAI-compatible
// endpoint.
func LocalBackend(endpoint string, concurrencyLimit int) Backend {
return Backend{
ID: BackendLocal,
Endpoint: endpoint,
ConcurrencyLimit: concurrencyLimit,
}
}
The exact GoDoc may be wrapped or expanded for clarity, but it must own and communicate all of these contract points:
BackendLocalis the case-sensitive conventional ID"local";- it is neither built in nor reserved;
- calling
LocalBackenddoes not register anything; - the returned value must be supplied through
WithBackend; endpointandconcurrencyLimitare copied into the corresponding fields without normalization or validation;APIKeyEnv,ExtraParams, andQueueCapacityretain their zero values; and- normal
NewEnginebackend validation and concurrency semantics apply after registration.
Keep BackendOpenRouter unchanged. BackendLocal must not alias an internal
registry constant because the internal registry has no special local-backend
identity or behavior.
Place BackendLocal near BackendOpenRouter, and place LocalBackend after
the Backend declaration and before WithBackend. This keeps the conventional
IDs, configured value, convenience constructor, and registration option
discoverable in one file.
Constructor Semantics
LocalBackend is a pure struct constructor. Its complete behavior is
equivalent to the keyed literal shown above.
In particular, the constructor must not:
- trim or parse the endpoint;
- reject blank endpoints or negative limits;
- choose a default limit;
- assign an API-key environment variable;
- allocate an empty
ExtraParamsmap; - assign a queue-capacity pointer;
- read process environment;
- mutate package-global or engine state; or
- call
WithBackenditself.
Deferred validation is intentional. It keeps one validation path for all
Backend values: WithBackend copies the public value into engine options,
and NewEngine constructs the validated immutable registry. A positive limit
with a nil queue continues to select the existing default queue capacity of
1024; zero continues to mean unlimited; a negative value continues to make
NewEngine fail with ErrInvalidConfig.
The returned Backend remains an ordinary caller-owned value. Consumers can
modify it before passing it to WithBackend, although task-oriented
documentation should direct materially customized configurations to an
explicit keyed Backend literal.
Identity And Compatibility
Do not add "local" to the internal built-in or reserved ID set. A consumer
must be able to register either:
promptkit.LocalBackend(endpoint, limit)
or:
promptkit.Backend{
ID: promptkit.BackendLocal,
Endpoint: endpoint,
}
through the existing WithBackend option. Both forms participate in ordinary
duplicate-ID detection. Existing consumers that already use the literal ID
"local" remain source- and behavior-compatible.
No existing Backend, WithBackend, profile, registry, execution-target, or
capacity semantics change. Do not modify internal/backend,
internal/capacity, internal/domain, engine.go, profiles.go, or
types.go for this feature.
Test Ownership
The root external-package contract suite in public_contract_test.go owns the
new public behavior. Add one focused test named:
func TestLocalBackendConstructsAndRegistersConventionalBackend(t *testing.T)
The test must:
-
call
promptkit.LocalBackendwith a test endpoint and a positive, test-owned concurrency limit; -
compare the returned value with this complete expected value:
promptkit.Backend{ ID: promptkit.BackendLocal, Endpoint: endpoint, ConcurrencyLimit: limit, }A whole-value comparison is appropriate here because the exact zero-value fields are part of this small public constructor's contract.
-
register that returned value with
WithBackend; -
add an in-memory profile whose
BackendIDispromptkit.BackendLocal; -
construct an engine through the existing contract-test prompt fixture;
-
call
Prepare; and -
assert that the prepared result exposes
BackendLocalas the selected backend and the supplied endpoint as the effective endpoint.
This single test protects the realistic compatibility risks: accidental field
defaults, a changed conventional ID, failure to compose with WithBackend,
and accidental treatment of "local" as reserved. It also demonstrates that
the helper uses the existing backend/profile path.
Do not add separate tests for blank endpoints, malformed endpoints, negative limits, queue defaulting, duplicate IDs, engine isolation, runtime capacity, or caller mutation. Those mechanisms are unchanged and already have tests at their owning boundaries. Do not add internal-package tests for this root facade constructor.
Consumer Documentation
Update docs/consumers/pkg-promptkit.md after the API exists. Keep Go
declarations and GoDoc as the exact API owner; the guide should help consumers
choose a workflow and link to backends.go for precise semantics.
Restructure the backend guidance to present these paths in increasing order of configuration:
- Endpoint-only profile. Show a small in-memory
ProfilewithEndpointandModel. Explain that this is the simplest choice when only one profile needs the endpoint and shared backend identity or capacity policy is unnecessary. - Local convenience constructor. Show
WithBackend(promptkit.LocalBackend("http://localhost:8000/v1", 2))together with a profile usingBackendID: promptkit.BackendLocal. Explain briefly that the helper is explicit, is not pre-registered, does not read environment variables, and leaves the queue capacity at the existing default for a positive limit. - Complete backend value. Preserve an advanced example using a keyed
Backendliteral for needs such asAPIKeyEnv, an explicitQueueCapacity, extra parameters, a custom ID, or multiple local endpoints. Use a custom ID other than"local"in that example so the distinction from the conventional helper is clear.
Keep the existing backend-routing, selected-backend identity, concurrency, credential, and error guidance unless a small wording adjustment is required to make the new decision path coherent. Avoid repeating the complete field contract or registry validation rules from GoDoc.
Do not add a new maintained example, README section, format-reference entry, integration-contract change, internal-document change, or release note. The consumer-guide snippets are sufficient for this small convenience API, and none of those other documents owns the affected task or contract.
Stage 1: Add The Public Constructor And Contract Test
Status: Complete.
Objective
Add the smallest public API that expresses the accepted local-backend convention and protect its compatibility through the root public boundary.
Implementation Prompt
- Re-read
docs/development.md, all files underdocs/policy/,local-backend.md,backends.go, the backend-related portion ofpublic_contract_test.go, and the existing backend/concurrency GoDoc before editing. - Confirm the working tree and preserve the uncommitted roadmaps and any unrelated consumer changes.
- Add the untyped exported string constant
BackendLocal = "local"tobackends.gowithout changingBackendOpenRouter. - Add
LocalBackend(endpoint string, concurrencyLimit int) Backendtobackends.gousing the exact keyed-literal implementation in the fixed design. - Write complete GoDoc for both declarations. Make their conventional, explicit, non-built-in, non-reserved, and deferred-validation semantics unambiguous.
- Add
TestLocalBackendConstructsAndRegistersConventionalBackendtopublic_contract_test.goexactly as specified under Test Ownership. Reuse the existing contract prompt fixture rather than adding a fixture or test helper. - Do not edit internal packages. If the constructor appears to require an internal change, stop and reconcile the implementation with the fixed transparent-constructor design instead.
Focused Validation
Run:
gofmt -w backends.go public_contract_test.go
go test . -run '^TestLocalBackendConstructsAndRegistersConventionalBackend$'
go test .
go vet .
go build .
git diff --check
Inspect the diff and confirm that this stage changes only backends.go,
public_contract_test.go, and the already-present roadmap files.
Completion Gate
Stage 1 is complete when:
- the exported constant and constructor match the fixed API;
- the constructor returns only the three specified non-zero fields;
- the helper registers through the ordinary
WithBackendpath; "local"remains a valid consumer registration rather than a reserved built-in;- the focused public-contract test passes; and
- the root package test, vet, build, formatting, and whitespace checks pass.
Stage 2: Publish Consumer Guidance And Validate The Repository
Status: Complete.
Objective
Make the simplest suitable local-endpoint configuration easy to discover, confirm the complete change across the repository, and close the temporary roadmaps.
Implementation Prompt
- Re-read the implemented declarations and GoDoc before describing them.
- Update
docs/consumers/pkg-promptkit.mdaccording to the three-path structure under Consumer Documentation. - Keep examples illustrative, minimal, secret-free, and consistent with the
implemented declarations. Link precise semantics to
backends.gorather than duplicating its field-by-field contract. - Follow every added or changed Markdown link and confirm its target exists.
Confirm that all local repository-relative links in
local-backend.md, this plan, and the changed consumer guide resolve. - Run the complete maintainer validation sequence below.
- Inspect the final diff for accidental internal behavior changes, generated artifacts, credentials, local workspace files, or module replacements.
- After every completion gate passes, set both stage statuses, this plan's
status, and the status in
local-backend.mdtoComplete. Do not retire or remove the roadmaps in the implementation change; roadmap retirement follows implementation review.
Full Validation
Run the complete sequence from docs/development.md:
go test ./...
go test -race ./...
go vet ./...
go build ./...
go run ./examples/go-library/prepare
gofmt -l $(git ls-files '*.go')
git diff --check
The formatting command must produce no paths. Follow every added or changed Markdown link and confirm its target and heading exist.
Also inspect:
git status --short
git diff --stat
git diff
Confirm that:
- production changes are limited to the root convenience API;
- no internal backend, registry, capacity, profile, or engine behavior changed;
BackendLocalis conventional and consumer-registerable, not built in or reserved;LocalBackendperforms no validation, normalization, environment lookup, registration, allocation, or hidden default selection;- the returned
BackendleavesQueueCapacitynil so existing positive-limit defaulting remains owned by the registry; - existing endpoint-only profiles and complete custom backends remain documented and supported;
- current-state documentation describes only the now-implemented API and links to the canonical GoDoc for exact semantics;
- no
go.work,go.work.sum, local module replacement, credential, generated binary, or unrelated change was introduced; and - the feature and implementation roadmaps contain no unresolved work marked complete.
Completion Gate
Stage 2 is complete when every target-end-state item in
local-backend.md is implemented, the consumer guide clearly presents all
three configuration paths, all complete validation commands pass, all changed
links resolve, and the roadmap statuses accurately report completion.
Implementation Handoff
The implementation handoff should report:
- the new
BackendLocalandLocalBackendpublic API; - that the helper remains explicit and composes with the ordinary backend registry;
- the focused public-contract coverage added;
- the consumer-guide decision path added;
- the complete validation commands and results; and
- any unrelated working-tree changes that were preserved.
Do not claim that a local backend is built in, pre-registered, configured from environment variables, or assigned a default model or concurrency limit.
Open Questions
None. The feature roadmap and this plan fix the exported API, constructor semantics, identity treatment, compatibility behavior, test boundary, documentation ownership, implementation sequence, validation gates, and non-goals required for implementation.