From f5fa607c35f763d288a16f258c79b301dea1e484 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Sun, 4 Oct 2026 00:33:31 +0000 Subject: [PATCH] Adopt PromptKit layered configuration sources --- docs/roadmap/implementation.md | 9 +++- go.mod | 6 +-- go.sum | 14 +++--- internal/framework/llm/runtime.go | 73 ++++++++++++++----------------- internal/prompts/registry_test.go | 19 ++++++-- 5 files changed, 66 insertions(+), 55 deletions(-) diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index 5fe0e70..80b2854 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -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`, diff --git a/go.mod b/go.mod index 2fedd99..e37181e 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index d82d6b0..cb485d6 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/internal/framework/llm/runtime.go b/internal/framework/llm/runtime.go index df26516..471d6b5 100644 --- a/internal/framework/llm/runtime.go +++ b/internal/framework/llm/runtime.go @@ -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 -} diff --git a/internal/prompts/registry_test.go b/internal/prompts/registry_test.go index e101563..e82802b 100644 --- a/internal/prompts/registry_test.go +++ b/internal/prompts/registry_test.go @@ -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)