Skip to content

Commit 905a671

Browse files
committed
feat: add eval regression gates
1 parent 5c33e82 commit 905a671

15 files changed

Lines changed: 794 additions & 14 deletions

File tree

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,16 @@ Operational docs: https://swarmclaw.ai/docs/observability
399399

400400
## Releases
401401

402+
### v1.9.7 Highlights
403+
404+
Bundled eval-gate release: approved baselines, regression checks, and Quality Center release gates for repeatable eval evidence.
405+
406+
- **Eval regression baselines.** Operators can snapshot the latest scenario or suite score as an approved baseline with minimum score and regression allowance settings.
407+
- **Release gate API.** `/api/eval/gate` compares current eval evidence against thresholds and baselines, while `/api/eval/baselines` lists and updates approved baselines.
408+
- **CLI gate checks.** `swarmclaw eval gate`, `swarmclaw eval baselines`, and `swarmclaw eval baseline-set` expose the same release-gate workflow from automation.
409+
- **Quality Center gate panel.** Eval Lab now shows pass/warn/fail status, latest-run coverage, current score, baseline score, regression points, and actionable checks.
410+
- **Public-source hygiene.** Generic implementation comments now describe SwarmClaw behavior without naming internal comparison sources.
411+
402412
### v1.9.6 Highlights
403413

