33 KiB
Backend-Specific Concurrency Management Implementation Plan
Status: Ready for implementation.
Purpose
This document is the decision-complete implementation plan for backend-specific concurrency management. It is written for a coding agent that will implement each stage in order.
The feature roadmap owns the intended capability, consumer value, policy choices, compatibility decision, and target end state. This document owns the concrete API, internal representation, scheduling architecture, implementation sequence, test ownership, documentation updates, and completion gates.
Implementation Rules
- Complete the stages in order. Keep the repository compiling and the focused tests passing at every stage boundary.
- Preserve unrelated working-tree changes. In particular,
concurrency.mdand the removal of its source idea fromfuture.mdmay already be uncommitted when implementation begins; retain both. - Follow every policy under
docs/policy/, the task-specific reading guide indocs/development.md, and the target behavior inconcurrency.md. - Keep the public API in the root
promptkitpackage and implementation details underinternal/. Do not expose scheduler types or create another public package. - Use only the Go standard library for scheduling. Do not add a queue, semaphore, worker-pool, or metrics dependency.
- Preserve synchronous, wait-for-result
Run, unrestrictedPrepare, engine-local state, endpoint-only profiles, backend-selected profiles, backend identity through endpoint overrides, and injectedLLMClientbehavior. - Do not broaden the work into asynchronous jobs, durable queues, retries, rate limiting, dynamic configuration, priorities, worker lifecycle, endpoint-keyed pools, or public queue observability.
- Keep all tests deterministic, bounded, offline, and race-safe. Coordinate concurrent tests with channels and barriers rather than timing assumptions or live providers.
- Update exact GoDoc with each exported declaration change. Update durable current-state documents only after the corresponding behavior is implemented.
- Test configurable mechanisms with small test-owned limits. Assert the exact
OpenRouter
16and default queue1024values only at the registry contract that owns those operational defaults. - Do not create a release, change a module version, or tag a commit. The final
implementation handoff must identify the built-in OpenRouter behavior change
for the next pre-
v1minor release.
Fixed Design
Public Backend Configuration
Append these fields to the existing root Backend type in backends.go:
type Backend struct {
// Existing fields remain unchanged and in their current order.
ConcurrencyLimit int
QueueCapacity *int
}
Use these exact semantics:
| Public values | Meaning |
|---|---|
ConcurrencyLimit == 0, QueueCapacity == nil |
Unlimited backend; preserve current behavior. |
ConcurrencyLimit > 0, QueueCapacity == nil |
Limit active generations and use the default waiting capacity of 1024. |
ConcurrencyLimit > 0, QueueCapacity != nil |
Limit active generations and use the pointed-to capacity exactly, including zero. |
ConcurrencyLimit < 0 |
Invalid engine configuration. |
QueueCapacity != nil and *QueueCapacity < 0 |
Invalid engine configuration. |
ConcurrencyLimit == 0 and QueueCapacity != nil |
Invalid engine configuration because a queue without an active limit has no defined consumer value. |
ConcurrencyLimit counts simultaneous calls to the engine-owned internal
model-client boundary for this backend. QueueCapacity controls additional
accepted Run invocations beyond that limit. The maximum admitted runs for a
limited backend is therefore:
ConcurrencyLimit + effective QueueCapacity
Guard that addition against integer overflow during backend validation. Do not impose an arbitrary upper bound beyond non-negativity and overflow safety.
The QueueCapacity pointer exists only to distinguish omission from explicit
zero. WithBackend and NewEngine must not retain the caller's pointer.
Backend continues to have no stable JSON representation, and consumers
remain directed to keyed literals.
Do not add concurrency fields to Profile, ExecutionTarget,
ExecutionTargetOverride, RunRequest, prompt or profile files, or stable
prepared/result JSON.
Built-In And Custom Defaults
The backend registry owns these exact operational defaults:
const (
openRouterConcurrencyLimit = 16
defaultQueueCapacity = 1024
)
The built-in openrouter definition has a normalized concurrency limit of 16
and queue capacity of 1024.
Consumer registrations remain unlimited when concurrency is omitted. For a consumer backend with a positive limit and omitted queue capacity, normalize the queue capacity to 1024. Preserve an explicitly configured zero.
Consumers still cannot replace the reserved openrouter registration.
Endpoint-only profiles have no backend policy and remain unlimited. A selected
backend retains its pool when a profile or request overrides only its endpoint.
Internal Backend Representation
Extend internal/domain.Backend with scalar policy values and explicit
presence rather than retaining a pointer:
type Backend struct {
// Existing fields...
ConcurrencyLimit int
QueueCapacity int
QueueCapacitySet bool
}
type BackendCapacityPolicy struct {
ConcurrencyLimit int
QueueCapacity int
}
WithBackend converts the public pointer into QueueCapacity plus
QueueCapacitySet. Registry normalization validates the combinations above,
fills the default, and leaves every limited stored backend with
QueueCapacitySet == true. Unlimited stored backends retain zero values and
QueueCapacitySet == false.
Add this internal registry method:
func (r *Registry) CapacityPolicies() map[string]domain.BackendCapacityPolicy
It returns a newly allocated map containing only limited backends. Values are
scalars, so callers cannot mutate registry state. The built-in OpenRouter
policy is included. GetBackend continues returning a defensive backend copy,
now including normalized scalar capacity metadata.
Capacity policy is operational registry metadata. Do not merge it into an execution target or expose it to injected model clients.
Public Capacity Error
Add this root sentinel beside the other run errors in engine.go:
var ErrCapacityExceeded = errors.New("backend capacity exceeded")
Its GoDoc must state that it identifies a Run rejected because the selected
backend has already admitted ConcurrencyLimit + QueueCapacity runs. It is
not an invalid request, an LLM/provider rate-limit response, or an
ErrLLMGenerate failure.
The internal capacity component owns a corresponding internal
ErrCapacityExceeded. Add its mapping in publicErrorFor before the broader
generation and invalid-request cases. The public error must preserve the
internal error through wrapping while matching ErrCapacityExceeded with
errors.Is.
A capacity rejection returns no partial result and must not invoke the artifact reader, renderer, schema loader, validator, or model client. Prompt, profile, and backend loading needed to select the pool may already have occurred.
Internal Capacity Component
Add internal/capacity as the single owner of engine-local run admission and
active-generation permits.
Use these package-level boundaries:
var ErrCapacityExceeded error
type Manager struct {
// Private immutable pool map.
}
func NewManager(
policies map[string]domain.BackendCapacityPolicy,
) (*Manager, error)
func (m *Manager) Admit(
ctx context.Context,
backendID string,
) (release func(), err error)
func NewClient(m *Manager, next llm.Client) llm.Client
NewManager copies the supplied map and creates one independent pool per
limited backend. Defensively reject blank IDs, non-positive concurrency
limits, negative queue capacities, or total-capacity overflow even though the
registry normally supplies normalized values. Construction creates no worker
goroutines.
An absent manager, blank backend ID, or ID absent from the policy map is unlimited:
Admitsucceeds with a non-nil no-op release function; and- the client wrapper calls the next client directly.
For a limited pool, Admit is immediate and context-aware:
- return
ctx.Err()if the context is already done; - under the pool lock, compare admitted runs with
ConcurrencyLimit + QueueCapacity; - return an error matching internal
ErrCapacityExceededwhen full; or - increment admitted runs and return an idempotent release function.
The release function decrements admission exactly once, even if accidentally called more than once. It does not release an active-generation permit; those permits have their own lifetime.
FIFO Generation Permits
NewClient returns an internal llm.Client wrapper around either the built-in
client or the public-client adapter. It must preserve requests, successful
responses, nil responses, and collaborator error identities exactly.
next must be non-nil; NewEngine and internal runner construction maintain
that invariant. A nil manager returns next unchanged.
For a configured backend ID, the wrapper:
- acquires one active-generation permit from the matching pool;
- waits in FIFO order when the active count equals
ConcurrencyLimit; - removes a canceled waiter and returns
ctx.Err()when cancellation wins before the permit is granted; - invokes the next client only after a permit is granted; and
- releases the permit with
deferafter every success, nil response, collaborator error, panic unwinding, or context outcome.
Implement FIFO and cancellation explicitly with a mutex and an ordered waiter list. A channel used only as a counting semaphore is insufficient because it does not define FIFO ordering or safe removal of canceled waiters.
Permit grant and cancellation must have one lock-protected linearization point. If cancellation removes the waiter first, do not invoke the next client. If grant wins first, invoke the next client with the caller's context; the next client may then observe cancellation normally. Never lose or double-release a permit in this race.
Releasing a permit transfers it to the oldest non-canceled waiter before making it generally available. Different backend pools never share admission or active counts.
The active wrapper enforces its limit even if an internal caller invokes it
without a run admission lease. Bounded backlog is guaranteed for ordinary
engine Run calls by the runner admission path; no public API exposes the
wrapped internal client directly.
Engine Assembly
In NewEngine, after constructing the validated backend registry:
- obtain
backendRegistry.CapacityPolicies(); - construct one
capacity.Manager; - construct the selected base internal LLM client exactly as today;
- wrap that base client with
capacity.NewClient; and - pass both the wrapped client and manager-as-admitter to the runner.
Every NewEngine call constructs a distinct manager. Do not cache managers,
pools, or policies in package globals. The wrapper must be applied after a
public injected client is adapted to internal/llm.Client, so built-in and
injected clients receive identical scheduling behavior.
If NewManager reports a defensive configuration error, make NewEngine
return an error matching ErrInvalidConfig.
Prepare does not use the manager. An injected client remains required to be
safe for concurrent calls because different backend pools and unlimited
backends may still invoke it concurrently.
Shared Two-Phase Preparation
Refactor internal/usecase.Runner so Prepare and Run share one preparation
pipeline with two private phases. Do not duplicate prompt/profile/backend
selection or execution precedence.
The first phase resolves only the state required before admission:
- validate
PromptID; - normalize the direct session ID;
- load the prompt definition;
- hash the original prompt definition at its existing error-order position;
- select and load the execution profile;
- resolve the selected backend;
- resolve and validate the effective execution target and credentials; and
- resolve the effective output contract without loading its schema.
Return a private state value containing the loaded definition, normalized
direct session, prompt-definition hash, selected profile ID, effective target,
numeric-presence metadata, effective output contract, and preparation start
time. Keep this value private to internal/usecase.
The second phase consumes that state and performs:
- structured-output schema loading;
- artifact loading and input hashing;
- message and prompt-session rendering;
- direct-session application;
- rendered-prompt hashing; and
PreparedRunconstruction and timing.
Preserve every existing precedence rule, error identity, direct-session template bypass, hash input, selected identity, copy guarantee, and timing field. Do not reload the prompt, profile, or backend between phases.
Runner.Prepare records its start time, runs both phases consecutively, and
never calls admission. Its behavior and error ordering remain unchanged.
Runner.Run records its existing run start time, runs the first preparation
phase, and then calls:
release, err := r.admitter.Admit(ctx, effectiveBackendID)
Use a narrow use-case-owned interface with the same signature:
type RunAdmitter interface {
Admit(context.Context, string) (func(), error)
}
A nil admitter means unlimited behavior for internal constructors and tests.
On successful admission, immediately defer release() around the remainder of
the run. Then run the second preparation phase, initial generation,
validation, and all repair attempts.
If admission returns internal capacity.ErrCapacityExceeded, add useful
backend context without changing its identity. If it returns ctx.Err(),
preserve that identity directly rather than recategorizing it as invalid
request or generation failure.
This refactor intentionally replaces the current literal Run-calls-Prepare
implementation with shared private phases. Update current-state documentation
to describe one shared pipeline rather than retaining an inaccurate call-graph
claim.
Generation And Repair Lifetime
The admission lease covers the entire accepted run:
- second-phase preparation;
- initial generation;
- validation;
- every repair; and
- all failure and cancellation exits.
Preparation and validation do not hold an active-generation permit. The
wrapped client acquires a permit only around each actual Generate call.
The runner's initial generation already carries the effective backend ID in
GenerateRequest.Target. Preserve that value. RepairRequest.Target and the
default repairer's generated request must continue carrying the same backend
ID, allowing each repair to reacquire the same pool's active permit.
When testing or constructing NewRunnerWithRepairer, pass the same wrapped
client to both the runner and NewDefaultOutputRepairer. Do not add capacity
state to RepairRequest, ExecutionTarget, or public generation values.
A repair remains within its existing admission lease. It waits for a FIFO active permit but never performs a second bounded admission and therefore cannot fail merely because later runs filled the admission capacity.
Error And Cancellation Semantics
The required public outcomes are:
| Situation | Required error identity |
|---|---|
| Admission capacity is full | ErrCapacityExceeded only; not ErrInvalidRequest or ErrLLMGenerate. |
| Context is done before admission succeeds | Preserve ctx.Err(); do not return capacity exhaustion. |
| Context cancels while waiting for an active permit | Preserve ctx.Err() through the existing ErrLLMGenerate generation category. |
| Wrapped client fails after permit acquisition | Preserve existing ErrLLMGenerate and collaborator identities. |
| Preparation or validation fails after admission | Preserve its existing category and release admission. |
Maintain the existing rule that Run returns no partial result on any
operational error. Do not add queue status to errors or results.
RunResult.Duration continues to start at runner entry and therefore includes
pre-admission resolution, accepted preparation, and active-permit waiting.
PreparedRun.DurationMS continues to cover only its shared preparation phases;
it does not include later generation waiting. Capacity-rejected calls have no
result or timing value.
Ownership And Concurrency Safety
The registry, capacity policy map, pool map, and per-pool limits are immutable after engine construction. Only admission counts, active counts, and waiter lists are mutable and must be protected by the owning pool mutex.
Do not retain public queue pointers, caller request values, contexts, or generation requests after their call completes. A canceled waiter must be unlinked so its context and request cannot remain reachable from the pool.
Do not hold a pool mutex while:
- loading or rendering prompts;
- reading artifacts or schemas;
- invoking a model client;
- validating output;
- closing a waiter notification channel if the implementation could re-enter pool code; or
- calling consumer code.
No scheduler operation may spawn a goroutine whose lifetime outlasts the
calling Run. The zero steady-state goroutine count is part of the
in-process/no-worker-lifecycle design.
Test Ownership
Use this ownership split and avoid repeating the full policy matrix at every layer:
internal/backend/registry_test.goowns normalization, validation, the exact OpenRouter policy, the custom default queue, explicit zero, unlimited omission, and policy-map copying.internal/capacity/manager_test.goowns admission bounds, idempotent release, FIFO active permits, cancellation races, capacity recovery, independent pools, unlimited IDs, and observed peak concurrency.internal/capacity/client_test.goowns wrapper request/response/error transparency and the rule that cancellation before grant does not invoke the next client. Combine these with manager tests if one coherent package test expresses the behavior more clearly.internal/usecase/runner_test.goowns two-phase preparation parity, pool selection, admission before expensive work, admission release across run exits,Preparebypass, and repair reuse of the admitted backend.- Root external-package tests own public configuration conversion, assembled engine-local behavior, endpoint-override routing, injected-client limiting, and public capacity/context error identities.
- Existing model-client HTTP tests remain unchanged because scheduling does not alter the OpenAI-compatible wire contract.
Concurrency tests must use test-owned limits such as one or two and
channel-controlled blocking clients. Record observed active and peak counts
under a mutex or atomics. Do not use time.Sleep to infer queue state.
Package-internal tests may inspect a waiter list under its mutex through a
small test helper when necessary to establish deterministic FIFO ordering; do
not add production metrics or hooks solely for tests.
Do not add separate tests for trivial scalar copies when registry or assembled behavior already protects them.
Stage 1 — Backend Policy And Public Configuration
Status: Complete.
Goal
Add the public and internal backend policy representation, normalize all configured states, and expose immutable normalized policies without changing runtime scheduling yet.
Work
- Add
ConcurrencyLimitandQueueCapacitytoBackendinbackends.gowith exact GoDoc for unlimited, defaulted, explicit-zero, invalid, and engine-scoped behavior. - Convert the public queue pointer into scalar value plus presence in
WithBackend; do not retain the pointer. - Add the internal backend policy fields and
BackendCapacityPolicytointernal/domain/domain.go. - Add the two registry-owned constants and configure the built-in OpenRouter definition with 16 and 1024.
- Extend
normalizeBackendwith the fixed validation, defaulting, explicit zero, and overflow rules. - Add
Registry.CapacityPolicies, returning only limited policies in a fresh map. - Update existing backend composite literals and assertions only where the new fields are relevant. Continue using keyed literals.
Tests
- Extend the exact built-in registry test with the OpenRouter limit and queue.
- Add one coherent table covering unlimited omission, default queue, explicit-zero queue, negative values, queue-without-limit, and total overflow.
- Extend the registry copy/normalization test to prove returned policy maps cannot mutate registry state.
- Add root coverage only if needed to prove the public pointer/presence conversion; do not reproduce registry validation cases at the facade.
Focused Validation
Run:
gofmt -w backends.go internal/domain/domain.go \
internal/backend/registry.go internal/backend/registry_test.go
go test . ./internal/backend
go vet . ./internal/backend
git diff --check
Include another touched Go test file in gofmt only if it actually changed.
Completion Gate
This stage is complete when every public configuration state has one normalized internal meaning, OpenRouter exposes exactly 16/1024, custom backends remain unlimited by omission, and no runtime call is scheduled yet.
Stage 2 — Engine-Local Capacity Manager
Status: Pending.
Goal
Implement and prove the bounded admission mechanism and FIFO active-generation client wrapper independently of runner orchestration.
Work
- Add
internal/capacity/manager.gowith the manager, immutable policy copy, per-backend pools, internal error, immediate admission, idempotent release, and FIFO context-aware active permits. - Add
internal/capacity/client.gowith the transparentllm.Clientwrapper. - Use mutex-protected waiter state and an ordered list; explicitly resolve grant-versus-cancel races.
- Ensure unlimited and independent-pool fast paths avoid queue allocation.
- Do not start workers, timers, cleanup goroutines, or process-global state.
Tests
- Add a compact constructor-validation table for blank IDs, non-positive limits, negative queues, and total-capacity overflow.
- With a small configured policy, prove that exactly
limit + queueCapacityadmissions succeed, the next matchesErrCapacityExceeded, and a release permits another admission. - Prove release is idempotent.
- Drive more blocked client calls than the active limit and assert observed peak concurrency never exceeds that limit.
- Prove FIFO order with deterministic queue-entry synchronization.
- Cancel the first and a middle waiter and prove they are removed, never call the wrapped client, and do not block later waiters.
- Exercise the grant/cancel race repeatedly under
go test -race, asserting no permit leak or double invocation. - Prove different backend IDs proceed independently and blank, unknown, or nil-manager paths remain unlimited.
- Prove request values, successful and nil responses, and collaborator errors pass through unchanged after permit acquisition.
Focused Validation
Run:
gofmt -w internal/capacity/manager.go \
internal/capacity/manager_test.go \
internal/capacity/client.go \
internal/capacity/client_test.go
go test ./internal/capacity
go test -race ./internal/capacity
go vet ./internal/capacity
git diff --check
If tests are combined into one file, omit the nonexistent file from gofmt.
Completion Gate
This stage is complete when the standalone component enforces relational admission and active limits, FIFO cancellation is race-safe, separate pools are independent, and the wrapper is transparent apart from waiting.
Stage 3 — Shared Preparation And Early Run Admission
Status: Pending.
Goal
Refactor runner preparation into one shared two-phase pipeline and place bounded admission after backend resolution but before schema, artifact, and rendering work.
Work
- Add the private pre-admission preparation state and split the existing
Preparelogic according to the fixed design. - Make
Runner.Preparecall both phases without an admitter. - Add the
RunAdmitterinterface and runner field. - Update
NewRunnerandNewRunnerWithRepairerto accept the optional admitter; update internal call sites with nil until root assembly is wired. - Change
Runner.Runto use the first phase, admit by effective backend ID, defer the returned release, and then use the second phase. - Preserve all existing error precedence, target resolution, hashes, metadata, session behavior, and timing.
- Return capacity and context errors with the fixed identities. Do not invoke later collaborators after rejection.
Tests
- Keep the existing
Run/Prepareparity coverage passing to prove the shared phases do not drift. - Add a fake admitter that records backend IDs and release calls.
- Prove a backend-selected run admits with the selected ID even when the endpoint is overridden.
- Prove an endpoint-only run uses the unlimited/blank identity and that
Preparenever calls admission. - Reject admission and assert schema, artifact, renderer, validator, repairer, and LLM collaborators are not invoked.
- Prove admission is released after one successful run and representative
second-phase, generation, and validation errors. Prefer a small table around
the single
deferinvariant rather than duplicating every error test. - Retain direct-session, backend precedence, credential, hashing, and repair tests unchanged except for constructor arguments.
Focused Validation
Run:
gofmt -w internal/usecase/runner.go \
internal/usecase/runner_test.go
go test ./internal/usecase
go test -race ./internal/usecase
go vet ./internal/usecase
git diff --check
Completion Gate
This stage is complete when Prepare remains unrestricted, Run admits after
one canonical routing phase and before expensive completion work, every exit
releases admission, and existing preparation semantics remain unchanged.
Stage 4 — Engine Assembly And Public Runtime Contract
Status: Pending.
Goal
Wire one manager into each engine, schedule built-in and injected clients, expose the capacity error, and prove assembled runtime behavior.
Work
- Add public
ErrCapacityExceededand its exact GoDoc inengine.go. - Map internal capacity exhaustion in
errors.go. - Construct the manager from the registry policy snapshot in
NewEngine. - Wrap the selected internal client after built-in or injected-client selection and pass the manager and wrapped client to the runner.
- Update
Engine,NewEngine,Run,WithLLMClient, andLLMClientGoDoc only where concurrency, capacity, or cancellation statements change. - Ensure manager-construction errors match
ErrInvalidConfig. - For internal repair coverage, construct the default repairer with the same wrapped client used by its runner and confirm repair target backend identity remains intact.
Tests
- Add an external-package assembled test with a small custom limit and a blocking injected client; assert peak generation equals or remains below the configured limit.
- With queue capacity zero, block one accepted run before generation and
assert the next matching-backend run returns
ErrCapacityExceeded, does not matchErrInvalidRequestorErrLLMGenerate, returns no result, and never reaches expensive collaborators or the client. - In the same or another focused workflow, prove an endpoint override remains in the selected backend's pool.
- Prove two engines with the same backend ID have independent capacity.
- Prove an unlimited custom backend and an endpoint-only profile preserve concurrent behavior.
- Cancel a call waiting for an active permit; assert it matches both
context.CanceledandErrLLMGenerate, never invokes the injected client, and leaves capacity reusable. - Add one internal repair workflow with concurrent runs or controlled permits showing initial and repair generations never exceed the same backend limit and repairs do not perform a second admission.
- Extend the public error sentinel contract test with
ErrCapacityExceeded.
Avoid a second HTTP-level concurrency suite: the capacity client tests and one assembled injected-client workflow already protect the shared wrapper used by the built-in client.
Focused Validation
Run:
gofmt -w engine.go errors.go backends.go \
internal/usecase/runner.go internal/usecase/runner_test.go \
engine_test.go public_contract_test.go
go test . ./internal/backend ./internal/capacity ./internal/usecase
go test -race . ./internal/capacity ./internal/usecase
go vet . ./internal/backend ./internal/capacity ./internal/usecase
git diff --check
Add any newly created capacity files to gofmt when they changed in this
stage.
Completion Gate
This stage is complete when every engine has independent pools, limited runs are bounded and FIFO at generation, endpoint routing is correct, capacity and context errors are stable, repairs reuse admission, and both client kinds pass through the same wrapper.
Stage 5 — Durable Documentation And Final Validation
Status: Pending.
Goal
Move implemented contracts into their durable owners, record compatibility impact, and validate the complete repository.
Work
- Review every changed exported declaration. Ensure GoDoc is the canonical owner of exact field types, nil/zero semantics, defaulting, error identity, engine scope, concurrency safety, cancellation, and source compatibility.
- Update
doc.goso its concurrency summary acknowledges backend scheduling while continuing to require injected collaborators to be concurrency-safe. - Update
docs/consumers/pkg-promptkit.mdwith task-oriented examples for:- a limited local backend;
- omitted queue capacity selecting 1024;
- explicit zero queue capacity; and
- handling
ErrCapacityExceeded. Keep exact field semantics in GoDoc rather than duplicating a full table.
- Add
docs/internal/capacity.mdas the durable owner of pool lifecycle, admission, FIFO active permits, cancellation, client wrapping, and test ownership. - Add
internal/capacitytodocs/internal/overview.md. - Update
docs/policy/architecture.mdto include the implemented component and root assembly dependency without turning policy into an API reference. - Update
docs/internal/runner.mdto describe the shared two-phase preparation pipeline, early bounded admission, lease lifetime, generation permits, repairs, capacity failures, and cancellation. - Review
docs/formats.md; add only a concise link or clarification if needed to explain that endpoint overrides preserve backend capacity identity. Do not add concurrency fields to YAML. - Do not change the OpenAI-compatible integration contract or
docs/internal/llm.mdunless implementation changes their current statements; scheduling is outside the provider wire contract and concrete model-client implementation. - Record in the implementation handoff that built-in OpenRouter now limits
active generations to 16 with queue capacity 1024 and that the release
must be a pre-
v1minor release. Do not edit the release procedure or create a tag. - After every check passes, set
concurrency.md, this implementation plan, and each stage status toComplete. Do not remove the roadmaps in the implementation change; lifecycle retirement follows 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:
- only intended backend, capacity, runner, facade, test, documentation, and roadmap files changed;
- no
go.work,go.work.sum, local module replacement, credential, generated binary, coverage output, or unrelated change was introduced; - the built-in OpenRouter policy is exactly 16/1024;
- custom and endpoint-only backends remain unlimited by omission;
- explicit queue zero is distinguishable from omission;
- no capacity value enters execution targets, generated requests, stable JSON, prompt/profile YAML, or provider payloads;
- every engine owns distinct pools with no package-global mutable state;
- every initial and repair generation uses the active permit wrapper;
- capacity and waiter state is released on success, error, panic unwinding, and cancellation;
- concurrency tests use deterministic coordination rather than sleeps;
- current-state documentation describes implemented behavior rather than referring readers to the roadmaps; and
- the feature and implementation roadmaps contain no unresolved work marked complete.
Completion Gate
The implementation is complete only when every target-end-state item in
concurrency.md is implemented, race-enabled tests demonstrate the configured
limits and cancellation safety, durable contracts no longer depend on roadmap
prose, and the OpenRouter compatibility change is clearly reported for the
next minor release.
Open Questions
None. The feature roadmap and this plan fix the public representation, registry defaults, admission bound, FIFO generation behavior, early-routing refactor, cancellation races, error identities, engine and repair lifetimes, test ownership, compatibility treatment, and non-goals required for implementation.