materializer + replay ports + vectord substrate fix verified at scale

Two threads landing together — the doc edits interleave so they ship
in a single commit.

1. **vectord substrate fix verified at original scale** (closes the
   2026-05-01 thread). Re-ran multitier 5min @ conc=50: 132,211
   scenarios at 438/sec, 6/6 classes at 0% failure (was 4/6 pre-fix).
   Throughput dropped 1,115 → 438/sec because previously-broken
   scenarios now do real HNSW Add work — honest cost of correctness.
   The fix (i.vectors side-store + safeGraphAdd recover wrappers +
   smallIndexRebuildThreshold=32 + saveTask coalescing) holds at the
   footprint that originally surfaced the bug.

2. **Materializer port** — internal/materializer + cmd/materializer +
   scripts/materializer_smoke.sh. Ports scripts/distillation/transforms.ts
   (12 transforms) + build_evidence_index.ts (idempotency, day-partition,
   receipt). On-wire JSON shape matches TS so Bun and Go runs are
   interchangeable. 14 tests green.

3. **Replay port** — internal/replay + cmd/replay +
   scripts/replay_smoke.sh. Ports scripts/distillation/replay.ts
   (retrieve → bundle → /v1/chat → validate → log). Closes audit-FULL
   phase 7 live invocation on the Go side. Both runtimes append to the
   same data/_kb/replay_runs.jsonl (schema=replay_run.v1). 14 tests green.

Side effect on internal/distillation/types.go: EvidenceRecord gained
prompt_tokens, completion_tokens, and metadata fields to mirror the TS
shape the materializer transforms produce.

