Skip to content

Commit 73cffca

Browse files
committed
fix(results): restore index jsonl run manifest
1 parent 28171e1 commit 73cffca

59 files changed

Lines changed: 330 additions & 416 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/ci.yml‎

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ jobs:
213213
name: Validate Evals
214214
runs-on: ubuntu-latest
215215
timeout-minutes: 15
216+
needs: build
216217
steps:
217218
- uses: actions/checkout@v6
218219
- name: Setup Bun
@@ -223,8 +224,84 @@ jobs:
223224
- name: Install dependencies
224225
run: bun install --frozen-lockfile
225226

226-
- name: Build
227-
run: bun run build
227+
- name: Download build artifact
228+
uses: actions/download-artifact@v4
229+
with:
230+
name: agentv-build-${{ runner.os }}-${{ runner.arch }}-${{ github.sha }}
231+
path: ${{ runner.temp }}/agentv-build-artifact
232+
233+
- name: Restore build artifact
234+
env:
235+
ARTIFACT_DIR: ${{ runner.temp }}/agentv-build-artifact
236+
EXPECTED_COMMIT_SHA: ${{ github.sha }}
237+
EXPECTED_RUNNER_ARCH: ${{ runner.arch }}
238+
EXPECTED_RUNNER_OS: ${{ runner.os }}
239+
run: |
240+
set -euo pipefail
241+
242+
bun -e '
243+
import { createHash } from "node:crypto";
244+
import { Buffer } from "node:buffer";
245+
import { cpSync, existsSync, mkdirSync, rmSync } from "node:fs";
246+
import path from "node:path";
247+
248+
const artifactDir = process.env.ARTIFACT_DIR;
249+
if (!artifactDir) {
250+
throw new Error("ARTIFACT_DIR is required");
251+
}
252+
253+
const manifestPath = path.join(artifactDir, "manifest.json");
254+
if (!existsSync(manifestPath)) {
255+
throw new Error(`Build artifact manifest is missing: ${manifestPath}`);
256+
}
257+
258+
const manifest = await Bun.file(manifestPath).json();
259+
const rootPackageJson = await Bun.file("package.json").json();
260+
const lockfile = await Bun.file("bun.lock").arrayBuffer();
261+
const lockHash = createHash("sha256")
262+
.update(Buffer.from(lockfile))
263+
.digest("hex");
264+
265+
const expected = {
266+
commit_sha: process.env.EXPECTED_COMMIT_SHA,
267+
bun_lock_sha256: lockHash,
268+
runner_os: process.env.EXPECTED_RUNNER_OS,
269+
runner_arch: process.env.EXPECTED_RUNNER_ARCH,
270+
bun_version_spec: rootPackageJson.packageManager ?? null,
271+
bun_version: Bun.version,
272+
};
273+
274+
for (const [key, value] of Object.entries(expected)) {
275+
if (manifest[key] !== value) {
276+
throw new Error(
277+
`Build artifact manifest mismatch for ${key}: expected ${value}, got ${manifest[key]}`,
278+
);
279+
}
280+
}
281+
282+
const requiredPaths = [
283+
"packages/core/dist",
284+
"packages/sdk/dist",
285+
"apps/cli/dist",
286+
"apps/dashboard/dist",
287+
];
288+
const includedPaths = new Set(manifest.included_paths ?? []);
289+
290+
for (const relativePath of requiredPaths) {
291+
if (!includedPaths.has(`${relativePath}/**`)) {
292+
throw new Error(`Build artifact manifest does not include ${relativePath}/**`);
293+
}
294+
295+
const source = path.join(artifactDir, relativePath);
296+
if (!existsSync(source)) {
297+
throw new Error(`Build artifact path is missing: ${source}`);
298+
}
299+
300+
rmSync(relativePath, { recursive: true, force: true });
301+
mkdirSync(path.dirname(relativePath), { recursive: true });
302+
cpSync(source, relativePath, { recursive: true });
303+
}
304+
'
228305
229306
- name: Check evals directories have eval files
230307
run: bun scripts/validate-eval-dirs.ts

‎CONCEPTS.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@ Shared domain vocabulary for this project — entities, named processes, and sta
2424

2525
**Workspace** — The task environment an eval prepares for the agent: repositories, templates, fixture files, and lifecycle hooks. It is not prompt input; use `input` for instructions and `workspace.repos[]` for multi-repo workspaces the agent can inspect or modify through tools.
2626

27-
**Run manifest** — The root `run_manifest.jsonl` file in a run bundle. It is the dashboard and tooling loading contract for per-case result rows and artifact locations, including fields such as `result_dir`, `task_dir`, `summary_path`, and `grading_path`.
27+
**Run manifest** — The root `index.jsonl` file in a run bundle. It is the dashboard and tooling loading contract for per-case result rows and artifact locations, including fields such as `result_dir`, `task_dir`, `summary_path`, and `grading_path`.
2828

2929
**Result source identity** — The stable source identity for a result row: repo-relative `eval_path`, `test_id`, and `target`. `suite` and `name` are display metadata, not storage or routing identity.
3030

