Why: The first real-model sample batched discoveries, overcalled scope differences and exposed process chatter as a terminal conclusion. Evidence: Preserve the failed A01 sample; process-text regression failed before the fix, then complete Backend and focused runtime checks passed. Impact: Bump recipe identity, qualify quantities and approvals, exclude Process parts from canonical results and capture independent historical evaluation snapshots. Refs: Approved U2/U3 plan; independent model review and native SELF receipts
213 lines
7.2 KiB
Go
213 lines
7.2 KiB
Go
//go:build live
|
|
|
|
package live
|
|
|
|
import (
|
|
core "agent-system-core"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
type checkpointManifest struct {
|
|
PID int `json:"pid"`
|
|
Binary, BinarySHA256, BackendSHA, DevSHA, Base, DataDir, Mode string
|
|
}
|
|
|
|
func verifiedCheckpointManifest(t *testing.T) checkpointManifest {
|
|
t.Helper()
|
|
path := os.Getenv("WORK_CHECKPOINT_EVAL_OWNERSHIP_MANIFEST")
|
|
if path == "" {
|
|
t.Fatal("explicit task-owned process manifest required")
|
|
}
|
|
raw, e := os.ReadFile(path)
|
|
if e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
var m checkpointManifest
|
|
if json.Unmarshal(raw, &m) != nil {
|
|
t.Fatal("invalid manifest")
|
|
}
|
|
u, e := url.Parse(m.Base)
|
|
if e != nil || u.Hostname() != "127.0.0.1" || m.Base != os.Getenv("AGENT_EVAL_BASE") || m.PID <= 0 || !filepath.IsAbs(m.DataDir) || !strings.Contains(m.DataDir, "fast-slow-u2-u3-") {
|
|
t.Fatal("manifest scope mismatch")
|
|
}
|
|
if e = syscall.Kill(m.PID, 0); e != nil {
|
|
t.Fatal("owned process is not live")
|
|
}
|
|
actual, e := os.Readlink(fmt.Sprintf("/proc/%d/exe", m.PID))
|
|
if e != nil || actual != m.Binary {
|
|
t.Fatal("process executable mismatch")
|
|
}
|
|
bytes, e := os.ReadFile(actual)
|
|
if e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
sum := sha256.Sum256(bytes)
|
|
if hex.EncodeToString(sum[:]) != m.BinarySHA256 {
|
|
t.Fatal("binary hash mismatch")
|
|
}
|
|
if _, e = os.Stat(filepath.Join(m.DataDir, "agent-system.db")); e != nil {
|
|
t.Fatal("isolated data missing")
|
|
}
|
|
return m
|
|
}
|
|
|
|
// The batch driver invokes this once per baseline/candidate, using the same
|
|
// three synthetic materials, route and profile. It never resolves a business HITL.
|
|
func TestWorkCheckpoints(t *testing.T) {
|
|
m := verifiedCheckpointManifest(t)
|
|
if m.Mode != "model" {
|
|
t.Fatal("real model evaluation requires model mode")
|
|
}
|
|
materials := os.Getenv("AGENT_EVAL_MATERIALS")
|
|
outDir := os.Getenv("AGENT_EVAL_OUT")
|
|
if materials == "" || outDir == "" {
|
|
t.Fatal("explicit external material and evidence directories required")
|
|
}
|
|
if e := os.MkdirAll(outDir, 0700); e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
c := &client{base: m.Base, http: &http.Client{Timeout: 30 * time.Second}, t: t}
|
|
label := os.Getenv("WORK_CHECKPOINT_EVAL_LABEL")
|
|
if label != "baseline" && label != "candidate" {
|
|
t.Fatal("fixed baseline/candidate label required")
|
|
}
|
|
caseFilter := os.Getenv("WORK_CHECKPOINT_EVAL_CASES")
|
|
outputName := label
|
|
if caseFilter != "" {
|
|
if label != "candidate" || caseFilter != "A02,A04" {
|
|
t.Fatal("only the two unexecuted candidate cases may be selected")
|
|
}
|
|
outputName = "candidate-remaining"
|
|
}
|
|
all := []map[string]any{}
|
|
total := 0
|
|
for _, testCase := range []struct {
|
|
id, focus string
|
|
files []string
|
|
}{{"A01", "", []string{"需求.txt", "会议.txt", "评审.txt"}}, {"A02", "重点核对预算、外部依赖和交付日期;请保留限制", []string{"需求.txt", "会议.txt", "评审.txt"}}, {"A04", "", []string{"需求.txt", "会议.txt", "扫描.pdf"}}} {
|
|
if caseFilter != "" && !strings.Contains(caseFilter, testCase.id) {
|
|
continue
|
|
}
|
|
if total >= 120000 {
|
|
t.Fatal("instance reported token budget exhausted")
|
|
}
|
|
var project struct {
|
|
ID string `json:"id"`
|
|
}
|
|
if e := c.do("POST", "/api/projects", map[string]any{"name": "checkpoint-" + label + "-" + testCase.id}, &project); e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
var conv struct {
|
|
ID string `json:"id"`
|
|
}
|
|
if e := c.do("POST", "/api/projects/"+project.ID+"/conversations", map[string]any{"title": testCase.id, "profileId": "checkpoint-eval"}, &conv); e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
ids := []string{}
|
|
for _, name := range testCase.files {
|
|
bytes, e := os.ReadFile(filepath.Join(materials, name))
|
|
if e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
ids = append(ids, c.upload(t, project.ID, name, bytes))
|
|
}
|
|
var send map[string]any
|
|
if e := c.do("POST", "/api/conversations/"+conv.ID+"/messages", map[string]any{"text": testCase.focus, "attachmentIds": ids, "requestId": "checkpoint-" + label + "-" + testCase.id}, &send); e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
deadline := time.Now().Add(300 * time.Second)
|
|
var snapshot struct {
|
|
Cards []core.ActivityCard `json:"cards"`
|
|
ReplaySeq int64 `json:"replaySeq"`
|
|
}
|
|
observations := []map[string]any{}
|
|
seen := map[string]bool{}
|
|
for {
|
|
// Decode into fresh storage: reusing a slice lets JSON overwrite Task
|
|
// pointers already saved as historical observations.
|
|
snapshot.Cards = nil
|
|
if e := c.do("GET", "/api/conversations/"+conv.ID+"/cards", nil, &snapshot); e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
terminal := false
|
|
for _, card := range snapshot.Cards {
|
|
if card.Kind != core.CardKindProgress {
|
|
continue
|
|
}
|
|
if card.Task != nil {
|
|
key := fmt.Sprintf("%s:%d", card.ID, card.Task.Revision)
|
|
if !seen[key] {
|
|
seen[key] = true
|
|
observations = append(observations, map[string]any{"observedAt": time.Now().UTC(), "card": card, "replaySeq": snapshot.ReplaySeq})
|
|
}
|
|
terminal = card.Task.Terminal()
|
|
} else {
|
|
terminal = card.Status == core.CardDone || card.Status == core.CardFailed
|
|
}
|
|
}
|
|
if terminal {
|
|
break
|
|
}
|
|
if label == "baseline" && time.Until(deadline) < 298*time.Second && len(snapshot.Cards) == 0 {
|
|
runObj, ok := send["run"].(map[string]any)
|
|
if ok {
|
|
var state struct {
|
|
Run core.Run `json:"run"`
|
|
}
|
|
if c.do("GET", "/api/runs/"+fmt.Sprint(runObj["id"]), nil, &state) == nil && state.Run.Status == core.RunCompleted {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if time.Now().After(deadline) {
|
|
t.Fatalf("%s background did not finish within budget", testCase.id)
|
|
}
|
|
time.Sleep(250 * time.Millisecond)
|
|
}
|
|
var detail map[string]json.RawMessage
|
|
if e := c.do("GET", "/api/conversations/"+conv.ID, nil, &detail); e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
var runs []core.Run
|
|
_ = json.Unmarshal(detail["runs"], &runs)
|
|
for _, r := range runs {
|
|
if r.Usage != nil {
|
|
total += r.Usage.TotalTokens
|
|
}
|
|
}
|
|
record := map[string]any{"case": testCase.id, "projectId": project.ID, "conversationId": conv.ID, "send": send, "observations": observations, "finalCards": snapshot.Cards, "detail": detail, "reportedTokensCumulative": total, "quality": "requires independent comparison against provided materials; quote existence is not semantic proof"}
|
|
all = append(all, record)
|
|
raw, _ := json.MarshalIndent(map[string]any{"manifest": m, "label": label, "cases": all, "reportedTokens": total, "noticeModelTurns": "disabled equally in controlled comparison"}, "", " ")
|
|
if e := os.WriteFile(filepath.Join(outDir, outputName+".json"), raw, 0600); e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
if label == "candidate" && testCase.id == "A01" {
|
|
maximum := 0
|
|
for _, o := range observations {
|
|
card := o["card"].(core.ActivityCard)
|
|
if card.Task != nil && !card.Task.Terminal() && int(card.Task.CheckpointSeq) > maximum {
|
|
maximum = int(card.Task.CheckpointSeq)
|
|
}
|
|
}
|
|
if maximum < 2 {
|
|
t.Fatal("A01 has fewer than two in-flight persisted checkpoints; inspect raw evidence")
|
|
}
|
|
}
|
|
if total > 120000 {
|
|
t.Fatal("reported instance token target exceeded; no further sample started")
|
|
}
|
|
}
|
|
t.Logf("%s: %d material tasks, cumulative reported tokens=%d; raw quality evidence=%s", label, len(all), total, filepath.Join(outDir, outputName+".json"))
|
|
}
|