Derive stable prompt sessions for CLI runs

This commit is contained in:
2026-08-03 19:05:49 +00:00
parent 39388e96d4
commit 7a4fd7be7a
6 changed files with 149 additions and 17 deletions

View File

@@ -153,10 +153,10 @@ func runPipelineCommand(args []string, stdout, stderr io.Writer, opts Options) i
resume := fs.Bool("resume", false, "reuse compatible recorded checkpoints")
recomputeStep := singleValueFlag{name: "--recompute-step"}
chunkCache := chunkCacheFlag{}
sessionID := sessionIDFlag{}
requestedSessionID := sessionIDFlag{}
referenceFlags := stringListFlag{}
withoutReferenceFlags := stringListFlag{}
fs.Var(&sessionID, "session-id", "prompt session identifier")
fs.Var(&requestedSessionID, "session-id", "prompt session identifier")
fs.Var(&reasoningEffort, "reasoning-effort", "reasoning effort override")
fs.Var(&chunkCache, "chunk_cache", "chunk plan cache mode: auto, bypass, or refresh")
fs.Var(&referenceFlags, "reference", "reference binding, as slot=path, chunk.slot=path, merge.slot=path, lane.slot=path, lane.extract.slot=path, lane.merge.slot=path, or lane.normalize.slot=path")
@@ -199,7 +199,7 @@ func runPipelineCommand(args []string, stdout, stderr io.Writer, opts Options) i
fmt.Fprintln(stderr, "notarius: --debug-dir must not be empty")
return 2
}
if sessionID.set && strings.TrimSpace(sessionID.value) == "" {
if requestedSessionID.set && strings.TrimSpace(requestedSessionID.value) == "" {
fmt.Fprintln(stderr, "notarius: --session-id must not be empty")
return 2
}
@@ -414,11 +414,15 @@ func runPipelineCommand(args []string, stdout, stderr io.Writer, opts Options) i
if err != nil {
return failPipelineCommand(stderr, commandState, terminalWriter, fmt.Errorf("read input %q: %w", strings.TrimSpace(*inputPath), err))
}
effectiveSessionID, err := resolvePromptSessionID(requestedSessionID.value, effective.ResolvedPipeline.Input.Module, rawInput)
if err != nil {
return failPipelineCommand(stderr, commandState, terminalWriter, err)
}
chunkPlans, err := chunkPlanStoreForRun(effective.Config.Cache.ChunkPlans, opts)
if err != nil {
return failPipelineCommand(stderr, commandState, terminalWriter, err)
}
checkpointRecorder, checkpointLoader, err := checkpointHandlersForRun(effective.Config.Cache.Checkpoints, opts, effective.ResolvedPipeline, prepared.CheckpointFingerprints(), llmFingerprints, rawInput, only, llmProfiles, strings.TrimSpace(*llmProfile), strings.TrimSpace(sessionID.value), runtimeOverrides, *resume)
checkpointRecorder, checkpointLoader, err := checkpointHandlersForRun(effective.Config.Cache.Checkpoints, opts, effective.ResolvedPipeline, prepared.CheckpointFingerprints(), llmFingerprints, rawInput, only, llmProfiles, strings.TrimSpace(*llmProfile), effectiveSessionID, runtimeOverrides, *resume)
if err != nil {
return failPipelineCommand(stderr, commandState, terminalWriter, err)
}
@@ -427,7 +431,7 @@ func runPipelineCommand(args []string, stdout, stderr io.Writer, opts Options) i
Prepared: prepared,
Path: strings.TrimSpace(*inputPath),
RawInput: rawInput,
SessionID: strings.TrimSpace(sessionID.value),
SessionID: effectiveSessionID,
RunID: runID,
StartedAt: startedAt,
LLMProfiles: llmProfiles,

View File

@@ -492,17 +492,28 @@ func TestEffectiveLLMProfileIDsAreSortedDeduplicatedAndLLMOnly(t *testing.T) {
}
}
func TestRunSessionIDUsesExplicitValueOrSourceDocumentID(t *testing.T) {
func TestRunSessionIDUsesEffectiveValueForPromptRequests(t *testing.T) {
for _, tt := range []struct {
name string
args []string
want string
}{
{name: "source document", want: "source"},
{name: "derived default"},
{name: "explicit trimmed value", args: []string{"--session-id", " explicit-session "}, want: "explicit-session"},
} {
t.Run(tt.name, func(t *testing.T) {
roots := newStateTestRoots(t)
want := tt.want
if want == "" {
rawInput, err := os.ReadFile(roots.input)
if err != nil {
t.Fatal(err)
}
want, err = resolvePromptSessionID("", "test/input", rawInput)
if err != nil {
t.Fatal(err)
}
}
harness := newStateTestHarness()
args := append([]string{"run", "sample", "--config", roots.config, "--input", roots.input, "--chunk_cache", "bypass"}, tt.args...)
var stdout, stderr bytes.Buffer
@@ -517,8 +528,8 @@ func TestRunSessionIDUsesExplicitValueOrSourceDocumentID(t *testing.T) {
t.Fatalf("session IDs = %#v, want all prompt-facing module requests", sessions)
}
for _, session := range sessions {
if session != tt.want {
t.Fatalf("session IDs = %#v, want %q", sessions, tt.want)
if session != want {
t.Fatalf("session IDs = %#v, want %q", sessions, want)
}
}
})

26
internal/cli/session.go Normal file
View File

@@ -0,0 +1,26 @@
package cli
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"strings"
)
const generatedSessionIDPrefix = "notarius:v1:"
func resolvePromptSessionID(explicitSessionID, inputModule string, rawInput []byte) (string, error) {
inputModule = strings.TrimSpace(inputModule)
if inputModule == "" {
return "", fmt.Errorf("resolve prompt session: input module key must not be empty")
}
if sessionID := strings.TrimSpace(explicitSessionID); sessionID != "" {
return sessionID, nil
}
hasher := sha256.New()
_, _ = hasher.Write([]byte(inputModule))
_, _ = hasher.Write([]byte{0})
_, _ = hasher.Write(rawInput)
return generatedSessionIDPrefix + hex.EncodeToString(hasher.Sum(nil)), nil
}

View File

@@ -0,0 +1,61 @@
package cli
import "testing"
func TestResolvePromptSessionIDUsesVersionedInputIdentity(t *testing.T) {
got, err := resolvePromptSessionID("", " seriatim/input/transcript ", []byte("{\"entries\":[\"one\"]}\n"))
if err != nil {
t.Fatal(err)
}
const want = "notarius:v1:e15fefdca48653e73248b4157900547be1fd34250c0138f8d3d1f2e89b43bb25"
if got != want {
t.Fatalf("resolved session = %q, want %q", got, want)
}
}
func TestResolvePromptSessionIDStabilityAndOverride(t *testing.T) {
rawInput := []byte("same input")
baseline, err := resolvePromptSessionID("", "input/transcript", rawInput)
if err != nil {
t.Fatal(err)
}
repeated, err := resolvePromptSessionID("", "input/transcript", rawInput)
if err != nil {
t.Fatal(err)
}
if baseline != repeated {
t.Fatalf("resolved sessions = %q and %q, want stable value", baseline, repeated)
}
differentModule, err := resolvePromptSessionID("", "input/other", rawInput)
if err != nil {
t.Fatal(err)
}
if baseline == differentModule {
t.Fatalf("resolved sessions = %q for distinct input modules", baseline)
}
differentInput, err := resolvePromptSessionID("", "input/transcript", []byte("same inpuu"))
if err != nil {
t.Fatal(err)
}
if baseline == differentInput {
t.Fatalf("resolved sessions = %q for distinct input bytes", baseline)
}
override, err := resolvePromptSessionID(" explicit-session ", "input/other", []byte("different input"))
if err != nil {
t.Fatal(err)
}
if override != "explicit-session" {
t.Fatalf("resolved override = %q, want %q", override, "explicit-session")
}
}
func TestResolvePromptSessionIDRejectsEmptyInputModule(t *testing.T) {
for _, explicitSessionID := range []string{"", "explicit-session"} {
if _, err := resolvePromptSessionID(explicitSessionID, " \t", []byte("input")); err == nil {
t.Fatalf("resolvePromptSessionID(%q) error = nil, want empty module failure", explicitSessionID)
}
}
}

View File

@@ -191,7 +191,7 @@ func (r *Runner) Run(ctx context.Context, input RunInput) (output RunOutput, err
return failOutput(output), fmt.Errorf("write source debug artifact: %w", err)
}
sourceInput := sourceInputMaterial(input.Path, input.RawInput)
sessionID := resolvedSessionID(input.SessionID, doc.ID)
sessionID := strings.TrimSpace(input.SessionID)
output.Manifest.Metadata, err = manifestMetadataWithSessionID(output.Manifest.Metadata, sessionID)
if err != nil {
return failOutput(output), err
@@ -896,13 +896,6 @@ func sourceInputOriginURI(inputPath string) string {
return fileURI(inputPath)
}
func resolvedSessionID(explicit string, sourceDocumentID string) string {
if trimmed := strings.TrimSpace(explicit); trimmed != "" {
return trimmed
}
return strings.TrimSpace(sourceDocumentID)
}
func manifestMetadataWithSessionID(metadata map[string]any, sessionID string) (map[string]any, error) {
out, err := cloneMetadata(metadata)
if err != nil {

View File

@@ -0,0 +1,37 @@
package pipeline
import (
"context"
"testing"
)
func TestRunnerUsesProvidedSessionWithoutSourceFallback(t *testing.T) {
for _, test := range []struct {
name string
session string
wantSet bool
want string
}{
{name: "provided", session: " supplied-session ", wantSet: true, want: "supplied-session"},
{name: "empty", wantSet: false},
} {
t.Run(test.name, func(t *testing.T) {
prepared, _ := preparedTerminalDebugPipeline(t)
output, err := New().Run(context.Background(), RunInput{
Prepared: prepared,
RawInput: []byte("input"),
SessionID: test.session,
})
if err != nil {
t.Fatal(err)
}
value, found := output.Manifest.Metadata["session_id"]
if found != test.wantSet {
t.Fatalf("manifest session presence = %t, want %t; metadata = %#v", found, test.wantSet, output.Manifest.Metadata)
}
if found && value != test.want {
t.Fatalf("manifest session = %#v, want %q", value, test.want)
}
})
}
}