Files
promptkit/docs/roadmap/implementation.md

15 KiB

Local Backend Convenience Implementation Plan

Status: Ready for implementation.

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 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:

// 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:

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:

  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:

    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:

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:

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;
  • 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.