STATE_OF_PLAY refreshed to 2026-05-02; ARCHITECTURE_COMPARISON decisions
tracker moves the materializer + replay items from _open_ to DONE and
adds the substrate-fix scale verification row.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
root
2026-05-02 03:31:02 -05:00
co-authored by Claude Opus 4.7
parent 277884b5eb
commit 89ca72d471
26 changed files with 3961 additions and 124 deletions
+131
View File
@@ -0,0 +1,131 @@
package replay
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"time"
)
// callModelResult is what the gateway round-trip returns.
type callModelResult struct {
Content string
OK bool
Error string
}
// ModelCaller is the seam tests use to swap out HTTP. Production
// supplies httpModelCaller; tests can supply scripted responses.
type ModelCaller func(ctx context.Context, model, system, user string) callModelResult
// httpModelCaller posts to ${gatewayURL}/v1/chat with provider derived
// from model name. Mirrors replay.ts:callModel.
func httpModelCaller(gatewayURL string) ModelCaller {
client := &http.Client{Timeout: 180 * time.Second}
return func(ctx context.Context, model, system, user string) callModelResult {
provider := inferProvider(model)
body, err := json.Marshal(map[string]any{
"provider": provider,
"model": model,
"messages": []map[string]string{
{"role": "system", "content": system},
{"role": "user", "content": user},
},
"max_tokens": 1500,
"temperature": 0.1,
})
if err != nil {
return callModelResult{Error: "marshal request: " + err.Error()}
}
req, err := http.NewRequestWithContext(ctx, "POST", gatewayURL+"/v1/chat", bytes.NewReader(body))
if err != nil {
return callModelResult{Error: "build request: " + err.Error()}
}
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return callModelResult{Error: trim(err.Error(), 240)}
}
defer resp.Body.Close()
buf, _ := io.ReadAll(resp.Body)
if resp.StatusCode >= 400 {
return callModelResult{Error: fmt.Sprintf("HTTP %d: %s", resp.StatusCode, trim(string(buf), 240))}
}
var parsed struct {
Choices []struct {
Message struct {
Content string `json:"content"`
} `json:"message"`
} `json:"choices"`
}
if err := json.Unmarshal(buf, &parsed); err != nil {
return callModelResult{Error: "parse response: " + err.Error()}
}
content := ""
if len(parsed.Choices) > 0 {
content = parsed.Choices[0].Message.Content
}
return callModelResult{Content: content, OK: true}
}
}
// inferProvider picks the right /v1/chat provider for a given model
// name. Mirrors replay.ts:callModel's branching exactly so the gateway
// sees the same request shape regardless of caller runtime.
//
// "/" in name → openrouter
// kimi-/qwen3-coder/... → ollama_cloud
// else → ollama (local)
func inferProvider(model string) string {
if strings.Contains(model, "/") {
return "openrouter"
}
switch {
case strings.HasPrefix(model, "kimi-"),
strings.HasPrefix(model, "qwen3-coder"),
strings.HasPrefix(model, "deepseek-v"),
strings.HasPrefix(model, "mistral-large"),
model == "gpt-oss:120b",
model == "qwen3.5:397b":
return "ollama_cloud"
}
return "ollama"
}
// dryRunSynthesize produces a deterministic synthetic response that
// echoes context-bundle signals. Used by tests + dry-run mode to
// exercise retrieval + validation without a live LLM.
func dryRunSynthesize(task string, bundle *ContextBundle) string {
parts := []string{
"Synthetic dry-run response for task: " + trim(task, 120),
"",
}
if bundle != nil {
parts = append(parts, fmt.Sprintf(
"Retrieved %d playbooks; %d accepted, %d partial.",
len(bundle.RetrievedPlaybooks),
len(bundle.PriorSuccessfulOutputs),
len(bundle.FailurePatterns),
))
if len(bundle.ValidationSteps) > 0 {
parts = append(parts, "Following validation checklist:")
for i, s := range bundle.ValidationSteps {
if i >= 3 {
break
}
parts = append(parts, "- "+s)
}
}
if len(bundle.PriorSuccessfulOutputs) > 0 {
parts = append(parts, "")
parts = append(parts, "Anchored on prior accepted: "+bundle.PriorSuccessfulOutputs[0].Title)
}
} else {
parts = append(parts, "No retrieval context — answering from task alone. Verify and check produced output before approving.")
}
return strings.Join(parts, "\n")
}
+64
View File
@@ -0,0 +1,64 @@
package replay
import "strings"
// PromptParts captures the two roles the prompt assembly produces.
type PromptParts struct {
System string
User string
}
const systemPrompt = "You are a Lakehouse task executor. Stay grounded — only assert what you can derive from the prior successful patterns or the task itself. " +
"Do NOT hedge. Do NOT say 'as an AI'. Produce a concrete actionable answer. " +
"When prior successful outputs are provided, follow their style and format."
// BuildPrompt assembles the system + user messages for a model call.
// When bundle is nil (NoRetrieval mode), the user message is just the
// task — same wording as replay.ts so completions stay comparable.
func BuildPrompt(task string, bundle *ContextBundle) PromptParts {
if bundle == nil {
return PromptParts{
System: systemPrompt,
User: "Task: " + task + "\n\nProduce the answer.",
}
}
var b strings.Builder
if len(bundle.PriorSuccessfulOutputs) > 0 {
b.WriteString("## Prior successful runs on similar tasks\n\n")
for _, r := range bundle.PriorSuccessfulOutputs {
b.WriteString("### ")
b.WriteString(r.Title)
b.WriteString(" (score: ")
b.WriteString(r.SuccessScore)
b.WriteString(")\n")
b.WriteString(r.ContentPreview)
b.WriteString("\n\n")
}
}
if len(bundle.FailurePatterns) > 0 {
b.WriteString("## Patterns that produced PARTIAL results — avoid these failure modes\n\n")
for _, r := range bundle.FailurePatterns {
b.WriteString("- ")
b.WriteString(r.Title)
b.WriteString(": ")
b.WriteString(trim(r.ContentPreview, 160))
b.WriteByte('\n')
}
b.WriteByte('\n')
}
if len(bundle.ValidationSteps) > 0 {
b.WriteString("## Validation checklist (from accepted runs)\n")
for _, s := range bundle.ValidationSteps {
b.WriteString("- ")
b.WriteString(s)
b.WriteByte('\n')
}
b.WriteByte('\n')
}
b.WriteString("## Task\n")
b.WriteString(task)
b.WriteString("\n\nProduce the answer following the style of the prior successful runs above.")
return PromptParts{System: systemPrompt, User: b.String()}
}
+193
View File
@@ -0,0 +1,193 @@
package replay
import (
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"path/filepath"
"time"
)
// DefaultRoot is what the CLI uses when --root isn't passed.
func DefaultRoot() string {
if r := os.Getenv("LH_DISTILL_ROOT"); r != "" {
return r
}
if cwd, err := os.Getwd(); err == nil {
return cwd
}
return "/home/profit/lakehouse"
}
// Replay runs the retrieve→prompt→model→validate→log pipeline.
// Returns a ReplayResult that's already been appended to
// data/_kb/replay_runs.jsonl unless DryRun + the file is read-only.
//
// Errors here are *infrastructure* failures (corpus unreadable, log
// write failed). A failed model call OR a failed validation gate is
// captured in ReplayResult.ValidationResult, not returned as error —
// callers can branch on Passed / EscalationPath.
func Replay(ctx context.Context, opts ReplayRequest, root string) (ReplayResult, error) {
t0 := time.Now()
recordedAt := time.Now().UTC().Format(time.RFC3339Nano)
taskHash := sha256Hex(opts.Task)
corpus, err := LoadRagCorpus(root)
if err != nil {
return ReplayResult{}, fmt.Errorf("load rag corpus: %w", err)
}
var bundle *ContextBundle
if !opts.NoRetrieval {
bundle = BuildContextBundle(corpus, opts.Task)
}
prompt := BuildPrompt(opts.Task, bundle)
localModel := orDefault(opts.LocalModel, DefaultLocalModel)
escalationModel := orDefault(opts.EscalationModel, DefaultEscalationModel)
gatewayURL := orDefault(opts.GatewayURL, gatewayFromEnv())
caller := httpModelCaller(gatewayURL)
if opts.DryRun {
caller = dryRunCaller(opts.Task, bundle)
}
escalation := []string{localModel}
modelUsed := localModel
var modelResponse string
var validation ValidationResult
localCall := caller(ctx, localModel, prompt.System, prompt.User)
if localCall.OK {
modelResponse = localCall.Content
validation = ValidateResponse(modelResponse, bundle)
} else {
validation = ValidationResult{
Passed: false,
Reasons: []string{"local call failed: " + localCall.Error},
}
}
if !validation.Passed && opts.AllowEscalation && !opts.LocalOnly {
escalation = append(escalation, escalationModel)
escalCall := caller(ctx, escalationModel, prompt.System, prompt.User)
if escalCall.OK {
modelResponse = escalCall.Content
modelUsed = escalationModel
validation = ValidateResponse(modelResponse, bundle)
if validation.Passed {
validation.Reasons = append([]string{"recovered via escalation to " + escalationModel}, validation.Reasons...)
}
} else {
validation.Reasons = append(validation.Reasons, "escalation also failed: "+escalCall.Error)
}
}
recordedRunID := fmt.Sprintf("replay:%s:%s",
taskHash[:16],
sha256Hex(recordedAt)[:12],
)
result := ReplayResult{
InputTask: opts.Task,
TaskHash: taskHash,
RetrievedArtifacts: RetrievedIDs{RagIDs: ragIDs(bundle)},
ContextBundle: bundle,
ModelResponse: modelResponse,
ModelUsed: modelUsed,
EscalationPath: escalation,
ValidationResult: validation,
RecordedRunID: recordedRunID,
RecordedAt: recordedAt,
DurationMs: time.Since(t0).Milliseconds(),
}
if err := logReplayEvidence(root, result); err != nil {
// Logging failure is real — surface it. The caller still gets the
// in-memory result so they can inspect what happened.
return result, fmt.Errorf("log replay evidence: %w", err)
}
return result, nil
}
// dryRunCaller wraps dryRunSynthesize as a ModelCaller. The escalation
// branch in Replay calls the caller a second time; for parity with TS,
// we return the same content suffixed with [ESCALATED] so a smoke can
// detect escalation in dry-run mode.
func dryRunCaller(task string, bundle *ContextBundle) ModelCaller {
calls := 0
return func(_ context.Context, _ string, _ string, _ string) callModelResult {
calls++
content := dryRunSynthesize(task, bundle)
if calls >= 2 {
content += "\n\n[ESCALATED]"
}
return callModelResult{Content: content, OK: true}
}
}
// logReplayEvidence appends one row to data/_kb/replay_runs.jsonl.
// model_response is truncated to 4000 chars in the persisted log to
// keep the file lean (matches TS behavior).
func logReplayEvidence(root string, result ReplayResult) error {
path := filepath.Join(root, "data", "_kb", "replay_runs.jsonl")
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return err
}
persist := struct {
Schema string `json:"schema"`
ReplayResult
}{
Schema: "replay_run.v1",
ReplayResult: result,
}
persist.ReplayResult.ModelResponse = trim(persist.ReplayResult.ModelResponse, 4000)
buf, err := json.Marshal(persist)
if err != nil {
return err
}
buf = append(buf, '\n')
f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return err
}
defer f.Close()
_, err = f.Write(buf)
return err
}
func ragIDs(bundle *ContextBundle) []string {
if bundle == nil {
return []string{}
}
out := make([]string, 0, len(bundle.RetrievedPlaybooks))
for _, p := range bundle.RetrievedPlaybooks {
out = append(out, p.RagID)
}
return out
}
func sha256Hex(s string) string {
h := sha256.Sum256([]byte(s))
return hex.EncodeToString(h[:])
}
func gatewayFromEnv() string {
if u := os.Getenv("LH_GATEWAY_URL"); u != "" {
return u
}
return DefaultGatewayURL
}
func orDefault(v, fallback string) string {
if v == "" {
return fallback
}
return v
}
+283
View File
@@ -0,0 +1,283 @@
package replay
import (
"context"
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
)
// ─── Tokenization + retrieval primitives ───────────────────────────
func TestTokenize_FiltersShortAndLowercase(t *testing.T) {
got := tokenize("Hello, World! Foo BAR baz x12 a")
want := map[string]bool{"hello": true, "world": true, "foo": true, "bar": true, "baz": true, "x12": true}
for k := range want {
if _, ok := got[k]; !ok {
t.Errorf("missing token %q", k)
}
}
if _, ok := got["a"]; ok {
t.Errorf("len=1 token should be filtered: a")
}
}
func TestJaccard_EdgeCases(t *testing.T) {
a := map[string]struct{}{"x": {}, "y": {}, "z": {}}
b := map[string]struct{}{"y": {}, "z": {}, "w": {}}
got := jaccard(a, b)
want := 2.0 / 4.0 // |A∩B|=2 (y,z); |A∪B|=4 (x,y,z,w)
if got != want {
t.Errorf("jaccard = %v, want %v", got, want)
}
if jaccard(map[string]struct{}{}, b) != 0 {
t.Error("empty set should produce 0")
}
}
// ─── Retrieval ───────────────────────────────────────────────────
func TestRetrieveRag_ScoresAndCaps(t *testing.T) {
corpus := []RagSample{
{ID: "p1", Title: "validate scrum", Content: "verify the build, check tests", Tags: []string{"scrum"}, SuccessScore: "accepted"},
{ID: "p2", Title: "irrelevant cooking notes", Content: "boil pasta longer than ten minutes", Tags: []string{"food"}, SuccessScore: "accepted"},
{ID: "p3", Title: "build verification ladder", Content: "verify build steps, assert green", Tags: []string{"build"}, SuccessScore: "partially_accepted"},
}
got := retrieveRag(corpus, "verify the build assert green", 3)
if len(got) == 0 {
t.Fatal("expected at least one result")
}
for _, a := range got {
if a.RagID == "p2" {
t.Errorf("irrelevant sample p2 should not surface, got: %+v", got)
}
}
}
func TestBuildContextBundle_SplitsAcceptedAndPartial(t *testing.T) {
corpus := []RagSample{
{ID: "a1", Title: "A1", Content: "verify build assert green check tests", SuccessScore: "accepted"},
{ID: "p1", Title: "P1", Content: "verify build sometimes fails to assert", SuccessScore: "partially_accepted"},
}
b := BuildContextBundle(corpus, "verify build assert tests")
if b == nil {
t.Fatal("nil bundle")
}
if len(b.PriorSuccessfulOutputs) != 1 || b.PriorSuccessfulOutputs[0].RagID != "a1" {
t.Errorf("accepted bucket wrong: %+v", b.PriorSuccessfulOutputs)
}
if len(b.FailurePatterns) != 1 || b.FailurePatterns[0].RagID != "p1" {
t.Errorf("partially_accepted bucket wrong: %+v", b.FailurePatterns)
}
if len(b.ValidationSteps) == 0 {
t.Errorf("expected validation_steps from accepted sample, got none")
}
}
// ─── Prompt assembly ─────────────────────────────────────────────
func TestBuildPrompt_NoBundleIsCompact(t *testing.T) {
p := BuildPrompt("rebuild evidence index", nil)
if !strings.Contains(p.User, "Task: rebuild evidence index") {
t.Errorf("user prompt missing task: %q", p.User)
}
if strings.Contains(p.User, "## Prior successful runs") {
t.Error("no-bundle prompt should not include retrieval headers")
}
}
func TestBuildPrompt_WithBundleIncludesAllSections(t *testing.T) {
bundle := &ContextBundle{
PriorSuccessfulOutputs: []RetrievedArtifact{{RagID: "a1", Title: "A1", ContentPreview: "verified", SuccessScore: "accepted"}},
FailurePatterns: []RetrievedArtifact{{RagID: "p1", Title: "P1", ContentPreview: "partial result", SuccessScore: "partially_accepted"}},
ValidationSteps: []string{"verify the build"},
}
p := BuildPrompt("task X", bundle)
for _, marker := range []string{
"## Prior successful runs",
"## Patterns that produced PARTIAL results",
"## Validation checklist",
"## Task",
"task X",
} {
if !strings.Contains(p.User, marker) {
t.Errorf("user prompt missing marker %q in:\n%s", marker, p.User)
}
}
}
// ─── Validation gate ─────────────────────────────────────────────
func TestValidateResponse_FailsOnEmptyAndShort(t *testing.T) {
if got := ValidateResponse("", nil); got.Passed {
t.Error("empty should fail")
}
if got := ValidateResponse("too short", nil); got.Passed {
t.Error("too-short should fail")
}
}
func TestValidateResponse_FailsOnFiller(t *testing.T) {
resp := strings.Repeat("This is a real long response that meets the eighty character minimum for the gate. ", 2) +
" As an AI, I cannot help."
got := ValidateResponse(resp, nil)
if got.Passed {
t.Errorf("response with hedge phrase should fail, reasons=%v", got.Reasons)
}
}
func TestValidateResponse_PassesWhenChecklistOverlaps(t *testing.T) {
bundle := &ContextBundle{ValidationSteps: []string{"verify the build is green"}}
resp := "I followed the procedure and verified that the build is green and tests passed before merging the change."
got := ValidateResponse(resp, bundle)
if !got.Passed {
t.Errorf("expected pass, got reasons=%v", got.Reasons)
}
}
func TestValidateResponse_FailsWhenChecklistOrthogonal(t *testing.T) {
bundle := &ContextBundle{ValidationSteps: []string{"verify mango ripeness"}}
resp := "I followed completely unrelated steps about Quantum Tax compliance — I did not look at any fruit at all and that's the point."
got := ValidateResponse(resp, bundle)
if got.Passed {
t.Errorf("expected fail because no checklist token overlap, got pass")
}
}
// ─── End-to-end (dry-run, no LLM) ────────────────────────────────
func TestReplay_DryRun_LogsResult(t *testing.T) {
root := t.TempDir()
mustWriteRagFixture(t, root, []RagSample{
{ID: "p1", Title: "build verification", Content: "verify the build, check tests pass before merge",
Tags: []string{"scrum"}, SuccessScore: "accepted", SourceRunID: "r-1"},
})
res, err := Replay(context.Background(), ReplayRequest{
Task: "verify the build before merging",
DryRun: true,
}, root)
if err != nil {
t.Fatalf("Replay: %v", err)
}
if res.RecordedRunID == "" {
t.Error("expected recorded_run_id")
}
if !strings.HasPrefix(res.RecordedRunID, "replay:") {
t.Errorf("run_id shape: %s", res.RecordedRunID)
}
if res.ContextBundle == nil {
t.Fatal("expected retrieval to fire by default")
}
if len(res.ContextBundle.RetrievedPlaybooks) == 0 {
t.Errorf("expected at least one retrieved playbook")
}
logPath := filepath.Join(root, "data/_kb/replay_runs.jsonl")
body, err := os.ReadFile(logPath)
if err != nil {
t.Fatalf("read log: %v", err)
}
var row map[string]any
if err := json.Unmarshal([]byte(strings.TrimSpace(string(body))), &row); err != nil {
t.Fatalf("parse log row: %v", err)
}
if row["schema"] != "replay_run.v1" {
t.Errorf("schema field: %v", row["schema"])
}
}
func TestReplay_NoRetrievalSkipsCorpus(t *testing.T) {
root := t.TempDir()
mustWriteRagFixture(t, root, []RagSample{
{ID: "p1", Title: "would match", Content: "verify build assert", SuccessScore: "accepted"},
})
res, err := Replay(context.Background(), ReplayRequest{
Task: "verify build assert",
DryRun: true,
NoRetrieval: true,
}, root)
if err != nil {
t.Fatalf("Replay: %v", err)
}
if res.ContextBundle != nil {
t.Errorf("expected nil bundle in NoRetrieval mode")
}
if len(res.RetrievedArtifacts.RagIDs) != 0 {
t.Errorf("expected empty rag_ids, got %v", res.RetrievedArtifacts.RagIDs)
}
}
func TestReplay_EscalationFiresOnFailedValidation(t *testing.T) {
root := t.TempDir()
// Trick: the dry-run synthesizer copies validation_steps verbatim
// into its output. If a checklist step contains a hedge phrase, the
// synthesized response will contain it too — triggering the
// filler-pattern guard in ValidateResponse and forcing escalation.
mustWriteRagFixture(t, root, []RagSample{
{ID: "p1", Title: "demo step", Content: "verify the build then i cannot proceed without approval", SuccessScore: "accepted"},
})
res, err := Replay(context.Background(), ReplayRequest{
Task: "verify the build then proceed",
DryRun: true,
AllowEscalation: true,
}, root)
if err != nil {
t.Fatalf("Replay: %v", err)
}
if len(res.EscalationPath) < 2 {
t.Errorf("expected escalation, path=%v reasons=%v", res.EscalationPath, res.ValidationResult.Reasons)
}
if !strings.Contains(res.ModelResponse, "[ESCALATED]") {
t.Errorf("expected escalated marker in response, got: %q", res.ModelResponse)
}
}
func TestReplay_NoEscalationWhenValidationPasses(t *testing.T) {
root := t.TempDir()
mustWriteRagFixture(t, root, []RagSample{
{ID: "p1", Title: "build verification", Content: "verify the build, check tests pass before merge",
Tags: []string{"scrum"}, SuccessScore: "accepted", SourceRunID: "r-1"},
})
res, err := Replay(context.Background(), ReplayRequest{
Task: "verify the build before merging",
DryRun: true,
AllowEscalation: true,
}, root)
if err != nil {
t.Fatalf("Replay: %v", err)
}
if len(res.EscalationPath) != 1 {
t.Errorf("expected single-step path on validation pass, got %v", res.EscalationPath)
}
if !res.ValidationResult.Passed {
t.Errorf("expected pass, got reasons=%v", res.ValidationResult.Reasons)
}
}
// ─── Helpers ────────────────────────────────────────────────────
func mustWriteRagFixture(t *testing.T, root string, samples []RagSample) {
t.Helper()
path := filepath.Join(root, "exports/rag/playbooks.jsonl")
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatalf("mkdir: %v", err)
}
var buf strings.Builder
for _, s := range samples {
b, err := json.Marshal(s)
if err != nil {
t.Fatalf("marshal sample: %v", err)
}
buf.Write(b)
buf.WriteByte('\n')
}
if err := os.WriteFile(path, []byte(buf.String()), 0o644); err != nil {
t.Fatalf("write fixture: %v", err)
}
}
+215
View File
@@ -0,0 +1,215 @@
package replay
import (
"bufio"
"encoding/json"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
)
// tokenize lowercases and splits on non-[a-z0-9_] runs, keeping tokens
// of length ≥3. Matches replay.ts so retrieval scoring is consistent
// across runtimes.
func tokenize(text string) map[string]struct{} {
out := map[string]struct{}{}
if text == "" {
return out
}
lower := strings.ToLower(text)
var b strings.Builder
flush := func() {
if b.Len() >= 3 {
out[b.String()] = struct{}{}
}
b.Reset()
}
for _, r := range lower {
if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '_' {
b.WriteRune(r)
} else {
flush()
}
}
flush()
return out
}
// jaccard returns |A ∩ B| / |A ∪ B| over token sets.
func jaccard(a, b map[string]struct{}) float64 {
if len(a) == 0 || len(b) == 0 {
return 0
}
inter := 0
for t := range a {
if _, ok := b[t]; ok {
inter++
}
}
union := len(a) + len(b) - inter
if union == 0 {
return 0
}
return float64(inter) / float64(union)
}
// LoadRagCorpus reads `exports/rag/playbooks.jsonl` under root.
// Returns empty slice when the file is missing — callers fall back to
// a context-less prompt rather than failing.
func LoadRagCorpus(root string) ([]RagSample, error) {
path := filepath.Join(root, "exports", "rag", "playbooks.jsonl")
f, err := os.Open(path)
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, err
}
defer f.Close()
var corpus []RagSample
sc := bufio.NewScanner(f)
sc.Buffer(make([]byte, 0, 1<<16), 1<<24)
for sc.Scan() {
line := sc.Bytes()
if len(line) == 0 {
continue
}
var rec RagSample
if err := json.Unmarshal(line, &rec); err != nil {
continue // malformed line — skip, matches TS behavior
}
corpus = append(corpus, rec)
}
return corpus, sc.Err()
}
// retrieveRag returns up to topK playbooks with non-zero overlap.
// Sorted by score descending. Matches replay.ts.
func retrieveRag(corpus []RagSample, task string, topK int) []RetrievedArtifact {
taskTokens := tokenize(task)
type scored struct {
rec RagSample
score float64
}
all := make([]scored, 0, len(corpus))
for _, r := range corpus {
text := r.Title + " " + r.Content + " " + strings.Join(r.Tags, " ")
all = append(all, scored{rec: r, score: jaccard(taskTokens, tokenize(text))})
}
sort.SliceStable(all, func(i, j int) bool { return all[i].score > all[j].score })
out := make([]RetrievedArtifact, 0, topK)
for _, s := range all {
if len(out) >= topK {
break
}
if s.score <= 0 {
break
}
out = append(out, RetrievedArtifact{
RagID: s.rec.ID,
SourceRunID: s.rec.SourceRunID,
Title: s.rec.Title,
ContentPreview: trim(s.rec.Content, 240),
SuccessScore: s.rec.SuccessScore,
Tags: tagsOrEmpty(s.rec.Tags),
Score: s.score,
})
}
return out
}
var validationLineRE = regexp.MustCompile(`(?i)^[-*]\s*(verify|check|assert|confirm|ensure)\b|^\s*(verify|check|assert|confirm|ensure)\s`)
// extractValidationSteps pulls verify/check/assert/confirm/ensure
// lines from accepted samples. Used as a soft-anchor in the
// validation gate (response should touch at least one of these
// tokens) and surfaced into the prompt.
func extractValidationSteps(samples []RetrievedArtifact, corpus []RagSample) []string {
ids := map[string]struct{}{}
for _, s := range samples {
ids[s.RagID] = struct{}{}
}
var steps []string
for _, r := range corpus {
if _, ok := ids[r.ID]; !ok {
continue
}
for _, line := range strings.Split(r.Content, "\n") {
t := strings.TrimSpace(line)
if validationLineRE.MatchString(t) {
steps = append(steps, trim(t, 200))
if len(steps) >= 6 {
return steps
}
}
}
}
return steps
}
// BuildContextBundle assembles a ContextBundle from a corpus + task.
// Top 8 retrieved → split by success_score → at most 3 accepted, 2
// warnings → extract validation steps → estimate token cost.
func BuildContextBundle(corpus []RagSample, task string) *ContextBundle {
top := retrieveRag(corpus, task, 8)
accepted := filterByScore(top, "accepted", 3)
warnings := filterByScore(top, "partially_accepted", 2)
steps := extractValidationSteps(accepted, corpus)
totalChars := 0
for _, r := range accepted {
totalChars += len(r.ContentPreview) + len(r.Title)
}
for _, r := range warnings {
totalChars += len(r.ContentPreview) + len(r.Title)
}
for _, s := range steps {
totalChars += len(s)
}
tokenEstimate := (totalChars + 3) / 4 // ceil(chars/4)
return &ContextBundle{
RetrievedPlaybooks: top,
PriorSuccessfulOutputs: accepted,
FailurePatterns: warnings,
ValidationSteps: stepsOrEmpty(steps),
BundleTokenEstimate: tokenEstimate,
}
}
func filterByScore(arts []RetrievedArtifact, score string, max int) []RetrievedArtifact {
out := make([]RetrievedArtifact, 0, max)
for _, a := range arts {
if a.SuccessScore == score {
out = append(out, a)
if len(out) >= max {
break
}
}
}
return out
}
func tagsOrEmpty(t []string) []string {
if t == nil {
return []string{}
}
return t
}
func stepsOrEmpty(s []string) []string {
if s == nil {
return []string{}
}
return s
}
func trim(s string, n int) string {
if len(s) <= n {
return s
}
return s[:n]
}
+98
View File
@@ -0,0 +1,98 @@
// Package replay ports scripts/distillation/replay.ts to Go.
//
// Replay takes a task → retrieves matching playbooks/RAG records →
// builds a context bundle → calls a LOCAL model via the gateway's
// /v1/chat → validates → escalates to a stronger model if needed →
// logs the run as new evidence in `data/_kb/replay_runs.jsonl`.
//
// Spec invariants (carry over from replay.ts):
// - never bypass retrieval (unless caller passes NoRetrieval)
// - never discard provenance
// - never allow free-form hallucinated output (validation gate)
// - log every run as new evidence
//
// This is NOT training — it's runtime behavior shaping via retrieval.
package replay
// ReplayRequest mirrors the TS interface. NoRetrieval skips the
// context bundle entirely (baseline mode for A/B tests). DryRun returns
// a deterministic synthetic response without calling the gateway —
// used by tests to exercise retrieval/validation without an LLM.
type ReplayRequest struct {
Task string
LocalOnly bool
AllowEscalation bool
NoRetrieval bool
DryRun bool
GatewayURL string // overrides $LH_GATEWAY_URL
LocalModel string // overrides default
EscalationModel string // overrides default
}
// RagSample is one record in exports/rag/playbooks.jsonl.
type RagSample struct {
ID string `json:"id"`
Title string `json:"title"`
Content string `json:"content"`
Tags []string `json:"tags"`
SourceRunID string `json:"source_run_id"`
SuccessScore string `json:"success_score"`
SourceCategory string `json:"source_category"`
}
// RetrievedArtifact is one playbook surfaced into a ContextBundle.
type RetrievedArtifact struct {
RagID string `json:"rag_id"`
SourceRunID string `json:"source_run_id"`
Title string `json:"title"`
ContentPreview string `json:"content_preview"` // first 240 chars
SuccessScore string `json:"success_score"`
Tags []string `json:"tags"`
Score float64 `json:"score"`
}
// ContextBundle is what the prompt builder consumes. Empty bundles
// (no retrieved playbooks) still pass through — buildPrompt downgrades
// to a no-context prompt when both accepted and warnings are empty.
type ContextBundle struct {
RetrievedPlaybooks []RetrievedArtifact `json:"retrieved_playbooks"`
PriorSuccessfulOutputs []RetrievedArtifact `json:"prior_successful_outputs"`
FailurePatterns []RetrievedArtifact `json:"failure_patterns"`
ValidationSteps []string `json:"validation_steps"`
BundleTokenEstimate int `json:"bundle_token_estimate"`
}
// ValidationResult is the deterministic gate's verdict. Reasons is
// always non-nil so JSON consumers can iterate without a nil check.
type ValidationResult struct {
Passed bool `json:"passed"`
Reasons []string `json:"reasons"`
}
// ReplayResult is what Replay returns. Mirrors the TS type one-to-one
// so JSONL emitted by either runtime parses identically.
type ReplayResult struct {
InputTask string `json:"input_task"`
TaskHash string `json:"task_hash"`
RetrievedArtifacts RetrievedIDs `json:"retrieved_artifacts"`
ContextBundle *ContextBundle `json:"context_bundle"`
ModelResponse string `json:"model_response"`
ModelUsed string `json:"model_used"`
EscalationPath []string `json:"escalation_path"`
ValidationResult ValidationResult `json:"validation_result"`
RecordedRunID string `json:"recorded_run_id"`
RecordedAt string `json:"recorded_at"`
DurationMs int64 `json:"duration_ms"`
}
// RetrievedIDs is the {rag_ids} envelope the TS shape uses.
type RetrievedIDs struct {
RagIDs []string `json:"rag_ids"`
}
// Defaults match replay.ts. Override via env or ReplayRequest fields.
const (
DefaultLocalModel = "qwen3.5:latest"
DefaultEscalationModel = "deepseek-v3.1:671b"
DefaultGatewayURL = "http://localhost:3110"
)
+66
View File
@@ -0,0 +1,66 @@
package replay
import (
"fmt"
"regexp"
"strings"
)
// fillerPatterns are the hedge phrases the spec rejects. Compiled once
// per package — the gate runs on every replay call.
var fillerPatterns = []*regexp.Regexp{
regexp.MustCompile(`(?i)as an ai`),
regexp.MustCompile(`(?i)i cannot`),
regexp.MustCompile(`(?i)i'?m sorry, but`),
regexp.MustCompile(`(?i)i don'?t have access`),
regexp.MustCompile(`(?i)i am unable to`),
}
// ValidateResponse runs the deterministic gate on a model response.
// Empty / too-short / hedge-bearing / context-disconnected responses
// fail. Matches replay.ts:validateResponse one-to-one.
func ValidateResponse(response string, bundle *ContextBundle) ValidationResult {
trimmed := strings.TrimSpace(response)
var reasons []string
if len(trimmed) == 0 {
return ValidationResult{Passed: false, Reasons: []string{"empty response"}}
}
if len(trimmed) < 80 {
reasons = append(reasons, fmt.Sprintf("response too short (%d chars; min 80)", len(trimmed)))
}
for _, re := range fillerPatterns {
if re.MatchString(trimmed) {
reasons = append(reasons, fmt.Sprintf("filler/hedge phrase detected: %s", re.String()))
}
}
// Soft anchor: if a validation checklist was supplied, the response
// should share at least one token with it (≥3 chars per tokenize()).
if bundle != nil && len(bundle.ValidationSteps) > 0 {
checklistTokens := map[string]struct{}{}
for _, s := range bundle.ValidationSteps {
for t := range tokenize(s) {
checklistTokens[t] = struct{}{}
}
}
respTokens := tokenize(trimmed)
overlap := 0
for t := range checklistTokens {
if _, ok := respTokens[t]; ok {
overlap++
}
}
if len(checklistTokens) > 0 && overlap == 0 {
reasons = append(reasons, "response shares no tokens with validation checklist (may not have followed prior patterns)")
}
}
return ValidationResult{Passed: len(reasons) == 0, Reasons: reasonsOrEmpty(reasons)}
}
func reasonsOrEmpty(r []string) []string {
if r == nil {
return []string{}
}
return r
}