Implement support for passing a session_id variable to scriptorium to support sticky routing

This commit is contained in:
2026-07-02 21:04:37 -05:00
parent ed879b8bb0
commit a90231ce0c
8 changed files with 78 additions and 5 deletions

View File

@@ -347,6 +347,7 @@ func executeAnalyzeArtifact(
if err != nil {
return nil, fmt.Errorf("analyze: resolve vars for artifact %q: %w", artifactName, err)
}
vars = withScriptoriumStickySessionVar(vars, sessionID)
canonicalOutputPath, err := resolveScriptoriumOutputPath(paths, artifactCfg.OutputPath)
if err != nil {

View File

@@ -54,6 +54,9 @@ func TestAnalyzeGeneratesSessionRecapFromTrimmedTranscript(t *testing.T) {
if req.Timeout != 2*time.Minute {
t.Fatalf("timeout = %s, want 2m", req.Timeout)
}
if req.Vars["session_id"] != "narratio-session-2026-05-03" {
t.Fatalf("session_id var = %q, want sticky narratio session id", req.Vars["session_id"])
}
if len(result.Outputs) != 1 || result.Outputs[0].Kind != "session_recap" {
t.Fatalf("outputs = %#v, want one session_recap output", result.Outputs)
@@ -69,6 +72,34 @@ func TestAnalyzeGeneratesSessionRecapFromTrimmedTranscript(t *testing.T) {
}
}
func TestAnalyzeStickySessionVarOverridesConfiguredAndPreservesArbitraryVars(t *testing.T) {
env, m, fake := setupAnalyzeEnv(t)
paths := sessionPathsForEnv(env, m.SessionID)
writeAnalyzeFile(t, filepath.Join(paths.TranscriptsDir, "final.trimmed.json"), `{"segments":[]}`)
artifact := env.Config.Pipeline.Scriptorium.Artifacts["session_recap"]
artifact.Vars = map[string]any{
"session_id": "configured-session",
"character_name": "Hrank",
"character_class": "Fighter",
}
env.Config.Pipeline.Scriptorium.Artifacts["session_recap"] = artifact
if _, err := (analyzeStage{}).Run(context.Background(), env, m); err != nil {
t.Fatalf("Run() error = %v", err)
}
req := fake.RunRequests[0]
if req.Vars["session_id"] != "narratio-session-2026-05-03" {
t.Fatalf("session_id var = %q, want sticky value", req.Vars["session_id"])
}
if req.Vars["character_name"] != "Hrank" {
t.Fatalf("character_name var = %q, want Hrank", req.Vars["character_name"])
}
if req.Vars["character_class"] != "Fighter" {
t.Fatalf("character_class var = %q, want Fighter", req.Vars["character_class"])
}
}
func TestAnalyzeRenderDebugFalseDoesNotCallRenderArtifact(t *testing.T) {
env, m, fake := setupAnalyzeEnv(t)
paths := sessionPathsForEnv(env, m.SessionID)
@@ -142,6 +173,12 @@ func TestAnalyzeRenderOutputPathIsRecorded(t *testing.T) {
if len(fake.RenderRequests) != 1 {
t.Fatalf("render requests = %d, want 1", len(fake.RenderRequests))
}
if fake.RenderRequests[0].Vars["session_id"] != "narratio-session-2026-05-03" {
t.Fatalf("render session_id var = %q, want sticky narratio session id", fake.RenderRequests[0].Vars["session_id"])
}
if fake.RunRequests[0].Vars["session_id"] != "narratio-session-2026-05-03" {
t.Fatalf("run session_id var = %q, want sticky narratio session id", fake.RunRequests[0].Vars["session_id"])
}
if result.Metadata["render_output_path"] != filepath.Join(paths.ArtifactsDir, "session_recap.render.json") {
t.Fatalf("render_output_path = %#v, want session_recap.render.json path", result.Metadata["render_output_path"])
}

View File

@@ -0,0 +1,21 @@
package stage
import (
"strings"
"gitea.maximumdirect.net/eric/narratio/internal/config"
)
func withScriptoriumStickySessionVar(vars map[string]string, sessionID string) map[string]string {
out := make(map[string]string, len(vars)+1)
for k, v := range vars {
out[k] = v
}
if trimmedSessionID := strings.TrimSpace(sessionID); trimmedSessionID != "" {
out[config.DefaultScriptoriumStickySessionVarName] = config.DefaultScriptoriumStickySessionVarPrefix + trimmedSessionID
}
if len(out) == 0 {
return nil
}
return out
}

View File

@@ -150,7 +150,7 @@ func (trimStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*Stag
inputPaths := map[string]string{
boundsCfg.TranscriptInputName: normalizedPath,
}
vars := map[string]string{}
vars := withScriptoriumStickySessionVar(nil, sessionID)
metadata["bounds_prompt_id"] = boundsCfg.PromptID
metadata["bounds_profile_id"] = boundsCfg.ProfileID

View File

@@ -34,6 +34,9 @@ func TestTrimStageConsumesNormalizedAndProducesTrimmedTranscript(t *testing.T) {
if len(scr.RunRequests) != 1 {
t.Fatalf("scriptorium run requests = %d, want 1", len(scr.RunRequests))
}
if scr.RunRequests[0].Vars["session_id"] != "narratio-session-2026-05-03" {
t.Fatalf("session_id var = %q, want sticky narratio session id", scr.RunRequests[0].Vars["session_id"])
}
if len(ser.TrimRequests) != 1 {
t.Fatalf("seriatim trim requests = %d, want 1", len(ser.TrimRequests))
}
@@ -129,6 +132,12 @@ func TestTrimStageRenderDebugDiagnosticsAreNotStageOutputs(t *testing.T) {
if len(scr.RenderRequests) != 1 {
t.Fatalf("scriptorium render requests = %d, want 1", len(scr.RenderRequests))
}
if scr.RenderRequests[0].Vars["session_id"] != "narratio-session-2026-05-03" {
t.Fatalf("render session_id var = %q, want sticky narratio session id", scr.RenderRequests[0].Vars["session_id"])
}
if scr.RunRequests[0].Vars["session_id"] != "narratio-session-2026-05-03" {
t.Fatalf("run session_id var = %q, want sticky narratio session id", scr.RunRequests[0].Vars["session_id"])
}
for _, out := range result.Outputs {
if out.Kind == "session_bounds_render" {
t.Fatalf("render diagnostics should not be stage outputs: %#v", result.Outputs)