31-
**Result directory** — The `result_dir` field in a `run_manifest.jsonl` row. It is a run-local directory allocation for that row's sidecars and outputs. Consumers discover it from `run_manifest.jsonl` and must not infer it from suite names, display names, test IDs, or targets.
31+
**Result directory** — The `result_dir` field in a `index.jsonl` row. It is a run-local directory allocation for that row's sidecars and outputs. Consumers discover it from `index.jsonl` and must not infer it from suite names, display names, test IDs, or targets.
3232

3333
**Artifact sidecar** — A file beside or below a result directory that provides evidence for a result, such as `summary.json`, `grading.json`, `result.json`, transcripts, logs, or outputs. Sidecars are evidence, not the primary discovery mechanism for a run.
3434

‎README.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,14 +73,14 @@ agentv eval evals/my-eval.yaml
7373

7474
**5. Compare results across targets:**
7575
```bash
76-
agentv compare .agentv/results/default/<timestamp>/run_manifest.jsonl
76+
agentv compare .agentv/results/default/<timestamp>/index.jsonl
7777
```
7878

7979
## Output formats
8080

8181
```bash
82-
agentv eval evals/my-eval.yaml --output ./run # writes ./run/run_manifest.jsonl
83-
cat ./run/run_manifest.jsonl # JSONL results for scripts/CI
82+
agentv eval evals/my-eval.yaml --output ./run # writes ./run/index.jsonl
83+
cat ./run/index.jsonl # JSONL results for scripts/CI
8484
```
8585

8686
## TypeScript SDK

‎ROADMAP.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ This roadmap translates [STRATEGY.md](STRATEGY.md) into the next few product pha
1515

1616
## Phase 1: Finish the artifact and local inspection foundation
1717

18-
- Keep the canonical handoff surface centered on completed run bundles, `run_manifest.jsonl`, grading/timing/metrics artifacts, normalized transcripts, and optional `external_trace` link metadata.
18+
- Keep the canonical handoff surface centered on completed run bundles, `index.jsonl`, grading/timing/metrics artifacts, normalized transcripts, and optional `external_trace` link metadata.
1919
- Finish the vendor-neutral local export seams that let completed runs be re-read, compared, exported, and attached to non-Phoenix adapters without vendor-specific logic in core.
2020
- Keep OTLP/OpenInference mapping generic and reusable before building backend-specific upload or import paths.
2121

‎STRATEGY.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ AgentV stays repo-native and workspace-native: it runs or imports evaluations ar
2121

2222
- **Repo-native eval success** - Share of dogfood and example eval flows that run against real workspaces, hooks, repo materialization, or imported artifacts without extra infrastructure; measured by CI and manual UAT on canonical suites.
2323
- **Time to inspect a run** - Time from completed `agentv eval` to usable local review, compare, or report output from the canonical run bundle; measured through CLI and Dashboard/report workflows.
24-
- **Artifact portability coverage** - Share of integrations and follow-on workflows that consume `run_manifest.jsonl`, `summary.json`, trace sidecars, or imported run bundles instead of bespoke stores; measured by adapter smoke tests, docs, and example coverage.
24+
- **Artifact portability coverage** - Share of integrations and follow-on workflows that consume `index.jsonl`, `summary.json`, trace sidecars, or imported run bundles instead of bespoke stores; measured by adapter smoke tests, docs, and example coverage.
2525
- **Git-backed results reliability** - Success rate for publish, sync, resume, and WIP checkpoint flows across local branches and dedicated results repos; measured by integration tests and manual end-to-end verification.
2626

2727
## Tracks

‎apps/cli/src/cli.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { runCli } from './index.js';
66
// Forward SIGINT/SIGTERM to spawned provider subprocesses before exiting.
77
// Without this, Dashboard's `child.kill('SIGTERM')` against the CLI orphans
88
// any in-flight `claude`/`codex`/`pi`/`copilot` subprocess. The partial
9-
// `run_manifest.jsonl` is already row-by-row durable, so finished tests survive.
9+
// `index.jsonl` is already row-by-row durable, so finished tests survive.
1010
//
1111
// First signal: kill children, exit with the conventional 128+signal code.
1212
// Second signal within the same process: hard-exit so a hung child cannot