404414
Bundled eval-environment release: validation preflights, deterministic eval workspaces, and clearer operator readiness before spending run budget.

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@swarmclawai/swarmclaw",
3-
"version": "1.9.6",
3+
"version": "1.9.7",
44
"description": "Build and run autonomous AI agents with OpenClaw, Hermes, multiple model providers, orchestration, delegation, memory, skills, schedules, and chat connectors.",
55
"main": "electron-dist/main.js",
66
"license": "MIT",
@@ -87,7 +87,7 @@
8787
"test:cli": "node --test src/cli/*.test.js bin/*.test.js scripts/electron-after-pack.test.mjs scripts/ensure-sandbox-browser-image.test.mjs scripts/postinstall.test.mjs scripts/run-next-build.test.mjs scripts/run-next-typegen.test.mjs",
8888
"test:setup": "tsx --test src/app/api/setup/check-provider/route.test.ts src/lib/server/provider-model-discovery.test.ts src/components/auth/setup-wizard/utils.test.ts src/components/auth/setup-wizard/types.test.ts src/hooks/setup-done-detection.test.ts src/lib/setup-defaults.test.ts src/lib/server/storage-auth.test.ts src/lib/server/storage-auth-docker.test.ts",
8989
"test:openclaw": "tsx --test src/lib/openclaw/openclaw-agent-id.test.ts src/lib/openclaw/openclaw-endpoint.test.ts src/lib/server/agents/agent-runtime-config.test.ts src/lib/server/build-llm.test.ts src/lib/server/connectors/connector-routing.test.ts src/lib/server/connectors/openclaw.test.ts src/lib/server/connectors/swarmdock.test.ts src/lib/server/gateway/protocol.test.ts src/lib/server/gateways/gateway-topology.test.ts src/lib/server/llm-response-cache.test.ts src/lib/server/mcp-conformance.test.ts src/lib/server/openclaw/agent-resolver.test.ts src/lib/server/openclaw/deploy.test.ts src/lib/server/openclaw/skills-normalize.test.ts src/lib/server/session-tools/openclaw-nodes.test.ts src/lib/server/session-tools/swarmdock.test.ts src/lib/server/tasks/task-quality-gate.test.ts src/lib/server/tasks/task-validation.test.ts src/lib/server/tool-capability-policy.test.ts src/lib/providers/openai.test.ts src/lib/providers/openclaw-exports.test.ts src/app/api/gateways/topology-route.test.ts src/app/api/openclaw/dashboard-url/route.test.ts",
90-
"test:runtime": "tsx --test src/lib/a2a/agent-card.test.ts src/lib/strip-internal-metadata.test.ts src/lib/provider-sets.test.ts src/lib/providers/opencode-cli.test.ts src/lib/providers/cli-provider-metadata.test.ts src/lib/providers/cli-utils.test.ts src/lib/providers/generic-cli.test.ts src/lib/server/agents/delegation-advisory.test.ts src/lib/server/cli-provider-readiness.test.ts src/lib/server/provider-health.test.ts src/lib/server/mcp-gateway-runtime.test.ts src/lib/server/mcp-connection-pool.test.ts src/lib/server/knowledge-sources.test.ts src/lib/server/extension-managed-resources.test.ts src/lib/server/eval/environment-plan.test.ts src/lib/server/chat-execution/chat-execution-grounding.test.ts src/lib/server/chat-execution/chat-turn-preparation.test.ts src/lib/server/chat-execution/iteration-timers.test.ts src/lib/server/chat-execution/post-stream-finalization.test.ts src/lib/server/chat-execution/reasoning-tag-scrubber.test.ts src/lib/server/chats/clear-undo-snapshots.test.ts src/lib/server/connectors/email.test.ts src/lib/server/protocols/protocol-service.test.ts src/lib/server/runtime/run-ledger.test.ts src/lib/server/runtime/queue-retry-policy.test.ts src/lib/server/runs/run-brief.test.ts src/lib/server/operations/operation-pulse.test.ts src/lib/server/artifacts/artifact-resolver.test.ts src/lib/server/observability/otel-config.test.ts src/lib/server/safe-parse-body.test.ts src/lib/server/missions/mission-templates.test.ts src/lib/server/sharing/share-link-repository.test.ts src/lib/server/sharing/share-resolver.test.ts src/lib/server/tasks/task-execution-workspace.test.ts src/lib/server/tasks/task-service.test.ts src/lib/server/session-tools/execute.test.ts src/lib/server/session-tools/manage-tasks.test.ts src/lib/app/view-constants.test.ts src/lib/quality/quality-summary.test.ts src/app/api/approvals/route.test.ts src/app/api/agents/agents-route.test.ts src/app/api/tasks/tasks-route.test.ts src/app/api/tasks/task-workspace-route.test.ts src/app/api/chats/chat-route.test.ts src/app/api/chats/clear-route.test.ts src/app/api/chats/compact-route.test.ts src/app/api/chats/context-status-route.test.ts src/app/api/connectors/connector-doctor-route.test.ts src/app/api/extensions/managed-resources/route.test.ts src/app/api/healthz/route.test.ts src/app/api/logs/route.test.ts src/app/api/portability/export/route.test.ts src/app/api/portability/import/route.test.ts src/app/api/providers/[id]/route.test.ts src/app/api/tts/route.test.ts",
90+
"test:runtime": "tsx --test src/lib/a2a/agent-card.test.ts src/lib/strip-internal-metadata.test.ts src/lib/provider-sets.test.ts src/lib/providers/opencode-cli.test.ts src/lib/providers/cli-provider-metadata.test.ts src/lib/providers/cli-utils.test.ts src/lib/providers/generic-cli.test.ts src/lib/server/agents/delegation-advisory.test.ts src/lib/server/cli-provider-readiness.test.ts src/lib/server/provider-health.test.ts src/lib/server/mcp-gateway-runtime.test.ts src/lib/server/mcp-connection-pool.test.ts src/lib/server/knowledge-sources.test.ts src/lib/server/extension-managed-resources.test.ts src/lib/server/eval/baseline.test.ts src/lib/server/eval/environment-plan.test.ts src/lib/server/chat-execution/chat-execution-grounding.test.ts src/lib/server/chat-execution/chat-turn-preparation.test.ts src/lib/server/chat-execution/iteration-timers.test.ts src/lib/server/chat-execution/post-stream-finalization.test.ts src/lib/server/chat-execution/reasoning-tag-scrubber.test.ts src/lib/server/chats/clear-undo-snapshots.test.ts src/lib/server/connectors/email.test.ts src/lib/server/protocols/protocol-service.test.ts src/lib/server/runtime/run-ledger.test.ts src/lib/server/runtime/queue-retry-policy.test.ts src/lib/server/runs/run-brief.test.ts src/lib/server/operations/operation-pulse.test.ts src/lib/server/artifacts/artifact-resolver.test.ts src/lib/server/observability/otel-config.test.ts src/lib/server/safe-parse-body.test.ts src/lib/server/missions/mission-templates.test.ts src/lib/server/sharing/share-link-repository.test.ts src/lib/server/sharing/share-resolver.test.ts src/lib/server/tasks/task-execution-workspace.test.ts src/lib/server/tasks/task-service.test.ts src/lib/server/session-tools/execute.test.ts src/lib/server/session-tools/manage-tasks.test.ts src/lib/app/view-constants.test.ts src/lib/quality/quality-summary.test.ts src/app/api/approvals/route.test.ts src/app/api/agents/agents-route.test.ts src/app/api/tasks/tasks-route.test.ts src/app/api/tasks/task-workspace-route.test.ts src/app/api/chats/chat-route.test.ts src/app/api/chats/clear-route.test.ts src/app/api/chats/compact-route.test.ts src/app/api/chats/context-status-route.test.ts src/app/api/connectors/connector-doctor-route.test.ts src/app/api/extensions/managed-resources/route.test.ts src/app/api/healthz/route.test.ts src/app/api/logs/route.test.ts src/app/api/portability/export/route.test.ts src/app/api/portability/import/route.test.ts src/app/api/providers/[id]/route.test.ts src/app/api/tts/route.test.ts",
9191
"test:builder": "tsx --test src/features/protocols/builder/utils/nodes-to-template.test.ts src/features/protocols/builder/utils/template-to-nodes.test.ts src/features/protocols/builder/validators/dag-validator.test.ts",
9292
"test:e2e": "node --import tsx scripts/browser-e2e-smoke.ts",
9393
"test:mcp:conformance": "node --import tsx ./scripts/mcp-conformance-check.ts",

