10 KiB
Runtime Parameter Implementation Plan
This plan implements the target state in docs/roadmap/params.md.
Audience: LLM coding agents implementing the feature in order. Follow docs/policy/architecture.md, docs/policy/development.md, and docs/policy/documentation.md before changing code.
Constraints
- Keep adapters thin. CLI and HTTP should capture caller intent and map it into domain request types; merge decisions belong in
internal/usecase. - Keep external decoding strict. Unknown YAML/JSON fields must continue to fail.
- Do not accept or emit raw API key values.
- Do not add dependencies unless there is a clear need. This feature should use the standard library plus existing dependencies.
- Do not expand the HTTP API surface beyond
POST /v1/runs. - Do not add provider-specific adapter packages.
- Keep each stage passing
go test ./...before moving to the next stage.
Stage 1: Presence-Aware Request Overrides
Goal: make per-request numeric execution overrides presence-aware while keeping resolved execution settings concrete.
Domain Changes
- In
internal/domain/domain.go, add a request-only type:
type ExecutionTargetOverride struct {
Endpoint string `json:"endpoint,omitempty"`
Model string `json:"model,omitempty"`
Temperature *float64 `json:"temperature,omitempty"`
MaxTokens *int `json:"max_tokens,omitempty"`
TopP *float64 `json:"top_p,omitempty"`
TimeoutSeconds *int `json:"timeout_seconds,omitempty"`
ServiceTier string `json:"service_tier,omitempty"`
ReasoningEffort string `json:"reasoning_effort,omitempty"`
APIKeyEnv string `json:"api_key_env,omitempty"`
ExtraParams map[string]any `json:"extra_params,omitempty"`
}
- Change
domain.RunRequest.Executionfrom*ExecutionTargetto*ExecutionTargetOverride. - Change
ExecutionProfile.ExtraParamsandExecutionTarget.ExtraParamsfrommap[string]stringtomap[string]any. - Keep
ExecutionTargetconcrete. It represents the resolved effective runtime target after defaults, profile, and request overrides are merged.
Runner Changes
- Update
internal/usecase/runner.goso profile values still merge over built-in defaults and request overrides merge over that result. - Keep the existing concrete profile merge semantics for profile numeric fields.
- Add a separate request override merge path that uses pointer presence:
nilnumeric pointer means omitted; preserve the current value.- non-nil numeric pointer means explicit override, even when the value is
0.
- Validate request override numeric values before or during merge:
temperature:0 <= value <= 2max_tokens:value >= 0top_p:0 <= value <= 1timeout_seconds:value >= 0
- Preserve existing validation after merge:
- effective endpoint required
- effective model required
api_key_env, when set, must name a non-empty environment variable
- Preserve secret handling. The resolved API key value must never be stored in
PreparedRun,RunResult, logs, or HTTP responses.
CLI Changes
- Update
internal/adapter/cli/run.gorequest construction to builddomain.ExecutionTargetOverride. - Use the existing
flagWasSetbooleans to populate numeric pointers only when the user provided the flag. - Required behavior:
- omitted
--temperaturepreserves profile/default temperature; --temperature 0explicitly sets temperature to zero;- omitted
--top-ppreserves profile/default top-p; --top-p 0explicitly sets top-p to zero;- omitted
--max-tokenspreserves profile/default max tokens; --max-tokens 0explicitly sets max tokens to zero;- omitted
--timeoutpreserves profile/default timeout; --timeout 0sexplicitly sets timeout seconds to zero.
- omitted
- Do not add new CLI flags in this stage.
HTTP Changes
- Update
internal/adapter/http/dto.goso numeric model override fields are pointers:Temperature *float64MaxTokens *intTopP *float64TimeoutSeconds *int
- Update DTO mapping in
internal/adapter/http/handler.goto builddomain.ExecutionTargetOverride. - Preserve strict JSON decoding and existing error mapping.
- Required behavior:
- omitted numeric JSON fields preserve profile/default values;
- explicit numeric zero JSON fields override profile/default values.
Tests
Add or update tests in:
internal/usecase/runner_test.gointernal/adapter/cli/run_test.gointernal/adapter/http/handler_test.go
Required test coverage:
- Runner preserves profile value when request numeric override is omitted.
- Runner applies explicit zero request override for
temperature. - Runner applies explicit zero request override for
top_p. - Runner applies explicit zero request override for
max_tokens. - Runner applies explicit zero request override for
timeout_seconds. - Invalid request override ranges fail as invalid request errors.
- CLI
--temperature 0reaches effective settings as zero. - HTTP
"temperature": 0reaches effective settings as zero. - HTTP omitted
temperaturepreserves profile/default value.
Verification
Run:
go test ./...
Stage 2: JSON-Compatible extra_params
Goal: allow provider-specific parameters to carry JSON-compatible values throughout profile, HTTP, prepared output, metadata, and LLM request construction.
Domain And Loader Changes
- Complete all compile fixes from changing
ExtraParamstomap[string]any. - Ensure
internal/profile/filesystem_repository.gocontinues to decode profiles strictly while allowing nested JSON-compatible values underextra_params. - Add profile repository tests for
extra_paramscontaining:- string
- number
- boolean
- nested object or array
- Ensure formatter output remains deterministic:
- keep sorting
extra_paramskeys ininternal/format/prepared_run.go; - render non-string values with stable JSON encoding in text output.
- keep sorting
- Preserve JSON formatter behavior through normal
encoding/jsonoutput.
HTTP Changes
- Change HTTP model override
ExtraParamstomap[string]any. - Add handler tests proving HTTP accepts JSON-compatible
extra_paramsvalues. - Preserve strict rejection of unknown fields and raw API-key payload fields.
Verification
Run:
go test ./...
Stage 3: Outbound Serialization
Goal: serialize reasoning_effort and extra_params to the OpenAI-compatible chat-completions request.
LLM Adapter Changes
- In
internal/llm/openai_compatible_client.go, add first-class outbound support forreasoning_effort. - Add
extra_paramssupport by flatteningdomain.ExecutionTarget.ExtraParamsinto additional top-level JSON request fields. - Implement reserved-field collision checks before the HTTP request is made.
- Reserved keys must include:
modelsession_idmessagestemperaturemax_tokenstop_pservice_tierreasoning_effortresponse_format
- Reject empty
extra_paramskeys. - Ensure each
extra_paramsvalue can be marshaled as JSON. If marshaling fails, returnErrInvalidRequestwith context. - Keep existing request behavior unchanged when
reasoning_effortandextra_paramsare unset.
Recommended Implementation Shape
Use a custom marshal path for the outbound chat request rather than string manipulation.
One acceptable shape:
- Add
ReasoningEffort stringandExtraParams map[string]anyto the internalopenAIChatRequest. - Add a helper that converts
openAIChatRequestintomap[string]any, inserts first-class fields when set, then insertsExtraParamsafter collision validation. - Marshal that map with
encoding/json.
Do not construct outbound JSON with manual string concatenation.
Tests
Update internal/llm/openai_compatible_client_test.go.
Required test coverage:
- outbound JSON includes
reasoning_effortwhen set; - outbound JSON omits
reasoning_effortwhen unset; - outbound JSON includes string, number, boolean, object, and array
extra_params; - reserved
extra_paramskeys fail before provider call; - empty
extra_paramskeys fail before provider call; - existing message, cache-control, service-tier, response-format, and usage parsing tests continue to pass.
Verification
Run:
go test ./...
Stage 4: Documentation And Examples
Goal: move implemented behavior from roadmap to canonical docs after code is complete.
Update only after Stages 1 through 3 are implemented.
Required Docs
Update:
docs/config.mddocs/cli.mddocs/integrations/http-api.mddocs/integrations/openai-compatible-chat.mddocs/internal/runner.mddocs/internal/adapters.md
Required documentation content:
reasoning_effortis serialized outbound when set.extra_paramsserializes as provider-specific top-level outbound JSON fields.extra_paramssupports JSON-compatible values.- reserved
extra_paramsfields are rejected. - per-request numeric overrides distinguish omitted values from explicit zero values.
- CLI explicit zero behavior for existing numeric flags.
- HTTP explicit zero behavior for model override numeric fields.
- no raw API-key values are accepted or emitted.
Examples
Update examples only if needed to keep them accurate and runnable.
If adding an extra_params example, keep it secret-free and simple. Prefer a harmless provider-routing example over a vendor-specific feature that requires special credentials.
Verification
Run:
go test ./...
go run ./cmd/scriptorium render \
--config ./examples/config.yml \
--prompt generic.markdown_summary \
--input transcript=./examples/fixtures/transcript.md \
--input glossary=./examples/fixtures/glossary.yml \
--format json
Final Checks
Before considering the feature complete:
- Confirm
git diffcontains only intended code, test, doc, and example changes. - Confirm all non-roadmap docs describe implemented behavior only.
- Confirm no output path exposes raw API key values.
- Confirm
go test ./...passes. - Confirm the render smoke command passes.