‎apps/cli/src/commands/eval/commands/run.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,12 @@ export const evalRunCommand = command({
5252
long: 'output',
5353
short: 'o',
5454
description:
55-
'Run artifact directory (writes run_manifest.jsonl, summary.json, and per-case artifacts)',
55+
'Run artifact directory (writes index.jsonl, summary.json, and per-case artifacts)',
5656
}),
5757
outputFormat: option({
5858
type: optional(string),
5959
long: 'output-format',
60-
description: '[Removed] Run directories always write run_manifest.jsonl',
60+
description: '[Removed] Run directories always write index.jsonl',
6161
}),
6262
experiment: option({
6363
type: optional(string),

‎apps/cli/src/commands/eval/result-layout.ts‎

Lines changed: 8 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,7 @@
1-
import { type Dirent, existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
1+
import { type Dirent, existsSync, readdirSync, statSync } from 'node:fs';
22
import path from 'node:path';
33

4-
export const RESULT_MANIFEST_FILENAME = 'run_manifest.jsonl';
5-
export const LEGACY_RESULT_INDEX_FILENAME = 'index.jsonl';
6-
// Backward-compatible export name retained for existing callers. New writes use
7-
// the row-level run manifest filename.
8-
export const RESULT_INDEX_FILENAME = RESULT_MANIFEST_FILENAME;
9-
export const RESULT_MANIFEST_FILENAMES = [
10-
RESULT_MANIFEST_FILENAME,
11-
LEGACY_RESULT_INDEX_FILENAME,
12-
] as const;
4+
export const RESULT_INDEX_FILENAME = 'index.jsonl';
135
export const RUN_SUMMARY_FILENAME = 'summary.json';
146
export const RESULTS_DIRNAME = 'results';
157
export const DEFAULT_EXPERIMENT_NAME = 'default';
@@ -73,48 +65,13 @@ export function resolveRunIndexPath(runDir: string): string {
7365
}
7466

7567
export function isRunManifestPath(filePath: string): boolean {
76-
return RESULT_MANIFEST_FILENAMES.includes(
77-
path.basename(filePath) as (typeof RESULT_MANIFEST_FILENAMES)[number],
78-
);
79-
}
80-
81-
function safeSummaryManifestPath(runDir: string, manifestPath: unknown): string | undefined {
82-
if (typeof manifestPath !== 'string' || manifestPath.trim().length === 0) {
83-
return undefined;
84-
}
85-
if (path.isAbsolute(manifestPath)) {
86-
return undefined;
87-
}
88-
const normalized = path.normalize(manifestPath);
89-
if (normalized.startsWith('..') || path.isAbsolute(normalized)) {
90-
return undefined;
91-
}
92-
return path.join(runDir, normalized);
93-
}
94-
95-
function resolveSummaryManifestPath(runDir: string): string | undefined {
96-
try {
97-
const summary = JSON.parse(readFileSync(path.join(runDir, RUN_SUMMARY_FILENAME), 'utf8')) as {
98-
manifest_path?: unknown;
99-
};
100-
const manifestPath = safeSummaryManifestPath(runDir, summary.manifest_path);
101-
return manifestPath && existsSync(manifestPath) ? manifestPath : undefined;
102-
} catch {
103-
return undefined;
104-
}
68+
return path.basename(filePath) === RESULT_INDEX_FILENAME;
10569
}
10670

10771
export function resolveExistingRunPrimaryPath(runDir: string): string | undefined {
108-
const summaryManifestPath = resolveSummaryManifestPath(runDir);
109-
if (summaryManifestPath) {
110-
return summaryManifestPath;
111-
}
112-
113-
for (const filename of RESULT_MANIFEST_FILENAMES) {
114-
const manifestPath = path.join(runDir, filename);
115-
if (existsSync(manifestPath)) {
116-
return manifestPath;
117-
}
72+
const indexPath = resolveRunIndexPath(runDir);
73+
if (existsSync(indexPath)) {
74+
return indexPath;
11875
}
11976

12077
return undefined;
@@ -178,9 +135,7 @@ export function resolveWorkspaceOrFilePath(filePath: string): string {
178135
`Result workspace contains multiple run manifests; pass one bundle directory or manifest: ${filePath}`,
179136
);
180137
}
181-
throw new Error(
182-
`Result workspace is missing ${RESULT_MANIFEST_FILENAME} or legacy ${LEGACY_RESULT_INDEX_FILENAME}: ${filePath}`,
183-
);
138+
throw new Error(`Result workspace is missing ${RESULT_INDEX_FILENAME}: ${filePath}`);
184139
}
185140

186141
export function resolveRunManifestPath(filePath: string): string {
@@ -190,7 +145,7 @@ export function resolveRunManifestPath(filePath: string): string {
190145

191146
if (!isRunManifestPath(filePath)) {
192147
throw new Error(
193-
`Expected a run workspace directory or ${RESULT_MANIFEST_FILENAME} manifest (legacy ${LEGACY_RESULT_INDEX_FILENAME} is also readable): ${filePath}`,
148+
`Expected a run workspace directory or ${RESULT_INDEX_FILENAME} manifest: ${filePath}`,
194149
);
195150
}
196151

‎apps/cli/src/commands/eval/run-eval.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ interface NormalizedOptions {
140140
readonly keepWorkspaces: boolean;
141141
/** Removed: use --output instead */
142142
readonly artifacts?: string;
143-
/** Removed: the run directory always uses run_manifest.jsonl */
143+
/** Removed: the run directory always uses index.jsonl */
144144
readonly outputFormat?: string;
145145
readonly graderTarget?: string;
146146
readonly model?: string;

‎apps/cli/src/commands/grade/index.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -625,7 +625,7 @@ export const gradeCommand = command({
625625
type: optional(string),
626626
long: 'output',
627627
short: 'o',
628-
description: 'Run artifact directory (writes run_manifest.jsonl and per-test artifacts)',
628+
description: 'Run artifact directory (writes index.jsonl and per-test artifacts)',
629629
}),
630630
response: option({
631631
type: optional(string),

0 commit comments

Comments
 (0)