scripts/browser-e2e-smoke.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,13 @@ async function runBrowserSmoke(baseUrl: string): Promise<void> {
315315
await waitForPageText(page, '/quality', {
316316
anyText: ['Quality', 'Operator Quality Center', 'Eval Lab', 'Approval Desk'],
317317
})
318+
await waitForPageText(page, '/quality?tab=evals', {
319+
anyText: ['Eval Lab'],
320+
})
321+
await page.waitForFunction(() => {
322+
const text = document.body?.innerText || ''
323+
return text.includes('Validation environment') && text.includes('Regression gate')
324+
}, { timeout: PAGE_TIMEOUT_MS })
318325

319326
const taskRes = await fetchWithTimeout(new URL('/api/tasks', baseUrl).toString(), {
320327
method: 'POST',
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { NextResponse } from 'next/server'
2+
import { z } from 'zod'
3+
import { evaluateEvalGate, listEvalBaselinesForAgent, setEvalBaseline } from '@/lib/server/eval/baseline'
4+
import { errorMessage } from '@/lib/shared-utils'
5+
6+
const BaselineSchema = z.object({
7+
agentId: z.string().min(1),
8+
scenarioId: z.string().min(1).nullable().optional(),
9+
suite: z.string().min(1).nullable().optional(),
10+
minPercent: z.number().min(0).max(100).nullable().optional(),
11+
maxRegressionPoints: z.number().min(0).max(100).nullable().optional(),
12+
label: z.string().max(160).nullable().optional(),
13+
notes: z.string().max(1_000).nullable().optional(),
14+
})
15+
16+
export async function GET(req: Request) {
17+
try {
18+
const { searchParams } = new URL(req.url)
19+
const agentId = searchParams.get('agentId')
20+
return NextResponse.json(listEvalBaselinesForAgent(agentId))
21+
} catch (err: unknown) {
22+
return NextResponse.json(
23+
{ error: errorMessage(err) },
24+
{ status: 500 },
25+
)
26+
}
27+
}
28+
29+
export async function POST(req: Request) {
30+
try {
31+
const body: unknown = await req.json()
32+
const parsed = BaselineSchema.safeParse(body)
33+
if (!parsed.success) {
34+
return NextResponse.json(
35+
{ error: parsed.error.issues.map((issue) => issue.message).join(', ') },
36+
{ status: 400 },
37+
)
38+
}
39+
40+
const baseline = setEvalBaseline(parsed.data)
41+
const gate = evaluateEvalGate({
42+
agentId: parsed.data.agentId,
43+
scenarioId: parsed.data.scenarioId,
44+
suite: parsed.data.suite,
45+
minPercent: parsed.data.minPercent,
46+
maxRegressionPoints: parsed.data.maxRegressionPoints,
47+
})
48+
return NextResponse.json({ baseline, gate })
49+
} catch (err: unknown) {
50+
return NextResponse.json(
51+
{ error: errorMessage(err) },
52+
{ status: 500 },
53+
)
54+
}
55+
}

src/app/api/eval/gate/route.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { NextResponse } from 'next/server'
2+
import { evaluateEvalGate } from '@/lib/server/eval/baseline'
3+
import { errorMessage } from '@/lib/shared-utils'
4+
5+
function parseNumberParam(value: string | null): number | null {
6+
if (value == null || value.trim() === '') return null
7+
const parsed = Number(value)
8+
return Number.isFinite(parsed) ? parsed : null
9+
}
10+
11+
export async function GET(req: Request) {
12+
try {
13+
const { searchParams } = new URL(req.url)
14+
const agentId = searchParams.get('agentId') || ''
15+
if (!agentId) {
16+
return NextResponse.json(
17+
{ error: 'agentId is required' },
18+
{ status: 400 },
19+
)
20+
}
21+
22+
const result = evaluateEvalGate({
23+
agentId,
24+
scenarioId: searchParams.get('scenarioId'),
25+
suite: searchParams.get('suite'),
26+
minPercent: parseNumberParam(searchParams.get('minPercent')),
27+
maxRegressionPoints: parseNumberParam(searchParams.get('maxRegressionPoints')),
28+
})
29+
return NextResponse.json(result)
30+
} catch (err: unknown) {
31+
return NextResponse.json(
32+
{ error: errorMessage(err) },
33+
{ status: 500 },
34+
)
35+
}
36+
}

src/cli/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,9 +232,12 @@ const COMMAND_GROUPS = [
232232
cmd('suites', 'GET', '/eval/suites', 'List available eval suites (core, swe-bench-lite, gaia-l1, ...)'),
233233
cmd('status', 'GET', '/eval/run', 'Get eval run status'),
234234
cmd('environment', 'GET', '/eval/environments', 'Preview validation environment readiness for an eval'),
235+
cmd('baselines', 'GET', '/eval/baselines', 'List eval regression baselines'),
236+
cmd('gate', 'GET', '/eval/gate', 'Check the latest eval score against thresholds and baseline'),
235237
cmd('run', 'POST', '/eval/run', 'Run an eval scenario against an agent', { expectsJsonBody: true }),
236238
cmd('suite', 'POST', '/eval/suite', 'Run a full eval suite against an agent (pass { suite: "swe-bench-lite" } in body)', { expectsJsonBody: true }),
237239
cmd('environment-prepare', 'POST', '/eval/environments', 'Prepare validation environment readiness for an eval', { expectsJsonBody: true }),
240+
cmd('baseline-set', 'POST', '/eval/baselines', 'Set an eval regression baseline from latest completed runs', { expectsJsonBody: true }),
238241
],
239242
},
240243
{

0 commit comments

Comments
 (0)