Skip to content

Commit 30331c0

Browse files
christsoclaude
andauthored
feat(config): flatten results.export.* to results.* in .agentv/config.yaml (#1258)
The `results` block currently only contains `export` as a single sibling, so `results.export.{repo,path,auto_push,branch_prefix}` carries no information that flat `results.{repo,path,auto_push,branch_prefix}` wouldn't. The `export` name was also misleading — the same config governs sync/read (the cached clone), not just push. Rename: - `ResultsExportConfig` → `ResultsConfig` - `normalizeResultsExportConfig` → `normalizeResultsConfig` Inline `parseResultsExportConfig` into `parseResultsConfig`. Flatten the validator and all test fixtures, docs, and Studio user-facing copy. Breaking change: configs using `results.export.{repo,path,...}` must move to `results.{repo,path,...}`. The config is undocumented elsewhere and zero committed examples used it. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 724c4d7 commit 30331c0

11 files changed

Lines changed: 110 additions & 199 deletions

File tree

apps/cli/src/commands/results/remote.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import path from 'node:path';
33
import {
44
DEFAULT_THRESHOLD,
55
type EvaluationResult,
6-
type ResultsExportConfig,
6+
type ResultsConfig,
77
type ResultsRepoStatus,
88
directPushResults,
99
directorySizeBytes,
@@ -59,7 +59,7 @@ function getStatusMessage(error: unknown): string {
5959
return error instanceof Error ? error.message : String(error);
6060
}
6161

62-
function normalizeResultsExportConfig(config: ResultsExportConfig): Required<ResultsExportConfig> {
62+
function normalizeResultsConfig(config: ResultsConfig): Required<ResultsConfig> {
6363
return {
6464
repo: config.repo,
6565
path: config.path,
@@ -107,13 +107,13 @@ async function maybeWarnLargeArtifact(runDir: string): Promise<void> {
107107

108108
async function loadNormalizedResultsConfig(
109109
cwd: string,
110-
): Promise<Required<ResultsExportConfig> | undefined> {
110+
): Promise<Required<ResultsConfig> | undefined> {
111111
const repoRoot = (await findRepoRoot(cwd)) ?? cwd;
112112
const config = await loadConfig(path.join(cwd, '_'), repoRoot);
113-
if (!config?.results?.export) {
113+
if (!config?.results) {
114114
return undefined;
115115
}
116-
return normalizeResultsExportConfig(config.results.export);
116+
return normalizeResultsConfig(config.results);
117117
}
118118

119119
export function encodeRemoteRunId(filename: string): string {

apps/cli/test/commands/results/serve.test.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -501,9 +501,8 @@ describe('serve app', () => {
501501
writeFileSync(
502502
path.join(tempDir, '.agentv', 'config.yaml'),
503503
`results:
504-
export:
505-
repo: EntityProcess/agentv-evals
506-
path: autopilot-dev/runs
504+
repo: EntityProcess/agentv-evals
505+
path: autopilot-dev/runs
507506
`,
508507
);
509508

@@ -586,9 +585,8 @@ describe('serve app', () => {
586585
writeFileSync(
587586
path.join(tempDir, '.agentv', 'config.yaml'),
588587
`results:
589-
export:
590-
repo: EntityProcess/agentv-evals
591-
path: autopilot-dev/runs
588+
repo: EntityProcess/agentv-evals
589+
path: autopilot-dev/runs
592590
`,
593591
);
594592

apps/studio/src/components/RunSourceToolbar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ export function RunSourceToolbar({
8282
) : filter === 'all' ? (
8383
<p className="text-sm text-gray-500">
8484
Remote results are not configured. Add{' '}
85-
<code className="rounded bg-gray-800 px-1 text-gray-400">results.export</code> to{' '}
85+
<code className="rounded bg-gray-800 px-1 text-gray-400">results</code> to{' '}
8686
<code className="rounded bg-gray-800 px-1 text-gray-400">.agentv/config.yaml</code> to
8787
enable.
8888
</p>

apps/studio/src/routes/index.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -349,10 +349,7 @@ function RunsTabContent({
349349
<>
350350
<p className="text-lg text-gray-400">Remote results are not configured.</p>
351351
<p className="mt-2 text-sm text-gray-500">
352-
Add{' '}
353-
<code className="rounded bg-gray-800 px-2 py-1 text-cyan-400">
354-
results.export
355-
</code>{' '}
352+
Add <code className="rounded bg-gray-800 px-2 py-1 text-cyan-400">results</code>{' '}
356353
to{' '}
357354
<code className="rounded bg-gray-800 px-2 py-1 text-cyan-400">
358355
.agentv/config.yaml

apps/web/src/content/docs/docs/tools/studio.mdx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -210,14 +210,13 @@ Studio can display runs pushed to a remote git repository by other machines or C
210210

211211
### Configuration
212212

213-
Add a `results.export` block to `.agentv/config.yaml`:
213+
Add a `results` block to `.agentv/config.yaml`:
214214

215215
```yaml
216216
results:
217-
export:
218-
repo: EntityProcess/agentv-evals # GitHub repo (owner/repo or full URL)
219-
path: runs # Directory within the repo
220-
auto_push: true # Push directly to base branch after every eval run
217+
repo: EntityProcess/agentv-evals # GitHub repo (owner/repo or full URL)
218+
path: runs # Directory within the repo
219+
auto_push: true # Push directly to base branch after every eval run
221220
```
222221

223222
With `auto_push: true`, every `agentv eval` or `agentv pipeline bench` pushes results directly to the configured repo's base branch (e.g., `main`). Results appear immediately in Studio without requiring PR merges.

packages/core/src/evaluation/loaders/config-loader.ts

Lines changed: 10 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export type ExecutionDefaults = {
3636
readonly pool_slots?: number;
3737
};
3838

39-
export type ResultsExportConfig = {
39+
export type ResultsConfig = {
4040
readonly repo: string;
4141
readonly path: string;
4242
readonly auto_push?: boolean;
@@ -52,9 +52,7 @@ export type AgentVConfig = {
5252
readonly required_version?: string;
5353
readonly eval_patterns?: readonly string[];
5454
readonly execution?: ExecutionDefaults;
55-
readonly results?: {
56-
readonly export?: ResultsExportConfig;
57-
};
55+
readonly results?: ResultsConfig;
5856
readonly hooks?: HooksConfig;
5957
};
6058

@@ -560,10 +558,7 @@ export function parseExecutionDefaults(
560558
return Object.keys(result).length > 0 ? (result as ExecutionDefaults) : undefined;
561559
}
562560

563-
export function parseResultsConfig(
564-
raw: unknown,
565-
configPath: string,
566-
): AgentVConfig['results'] | undefined {
561+
export function parseResultsConfig(raw: unknown, configPath: string): ResultsConfig | undefined {
567562
if (raw === undefined || raw === null) {
568563
return undefined;
569564
}
@@ -572,60 +567,37 @@ export function parseResultsConfig(
572567
return undefined;
573568
}
574569

575-
const obj = raw as Record<string, unknown>;
576-
const exportConfig = parseResultsExportConfig(obj.export, configPath);
577-
if (!exportConfig) {
578-
return undefined;
579-
}
580-
581-
return { export: exportConfig };
582-
}
583-
584-
export function parseResultsExportConfig(
585-
raw: unknown,
586-
configPath: string,
587-
): ResultsExportConfig | undefined {
588-
if (raw === undefined || raw === null) {
589-
return undefined;
590-
}
591-
if (typeof raw !== 'object' || Array.isArray(raw)) {
592-
logWarning(`Invalid results.export in ${configPath}, expected object`);
593-
return undefined;
594-
}
595-
596570
const obj = raw as Record<string, unknown>;
597571
const repo = typeof obj.repo === 'string' ? obj.repo.trim() : '';
598-
const exportPath = typeof obj.path === 'string' ? obj.path.trim() : '';
572+
const resultsPath = typeof obj.path === 'string' ? obj.path.trim() : '';
599573

600574
if (!repo) {
601-
logWarning(`Invalid results.export.repo in ${configPath}, expected non-empty string`);
575+
logWarning(`Invalid results.repo in ${configPath}, expected non-empty string`);
602576
return undefined;
603577
}
604578

605-
if (!exportPath) {
606-
logWarning(`Invalid results.export.path in ${configPath}, expected non-empty string`);
579+
if (!resultsPath) {
580+
logWarning(`Invalid results.path in ${configPath}, expected non-empty string`);
607581
return undefined;
608582
}
609583

610584
if (obj.auto_push !== undefined && typeof obj.auto_push !== 'boolean') {
611-
logWarning(`Invalid results.export.auto_push in ${configPath}, expected boolean`);
585+
logWarning(`Invalid results.auto_push in ${configPath}, expected boolean`);
612586
return undefined;
613587
}
614588

615589
let branchPrefix: string | undefined;
616590
if (obj.branch_prefix !== undefined) {
617591
if (typeof obj.branch_prefix !== 'string' || obj.branch_prefix.trim().length === 0) {
618-
logWarning(
619-
`Invalid results.export.branch_prefix in ${configPath}, expected non-empty string`,
620-
);
592+
logWarning(`Invalid results.branch_prefix in ${configPath}, expected non-empty string`);
621593
return undefined;
622594
}
623595
branchPrefix = obj.branch_prefix.trim();
624596
}
625597

626598
return {
627599
repo,
628-
path: exportPath,
600+
path: resultsPath,
629601
...(typeof obj.auto_push === 'boolean' && { auto_push: obj.auto_push }),
630602
...(branchPrefix && { branch_prefix: branchPrefix }),
631603
};

packages/core/src/evaluation/results-repo.ts

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import path from 'node:path';
66
import { promisify } from 'node:util';
77

88
import { getAgentvHome } from '../paths.js';
9-
import type { ResultsExportConfig } from './loaders/config-loader.js';
9+
import type { ResultsConfig } from './loaders/config-loader.js';
1010

1111
const execFileAsync = promisify(execFile);
1212

@@ -61,9 +61,7 @@ function withFriendlyGitHubAuthError(error: unknown): Error {
6161
return new Error(message);
6262
}
6363

64-
export function normalizeResultsExportConfig(
65-
config: ResultsExportConfig,
66-
): Required<ResultsExportConfig> {
64+
export function normalizeResultsConfig(config: ResultsConfig): Required<ResultsConfig> {
6765
return {
6866
repo: config.repo.trim(),
6967
path: config.path.trim().replace(/^\/+|\/+$/g, ''),
@@ -172,7 +170,7 @@ async function updateCacheRepo(repoDir: string, baseBranch: string): Promise<voi
172170
await runGit(['pull', '--ff-only', 'origin', baseBranch], { cwd: repoDir });
173171
}
174172

175-
function updateStatusFile(config: ResultsExportConfig, patch: PersistedStatus): void {
173+
function updateStatusFile(config: ResultsConfig, patch: PersistedStatus): void {
176174
const cachePaths = getResultsRepoCachePaths(config.repo);
177175
const current = readPersistedStatus(cachePaths.statusFile);
178176
writePersistedStatus(cachePaths.statusFile, {
@@ -181,8 +179,8 @@ function updateStatusFile(config: ResultsExportConfig, patch: PersistedStatus):
181179
});
182180
}
183181

184-
export async function ensureResultsRepoClone(config: ResultsExportConfig): Promise<string> {
185-
const normalized = normalizeResultsExportConfig(config);
182+
export async function ensureResultsRepoClone(config: ResultsConfig): Promise<string> {
183+
const normalized = normalizeResultsConfig(config);
186184
const cachePaths = getResultsRepoCachePaths(normalized.repo);
187185
mkdirSync(cachePaths.rootDir, { recursive: true });
188186

@@ -208,7 +206,7 @@ export async function ensureResultsRepoClone(config: ResultsExportConfig): Promi
208206
return cachePaths.repoDir;
209207
}
210208

211-
export function getResultsRepoStatus(config?: ResultsExportConfig): ResultsRepoStatus {
209+
export function getResultsRepoStatus(config?: ResultsConfig): ResultsRepoStatus {
212210
if (!config) {
213211
return {
214212
configured: false,
@@ -218,7 +216,7 @@ export function getResultsRepoStatus(config?: ResultsExportConfig): ResultsRepoS
218216
};
219217
}
220218

221-
const normalized = normalizeResultsExportConfig(config);
219+
const normalized = normalizeResultsConfig(config);
222220
const cachePaths = getResultsRepoCachePaths(normalized.repo);
223221
const persisted = readPersistedStatus(cachePaths.statusFile);
224222

@@ -235,8 +233,8 @@ export function getResultsRepoStatus(config?: ResultsExportConfig): ResultsRepoS
235233
};
236234
}
237235

238-
export async function syncResultsRepo(config: ResultsExportConfig): Promise<ResultsRepoStatus> {
239-
const normalized = normalizeResultsExportConfig(config);
236+
export async function syncResultsRepo(config: ResultsConfig): Promise<ResultsRepoStatus> {
237+
const normalized = normalizeResultsConfig(config);
240238

241239
try {
242240
const repoDir = await ensureResultsRepoClone(normalized);
@@ -257,10 +255,10 @@ export async function syncResultsRepo(config: ResultsExportConfig): Promise<Resu
257255
}
258256

259257
export async function checkoutResultsRepoBranch(
260-
config: ResultsExportConfig,
258+
config: ResultsConfig,
261259
branchName: string,
262260
): Promise<CheckedOutResultsRepoBranch> {
263-
const normalized = normalizeResultsExportConfig(config);
261+
const normalized = normalizeResultsConfig(config);
264262
const repoDir = await ensureResultsRepoClone(normalized);
265263
const baseBranch = await resolveDefaultBranch(repoDir);
266264
await updateCacheRepo(repoDir, baseBranch);
@@ -274,10 +272,10 @@ export async function checkoutResultsRepoBranch(
274272
}
275273

276274
export async function prepareResultsRepoBranch(
277-
config: ResultsExportConfig,
275+
config: ResultsConfig,
278276
branchName: string,
279277
): Promise<PreparedResultsRepoBranch> {
280-
const normalized = normalizeResultsExportConfig(config);
278+
const normalized = normalizeResultsConfig(config);
281279
const cloneDir = await ensureResultsRepoClone(normalized);
282280
const baseBranch = await resolveDefaultBranch(cloneDir);
283281
await updateCacheRepo(cloneDir, baseBranch);
@@ -312,8 +310,8 @@ export async function stageResultsArtifacts(params: {
312310
await cp(params.sourceDir, params.destinationDir, { recursive: true });
313311
}
314312

315-
export function resolveResultsRepoRunsDir(config: ResultsExportConfig): string {
316-
const normalized = normalizeResultsExportConfig(config);
313+
export function resolveResultsRepoRunsDir(config: ResultsConfig): string {
314+
const normalized = normalizeResultsConfig(config);
317315
return path.join(
318316
getResultsRepoCachePaths(normalized.repo).repoDir,
319317
...normalized.path.split('/'),
@@ -354,11 +352,11 @@ export async function commitAndPushResultsBranch(params: {
354352
}
355353

356354
export async function pushResultsRepoBranch(
357-
config: ResultsExportConfig,
355+
config: ResultsConfig,
358356
branchName: string,
359357
cwd?: string,
360358
): Promise<void> {
361-
const normalized = normalizeResultsExportConfig(config);
359+
const normalized = normalizeResultsConfig(config);
362360
await runGit(['push', '-u', 'origin', branchName], {
363361
cwd: cwd ?? getResultsRepoCachePaths(normalized.repo).repoDir,
364362
});
@@ -405,12 +403,12 @@ const DIRECT_PUSH_MAX_RETRIES = 3;
405403
* Returns true if artifacts were pushed, false if no changes were detected.
406404
*/
407405
export async function directPushResults(params: {
408-
readonly config: ResultsExportConfig;
406+
readonly config: ResultsConfig;
409407
readonly sourceDir: string;
410408
readonly destinationPath: string;
411409
readonly commitMessage: string;
412410
}): Promise<boolean> {
413-
const normalized = normalizeResultsExportConfig(params.config);
411+
const normalized = normalizeResultsConfig(params.config);
414412
const repoDir = await ensureResultsRepoClone(normalized);
415413
const baseBranch = await resolveDefaultBranch(repoDir);
416414
await updateCacheRepo(repoDir, baseBranch);

0 commit comments

Comments
 (0)