27 KiB
Prompt-Independent Profile Inspection Implementation Plan
Status: Ready for implementation.
Purpose
This document is the decision-complete implementation plan for prompt-independent profile inspection. It is written for a coding agent that will implement each stage in order.
The feature roadmap owns the motivation, consumer workflow, policy choices, compatibility requirements, non-goals, and target end state. This document owns the concrete design, file-level changes, implementation sequence, test ownership, documentation work, validation commands, 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, retain the accepted feature roadmap and the corresponding future-roadmap and downstream-wishlist edits that may already be uncommitted.
- Follow every policy under
docs/policy/, the task-specific reading guide indocs/development.md, and the accepted behavior inprofile-inspection.md. - Keep the supported API in the root
promptkitpackage. Profile repositories, backend resolution, and target assembly remain below Go'sinternal/boundary. - Reuse the exact profile-source, backend-membership, and execution-target
precedence used by
Prepare,PrepareExecution, andRun. Do not create a second profile loader, backend registry, or target-merging implementation. - Preserve the observable behavior and error ordering of existing preparation and execution methods. The new inspection operation must not require a prompt, prompt default profile, request override, credential value, capacity admission, model client, renderer, artifact reader, schema source, or validator.
- Treat structural validation as the existing profile, backend, and effective target invariants. Do not add new endpoint URL policy, provider connectivity checks, reserved-extra-parameter policy, credential syntax, or file-format validation rules as part of this feature.
- Never read, retain, return, format, or log an environment credential value. The result may contain only the effective environment-variable name and the direct-key-required boolean.
- Return caller-owned public values. In particular, nested
ExecutionTarget.ExtraParamsmaps, slices, and objects must not alias engine-owned state or another inspection result. - Keep tests lean and behavior-focused. Reuse existing profile repository, backend registry, target precedence, and preparation tests rather than duplicating their complete matrices.
- Update exact contracts in GoDoc with the exported declarations. Update current-state consumer, format, and internal documentation only after the corresponding code exists.
- Do not add release notes, change a module version, create a release, or tag a commit as part of this work.
Fixed Design
Public API
Add this root-package value immediately after ExecutionTarget in types.go:
// ProfileInspection is the caller-owned result of Engine.InspectProfile.
// The exact GoDoc is specified below.
type ProfileInspection struct {
ProfileID string
EffectiveModelParams ExecutionTarget
APIKeyRequired bool
}
Add this method to Engine in engine.go, immediately before Prepare:
func (e *Engine) InspectProfile(
ctx context.Context,
profileID string,
) (*ProfileInspection, error)
Do not accept RunRequest, ExecutionTargetOverride, an API key, an
environment override, or options on this method. Exact inspection of one
explicit profile ID is the complete operation.
ProfileInspection has no stable JSON contract. Do not add JSON tags,
MarshalJSON, UnmarshalJSON, or a custom string representation. Ordinary Go
encoding of the exported fields is not prohibited, but consumers must not be
promised compatibility for that encoding.
The exact type and method GoDoc must establish:
- surrounding whitespace is trimmed from
profileID; the resulting nonblank ID is looked up exactly and case-sensitively; - the result applies the engine's ordinary in-memory, configured-source, and built-in profile precedence;
EffectiveModelParamscontains framework defaults overlaid by the selected backend and then the selected profile, with no request override;EffectiveModelParams.BackendIDis empty for endpoint-only profiles;EffectiveModelParams.APIKeyEnvis an environment-variable name and never its value;APIKeyRequiredmeans a direct request credential is required and is mutually exclusive with a nonblank effectiveAPIKeyEnv;- the method never derives an ID from a prompt's
default_profile; - the method performs no prompt lookup, rendering, artifact or schema work, backend admission, provider connectivity check, or model generation;
- credential availability is not checked, so an absent or blank named environment variable is not an error;
- the returned value and all nested mutable values are caller-owned;
- filesystem-backed inspection is a point-in-time lookup and does not freeze a profile for a later execution;
- a nil engine matches
ErrInvalidConfig; - a blank ID matches
ErrInvalidRequest; - an absent exact ID matches
ErrProfileNotFoundand notErrProfileLoad; - malformed or unreadable profile data, an unknown backend, or an invalid
resolved target matches
ErrProfileLoad; - cancellation observed during profile loading matches
ErrProfileLoadwhile preserving the context error througherrors.Is; and - the method returns no partial result on error.
Update the Engine type GoDoc to include InspectProfile among the operations
safe for concurrent calls. Do not imply that injected collaborators become
safe when their existing contracts do not provide that guarantee.
Update doc.go in the same stage:
- include
Engine.InspectProfilein the package's operation list; - include inspection in the concurrency and caller-ownership summary; and
- list
ProfileInspectionamong construction and inspection values without a stable JSON representation.
Do not add ProfileInspection to the stable JSON list.
Internal Result
Add this internal value to internal/domain/domain.go near
ExecutionProfile and ExecutionTarget:
// ProfileInspection is the resolved result of exact profile inspection.
type ProfileInspection struct {
ProfileID string
EffectiveModelParams ExecutionTarget
APIKeyRequired bool
}
Do not add JSON or YAML tags. The internal value is a use-case result, not a file format or persistence contract.
Add a root conversion in convert.go:
func fromDomainProfileInspection(
inspection *domain.ProfileInspection,
) *ProfileInspection
The conversion must:
- return
nilfor a nil internal value; - copy the scalar fields;
- convert the target through the existing
fromDomainExecutionTarget; and - therefore use the existing recursive
copyAnyMapboundary for nested extra parameters.
Do not add APIKeyRequired to the public ExecutionTarget. Keeping the
requirement on ProfileInspection preserves the existing stable JSON and
general execution-target contract.
Shared Profile Selection
Add internal/usecase/profile_inspection.go. Define one private selection
value:
type resolvedProfileSelection struct {
id string
profile *domain.ExecutionProfile
backend *domain.Backend
}
Add a private runner helper:
func (r *Runner) resolveProfileSelection(
ctx context.Context,
profileID string,
) (*resolvedProfileSelection, error)
The helper must perform these operations in order:
- trim surrounding whitespace from
profileID; - reject a blank result with
ErrInvalidRequest; - reject a nil runner profile repository with
ErrProfileLoadrather than panicking; - call the existing
profile.Repository.GetProfileexactly once; - wrap every repository error with
ErrProfileLoadwhile preserving the underlying error with%w; - reject a nil profile returned without an error as
ErrProfileLoad; - make a value copy of the selected profile before normalization so the runner does not mutate repository-owned state;
- trim the copied profile's backend ID;
- when that ID is nonblank, resolve it exactly once through the existing
BackendResolver, preserving the current unknown-backendErrProfileLoadwrapping and backend ID context; and - return the normalized exact ID, copied profile, and optional defensive backend value.
Do not inspect all sources, enumerate profiles, expose a source path, fall back after a malformed higher-precedence match, or infer a backend from an endpoint or model.
Refactor the profile-loading and backend-resolution block in
Runner.resolvePreparation to call resolveProfileSelection. Leave prompt
selection, prompt default-profile selection, prompt hashing, target overrides,
request credentials, output-contract resolution, artifact work, and all later
ordering where they currently occur.
This refactor must preserve:
- explicit request profile selection over prompt
default_profile; - the existing
ErrProfileRequiredresult when neither exists; - exact repository and backend lookup behavior;
- existing public not-found versus profile-load classification;
- request override precedence and target-presence tracking;
- credential checking during ordinary preparation; and
- current
Prepare,PrepareExecution,Run, andRunPreparedbehavior.
Shared Target Resolution And Structural Validation
Continue using the existing resolveExecutionTarget for framework, backend,
profile, and optional request precedence. Do not duplicate or move the
individual merge rules into the inspection code.
Extract the two existing effective-target requiredness checks from
resolvePreparation into a private pure helper:
func validateResolvedExecutionTarget(
target domain.ExecutionTarget,
) error
It checks only that the trimmed endpoint and model are nonblank and returns a plain descriptive error. It does not validate URL syntax, contact the endpoint, validate credentials, or introduce new parameter rules.
resolvePreparation calls this helper at the same current point: after
request override resolution and direct API-key assignment, and before
validateAPIKey. It wraps a failure with ErrInvalidRequest, preserving
ordinary preparation behavior.
Add the internal inspection method:
func (r *Runner) InspectProfile(
ctx context.Context,
profileID string,
) (*domain.ProfileInspection, error)
It performs these operations:
- trim and reject a blank ID as
ErrInvalidRequest; - if the supplied context is already canceled, return an error wrapping both
ErrProfileLoadandctx.Err()without touching the repository; - call
resolveProfileSelection; - call
resolveExecutionTargetwith the selected backend, selected profile, and a nil request override; - wrap any target-resolution error with
ErrProfileLoad; - call
validateResolvedExecutionTargetand wrap a failure withErrProfileLoad; - defensively clear
target.APIKey; - copy
target.APIKeyRequiredinto the result'sAPIKeyRequired; and - return the normalized profile ID and effective target.
Do not call validateAPIKey, os.Getenv, prompt repositories, artifact
readers, renderers, validators, capacity admission, output repair, or the
model client. Do not populate request target-presence state.
The internal ExecutionTarget may retain its private APIKeyRequired field;
the root conversion intentionally omits that private field from the public
target and publishes the separate inspection boolean.
Root Facade And Error Mapping
Engine.InspectProfile follows the existing facade pattern:
- reject a nil engine or nil runner with
ErrInvalidConfig; - pass the context and string ID directly to
Runner.InspectProfile; - map internal failures through the existing
mapPublicError; and - convert a successful result with
fromDomainProfileInspection.
No request conversion is needed. Do not add a public sentinel or typed error.
The existing error mapping already has the required ordering:
- an underlying
profile.ErrProfileNotFoundmaps toErrProfileNotFoundbefore the enclosing use-caseErrProfileLoadis considered; - other use-case
ErrProfileLoadfailures map toErrProfileLoad; and usecase.ErrInvalidRequestmaps toErrInvalidRequest.
Do not reorder or otherwise change errors.go unless a focused test proves
that the existing mapping does not meet this plan. Preserve underlying
repository, backend, and context errors through errors.Is.
Credentials, Ownership, And Concurrency
Inspection has no input through which a direct credential value can enter.
Backend and file-profile APIKeyEnv values remain names only. An in-memory
profile with APIKeyRequired clears an inherited backend environment name
through the existing target merge behavior.
The implementation must succeed for:
- a backend or profile naming an unset environment variable;
- a backend or profile naming an environment variable whose value is blank;
- an in-memory profile requiring a direct key when no key is supplied; and
- a profile requiring no credential.
The result expresses these effective states:
EffectiveModelParams.APIKeyEnv |
APIKeyRequired |
Meaning |
|---|---|---|
| nonblank | false |
The profile resolves to the named environment source. |
| blank | true |
A later execution must supply a direct key or explicit request environment override. |
| blank | false |
The resolved target declares no credential requirement. |
The implementation must never produce a successful public result with both a
nonblank APIKeyEnv and APIKeyRequired == true.
Public ownership is enforced at the root conversion boundary. A consumer may mutate the returned target and arbitrarily nested JSON-compatible extra parameters without affecting:
- the engine registry or profile repository;
- a later
InspectProfilecall; Prepare, prepared execution, orRun; or- another result already returned to a caller.
No new mutable engine state, cache, global registry, lock, or goroutine is needed. Concurrency safety follows from the existing immutable registry and repository contracts plus per-call values.
Test Ownership
Add focused internal tests in
internal/usecase/profile_inspection_test.go. Use small repository and backend
fakes already present in the package where practical; do not build a parallel
fixture framework.
The internal tests own:
- blank-ID rejection;
- pre-canceled context classification without repository access;
- one exact profile and backend lookup;
- framework-default, backend, and profile target precedence through the existing resolver;
- endpoint-only behavior;
- effective environment-name reporting without availability checks;
- direct-key-required behavior clearing an inherited backend environment name;
- missing-profile and unknown-backend wrapping; and
- nil repository, nil returned profile, and invalid resolved target defenses only if these cases are not already cheaply covered through existing runner fakes.
Keep the internal matrix compact. Profile parser tests continue to own YAML, duplicates, raw-key rejection, ranges, and source discovery. Backend tests continue to own registration validation. Existing target tests continue to own every merge field and numeric override boundary.
Add external-package public contract tests in public_contract_test.go. They
own:
- the exported method and result shape through normal Go use;
- a nil engine returning
ErrInvalidConfig; - blank, missing, malformed, and unknown-backend public error identities,
including that not-found does not also match
ErrProfileLoad; - a pre-canceled inspection preserving both
ErrProfileLoadand the context error without consulting the profile repository; - successful inspection with an absent credential environment value;
- no prompt dependency, demonstrated with an empty configured prompt
fs.FS; - no model invocation, using the existing deterministic fake client;
- the three credential-requirement result states;
- an endpoint-only profile's empty backend ID;
- deep caller ownership of nested extra parameters across repeated inspections; and
- representative equivalence between
InspectProfile.EffectiveModelParamsandPrepare.EffectiveModelParamsfor the same engine, profile, and source state with no request execution override.
Combine closely related assertions into a few readable behavioral tests.
Do not add a JSON golden test because ProfileInspection deliberately has no
stable JSON contract. Do not duplicate the complete repository-precedence,
target-field, parser-error, or credential-execution suites at the root layer.
Existing tests that must continue passing without semantic edits include:
- profile repository and built-in fallback tests;
- backend registry defensive-copy and lookup tests;
- execution-target precedence tests;
PrepareandRunprofile/backend equivalence tests;- prepared-execution snapshot and credential tests; and
- public error mapping tests.
Documentation Ownership
After the implementation and public tests pass, update current-state documentation:
docs/consumers/pkg-promptkit.md: add a concise task-oriented section near profile selection showingEngine.InspectProfile, explaining when to use it instead of a syntheticPrepare, how to interpretAPIKeyEnvandAPIKeyRequired, and that credential enforcement timing remains application policy. Link to the exact GoDoc rather than restating every error and field contract.docs/formats.md: update profile selection and backend-membership wording so exact inspection is recognized as another consumer of the existing source and profile precedence. Do not redefine the exported method here.docs/internal/runner.md: describe the shared profile-selection boundary and explain that inspection stops after structural target resolution, before credential availability and all prompt-dependent work.docs/internal/sources.md: record that exact profile inspection performs one point-in-time profile-source lookup without reading prompt, input, or schema sources, and replace any ambiguous use of “inspection value” forPreparedRunwith “preparation value.”docs/internal/overview.md: add profile inspection to the existing root facade andinternal/usecaseresponsibility descriptions. Do not add a new component or package row.docs/roadmap/future.md: remove the statement that profile inspection is in active planning and leave the remaining unselected ideas intact.docs/roadmap/notarius-promptkit-wishlist.mdanddocs/roadmap/weatherreporter-promptkit-wishlist.md: change the profile inspection disposition from accepted planning to implemented behavior and link to the durable consumer guidance or GoDoc rather than treating the roadmap as current-state documentation.docs/roadmap/profile-inspection.md: change its status toCompleteonly after the code, tests, current-state documentation, and full validation are complete.docs/roadmap/implementation.md: change its status toCompleteonly after every completion gate in this plan is satisfied.
Do not update release guidance in this feature implementation. A later release pass decides whether the change warrants a supplemental release document.
Stage 1: Implement Shared Internal Profile Resolution
Objective
Add the internal inspection result and operation, share profile/backend selection and structural target validation with ordinary preparation, and prove the internal behavior without publishing the root API yet.
Implementation Prompt
Implement only Stage 1 of
docs/roadmap/implementation.md. Read the complete feature roadmap and the
implementation rules and fixed design above before editing.
- Add
domain.ProfileInspectiontointernal/domain/domain.gowithout serialization tags. - Add
internal/usecase/profile_inspection.gowithresolvedProfileSelection,resolveProfileSelection,validateResolvedExecutionTarget, andRunner.InspectProfileexactly as specified. - Refactor
Runner.resolvePreparationininternal/usecase/runner.goto use the shared selection and target-validation helpers while preserving its current ordering, error classification, target overrides, credentials, and results. - Add lean behavioral tests in
internal/usecase/profile_inspection_test.go. - Run the focused validation below and repair regressions before ending the stage.
Do not add the root public type or method, update current-state documentation, or alter profile formats, backend registration, credential availability, capacity, model-client, or provider behavior in this stage.
Focused Validation
Run:
gofmt -w internal/domain/domain.go \
internal/usecase/profile_inspection.go \
internal/usecase/profile_inspection_test.go \
internal/usecase/runner.go
go test ./internal/usecase ./internal/profile ./internal/profile/builtin \
./internal/backend
go test ./internal/usecase -run \
'TestRunner(InspectProfile|Prepare|Run|PrepareExecution|RunPrepared)'
go vet ./internal/usecase ./internal/profile ./internal/backend
Completion Gate
Stage 1 is complete only when:
- inspection reaches profile and backend resolution without any prompt or execution collaborator;
- profile/backend selection is shared with ordinary preparation;
- target merging and requiredness checks are shared rather than duplicated;
- inspection does not check credential values or capacity;
- internal errors preserve the required identities;
- the ordinary preparation and execution tests still pass without weakened assertions; and
- no root public API or current-state documentation claims the feature yet.
Stage 2: Publish The Root Facade And Public Contract
Objective
Expose the minimal caller-owned inspection API through Engine, preserve
public error and JSON compatibility, and protect the consumer-visible
contract.
Implementation Prompt
Implement only Stage 2 of
docs/roadmap/implementation.md after Stage 1 satisfies its completion gate.
Re-read the fixed public API, error, credential, ownership, and test sections
before editing.
- Add
ProfileInspectionand its exact GoDoc totypes.goimmediately afterExecutionTarget. Do not add JSON tags or changeExecutionTarget. - Add
fromDomainProfileInspectiontoconvert.gousingfromDomainExecutionTarget. - Add
Engine.InspectProfileand its exact GoDoc toengine.goimmediately beforePrepare. - Update
EngineGoDoc to include concurrent inspection. - Update the package GoDoc in
doc.gofor operation discovery, concurrency, ownership, and the non-stable JSON classification. - Add compact external-package contract coverage in
public_contract_test.go, using existing fixtures and fakes where they remain clear. - Confirm that the existing
errors.gomapping meets the plan; do not change it unless a required public identity test fails for a genuine mapping reason. - Run the focused validation below and repair regressions before ending the stage.
Do not add enumeration, request overrides, profile fingerprints, JSON stability, prompt inspection, environment credential checks, caching, or current-state prose documentation in this stage.
Focused Validation
Run:
gofmt -w doc.go types.go convert.go engine.go public_contract_test.go
go test .
go test ./internal/usecase
go test . -run 'Test(InspectProfile|.*Profile.*PublicError|.*Profile.*Contract)'
go vet .
go build .
If the repository's actual focused test names differ, use the implemented test names rather than weakening or skipping the intended assertions.
Completion Gate
Stage 2 is complete only when:
- a consumer can call
Engine.InspectProfilethrough the root package; - the result contains only the normalized ID, caller-owned effective target, and direct-key-required signal;
- no credential value can enter the result;
- environment, direct, and no-credential states are unambiguous;
- nil, blank, missing, malformed, unknown-backend, and cancellation errors have the required public identities;
- not-found remains distinct from profile-load failure;
- the effective target matches ordinary preparation absent request overrides;
- repeated calls and caller mutation cannot alter engine-owned state;
- no stable JSON or new error contract was introduced; and
- existing public preparation, execution, and stable JSON tests remain unchanged and passing.
Stage 3: Update Documentation And Validate The Repository
Objective
Make implemented profile inspection discoverable in its canonical documentation, reconcile temporary roadmap state, and complete full repository validation.
Implementation Prompt
Implement only Stage 3 of
docs/roadmap/implementation.md after Stages 1 and 2 satisfy their completion
gates.
- Update
docs/consumers/pkg-promptkit.md,docs/formats.md,docs/internal/runner.md,docs/internal/sources.md, anddocs/internal/overview.mdaccording to the documentation ownership section above. - Update the future catalog and both downstream wishlist dispositions so they no longer describe profile inspection as merely accepted work.
- Check every changed Markdown link and confirm its file and heading target.
- Run the full validation sequence below.
- Only after every check passes, set the feature roadmap and this
implementation plan to
**Status:** Complete. - Re-run
git diff --checkafter the status edits.
Do not add release notes, an example program, a new public package, or a duplicate API reference. Keep detailed contracts in GoDoc and task-oriented usage in the consumer guide.
Full Validation
Run from the repository root:
gofmt -w internal/domain/domain.go \
internal/usecase/profile_inspection.go \
internal/usecase/profile_inspection_test.go \
internal/usecase/runner.go \
doc.go types.go convert.go engine.go public_contract_test.go
gofmt -l $(git ls-files '*.go')
go test ./...
go test -race ./...
go vet ./...
go build ./...
go run ./examples/go-library/prepare
git diff --check
git status --short
The gofmt -l command must print no paths. The maintained example must remain
offline and must not require a real credential or provider.
Inspect the final diff and confirm:
- only files required by this feature and pre-existing user changes are present;
- no credential values, private infrastructure details, workspace files, local replacements, generated binaries, or unrelated formatting changes were added;
- the public declarations and GoDoc own exact API behavior;
- current-state documentation describes only implemented behavior and links to canonical owners;
- roadmap documents contain future scope or completion status rather than a duplicate current API reference; and
- no release, commit, or tag was created.
Completion Gate
The implementation is complete only when:
- every Stage 1 and Stage 2 gate remains satisfied;
- the complete ordinary and race-enabled suites pass;
- vet, build, formatting, the maintained offline example, Markdown links, and whitespace checks pass;
- consumer, format, internal, future, and wishlist documentation are consistent with the implemented boundary;
- both roadmap statuses are
Complete; - the working tree contains no unintended files or changes; and
- the repository is ready for maintainer review without a commit or release having been created by this plan.
Open Questions
None. The accepted feature roadmap and the fixed decisions above fully specify the implementation boundary.