Adopt PromptKit layered configuration sources

This commit is contained in:
2026-10-04 00:33:31 +00:00
parent 43a68e97ee
commit f5fa607c35
5 changed files with 66 additions and 55 deletions

View File

@@ -1,7 +1,7 @@
# Implementation Plan: PromptKit v0.12.0 Adoption
**Last updated:** 2026-10-03
**Status:** In progress; Stages 1–2 completed.
**Status:** In progress; Stages 1–3 completed.
**Audience:** Luna coding agent.
## Scope and Execution
@@ -199,6 +199,13 @@ diff. The obsolete file-reload assertion is intentionally replaced in Stage 6;
do not restore reload behavior to make that assertion pass. Commit the runtime
API migration.
**Completion marker:** Stage 3 complete. Runtime construction now uses the
released PromptKit source API with the selected application mapping; module
selections match PromptKit v0.12.0 and both `/v3` catalogs at
`v3.20260930.0`. `go mod tidy`, prompt registry tests, offline profile
assembly, unsafe-selection, absolute-directory tests, and `git diff --check`
passed. Captured source accounting remains in Stage 5.
## Stage 4 — Preserve Startup Failure Classification and Validate Assembly
**Primary files:** `internal/framework/llm/runtime.go`,

6
go.mod
View File

@@ -4,13 +4,13 @@ go 1.25.5
require (
gitea.maximumdirect.net/eric/ConfigKit v0.5.0
gitea.maximumdirect.net/eric/promptkit v0.11.2
gitea.maximumdirect.net/eric/promptkit v0.12.0
gopkg.in/yaml.v3 v3.0.1
)
require (
gitea.maximumdirect.net/eric/promptkit-backend-openrouter/v2 v2.20260926.0 // indirect
gitea.maximumdirect.net/eric/promptkit-backend-rakestrawhome/v2 v2.20260926.0 // indirect
gitea.maximumdirect.net/eric/promptkit-backend-openrouter/v3 v3.20260930.0 // indirect
gitea.maximumdirect.net/eric/promptkit-backend-rakestrawhome/v3 v3.20260930.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect

14
go.sum
View File

@@ -1,13 +1,11 @@
gitea.maximumdirect.net/eric/ConfigKit v0.4.2 h1:ZMxmv8pOnLtAx2hFTf8Z+pHKUbR89xErkBhwjr4yK24=
gitea.maximumdirect.net/eric/ConfigKit v0.4.2/go.mod h1:tYWpsrbLyu05r8+1MapWp5fN6ZrX9I8hU2xEPedYmYk=
gitea.maximumdirect.net/eric/ConfigKit v0.5.0 h1:2KwG10+kApWM3avOoYA+p768xcbHUp7tQ2rqizBNpyg=
gitea.maximumdirect.net/eric/ConfigKit v0.5.0/go.mod h1:tYWpsrbLyu05r8+1MapWp5fN6ZrX9I8hU2xEPedYmYk=
gitea.maximumdirect.net/eric/promptkit v0.11.2 h1:2RqI+KtflvSh7vLQaTAQCmMaFQscEA7EPwPazyvXRoY=
gitea.maximumdirect.net/eric/promptkit v0.11.2/go.mod h1:onm/FBPUKQ8/NSPwTEk26Ut+dVX2iXcq8vEseB+g/0E=
gitea.maximumdirect.net/eric/promptkit-backend-openrouter/v2 v2.20260926.0 h1:wdab+R+lgMZxzYwhbAuCrW7zdSQ1cW9ovBR6UzLmZR4=
gitea.maximumdirect.net/eric/promptkit-backend-openrouter/v2 v2.20260926.0/go.mod h1:a3VBbh9oaGXD6ruxhtwHj+AWW6DY/SSyby1QtMmYSUo=
gitea.maximumdirect.net/eric/promptkit-backend-rakestrawhome/v2 v2.20260926.0 h1:baCCIB6Ee5t0S4Zc++NTBIPcJEv5UPS6DlSrpZfykwg=
gitea.maximumdirect.net/eric/promptkit-backend-rakestrawhome/v2 v2.20260926.0/go.mod h1:mtgD1E0SGm0e/I8Ffhz+AYX7GN7RKUWJXehNFIKHs4Y=
gitea.maximumdirect.net/eric/promptkit v0.12.0 h1:oIFWEg6GPxtmX3Plqv0euP+FnK1LCk/t4xDy6oKCpP4=
gitea.maximumdirect.net/eric/promptkit v0.12.0/go.mod h1:pFOOy5v1zH17stEi9rmHYmfbuLjnfVMcUhfmk+4VtJY=
gitea.maximumdirect.net/eric/promptkit-backend-openrouter/v3 v3.20260930.0 h1:lzfCDbzJQRGzIlRPRprnAV9hVVL5zUQy80VMQ0kbYa0=
gitea.maximumdirect.net/eric/promptkit-backend-openrouter/v3 v3.20260930.0/go.mod h1:HjS0f+3q0IdHCTEz+VVAEgIwIWJFAZUjuKdAtvtqKdg=
gitea.maximumdirect.net/eric/promptkit-backend-rakestrawhome/v3 v3.20260930.0 h1:h12ZTRldAmpLAomj0oQRGteFxHLN1LPE6XDl9zJNbm8=
gitea.maximumdirect.net/eric/promptkit-backend-rakestrawhome/v3 v3.20260930.0/go.mod h1:OEFBeKdjTsLzqJwLmXI2O5gG3r64FktcVJVb3mBrRfg=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=

View File

@@ -4,15 +4,12 @@ import (
"context"
"errors"
"fmt"
"io/fs"
"net/http"
"os"
"path/filepath"
"sort"
"strings"
"sync"
"sync/atomic"
ck "gitea.maximumdirect.net/eric/ConfigKit"
"gitea.maximumdirect.net/eric/audita/internal/core/config"
"gitea.maximumdirect.net/eric/audita/internal/core/redaction"
"gitea.maximumdirect.net/eric/audita/internal/framework/responseschema"
@@ -43,22 +40,36 @@ func NewRuntime(ctx context.Context, c config.Config, mainPath string, client *h
if !filepath.IsAbs(profileDir) {
profileDir = filepath.Join(filepath.Dir(mainPath), profileDir)
}
profileFS := &profileSourceFS{FS: os.DirFS(profileDir), paths: make(map[string]struct{})}
options := []pk.Option{
pk.WithPromptFS(prompts.Filesystem(), prompts.AssetRoot),
pk.WithSchemaFS(responseschema.Filesystem(), responseschema.AssetRoot),
pk.WithProfileFS(profileFS, "."),
promptInputs, err := pk.FilesystemInputs("Audita embedded prompts", prompts.Filesystem(), prompts.AssetRoot)
if err != nil {
return nil, fmt.Errorf("discover embedded prompts: %w", err)
}
ids := make([]string, 0, len(c.Backends))
profileInputs, err := pk.DirectoryInputs(profileDir)
if err != nil {
return nil, fmt.Errorf("discover operator definitions: %w", err)
}
applicationBackends := make(map[string]any, len(c.Backends))
for id := range c.Backends {
ids = append(ids, id)
}
sort.Strings(ids)
for _, id := range ids {
backend := c.Backends[id]
options = append(options, pk.WithBackend(pk.Backend{ID: id, Endpoint: backend.Endpoint, ConcurrencyLimit: backend.Concurrency, QueueCapacity: &backend.QueueCapacity}))
applicationBackends[id] = map[string]any{
"endpoint": backend.Endpoint,
"concurrency_limit": backend.Concurrency,
"queue_capacity": backend.QueueCapacity,
"api_key_env": nil,
}
}
engine, err := pk.NewEngine(pk.Config{HTTPClient: client, TransientRetries: &c.LLM.TransientRetries}, options...)
sources := pk.ConfigurationSources{
Underlays: []ck.Underlay{
{Inputs: promptInputs},
{Inputs: profileInputs},
},
Main: &ck.Input{Source: ck.Mapping("application backends", map[string]any{"backends": applicationBackends})},
}
engine, err := pk.NewEngine(pk.Config{
Sources: sources,
HTTPClient: client,
TransientRetries: &c.LLM.TransientRetries,
}, pk.WithSchemaFS(responseschema.Filesystem(), responseschema.AssetRoot))
if err != nil {
return nil, fmt.Errorf("assemble LLM runtime: %w", err)
}
@@ -78,6 +89,12 @@ func NewRuntime(ctx context.Context, c config.Config, mainPath string, client *h
if !ok {
return nil, fmt.Errorf("%s profile must select a registered application backend", selection.role)
}
if strings.TrimSpace(target.Model) == "" {
return nil, fmt.Errorf("%s profile must select a nonblank model", selection.role)
}
if strings.TrimSpace(target.Endpoint) == "" {
return nil, fmt.Errorf("%s profile must resolve to a nonblank endpoint", selection.role)
}
endpoint := func(value string) string { return strings.TrimRight(strings.TrimSpace(value), "/") }
if endpoint(target.Endpoint) != endpoint(backend.Endpoint) {
return nil, fmt.Errorf("%s profile endpoint must match its registered backend", selection.role)
@@ -90,12 +107,6 @@ func NewRuntime(ctx context.Context, c config.Config, mainPath string, client *h
}
runtime.Profiles[selection.role] = *profile
}
profileFS.mu.Lock()
for name := range profileFS.paths {
runtime.ProfilePaths = append(runtime.ProfilePaths, filepath.Join(profileDir, filepath.FromSlash(name)))
}
profileFS.mu.Unlock()
sort.Strings(runtime.ProfilePaths)
return runtime, nil
}
@@ -109,21 +120,3 @@ func RedactError(err error, c config.Config) string {
}
return redaction.New(ConfiguredSecrets(&c)).String(err.Error())
}
// Record successful profile reads at the library boundary, including inherited
// and differently named sources, without duplicating PromptKit's discovery rules.
type profileSourceFS struct {
fs.FS
mu sync.Mutex
paths map[string]struct{}
}
func (f *profileSourceFS) ReadFile(name string) ([]byte, error) {
data, err := fs.ReadFile(f.FS, name)
if err == nil {
f.mu.Lock()
f.paths[name] = struct{}{}
f.mu.Unlock()
}
return data, err
}

View File

@@ -5,15 +5,28 @@ import (
"strings"
"testing"
ck "gitea.maximumdirect.net/eric/ConfigKit"
"gitea.maximumdirect.net/eric/audita/internal/framework/responseschema"
pk "gitea.maximumdirect.net/eric/promptkit"
)
func TestEmbeddedDefinitionsPrepareThroughPromptKit(t *testing.T) {
engine, err := pk.NewEngine(pk.Config{},
pk.WithPromptFS(Filesystem(), AssetRoot),
promptInputs, err := pk.FilesystemInputs("embedded prompts", Filesystem(), AssetRoot)
if err != nil {
t.Fatal(err)
}
endpoint := "https://example.invalid/v1"
model := "fixture-model"
sources := pk.ConfigurationSources{
Underlays: []ck.Underlay{
{Inputs: promptInputs},
{Inputs: []ck.Input{{Source: pk.ProfileSource("registry fixture profile", map[string]pk.ProfileConfig{
"fixture": {Endpoint: &endpoint, Model: &model},
})}}},
},
}
engine, err := pk.NewEngine(pk.Config{Sources: sources},
pk.WithSchemaFS(responseschema.Filesystem(), responseschema.AssetRoot),
pk.WithProfiles(pk.Profile{ID: "fixture", Endpoint: "https://example.invalid/v1", Model: "fixture-model"}),
)
if err != nil {
t.Fatal(err)