# Local Backend Convenience Implementation Plan **Status:** Ready for implementation. ## Purpose This document is the decision-complete implementation plan for [local backend convenience](local-backend.md). 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 in `docs/development.md`, and the accepted behavior in `local-backend.md`. - Keep the API in the root `promptkit` package. Do not add a public or internal package for this feature. - Implement the helper as a transparent constructor for the existing `Backend` type. Do not add a second backend representation or bypass `WithBackend`. - 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`: ```go // 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: - `BackendLocal` is the case-sensitive conventional ID `"local"`; - it is neither built in nor reserved; - calling `LocalBackend` does not register anything; - the returned value must be supplied through `WithBackend`; - `endpoint` and `concurrencyLimit` are copied into the corresponding fields without normalization or validation; - `APIKeyEnv`, `ExtraParams`, and `QueueCapacity` retain their zero values; and - normal `NewEngine` backend 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 `ExtraParams` map; - assign a queue-capacity pointer; - read process environment; - mutate package-global or engine state; or - call `WithBackend` itself. 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: ```go promptkit.LocalBackend(endpoint, limit) ``` or: ```go 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: ```go func TestLocalBackendConstructsAndRegistersConventionalBackend(t *testing.T) ``` The test must: 1. call `promptkit.LocalBackend` with a test endpoint and a positive, test-owned concurrency limit; 2. compare the returned value with this complete expected value: ```go 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. 3. register that returned value with `WithBackend`; 4. add an in-memory profile whose `BackendID` is `promptkit.BackendLocal`; 5. construct an engine through the existing contract-test prompt fixture; 6. call `Prepare`; and 7. assert that the prepared result exposes `BackendLocal` as 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: 1. **Endpoint-only profile.** Show a small in-memory `Profile` with `Endpoint` and `Model`. Explain that this is the simplest choice when only one profile needs the endpoint and shared backend identity or capacity policy is unnecessary. 2. **Local convenience constructor.** Show `WithBackend(promptkit.LocalBackend("http://localhost:8000/v1", 2))` together with a profile using `BackendID: 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. 3. **Complete backend value.** Preserve an advanced example using a keyed `Backend` literal for needs such as `APIKeyEnv`, an explicit `QueueCapacity`, 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:** Not started. ### Objective Add the smallest public API that expresses the accepted local-backend convention and protect its compatibility through the root public boundary. ### Implementation Prompt 1. Re-read `docs/development.md`, all files under `docs/policy/`, `local-backend.md`, `backends.go`, the backend-related portion of `public_contract_test.go`, and the existing backend/concurrency GoDoc before editing. 2. Confirm the working tree and preserve the uncommitted roadmaps and any unrelated consumer changes. 3. Add the untyped exported string constant `BackendLocal = "local"` to `backends.go` without changing `BackendOpenRouter`. 4. Add `LocalBackend(endpoint string, concurrencyLimit int) Backend` to `backends.go` using the exact keyed-literal implementation in the fixed design. 5. Write complete GoDoc for both declarations. Make their conventional, explicit, non-built-in, non-reserved, and deferred-validation semantics unambiguous. 6. Add `TestLocalBackendConstructsAndRegistersConventionalBackend` to `public_contract_test.go` exactly as specified under Test Ownership. Reuse the existing contract prompt fixture rather than adding a fixture or test helper. 7. 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: ```sh 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 `WithBackend` path; - `"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:** Not started. ### 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 1. Re-read the implemented declarations and GoDoc before describing them. 2. Update `docs/consumers/pkg-promptkit.md` according to the three-path structure under Consumer Documentation. 3. Keep examples illustrative, minimal, secret-free, and consistent with the implemented declarations. Link precise semantics to `backends.go` rather than duplicating its field-by-field contract. 4. 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. 5. Run the complete maintainer validation sequence below. 6. Inspect the final diff for accidental internal behavior changes, generated artifacts, credentials, local workspace files, or module replacements. 7. After every completion gate passes, set both stage statuses, this plan's status, and the status in `local-backend.md` to `Complete`. 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`: ```sh 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: ```sh 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; - `BackendLocal` is conventional and consumer-registerable, not built in or reserved; - `LocalBackend` performs no validation, normalization, environment lookup, registration, allocation, or hidden default selection; - the returned `Backend` leaves `QueueCapacity` nil 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 `BackendLocal` and `LocalBackend` public 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.