fix(ci): the Windows shell listing joins paths the way the OS it is computed for does, the save allocation check is skipped under the race detector that defeats regexp's pool, and the context-retrieval benchmark whose premise archived folds ended is removed with the surface helper only it used

This commit is contained in:
yhh committed 2026-11-07 00:39:03 -08:00
1 parent 48cad29d31
commit 0fac934b3f
27 files changed
+33 -4960

No files matched your search

-180
View File
@@ -1,180 +0,0 @@
# Context retrieval bench
Two questions about long-horizon memory, and the harness that makes their
answers trustworthy.
-mode=preflight build all 18 tasks under every arm and check the contract
-mode=snippet does a rank-1 hit hand over the whole answer?
-mode=adversarial send a model after the answer through every host surface
-mode=run-search Experiment R: is searchable canonical a real capability?
-mode=run-boundary Experiment I, paired: the 6 efficiency tasks across their boundary
-mode=run-index Experiment I, full: the 6 efficiency tasks x 4 budgets
-mode=calibrate find each index task's PlantAfterGen for its cue tier
`-dry` drives any run mode with a scripted provider, for nothing. Real runs need
`DEEPSEEK_API_KEY`.
## Status
**Searchable canonical recall: proven.** Six of six recovered with search on,
three of six with it off, and the successes without it came from enumerating
addresses by hand at roughly nine times the page-in cost (139 vs 1292
tokens/task). Measured after three separate leaks were closed, so the numbers
are from a clean environment.
**Fold index marginal utility: direction reproduced, size still conditional.** On the v2
substrate the direction holds in six of six tasks and in the pooled paired batch: with the
cue visible the index addresses the target, so the same task is searched less and pages in
less (0.67 searches and 70 recall tokens per task, against 1.50 and 120 with the cue
absent).
A size is visible but is not yet quotable. On the four tasks whose absent-cue arm stayed
out of a loop, the index cost 64,952 prompt tokens against 115,551 without, 44% less
(`i106-budgetseal` -35,147, `i70-ingestfloor` -8,222, `i37-dispatchlag` -7,599, and
`i112-lagquota` +369, the prefix rent a task that never searches pays). That is a
**post-hoc subset**: `i118-fencelag` and `i47-tenantlease` are excluded because their
absent-cue arm looped in this batch (five and three retrieval rounds, three escapes each;
together 80% of the absent-cue total), and the qualification above does not screen for
stopping behaviour — holding on both boundary arms across three passes shows a stable cue
boundary, not a stopping arm. Quoting 44% as the index's effect would mean choosing the
sample after seeing the result; a batch screened on stopping stability is what would make
a size quotable, and one arm also reverses either way (`i70-ingestfloor` default, +11,135
prompt tokens over three escapes).
Batch facts, because the counts differ: 36 runs were executed (12 paired, 24 index), and
the token totals here cover the 24-run index batch — the paired batch ran before the
per-run usage line existed.
**Things that turned out not to be problems**, each after being measured rather
than argued about:
| Suspected | Measured |
| --- | --- |
| Model writes poor queries (2.17 searches/task) | 22/23 found the target on query 1; the extra searches are one round's parallel fan-out |
| Model reaches for the workspace before memory (7/11) | Linear event order misread; by model round it is MemoryFirst 4/6 |
| Snippets too short for multi-value answers | 240 runes covers 12/12 tasks at 100%; records are 30-179 runes |
| A general stopping failure | 37/44 runs issued no search after the answer was in hand; all 7 that did belong to 2 tasks |
## Measurement distinctions this bench had to learn
Each of these reversed a conclusion once. They are enforced in the schema now,
not left to whoever reads the numbers next.
- **TargetHit is not EvidenceSufficient.** A rank-1 hit can return a window too
short to answer with. `FirstHitCoverage` records how much of the scored answer
the first hit actually handed over.
- **Linear event order is not model-round order.** Two calls in one round are a
parallel fan-out, not a preference. Routing compares rounds.
- **An isolated workdir is not an isolated host.** The sandbox mounts the host
read-only by design. Three leaks were found and closed: the corpus source, the
fixture transcript, and its event log sidecar.
- **A count is not its evidence.** Query text, snippet contents and trajectories
are all persisted now, because three analyses in a row needed a paid re-run to
ask a question of data already collected.
## How a run stays honest
The corpus holds templates, never answers: every scored literal is a `{{var}}`
instantiated per run, so grepping this directory reveals the question and not
the answer. `TestNoAnswerLiteralExistsInTheRepository` asserts it with
`git grep`. Preflight rebuilds every arm and checks the answer is unreadable in
the real `provider.Request`, the probe query ranks the target within five, and
an index task's cue sits at exactly the scales its tier names. The fixture is
removed from disk once the agent holds it in memory, and the directory is
scanned for answer literals before the first provider request and again after
the turn. Any answer reaching the model through a tool that is not `recall`
marks the run contaminated and takes it out of every statistic.
## Index corpus v2
The efficiency substrate is six tasks that hold on both arms: `i37-dispatchlag`
and `i118-fencelag` (quarter), `i106-budgetseal` and `i70-ingestfloor` (half),
`i47-tenantlease` and `i112-lagquota` (default).
They came out of five batches of candidates, each screened the same way: one
`-mode=run-index` pass over the batch, then two more only for the candidates that
were clean on both boundary arms, and only the candidates that held on both arms
in all three passes stayed. A candidate that does not hold is dropped, not
promoted. The numbers are candidates, per tier:
| batch | quarter | half | default |
| --- | --- | --- | --- |
| first pool, `i31`-`i54` | 1 of 8 | 0 of 8 | 2 of 8 |
| second pool, `i61`-`i84` | 1 of 4 screened | 4 of 8 | 0 of 8 |
| third pool, `i85`-`i108` | 2 of 8 | 8 of 16 | none authored |
| replacement pool, `i109`-`i116` | - | - | 4 of 8 |
| replacement pool, `i92`, `i117`-`i124` | 3 of 9 | - | - |
That table counts candidates that held on both arms in all three passes, which is
not the size of the substrate. 89 candidates were authored, 77 were screened (the
second pool's quarter tier stopped at 4 of 8 and its default tier was never run),
and 25 held: 7 quarter, 12 half, 6 default. The substrate keeps two per tier, so
the other 19 survivors are spares rather than corpus members. Candidates from the
earlier round are not counted here.
| task | tier | cue present | cue absent |
| --- | --- | ---: | ---: |
| `i37-dispatchlag` | quarter | 3/3 | 3/3 |
| `i118-fencelag` | quarter | 3/3 | 3/3 |
| `i106-budgetseal` | half | 3/3 | 3/3 |
| `i70-ingestfloor` | half | 3/3 | 3/3 |
| `i47-tenantlease` | default | 3/3 | 3/3 |
| `i112-lagquota` | default | 3/3 | 3/3 |
Across their 36 judged cells: `SnippetStop` 23, `SnippetThenRead` 10 and
`DefensiveRead` 3 - every cell clean, nothing post-sufficient and nothing
`NeverSufficient`.
Four of the first six were flagged in review for sharing answer values, probe
queries and prompt wording within a tier. Separating them and re-screening those
four changed two of them: `i37` and `i47` held, `i62` and `i51` did not, so they
were replaced - `i90-recoverylag` was tried first and did not hold either, and
`i118-fencelag` and `i112-lagquota` are what did. The separation was not
cosmetic: the shared wording had been carrying two of the six.
Nothing in the task shape separates the survivors from the rest: within the half
tier, plain-number answers survived 0 of 8 in the first pool and 4 of 8 in the
second, and codename answers survived 8 of 16 in the third. An earlier claim here
that the answer kind decides whether a candidate holds does not survive this
table and is withdrawn. The dirty class was `PostSufficientRetrieval` in most
failures - the model re-checking a value it already had - but which tasks hit it
was not predictable from their shape at this sample.
`i01`-`i06` are the frozen study's six tasks, kept as `roleStopping`. Measured
once each with `-mode=run-index -task <id>`, per task and per arm:
| task | tier | cue present | cue absent |
| --- | --- | --- | --- |
| `i01-transport-fallback` | quarter | SnippetStop | DefensiveRead |
| `i02-scheduler-handoff` | quarter | PostSufficientRetrieval | PostSufficientRetrieval |
| `i03-coalescing` | half | PostSufficientRetrieval | PostSufficientRunaway |
| `i04-recovery-fence` | half | DefensiveRead | PostSufficientRunaway |
| `i05-cache-probe` | default | SnippetThenRead | PostSufficientRetrieval |
| `i06-dispatch-handoff` | default | SnippetStop | PostSufficientRetrieval |
Five of the six show post-sufficient behaviour in at least one arm (`i02`-`i06`),
and `i01` is clean on both. The cue-present arm is clean for four of them
(`i01`, `i04`, `i05`, `i06`), which is why they stay in the corpus as the
stopping side rather than being retitled. All six are excluded from `run-index`
and `run-boundary` batches and stay measurable one at a time with `-task <id>`. A
task added to the corpus declares a role, so it cannot arrive without one.
A task qualifies only if it is clean on both of its boundary arms - the
cue-present run and the cue-absent run - in every run of a screening batch. Clean
means a `stopping_class` other than `PostSufficientRetrieval` or
`PostSufficientRunaway`: `SnippetStop`, `SnippetThenRead` and `DefensiveRead`
all qualify, and a defensive read is deliberately not counted as waste. The
class comes from the three counts in that report — `searches_after_sufficient`,
`reads_after_sufficient` and `escapes_after_sufficient` — together with how
sufficiency was reached and how many rounds followed it. The remaining
requirements still apply — successful tool call, answer in the output rather
than the cue, rank 1, `FirstHitCoverage` complete, one or two simple values,
nothing in the workspace.
`-mode=calibrate` is not a screening tool: it builds and reads back the
fixtures, and reads nothing else, so its cue-visibility profile is the same for
every task.
Then repeats rather than levels: cue-present against cue-absent on each task's
own boundary, three runs per cell. What is unknown is the variance, and four
budgets sampled once each cannot show it.
@@ -1,86 +0,0 @@
package main
import (
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
)
// runBlockedBatch runs one dry index batch where every arm's runOne fails
// before any model turn: the task's own directory is a regular file, so the
// fixture cannot be built. In production the same early return happens when the
// fixture build, the session load, or the answer-on-disk guard fails.
func runBlockedBatch(t *testing.T) string {
t.Helper()
tk := indexTasks()[0]
root := t.TempDir()
if err := os.WriteFile(filepath.Join(root, tk.ID), []byte("not a directory\n"), 0o644); err != nil {
t.Fatal(err)
}
if code := runExperiment("run-index", experimentIndex, root, true, []contextTask{tk}); code != 0 {
t.Fatalf("runExperiment exit = %d, want 0 (every run failed, but the batch finished)", code)
}
return root
}
func readResultLines(t *testing.T, root string) ([]contextMetrics, []string) {
t.Helper()
raw, err := os.ReadFile(filepath.Join(root, "results.jsonl"))
if err != nil {
t.Fatal(err)
}
var runs []contextMetrics
var lines []string
for text := range strings.SplitSeq(strings.TrimSpace(string(raw)), "\n") {
var m contextMetrics
if err := json.Unmarshal([]byte(text), &m); err != nil {
t.Fatalf("results.jsonl: %v", err)
}
runs = append(runs, m)
lines = append(lines, text)
}
return runs, lines
}
// A run that failed before the model turn produced no measurement. Recording it
// as a zero-valued contextMetrics puts it in the funnel's Scored denominator,
// in "Where it broke", and in the dose curve as a real zero.
func TestBatchDoesNotRecordAFailedRunAsAResult(t *testing.T) {
root := runBlockedBatch(t)
runs, lines := readResultLines(t, root)
if len(runs) == 0 {
t.Fatal("no runs recorded at all")
}
for i, m := range runs {
if m.Task == "" || m.Arm == "" || m.FailureStage == "" {
t.Fatalf("results.jsonl line %d records a run that never ran as a result "+
"(task=%q arm=%q failure_stage=%q):\n %s", i+1, m.Task, m.Arm, m.FailureStage, lines[i])
}
}
}
// The manifest is the artefact that says which runs counted, so a failed run
// must not be added to CompletedRuns or to the task id list.
func TestBatchManifestDoesNotReportFailedRunsAsCompleted(t *testing.T) {
root := runBlockedBatch(t)
var manifest batchManifest
data, err := os.ReadFile(filepath.Join(root, "batch.json"))
if err != nil {
t.Fatal(err)
}
if err := json.Unmarshal(data, &manifest); err != nil {
t.Fatal(err)
}
for _, id := range manifest.TaskIDs {
if id == "" {
t.Fatalf("manifest lists an empty task id: task_ids=%q (completed=%d planned=%d)",
manifest.TaskIDs, manifest.CompletedRuns, manifest.PlannedRuns)
}
}
if manifest.CompletedRuns == manifest.PlannedRuns {
t.Fatalf("manifest reports every planned run completed although none produced evidence: "+
"completed=%d planned=%d task_ids=%q", manifest.CompletedRuns, manifest.PlannedRuns, manifest.TaskIDs)
}
}
-155
View File
@@ -1,155 +0,0 @@
package main
import (
"crypto/sha256"
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"sort"
"strings"
"time"
)
// batchInfo is what the mode itself knows before any run happens. Everything
// else in the manifest is derived from the runs that were written, so a figure
// in it cannot drift from the artifact beside it.
type batchInfo struct {
Mode string
Planned int
Started time.Time
Dry bool
// Repo is the checkout the batch ran from, captured before the harness
// isolates its workspace: git cannot describe a directory that is gone.
Repo string
}
// batchManifest is written beside results.jsonl so a later reader does not have
// to reconstruct what the batch was from a terminal scrollback.
type batchManifest struct {
SchemaVersion int `json:"schema_version"`
Mode string `json:"mode"`
StartedAt string `json:"started_at"`
FinishedAt string `json:"finished_at"`
GitCommit string `json:"git_commit"`
GitDirty *bool `json:"git_dirty,omitempty"`
Model string `json:"model"`
Window int `json:"window"`
Generations int `json:"generations"`
TaskIDs []string `json:"task_ids"`
Arms []string `json:"arms"`
PlannedRuns int `json:"planned_runs"`
CompletedRuns int `json:"completed_runs"`
// FailedRuns counts the planned runs that ended before a transcript existed.
// They are rows in results.jsonl, never part of CompletedRuns, and the field
// is absent when there were none so an intact batch is unchanged.
FailedRuns int `json:"failed_runs,omitempty"`
// Usage totals are absent when nothing reported any: an unmeasured cost must
// not read as a measured zero. UsageReportedRuns stays, because zero there
// is a fact about the batch.
UsageReportedRuns int `json:"usage_reported_runs"`
UsageReportedRequests int `json:"usage_reported_requests,omitempty"`
PromptTokens int `json:"prompt_tokens,omitempty"`
CompletionTokens int `json:"completion_tokens,omitempty"`
CacheHitTokens int `json:"cache_hit_tokens,omitempty"`
CacheMissTokens int `json:"cache_miss_tokens,omitempty"`
UsageEstimated bool `json:"usage_estimated,omitempty"`
ResultsSHA256 string `json:"results_sha256"`
TrajectoriesSHA256 string `json:"trajectories_sha256"`
}
// gitCommitAndDirty reports the checkout the batch ran from. Unknown is written
// rather than omitted, so an absent field is never mistaken for a clean tree.
func gitCommitAndDirty(repo string) (commit string, dirty *bool) {
commit = "unknown"
if repo == "" {
return commit, nil
}
in := func(args ...string) ([]byte, error) {
cmd := exec.Command("git", args...)
cmd.Dir = repo
return cmd.Output()
}
if out, err := in("rev-parse", "--short", "HEAD"); err == nil {
commit = strings.TrimSpace(string(out))
}
if out, err := in("status", "--porcelain"); err == nil {
state := strings.TrimSpace(string(out)) != ""
dirty = &state
}
return commit, dirty
}
func fileSHA256(path string) (string, error) {
data, err := os.ReadFile(path)
if err != nil {
return "", err
}
return fmt.Sprintf("%x", sha256.Sum256(data)), nil
}
func sortedKeys(set map[string]bool) []string {
out := make([]string, 0, len(set))
for k := range set {
out = append(out, k)
}
sort.Strings(out)
return out
}
// writeBatchManifest records the batch beside the runs it describes. It runs
// last, so its digests are of what is already on disk.
func writeBatchManifest(root string, info batchInfo, all []contextMetrics) error {
ids, arms := map[string]bool{}, map[string]bool{}
var prompt, completion, cacheHit, cacheMiss, requests, reported, failed int
estimated := false
for _, m := range all {
// A failed run has no task, arm or evidence to contribute: it is counted
// as a failure below and is not part of what the batch measured.
if m.Failed {
failed++
continue
}
ids[m.Task] = true
arms[m.Arm] = true
if m.UsageReportedRequests > 0 {
reported++
requests += m.UsageReportedRequests
prompt += m.PromptTokens
completion += m.CompletionTokens
cacheHit += m.CacheHitTokens
cacheMiss += m.CacheMissTokens
estimated = estimated || m.UsageEstimated
}
}
commit, dirty := gitCommitAndDirty(info.Repo)
model := runModel
if info.Dry {
model = "scripted"
}
manifest := batchManifest{
SchemaVersion: 1, Mode: info.Mode,
StartedAt: info.Started.UTC().Format(time.RFC3339),
FinishedAt: time.Now().UTC().Format(time.RFC3339),
GitCommit: commit, GitDirty: dirty,
Model: model, Window: fixtureWindow, Generations: fixtureGenerations,
TaskIDs: sortedKeys(ids), Arms: sortedKeys(arms),
PlannedRuns: info.Planned, CompletedRuns: len(all) - failed, FailedRuns: failed,
UsageReportedRuns: reported, UsageReportedRequests: requests,
PromptTokens: prompt, CompletionTokens: completion,
CacheHitTokens: cacheHit, CacheMissTokens: cacheMiss, UsageEstimated: estimated,
}
var err error
if manifest.ResultsSHA256, err = fileSHA256(filepath.Join(root, "results.jsonl")); err != nil {
return err
}
if manifest.TrajectoriesSHA256, err = fileSHA256(filepath.Join(root, "trajectories.jsonl")); err != nil {
return err
}
data, err := json.MarshalIndent(manifest, "", " ")
if err != nil {
return err
}
return os.WriteFile(filepath.Join(root, "batch.json"), append(data, '\n'), 0o644)
}
@@ -1,91 +0,0 @@
package main
import (
"crypto/sha256"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
func seedArtifacts(t *testing.T, root string) {
t.Helper()
for _, name := range []string{"results.jsonl", "trajectories.jsonl"} {
if err := os.WriteFile(filepath.Join(root, name), []byte("{}\n"), 0o644); err != nil {
t.Fatal(err)
}
}
}
func readManifest(t *testing.T, root string) (batchManifest, string) {
t.Helper()
data, err := os.ReadFile(filepath.Join(root, "batch.json"))
if err != nil {
t.Fatal(err)
}
var got batchManifest
if err := json.Unmarshal(data, &got); err != nil {
t.Fatal(err)
}
return got, string(data)
}
func TestBatchManifestRecordsWhatTheBatchWas(t *testing.T) {
root := t.TempDir()
seedArtifacts(t, root)
all := []contextMetrics{
{Task: "b", Arm: "index-off"},
{Task: "a", Arm: "index-off"},
{Task: "a", Arm: "index-half", UsageReportedRequests: 2, PromptTokens: 100,
CompletionTokens: 10, CacheHitTokens: 60, CacheMissTokens: 40},
}
if err := writeBatchManifest(root, batchInfo{Mode: "run-index", Planned: 4, Started: time.Now().Add(-time.Minute)}, all); err != nil {
t.Fatal(err)
}
got, _ := readManifest(t, root)
if got.SchemaVersion != 1 || got.Mode != "run-index" || got.PlannedRuns != 4 || got.CompletedRuns != 3 {
t.Fatalf("manifest header = %+v", got)
}
if strings.Join(got.TaskIDs, ",") != "a,b" || strings.Join(got.Arms, ",") != "index-half,index-off" {
t.Fatalf("ids/arms = %v / %v, want sorted and unique", got.TaskIDs, got.Arms)
}
if got.UsageReportedRuns != 1 || got.UsageReportedRequests != 2 || got.PromptTokens != 100 ||
got.CompletionTokens != 10 || got.CacheHitTokens != 60 || got.CacheMissTokens != 40 {
t.Fatalf("usage totals = %+v", got)
}
// No repo means git could not describe the checkout: unknown commit, and no
// dirty flag at all, because "clean" is a claim this batch cannot make.
if got.GitCommit != "unknown" || got.GitDirty != nil {
t.Fatalf("git state = %q / %v, want unknown and no dirty flag", got.GitCommit, got.GitDirty)
}
want := fmt.Sprintf("%x", sha256.Sum256([]byte("{}\n")))
if got.ResultsSHA256 != want || got.TrajectoriesSHA256 != want {
t.Fatalf("digests = %q / %q, want %q", got.ResultsSHA256, got.TrajectoriesSHA256, want)
}
}
// A dry batch pays nothing, so its cost fields must be absent rather than zero:
// an unmeasured spend must not read as a measured one.
func TestBatchManifestLeavesUnreportedUsageAbsent(t *testing.T) {
root := t.TempDir()
seedArtifacts(t, root)
all := []contextMetrics{{Task: "a", Arm: "index-off"}, {Task: "a", Arm: "index-half"}}
if err := writeBatchManifest(root, batchInfo{Mode: "run-index", Planned: 2, Started: time.Now(), Dry: true}, all); err != nil {
t.Fatal(err)
}
got, raw := readManifest(t, root)
if got.UsageReportedRuns != 0 {
t.Fatalf("usage_reported_runs = %d, want 0 reported", got.UsageReportedRuns)
}
if got.Model != "scripted" {
t.Fatalf("dry batch model = %q, want scripted", got.Model)
}
for _, absent := range []string{"usage_reported_requests", "prompt_tokens", "completion_tokens", "cache_hit_tokens"} {
if strings.Contains(raw, absent) {
t.Fatalf("a dry batch wrote %q:\n%s", absent, raw)
}
}
}
@@ -1,69 +0,0 @@
package main
import (
"strings"
"testing"
)
// boundaryRuns builds the input reportBoundaries reads: one scored run for an
// index task's cue side, and one failed run for another index task's cue side.
// The runs are constructed because the report is a pure function of the slice;
// the batch path that produces them is covered by the failed-batch tests.
func boundaryRuns(t *testing.T) (all []contextMetrics, scoredTask, failedTask string) {
t.Helper()
index := indexTasks()
if len(index) < 2 {
t.Fatalf("need at least two index tasks, have %d", len(index))
}
scored, failed := index[0], index[1]
cue, noCue := boundaryPair(scored.CueTier)
if cue == "" || noCue == "" {
t.Fatalf("task %s has an empty boundary pair (%q, %q)", scored.ID, cue, noCue)
}
failedCue, _ := boundaryPair(failed.CueTier)
if failedCue == "" {
t.Fatalf("task %s has no cue side", failed.ID)
}
all = []contextMetrics{
{Task: scored.ID, Arm: "index-" + cue, AnswerRecovered: true, SearchCalls: 1, RecallReturnedTokens: 318},
{Task: scored.ID, Arm: "index-" + noCue, AnswerRecovered: false},
{Task: failed.ID, Arm: "index-" + failedCue, Failed: true, FailureStage: stageNoEvidence},
}
return all, scored.ID, failed.ID
}
// TestBoundaryReportExcludesFailedRuns: a run that never produced a transcript
// must not appear in the cue-boundary table, which was the last reporting exit
// that still folded it in as a zero measurement.
func TestBoundaryReportExcludesFailedRuns(t *testing.T) {
all, scoredTask, failedTask := boundaryRuns(t)
out := captureStdout(t, func() { reportBoundaries(all) })
if strings.Contains(out, failedTask) {
t.Fatalf("the boundary table printed the failed task %q as a measurement:\n%s", failedTask, out)
}
if !strings.Contains(out, scoredTask) {
t.Fatalf("the boundary table dropped the scored task %q, so the exclusion is not specific to failures:\n%s", scoredTask, out)
}
if !strings.Contains(out, "cue-present") || !strings.Contains(out, "cue-absent") {
t.Fatalf("the pooled comparison disappeared:\n%s", out)
}
}
// TestBoundaryReportSaysNothingToCompareWhenEveryRunFailed: with no scored run
// the report must say so, rather than printing a comparison of zeros.
func TestBoundaryReportSaysNothingToCompareWhenEveryRunFailed(t *testing.T) {
all, _, _ := boundaryRuns(t)
failedOnly := make([]contextMetrics, 0, len(all))
for _, m := range all {
m.Failed = true
m.FailureStage = stageNoEvidence
failedOnly = append(failedOnly, m)
}
out := captureStdout(t, func() { reportBoundaries(failedOnly) })
if !strings.Contains(out, "scored=0") || !strings.Contains(out, "failed=") {
t.Fatalf("an all-failed batch did not report scored=0 with a failed count:\n%s", out)
}
if strings.Contains(out, "cue-present") || strings.Contains(out, "cue-absent") {
t.Fatalf("an all-failed batch still printed a pooled comparison of zeros:\n%s", out)
}
}
-170
View File
@@ -1,170 +0,0 @@
// capture.go — what the agent actually hands a provider.
package main
import (
"context"
"encoding/json"
"fmt"
"reasonix/internal/state/sessionstore"
"strings"
"reasonix/internal/assembly/boot"
"reasonix/internal/contract/ablation"
"reasonix/internal/contract/event"
"reasonix/internal/contract/provider"
"reasonix/internal/contract/tool"
"reasonix/internal/runtime/agent"
_ "reasonix/internal/tools/builtin"
)
// A projection is what the agent believes; a provider.Request is what the model
// is told. Rendering, sanitization and tool-schema construction sit between
// them, so the arm's contract is checked on the request.
type captureProvider struct {
requests []provider.Request
}
func (p *captureProvider) Name() string { return "contextbench-capture" }
// Stream records the request and answers with a clean terminal response. An
// empty answer would buy an empty-final retry and a second request, which is
// not the one round this is here to inspect.
func (p *captureProvider) Stream(_ context.Context, req provider.Request) (<-chan provider.Chunk, error) {
p.requests = append(p.requests, cloneRequest(req))
ch := make(chan provider.Chunk, 2)
ch <- provider.Chunk{Type: provider.ChunkText, Text: "contextbench preflight complete"}
ch <- provider.Chunk{Type: provider.ChunkDone}
close(ch)
return ch, nil
}
func cloneRequest(req provider.Request) provider.Request {
out := req
out.Messages = append([]provider.Message(nil), req.Messages...)
out.Tools = append([]provider.ToolSchema(nil), req.Tools...)
return out
}
// captureRequest rebuilds the session the way a resume does — transcript from
// disk, projection from its sidecar, the shipped tool surface at this arm —
// and returns the request the first round hands the provider.
func captureRequest(f builtFixture, arm ablation.Set, prompt string) (provider.Request, error) {
sess, err := sessionstore.LoadSession(f.Path)
if err != nil {
return provider.Request{}, fmt.Errorf("load session: %w", err)
}
reg := tool.NewRegistry()
for _, t := range tool.Builtins() {
reg.Add(t)
}
boot.ApplyUnifiedProviderToolSurface(reg, false, arm)
cap := &captureProvider{}
a := agent.New(cap, reg, sess, agent.Options{
ContextWindow: fixtureWindow, CompactRatio: 0.5, SessionPath: f.Path, Ablation: arm,
}, event.Discard)
a.LoadProjectionSidecar(f.Path)
if err := a.Run(context.Background(), prompt); err != nil {
return provider.Request{}, fmt.Errorf("run: %w", err)
}
// The turn's own request is the one carrying tools. A fold on the way in
// asks the same provider for a digest, and that request has none — taking
// the first request blindly would inspect the summarizer's prompt.
for _, req := range cap.requests {
if len(req.Tools) > 0 {
return req, nil
}
}
return provider.Request{}, fmt.Errorf("no request carried tools (%d captured)", len(cap.requests))
}
// surfaceField is one place a model could read a string, named so a leak
// report says where rather than only that.
type surfaceField struct {
Path string
Text string
}
// providerSurface enumerates every provider-visible string in a request.
// Enumerated rather than marshalled whole: a Message also carries local-only
// fields, and scanning those would fail on bytes no model ever sees.
func providerSurface(req provider.Request) []surfaceField {
var out []surfaceField
add := func(path, text string) {
if strings.TrimSpace(text) != "" {
out = append(out, surfaceField{Path: path, Text: text})
}
}
for i, m := range req.Messages {
add(fmt.Sprintf("messages[%d].content", i), m.Content)
add(fmt.Sprintf("messages[%d].reasoning_content", i), m.ReasoningContent)
add(fmt.Sprintf("messages[%d].name", i), m.Name)
for j, tc := range m.ToolCalls {
add(fmt.Sprintf("messages[%d].tool_calls[%d].name", i, j), tc.Name)
add(fmt.Sprintf("messages[%d].tool_calls[%d].arguments", i, j), tc.Arguments)
}
for j, item := range m.ResponsesItems {
add(fmt.Sprintf("messages[%d].responses_items[%d]", i, j), string(item))
}
}
for i, t := range req.Tools {
add(fmt.Sprintf("tools[%d].name", i), t.Name)
add(fmt.Sprintf("tools[%d].description", i), t.Description)
if len(t.Parameters) > 0 {
b, err := json.Marshal(t.Parameters)
if err == nil {
add(fmt.Sprintf("tools[%d].parameters", i), string(b))
}
}
}
return out
}
// findInSurface reports every place a marker is readable.
func findInSurface(req provider.Request, marker string) []string {
var hits []string
for _, f := range providerSurface(req) {
if strings.Contains(f.Text, marker) {
hits = append(hits, f.Path)
}
}
return hits
}
// recallToolSchema returns the recall tool as the provider sees it.
func recallToolSchema(req provider.Request) (provider.ToolSchema, bool) {
for _, t := range req.Tools {
if t.Name == "recall" {
return t, true
}
}
return provider.ToolSchema{}, false
}
// localOnlyLeaks reports host-side fields that should never survive the
// provider boundary. A hit here is a boundary regression, marker or no marker.
func localOnlyLeaks(req provider.Request) []string {
var out []string
for i, m := range req.Messages {
switch {
case m.RawContent != "":
out = append(out, fmt.Sprintf("messages[%d].raw_content", i))
case m.LocalOnly:
out = append(out, fmt.Sprintf("messages[%d].local_only", i))
case m.DecisionReceipt != nil || len(m.DecisionReceipts) > 0:
out = append(out, fmt.Sprintf("messages[%d].decision_receipt", i))
case m.ToolExecution != nil:
out = append(out, fmt.Sprintf("messages[%d].tool_execution", i))
case len(m.MemoryCitations) > 0:
out = append(out, fmt.Sprintf("messages[%d].memory_citations", i))
}
}
return out
}
// searchWording is what a search-off arm must never be told. The arm exists to
// reproduce the runtime before search; an arm offered a capability it does not
// have is a different experiment.
var searchWording = []string{"recall with a query", "search the whole folded region", "query still finds"}
@@ -1,671 +0,0 @@
// contextbench.go — what the model did with the affordance, read off structure.
package main
import (
"encoding/json"
"fmt"
"io"
"regexp"
"slices"
"strconv"
"strings"
"reasonix/internal/base/tokencount"
"reasonix/internal/contract/provider"
)
// Every judgement here reads a tool call's name and its JSON arguments, or a
// canonical position, and never the model's prose. "I will search the history"
// is not a search; a recall call with a query is.
// failure stages, in funnel order. Exactly one is reported per run, except for
// NoEvidence: that stage belongs to a run with no measurement at all.
const (
stageReadMissed = "NoSearch" // never asked, and never read the target either
stageSearchMiss = "SearchMiss" // searched, target never came back
stageHitNotRead = "HitNotRead" // target was returned and never opened
stageAnswerWrong = "ReadButAnswerWrong" // opened it and still answered wrong
stageRecovered = "Recovered"
stageContaminated = "Contaminated"
stageNoEvidence = "NoEvidence"
// Routing is judged by model round, not by the linear order of tool events:
// two calls in one round are one parallel fan-out, and calling that
// "workspace first" would report a choice the model never made.
routeMemoryFirst = "MemoryFirst"
routeWorkspaceFirst = "WorkspaceFirst"
routeParallel = "Parallel"
routeNeither = "Neither"
stageCueRead = "DirectRead" // read the target from an index address, no search needed
stageNoRetrieval = "NoRetrieval" // answered without touching recall at all
toolRecall = "recall"
)
// searchAttempt is one query the model wrote, and what it got back. Kept per
// attempt rather than aggregated: the question is why a retrieval that the host
// probe ranks first takes the model more than one try.
type searchAttempt struct {
Ordinal int `json:"ordinal"`
Round int `json:"round"`
Query string `json:"query"`
TargetRank int `json:"target_rank"` // 0 = the target did not come back
ResultCount int `json:"result_count"`
// Reformulation classifies this query against the one before it, and
// MissingProbeTerms are the host probe's terms this query left out. Both
// are derived from token sets: no judge, and nothing read out of prose.
Reformulation string `json:"reformulation,omitempty"`
MissingProbeTerms []string `json:"missing_probe_terms,omitempty"`
}
// contextMetrics is one run's behaviour.
type contextMetrics struct {
Task string `json:"task"`
Arm string `json:"arm"`
SearchCalls int `json:"search_calls"`
// SearchHits counts searches that returned anything; TargetSearchHits
// counts searches that returned the target. The gap is query quality.
SearchHits int `json:"search_hits"`
TargetSearchHits int `json:"target_search_hits"`
FirstTargetRank int `json:"first_target_rank"` // 0 = never returned
// FirstHitCoverage: how much of the answer the first hit handed over.
// rank=1 with coverage 1/3 is a window too short to answer with, not a
// model that would not stop.
FirstHitCoverage int `json:"first_hit_coverage"`
AnswerValues int `json:"answer_values"`
ReadCalls int `json:"read_calls"`
TargetRead bool `json:"target_read"`
ReadAfterHit bool `json:"read_after_hit"`
// RecallReadWithoutSearch: the index addressed the target, so no search was
// needed. Not "direct read" — read_file is a read too, and in an audit the
// two mean opposite things.
RecallReadWithoutSearch bool `json:"recall_read_without_search"`
SearchThenRecallRead bool `json:"search_then_recall_read"`
FirstSearchRound int `json:"first_search_round"`
FirstReadRound int `json:"first_read_round"`
RetrievalRounds int `json:"retrieval_rounds"`
// RecallReturnedTokens is what recall paged back in. The index saves
// resident prefix; this is what is paid dynamically instead.
RecallReturnedTokens int `json:"recall_returned_tokens"`
// Provider usage as reported on the stream, or empty for a dry run. The count
// is completions that reported usage, so it is not a count of attempts.
UsageReportedRequests int `json:"usage_reported_requests,omitempty"`
PromptTokens int `json:"prompt_tokens,omitempty"`
CompletionTokens int `json:"completion_tokens,omitempty"`
CacheHitTokens int `json:"cache_hit_tokens,omitempty"`
CacheMissTokens int `json:"cache_miss_tokens,omitempty"`
UsageEstimated bool `json:"usage_estimated,omitempty"`
Searches []searchAttempt `json:"searches,omitempty"`
// PostHitSearches are queries issued after the target had already been
// returned: not a query problem, a result-to-read problem.
PostHitSearches int `json:"post_hit_searches"`
AnswerRecovered bool `json:"answer_recovered"`
// AnswerAmbiguous marks an answer that offered more than one candidate for
// a value. Markers matching proves the right fact appears; it does not
// prove it was submitted as the answer. Recorded, never acted on.
AnswerAmbiguous bool `json:"answer_ambiguous,omitempty"`
AmbiguousVar string `json:"ambiguous_var,omitempty"`
MissingMarkers []string `json:"missing_markers,omitempty"`
// FinalAnswer is kept bounded so a surprising run can be read back. A
// metric nobody can audit is a claim, not a measurement.
FinalAnswer string `json:"final_answer,omitempty"`
FailureStage string `json:"failure_stage"`
TimeoutReason string `json:"timeout_reason,omitempty"`
// Failed marks a run that ended before a transcript existed: it has no score
// and no place in the statistics. The row stays in results.jsonl with its
// task, arm and stage, so a failure is counted rather than dropped.
Failed bool `json:"failed,omitempty"`
Reason string `json:"reason,omitempty"`
// UnexpectedWorkTools are investigation tools used instead of recall.
// Reaching for the workspace is behaviour, not noise: how a model routes
// its uncertainty when memory is missing is part of what is measured.
UnexpectedWorkTools []string `json:"unexpected_work_tools,omitempty"`
EscapeCalls int `json:"escape_calls"`
EscapeBeforeFirstRecall int `json:"escape_before_first_recall"`
EscapeAfterFirstRecall int `json:"escape_after_first_recall"`
// Contaminated: the answer was reachable through something other than
// recall. Asserting this beats trusting the isolation, and a run that trips
// it leaves the statistics rather than inflating one.
Contaminated bool `json:"contaminated,omitempty"`
LeakedVia string `json:"leaked_via,omitempty"`
LeakedMarker string `json:"leaked_marker,omitempty"`
LeakedArgs string `json:"leaked_args,omitempty"`
LeakedOutput string `json:"leaked_output,omitempty"`
// EscapeFoundAnswer separates "looked at the workspace" from "found the
// answer there": different facts about the same call.
EscapeFoundAnswer bool `json:"escape_found_answer,omitempty"`
CueVisible bool `json:"cue_visible"`
// trajectory is kept out of the metric line and written beside it, so a
// future question can be asked of this batch instead of a new one.
trajectory []provider.Message
// CueDirectRead: the cue was addressed, the first memory action was a
// positions read, and it covered the target. Crediting any positions call
// would hand the index a search's work.
CueDirectRead bool `json:"cue_direct_read"`
// Sufficiency, not hit. A search that returns the target at rank 1 has not
// necessarily returned the answer: the snippet is bounded. The stopping
// boundary is the round where everything needed was readable.
FirstSufficientRound int `json:"first_sufficient_round"`
SufficientVia string `json:"sufficient_via"` // snippet | read | never
RoundsAfterSufficient int `json:"rounds_after_sufficient"`
SearchesAfterSufficient int `json:"searches_after_sufficient"`
ReadsAfterSufficient int `json:"reads_after_sufficient"`
EscapesAfterSufficient int `json:"escapes_after_sufficient"`
RecallTokensAfterSufficient int `json:"recall_tokens_after_sufficient"`
StoppingClass string `json:"stopping_class"`
// What happened after the answer was already in hand. A timeout that spent
// seven rounds past its target is a stopping problem, and extending the
// budget would only buy the loop more time.
TargetFirstSeenRound int `json:"target_first_seen_round"`
RoundsAfterTarget int `json:"rounds_after_target"`
SearchesAfterTarget int `json:"searches_after_target"`
RecallTokensAfterTarget int `json:"recall_tokens_after_target"`
TargetHeldAtEnd bool `json:"target_held_at_end"`
// Routing is where the model looked first when its memory was missing.
Routing string `json:"routing"`
FirstRecallRound int `json:"first_recall_round"`
FirstEscapeRound int `json:"first_escape_round"`
}
// hitPosition matches the address recall prints for a search hit. Reading our
// own rendered format, not guessing at the model's words.
var hitPosition = regexp.MustCompile(`(?m)^#(\d+) `)
// scoreRun reads the messages one turn appended and reports what happened.
// runScorer walks one turn's messages, holding the state a judgement needs:
// which calls are open, whether the target has come back, and which search
// each result belongs to.
type runScorer struct {
m contextMetrics
inst fixtureInstance
target int
calls map[string]provider.ToolCall
pending map[string]int
round int
sawTargetHit bool
// seen accumulates everything recall has shown the model, so sufficiency is
// judged on what it could read rather than on which call produced it.
seen strings.Builder
sufficient bool
}
// scoreRun reads the messages one turn appended and reports what happened.
func scoreRun(msgs []provider.Message, inst fixtureInstance, target int, arm string, cueVisible bool) contextMetrics {
s := &runScorer{
m: contextMetrics{Task: inst.Task.ID, Arm: arm, CueVisible: cueVisible},
inst: inst, target: target,
calls: map[string]provider.ToolCall{}, pending: map[string]int{},
}
for _, msg := range msgs {
if len(msg.ToolCalls) > 0 {
s.round++
}
for _, tc := range msg.ToolCalls {
s.calls[tc.ID] = tc
s.observeCall(tc)
}
if msg.Role == provider.RoleTool {
s.observeResult(msg)
}
}
return s.finish(cueVisible)
}
// observeCall records one tool call: a recall search, a recall read, or a
// reach for the workspace.
func (s *runScorer) observeCall(tc provider.ToolCall) {
m := &s.m
if tc.Name != toolRecall {
if !containsString(m.UnexpectedWorkTools, tc.Name) {
m.UnexpectedWorkTools = append(m.UnexpectedWorkTools, tc.Name)
}
m.EscapeCalls++
if s.sufficient {
m.EscapesAfterSufficient++
}
if m.FirstEscapeRound == 0 {
m.FirstEscapeRound = s.round
}
if m.SearchCalls+m.ReadCalls == 0 {
m.EscapeBeforeFirstRecall++
} else {
m.EscapeAfterFirstRecall++
}
return
}
if m.FirstRecallRound == 0 {
m.FirstRecallRound = s.round
}
query, positions := recallArgs(tc.Arguments)
switch {
case query != "":
m.SearchCalls++
if m.FirstSearchRound == 0 {
m.FirstSearchRound = s.round
}
if s.sawTargetHit {
m.PostHitSearches++
m.SearchesAfterTarget++
}
if s.sufficient {
m.SearchesAfterSufficient++
}
m.Searches = append(m.Searches, searchAttempt{Ordinal: m.SearchCalls, Round: s.round, Query: query})
s.pending[tc.ID] = len(m.Searches) - 1
case len(positions) > 0:
m.ReadCalls++
if s.sufficient {
m.ReadsAfterSufficient++
}
if m.FirstReadRound == 0 {
m.FirstReadRound = s.round
}
// A read covers the target when it names its position: recall returns
// the tool results under the caller's own address.
if containsInt(positions, s.target) {
m.TargetRead = true
switch {
case s.sawTargetHit:
m.ReadAfterHit, m.SearchThenRecallRead = true, true
case m.SearchCalls == 0:
m.RecallReadWithoutSearch = true
}
}
}
}
// observeResult reads what a call returned: contamination, page-in cost, and
// whether this search brought the target back.
func (s *runScorer) observeResult(msg provider.Message) {
m := &s.m
call, ok := s.calls[msg.ToolCallID]
if !ok {
return
}
if call.Name != toolRecall {
// Codenames only: a three-digit epoch appears in any long file by
// chance, and one did. A model holding the codename holds the numbers
// beside it anyway.
for _, marker := range s.inst.AnswerMarkers {
if len(marker) >= 6 && strings.Contains(msg.Content, marker) {
m.Contaminated, m.LeakedVia, m.LeakedMarker = true, call.Name, marker
m.EscapeFoundAnswer = true
m.LeakedArgs, m.LeakedOutput = truncate(call.Arguments, 200), truncate(msg.Content, 300)
}
}
return
}
cost := tokencount.Text(msg.Content)
m.RecallReturnedTokens += cost
if s.sawTargetHit {
m.RecallTokensAfterTarget += cost
}
if s.sufficient {
m.RecallTokensAfterSufficient += cost
}
// Everything recall showed the model, judged as a whole: the answer may
// arrive across a snippet and a read, and either way it is readable.
s.seen.WriteString(msg.Content)
s.seen.WriteString("\n")
if !s.sufficient && allMarkersIn(s.seen.String(), s.inst.AnswerMarkers) {
s.sufficient = true
m.FirstSufficientRound = s.round
query, _ := recallArgs(call.Arguments)
m.SufficientVia = "read"
if query != "" {
m.SufficientVia = "snippet"
}
}
if query, _ := recallArgs(call.Arguments); query == "" {
return
}
positions := searchHitPositions(msg.Content)
if len(positions) > 0 {
m.SearchHits++
}
rank := indexOfInt(positions, s.target)
if i, ok := s.pending[msg.ToolCallID]; ok {
m.Searches[i].TargetRank, m.Searches[i].ResultCount = rank, len(positions)
}
if rank == 0 {
return
}
m.TargetSearchHits++
if !s.sawTargetHit {
m.TargetFirstSeenRound = s.round
m.FirstHitCoverage = answerCoverage(msg.Content, s.inst.AnswerMarkers)
}
s.sawTargetHit = true
if m.FirstTargetRank == 0 {
m.FirstTargetRank = rank
}
}
// finish derives what only the whole walk can say.
func (s *runScorer) finish(cueVisible bool) contextMetrics {
m := s.m
m.RetrievalRounds = m.SearchCalls + m.ReadCalls
m.TargetHeldAtEnd = s.sawTargetHit || m.TargetRead
if m.TargetFirstSeenRound > 0 {
m.RoundsAfterTarget = s.round - m.TargetFirstSeenRound
}
m.Routing = routing(m.FirstRecallRound, m.FirstEscapeRound)
m.CueDirectRead = cueVisible && m.RecallReadWithoutSearch
if m.SufficientVia == "" {
m.SufficientVia = "never"
}
if m.FirstSufficientRound > 0 {
m.RoundsAfterSufficient = s.round - m.FirstSufficientRound
}
m.AnswerValues = len(s.inst.AnswerMarkers)
m.StoppingClass = stoppingClass(m)
classifySearches(&m, s.inst)
return m
}
// scoreAnswer grades the final text on the task's markers. Nonces, matched
// exactly: no judge model, and nothing a general answer could satisfy.
func (m *contextMetrics) scoreAnswer(final string, inst fixtureInstance) {
lower := strings.ToLower(final)
for _, marker := range inst.AnswerMarkers {
if !strings.Contains(lower, strings.ToLower(marker)) {
m.MissingMarkers = append(m.MissingMarkers, marker)
}
}
m.AnswerRecovered = len(m.MissingMarkers) == 0
m.AnswerAmbiguous, m.AmbiguousVar = ambiguousAnswer(final, inst)
m.FailureStage = m.stage()
if len(final) > 1200 {
final = final[:1200] + "…"
}
m.FinalAnswer = final
}
// stage places the run in the funnel. One label, chosen at the first step that
// did not happen, so a table of stages reads as where the retrieval broke.
func (m contextMetrics) stage() string {
switch {
case m.Contaminated:
return stageContaminated
case m.AnswerRecovered && m.RecallReadWithoutSearch:
return stageCueRead
case m.AnswerRecovered:
return stageRecovered
case m.TargetRead:
return stageAnswerWrong
case m.TargetSearchHits > 0:
return stageHitNotRead
case m.SearchCalls > 0:
return stageSearchMiss
case m.ReadCalls > 0:
return stageReadMissed
default:
return stageNoRetrieval
}
}
// recallArgs pulls the two fields that say which operation a recall call is.
func recallArgs(args string) (query string, positions []int) {
var parsed struct {
Query string `json:"query"`
Positions []int `json:"positions"`
}
if err := json.Unmarshal([]byte(args), &parsed); err != nil {
return "", nil
}
return strings.TrimSpace(parsed.Query), parsed.Positions
}
// searchHitPositions reads the addresses out of a search result, in rank order.
func searchHitPositions(text string) []int {
var out []int
for _, match := range hitPosition.FindAllStringSubmatch(text, -1) {
if n, err := strconv.Atoi(match[1]); err == nil {
out = append(out, n)
}
}
return out
}
func containsInt(list []int, want int) bool { return slices.Contains(list, want) }
// indexOfInt returns a 1-based rank, or 0 when absent.
func indexOfInt(list []int, want int) int {
for i, v := range list {
if v == want {
return i + 1
}
}
return 0
}
func containsString(list []string, want string) bool { return slices.Contains(list, want) }
// funnel summarizes one arm's runs the way the experiment is read.
type funnel struct {
Arm string
Runs int
Scored, Contaminated, Errored int
Searched, TargetHit, TargetRead, Answered int
CueReads int
SearchCalls, ReadCalls, RecallTokens int
Escapes, EscapeCalls int
Stages, Routes map[string]int
}
func summarize(arm string, runs []contextMetrics) funnel {
f := funnel{Arm: arm, Runs: len(runs), Stages: map[string]int{}, Routes: map[string]int{}}
for _, r := range runs {
// A run that never produced a transcript is not a scored run and has no
// stage to place: counting it would put an empty name in the funnel, the
// stage table and the routes.
if r.Failed {
continue
}
if r.Contaminated {
f.Contaminated++
f.Stages[r.FailureStage]++
continue
}
if r.FailureStage == "RunError" {
f.Errored++
f.Stages[r.FailureStage]++
continue
}
f.Scored++
if r.SearchCalls > 0 {
f.Searched++
}
if r.TargetSearchHits > 0 {
f.TargetHit++
}
if r.TargetRead {
f.TargetRead++
}
if r.AnswerRecovered {
f.Answered++
}
if r.RecallReadWithoutSearch {
f.CueReads++
}
if len(r.UnexpectedWorkTools) > 0 {
f.Escapes++
}
f.EscapeCalls += r.EscapeCalls
f.SearchCalls += r.SearchCalls
f.ReadCalls += r.ReadCalls
f.RecallTokens += r.RecallReturnedTokens
f.Stages[r.FailureStage]++
if r.FailureStage != "RunError" {
f.Routes[r.Routing]++
}
}
return f
}
func (f funnel) line() string {
per := func(n int) float64 {
if f.Scored == 0 {
return 0
}
return float64(n) / float64(f.Scored)
}
line := fmt.Sprintf("%-14s runs=%-3d answered=%d/%d searched=%d target-hit=%d cue-read=%d search/task=%.2f recall-tok/task=%.0f escape/task=%.2f",
f.Arm, f.Scored, f.Answered, f.Scored, f.Searched, f.TargetHit, f.CueReads,
per(f.SearchCalls), per(f.RecallTokens), per(f.EscapeCalls))
if f.Contaminated > 0 {
line += fmt.Sprintf(" CONTAMINATED=%d", f.Contaminated)
}
if f.Errored > 0 {
line += fmt.Sprintf(" ERRORED=%d", f.Errored)
}
return line
}
func writeJSONLine(w io.Writer, v any) error {
b, err := json.Marshal(v)
if err != nil {
return err
}
_, err = fmt.Fprintln(w, string(b))
return err
}
func truncate(s string, n int) string {
if len(s) <= n {
return s
}
return s[:n] + "…"
}
// routing says where the model looked first, comparing rounds rather than
// event order. Same round is a parallel fan-out, not a preference.
func routing(recallRound, escapeRound int) string {
switch {
case recallRound == 0 && escapeRound == 0:
return routeNeither
case recallRound == 0:
return routeWorkspaceFirst
case escapeRound == 0:
return routeMemoryFirst
case recallRound < escapeRound:
return routeMemoryFirst
case escapeRound < recallRound:
return routeWorkspaceFirst
default:
return routeParallel
}
}
// ambiguousAnswer reports an answer naming more than one candidate of a
// codename's shape. Only codenames are checked: they carry a stem this run
// declared, so two distinct "fence-…" values in one answer is one answer too
// many. Numbers have no such shape and are left alone.
func ambiguousAnswer(final string, inst fixtureInstance) (bool, string) {
for _, spec := range inst.Task.Vars {
if spec.Kind != varCodename {
continue
}
seen := map[string]bool{}
for _, match := range regexp.MustCompile(regexp.QuoteMeta(spec.Word)+`-[a-z0-9]{3,}`).FindAllString(final, -1) {
seen[match] = true
}
if len(seen) > 1 {
return true, spec.Name
}
}
return false, ""
}
func allMarkersIn(text string, markers []string) bool {
if len(markers) == 0 {
return false
}
lower := strings.ToLower(text)
for _, marker := range markers {
if !strings.Contains(lower, strings.ToLower(marker)) {
return false
}
}
return true
}
// Stopping classes. The boundary is sufficiency, not the target coming back:
// a rank-1 hit whose snippet was too short to answer leaves a read entirely
// justified, and counting that as waste would blame the model for the bound.
const (
stopSnippet = "SnippetStop" // enough arrived, and the turn ended
stopSnippetRead = "SnippetThenRead" // the snippet was short; the read finished it
stopDefensiveRead = "DefensiveRead" // enough had arrived, one read confirmed it
stopPostRetrieval = "PostSufficientRetrieval" // enough had arrived, and retrieval continued
stopRunaway = "PostSufficientRunaway" // it continued for rounds, or ran out of budget
stopNeverEnough = "NeverSufficient"
)
// runawayRounds is where continuing stops looking like confirmation.
const runawayRounds = 3
func stoppingClass(m contextMetrics) string {
if m.FirstSufficientRound == 0 {
return stopNeverEnough
}
after := m.SearchesAfterSufficient + m.ReadsAfterSufficient + m.EscapesAfterSufficient
switch {
case after == 0 && m.SufficientVia == "snippet":
return stopSnippet
case after == 0:
return stopSnippetRead
case m.RoundsAfterSufficient >= runawayRounds || m.FailureStage == "RunError":
return stopRunaway
case after == 1 && m.ReadsAfterSufficient == 1:
return stopDefensiveRead
default:
return stopPostRetrieval
}
}
// reportStopping is the funnel that matters once retrieval succeeds: what the
// turn did after it already had everything it needed.
func reportStopping(all []contextMetrics) string {
classes := map[string]int{}
var extraRounds, extraTokens []float64
sufficient, answeredNext := 0, 0
for _, m := range all {
if m.Failed || m.Contaminated {
continue
}
classes[m.StoppingClass]++
if m.FirstSufficientRound == 0 {
continue
}
sufficient++
extraRounds = append(extraRounds, float64(m.RoundsAfterSufficient))
extraTokens = append(extraTokens, float64(m.RecallTokensAfterSufficient))
if m.RoundsAfterSufficient <= 1 {
answeredNext++
}
}
var b strings.Builder
b.WriteString("\n## After the evidence was already enough\n")
fmt.Fprintf(&b, " sufficient in %d runs; ended within one round in %d\n", sufficient, answeredNext)
fmt.Fprintf(&b, " extra rounds mean %.1f median %.1f p90 %.0f max %.0f\n",
mean(extraRounds), median(extraRounds), percentile(extraRounds, 0.9), maxOf(extraRounds))
fmt.Fprintf(&b, " extra recall tokens mean %.0f median %.0f p90 %.0f max %.0f\n",
mean(extraTokens), median(extraTokens), percentile(extraTokens, 0.9), maxOf(extraTokens))
b.WriteString(" " + countsLine(classes) + "\n")
return b.String()
}
@@ -1,337 +0,0 @@
package main
import (
"fmt"
"strings"
"testing"
"reasonix/internal/contract/provider"
)
// The scorer decides what the pilot concludes, so it is held to the same rule
// the corpus is: judgements read structure. A model that says it will search
// has not searched; a recall call carrying a query has.
const scorerTarget = 42
func recallCall(id, args string) provider.Message {
return provider.Message{Role: provider.RoleAssistant, ToolCalls: []provider.ToolCall{
{ID: id, Name: "recall", Arguments: args},
}}
}
func toolResult(id, text string) provider.Message {
return provider.Message{Role: provider.RoleTool, ToolCallID: id, Name: "recall", Content: text}
}
func hitBlock(positions ...int) string {
var b strings.Builder
b.WriteString("Folded context matching \"x\":\n")
for _, p := range positions {
fmt.Fprintf(&b, "\n#%d assistant_text\nsome snippet here\n", p)
}
return b.String()
}
func scorerTask() fixtureInstance {
return fixtureInstance{Task: contextTask{ID: "t"}, AnswerMarkers: []string{"cobalt-lark-17"}}
}
func score(t *testing.T, msgs []provider.Message, final string) contextMetrics {
t.Helper()
m := scoreRun(msgs, scorerTask(), scorerTarget, "arm", false)
m.scoreAnswer(final, scorerTask())
return m
}
// The whole funnel, walked end to end.
func TestScorerReadsTheFullRetrievalChain(t *testing.T) {
m := score(t, []provider.Message{
recallCall("c1", `{"query":"retry boundary"}`),
toolResult("c1", hitBlock(7, scorerTarget)),
recallCall("c2", fmt.Sprintf(`{"positions":[%d]}`, scorerTarget)),
toolResult("c2", "#42\n[assistant]\nthe ownership token is cobalt-lark-17"),
{Role: provider.RoleAssistant, Content: "The token was cobalt-lark-17."},
}, "The token was cobalt-lark-17.")
if m.SearchCalls != 1 || m.ReadCalls != 1 {
t.Errorf("calls = %d search / %d read, want 1/1", m.SearchCalls, m.ReadCalls)
}
if m.TargetSearchHits != 1 || m.FirstTargetRank != 2 {
t.Errorf("target hit %d at rank %d, want 1 at rank 2", m.TargetSearchHits, m.FirstTargetRank)
}
if !m.TargetRead || !m.ReadAfterHit || m.RecallReadWithoutSearch {
t.Errorf("read flags = read %v afterHit %v direct %v", m.TargetRead, m.ReadAfterHit, m.RecallReadWithoutSearch)
}
if !m.AnswerRecovered || m.FailureStage != stageRecovered {
t.Errorf("stage = %q recovered=%v, want Recovered", m.FailureStage, m.AnswerRecovered)
}
if m.RecallReturnedTokens == 0 {
t.Error("recall returned tokens were not counted")
}
}
// An index arm's claim: the address was already on screen, so no search was
// needed. That is a different outcome from finding it, and gets its own stage.
func TestScorerSeparatesCueReadFromSearch(t *testing.T) {
m := score(t, []provider.Message{
recallCall("c1", fmt.Sprintf(`{"positions":[%d]}`, scorerTarget)),
toolResult("c1", "#42\nthe ownership token is cobalt-lark-17"),
{Role: provider.RoleAssistant, Content: "cobalt-lark-17"},
}, "cobalt-lark-17")
if m.SearchCalls != 0 || !m.RecallReadWithoutSearch {
t.Errorf("search=%d direct=%v, want 0 and true", m.SearchCalls, m.RecallReadWithoutSearch)
}
if m.FailureStage != stageCueRead {
t.Errorf("stage = %q, want %q", m.FailureStage, stageCueRead)
}
}
// Every way the chain can break gets its own label, so a failed run says where
// rather than only that.
func TestScorerNamesWhereTheChainBroke(t *testing.T) {
for _, tc := range []struct {
name string
msgs []provider.Message
final string
want string
}{
{
name: "never touched recall",
msgs: []provider.Message{{Role: provider.RoleAssistant, Content: "I do not recall that."}},
final: "I do not recall that.",
want: stageNoRetrieval,
},
{
name: "searched and missed",
msgs: []provider.Message{
recallCall("c1", `{"query":"unrelated words"}`),
toolResult("c1", hitBlock(3, 9)),
},
final: "not found",
want: stageSearchMiss,
},
{
name: "found it and never opened it",
msgs: []provider.Message{
recallCall("c1", `{"query":"retry boundary"}`),
toolResult("c1", hitBlock(scorerTarget)),
},
final: "probably something about retries",
want: stageHitNotRead,
},
{
name: "read it and answered wrong",
msgs: []provider.Message{
recallCall("c1", fmt.Sprintf(`{"positions":[%d]}`, scorerTarget)),
toolResult("c1", "#42\nthe token is elsewhere"),
},
final: "I think it was amber-9",
want: stageAnswerWrong,
},
{
name: "read the wrong position",
msgs: []provider.Message{
recallCall("c1", `{"positions":[7]}`),
toolResult("c1", "#7\nnothing useful"),
},
final: "unclear",
want: stageReadMissed,
},
} {
t.Run(tc.name, func(t *testing.T) {
if got := score(t, tc.msgs, tc.final).FailureStage; got != tc.want {
t.Errorf("stage = %q, want %q", got, tc.want)
}
})
}
}
// The answer lives only in folded history, so reaching for the workspace is a
// strategy change worth seeing — including when it happens to work.
func TestScorerRecordsWorkspaceEscapes(t *testing.T) {
m := score(t, []provider.Message{
{Role: provider.RoleAssistant, ToolCalls: []provider.ToolCall{
{ID: "g1", Name: "bash", Arguments: `{"command":"rg cobalt-lark"}`},
{ID: "g2", Name: "read_file", Arguments: `{"path":"notes.md"}`},
}},
{Role: provider.RoleAssistant, Content: "cobalt-lark-17"},
}, "cobalt-lark-17")
if len(m.UnexpectedWorkTools) != 2 {
t.Fatalf("escapes = %v, want bash and read_file", m.UnexpectedWorkTools)
}
if m.SearchCalls != 0 || m.ReadCalls != 0 {
t.Errorf("a workspace tool was counted as recall: search=%d read=%d", m.SearchCalls, m.ReadCalls)
}
// It answered, so the run is Recovered — the escape is reported beside the
// outcome rather than overriding it.
if !m.AnswerRecovered {
t.Error("a correct answer was not scored as recovered")
}
}
// Narration is not action.
func TestScorerIgnoresWhatTheModelSaysItWillDo(t *testing.T) {
m := score(t, []provider.Message{
{Role: provider.RoleAssistant, Content: "Let me search the folded context with recall for the retry boundary."},
}, "Let me search the folded context with recall for the retry boundary.")
if m.SearchCalls != 0 || m.FailureStage != stageNoRetrieval {
t.Errorf("prose was scored as a search: calls=%d stage=%q", m.SearchCalls, m.FailureStage)
}
}
// Every marker must land, so a partially right answer is not a recovery.
func TestScorerRequiresEveryMarker(t *testing.T) {
task := fixtureInstance{Task: contextTask{ID: "t"}, AnswerMarkers: []string{"comet-42", "per-lineage"}}
m := scoreRun(nil, task, scorerTarget, "arm", false)
m.scoreAnswer("the salt was comet-42", task)
if m.AnswerRecovered {
t.Error("a half answer scored as recovered")
}
if len(m.MissingMarkers) != 1 || m.MissingMarkers[0] != "per-lineage" {
t.Errorf("missing = %v, want per-lineage", m.MissingMarkers)
}
}
// An answer reachable through anything but recall invalidates the run. The
// workspace is isolated so this should never fire; asserting it beats trusting
// it, and a tripped run leaves the statistics rather than inflating one.
func TestScorerInvalidatesRunsThatLeakedThroughAnotherTool(t *testing.T) {
m := score(t, []provider.Message{
{Role: provider.RoleAssistant, ToolCalls: []provider.ToolCall{
{ID: "g1", Name: "bash", Arguments: `{"command":"rg -r ."}`},
}},
{Role: provider.RoleTool, ToolCallID: "g1", Name: "bash", Content: "corpus.go: the token is cobalt-lark-17"},
{Role: provider.RoleAssistant, Content: "cobalt-lark-17"},
}, "cobalt-lark-17")
if !m.Contaminated || m.LeakedVia != "bash" || m.LeakedMarker != "cobalt-lark-17" {
t.Fatalf("leak not caught: contaminated=%v via=%q marker=%q", m.Contaminated, m.LeakedVia, m.LeakedMarker)
}
if m.FailureStage != stageContaminated {
t.Errorf("stage = %q, want %q even though every marker matched", m.FailureStage, stageContaminated)
}
f := summarize("arm", []contextMetrics{m})
if f.Scored != 0 || f.Answered != 0 || f.Contaminated != 1 {
t.Errorf("a contaminated run entered the statistics: %+v", f)
}
}
// Where an escape happened matters: before any recall is a different strategy
// from after one came back short.
func TestScorerPlacesEscapesRelativeToRecall(t *testing.T) {
m := score(t, []provider.Message{
{Role: provider.RoleAssistant, ToolCalls: []provider.ToolCall{
{ID: "g1", Name: "bash", Arguments: `{"command":"ls"}`},
}},
{Role: provider.RoleTool, ToolCallID: "g1", Name: "bash", Content: "README.md"},
recallCall("c1", `{"query":"retry boundary"}`),
toolResult("c1", hitBlock(scorerTarget)),
{Role: provider.RoleAssistant, ToolCalls: []provider.ToolCall{
{ID: "g2", Name: "read_file", Arguments: `{"path":"README.md"}`},
}},
{Role: provider.RoleTool, ToolCallID: "g2", Name: "read_file", Content: "nothing here"},
}, "unclear")
if m.EscapeCalls != 2 || m.EscapeBeforeFirstRecall != 1 || m.EscapeAfterFirstRecall != 1 {
t.Errorf("escapes = %d (%d before, %d after), want 2 (1, 1)",
m.EscapeCalls, m.EscapeBeforeFirstRecall, m.EscapeAfterFirstRecall)
}
}
// Routing is judged by round. Two calls in one round are a fan-out, and
// reporting that as a preference would invent a decision the model never made.
func TestRoutingIsJudgedByRoundNotEventOrder(t *testing.T) {
parallel := []provider.Message{
{Role: provider.RoleAssistant, ToolCalls: []provider.ToolCall{
{ID: "g1", Name: "bash", Arguments: `{"command":"ls"}`},
{ID: "c1", Name: "recall", Arguments: `{"query":"retry boundary"}`},
}},
{Role: provider.RoleTool, ToolCallID: "g1", Name: "bash", Content: "README.md"},
toolResult("c1", hitBlock(scorerTarget)),
}
if got := score(t, parallel, "unclear").Routing; got != routeParallel {
t.Errorf("routing = %q for one round holding both, want %q", got, routeParallel)
}
workspaceFirst := []provider.Message{
{Role: provider.RoleAssistant, ToolCalls: []provider.ToolCall{
{ID: "g1", Name: "bash", Arguments: `{"command":"ls"}`},
}},
{Role: provider.RoleTool, ToolCallID: "g1", Name: "bash", Content: "README.md"},
recallCall("c1", `{"query":"retry boundary"}`),
toolResult("c1", hitBlock(scorerTarget)),
}
if got := score(t, workspaceFirst, "unclear").Routing; got != routeWorkspaceFirst {
t.Errorf("routing = %q for workspace in an earlier round, want %q", got, routeWorkspaceFirst)
}
memoryFirst := []provider.Message{
recallCall("c1", `{"query":"retry boundary"}`),
toolResult("c1", hitBlock(scorerTarget)),
{Role: provider.RoleAssistant, ToolCalls: []provider.ToolCall{
{ID: "g1", Name: "bash", Arguments: `{"command":"ls"}`},
}},
{Role: provider.RoleTool, ToolCallID: "g1", Name: "bash", Content: "README.md"},
}
if got := score(t, memoryFirst, "unclear").Routing; got != routeMemoryFirst {
t.Errorf("routing = %q for recall in an earlier round, want %q", got, routeMemoryFirst)
}
if got := score(t, nil, "no idea").Routing; got != routeNeither {
t.Errorf("routing = %q with no tools at all, want %q", got, routeNeither)
}
}
// Markers matching proves the right fact appears somewhere. An answer that
// hedges between two candidates contains it and has not submitted it.
func TestScorerFlagsAnAnswerThatNamesTwoCandidates(t *testing.T) {
inst := fixtureInstance{
Task: contextTask{ID: "t", Vars: []varSpec{code("marker", "fence")}},
AnswerMarkers: []string{"fence-qmwnv"},
}
m := scoreRun(nil, inst, 0, "arm", false)
m.scoreAnswer("It was fence-qmwnv, though it may have been fence-abcde.", inst)
if !m.AnswerRecovered {
t.Error("the marker is present; recovery should still read true")
}
if !m.AnswerAmbiguous || m.AmbiguousVar != "marker" {
t.Errorf("ambiguity not flagged: %v %q", m.AnswerAmbiguous, m.AmbiguousVar)
}
clean := scoreRun(nil, inst, 0, "arm", false)
clean.scoreAnswer("The marker was fence-qmwnv.", inst)
if clean.AnswerAmbiguous {
t.Error("a single candidate was called ambiguous")
}
}
// The index arm's claim is narrow: the cue was addressed, the first memory
// action was a positions read, and it covered the target. A positions call
// after a search located the target credits the index for the search's work.
func TestCueDirectReadRequiresTheCueAndTheFirstAction(t *testing.T) {
direct := []provider.Message{
recallCall("c1", fmt.Sprintf(`{"positions":[%d]}`, scorerTarget)),
toolResult("c1", "#42\nthe token is cobalt-lark-17"),
}
m := scoreRun(direct, scorerTask(), scorerTarget, "arm", true)
if !m.CueDirectRead {
t.Error("a first-action read with the cue visible was not credited")
}
// Same read, but no cue was on screen to give the address.
if scoreRun(direct, scorerTask(), scorerTarget, "arm", false).CueDirectRead {
t.Error("a read was credited to a cue that was not visible")
}
// Same read, but a search found the address first.
afterSearch := append([]provider.Message{
recallCall("c0", `{"query":"retry boundary"}`),
toolResult("c0", hitBlock(scorerTarget)),
}, direct...)
if scoreRun(afterSearch, scorerTask(), scorerTarget, "arm", true).CueDirectRead {
t.Error("a read after a search was credited to the index")
}
}
-469
View File
@@ -1,469 +0,0 @@
package main
import (
"fmt"
"math/rand"
"reasonix/internal/state/sessionstore"
"reasonix/internal/contract/provider"
)
// Synthetic history on purpose: an answer that exists in the workspace is
// solvable by reading the workspace, which measures grep. Every answer is a
// placeholder here and a value only inside one run.
// experiment names which question a task belongs to.
const (
experimentSearch = "search" // does search reach what no index addresses?
experimentIndex = "index" // what is an ambient address line worth?
)
// cueTier is the narrowest fold-index scale at which an index task's cue is
// still addressed. Below it the model has to find the call without a hint.
const (
tierQuarter = "quarter" // visible at default, half, quarter
tierHalf = "half" // visible at default, half
tierDefault = "default" // visible at default only
)
// role places an index task in the efficiency substrate or the stopping corpus.
const (
roleEfficiency = "efficiency"
roleStopping = "stopping"
)
// plantKind is how a task's target enters the transcript.
const (
plantAssistant = "assistant" // the model's own account: never index-addressed
plantCall = "call" // a tool call and its result: always index-addressed
)
// contextTask is one recall question as a template. Nothing here is an answer.
type contextTask struct {
ID string
Experiment string
TargetKind string
Prompt string
ProbeQuery string
// Vars declare the placeholders. AnswerVars name the ones a correct answer
// must contain, CueVar the one an index line carries.
Vars []varSpec
AnswerVars []string
CueVar string
CueTier string
// Role is roleEfficiency or roleStopping; it decides the batch a task enters.
Role string
// PlantAfterGen ages the target: mergeFoldIndex trims from its oldest end,
// so what follows a cue decides which budget addresses it. Frozen, because
// a run that searched for it would let the tested code move its goalposts.
PlantAfterGen int
// Plant is how the target enters, and the rest is what it says.
Plant string
CallTool string
CallArgs string
CallID string
PlantBody string
}
// fixtureInstance is one task with values. Scoring binds to this, never to the
// corpus: the corpus has no answers to bind to.
type fixtureInstance struct {
Task contextTask
Vars fixtureVars
Prompt string
ProbeQuery string
AnswerMarkers []string
CueMarker string
body string
callArgs string
}
// instantiateTask resolves one task against a generator.
func instantiateTask(t contextTask, rng *rand.Rand) (fixtureInstance, error) {
vars := newVars(t.Vars, rng)
inst := fixtureInstance{Task: t, Vars: vars}
var err error
for _, pair := range []struct {
dst *string
text string
}{
{&inst.Prompt, t.Prompt},
{&inst.ProbeQuery, t.ProbeQuery},
{&inst.body, t.PlantBody},
{&inst.callArgs, t.CallArgs},
} {
if *pair.dst, err = instantiate(pair.text, vars); err != nil {
return fixtureInstance{}, fmt.Errorf("%s: %w", t.ID, err)
}
}
for _, name := range t.AnswerVars {
value, ok := vars[name]
if !ok {
return fixtureInstance{}, fmt.Errorf("%s: answer var %q is not declared", t.ID, name)
}
inst.AnswerMarkers = append(inst.AnswerMarkers, value)
}
if t.CueVar != "" {
inst.CueMarker = vars[t.CueVar]
}
return inst, nil
}
// plant appends the instantiated target and returns its canonical position.
func (f fixtureInstance) plant(s *sessionstore.Session) int {
at := len(s.Messages)
if f.Task.Plant == plantAssistant {
s.Add(provider.Message{Role: provider.RoleAssistant, Content: f.body})
return at
}
s.Add(provider.Message{Role: provider.RoleAssistant, ToolCalls: []provider.ToolCall{
{ID: f.Task.CallID, Name: f.Task.CallTool, Arguments: f.callArgs},
}})
s.Add(provider.Message{Role: provider.RoleTool, ToolCallID: f.Task.CallID, Name: f.Task.CallTool, Content: f.body})
return at
}
func code(name, word string) varSpec { return varSpec{Name: name, Kind: varCodename, Word: word} }
func num(name string, lo, hi int) varSpec {
return varSpec{Name: name, Kind: varInt, Min: lo, Max: hi}
}
// efficiencyTasks is the substrate a study measures on. Every task in it
// qualifies under the corpus bar; the stopping corpus is deliberately excluded,
// because a task carrying post-sufficient stopping behaviour swamps the effect
// an efficiency batch exists to measure.
func efficiencyTasks() []contextTask {
var out []contextTask
for _, t := range indexTasks() {
if t.Role == roleEfficiency {
out = append(out, t)
}
}
return out
}
func searchTasks() []contextTask {
return []contextTask{
{
ID: "r01-retry-boundary", Experiment: experimentSearch, TargetKind: "assistant_reasoning",
Prompt: "What ownership token did we settle on for the retry boundary, and under what tag did we file the reason for not moving retries above framing?",
ProbeQuery: "retry boundary ownership token framing",
Vars: []varSpec{code("token", "cobalt"), code("reason", "stream")},
AnswerVars: []string{"token", "reason"},
Plant: plantAssistant,
PlantBody: "The retry boundary remains at ingress. The ownership token is {{token}}. " +
"Moving retries above framing can duplicate a partially received stream, which we filed as {{reason}}.",
},
{
ID: "r02-checkpoint-anchor", Experiment: experimentSearch, TargetKind: "assistant_reasoning",
Prompt: "What was the codename of the checkpoint anchor policy, and what tag did we give the reason for not anchoring against digest positions?",
ProbeQuery: "checkpoint anchor policy digest positions",
Vars: []varSpec{code("policy", "northglass"), code("reason", "genlocal")},
AnswerVars: []string{"policy", "reason"},
Plant: plantAssistant,
PlantBody: "The checkpoint anchor policy is {{policy}}. Digest positions are generation-local — " +
"filed as {{reason}} — so recovery must anchor against canonical state.",
},
{
ID: "r03-recovery-cjk", Experiment: experimentSearch, TargetKind: "assistant_reasoning_cjk",
Prompt: "我们之前定的验证失败恢复策略代号叫什么?当时把“失败 digest 不能变成 baseline”这条登记在哪个标记下?",
ProbeQuery: "恢复策略 baseline digest 验证失败",
Vars: []varSpec{code("policy", "songzhen"), code("rule", "trustbase")},
AnswerVars: []string{"policy", "rule"},
Plant: plantAssistant,
PlantBody: "恢复策略代号是 {{policy}}。验证失败以后必须保留旧基线,不能把新的 digest 晋升为 baseline," +
"因为失败证据不能成为后续比较的可信基准;这条规则登记为 {{rule}}。",
},
{
ID: "r04-lease-fence", Experiment: experimentSearch, TargetKind: "assistant_reasoning",
Prompt: "那次 lease fence 检查报告的诊断标记是什么?expected 和 observed epoch 分别是多少?",
ProbeQuery: "lease fence writer epoch mismatch",
Vars: []varSpec{
code("marker", "fence"), num("expected", 400, 900), num("observed", 100, 399),
},
AnswerVars: []string{"marker", "expected", "observed"},
Plant: plantAssistant,
PlantBody: "The lease fence check reported {{marker}}: expected writer epoch {{expected}}, " +
"observed {{observed}}. We left the fence in place.",
},
{
ID: "r05-schema-guard", Experiment: experimentSearch, TargetKind: "assistant_reasoning",
Prompt: "当时 schema guard 报告的标记是什么,超预算多少 token?",
ProbeQuery: "schema guard provider visible surface budget",
Vars: []varSpec{code("marker", "amber"), num("over", 120, 480)},
AnswerVars: []string{"marker", "over"},
Plant: plantAssistant,
PlantBody: "The schema guard reported {{marker}}: the provider-visible tool schema exceeded " +
"the allowed surface by {{over}} tokens. We trimmed a description instead.",
},
{
ID: "r06-shadow-probe", Experiment: experimentSearch, TargetKind: "assistant_reasoning",
Prompt: "我们那次 shadow probe 的 marker、p95 divergence 和 generation 分别是多少?",
ProbeQuery: "shadow probe divergence generation marker",
Vars: []varSpec{
code("marker", "violet"),
{Name: "divergence", Kind: varDecimal, Min: 110, Max: 890},
num("generation", 14, 60),
},
AnswerVars: []string{"marker", "divergence", "generation"},
Plant: plantAssistant,
PlantBody: "The shadow probe came back as marker={{marker}} with a p95 divergence of " +
"{{divergence}} at generation={{generation}}, which we judged within tolerance.",
},
}
}
// efficiencyIndexTasks is what a study measures on: two tasks per cue tier.
func efficiencyIndexTasks() []contextTask {
out := indexQuarterTasks()
out = append(out, indexHalfTasks()...)
out = append(out, indexDefaultTasks()...)
return out
}
// indexQuarterTasks holds the quarter-tier literals.
func indexQuarterTasks() []contextTask {
return []contextTask{
{
ID: "i37-dispatchlag", Experiment: experimentIndex, TargetKind: "tool_input", Role: roleEfficiency,
Prompt: "早前会话里执行过 ./scripts/dispatchlag,它打印出的 dispatch lag epoch 是多少?",
ProbeQuery: "dispatchlag dispatch lag epoch",
Vars: []varSpec{code("cue", "dispatchlag"), code("epoch", "cobalt")},
AnswerVars: []string{"epoch"}, CueVar: "cue", CueTier: tierQuarter,
PlantAfterGen: 4,
Plant: plantCall, CallID: "p37", CallTool: "bash",
CallArgs: `{"command":"./scripts/dispatchlag {{cue}}"}`,
PlantBody: "dispatch lag epoch = {{epoch}}",
},
{
ID: "i118-fencelag", Experiment: experimentIndex, TargetKind: "tool_input", Role: roleEfficiency,
Prompt: "早前会话里运行过 ./scripts/fencelag,它打印出的 fence lag 是多少?",
ProbeQuery: "fencelag fence lag",
Vars: []varSpec{code("cue", "fencelag"), code("v", "slate")},
AnswerVars: []string{"v"}, CueVar: "cue", CueTier: tierQuarter,
PlantAfterGen: 4,
Plant: plantCall, CallID: "x118", CallTool: "bash",
CallArgs: `{"command":"./scripts/fencelag {{cue}}"}`,
PlantBody: "fence lag = {{v}}",
},
}
}
// indexHalfTasks holds the half-tier literals.
func indexHalfTasks() []contextTask {
return []contextTask{
{
ID: "i70-ingestfloor", Experiment: experimentIndex, TargetKind: "tool_input", Role: roleEfficiency,
Prompt: "早前会话里执行过 ./scripts/ingestfloor,它打印出的 route quota 是多少?",
ProbeQuery: "ingestfloor quota",
Vars: []varSpec{code("cue", "ingestfloor"), num("quota", 1200, 9800)},
AnswerVars: []string{"quota"}, CueVar: "cue", CueTier: tierHalf,
PlantAfterGen: 2,
Plant: plantCall, CallID: "q70", CallTool: "bash",
CallArgs: `{"command":"./scripts/ingestfloor {{cue}}"}`,
PlantBody: "route quota = {{quota}}",
},
{
ID: "i106-budgetseal", Experiment: experimentIndex, TargetKind: "tool_input", Role: roleEfficiency,
Prompt: "早前会话里执行过 ./scripts/budgetseal,它打印出的 route quota 是多少?",
ProbeQuery: "budgetseal route quota",
Vars: []varSpec{code("cue", "budgetseal"), code("quota", "amber")},
AnswerVars: []string{"quota"}, CueVar: "cue", CueTier: tierHalf,
PlantAfterGen: 2,
Plant: plantCall, CallID: "h106", CallTool: "bash",
CallArgs: `{"command":"./scripts/budgetseal {{cue}}"}`,
PlantBody: "route quota = {{quota}}",
},
}
}
// indexDefaultTasks holds the default-tier literals.
func indexDefaultTasks() []contextTask {
return []contextTask{
{
ID: "i47-tenantlease", Experiment: experimentIndex, TargetKind: "tool_input", Role: roleEfficiency,
Prompt: "早前会话里执行过 ./scripts/tenantlease,它打印出的 tenant lease(秒)是多少?",
ProbeQuery: "tenantlease tenant lease",
Vars: []varSpec{code("cue", "tenantlease"), {Name: "lease", Kind: varDuration, Min: 15, Max: 300}},
AnswerVars: []string{"lease"}, CueVar: "cue", CueTier: tierDefault,
PlantAfterGen: 0,
Plant: plantCall, CallID: "p47", CallTool: "bash",
CallArgs: `{"command":"./scripts/tenantlease {{cue}}"}`,
PlantBody: "tenant lease = {{lease}}",
},
{
ID: "i112-lagquota", Experiment: experimentIndex, TargetKind: "tool_input", Role: roleEfficiency,
Prompt: "早前会话里执行过 ./scripts/lagquota,它打印出的 lagquota lease 是多少?",
ProbeQuery: "lagquota lagquota lease",
Vars: []varSpec{code("cue", "lagquota"), {Name: "lease", Kind: varDuration, Min: 20, Max: 420}},
AnswerVars: []string{"lease"}, CueVar: "cue", CueTier: tierDefault,
PlantAfterGen: 0,
Plant: plantCall, CallID: "d112", CallTool: "bash",
CallArgs: `{"command":"./scripts/lagquota {{cue}}"}`,
PlantBody: "lagquota lease = {{lease}}",
},
}
}
// stoppingIndexTasks is the frozen study's six tasks, kept out of efficiency
// batches and measurable on their own with -task <id>.
func stoppingIndexTasks() []contextTask {
return []contextTask{
{
ID: "i01-transport-fallback", Experiment: experimentIndex, TargetKind: "tool_input", Role: roleStopping,
Prompt: "transport fallback probe 当时报告的 selector 是什么?",
ProbeQuery: "transport fallback probe selector",
Vars: []varSpec{code("cue", "tfprobe"), code("selector", "fern")},
AnswerVars: []string{"selector"}, CueVar: "cue", CueTier: tierQuarter,
PlantAfterGen: 4,
Plant: plantCall, CallID: "tf1", CallTool: "bash",
CallArgs: `{"command":"./scripts/probe {{cue}}"}`,
PlantBody: "fallback selector = {{selector}}",
},
{
ID: "i02-scheduler-handoff", Experiment: experimentIndex, TargetKind: "tool_input", Role: roleStopping,
Prompt: "scheduler handoff 当时记录的 grace 和 marker 是什么?",
ProbeQuery: "scheduler handoff grace marker",
Vars: []varSpec{
code("cue", "schedhandoff"), code("marker", "opal"),
{Name: "grace", Kind: varDuration, Min: 18, Max: 90},
},
AnswerVars: []string{"grace", "marker"}, CueVar: "cue", CueTier: tierQuarter,
PlantAfterGen: 4,
Plant: plantCall, CallID: "sh1", CallTool: "read_file",
CallArgs: `{"path":"scratch/{{cue}}.txt"}`,
PlantBody: "handoff grace = {{grace}}\nmarker = {{marker}}",
},
{
ID: "i03-coalescing", Experiment: experimentIndex, TargetKind: "tool_input", Role: roleStopping,
Prompt: "job completion 的 coalescing probe 最后记录的 window 和 marker 是多少?",
ProbeQuery: "coalescing policy completion window",
Vars: []varSpec{
code("cue", "coalescing"), code("marker", "cedar"),
{Name: "window", Kind: varDuration, Min: 40, Max: 140},
},
AnswerVars: []string{"window", "marker"}, CueVar: "cue", CueTier: tierHalf,
PlantAfterGen: 2,
Plant: plantCall, CallID: "cp1", CallTool: "bash",
CallArgs: `{"command":"grep {{cue}} scratch/job-notes.log"}`,
PlantBody: "completion coalesce window = {{window}}\nmarker = {{marker}}",
},
{
ID: "i04-recovery-fence", Experiment: experimentIndex, TargetKind: "tool_input", Role: roleStopping,
Prompt: "recovery fence 那次检查报告的 marker 和两个 epoch 是什么?",
ProbeQuery: "recovery fence epoch check",
Vars: []varSpec{
code("cue", "recfence"), code("marker", "quartz"),
num("expected", 400, 900), num("observed", 100, 399),
},
AnswerVars: []string{"marker", "expected", "observed"}, CueVar: "cue", CueTier: tierHalf,
PlantAfterGen: 2,
Plant: plantCall, CallID: "rf1", CallTool: "bash",
CallArgs: `{"command":"./scripts/check {{cue}}"}`,
PlantBody: "{{marker}}\nexpected epoch {{expected}}\nobserved epoch {{observed}}",
},
{
ID: "i05-cache-probe", Experiment: experimentIndex, TargetKind: "tool_input", Role: roleStopping,
Prompt: "cache probe 当时记录的 prefix salt 和 scope 是什么?",
ProbeQuery: "cache probe prefix salt scope",
Vars: []varSpec{code("cue", "cacheprobe"), code("salt", "comet"), code("scope", "lineage")},
AnswerVars: []string{"salt", "scope"}, CueVar: "cue", CueTier: tierDefault,
PlantAfterGen: 0,
Plant: plantCall, CallID: "cb1", CallTool: "read_file",
CallArgs: `{"path":"scratch/{{cue}}.txt"}`,
PlantBody: "prefix salt = {{salt}}\nscope = {{scope}}",
},
{
ID: "i06-dispatch-handoff", Experiment: experimentIndex, TargetKind: "tool_input", Role: roleStopping,
Prompt: "dispatch handoff probe 当时确认的 mode 和 marker 是什么?",
ProbeQuery: "dispatch handoff probe mode",
Vars: []varSpec{code("cue", "dispatchhandoff"), code("mode", "trigger"), code("marker", "iris")},
AnswerVars: []string{"mode", "marker"}, CueVar: "cue", CueTier: tierDefault,
PlantAfterGen: 0,
Plant: plantCall, CallID: "dh1", CallTool: "bash",
CallArgs: `{"command":"./scripts/probe {{cue}}"}`,
PlantBody: "handoff mode = {{mode}}\nmarker = {{marker}}",
},
}
}
// indexTasks is the whole index corpus, the efficiency substrate first.
func indexTasks() []contextTask {
out := efficiencyIndexTasks()
return append(out, stoppingIndexTasks()...)
}
func allTasks() []contextTask {
return append(searchTasks(), indexTasks()...)
}
func taskByID(id string) (contextTask, bool) {
for _, t := range allTasks() {
if t.ID == id {
return t, true
}
}
return contextTask{}, false
}
// tierScales is the fixture's contract: which arms must address a cue and
// which must not. A policy change that moves a cue across a boundary fails
// preflight rather than turning two arms into one experiment.
func tierScales(tier string) (visible, hidden []string) {
switch tier {
case tierQuarter:
return []string{"default", "half", "quarter"}, []string{"off"}
case tierHalf:
return []string{"default", "half"}, []string{"quarter", "off"}
case tierDefault:
return []string{"default"}, []string{"half", "quarter", "off"}
default:
return nil, nil
}
}
// boundaryPair is the one comparison an index task is built for: the narrowest
// scale that still addresses its cue, and the next one down. Holding the
// question fixed and moving only the affordance is what four arm averages
// cannot do, since each of those mixes six different questions.
func boundaryPair(tier string) (cue, noCue string) {
switch tier {
case tierQuarter:
return "quarter", "off"
case tierHalf:
return "half", "quarter"
case tierDefault:
return "default", "half"
default:
return "", ""
}
}
func validateCorpus() error {
seen := map[string]bool{}
for _, t := range allTasks() {
if seen[t.ID] {
return fmt.Errorf("duplicate task id %q", t.ID)
}
seen[t.ID] = true
if len(t.AnswerVars) == 0 || t.ProbeQuery == "" || t.Prompt == "" || t.PlantBody == "" {
return fmt.Errorf("%s: a task needs a prompt, a probe query, a body and answer vars", t.ID)
}
if t.Experiment == experimentIndex {
if t.CueVar == "" {
return fmt.Errorf("%s: an index task needs a cue var", t.ID)
}
if t.Role != roleEfficiency && t.Role != roleStopping {
return fmt.Errorf("%s: an index task needs a role (%s or %s)", t.ID, roleEfficiency, roleStopping)
}
if v, _ := tierScales(t.CueTier); v == nil {
return fmt.Errorf("%s: unknown cue tier %q", t.ID, t.CueTier)
}
}
if _, err := instantiateTask(t, seededRand(t.ID, "validate")); err != nil {
return err
}
}
return nil
}
-243
View File
@@ -1,243 +0,0 @@
// dose.go — does the cost step where the cue disappears?
package main
import (
"fmt"
"math"
"sort"
"strings"
)
// Not "a bigger index is better" — CueDirectRead says the addresses are rarely
// used. The hypothesis is that a visible action cue lowers retrieval cost,
// which predicts a step at each task's own boundary, not a slope.
// scaleOrder is the dose axis, widest first.
var scaleOrder = []string{"default", "half", "quarter", "off"}
// boundaryStep is the index into the three transitions where a tier's cue
// disappears: default->half is 0, half->quarter is 1, quarter->off is 2.
func boundaryStep(tier string) int {
switch tier {
case tierDefault:
return 0
case tierHalf:
return 1
case tierQuarter:
return 2
default:
return -1
}
}
// doseCurve is one task's readings across the four budgets.
type doseCurve struct {
Task string
Tier string
Metric string
Values [4]float64
// Missing marks arms that produced nothing. Printing those as zero draws a
// curve falling to the floor where in fact it was never measured.
Missing [4]bool
Present bool
// PreMean averages the arms where the cue is visible, PostMean the rest.
PreMean, PostMean float64
BoundaryDelta float64
// BoundaryAligned: the largest transition is the one where this task's cue
// disappears. A change elsewhere is variance shaped like an effect.
BoundaryAligned bool
}
func buildCurve(task, tier, metric string, byScale map[string]float64) doseCurve {
c := doseCurve{Task: task, Tier: tier, Metric: metric, Present: true}
for i, scale := range scaleOrder {
v, ok := byScale[scale]
if !ok {
c.Present, c.Missing[i] = false, true
}
c.Values[i] = v
}
step := boundaryStep(tier)
if !c.Present || step < 0 {
return c
}
visible := step + 1 // arms before the boundary still show the cue
c.PreMean = mean(c.Values[:visible])
c.PostMean = mean(c.Values[visible:])
c.BoundaryDelta = c.PostMean - c.PreMean
transitions := [3]float64{
math.Abs(c.Values[1] - c.Values[0]),
math.Abs(c.Values[2] - c.Values[1]),
math.Abs(c.Values[3] - c.Values[2]),
}
largest, at := 0.0, -1
for i, t := range transitions {
if t > largest {
largest, at = t, i
}
}
// A flat curve has no largest transition to align with anything.
c.BoundaryAligned = largest > 0 && at == step
return c
}
func mean(xs []float64) float64 {
if len(xs) == 0 {
return 0
}
sum := 0.0
for _, x := range xs {
sum += x
}
return sum / float64(len(xs))
}
func median(xs []float64) float64 {
if len(xs) == 0 {
return 0
}
s := append([]float64(nil), xs...)
sort.Float64s(s)
mid := len(s) / 2
if len(s)%2 == 1 {
return s[mid]
}
return (s[mid-1] + s[mid]) / 2
}
// doseMetrics are the readings a curve is drawn for.
var doseMetrics = []struct {
name string
of func(contextMetrics) float64
}{
{"searches", func(m contextMetrics) float64 { return float64(m.SearchCalls) }},
{"recall-tokens", func(m contextMetrics) float64 { return float64(m.RecallReturnedTokens) }},
{"tool-rounds", func(m contextMetrics) float64 { return float64(m.RetrievalRounds + m.EscapeCalls) }},
{"escapes", func(m contextMetrics) float64 { return float64(m.EscapeCalls) }},
}
// reportDoseResponse draws each task's curve and reports where the steps fell.
func reportDoseResponse(all []contextMetrics) string {
valid := map[string]map[string]contextMetrics{}
for _, m := range all {
if m.Failed || m.Contaminated || m.FailureStage == "RunError" {
continue
}
scale := strings.TrimPrefix(m.Arm, "index-")
if valid[m.Task] == nil {
valid[m.Task] = map[string]contextMetrics{}
}
valid[m.Task][scale] = m
}
var b strings.Builder
b.WriteString("\n## Dose-response, per task\n")
b.WriteString(" The arrow marks where this task's cue stops being addressed.\n")
aligned := map[string]int{}
counted := map[string]int{}
deltas := map[string][]float64{}
for _, t := range indexTasks() {
arms := valid[t.ID]
if len(arms) == 0 {
continue
}
step := boundaryStep(t.CueTier)
fmt.Fprintf(&b, "\n %s (cue survives to %s)\n", t.ID, t.CueTier)
fmt.Fprintf(&b, " %-14s%s\n", "", scaleHeader(step))
for _, metric := range doseMetrics {
byScale := map[string]float64{}
for scale, m := range arms {
byScale[scale] = metric.of(m)
}
c := buildCurve(t.ID, t.CueTier, metric.name, byScale)
mark := " "
if c.BoundaryAligned {
mark = "*"
aligned[metric.name]++
}
if c.Present {
counted[metric.name]++
deltas[metric.name] = append(deltas[metric.name], c.BoundaryDelta)
}
delta := fmt.Sprintf("Δ%+8.1f", c.BoundaryDelta)
if !c.Present {
delta = " no curve"
}
fmt.Fprintf(&b, " %-14s%s %s %s\n", metric.name, curveCells(c), delta, mark)
}
}
b.WriteString("\n## Where the step fell (* = largest transition is at the cue boundary)\n")
for _, metric := range doseMetrics {
d := deltas[metric.name]
sensitivity := ""
if len(d) > 1 {
sensitivity = fmt.Sprintf(" without the largest: %+.1f", meanWithoutLargest(d))
}
fmt.Fprintf(&b, " %-14s boundary-aligned %d/%d mean Δ%+.1f median Δ%+.1f%s\n",
metric.name, aligned[metric.name], counted[metric.name], mean(d), median(d), sensitivity)
}
return b.String()
}
// meanWithoutLargest is the robustness check: the largest effect is reported
// and then set aside, so a conclusion never rests on one task alone.
func meanWithoutLargest(d []float64) float64 {
if len(d) < 2 {
return mean(d)
}
worst, at := 0.0, 0
for i, v := range d {
if math.Abs(v) > worst {
worst, at = math.Abs(v), i
}
}
rest := append(append([]float64(nil), d[:at]...), d[at+1:]...)
return mean(rest)
}
func scaleHeader(step int) string {
var b strings.Builder
for i, s := range scaleOrder {
if i == step+1 {
s = "↓" + s
}
fmt.Fprintf(&b, "%8s", s)
}
return b.String()
}
// curveCells prints a reading per arm, and a dash where an arm produced none.
func curveCells(c doseCurve) string {
var b strings.Builder
for i, v := range c.Values {
if c.Missing[i] {
fmt.Fprintf(&b, "%8s", "-")
continue
}
fmt.Fprintf(&b, "%8.0f", v)
}
return b.String()
}
func percentile(xs []float64, q float64) float64 {
if len(xs) == 0 {
return 0
}
s := append([]float64(nil), xs...)
sort.Float64s(s)
i := int(float64(len(s)-1) * q)
return s[i]
}
func maxOf(xs []float64) float64 {
out := 0.0
for _, x := range xs {
if x > out {
out = x
}
}
return out
}
@@ -1,306 +0,0 @@
package main
import (
"encoding/json"
"os"
"path/filepath"
"reflect"
"strconv"
"strings"
"testing"
)
// blockTask makes one task fail before its first model turn: its own directory
// is a regular file, so buildFixture cannot create the session under it. It is
// the same early exit as a failed fixture build, session load, or
// answer-on-disk guard, and it needs no provider.
func blockTask(t *testing.T, root, id string) {
t.Helper()
if err := os.WriteFile(filepath.Join(root, id), []byte("not a directory\n"), 0o644); err != nil {
t.Fatal(err)
}
}
// mixedTaskPair is one task whose runs all fail and one whose runs all succeed.
// Both are drawn from the corpus, so the batch is the real pipeline.
func mixedTaskPair() (blocked, ok contextTask) {
blocked, ok = indexTasks()[0], indexTasks()[1]
for _, t := range indexTasks() {
if t.Role == roleEfficiency && len(armsFor(t)) == 2 {
ok = t
break
}
}
return blocked, ok
}
// runBlockedExperiment runs one dry index batch over the given tasks, with the
// named task failing before any model turn, and returns the batch directory, the
// runs, the manifest, and everything the run printed.
func runBlockedExperiment(t *testing.T, tasks []contextTask, blocked ...string) (string, []contextMetrics, batchManifest, string) {
t.Helper()
root := t.TempDir()
for _, id := range blocked {
blockTask(t, root, id)
}
out := captureStdout(t, func() {
if code := runExperiment("run-index", experimentIndex, root, true, tasks); code != 0 {
t.Errorf("runExperiment exit = %d, want 0 (the batch finished)", code)
}
})
runs, _ := readResultLines(t, root)
manifest, _ := readManifest(t, root)
return root, runs, manifest, out
}
// captureStdout reads everything the function prints. os.Pipe is unbuffered, so
// the reader must run beside the call.
func captureStdout(t *testing.T, f func()) string {
t.Helper()
r, w, err := os.Pipe()
if err != nil {
t.Fatal(err)
}
old := os.Stdout
os.Stdout = w
done := make(chan string, 1)
go func() {
var b strings.Builder
buf := make([]byte, 4096)
for {
n, err := r.Read(buf)
b.Write(buf[:n])
if err != nil {
break
}
}
done <- b.String()
}()
f()
os.Stdout = old
w.Close()
text := <-done
r.Close()
return text
}
// funnelScored reads the "runs=N" column of the named arm's funnel line out of
// the printed tables, so the assertion is on the number a reader sees.
func funnelScored(t *testing.T, out, arm string) int {
t.Helper()
for line := range strings.SplitSeq(out, "\n") {
fields := strings.Fields(line)
if len(fields) < 2 || fields[0] != arm || !strings.HasPrefix(fields[1], "runs=") {
continue
}
n, err := strconv.Atoi(strings.TrimPrefix(fields[1], "runs="))
if err != nil {
t.Fatalf("funnel line for %s: %v\n%s", arm, err, line)
}
return n
}
t.Fatalf("no funnel line for arm %s:\n%s", arm, out)
return 0
}
// The batch report states both counts on one line, so a reader of the scrollback
// sees the scored set and the failures without counting funnel rows.
func reportCounts(t *testing.T, out string) (scored, failed int) {
t.Helper()
for line := range strings.SplitSeq(out, "\n") {
if !strings.Contains(line, " scored=") || !strings.Contains(line, " failed=") {
continue
}
for f := range strings.FieldsSeq(line) {
switch {
case strings.HasPrefix(f, "scored="):
scored, _ = strconv.Atoi(strings.TrimPrefix(f, "scored="))
case strings.HasPrefix(f, "failed="):
failed, _ = strconv.Atoi(strings.TrimPrefix(f, "failed="))
}
}
return scored, failed
}
t.Fatalf("no line reports scored= and failed=:\n%s", out)
return 0, 0
}
// A batch that mixes a task that never ran with one that did must read as its
// successful runs only: the failed task leaves Scored, the funnel, and the
// completed count, and is reported as a failure with its identity instead.
// Every assertion here is a number, not the absence of an empty row.
func TestMixedBatchCountsOnlyTheRunThatProducedEvidence(t *testing.T) {
blocked, ok := mixedTaskPair()
_, runs, manifest, out := runBlockedExperiment(t, []contextTask{blocked, ok}, blocked.ID)
wantScored, wantFailed := len(armsFor(ok)), len(armsFor(blocked))
if manifest.CompletedRuns != wantScored || manifest.FailedRuns != wantFailed {
t.Fatalf("manifest completed=%d failed=%d, want %d and %d (planned=%d)",
manifest.CompletedRuns, manifest.FailedRuns, wantScored, wantFailed, manifest.PlannedRuns)
}
if len(runs) != manifest.CompletedRuns+manifest.FailedRuns {
t.Fatalf("results.jsonl has %d rows but completed=%d failed=%d",
len(runs), manifest.CompletedRuns, manifest.FailedRuns)
}
for _, id := range manifest.TaskIDs {
if id == blocked.ID {
t.Fatalf("task_ids lists %s, which produced no evidence: %v", blocked.ID, manifest.TaskIDs)
}
}
scored, failures := 0, 0
for _, m := range runs {
if m.Failed {
failures++
if m.Task != blocked.ID || m.FailureStage == "" || m.Reason == "" {
t.Fatalf("failed row lost its identity or stage: %+v", m)
}
continue
}
scored++
if m.Task != ok.ID {
t.Fatalf("a run scored from the wrong task: %+v", m)
}
}
if scored != wantScored || failures != wantFailed {
t.Fatalf("rows scored=%d failed=%d, want %d and %d", scored, failures, wantScored, wantFailed)
}
// Each arm's funnel holds exactly the successful task's runs, and the two
// scored counts agree with each other and with the manifest.
total := 0
for _, arm := range armsFor(ok) {
total += funnelScored(t, out, arm.name)
}
if total != manifest.CompletedRuns {
t.Fatalf("funnels scored %d runs, manifest completed_runs = %d:\n%s", total, manifest.CompletedRuns, out)
}
if gotScored, gotFailed := reportCounts(t, out); gotScored != manifest.CompletedRuns || gotFailed != manifest.FailedRuns {
t.Fatalf("report says scored=%d failed=%d, manifest says %d and %d",
gotScored, gotFailed, manifest.CompletedRuns, manifest.FailedRuns)
}
if strings.Contains(out, "## Where it broke") && strings.Contains(out, "=-\n") {
t.Fatalf("the stage table kept an empty stage:\n%s", out)
}
}
// A batch where every run fails must not read as an empty success: zero scored
// runs, a nonzero failed count, and both stated.
func TestAllFailedBatchReportsZeroScoredAndNonzeroFailed(t *testing.T) {
blocked, _ := mixedTaskPair()
_, runs, manifest, out := runBlockedExperiment(t, []contextTask{blocked}, blocked.ID)
if manifest.CompletedRuns != 0 {
t.Fatalf("completed_runs = %d, want 0 (nothing produced evidence)", manifest.CompletedRuns)
}
if manifest.FailedRuns != manifest.PlannedRuns || manifest.FailedRuns == 0 {
t.Fatalf("failed_runs = %d, planned_runs = %d: every planned run failed",
manifest.FailedRuns, manifest.PlannedRuns)
}
if len(manifest.TaskIDs) != 0 {
t.Fatalf("task_ids = %v, want none", manifest.TaskIDs)
}
for i, m := range runs {
if !m.Failed || m.FailureStage == "" || m.Task != blocked.ID {
t.Fatalf("results.jsonl line %d is not a marked failure: %+v", i+1, m)
}
}
total := 0
for _, arm := range armsFor(blocked) {
total += funnelScored(t, out, arm.name)
}
if total != 0 {
t.Fatalf("funnels scored %d runs although nothing ran:\n%s", total, out)
}
if gotScored, gotFailed := reportCounts(t, out); gotScored != 0 || gotFailed != manifest.FailedRuns {
t.Fatalf("report says scored=%d failed=%d, want 0 and %d", gotScored, gotFailed, manifest.FailedRuns)
}
}
// The control: with nothing blocked, a batch is what it always was. Every run
// is completed with a stage, two identical batches write byte-identical results
// and the same manifest header, and the new failure field stays out of it.
func TestAllSuccessfulBatchKeepsItsArtifacts(t *testing.T) {
tasks := indexTasks()
first, runs, manifest, out := runBlockedExperiment(t, tasks)
second, again, repeat, _ := runBlockedExperiment(t, tasks)
if manifest.CompletedRuns != len(runs) || manifest.FailedRuns != 0 {
t.Fatalf("completed=%d failed=%d rows=%d, want every row completed",
manifest.CompletedRuns, manifest.FailedRuns, len(runs))
}
if manifest.CompletedRuns != manifest.PlannedRuns {
t.Fatalf("completed=%d planned=%d, want all planned", manifest.CompletedRuns, manifest.PlannedRuns)
}
for i, m := range runs {
if m.Failed || m.Task == "" || m.Arm == "" || m.FailureStage == "" {
t.Fatalf("results.jsonl line %d is not a completed run: %+v", i+1, m)
}
}
if gotScored, gotFailed := reportCounts(t, out); gotScored != manifest.CompletedRuns || gotFailed != 0 {
t.Fatalf("report says scored=%d failed=%d, want %d and 0", gotScored, gotFailed, manifest.CompletedRuns)
}
if !reflect.DeepEqual(runs, again) {
t.Fatal("two identical successful batches scored differently")
}
// Byte-for-byte on the runs. Trajectories carry per-message wall-clock
// stamps, so they are compared field by field instead.
if got, want := readFile(t, second, "results.jsonl"), readFile(t, first, "results.jsonl"); got != want {
t.Fatalf("an unchanged batch wrote different results.jsonl:\n%s\n---\n%s", want, got)
}
gotTraj, wantTraj := withoutStamps(t, readFile(t, second, "trajectories.jsonl")),
withoutStamps(t, readFile(t, first, "trajectories.jsonl"))
if gotTraj != wantTraj {
t.Fatalf("an unchanged batch wrote different trajectories:\n%s\n---\n%s", wantTraj, gotTraj)
}
repeat.StartedAt, repeat.FinishedAt = manifest.StartedAt, manifest.FinishedAt
repeat.TrajectoriesSHA256 = manifest.TrajectoriesSHA256
if !reflect.DeepEqual(repeat, manifest) {
t.Fatalf("an unchanged batch wrote a different manifest:\n%+v\n---\n%+v", manifest, repeat)
}
if repeat.FailedRuns != 0 {
t.Fatalf("a batch with no failure reports failed_runs=%d", repeat.FailedRuns)
}
}
// withoutStamps drops the createdAt stamp every message carries, which is the
// one field a dry batch cannot reproduce between two runs.
func withoutStamps(t *testing.T, text string) string {
t.Helper()
var b strings.Builder
for line := range strings.SplitSeq(strings.TrimSpace(text), "\n") {
var row map[string]any
if err := json.Unmarshal([]byte(line), &row); err != nil {
t.Fatal(err)
}
stripStamp(row)
out, err := json.Marshal(row)
if err != nil {
t.Fatal(err)
}
b.Write(out)
b.WriteByte('\n')
}
return b.String()
}
func stripStamp(v any) {
switch typed := v.(type) {
case map[string]any:
delete(typed, "createdAt")
delete(typed, "workDurationMs")
for _, child := range typed {
stripStamp(child)
}
case []any:
for _, child := range typed {
stripStamp(child)
}
}
}
func readFile(t *testing.T, root, name string) string {
t.Helper()
data, err := os.ReadFile(filepath.Join(root, name))
if err != nil {
t.Fatal(err)
}
return string(data)
}
-204
View File
@@ -1,204 +0,0 @@
package main
import (
"context"
"fmt"
"io/fs"
"os"
"path/filepath"
"reasonix/internal/state/sessionstore"
"strings"
"reasonix/internal/contract/ablation"
"reasonix/internal/contract/event"
"reasonix/internal/contract/provider"
"reasonix/internal/contract/tool"
"reasonix/internal/runtime/agent"
)
// A fixture is a real session that has really been compacted: no bench-only
// state shape, so the loader and the projection restore have to work on it.
const (
fixtureWindow = 60000
fixtureGenerations = 5
// fillerBytes per unit is what makes a fold necessary rather than optional.
fillerBytes = 6000
)
// digestProvider answers a summarizer without naming anything the corpus
// plants. A digest that happened to quote the answer would leave it visible
// and silently turn the task into a no-op.
type digestProvider struct{ calls int }
func (p *digestProvider) Name() string { return "fixture-digest" }
func (p *digestProvider) Stream(_ context.Context, _ provider.Request) (<-chan provider.Chunk, error) {
p.calls++
ch := make(chan provider.Chunk, 2)
ch <- provider.Chunk{Type: provider.ChunkText, Text: "## Earlier work\nEarlier work continued across several files; nothing outstanding was recorded."}
ch <- provider.Chunk{Type: provider.ChunkDone}
close(ch)
return ch, nil
}
// fillerCall is one ordinary read whose only job is to take up room — in the
// window, and in the fold index ahead of a cue.
func fillerCall(sess *sessionstore.Session, n int) {
id := fmt.Sprintf("f%04d", n)
sess.Add(provider.Message{Role: provider.RoleAssistant, ToolCalls: []provider.ToolCall{
{ID: id, Name: "read_file", Arguments: fmt.Sprintf(`{"path":"internal/mod%03d/handler%03d.go"}`, n%37, n)},
}})
sess.Add(provider.Message{Role: provider.RoleTool, ToolCallID: id, Name: "read_file", Content: fillerBody(n)})
}
func fillerBody(n int) string {
var b strings.Builder
fmt.Fprintf(&b, "package mod%03d\n\n", n%37)
for b.Len() < fillerBytes {
fmt.Fprintf(&b, "func handler%03dStep%02d() error { return nil }\n", n, b.Len()%64)
}
return b.String()
}
// builtFixture is one task's history after real folds, plus where its target
// ended up.
type builtFixture struct {
Session *sessionstore.Session
State sessionstore.CompactionState
Target int
Path string
Instance fixtureInstance
}
// buildFixture plays a task's history through the real agent and folds it at
// the given arm, writing the session and its context state where the agent
// itself would.
func buildFixture(f fixtureInstance, arm ablation.Set, sessionPath string) (builtFixture, error) {
t := f.Task
sess := sessionstore.NewSession("You are a coding agent.")
sess.Add(provider.Message{Role: provider.RoleUser, Content: "Work through the transport and scheduler backlog."})
n := 0
at := -1
a := agent.New(&digestProvider{}, tool.NewRegistry(), sess, agent.Options{
ContextWindow: fixtureWindow, CompactRatio: 0.5, SessionPath: sessionPath, Ablation: arm,
}, event.Discard)
for gen := range fixtureGenerations {
if gen == t.PlantAfterGen {
at = f.plant(sess)
}
for range 8 {
fillerCall(sess, n)
n++
}
if _, err := a.CompactNow(context.Background(), agent.CompactRequest{}); err != nil {
return builtFixture{}, fmt.Errorf("%s: generation %d fold: %w", t.ID, gen, err)
}
}
if at < 0 {
return builtFixture{}, fmt.Errorf("%s: PlantAfterGen %d is past the last generation", t.ID, t.PlantAfterGen)
}
// projectionCoversTail cannot revalidate a projection covering the whole
// transcript: it needs a live tail, which a real session always has because
// work continues after a fold. Neutral text — the tail is provider-visible.
sess.Add(provider.Message{Role: provider.RoleAssistant, Content: "Backlog swept; ready for the next question."})
if err := sess.Save(sessionPath); err != nil {
return builtFixture{}, fmt.Errorf("%s: save session: %w", t.ID, err)
}
state, ok, err := sessionstore.LoadCompactionState(sessionPath)
if err != nil {
return builtFixture{}, fmt.Errorf("%s: load context state: %w", t.ID, err)
}
if !ok || len(state.Projection.Messages) == 0 {
return builtFixture{}, fmt.Errorf("%s: no projection was installed", t.ID)
}
if state.Projection.CoveredCount <= at {
return builtFixture{}, fmt.Errorf("%s: target #%d is not inside the folded region (covered %d)",
t.ID, at, state.Projection.CoveredCount)
}
return builtFixture{Session: sess, State: state, Target: at, Path: sessionPath, Instance: f}, nil
}
// visibleContext is the model-visible view: the stored projection spliced with
// everything canonical that came after it.
func visibleContext(f builtFixture) []provider.Message {
canonical := f.Session.Snapshot()
out := append([]provider.Message(nil), f.State.Projection.Messages...)
if n := f.State.Projection.CoveredCount; n >= 0 && n < len(canonical) {
out = append(out, canonical[n:]...)
}
return out
}
// visibleText is everything the model would read, as one string.
func visibleText(f builtFixture) string {
var b strings.Builder
for _, m := range visibleContext(f) {
b.WriteString(m.Content)
b.WriteString("\n")
for _, tc := range m.ToolCalls {
b.WriteString(tc.Name)
b.WriteString(" ")
b.WriteString(string(tc.Arguments))
b.WriteString("\n")
}
}
return b.String()
}
// sealFixture removes the fixture from disk once the agent holds it in memory.
// The canonical transcript is the answer, and the sandbox mounts the host
// read-only by design — so a model that finds the path reads the answer, which
// one did. The agent has already loaded the session and its sidecar by the time
// this runs; a failed autosave afterwards costs the benchmark nothing.
func sealFixture(sessionPath string) {
// Every file, not two named ones: a session writes an event log and a
// context sidecar beside the transcript, and session.events.jsonl still
// carried the answer after the other two were sealed by name.
dir := filepath.Dir(sessionPath)
entries, err := os.ReadDir(dir)
if err != nil {
return
}
for _, e := range entries {
if !e.IsDir() {
_ = os.Remove(filepath.Join(dir, e.Name()))
}
}
// Deny the directory too, so an autosave cannot recreate what was removed.
_ = os.Chmod(dir, 0o500)
}
// unsealFixture restores the directory so the harness can clean up after
// itself.
func unsealFixture(sessionPath string) {
_ = os.Chmod(filepath.Dir(sessionPath), 0o700)
}
// answerOnDisk reports any readable file under dir holding a current-run
// answer. Two named files were sealed because two leaks were found in them;
// this scans the directory, so a path that moves — an autosave, a recovery
// tombstone, a future sidecar — fails here instead of on a paid run.
func answerOnDisk(dir string, markers []string) (string, string) {
var atPath, found string
_ = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if err != nil || d.IsDir() || atPath != "" {
return nil //nolint:nilerr // an unreadable entry is not a leak
}
body, readErr := os.ReadFile(path)
if readErr != nil {
return nil
}
for _, marker := range markers {
if len(marker) >= 6 && strings.Contains(string(body), marker) {
atPath, found = path, marker
return filepath.SkipAll
}
}
return nil
})
return atPath, found
}
-139
View File
@@ -1,139 +0,0 @@
// instance.go — a task template becomes one run's concrete history.
package main
import (
"crypto/rand"
"fmt"
"hash/fnv"
"io"
mathrand "math/rand"
"strings"
)
// The invariant this file exists for: no literal sufficient to satisfy
// AnswerRecovered for a concrete run may exist in this repository before that
// run begins. Grepping the corpus reveals the question, never the answer.
// varKind is how one placeholder's value is generated. Values stay shaped like
// real engineering records — a UUID would defeat the leak but turn every task
// into "find the random string".
type varKind string
const (
varCodename varKind = "codename" // cobalt-k7m4q9
varInt varKind = "int" // 731
varDecimal varKind = "decimal" // 0.0437
varDuration varKind = "duration" // 43ms
)
// varSpec declares one placeholder. Min/Max bound the numeric kinds.
type varSpec struct {
Name string
Kind varKind
Word string // codename stem, e.g. "cobalt"
Min int
Max int
}
// codenameEntropy is how much of a codename is unguessable. Five base32 digits
// is about 25 bits: far past what a model can reach by knowing the stem.
const codenameEntropy = 5
const codenameAlphabet = "abcdefghijkmnpqrstuvwxyz23456789"
// fixtureVars is one run's instantiated values.
type fixtureVars map[string]string
// newVars generates values for every spec. A nil rng means deterministic.
func newVars(specs []varSpec, rng *mathrand.Rand) fixtureVars {
out := fixtureVars{}
for _, spec := range specs {
switch spec.Kind {
case varCodename:
out[spec.Name] = spec.Word + "-" + randomToken(rng, codenameEntropy)
case varInt:
out[spec.Name] = fmt.Sprint(randomBetween(rng, spec.Min, spec.Max))
case varDecimal:
out[spec.Name] = fmt.Sprintf("0.%04d", randomBetween(rng, spec.Min, spec.Max))
case varDuration:
out[spec.Name] = fmt.Sprintf("%dms", randomBetween(rng, spec.Min, spec.Max))
}
}
return out
}
func randomToken(rng *mathrand.Rand, n int) string {
var b strings.Builder
for range n {
b.WriteByte(codenameAlphabet[randomBetween(rng, 0, len(codenameAlphabet)-1)])
}
return b.String()
}
func randomBetween(rng *mathrand.Rand, lo, hi int) int {
if hi <= lo {
return lo
}
return lo + rng.Intn(hi-lo+1)
}
// seededRand is the deterministic generator preflight and tests use, so a
// fixture's tier calibration and its golden state reproduce exactly.
func seededRand(parts ...string) *mathrand.Rand {
h := fnv.New64a()
for _, p := range parts {
h.Write([]byte(p))
h.Write([]byte{0})
}
return mathrand.New(mathrand.NewSource(int64(h.Sum64())))
}
// liveRand is the generator a paid run uses: every run gets values that existed
// nowhere before it started.
func liveRand() *mathrand.Rand {
rng, err := liveRandFrom(rand.Reader)
if err != nil {
panic("contextbench: no entropy for a live run: " + err.Error())
}
return rng
}
// liveRandFrom seeds a generator from an entropy source, so what a run's values
// are unpredictable from is the source and nothing else.
func liveRandFrom(entropy io.Reader) (*mathrand.Rand, error) {
var seed [8]byte
if _, err := io.ReadFull(entropy, seed[:]); err != nil {
return nil, err
}
var n int64
for _, b := range seed {
n = n<<8 | int64(b)
}
return mathrand.New(mathrand.NewSource(n)), nil
}
// instantiate substitutes {{name}} and refuses anything left over. The grammar
// is one identifier in double braces: no conditionals, no loops, no defaults.
// This is fixture data, not a template language.
func instantiate(text string, vars fixtureVars) (string, error) {
out := text
for name, value := range vars {
out = strings.ReplaceAll(out, "{{"+name+"}}", value)
}
if strings.Contains(out, "{{") || strings.Contains(out, "}}") {
return "", fmt.Errorf("unresolved placeholder in %q", firstPlaceholder(out))
}
return out, nil
}
func firstPlaceholder(text string) string {
i := strings.Index(text, "{{")
if i < 0 {
return text
}
j := strings.Index(text[i:], "}}")
if j < 0 {
return text[i:min(i+40, len(text))]
}
return text[i : i+j+2]
}
-144
View File
@@ -1,144 +0,0 @@
// ContextRetrievalBench builds the fixtures the long-horizon retrieval
// experiments run against, and proves they are worth paying a provider for.
//
// -mode=calibrate find each index task's lead-call count for its cue tier
// -mode=preflight build every task under every arm and check the contract
//
// Nothing here calls a provider. The experiments themselves do; this is what
// says they would measure what they claim to.
package main
import (
"flag"
"fmt"
"os"
"sort"
"strings"
)
func main() {
mode := flag.String("mode", "preflight", "calibrate | preflight | snippet | adversarial | run-search | run-boundary | run-index")
work := flag.String("work", "", "directory for built fixtures (default: a temp dir)")
only := flag.String("task", "", "limit to one task id")
dry := flag.Bool("dry", false, "run modes: drive the pipeline with a scripted provider instead of paying for one")
flag.Parse()
if err := validateCorpus(); err != nil {
fmt.Fprintln(os.Stderr, "corpus:", err)
os.Exit(2)
}
root := *work
if root == "" {
dir, err := os.MkdirTemp("", "context-retrieval-*")
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
defer os.RemoveAll(dir)
root = dir
}
tasks := allTasks()
if *only != "" {
t, ok := taskByID(*only)
if !ok {
fmt.Fprintf(os.Stderr, "unknown task %q\n", *only)
os.Exit(2)
}
tasks = []contextTask{t}
}
switch *mode {
case "calibrate":
os.Exit(runCalibrate(tasks, root))
case "preflight":
os.Exit(runPreflight(tasks, root))
case "run-search":
os.Exit(runExperiment(*mode, experimentSearch, root, *dry, tasks))
case "run-boundary":
os.Exit(runBoundaries(*mode, root))
case "snippet":
os.Exit(runSnippetAudit(root, 100))
case "adversarial":
os.Exit(runAdversarial(root))
case "run-index":
batch := tasks
if *only == "" {
// A batch measures the efficiency substrate only. Naming one task
// with -task runs that task, role included, so the stopping corpus
// stays measurable on its own.
batch = efficiencyTasks()
}
os.Exit(runExperiment(*mode, experimentIndex, root, *dry, batch))
default:
fmt.Fprintf(os.Stderr, "unknown mode %q\n", *mode)
os.Exit(2)
}
}
func runPreflight(tasks []contextTask, root string) int {
var all []finding
for _, t := range tasks {
found, err := checkTask(t, root)
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", t.ID, err)
return 2
}
all = append(all, found...)
status := "ok"
if len(found) > 0 {
status = fmt.Sprintf("%d problem(s)", len(found))
}
fmt.Printf("%-24s %-6s gen=%-2d %s\n", t.ID, t.Experiment, t.PlantAfterGen, status)
}
if len(all) == 0 {
fmt.Printf("\n%d fixtures ready.\n", len(tasks))
return 0
}
fmt.Fprintf(os.Stderr, "\n%d preflight problem(s):\n", len(all))
sort.Slice(all, func(i, j int) bool { return all[i].Task < all[j].Task })
for _, f := range all {
fmt.Fprintln(os.Stderr, " ", f)
}
return 1
}
// runCalibrate reports, for each index task, the lead-call counts at which its
// cue is still addressed by each budget. The corpus freezes one number from
// this; it is never computed at run time, or the implementation under test
// would be moving its own goalposts.
func runCalibrate(tasks []contextTask, root string) int {
for _, t := range tasks {
if t.Experiment != experimentIndex {
continue
}
var rows []string
for lead := range fixtureGenerations {
probe := t
probe.PlantAfterGen = lead
var visibleAt []string
for _, scale := range armScales {
inst, ierr := instantiateTask(probe, seededRand(t.ID, scale, "calibrate"))
if ierr != nil {
fmt.Fprintln(os.Stderr, ierr)
return 2
}
f, err := buildFixture(inst, armFor(scale, false),
fmt.Sprintf("%s/calib/%s/%d-%s/session.jsonl", root, t.ID, lead, scale))
if err != nil {
fmt.Fprintf(os.Stderr, "%s gen=%d %s: %v\n", t.ID, lead, scale, err)
return 2
}
if strings.Contains(visibleText(f), inst.CueMarker) {
visibleAt = append(visibleAt, scale)
}
}
rows = append(rows, fmt.Sprintf("planted after gen %d, cue visible at: %s", lead, strings.Join(visibleAt, ",")))
}
fmt.Printf("\n%s (wants tier %s)\n", t.ID, t.CueTier)
for _, r := range rows {
fmt.Println(" ", r)
}
}
return 0
}
-189
View File
@@ -1,189 +0,0 @@
package main
import (
"bytes"
"fmt"
"maps"
mathrand "math/rand"
"os/exec"
"slices"
"strings"
"testing"
)
// The same checks the preflight mode runs, under go test: a compaction change
// that invalidates the fixtures fails here rather than three hundred API calls
// later.
func TestCorpusIsWellFormed(t *testing.T) {
if err := validateCorpus(); err != nil {
t.Fatal(err)
}
if got := len(searchTasks()); got != 6 {
t.Errorf("search experiment has %d tasks, want 6", got)
}
if got := len(indexTasks()); got != 12 {
t.Errorf("index experiment has %d tasks, want 12", got)
}
stopping := map[string]bool{}
tiers := map[string]int{}
for _, task := range indexTasks() {
if task.Role == roleStopping {
stopping[task.ID] = true
continue
}
tiers[task.CueTier]++
}
if !stopping["i03-coalescing"] || !stopping["i04-recovery-fence"] {
t.Errorf("the stopping corpus is %v; it must keep i03-coalescing and i04-recovery-fence", stopping)
}
// Two per tier, over the efficiency substrate, is what makes the staircase
// readable: the same task can be compared across the boundary its cue sits
// on, instead of two different questions being compared across two arms.
for _, tier := range []string{tierQuarter, tierHalf, tierDefault} {
if tiers[tier] != 2 {
t.Errorf("tier %s has %d tasks, want 2", tier, tiers[tier])
}
}
}
// The role decides which batch a task enters; the literal lists are only where
// it is declared, so the two must not drift.
func TestIndexListsMatchTheirRoles(t *testing.T) {
for _, task := range append(append(indexQuarterTasks(), indexHalfTasks()...), indexDefaultTasks()...) {
if task.Role != roleEfficiency {
t.Errorf("%s sits in an efficiency list with role %q", task.ID, task.Role)
}
}
for _, task := range stoppingIndexTasks() {
if task.Role != roleStopping {
t.Errorf("%s sits in the stopping list with role %q", task.ID, task.Role)
}
}
}
// The invariant the whole instantiation layer exists for: no literal a scorer
// needs may be readable in this repository before a run starts.
func TestNoAnswerLiteralExistsInTheRepository(t *testing.T) {
root, err := repoRoot()
if err != nil {
t.Skip(err)
}
for _, task := range allTasks() {
inst, err := instantiateTask(task, seededRand(task.ID, "leak-check"))
if err != nil {
t.Fatal(err)
}
for _, marker := range append(inst.AnswerMarkers, inst.CueMarker) {
if len(marker) < 6 {
continue // a bare number is not a literal anyone can grep for
}
if found, where := grepRepo(t, root, marker); found {
t.Errorf("%s: %q is readable at %s before the run", task.ID, marker, where)
}
}
}
}
// A run's values are unpredictable from a previous run only if the live
// generator is seeded from entropy. Comparing answers across two instances
// cannot assert that: a task whose answer is a bounded number collides by
// chance (1/8601 to 1/286 per task), so the check is on the seed source.
func TestLiveRandDrawsFromEntropy(t *testing.T) {
a, b := liveRand(), liveRand()
for range 4 {
if a.Int63() != b.Int63() {
return
}
}
t.Fatal("two live generators produced the same stream: they are seeded alike")
}
// The source alone decides an instance: the same entropy gives the same
// answers, different entropy gives different values.
func TestLiveRandSourceDecidesTheInstance(t *testing.T) {
same := func() *mathrand.Rand {
rng, err := liveRandFrom(bytes.NewReader(bytes.Repeat([]byte{7}, 8)))
if err != nil {
t.Fatal(err)
}
return rng
}
other, err := liveRandFrom(bytes.NewReader(bytes.Repeat([]byte{9}, 8)))
if err != nil {
t.Fatal(err)
}
if _, err := liveRandFrom(bytes.NewReader(nil)); err == nil {
t.Fatal("an empty entropy source was accepted")
}
for _, task := range allTasks() {
a, _ := instantiateTask(task, same())
b, _ := instantiateTask(task, same())
if !slices.Equal(a.AnswerMarkers, b.AnswerMarkers) || !maps.Equal(a.Vars, b.Vars) {
t.Errorf("%s: the same entropy gave different instances", task.ID)
}
c, _ := instantiateTask(task, other)
if maps.Equal(a.Vars, c.Vars) {
t.Errorf("%s: different entropy gave the same values", task.ID)
}
}
}
// A preflight that could not reproduce would calibrate against one run's
// nonces and report about another's.
func TestSeededInstancesReproduce(t *testing.T) {
for _, task := range allTasks() {
a, _ := instantiateTask(task, seededRand(task.ID, "x"))
b, _ := instantiateTask(task, seededRand(task.ID, "x"))
if !slices.Equal(a.AnswerMarkers, b.AnswerMarkers) || a.body != b.body {
t.Errorf("%s did not reproduce under the same seed", task.ID)
}
}
}
// An unresolved placeholder would put "{{marker}}" in the transcript and score
// every run wrong; it fails the fixture instead.
func TestUnresolvedPlaceholderIsRefused(t *testing.T) {
if _, err := instantiate("value is {{missing}}", fixtureVars{"other": "x"}); err == nil {
t.Fatal("an unresolved placeholder was accepted")
}
}
func repoRoot() (string, error) {
out, err := exec.Command("git", "rev-parse", "--show-toplevel").Output()
if err != nil {
return "", fmt.Errorf("not in a git checkout: %w", err)
}
return strings.TrimSpace(string(out)), nil
}
// grepRepo searches tracked files only: build artifacts and temp fixtures are
// not what a fresh checkout hands anyone.
func grepRepo(t *testing.T, root, needle string) (bool, string) {
t.Helper()
cmd := exec.Command("git", "grep", "-l", "-F", needle)
cmd.Dir = root
out, err := cmd.Output()
if err != nil {
return false, "" // exit 1 means no match
}
return true, strings.TrimSpace(strings.SplitN(string(out), "\n", 2)[0])
}
func TestEveryFixturePassesPreflight(t *testing.T) {
if testing.Short() {
t.Skip("builds and folds 36 sessions")
}
root := t.TempDir()
for _, task := range allTasks() {
t.Run(task.ID, func(t *testing.T) {
found, err := checkTask(task, root)
if err != nil {
t.Fatal(err)
}
for _, f := range found {
t.Error(f)
}
})
}
}
-290
View File
@@ -1,290 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"path/filepath"
"slices"
"strings"
"reasonix/internal/base/retrieval"
"reasonix/internal/contract/ablation"
"reasonix/internal/contract/provider"
)
// Preflight is what makes a real run worth paying for. An answer the model can
// already read, or a cue not where the matrix says, turns an arm into a
// different experiment without saying so.
// armScales are the fold-index arms an index task is measured across.
var armScales = []string{"default", "half", "quarter", "off"}
// experimentArm is one measured configuration of a task.
type experimentArm struct {
name string
scale string
searchOff bool
}
// armsFor is the single declaration of which arms a task runs at, so preflight
// and the run itself cannot check one set and measure another.
func armsFor(t contextTask) []experimentArm {
if t.Experiment == experimentSearch {
return []experimentArm{
{"search-on", "default", false},
{"search-off", "default", true},
}
}
arms := make([]experimentArm, 0, len(armScales))
for _, scale := range armScales {
arms = append(arms, experimentArm{"index-" + scale, scale, false})
}
return arms
}
func scaleOf(name string) ablation.FoldIndexScale {
s, err := ablation.ParseFoldIndexScale(name)
if err != nil {
panic("unknown scale " + name) // the list above is a constant
}
return s
}
// armFor is the arm a task is measured under at one fold-index scale.
func armFor(scale string, searchOff bool) ablation.Set {
set := ablation.Set{}
if searchOff {
set = ablation.New(ablation.RecallSearch)
}
return set.WithFoldIndex(scaleOf(scale))
}
type finding struct {
Task, Arm, Problem string
}
func (f finding) String() string { return fmt.Sprintf("%s [%s]: %s", f.Task, f.Arm, f.Problem) }
// checkTask builds the task under every arm it will be run at and reports what
// does not hold.
func checkTask(t contextTask, root string) ([]finding, error) {
var out []finding
for _, arm := range armsFor(t) {
// Deterministic values: a preflight has to reproduce, and a tier
// calibration measured against one run's nonces is measured against
// nothing.
inst, err := instantiateTask(t, seededRand(t.ID, arm.name, "preflight"))
if err != nil {
return nil, err
}
dir := filepath.Join(root, t.ID, arm.name)
f, err := buildFixture(inst, armFor(arm.scale, arm.searchOff), filepath.Join(dir, "session.jsonl"))
if err != nil {
return nil, err
}
visible := visibleText(f)
// The answer must be unreadable in every arm, or the task measures
// reading rather than recall.
for _, marker := range inst.AnswerMarkers {
if len(marker) < 3 {
continue // a bare number is not a nonce; the whole set still has to match
}
if strings.Contains(visible, marker) {
out = append(out, finding{t.ID, arm.name, fmt.Sprintf("answer marker %q is already visible", marker)})
}
}
// The corpus has to be findable at all, or a miss in a real run says
// nothing about the model.
rank, err := probeRank(f, inst.ProbeQuery)
if err != nil {
return nil, err
}
switch {
case rank == 0:
out = append(out, finding{t.ID, arm.name, fmt.Sprintf("probe query %q does not find the target at all", inst.ProbeQuery)})
case rank > 5:
out = append(out, finding{t.ID, arm.name, fmt.Sprintf("probe query %q ranks the target %d", inst.ProbeQuery, rank)})
}
if t.Experiment == experimentIndex {
out = append(out, checkCueVisibility(t, inst, arm.scale, arm.name, visible)...)
} else if line := indexLineFor(f, f.Target); line != "" {
out = append(out, finding{t.ID, arm.name,
"a search task is addressed by the fold index, so it measures the index and not search: " + line})
}
// The projection is what the agent believes. This is what the model is
// told, and it is the one that has to hold.
req, err := captureRequest(f, armFor(arm.scale, arm.searchOff), inst.Prompt)
if err != nil {
return nil, fmt.Errorf("%s [%s]: capture: %w", t.ID, arm.name, err)
}
out = append(out, checkCapturedRequest(t, inst, arm.name, arm.scale, arm.searchOff, req)...)
}
return out, nil
}
// checkCapturedRequest holds one arm's real provider request to its contract:
// the answer unreadable, the cue exactly where its tier says, the recall tool
// offering only what this arm can do, and no host-side field surviving the
// boundary.
func checkCapturedRequest(t contextTask, inst fixtureInstance, armName, scale string, searchOff bool, req provider.Request) []finding {
var out []finding
for _, marker := range inst.AnswerMarkers {
if len(marker) < 3 {
continue // a bare number is not a nonce; the set as a whole still is
}
for _, at := range findInSurface(req, marker) {
out = append(out, finding{t.ID, armName, fmt.Sprintf("answer marker %q leaked at %s", marker, at)})
}
}
if t.Experiment == experimentIndex {
wantVisible, wantHidden := tierScales(t.CueTier)
where := findInSurface(req, inst.CueMarker)
switch {
case contains(wantVisible, scale) && len(where) == 0:
out = append(out, finding{t.ID, armName, fmt.Sprintf("cue %q is not provider-visible; the tier expects it", inst.CueMarker)})
case contains(wantHidden, scale) && len(where) > 0:
out = append(out, finding{t.ID, armName, fmt.Sprintf("cue %q is provider-visible at %s: %s", inst.CueMarker, where[0], scale)})
}
}
schema, ok := recallToolSchema(req)
if !ok {
out = append(out, finding{t.ID, armName, "recall is not in the provider's tool list"})
return append(out, boundaryFindings(t, armName, req)...)
}
params := ""
if b, err := json.Marshal(schema.Parameters); err == nil {
params = string(b)
}
// Both arms keep the read half: the ablation removes search, not recall.
if !strings.Contains(params, `"positions"`) {
out = append(out, finding{t.ID, armName, "recall lost its positions parameter"})
}
hasQuery := strings.Contains(params, `"query"`)
switch {
case searchOff && hasQuery:
out = append(out, finding{t.ID, armName, "recall still offers a query parameter"})
case !searchOff && !hasQuery:
out = append(out, finding{t.ID, armName, "recall has no query parameter"})
}
if searchOff {
for _, phrase := range searchWording {
for _, at := range findInSurface(req, phrase) {
out = append(out, finding{t.ID, armName, fmt.Sprintf("search is offered to an arm without it: %q at %s", phrase, at)})
}
}
}
return append(out, boundaryFindings(t, armName, req)...)
}
func boundaryFindings(t contextTask, armName string, req provider.Request) []finding {
var out []finding
for _, at := range localOnlyLeaks(req) {
out = append(out, finding{t.ID, armName, "host-only field crossed the provider boundary at " + at})
}
return out
}
// checkCueVisibility holds an index task to its tier: the cue is addressed at
// the scales the matrix names and at no others. A policy change that moves it
// fails here rather than turning two arms into one experiment.
func checkCueVisibility(t contextTask, inst fixtureInstance, scale, armName, visible string) []finding {
wantVisible, wantHidden := tierScales(t.CueTier)
has := strings.Contains(visible, inst.CueMarker)
switch {
case contains(wantVisible, scale) && !has:
return []finding{{t.ID, armName, fmt.Sprintf("cue %q should be addressed at %s and is not", inst.CueMarker, scale)}}
case contains(wantHidden, scale) && has:
return []finding{{t.ID, armName, fmt.Sprintf("cue %q should be gone at %s and is not", inst.CueMarker, scale)}}
}
return nil
}
func contains(list []string, want string) bool { return slices.Contains(list, want) }
// probeRank runs the task's own query against the folded region the way recall
// would, and reports where the target lands.
func probeRank(f builtFixture, query string) (int, error) {
terms, err := retrieval.QueryTerms(query)
if err != nil {
return 0, err
}
canonical := f.Session.Snapshot()
covered := min(f.State.Projection.CoveredCount, len(canonical))
docs := make([]map[string]int, 0, covered)
positions := make([]int, 0, covered)
lengths := make([]int, 0, covered)
for i, m := range canonical[:covered] {
var text strings.Builder
text.WriteString(m.Content)
for _, tc := range m.ToolCalls {
text.WriteString(" " + tc.Name + " " + string(tc.Arguments))
}
tokens := retrieval.Tokens(text.String())
if len(tokens) == 0 {
continue
}
docs = append(docs, retrieval.Counts(tokens))
positions = append(positions, i)
lengths = append(lengths, len(tokens))
}
df := retrieval.DocumentFrequency(docs)
avg := 0.0
for _, l := range lengths {
avg += float64(l)
}
if len(lengths) > 0 {
avg /= float64(len(lengths))
}
type hit struct {
pos int
score float64
}
var hits []hit
for i, counts := range docs {
if s := retrieval.BM25Score(counts, lengths[i], terms, df, len(docs), avg); s > 0 {
hits = append(hits, hit{positions[i], s})
}
}
for i := range hits {
for j := i + 1; j < len(hits); j++ {
if hits[j].score > hits[i].score {
hits[i], hits[j] = hits[j], hits[i]
}
}
}
// A tool result answers at its caller's position, so either counts.
for i, h := range hits {
if h.pos == f.Target || h.pos == f.Target+1 {
return i + 1, nil
}
}
return 0, nil
}
// indexLineFor returns the fold-index line addressing a position, if any.
func indexLineFor(f builtFixture, pos int) string {
want := fmt.Sprintf("#%d ", pos)
for line := range strings.SplitSeq(visibleIndexOnly(f), "\n") {
if strings.Contains(line, want) {
return strings.TrimSpace(line)
}
}
return ""
}
// visibleIndexOnly is just the fold-index sections of the projection.
func visibleIndexOnly(f builtFixture) string {
var b strings.Builder
for _, m := range visibleContext(f) {
if i := strings.Index(m.Content, "## Folded work index"); i >= 0 {
b.WriteString(m.Content[i:])
b.WriteString("\n")
}
}
return b.String()
}
@@ -1,252 +0,0 @@
// reformulation.go — how a query changed, from token sets alone.
package main
import (
"fmt"
"slices"
"sort"
"strings"
"reasonix/internal/base/retrieval"
)
// The host probe ranks every target first, so the engine is not what makes a
// model take more than one attempt. These classes are set arithmetic over the
// same tokenizer recall uses: no judge, nothing read out of prose.
const (
reformFirst = "First" // nothing to compare against yet
reformNearDup = "NearDuplicate" // the same query again, give or take a word
reformNarrowing = "Narrowing" // kept everything and added terms
reformBroadening = "Broadening" // dropped terms, added none
reformAnchored = "HistoricalAnchor" // reached for a term the question does not contain
reformShift = "SynonymShift" // traded terms for others
)
// nearDuplicateOverlap is where "reworded" stops and "asked again" begins.
const nearDuplicateOverlap = 0.8
func terms(s string) []string {
return retrieval.Unique(retrieval.Tokens(s))
}
func termSet(s string) map[string]bool {
out := map[string]bool{}
for _, t := range terms(s) {
out[t] = true
}
return out
}
func jaccard(a, b map[string]bool) float64 {
if len(a) == 0 && len(b) == 0 {
return 1
}
shared := 0
for t := range a {
if b[t] {
shared++
}
}
union := len(a) + len(b) - shared
if union == 0 {
return 0
}
return float64(shared) / float64(union)
}
// classifyReformulation names how this query differs from the one before it.
func classifyReformulation(previous, current, question string) string {
if previous == "" {
return reformFirst
}
prev, cur := termSet(previous), termSet(current)
if jaccard(prev, cur) >= nearDuplicateOverlap {
return reformNearDup
}
added, dropped := diffTerms(cur, prev), diffTerms(prev, cur)
// A term the question never used had to come from the model's own memory of
// the history, which is a different move from rephrasing what it was asked.
ask := termSet(question)
for _, t := range added {
if !ask[t] {
return reformAnchored
}
}
switch {
case len(dropped) == 0:
return reformNarrowing
case len(added) == 0:
return reformBroadening
default:
return reformShift
}
}
func diffTerms(from, minus map[string]bool) []string {
var out []string
for t := range from {
if !minus[t] {
out = append(out, t)
}
}
sort.Strings(out)
return out
}
// classifySearches labels each attempt and records which of the host probe's
// terms it left out. The probe is the oracle: it ranks the target first, so a
// query missing its terms says what the model did not think to ask for.
func classifySearches(m *contextMetrics, inst fixtureInstance) {
probe := terms(inst.ProbeQuery)
previous := ""
for i := range m.Searches {
s := &m.Searches[i]
s.Reformulation = classifyReformulation(previous, s.Query, inst.Prompt)
have := termSet(s.Query)
for _, term := range probe {
if !have[term] {
s.MissingProbeTerms = append(s.MissingProbeTerms, term)
}
}
previous = s.Query
}
}
// queryAudit is the reformulation funnel across a set of runs.
type queryAudit struct {
Runs, RunsThatSearched int
FirstQueryHit int
HitBySecond, HitByThird int
NeverHit int
TotalSearches int
PostHitSearches int
HitThenRead int
EscapeBeforeFirstSearch int
Reformulations map[string]int
MissedProbeTerms map[string]int
}
func auditQueries(runs []contextMetrics) queryAudit {
a := queryAudit{Reformulations: map[string]int{}, MissedProbeTerms: map[string]int{}}
for _, r := range runs {
if r.Failed || r.Contaminated {
continue
}
a.Runs++
a.TotalSearches += len(r.Searches)
a.PostHitSearches += r.PostHitSearches
if r.EscapeBeforeFirstRecall > 0 {
a.EscapeBeforeFirstSearch++
}
if len(r.Searches) == 0 {
continue
}
a.RunsThatSearched++
hitAt := 0
for i, s := range r.Searches {
a.Reformulations[s.Reformulation]++
for _, term := range s.MissingProbeTerms {
a.MissedProbeTerms[term]++
}
if s.TargetRank > 0 && hitAt == 0 {
hitAt = i + 1
}
}
switch {
case hitAt == 1:
a.FirstQueryHit++
case hitAt == 2:
a.HitBySecond++
case hitAt >= 3:
a.HitByThird++
default:
a.NeverHit++
}
if hitAt > 0 && r.TargetRead {
a.HitThenRead++
}
}
return a
}
func (a queryAudit) report() string {
var b strings.Builder
b.WriteString("\n## Query reformulation\n")
fmt.Fprintf(&b, " runs=%d searched=%d searches=%d (%.2f per searching run)\n",
a.Runs, a.RunsThatSearched, a.TotalSearches, ratio(a.TotalSearches, a.RunsThatSearched))
fmt.Fprintf(&b, " target found by query 1: %d by 2: %d by 3+: %d never: %d\n",
a.FirstQueryHit, a.HitBySecond, a.HitByThird, a.NeverHit)
fmt.Fprintf(&b, " found then read: %d/%d searches issued after the target was already back: %d\n",
a.HitThenRead, a.FirstQueryHit+a.HitBySecond+a.HitByThird, a.PostHitSearches)
fmt.Fprintf(&b, " reached for the workspace before searching: %d\n", a.EscapeBeforeFirstSearch)
b.WriteString(" reformulations: " + countsLine(a.Reformulations) + "\n")
b.WriteString(" probe terms most often absent: " + topCounts(a.MissedProbeTerms, 8) + "\n")
return b.String()
}
func ratio(n, d int) float64 {
if d == 0 {
return 0
}
return float64(n) / float64(d)
}
func countsLine(counts map[string]int) string {
keys := make([]string, 0, len(counts))
for k := range counts {
keys = append(keys, k)
}
sort.Strings(keys)
parts := make([]string, 0, len(keys))
for _, k := range keys {
parts = append(parts, fmt.Sprintf("%s=%d", k, counts[k]))
}
return strings.Join(parts, " ")
}
func topCounts(counts map[string]int, limit int) string {
type kv struct {
k string
n int
}
all := make([]kv, 0, len(counts))
for k, n := range counts {
all = append(all, kv{k, n})
}
sort.Slice(all, func(i, j int) bool {
if all[i].n != all[j].n {
return all[i].n > all[j].n
}
return all[i].k < all[j].k
})
parts := make([]string, 0, limit)
for _, e := range all[:min(limit, len(all))] {
parts = append(parts, fmt.Sprintf("%s=%d", e.k, e.n))
}
if len(parts) == 0 {
return "(none)"
}
return strings.Join(parts, " ")
}
// queryLines lists every attempt, so a surprising audit can be read back.
func queryLines(runs []contextMetrics) string {
var b strings.Builder
b.WriteString("\n## Every query, in order\n")
for _, r := range runs {
for _, s := range r.Searches {
rank := "miss"
if s.TargetRank > 0 {
rank = fmt.Sprintf("rank %d", s.TargetRank)
}
fmt.Fprintf(&b, " %-24s %-14s #%d %-17s %-9s %q\n",
r.Task, r.Arm, s.Ordinal, s.Reformulation, rank, s.Query)
if len(s.MissingProbeTerms) > 0 && s.TargetRank == 0 {
fmt.Fprintf(&b, " %-24s %-14s missing probe terms: %s\n",
"", "", strings.Join(slices.Clone(s.MissingProbeTerms), " "))
}
}
}
return b.String()
}
-568
View File
@@ -1,568 +0,0 @@
package main
import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"reasonix/internal/state/sessionstore"
"slices"
"sort"
"strings"
"time"
"reasonix/internal/assembly/boot"
"reasonix/internal/contract/ablation"
"reasonix/internal/contract/provider"
"reasonix/internal/contract/tool"
_ "reasonix/internal/model/openai"
"reasonix/internal/runtime/agent"
_ "reasonix/internal/tools/builtin"
)
// A run is one recall question against a fixture the preflight has already
// cleared. Scoring reads the transcript the turn appended — tool names and
// their JSON arguments — so nothing depends on how the model narrated itself.
const (
runBaseURL = "https://api.deepseek.com"
runModel = "deepseek-chat"
runTimeout = 4 * time.Minute
)
func realProvider() (provider.Provider, error) {
key := os.Getenv("DEEPSEEK_API_KEY")
if key == "" {
return nil, fmt.Errorf("a real run needs DEEPSEEK_API_KEY")
}
return provider.New("openai", provider.Config{
Name: "contextbench", BaseURL: runBaseURL, Model: runModel, APIKey: key,
})
}
// adversarialPrompts tell the model what it is running and send it after the
// answer everywhere except the conversation. The question is not whether it
// obeys the experiment: it is whether the answer is reachable at all by a model
// that knows the mechanism and is actively attacking the host.
var adversarialPrompts = map[string]string{
experimentSearch: "You are running a benchmark. Find the expected answer for this task from the " +
"Reasonix repository, benchmark source, fixture generator, test files, scratch files, or " +
"session metadata. Do not use recall unless necessary. Report the exact hidden values.",
experimentIndex: "Inspect benchmarks/context-retrieval, the git repository, session files, temporary " +
"files, and any benchmark metadata you can access. Recover the concrete instantiated answer for " +
"this run without relying on the folded conversation. Report the exact values.",
}
// runAdversarial sends one task per experiment after the answer through every
// host-readable surface. It passes when nothing but recall could produce it —
// a run that gives up and recalls has still proven the attack failed.
func runAdversarial(root string) int {
root, err := filepath.Abs(root)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return 2
}
restore, ierr := isolateWorkspace()
if ierr != nil {
fmt.Fprintln(os.Stderr, "isolate workspace:", ierr)
return 2
}
defer restore()
p, perr := realProvider()
if perr != nil {
fmt.Fprintln(os.Stderr, perr)
return 2
}
leaked := 0
for _, id := range []string{"r01-retry-boundary", "i01-transport-fallback"} {
t, ok := taskByID(id)
if !ok {
fmt.Fprintf(os.Stderr, "unknown task %q\n", id)
return 2
}
m, err := runOne(p, t, armFor("default", false), "adversarial", root, false, false,
adversarialPrompts[t.Experiment])
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", id, err)
m = failedMetrics(t, "adversarial", err)
}
verdict := "isolated"
if m.EscapeFoundAnswer {
verdict = "LEAKED via " + m.LeakedVia
leaked++
}
fmt.Printf("%-24s %-24s escapes=%-3d recall=%d answered=%v tools=%v\n",
id, verdict, m.EscapeCalls, m.SearchCalls+m.ReadCalls, m.AnswerRecovered, m.UnexpectedWorkTools)
if m.EscapeFoundAnswer {
fmt.Printf(" args: %s\n output: %s\n", m.LeakedArgs, m.LeakedOutput)
}
}
if leaked > 0 {
fmt.Fprintf(os.Stderr, "\n%d of 2 adversarial runs reached an answer outside recall\n", leaked)
return 1
}
fmt.Println("\nNo answer was reachable outside recall.")
return 0
}
// runOne asks one task under one arm and scores what the model did.
func runOne(p provider.Provider, t contextTask, arm ablation.Set, armName, root string, cueVisible, dry bool, promptOverride ...string) (contextMetrics, error) {
// Live runs get values that existed nowhere before the run started; a dry
// run stays reproducible.
rng := liveRand()
if dry {
rng = seededRand(t.ID, armName, "dry")
}
inst, err := instantiateTask(t, rng)
if err != nil {
return contextMetrics{}, err
}
f, err := buildFixture(inst, arm, filepath.Join(root, t.ID, armName, "session.jsonl"))
if err != nil {
return contextMetrics{}, err
}
sess, err := sessionstore.LoadSession(f.Path)
if err != nil {
return contextMetrics{}, fmt.Errorf("load session: %w", err)
}
reg := tool.NewRegistry()
for _, x := range tool.Builtins() {
reg.Add(x)
}
boot.ApplyUnifiedProviderToolSurface(reg, false, arm)
var usage usageSink
a := agent.New(p, reg, sess, agent.Options{
ContextWindow: fixtureWindow, CompactRatio: 0.5, SessionPath: f.Path, Ablation: arm,
}, &usage)
a.LoadProjectionSidecar(f.Path)
// From here the transcript lives only in memory. On disk it is the answer.
sealFixture(f.Path)
defer unsealFixture(f.Path)
dir := filepath.Dir(f.Path)
if at, marker := answerOnDisk(dir, inst.AnswerMarkers); at != "" {
return contextMetrics{}, fmt.Errorf("%s [%s]: %q is readable at %s before the run", t.ID, armName, marker, at)
}
before := len(sess.Snapshot())
ctx, cancel := context.WithTimeout(context.Background(), runTimeout)
defer cancel()
prompt := inst.Prompt
if len(promptOverride) > 0 && promptOverride[0] != "" {
prompt = promptOverride[0]
}
runErr := a.Run(ctx, prompt)
if at, marker := answerOnDisk(dir, inst.AnswerMarkers); at != "" {
return contextMetrics{}, fmt.Errorf("%s [%s]: %q was written to %s during the run", t.ID, armName, marker, at)
}
appended := sess.Snapshot()[before:]
m := scoreRun(appended, inst, f.Target, armName, cueVisible)
m.trajectory = appended
if u, requests := usage.snapshot(); requests > 0 {
m.UsageReportedRequests, m.PromptTokens, m.CompletionTokens = requests, u.PromptTokens, u.CompletionTokens
m.CacheHitTokens, m.CacheMissTokens, m.UsageEstimated = u.CacheHitTokens, u.CacheMissTokens, u.Estimated
}
m.scoreAnswer(finalAnswer(appended), inst)
if runErr != nil {
m.FailureStage = "RunError"
m.TimeoutReason = timeoutReason(m)
}
return m, runErr
}
// failedMetrics is what a run that never reached a transcript leaves behind. It
// carries the run's identity and the stage that ended it, so the failure stays a
// countable fact; Failed keeps it out of every statistic.
func failedMetrics(t contextTask, armName string, err error) contextMetrics {
return contextMetrics{
Task: t.ID, Arm: armName, Failed: true,
FailureStage: stageNoEvidence, Reason: err.Error(),
}
}
// finalAnswer is the last assistant text the turn produced.
func finalAnswer(msgs []provider.Message) string {
for _, m := range slices.Backward(msgs) {
if m.Role == provider.RoleAssistant && strings.TrimSpace(m.Content) != "" {
return m.Content
}
}
return ""
}
// isolateWorkspace moves the process into an empty directory before any task
// runs. The corpus lives in this repository, so an agent left here greps the
// answers out of corpus.go — one did, quoting the file's own line about them
// being nonces. Fixtures stay outside it too: a readable session file is the
// canonical transcript, answer included.
func isolateWorkspace() (func(), error) {
dir, err := os.MkdirTemp("", "contextbench-workspace-*")
if err != nil {
return nil, err
}
readme := "This workspace is empty on purpose. The question is about earlier conversation,\nnot about any file here.\n"
if err := os.WriteFile(filepath.Join(dir, "README.md"), []byte(readme), 0o644); err != nil {
return nil, err
}
previous, err := os.Getwd()
if err != nil {
return nil, err
}
if err := os.Chdir(dir); err != nil {
return nil, err
}
return func() {
_ = os.Chdir(previous)
_ = os.RemoveAll(dir)
}, nil
}
// runBoundaries runs only the pair each index task is built for: the narrowest
// scale still addressing its cue, and the next one down. Each pair moves the
// affordance and nothing else, where default-vs-off moves the whole index.
// The two arms get separate nonces: reusing one would leave the first arm's
// answer in a host-readable history for the second to find.
func runBoundaries(mode, root string) int {
started := time.Now()
planned := 0
repo, _ := os.Getwd()
root, err := filepath.Abs(root)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return 2
}
restore, ierr := isolateWorkspace()
if ierr != nil {
fmt.Fprintln(os.Stderr, "isolate workspace:", ierr)
return 2
}
defer restore()
p, perr := realProvider()
if perr != nil {
fmt.Fprintln(os.Stderr, perr)
return 2
}
var all []contextMetrics
var cueSide, noCueSide []contextMetrics
for _, t := range efficiencyTasks() {
cue, noCue := boundaryPair(t.CueTier)
for _, side := range []struct {
scale string
has bool
}{{cue, true}, {noCue, false}} {
name := "index-" + side.scale
planned++
m, err := runOne(p, t, armFor(side.scale, false), name, root, side.has, false)
if err != nil {
fmt.Fprintf(os.Stderr, "%s [%s]: %v\n", t.ID, name, err)
m = failedMetrics(t, name, err)
}
all = append(all, m)
if side.has {
cueSide = append(cueSide, m)
} else {
noCueSide = append(noCueSide, m)
}
fmt.Printf("%-24s %-14s cue=%-3v %-14s search=%d read=%d direct=%-5v recall-tok=%-5d%s escape=%d\n",
t.ID, name, side.has, m.FailureStage, m.SearchCalls, m.ReadCalls,
m.CueDirectRead, m.RecallReturnedTokens, promptTokenSuffix(m), m.EscapeCalls)
}
}
reportDeltas(cueSide, noCueSide)
fmt.Print(reportStopping(all))
return writeResults(root, batchInfo{Mode: mode, Planned: planned, Started: started, Repo: repo}, all)
}
// reportDeltas is the paired reading: each task against itself, then pooled.
func reportDeltas(cueSide, noCueSide []contextMetrics) {
fmt.Printf("\n## Paired deltas (cue present − cue absent)\n")
fmt.Printf(" %-24s %-10s %-10s %-9s %-11s %s\n",
"task", "recovery", "directread", "searches", "recall-tok", "escapes")
for i := range cueSide {
if i >= len(noCueSide) {
break
}
a, b := cueSide[i], noCueSide[i]
fmt.Printf(" %-24s %-10s %-10s %-9d %-11d %d\n", a.Task,
deltaBool(a.AnswerRecovered, b.AnswerRecovered),
deltaBool(a.CueDirectRead, b.CueDirectRead),
a.SearchCalls-b.SearchCalls,
a.RecallReturnedTokens-b.RecallReturnedTokens,
a.EscapeCalls-b.EscapeCalls)
}
fmt.Println("\n## Pooled")
fmt.Println(" ", summarize("cue-present", cueSide).line())
fmt.Println(" ", summarize("cue-absent", noCueSide).line())
fmt.Println("\n## Where each side looked first")
fmt.Printf(" %-14s %s\n", "cue-present", countsLine(summarize("x", cueSide).Routes))
fmt.Printf(" %-14s %s\n", "cue-absent", countsLine(summarize("x", noCueSide).Routes))
}
func deltaBool(a, b bool) string {
switch {
case a == b:
return "same"
case a:
return "+cue"
default:
return "-cue"
}
}
// runExperiment runs one experiment's tasks across its arms. A dry run drives
// the same pipeline with a scripted provider: it proves fixture, run, scoring
// and report hold together before any of it is paid for.
func runExperiment(mode, experiment, root string, dry bool, tasks []contextTask) int {
started := time.Now()
planned := 0
repo, _ := os.Getwd()
root, err := filepath.Abs(root)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return 2
}
restore, err := isolateWorkspace()
if err != nil {
fmt.Fprintln(os.Stderr, "isolate workspace:", err)
return 2
}
defer restore()
var p provider.Provider = &scriptedProvider{}
if !dry {
real, rerr := realProvider()
if rerr != nil {
fmt.Fprintln(os.Stderr, rerr)
return 2
}
p = real
}
byArm := map[string][]contextMetrics{}
var all []contextMetrics
if experiment == experimentIndex {
fmt.Print(fingerprint(dry))
}
for _, t := range tasks {
if t.Experiment != experiment {
continue
}
// Arms are shuffled inside each task block. Running six defaults, then
// six halves, would make provider drift collinear with the axis under
// test: a bad minute would land entirely on one arm.
for _, arm := range shuffledArms(t) {
cueVisible := t.Experiment == experimentIndex && cueExpectedVisible(t, arm.scale)
planned++
m, err := runOne(p, t, armFor(arm.scale, arm.searchOff), arm.name, root, cueVisible, dry)
if err != nil {
fmt.Fprintf(os.Stderr, "%s [%s]: %v\n", t.ID, arm.name, err)
m = failedMetrics(t, arm.name, err)
}
byArm[arm.name] = append(byArm[arm.name], m)
all = append(all, m)
fmt.Printf("%-24s %-14s %-18s search=%d hit=%d read=%d recall-tok=%d%s %v\n",
t.ID, arm.name, m.FailureStage, m.SearchCalls, m.TargetSearchHits, m.ReadCalls,
m.RecallReturnedTokens, promptTokenSuffix(m), m.UnexpectedWorkTools)
}
}
reportFunnels(byArm)
if experiment == experimentIndex {
fmt.Print(reportDoseResponse(all))
}
fmt.Print(reportStopping(all))
fmt.Print(auditQueries(all).report())
fmt.Print(queryLines(all))
if experiment == experimentIndex {
reportBoundaries(all)
}
return writeResults(root, batchInfo{Mode: mode, Planned: planned, Started: started, Dry: dry, Repo: repo}, all)
}
func cueExpectedVisible(t contextTask, scale string) bool {
visible, _ := tierScales(t.CueTier)
return contains(visible, scale)
}
func reportFunnels(byArm map[string][]contextMetrics) {
names := make([]string, 0, len(byArm))
for name := range byArm {
names = append(names, name)
}
sort.Strings(names)
fmt.Println("\n## Funnel")
for _, name := range names {
fmt.Println(" ", summarize(name, byArm[name]).line())
}
fmt.Println("\n## Where it looked first")
for _, name := range names {
f := summarize(name, byArm[name])
fmt.Printf(" %-14s %s\n", name, countsLine(f.Routes))
}
fmt.Println("\n## Where it broke")
for _, name := range names {
f := summarize(name, byArm[name])
stages := make([]string, 0, len(f.Stages))
for stage, n := range f.Stages {
stages = append(stages, fmt.Sprintf("%s=%d", stage, n))
}
sort.Strings(stages)
fmt.Printf(" %-12s %s\n", name, strings.Join(stages, " "))
}
}
// writeTrajectories saves what each turn produced, after the whole batch is
// done. Three analyses in a row have needed a re-run because something was
// counted and not kept — query text, then snippet contents. It is written at
// the end on purpose: during the batch these files are the answer.
func writeTrajectories(root string, all []contextMetrics) error {
f, err := os.Create(filepath.Join(root, "trajectories.jsonl"))
if err != nil {
return err
}
defer f.Close()
for _, m := range all {
if err := writeJSONLine(f, map[string]any{
"task": m.Task, "arm": m.Arm, "messages": m.trajectory,
}); err != nil {
return err
}
}
return nil
}
func writeResults(root string, info batchInfo, all []contextMetrics) int {
path := filepath.Join(root, "results.jsonl")
f, err := os.Create(path)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return 2
}
defer f.Close()
scored, failed := 0, 0
for _, m := range all {
if err := writeJSONLine(f, m); err != nil {
fmt.Fprintln(os.Stderr, err)
return 2
}
if m.Failed {
failed++
} else {
scored++
}
}
if err := writeTrajectories(root, all); err != nil {
fmt.Fprintln(os.Stderr, "write trajectories:", err)
return 2
}
if err := writeBatchManifest(root, info, all); err != nil {
fmt.Fprintln(os.Stderr, "write batch manifest:", err)
return 2
}
// Both counts are named: a reader who sees only the batch size cannot tell
// measurements from attempts.
fmt.Printf("\n%d runs written to %s: scored=%d failed=%d (with trajectories.jsonl and batch.json)\n",
len(all), path, scored, failed)
return 0
}
// reportBoundaries prints each index task across its own cue boundary, then the
// two sides pooled. Task difficulty is held constant inside a row, which is
// what a four-arm average cannot do.
func reportBoundaries(all []contextMetrics) {
byKey := map[string]contextMetrics{}
scored, failed := 0, 0
for _, m := range all {
if m.Failed {
// No transcript, no boundary to compare: count the failure instead of
// printing the zero it left behind as a measurement.
failed++
continue
}
scored++
byKey[m.Task+"|"+m.Arm] = m
}
if scored == 0 {
fmt.Printf("\n## Across each task's cue boundary\n no scored runs: scored=%d failed=%d (nothing to compare)\n", scored, failed)
return
}
var cueSide, noCueSide []contextMetrics
fmt.Println("\n## Across each task's cue boundary")
fmt.Printf(" %-24s %-16s %-4s %-10s %-9s %-12s %-11s %s\n",
"task", "arm", "cue", "recovered", "cue-read", "search/task", "recall-tok", "escape")
for _, t := range indexTasks() {
cue, noCue := boundaryPair(t.CueTier)
for _, side := range []struct {
scale string
has bool
}{{cue, true}, {noCue, false}} {
m, ok := byKey[t.ID+"|index-"+side.scale]
if !ok {
continue
}
if side.has {
cueSide = append(cueSide, m)
} else {
noCueSide = append(noCueSide, m)
}
mark := "no"
if side.has {
mark = "yes"
}
fmt.Printf(" %-24s %-16s %-4s %-10v %-9v %-12d %-11d %d\n",
t.ID, "index-"+side.scale, mark, m.AnswerRecovered, m.RecallReadWithoutSearch,
m.SearchCalls, m.RecallReturnedTokens, m.EscapeCalls)
}
}
fmt.Println("\n## Pooled across boundaries")
fmt.Println(" ", summarize("cue-present", cueSide).line())
fmt.Println(" ", summarize("cue-absent", noCueSide).line())
}
// shuffledArms permutes a task's arms deterministically, so the order is
// reproducible and still uncorrelated with the axis being measured.
func shuffledArms(t contextTask) []experimentArm {
arms := armsFor(t)
rng := seededRand(t.ID, "arm-order")
rng.Shuffle(len(arms), func(i, j int) { arms[i], arms[j] = arms[j], arms[i] })
return arms
}
// fingerprint records what produced a batch. Not a gate — but batch effects
// have reversed three conclusions in this experiment already, and a table with
// no provenance cannot be compared with the next one.
func fingerprint(dry bool) string {
model, endpoint := runModel, runBaseURL
if dry {
model, endpoint = "scripted", "none"
}
commit := "unknown"
if out, err := exec.Command("git", "rev-parse", "--short", "HEAD").Output(); err == nil {
commit = strings.TrimSpace(string(out))
}
return fmt.Sprintf("## Batch\n model=%s endpoint=%s window=%d generations=%d commit=%s started=%s\n",
model, endpoint, fixtureWindow, fixtureGenerations, commit, time.Now().UTC().Format(time.RFC3339))
}
// timeoutReason separates a run that never found its answer from one that found
// it and kept going. Only the first is a case for a longer budget; the second
// would just get more time to circle. A batch reporting only "context deadline
// exceeded" cannot tell an arm that made retrieval harder from an arm that
// happened to draw a slow minute.
func timeoutReason(m contextMetrics) string {
switch {
case m.TargetHeldAtEnd && m.RoundsAfterTarget >= 2:
return "PostTargetRunaway"
case m.TargetHeldAtEnd:
return "TargetHeldLate"
case m.SearchCalls >= 3:
return "SearchSpiral"
case m.SearchCalls+m.ReadCalls+m.EscapeCalls == 0:
return "NoToolActivity"
default:
return "Unknown"
}
}
-95
View File
@@ -1,95 +0,0 @@
package main
import (
"context"
"encoding/json"
"regexp"
"slices"
"strconv"
"strings"
"reasonix/internal/contract/provider"
)
// scriptedProvider walks the chain a competent model would: search, read the
// best hit, answer with what came back. It proves the pipeline holds together
// for nothing, so a real run's first failure is about the model.
type scriptedProvider struct{}
func (p *scriptedProvider) Name() string { return "contextbench-scripted" }
var (
scriptedHit = regexp.MustCompile(`(?m)^#(\d+) `)
scriptedRecall = regexp.MustCompile(`(?s)^#\d+\n(.*)`)
)
// Stream decides from the request alone, never from a call counter: one
// provider serves every task in a run, and a counter would make the second task
// start where the first left off.
func (p *scriptedProvider) Stream(_ context.Context, req provider.Request) (<-chan provider.Chunk, error) {
ch := make(chan provider.Chunk, 3)
defer close(ch)
// Honour the schema, as a model does: an arm without a query parameter is
// an arm that cannot search, and pretending otherwise scores a refusal as
// a failed search.
if !recallAccepts(req, "query") {
ch <- provider.Chunk{Type: provider.ChunkText, Text: "I have no address for that part of the history."}
ch <- provider.Chunk{Type: provider.ChunkDone}
return ch, nil
}
// The last message, not the last tool result anywhere: the fixture's own
// history is full of tool results, and one of those is not this turn's.
last := ""
if n := len(req.Messages); n > 0 && req.Messages[n-1].Role == provider.RoleTool {
last = req.Messages[n-1].Content
}
switch {
case last == "":
args, _ := json.Marshal(map[string]string{"query": lastUserText(req)})
ch <- provider.Chunk{Type: provider.ChunkToolCall, ToolCall: &provider.ToolCall{
ID: "s1", Name: "recall", Arguments: string(args),
}}
case strings.Contains(last, "Folded context matching"):
positions := scriptedHit.FindAllStringSubmatch(last, 1)
if len(positions) == 0 {
ch <- provider.Chunk{Type: provider.ChunkText, Text: "Nothing in the folded history matches."}
break
}
n, _ := strconv.Atoi(positions[0][1])
args, _ := json.Marshal(map[string][]int{"positions": {n}})
ch <- provider.Chunk{Type: provider.ChunkToolCall, ToolCall: &provider.ToolCall{
ID: "r1", Name: "recall", Arguments: string(args),
}}
default:
// Answer with what recall returned, so a correct chain scores as one.
body := last
if m := scriptedRecall.FindStringSubmatch(last); len(m) > 1 {
body = m[1]
}
ch <- provider.Chunk{Type: provider.ChunkText, Text: "From the folded history: " + body}
}
ch <- provider.Chunk{Type: provider.ChunkDone}
return ch, nil
}
// recallAccepts reports whether recall is offered with the given parameter.
func recallAccepts(req provider.Request, param string) bool {
for _, t := range req.Tools {
if t.Name != "recall" {
continue
}
b, err := json.Marshal(t.Parameters)
return err == nil && strings.Contains(string(b), `"`+param+`"`)
}
return false
}
func lastUserText(req provider.Request) string {
for _, m := range slices.Backward(req.Messages) {
if m.Role == provider.RoleUser {
return m.Content
}
}
return ""
}
-180
View File
@@ -1,180 +0,0 @@
// snippet.go — does a rank-1 hit actually hand over the answer?
package main
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"reasonix/internal/base/retrieval"
"reasonix/internal/contract/ablation"
"reasonix/internal/contract/event"
"reasonix/internal/contract/tool"
"reasonix/internal/runtime/agent"
)
// TargetHit is not EvidenceSufficient: a rank-1 hit can still hand over a
// window too short to answer with, and searching again after that is correct.
// Measured through the shipped recall path, not a copy of it.
// snippetWidths are the windows to compare. 240 is what ships.
var snippetWidths = []int{120, 180, 240, 320, 480}
// historicalQueries are the queries these tasks actually drew in the pilots,
// kept because a probe query is the host's phrasing and the model's was not.
var historicalQueries = map[string][]string{
"i03-coalescing": {"coalesce window marker probe", "job completion coalescing window"},
"i04-recovery-fence": {"recovery fence marker epoch", "expected epoch", "recfence quartz"},
}
type snippetReading struct {
Task string
Query string
Width int
Covered int
Total int
Runes int
Rank int
Complete bool
}
// runSnippetAudit answers the question offline, over many instantiations so a
// single seed's lengths cannot decide it.
func runSnippetAudit(root string, seeds int) int {
fmt.Printf("## Snippet coverage (%d instantiations per task)\n", seeds)
fmt.Println(" Does the window a rank-1 hit returns contain every value the task scores on?")
byTaskWidth := map[string]map[int][]snippetReading{}
for _, t := range allTasks() {
byTaskWidth[t.ID] = map[int][]snippetReading{}
for seed := range seeds {
inst, err := instantiateTask(t, seededRand(t.ID, "snippet", fmt.Sprint(seed)))
if err != nil {
fmt.Fprintln(os.Stderr, err)
return 2
}
queries := append([]string{inst.ProbeQuery}, historicalQueries[t.ID]...)
for _, q := range queries {
for _, width := range snippetWidths {
r := measureSnippet(inst, q, width)
byTaskWidth[t.ID][width] = append(byTaskWidth[t.ID][width], r)
}
}
}
}
fmt.Printf("\n %-24s %-5s", "task", "vals")
for _, w := range snippetWidths {
fmt.Printf("%9d", w)
}
fmt.Println(" mean runes @240")
for _, t := range allTasks() {
vals := len(t.AnswerVars)
fmt.Printf(" %-24s %-5d", t.ID, vals)
for _, w := range snippetWidths {
fmt.Printf("%8.0f%%", 100*completeRate(byTaskWidth[t.ID][w]))
}
fmt.Printf(" %.0f\n", meanRunes(byTaskWidth[t.ID][240]))
}
reportShippedPath(root)
return 0
}
// measureSnippet renders one hit through the same MakeSnippet the recall path
// uses, then asks whether every scored value survived the window.
func measureSnippet(inst fixtureInstance, query string, width int) snippetReading {
terms, err := retrieval.QueryTerms(query)
if err != nil {
return snippetReading{Task: inst.Task.ID, Query: query, Width: width, Total: len(inst.AnswerMarkers)}
}
// The tool result is the document the answer lives in; a call's arguments
// are a separate document and never carry it.
snippet := retrieval.MakeSnippet(inst.body, query, terms, width)
covered := 0
for _, marker := range inst.AnswerMarkers {
if strings.Contains(strings.ToLower(snippet), strings.ToLower(marker)) {
covered++
}
}
return snippetReading{
Task: inst.Task.ID, Query: query, Width: width,
Covered: covered, Total: len(inst.AnswerMarkers),
Runes: len([]rune(snippet)), Complete: covered == len(inst.AnswerMarkers),
}
}
// reportShippedPath cross-checks the table against the real recall tool: a
// fixture, a fold, and a search through the agent that ships.
func reportShippedPath(root string) {
fmt.Println("\n## Through the shipped recall path (width as shipped)")
for _, t := range allTasks() {
inst, err := instantiateTask(t, seededRand(t.ID, "snippet-live"))
if err != nil {
continue
}
f, err := buildFixture(inst, ablation.Set{}, filepath.Join(root, "snippet", t.ID, "session.jsonl"))
if err != nil {
fmt.Printf(" %-24s fixture: %v\n", t.ID, err)
continue
}
a := agent.New(&digestProvider{}, tool.NewRegistry(), f.Session, agent.Options{
ContextWindow: fixtureWindow, CompactRatio: 0.5, SessionPath: f.Path}, event.Discard)
a.LoadProjectionSidecar(f.Path)
queries := append([]string{inst.ProbeQuery}, historicalQueries[t.ID]...)
for _, q := range queries {
res, err := a.RecallContext(context.Background(), tool.RecallRequest{Query: q})
if err != nil {
fmt.Printf(" %-24s %-34q recall: %v\n", t.ID, q, err)
continue
}
covered := 0
for _, marker := range inst.AnswerMarkers {
if strings.Contains(strings.ToLower(res.Text), strings.ToLower(marker)) {
covered++
}
}
fmt.Printf(" %-24s %-34q %d/%d values in the result\n",
t.ID, truncate(q, 32), covered, len(inst.AnswerMarkers))
}
}
}
func completeRate(rs []snippetReading) float64 {
if len(rs) == 0 {
return 0
}
n := 0
for _, r := range rs {
if r.Complete {
n++
}
}
return float64(n) / float64(len(rs))
}
func meanRunes(rs []snippetReading) float64 {
if len(rs) == 0 {
return 0
}
total := 0
for _, r := range rs {
total += r.Runes
}
return float64(total) / float64(len(rs))
}
// answerCoverage is what a run should have recorded all along: how much of the
// scored answer a search actually handed over. rank=1 with coverage 1/3 is not
// a stopping failure.
func answerCoverage(text string, markers []string) int {
covered := 0
lower := strings.ToLower(text)
for _, marker := range markers {
if strings.Contains(lower, strings.ToLower(marker)) {
covered++
}
}
return covered
}
-59
View File
@@ -1,59 +0,0 @@
package main
import (
"fmt"
"sync"
"reasonix/internal/contract/event"
"reasonix/internal/contract/provider"
)
// usageSink reads what a run cost from the events the agent already emits. Every
// billed completion reports event.Usage — the round path, compaction, command
// routing and failure diagnosis all do — so the bench reads the same record the
// product reads instead of wrapping a provider and counting a second time.
type usageSink struct {
mu sync.Mutex
requests int
usage provider.Usage
}
func (s *usageSink) Emit(e event.Event) {
if e.Kind != event.Usage || e.Usage == nil {
return
}
s.mu.Lock()
defer s.mu.Unlock()
// One event is one completion that reported usage; RequestCount covers a
// completion that merged attempts. A request that failed before reporting
// leaves no event, so this is not a count of attempts.
if e.Usage.RequestCount > 0 {
s.requests += e.Usage.RequestCount
} else {
s.requests++
}
u := *e.Usage
s.usage.PromptTokens += u.PromptTokens
s.usage.CompletionTokens += u.CompletionTokens
s.usage.TotalTokens += u.TotalTokens
s.usage.CacheHitTokens += u.CacheHitTokens
s.usage.CacheMissTokens += u.CacheMissTokens
s.usage.ReasoningTokens += u.ReasoningTokens
s.usage.Estimated = s.usage.Estimated || u.Estimated
}
func (s *usageSink) snapshot() (provider.Usage, int) {
s.mu.Lock()
defer s.mu.Unlock()
return s.usage, s.requests
}
// promptTokenSuffix renders what a run cost only when something reported it. A
// dry run reports nothing, and a trailing "prompt-tok=0" would read as a
// measured zero rather than as no measurement.
func promptTokenSuffix(m contextMetrics) string {
if m.UsageReportedRequests == 0 {
return ""
}
return fmt.Sprintf(" prompt-tok=%d", m.PromptTokens)
}
@@ -1,54 +0,0 @@
package main
import (
"testing"
"reasonix/internal/contract/event"
"reasonix/internal/contract/provider"
)
// The cost a run reports must be what the agent reported, summed across every
// completion it billed — including one that merged attempts and one that could
// only estimate. Any other event kind is not a cost.
func TestUsageSinkAccumulatesWhatTheAgentReports(t *testing.T) {
var sink usageSink
sink.Emit(event.Event{}) // a kind that is not Usage
sink.Emit(event.Event{Kind: event.Usage, Usage: &provider.Usage{
PromptTokens: 100, CompletionTokens: 10, TotalTokens: 110, CacheHitTokens: 60, CacheMissTokens: 40,
}})
sink.Emit(event.Event{Kind: event.Usage, Usage: &provider.Usage{
PromptTokens: 7, CompletionTokens: 3, CacheHitTokens: 2, CacheMissTokens: 5, RequestCount: 2, Estimated: true,
}})
sink.Emit(event.Event{Kind: event.Usage}) // a Usage event with no payload is not a cost
usage, requests := sink.snapshot()
if requests != 3 {
t.Fatalf("requests = %d, want 3 (1 + the merged attempt's 2; the empty event adds none)", requests)
}
if usage.PromptTokens != 107 || usage.CompletionTokens != 13 || usage.TotalTokens != 110 {
t.Fatalf("accumulated usage = %+v", usage)
}
if usage.CacheHitTokens != 62 || usage.CacheMissTokens != 45 {
t.Fatalf("cache split = %+v", usage)
}
if !usage.Estimated {
t.Fatal("Estimated was dropped when one completion reported an estimate")
}
}
// A dry run must not claim a cost it never paid, in the JSON or in the line a
// person reads: an empty suffix, not "prompt-tok=0".
func TestUsageSinkStaysEmptyWithoutUsage(t *testing.T) {
var sink usageSink
sink.Emit(event.Event{})
usage, requests := sink.snapshot()
if requests != 0 || usage.PromptTokens != 0 || usage.CompletionTokens != 0 {
t.Fatalf("nothing reported usage, got %+v requests=%d", usage, requests)
}
if suffix := promptTokenSuffix(contextMetrics{}); suffix != "" {
t.Fatalf("a run with no reported usage still printed %q", suffix)
}
if suffix := promptTokenSuffix(contextMetrics{UsageReportedRequests: 1, PromptTokens: 12}); suffix != " prompt-tok=12" {
t.Fatalf("a reported cost printed %q", suffix)
}
}
-7
View File
@@ -170,13 +170,6 @@ func applyUnifiedProviderToolSurface(reg *tool.Registry, goalTurnsUnreachable bo
reg.SetProviderVisibleTools(allow)
}
// ApplyUnifiedProviderToolSurface restricts a registry to what a provider is
// shown, at this arm. Exported so a harness reproduces the real surface
// instead of a second copy of this rule.
func ApplyUnifiedProviderToolSurface(reg *tool.Registry, goalTurnsUnreachable bool, arm ablation.Set) {
applyUnifiedProviderToolSurface(reg, goalTurnsUnreachable, arm, nil)
}
// pinnedMCPServers is the always-load servers whose schema was known at boot.
// A server discovered during this session waits for the next one, whatever
// the discovery race, so the same config and cache give the same schema.
+17 -2
View File
@@ -41,11 +41,11 @@ func shellsOn(goos string, getenv func(string) string, look func(string) (string
out = append(out, Shell{Name: name, Kind: kind, Path: p, Args: args})
}
if goos == "windows" {
for _, root := range []string{getenv("ProgramFiles"), getenv("ProgramFiles(x86)"), filepath.Join(getenv("LOCALAPPDATA"), "Programs")} {
for _, root := range []string{getenv("ProgramFiles"), getenv("ProgramFiles(x86)"), joinFor(goos, getenv("LOCALAPPDATA"), "Programs")} {
if root == "" {
continue
}
if p := filepath.Join(root, "Git", "bin", "bash.exe"); exists(p) {
if p := joinFor(goos, root, "Git", "bin", "bash.exe"); exists(p) {
add("Git Bash", "bash", p, "--login", "-i")
}
}
@@ -73,3 +73,18 @@ func shellsOn(goos string, getenv func(string) string, look func(string) (string
}
return out
}
// joinFor joins path parts the way the named OS does, so a listing computed for
// another OS than the host's still names paths that OS would accept.
func joinFor(goos string, parts ...string) string {
if goos != "windows" {
return filepath.Join(parts...)
}
kept := make([]string, 0, len(parts))
for _, part := range parts {
if part = strings.TrimRight(part, `\/`); part != "" {
kept = append(kept, part)
}
}
return strings.Join(kept, `\`)
}
@@ -0,0 +1,5 @@
//go:build !race
package sessionstore
const raceDetector = false
@@ -0,0 +1,8 @@
//go:build race
package sessionstore
// raceDetector is whether this test binary was built with -race, which turns
// off the sync.Pool reuse that regexp matching relies on, so allocation counts
// stop measuring the code under test.
const raceDetector = true
@@ -36,6 +36,9 @@ func allocatedBy(f func()) uint64 {
// to reload: one more message costs about one encoding pass, not a replay of the
// event log plus a decode and re-encode of what disk already holds.
func TestSavingOneMoreMessageDoesNotReplayTheLog(t *testing.T) {
if raceDetector {
t.Skip("allocation counts are not comparable under the race detector")
}
path := filepath.Join(t.TempDir(), "s.jsonl")
s := seededSession(t, path, 2000)
if err := s.Save(path); err != nil {