| import { execFile } from 'node:child_process'; |
| import { existsSync } from 'node:fs'; |
| import { mkdir, readFile, readdir, rm } from 'node:fs/promises'; |
| import { writeFile } from 'node:fs/promises'; |
| import { basename, delimiter, join } from 'node:path'; |
| import { promisify } from 'node:util'; |
| import { |
| PROVIDER_DEFAULTS, |
| providerAuthRequiresSecret, |
| type ProviderType, |
| } from '@maka/core/llm-connections'; |
| import type { ThinkingLevel } from '@maka/core/model-thinking'; |
| import { fetchGitHubCopilotModels, isSupportedGitHubCopilotAccountToken } from '@maka/runtime'; |
| import { |
| selectHarborCellTokenSummary, |
| validateHarborCellExecutionIdentity, |
| validateHarborCellOutput, |
| validateHarborCellTokenSummary, |
| type HarborCellExecutionIdentity, |
| type HarborCellOutput, |
| } from './cell-output.js'; |
| import { |
| FixedPromptBudgetExhaustedError, |
| type FixedPromptBudgetExhaustedError as FixedPromptBudgetExhaustedErrorType, |
| type TaskRunInput, |
| type TaskRunOutput, |
| type TaskRunner, |
| } from './fixed-prompt-controller.js'; |
| import { buildAgentRepoMounts } from './agent-repo-mount.js'; |
| import { |
| MAKA_NODE_TOOLCHAIN_CONTAINER_PATH, |
| MAKA_NODE_TOOLCHAIN_FINGERPRINT, |
| } from './maka-node-toolchain.js'; |
| import type { |
| HarborTrialGrade, |
| HarborVerifierAttempt, |
| HarborVerifierOutcome, |
| } from './fixed-prompt-wal-types.js'; |
| import { |
| HARBOR_ORACLE_EXECUTION_POLICY, |
| HARBOR_ORACLE_MAX_ATTEMPTS, |
| resolveHarnessOracleWatchdogTimeoutMs, |
| type HarnessOracleTaskResult, |
| } from './harness-oracle-policy.js'; |
| import { |
| summarizeProviderTelemetry, |
| startProviderAuthProxy, |
| type ProviderRequestTelemetry, |
| type ProviderTokenUsage, |
| type ProviderUpstreamCredentialResolver, |
| } from './provider-auth-proxy.js'; |
| import { |
| harnessAgentImportPath, |
| providerProxyClientAuthMode, |
| providerProxyClientBaseUrl, |
| providerProxyUpstreamAuthMode, |
| providerProxyUpstreamBaseUrl, |
| providerProxyUsageProtocol, |
| type HarnessAgentId, |
| } from './harness-agent-registry.js'; |
| import { |
| isSensitiveEnvName, |
| providerBaseUrlFromEnv, |
| providerCredentialEnv, |
| } from './provider-env.js'; |
| import { lenientPositiveIntEnv } from './headless-run-env.js'; |
| import { |
| OPENCODE_TOOLCHAIN_CONTAINER_PATH, |
| OPENCODE_TOOLCHAIN_FINGERPRINT, |
| OPENCODE_TOOLCHAIN_SPEC, |
| } from './opencode-toolchain.js'; |
| import { |
| KIMI_CODE_TOOLCHAIN_CONTAINER_PATH, |
| KIMI_CODE_TOOLCHAIN_FINGERPRINT, |
| KIMI_CODE_TOOLCHAIN_SPEC, |
| } from './kimi-code-toolchain.js'; |
| import { |
| CODEX_TOOLCHAIN_CONTAINER_PATH, |
| CODEX_TOOLCHAIN_FINGERPRINT, |
| CODEX_TOOLCHAIN_SPEC, |
| } from './codex-toolchain.js'; |
| import { |
| CLAUDE_CODE_TOOLCHAIN_CONTAINER_PATH, |
| CLAUDE_CODE_TOOLCHAIN_FINGERPRINT, |
| CLAUDE_CODE_TOOLCHAIN_SPEC, |
| } from './claude-code-toolchain.js'; |
| import { |
| REASONIX_TOOLCHAIN_CONTAINER_PATH, |
| REASONIX_TOOLCHAIN_FINGERPRINT, |
| REASONIX_TOOLCHAIN_SPEC, |
| } from './reasonix-toolchain.js'; |
| |
| import { agentPhaseTimeoutSec, settlementGraceSec } from './maka-settlement.js'; |
| |
| export { MAKA_SETTLEMENT_GRACE_SEC } from './maka-settlement.js'; |
| |
| const execFileAsync = promisify(execFile); |
| |
| /** |
| * Every competitor arm is pinned the same way: a prepared toolchain directory |
| * bind-mounted read-only, a version that must match the pinned spec, and a |
| * fingerprint the in-container adapter re-verifies before it runs. Only Maka |
| * runs from the repo mount, so it has no entry. |
| */ |
| const COMPETITOR_TOOLCHAINS: Readonly< |
| Record< |
| Exclude<HarnessAgentId, 'maka'>, |
| { |
| readonly label: string; |
| readonly optionKey: keyof Pick< |
| HarborTaskRunnerOptions, |
| | 'opencodeToolchainPath' |
| | 'kimiCodeToolchainPath' |
| | 'codexToolchainPath' |
| | 'claudeCodeToolchainPath' |
| | 'reasonixToolchainPath' |
| >; |
| readonly version: string; |
| readonly containerPath: string; |
| readonly fingerprint: string; |
| readonly fingerprintEnvKey: string; |
| } |
| > |
| > = { |
| opencode: { |
| label: 'OpenCode', |
| optionKey: 'opencodeToolchainPath', |
| version: OPENCODE_TOOLCHAIN_SPEC.opencode.version, |
| containerPath: OPENCODE_TOOLCHAIN_CONTAINER_PATH, |
| fingerprint: OPENCODE_TOOLCHAIN_FINGERPRINT, |
| fingerprintEnvKey: 'MAKA_OPENCODE_TOOLCHAIN_FINGERPRINT', |
| }, |
| 'kimi-code': { |
| label: 'Kimi Code', |
| optionKey: 'kimiCodeToolchainPath', |
| version: KIMI_CODE_TOOLCHAIN_SPEC.kimiCode.version, |
| containerPath: KIMI_CODE_TOOLCHAIN_CONTAINER_PATH, |
| fingerprint: KIMI_CODE_TOOLCHAIN_FINGERPRINT, |
| fingerprintEnvKey: 'MAKA_KIMI_CODE_TOOLCHAIN_FINGERPRINT', |
| }, |
| codex: { |
| label: 'Codex', |
| optionKey: 'codexToolchainPath', |
| version: CODEX_TOOLCHAIN_SPEC.codex.version, |
| containerPath: CODEX_TOOLCHAIN_CONTAINER_PATH, |
| fingerprint: CODEX_TOOLCHAIN_FINGERPRINT, |
| fingerprintEnvKey: 'MAKA_CODEX_TOOLCHAIN_FINGERPRINT', |
| }, |
| 'claude-code': { |
| label: 'Claude Code', |
| optionKey: 'claudeCodeToolchainPath', |
| version: CLAUDE_CODE_TOOLCHAIN_SPEC.claudeCode.version, |
| containerPath: CLAUDE_CODE_TOOLCHAIN_CONTAINER_PATH, |
| fingerprint: CLAUDE_CODE_TOOLCHAIN_FINGERPRINT, |
| fingerprintEnvKey: 'MAKA_CLAUDE_CODE_TOOLCHAIN_FINGERPRINT', |
| }, |
| reasonix: { |
| label: 'Reasonix', |
| optionKey: 'reasonixToolchainPath', |
| version: REASONIX_TOOLCHAIN_SPEC.reasonix.version, |
| containerPath: REASONIX_TOOLCHAIN_CONTAINER_PATH, |
| fingerprint: REASONIX_TOOLCHAIN_FINGERPRINT, |
| fingerprintEnvKey: 'MAKA_REASONIX_TOOLCHAIN_FINGERPRINT', |
| }, |
| }; |
| const TRIAL_CELL_OUTPUT = 'agent/maka-cell-output.json'; |
| const TRIAL_EXECUTION_IDENTITY = 'agent/maka-cell-execution-identity.json'; |
| const TRIAL_USAGE_CHECKPOINT = 'agent/maka-cell-usage-checkpoint.json'; |
| const TRIAL_RUNTIME_EVENTS = 'agent/runtime-events.jsonl'; |
| const TRIAL_TASK_RUN_TRACE_EVENTS = 'agent/trace-events.jsonl'; |
| const TRIAL_REWARD = 'verifier/reward.txt'; |
| const TRIAL_VERIFIER_STDOUT = 'verifier/test-stdout.txt'; |
| const TRIAL_VERIFIER_OUTCOME = 'verifier/maka-verifier-outcome.json'; |
| const TRIAL_RESULT = 'result.json'; |
| const TRIAL_TASK_RUN_TRACE_EVENTS_ROOT = 'agent/maka-task-run/runs/sessions'; |
| const TRIAL_LEGACY_TRACE_EVENTS_ROOT = 'agent/maka-storage/sessions'; |
| const PROVIDER_REQUEST_TELEMETRY = 'provider-request-telemetry.json'; |
| |
| /** A Harbor-side failure (build/docker/timeout/missing artifact) — NOT a benchmark |
| * result. The controller turns a thrown error into an infra_failed event so it is |
| * excluded from scoring instead of polluting the KEEP/DISCARD decision as reward 0. */ |
| /** Shared across runners: the last proxied provider request must have |
| * completed. A 200 stream without its protocol terminal event means the |
| * provider response is incomplete — the trial is an infra failure, never a |
| * graded model failure. |
| * |
| * A settled trial's tail request is expected to be truncated, but only in one |
| * specific way: killing the agent tears the request down from the client side, |
| * which the proxy records as `aborted` (provider-auth-proxy.ts sets it from |
| * `signal.aborted`). `interrupted` and `failed` are the upstream's doing and |
| * are not explained by the agent phase ending, so they stay infra however the |
| * trial settled — that is what keeps a provider outage out of the denominator |
| * instead of scoring it as the agent's zero. */ |
| export function incompleteTerminalProviderRequest( |
| providerTelemetry: readonly ProviderRequestTelemetry[], |
| agentPhaseSettled: boolean, |
| ): ProviderRequestTelemetry | undefined { |
| const terminal = providerTelemetry.at(-1); |
| if (!terminal || terminal.outcome === 'completed') return undefined; |
| if (agentPhaseSettled && terminal.outcome === 'aborted') return undefined; |
| return terminal; |
| } |
| |
| /** Shared across runners: a verdict is this arm's evidence only if the agent got |
| * the run it was given. A tail request the upstream cut short says it did not, |
| * so the grade does not travel — the same boundary that makes a settled trial |
| * infra, applied where the trial ends as a budget exhaustion instead. Without |
| * it, a provider outage that a verifier happened to pass would be recorded as a |
| * pass on the one path that skips the infra check. `aborted` stays exempt: the |
| * agent phase ended, and tearing the request down is what that looks like. */ |
| export function trialGradeSurvivingProviderOutage<T>( |
| grade: T | undefined, |
| providerTelemetry: readonly ProviderRequestTelemetry[], |
| ): T | undefined { |
| return incompleteTerminalProviderRequest(providerTelemetry, true) ? undefined : grade; |
| } |
| |
| export class HarborInfraError extends Error { |
| constructor( |
| message: string, |
| readonly detail?: string, |
| readonly kind: 'infra_failed' | 'timed_out' = 'infra_failed', |
| readonly artifactRefs?: { providerTelemetryPath?: string }, |
| ) { |
| super(message); |
| this.name = 'HarborInfraError'; |
| } |
| } |
| |
| /** Structural shape shared by HarborInfraError and PierInfraError — PierInfraError |
| * is deliberately NOT a subclass (the controller classifies by behavior, never |
| * identity), so the shared helpers are typed against the structure, not the class. */ |
| export interface InfraErrorLike extends Error { |
| readonly detail?: string; |
| readonly kind: 'infra_failed' | 'timed_out'; |
| readonly artifactRefs?: { providerTelemetryPath?: string }; |
| } |
| |
| /** Constructor shape shared by HarborInfraError and PierInfraError. The shared |
| * trial-mapping helpers below take this so they throw the calling runner's own |
| * error type — diagnostics must keep naming the failing harness. */ |
| export type InfraErrorCtor = new ( |
| message: string, |
| detail?: string, |
| kind?: 'infra_failed' | 'timed_out', |
| artifactRefs?: { providerTelemetryPath?: string }, |
| ) => InfraErrorLike; |
| |
| export interface HarborTaskPricing { |
| inputUsdPer1M: number; |
| outputUsdPer1M: number; |
| cacheReadUsdPer1M?: number; |
| cacheWriteUsdPer1M?: number; |
| source?: string; |
| } |
| |
| export type HarborBillingMode = 'metered' | 'account-plan'; |
| |
| export interface HarborTaskRunnerOptions { |
| /** Host path to the maka repo, mounted read-only at /opt/maka-agent. */ |
| makaRepoPath: string; |
| /** Harbor adapter under test (default: Maka). */ |
| agent?: HarnessAgentId; |
| /** Version passed to Harbor's installed-agent adapter. */ |
| agentVersion?: string; |
| /** Prepared OpenCode toolchain mounted read-only into task containers. */ |
| opencodeToolchainPath?: string; |
| /** Prepared Kimi Code toolchain mounted read-only into task containers. */ |
| kimiCodeToolchainPath?: string; |
| /** Prepared Codex CLI toolchain mounted read-only into task containers. */ |
| codexToolchainPath?: string; |
| /** Prepared Claude Code native toolchain mounted read-only into task containers. */ |
| claudeCodeToolchainPath?: string; |
| /** Prepared Reasonix toolchain mounted read-only into task containers. */ |
| reasonixToolchainPath?: string; |
| /** |
| * Where the Maka arm runs. `host-bridge` keeps the controller on the host and |
| * bridges tool calls in; `task-container` puts it where every competitor arm |
| * already is, which is what makes the arms answer for the same environment. |
| */ |
| makaPlacement?: 'host-bridge' | 'task-container'; |
| /** |
| * Prepared Maka Node toolchain, mounted read-only into task containers. |
| * Required by `task-container` placement: the repo mount carries Maka's code |
| * but nothing in the task image is pinned to execute it. |
| */ |
| makaNodeToolchainPath?: string; |
| /** Explicit Docker target platform shared by comparison arms. */ |
| dockerPlatform?: 'linux/amd64'; |
| /** |
| * Compose overlay that points the task container's Ubuntu apt hosts at a |
| * mirror. Layered after the harness compose file so it adds `extra_hosts` |
| * entries beside the host-gateway mapping rather than replacing them. |
| */ |
| aptMirrorComposePath?: string; |
| /** Base directory under which each task gets an isolated per-task job dir. */ |
| jobsDir: string; |
| /** MAKA_MODEL, e.g. "deepseek/deepseek-v4-flash". */ |
| model: string; |
| /** MAKA_PROVIDER, e.g. "deepseek". */ |
| provider?: string; |
| reasoningEffort?: ThinkingLevel; |
| /** Host path to an API key file. The key stays in the Harbor control process; |
| * the task container receives no provider key env, key-file path, or secret mount. */ |
| apiKeyFile?: string; |
| /** Resolves the current provider authority inside the host proxy for every request. */ |
| resolveProviderCredential?: ProviderUpstreamCredentialResolver; |
| /** Per-1M USD pricing forwarded as MAKA_TRIAL_* so the cell emits real costUsd. */ |
| pricing?: HarborTaskPricing; |
| /** Extra agent env merged last (e.g. DEEPSEEK_BASE_URL). */ |
| agentEnv?: Record<string, string>; |
| harborBin?: string; |
| /** Harbor environment type (default "docker"). */ |
| environment?: string; |
| timeoutMultiplier?: number; |
| /** Wall-clock ceiling for a single `harbor run`; a hung Docker/Harbor would |
| * otherwise stall the unattended loop forever. Defaults to 45 minutes. */ |
| harborTimeoutMs?: number; |
| /** Injectable Harbor process runner (default: execFile the harbor binary). */ |
| runHarbor?: HarborProcessRunner; |
| /** Injectable only for deterministic GitHub Copilot account-discovery tests. */ |
| copilotFetch?: typeof fetch; |
| now?: () => number; |
| } |
| |
| export interface HarborRunRequest { |
| harborBin: string; |
| configPath: string; |
| jobName: string; |
| jobsDir: string; |
| args: readonly string[]; |
| cwd: string; |
| /** Wall-clock ceiling in ms; the default runner kills harbor past this. */ |
| timeoutMs?: number; |
| /** Env overlaid onto the harbor process (e.g. PYTHONPATH for the adapter). */ |
| env?: Record<string, string>; |
| } |
| |
| const DEFAULT_HARBOR_TIMEOUT_MS = 45 * 60_000; |
| const HARBOR_SETUP_TEARDOWN_GRACE_MS = 15 * 60_000; |
| export interface HarborRunResult { |
| exitCode: number; |
| stdout: string; |
| stderr: string; |
| timedOut?: boolean; |
| signal?: string; |
| } |
| |
| export type HarborProcessRunner = (request: HarborRunRequest) => Promise<HarborRunResult>; |
| |
| export interface HarborOracleQualifierOptions { |
| makaRepoPath: string; |
| jobsDir: string; |
| harborBin?: string; |
| runHarbor?: HarborProcessRunner; |
| } |
| |
| export type HarborOracleQualifier = ( |
| task: TaskRunInput['task'], |
| ) => Promise<HarnessOracleTaskResult>; |
| |
| const EXPERIMENT_IDENTITY_ENV_KEYS = new Set([ |
| 'MAKA_BACKEND', |
| 'MAKA_MODEL', |
| 'MAKA_PROVIDER', |
| 'MAKA_LLM_CONNECTION_SLUG', |
| 'MAKA_AGENT_TOOLS', |
| 'MAKA_REASONING_EFFORT', |
| 'MAKA_OPENCODE_VARIANT', |
| 'MAKA_SYSTEM_PROMPT', |
| 'MAKA_TRIAL_INPUT_USD_PER_1M', |
| 'MAKA_TRIAL_OUTPUT_USD_PER_1M', |
| 'MAKA_TRIAL_CACHE_READ_USD_PER_1M', |
| 'MAKA_TRIAL_CACHE_WRITE_USD_PER_1M', |
| 'MAKA_TRIAL_PRICING_SOURCE', |
| ]); |
| |
| export function createHarborTaskRunner(options: HarborTaskRunnerOptions): TaskRunner { |
| const runHarbor = options.runHarbor ?? defaultHarborProcessRunner; |
| const harborBin = options.harborBin ?? 'harbor'; |
| // The bare local adapter import paths resolve only when the adapter |
| // directory is on harbor's PYTHONPATH; harbor is a uv-installed tool, so its cwd |
| // is not enough. Prepend it (keeping any inherited PYTHONPATH). |
| const harborAdapterDir = join(options.makaRepoPath, 'packages', 'headless', 'harbor'); |
| const pythonPath = [harborAdapterDir, process.env.PYTHONPATH].filter(Boolean).join(delimiter); |
| |
| const runner: TaskRunner = async (input: TaskRunInput): Promise<TaskRunOutput> => { |
| const jobsDir = join( |
| options.jobsDir, |
| sanitize(input.runId), |
| sanitize(input.roundId), |
| sanitize(input.task.id), |
| ); |
| const jobName = 'trial'; |
| const jobDir = join(jobsDir, jobName); |
| // Start each attempt from a clean dir so a crashed prior attempt cannot be |
| // mistaken for this attempt's trial output. |
| await rm(jobsDir, { recursive: true, force: true }); |
| await mkdir(jobsDir, { recursive: true }); |
| |
| const runnerOptions = { |
| ...options, |
| agentEnv: mergeAgentEnv(options.agentEnv, input.agentEnv), |
| }; |
| const allowedHostCredentialEnvNames = |
| runnerOptions.provider === 'github-copilot' |
| ? new Set(providerCredentialEnv('github-copilot')?.apiKeys ?? []) |
| : undefined; |
| assertNoProviderSecretsInAgentEnv(runnerOptions.agentEnv, allowedHostCredentialEnvNames); |
| const hasHostProviderRuntime = |
| runnerOptions.apiKeyFile !== undefined || |
| runnerOptions.resolveProviderCredential !== undefined || |
| githubCopilotAccountTokenFromEnv(runnerOptions.provider, runnerOptions.agentEnv) !== |
| undefined || |
| (!usesHostProviderProxy(runnerOptions.agent, runnerOptions.makaPlacement) && |
| !providerRequiresSecret(runnerOptions.provider)); |
| const configPath = join(jobsDir, 'job-config.json'); |
| const { agentEnv: _attemptAgentEnv, ...inputWithoutAttemptEnv } = input; |
| const config = buildHarborJobConfig(inputWithoutAttemptEnv, { |
| ...runnerOptions, |
| jobsDir, |
| jobName, |
| ...(hasHostProviderRuntime |
| ? { agentEnv: taskAgentEnvWithoutProviderSecrets(runnerOptions) } |
| : {}), |
| }); |
| await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, 'utf8'); |
| |
| const args = ['run', '--config', configPath, '--yes']; |
| let result: HarborRunResult; |
| let providerUsage: ProviderTokenUsage | null = null; |
| let providerTelemetry: ProviderRequestTelemetry[] = []; |
| const providerTelemetryPath = join(jobsDir, PROVIDER_REQUEST_TELEMETRY); |
| try { |
| const providerRuntime = await hostSideProviderRuntime(runnerOptions); |
| try { |
| result = await runHarbor({ |
| harborBin, |
| configPath, |
| jobName, |
| jobsDir, |
| args, |
| cwd: options.makaRepoPath, |
| timeoutMs: resolveHarborTimeoutMs(runnerOptions, input), |
| env: { PYTHONPATH: pythonPath, ...(providerRuntime?.env ?? {}) }, |
| }); |
| } finally { |
| await providerRuntime?.close?.(); |
| providerUsage = providerRuntime?.usage?.() ?? null; |
| providerTelemetry = providerRuntime?.telemetry?.() ?? []; |
| if (providerTelemetry.length > 0) { |
| await writeFile( |
| providerTelemetryPath, |
| `${JSON.stringify( |
| { |
| schemaVersion: 1, |
| summary: summarizeProviderTelemetry(providerTelemetry), |
| requests: providerTelemetry, |
| }, |
| null, |
| 2, |
| )}\n`, |
| 'utf8', |
| ); |
| } |
| } |
| } catch (error) { |
| if (isBudgetExhaustedError(error)) throw error; |
| throw new HarborInfraError( |
| `harbor run failed to launch for task ${input.task.id}`, |
| errorText(error), |
| 'infra_failed', |
| providerTelemetryArtifactRefs(providerTelemetry, providerTelemetryPath), |
| ); |
| } |
| try { |
| if (result.timedOut) { |
| throw new HarborInfraError( |
| `harbor run timed out for task ${input.task.id}`, |
| tail(result.stderr || result.stdout), |
| ); |
| } |
| let trialDir: string; |
| try { |
| trialDir = await findTrialDir(jobDir, basename(input.task.path)); |
| } catch (error) { |
| if (result.exitCode === 0) throw error; |
| throw new HarborInfraError( |
| `harbor run exited ${result.exitCode} for task ${input.task.id}`, |
| tail(result.stderr || result.stdout), |
| ); |
| } |
| const cellOutputPath = join(trialDir, TRIAL_CELL_OUTPUT); |
| const rewardPath = join(trialDir, TRIAL_REWARD); |
| const resultPath = join(trialDir, TRIAL_RESULT); |
| const hostEventsPath = join(trialDir, TRIAL_RUNTIME_EVENTS); |
| |
| // Two facts decide an abnormally ended trial, in this order: who ended the |
| // agent phase (classifyTrialTermination, from the harness's own exception |
| // class) and whether the verifier reached a verdict. Harbor's |
| // single_step.py runs the verifier after any agent-phase exception, so an |
| // agent-owned termination can still carry an authoritative reward. An |
| // externally ended run never can — the agent did not get the run it was |
| // given — so its artifacts are not evidence and it stays infra below. |
| const trialException = await readTrialException(resultPath); |
| const termination = classifyTrialTermination(trialException); |
| let completeTimedOutTrial = false; |
| let verifierSettledTrial = false; |
| if (termination === 'agent_budget' || termination === 'agent_exit') { |
| const [rewardArtifact, verifierArtifact, cellArtifact] = await Promise.all([ |
| readOptionalText(rewardPath), |
| readOptionalText(join(trialDir, TRIAL_VERIFIER_OUTCOME)), |
| readOptionalText(cellOutputPath), |
| ]); |
| if (termination === 'agent_budget') { |
| if (rewardArtifact === null || verifierArtifact === null || cellArtifact === null) { |
| const artifactRefs = await readTimedOutTrialArtifacts( |
| trialDir, |
| input.task.id, |
| runnerOptions.agent, |
| harborTraceMode(runnerOptions.agentEnv), |
| ); |
| // The exhaustion is the agent's fact; the verifier's verdict is the |
| // harness's. Both are true at once, so both travel — dropping the |
| // verdict because the agent never filed its self-report threw away a |
| // pass Harbor had already awarded. |
| const harbor = trialGradeSurvivingProviderOutage( |
| trialVerifierArtifacts(rewardArtifact, verifierArtifact, input.task.id), |
| providerTelemetry, |
| ); |
| throw new FixedPromptBudgetExhaustedError( |
| `agent budget exhausted for task ${input.task.id}`, |
| formatTrialException(trialException), |
| { |
| ...(artifactRefs ?? {}), |
| ...(harbor ? { harbor } : {}), |
| ...(providerTelemetry.length > 0 ? { providerTelemetryPath } : {}), |
| }, |
| ); |
| } |
| completeTimedOutTrial = true; |
| } else { |
| // The agent's own process exited non-zero and the verifier graded the |
| // workspace it left: a real result. Keep the cell's own status and |
| // errorClass — nothing here is a deadline, so nothing may claim one — |
| // and let the structured verifier grade score it. |
| // |
| // The cell requirement is a known exclusion, not the veto fixed above: |
| // a graded agent-exit trial with no self-report has no truthful event |
| // to land in. It claims no deadline, so task_budget_exhausted would |
| // lie, and task_completed needs the runtimeRefs/steps only the cell |
| // attests. Until such a shape exists it stays infra. Widening it is a |
| // WAL taxonomy decision, tracked separately. |
| verifierSettledTrial = |
| rewardArtifact !== null && |
| cellArtifact !== null && |
| hasConclusiveVerifierOutcome(verifierArtifact); |
| } |
| } |
| if (result.exitCode !== 0 && !completeTimedOutTrial && !verifierSettledTrial) { |
| throw new HarborInfraError( |
| // WAL records only the message, so name the trial exception here: an |
| // infra bucket that silently swallows every timeout shape is exactly |
| // how the previous wording regression stayed invisible. |
| `harbor run exited ${result.exitCode} for task ${input.task.id}${trialExceptionSuffix(trialException)}`, |
| tail(result.stderr || result.stdout), |
| ); |
| } |
| // A trial that raised nothing ended its agent phase on its own terms, so it |
| // settles the tail request the same way the two abnormal shapes above do. |
| // Leaving it out read every clean exit that closed a stream mid-flight as |
| // an outage and threw the graded cell away. |
| const terminalProviderRequest = incompleteTerminalProviderRequest( |
| providerTelemetry, |
| termination === null || completeTimedOutTrial || verifierSettledTrial, |
| ); |
| if (terminalProviderRequest) { |
| throw new HarborInfraError( |
| `terminal provider request did not complete for task ${input.task.id}${trialExceptionSuffix(trialException)}`, |
| [ |
| `outcome=${terminalProviderRequest.outcome}`, |
| terminalProviderRequest.status !== undefined |
| ? `status=${terminalProviderRequest.status}` |
| : undefined, |
| terminalProviderRequest.errorClass |
| ? `errorClass=${terminalProviderRequest.errorClass}` |
| : undefined, |
| ] |
| .filter(Boolean) |
| .join(', '), |
| 'infra_failed', |
| { providerTelemetryPath }, |
| ); |
| } |
| const reward = await readReward(rewardPath, resultPath, input.task.id); |
| const rawCell = await readCellOutput(cellOutputPath, input.task.id); |
| const usageCheckpoint = await readOptionalTokenSummary( |
| join(trialDir, TRIAL_USAGE_CHECKPOINT), |
| ); |
| const selectedUsage = selectHarborCellTokenSummary(rawCell.tokenSummary, usageCheckpoint); |
| const checkpointedCell = |
| selectedUsage && selectedUsage !== rawCell.tokenSummary |
| ? { ...rawCell, tokenSummary: selectedUsage } |
| : rawCell; |
| const usageCell = |
| checkpointedCell.tokenSummary || !providerUsage || !runnerOptions.pricing |
| ? checkpointedCell |
| : { |
| ...checkpointedCell, |
| tokenSummary: providerTokenSummary(providerUsage, runnerOptions.pricing), |
| }; |
| const cell = completeTimedOutTrial |
| ? { |
| ...usageCell, |
| status: 'failed' as const, |
| errorClass: 'budget_exhausted', |
| deadlineSettlement: { |
| source: 'benchmark.deadline' as const, |
| mode: 'immediate' as const, |
| }, |
| } |
| : usageCell; |
| const verifierStdout = await readOptionalText(join(trialDir, TRIAL_VERIFIER_STDOUT)); |
| const verifier = await readVerifierOutcome( |
| join(trialDir, TRIAL_VERIFIER_OUTCOME), |
| input.task.id, |
| ); |
| if (!verifier) { |
| throw new HarborInfraError( |
| `custom verifier produced no structured verifier outcome for task ${input.task.id}`, |
| ); |
| } |
| assertVerifierRewardAgreement(verifier, reward, input.task.id); |
| const verifierFailureSummary = |
| verifier?.outcome === 'candidate_timeout' |
| ? 'candidate_timeout' |
| : reward <= 0 |
| ? summarizeVerifierFailure(verifierStdout) |
| : undefined; |
| |
| return { |
| harbor: { |
| reward, |
| ...(verifierFailureSummary ? { verifierFailureSummary } : {}), |
| ...(verifier ? { verifier } : {}), |
| }, |
| // Override the container-local runtimeEventsPath with the host path so the |
| // controller's reward-hack scan and structural smoke can read raw events. |
| cell: { |
| ...cell, |
| ...(providerTelemetry.length > 0 ? { providerTelemetryPath } : {}), |
| runtimeEventsPath: hostEventsPath, |
| traceEventsPath: hostTraceEventsPath( |
| runnerOptions.agent, |
| harborTraceMode(runnerOptions.agentEnv), |
| trialDir, |
| cell, |
| hostEventsPath, |
| ), |
| }, |
| }; |
| } catch (error) { |
| throw withProviderTelemetryArtifact(error, providerTelemetry, providerTelemetryPath); |
| } |
| }; |
| return runner; |
| } |
| |
| /** Shared across runners: attach the provider-request telemetry artifact to a |
| * thrown outcome so infra failures keep their billing/usage evidence. */ |
| export function providerTelemetryArtifactRefs( |
| telemetry: readonly ProviderRequestTelemetry[], |
| providerTelemetryPath: string, |
| ): { providerTelemetryPath: string } | undefined { |
| return telemetry.length > 0 ? { providerTelemetryPath } : undefined; |
| } |
| |
| /** Shared across runners: enriches only the calling runner's own infra error |
| * type, so a foreign error passes through untouched. */ |
| export function withProviderTelemetryArtifact( |
| error: unknown, |
| telemetry: readonly ProviderRequestTelemetry[], |
| providerTelemetryPath: string, |
| infraError: InfraErrorCtor = HarborInfraError, |
| ): unknown { |
| const artifactRefs = providerTelemetryArtifactRefs(telemetry, providerTelemetryPath); |
| if ( |
| !(error instanceof infraError) || |
| !artifactRefs || |
| error.artifactRefs?.providerTelemetryPath |
| ) { |
| return error; |
| } |
| const enriched = new infraError(error.message, error.detail, error.kind, artifactRefs); |
| enriched.stack = error.stack; |
| return enriched; |
| } |
| |
| export function createHarborOracleQualifier( |
| options: HarborOracleQualifierOptions, |
| ): HarborOracleQualifier { |
| const runHarbor = options.runHarbor ?? defaultHarborProcessRunner; |
| const harborBin = options.harborBin ?? 'harbor'; |
| const harborAdapterDir = join(options.makaRepoPath, 'packages', 'headless', 'harbor'); |
| const pythonPath = [harborAdapterDir, process.env.PYTHONPATH].filter(Boolean).join(delimiter); |
| return async (task) => { |
| const jobsDir = join(options.jobsDir, sanitize(task.id)); |
| const jobName = 'qualification'; |
| const jobDir = join(jobsDir, jobName); |
| await rm(jobsDir, { recursive: true, force: true }); |
| await mkdir(jobsDir, { recursive: true }); |
| const verifier = verifierPolicy(task); |
| const configPath = join(jobsDir, 'job-config.json'); |
| await writeFile( |
| configPath, |
| `${JSON.stringify( |
| { |
| job_name: jobName, |
| jobs_dir: jobsDir, |
| n_attempts: HARBOR_ORACLE_EXECUTION_POLICY.job.attempts, |
| n_concurrent_trials: HARBOR_ORACLE_EXECUTION_POLICY.job.concurrentTrials, |
| timeout_multiplier: HARBOR_ORACLE_EXECUTION_POLICY.job.timeoutMultiplier, |
| quiet: true, |
| environment: { |
| type: HARBOR_ORACLE_EXECUTION_POLICY.environment.type, |
| force_build: HARBOR_ORACLE_EXECUTION_POLICY.environment.forceBuild, |
| delete: HARBOR_ORACLE_EXECUTION_POLICY.environment.delete, |
| extra_docker_compose: [ |
| join( |
| options.makaRepoPath, |
| 'packages/headless/harbor', |
| HARBOR_ORACLE_EXECUTION_POLICY.environment.composeFile, |
| ), |
| ], |
| }, |
| verifier: harborVerifierConfig(verifier), |
| metrics: [{ type: 'mean', kwargs: {} }], |
| agents: [ |
| { |
| name: HARBOR_ORACLE_EXECUTION_POLICY.job.agent, |
| ...(task.metadata?.agentTimeoutSec !== undefined |
| ? { max_timeout_sec: task.metadata.agentTimeoutSec } |
| : {}), |
| }, |
| ], |
| datasets: [], |
| tasks: [{ path: task.path, overwrite: false }], |
| artifacts: [], |
| extra_instruction_paths: [], |
| plugins: [], |
| }, |
| null, |
| 2, |
| )}\n`, |
| 'utf8', |
| ); |
| const result = await runHarbor({ |
| harborBin, |
| configPath, |
| jobName, |
| jobsDir, |
| args: ['run', '--config', configPath, '--yes'], |
| cwd: options.makaRepoPath, |
| timeoutMs: resolveHarnessOracleWatchdogTimeoutMs({ |
| agentTimeoutSec: task.metadata?.agentTimeoutSec ?? 0, |
| verifierTimeoutSec: verifier.outerTimeoutSec, |
| }), |
| env: { PYTHONPATH: pythonPath }, |
| }); |
| if (result.timedOut) { |
| throw new HarborInfraError( |
| `Harbor Oracle qualification timed out for task ${task.id}`, |
| tail(result.stderr || result.stdout), |
| 'timed_out', |
| ); |
| } |
| if (result.exitCode !== 0) { |
| throw new Error( |
| `Harbor Oracle qualification exited ${result.exitCode} for task ${task.id}: ${tail(result.stderr || result.stdout)}`, |
| ); |
| } |
| const trialDir = await findTrialDir(jobDir, basename(task.path)); |
| const outcome = await readVerifierOutcome(join(trialDir, TRIAL_VERIFIER_OUTCOME), task.id); |
| if (!outcome) |
| throw new HarborInfraError( |
| `Oracle qualification produced no structured verifier outcome for task ${task.id}`, |
| ); |
| const reward = await readReward( |
| join(trialDir, TRIAL_REWARD), |
| join(trialDir, TRIAL_RESULT), |
| task.id, |
| ); |
| assertVerifierRewardAgreement(outcome, reward, task.id, 'Oracle qualification '); |
| return { outcome: outcome.outcome, reward, attempts: outcome.attempts.length }; |
| }; |
| } |
| |
| function assertVerifierRewardAgreement( |
| outcome: HarborVerifierOutcome, |
| reward: number, |
| taskId: string, |
| prefix = '', |
| ): void { |
| if ((outcome.outcome === 'passed') !== reward > 0) { |
| throw new HarborInfraError( |
| `${prefix}reward disagrees with verifier outcome for task ${taskId}`, |
| ); |
| } |
| } |
| |
| function resolveHarborTimeoutMs(options: HarborTaskRunnerOptions, input: TaskRunInput): number { |
| if (options.harborTimeoutMs !== undefined) return options.harborTimeoutMs; |
| return resolveNativeHarborTimeoutMs(options, input.task); |
| } |
| |
| function resolveNativeHarborTimeoutMs( |
| options: Pick<HarborTaskRunnerOptions, 'timeoutMultiplier' | 'agentEnv' | 'agent'>, |
| task: TaskRunInput['task'], |
| ): number { |
| const configuredCellTimeoutSec = lenientPositiveIntEnv(options.agentEnv?.MAKA_CELL_TIMEOUT_SEC); |
| const agentTimeoutSec = Math.max( |
| task.metadata?.agentTimeoutSec ?? 0, |
| configuredCellTimeoutSec ?? 0, |
| ); |
| return resolveNativeTrialTimeoutMs({ |
| // The agent phase Harbor is given is the model budget plus Maka's |
| // settlement window, so the watchdog bounds that same sum. It reads the |
| // runner-level env only, while the job config also sees per-attempt env — |
| // the shared setup/teardown grace is far wider than any window an operator |
| // would set, so the watchdog still outlasts the phase either way. |
| // Resolved through the shared rule so the two cannot drift in kind. |
| nativePhasesSec: |
| agentTimeoutSec + |
| settlementGraceSec(options.agent ?? 'maka', options.agentEnv) + |
| verifierPolicy(task).outerTimeoutSec, |
| timeoutMultiplier: options.timeoutMultiplier ?? 1, |
| }); |
| } |
| |
| /** Shared wall-clock watchdog contract for one trial: the harness's maximum |
| * legitimate lifecycle in native seconds, times the multiplier, plus |
| * setup/teardown grace, floored at 45 minutes so short tasks keep a sane |
| * ceiling. Cross-runner benchmark invariant: each runner owns its own |
| * lifecycle shape (which phases run, and how often its harness retries them) |
| * and supplies the summed seconds — Harbor passes agent + the oracle verifier |
| * policy's outer budget; Pier passes its full phase-and-retry model. */ |
| export function resolveNativeTrialTimeoutMs(input: { |
| nativePhasesSec: number; |
| timeoutMultiplier: number; |
| }): number { |
| const nativePhasesMs = input.nativePhasesSec * input.timeoutMultiplier * 1_000; |
| return Math.max(DEFAULT_HARBOR_TIMEOUT_MS, nativePhasesMs + HARBOR_SETUP_TEARDOWN_GRACE_MS); |
| } |
| |
| async function readOptionalCellOutput( |
| cellOutputPath: string, |
| taskId: string, |
| ): Promise<HarborCellOutput | null> { |
| try { |
| return await readCellOutput(cellOutputPath, taskId); |
| } catch { |
| return null; |
| } |
| } |
| |
| /** Shared across runners: resolve the richest host-side trace for a trial. |
| * Task-run mode prefers the combined agent/trace-events.jsonl, then the |
| * task-run session layout; cell mode resolves the maka-storage session events |
| * via cell.runtimeRefs — the raw runtime-events fallback is a last resort, and |
| * skipping the session branch silently drops tool_failed / |
| * provider_request_captured failure attribution downstream. */ |
| export function hostTraceEventsPath( |
| agent: HarborTaskRunnerOptions['agent'], |
| mode: 'cell' | 'task-run', |
| trialDir: string, |
| cell: HarborCellOutput, |
| hostEventsPath: string, |
| ): string { |
| if (agent !== undefined && agent !== 'maka') return hostEventsPath; |
| const traceSuffix = [cell.runtimeRefs.sessionId, 'runs', cell.runtimeRefs.runId, 'events.jsonl']; |
| if (mode === 'task-run') { |
| const combinedTracePath = join(trialDir, TRIAL_TASK_RUN_TRACE_EVENTS); |
| if (existsSync(combinedTracePath)) return combinedTracePath; |
| const taskRunTracePath = join(trialDir, TRIAL_TASK_RUN_TRACE_EVENTS_ROOT, ...traceSuffix); |
| return existsSync(taskRunTracePath) ? taskRunTracePath : hostEventsPath; |
| } |
| const cellTracePath = join(trialDir, TRIAL_LEGACY_TRACE_EVENTS_ROOT, ...traceSuffix); |
| return existsSync(cellTracePath) ? cellTracePath : hostEventsPath; |
| } |
| |
| /** Exported alongside readTimedOutTrialArtifacts: the Pier runner resolves the |
| * same MAKA_HARBOR_MODE contract when recovering timed-out trial artifacts. */ |
| export function harborTraceMode(agentEnv: Record<string, string> | undefined): 'cell' | 'task-run' { |
| return agentEnv?.MAKA_HARBOR_MODE === 'task-run' ? 'task-run' : 'cell'; |
| } |
| |
| function cellArtifactRefs( |
| cell: HarborCellOutput, |
| hostEventsPath: string, |
| trialDir: string, |
| agent: HarborTaskRunnerOptions['agent'], |
| mode: 'cell' | 'task-run', |
| ) { |
| const traceEventsPath = hostTraceEventsPath(agent, mode, trialDir, cell, hostEventsPath); |
| return { |
| runtimeEventsPath: hostEventsPath, |
| traceEventsPath, |
| ...(cell.tokenSummary ? { tokenSummary: cell.tokenSummary } : {}), |
| cellOutput: { ...cell, runtimeEventsPath: hostEventsPath, traceEventsPath }, |
| }; |
| } |
| |
| /** Recover whatever attested evidence a timed-out/budget-exhausted trial left |
| * behind (cell output, execution identity, usage checkpoint) so the sample keeps |
| * its Pass@1 eligibility instead of being excluded as missing_execution_identity. |
| * Cross-runner benchmark invariant: the Pier runner reuses this exact |
| * implementation — both runners' trials are written by the same adapters into |
| * the same agent/ layout, so the recovery contract must not fork. */ |
| export async function readTimedOutTrialArtifacts( |
| trialDir: string, |
| taskId: string, |
| agent: HarborTaskRunnerOptions['agent'], |
| mode: 'cell' | 'task-run', |
| ) { |
| const cell = await readOptionalCellOutput(join(trialDir, TRIAL_CELL_OUTPUT), taskId); |
| if (cell) { |
| const usageCheckpoint = await readOptionalTokenSummary(join(trialDir, TRIAL_USAGE_CHECKPOINT)); |
| const selectedUsage = selectHarborCellTokenSummary(cell.tokenSummary, usageCheckpoint); |
| const recoveredCell = |
| selectedUsage && selectedUsage !== cell.tokenSummary |
| ? { ...cell, tokenSummary: selectedUsage } |
| : cell; |
| return cellArtifactRefs( |
| recoveredCell, |
| join(trialDir, TRIAL_RUNTIME_EVENTS), |
| trialDir, |
| agent, |
| mode, |
| ); |
| } |
| const [executionIdentity, tokenSummary] = await Promise.all([ |
| readOptionalExecutionIdentity(join(trialDir, TRIAL_EXECUTION_IDENTITY)), |
| readOptionalTokenSummary(join(trialDir, TRIAL_USAGE_CHECKPOINT)), |
| ]); |
| return executionIdentity || tokenSummary |
| ? { |
| ...(executionIdentity ? { executionIdentity } : {}), |
| ...(tokenSummary ? { tokenSummary } : {}), |
| } |
| : null; |
| } |
| |
| async function readOptionalExecutionIdentity( |
| path: string, |
| ): Promise<HarborCellExecutionIdentity | null> { |
| try { |
| return validateHarborCellExecutionIdentity(JSON.parse(await readFile(path, 'utf8'))); |
| } catch { |
| return null; |
| } |
| } |
| |
| async function readOptionalTokenSummary(path: string) { |
| try { |
| return validateHarborCellTokenSummary(JSON.parse(await readFile(path, 'utf8'))); |
| } catch { |
| return null; |
| } |
| } |
| |
| async function readOptionalText(path: string): Promise<string | null> { |
| try { |
| return await readFile(path, 'utf8'); |
| } catch { |
| return null; |
| } |
| } |
| |
| async function readVerifierOutcome( |
| path: string, |
| taskId: string, |
| ): Promise<HarborVerifierOutcome | null> { |
| let raw: string; |
| try { |
| raw = await readFile(path, 'utf8'); |
| } catch (error) { |
| if ((error as { code?: unknown }).code === 'ENOENT') return null; |
| throw new HarborInfraError( |
| `failed to read verifier outcome for task ${taskId}`, |
| errorText(error), |
| ); |
| } |
| let value: unknown; |
| try { |
| value = JSON.parse(raw); |
| } catch (error) { |
| throw new HarborInfraError( |
| `verifier outcome is not valid JSON for task ${taskId}`, |
| errorText(error), |
| ); |
| } |
| return parseVerifierOutcome(value, taskId); |
| } |
| |
| function parseVerifierOutcome(value: unknown, taskId: string): HarborVerifierOutcome { |
| if (!isRecord(value) || value.schemaVersion !== 1) { |
| throw new HarborInfraError(`verifier outcome is malformed for task ${taskId}`); |
| } |
| const outcome = value.outcome; |
| if (outcome === 'infra_failed') { |
| throw new HarborInfraError(`verifier infrastructure failed for task ${taskId}`); |
| } |
| if (outcome !== 'passed' && outcome !== 'failed' && outcome !== 'candidate_timeout') { |
| throw new HarborInfraError(`verifier outcome is malformed for task ${taskId}`); |
| } |
| if ( |
| !Array.isArray(value.attempts) || |
| value.attempts.length < 1 || |
| value.attempts.length > HARBOR_ORACLE_MAX_ATTEMPTS |
| ) { |
| throw new HarborInfraError(`verifier outcome attempts are malformed for task ${taskId}`); |
| } |
| const attempts = value.attempts.map((attempt, index) => |
| validateVerifierAttempt(attempt, index + 1, taskId), |
| ); |
| const last = attempts.at(-1)!; |
| const priorAttemptsAreRetryable = attempts |
| .slice(0, -1) |
| .every( |
| (attempt) => |
| attempt.classification === 'infra_setup_failed' || |
| attempt.classification === 'infra_failed', |
| ); |
| if ( |
| !priorAttemptsAreRetryable || |
| (outcome === 'passed' && (last.classification !== 'passed' || (last.reward ?? 0) <= 0)) || |
| (outcome === 'failed' && (last.classification !== 'failed' || last.reward !== 0)) || |
| (outcome === 'candidate_timeout' && last.classification !== 'timeout') |
| ) { |
| throw new HarborInfraError(`verifier outcome disagrees with its attempts for task ${taskId}`); |
| } |
| return { outcome, attempts }; |
| } |
| |
| function validateVerifierAttempt( |
| value: unknown, |
| expectedAttempt: number, |
| taskId: string, |
| ): HarborVerifierAttempt { |
| if (!isRecord(value) || value.attempt !== expectedAttempt) { |
| throw new HarborInfraError(`verifier attempt is malformed for task ${taskId}`); |
| } |
| const classification = value.classification; |
| if ( |
| classification !== 'passed' && |
| classification !== 'failed' && |
| classification !== 'timeout' && |
| classification !== 'infra_setup_failed' && |
| classification !== 'infra_failed' |
| ) { |
| throw new HarborInfraError(`verifier attempt classification is malformed for task ${taskId}`); |
| } |
| if ( |
| typeof value.durationMs !== 'number' || |
| !Number.isFinite(value.durationMs) || |
| value.durationMs < 0 |
| ) { |
| throw new HarborInfraError(`verifier attempt duration is malformed for task ${taskId}`); |
| } |
| if ( |
| value.reward !== undefined && |
| (typeof value.reward !== 'number' || !Number.isFinite(value.reward)) |
| ) { |
| throw new HarborInfraError(`verifier attempt reward is malformed for task ${taskId}`); |
| } |
| return { |
| attempt: expectedAttempt, |
| classification, |
| durationMs: value.durationMs, |
| ...(typeof value.reward === 'number' ? { reward: value.reward } : {}), |
| }; |
| } |
| |
| function summarizeVerifierFailure(text: string | null): string | undefined { |
| if (!text) return undefined; |
| const normalized = text.toLowerCase(); |
| const parts: string[] = []; |
| if (normalized.includes('assertionerror') || normalized.includes('assert ')) { |
| parts.push('output_assertion_failed'); |
| } |
| if (integerAssertionOffByOne(text)) { |
| parts.push('integer_output_off_by_one'); |
| } |
| if (finalStateTextMismatch(text)) { |
| parts.push('final_state_expected_text_mismatch'); |
| } |
| if (structuredOutputValuesMismatch(normalized)) { |
| parts.push('structured_output_values_mismatch'); |
| } |
| if ( |
| normalized.includes("module 'numpy' has no attribute 'int'") || |
| normalized.includes('module "numpy" has no attribute "int"') |
| ) { |
| parts.push('python_numpy_removed_alias_np.int'); |
| } |
| return parts.length > 0 ? parts.join(' ') : undefined; |
| } |
| |
| function integerAssertionOffByOne(text: string): boolean { |
| const match = text.match(/assert\s+['"](\d+)['"]\s+in\s+['"](\d+)['"]/); |
| if (!match) return false; |
| const expected = Number(match[1]); |
| const actual = Number(match[2]); |
| return ( |
| Number.isSafeInteger(expected) && |
| Number.isSafeInteger(actual) && |
| Math.abs(expected - actual) === 1 |
| ); |
| } |
| |
| function finalStateTextMismatch(text: string): boolean { |
| return /\bExpected\s+['"][^'"\n]{1,200}['"]/i.test(text) && /\bGot:\s+['"]/i.test(text); |
| } |
| |
| function structuredOutputValuesMismatch(normalizedText: string): boolean { |
| return normalizedText.includes('only found') && normalizedText.includes('expected values'); |
| } |
| |
| /** Shared across runners: overlay attempt-level env onto runner-level env. */ |
| export function mergeAgentEnv( |
| base: Record<string, string> | undefined, |
| attempt: Record<string, string> | undefined, |
| ): Record<string, string> | undefined { |
| if (!base && !attempt) return undefined; |
| return { ...(base ?? {}), ...(attempt ?? {}) }; |
| } |
| |
| export function buildHarborJobConfig( |
| input: TaskRunInput, |
| options: HarborTaskRunnerOptions & { jobsDir: string; jobName: string }, |
| ): Record<string, unknown> { |
| const attemptAgentEnv = mergeAgentEnv(options.agentEnv, input.agentEnv); |
| assertNoProviderSecretsInAgentEnv(attemptAgentEnv); |
| assertNoExperimentIdentityOverrides(attemptAgentEnv); |
| const provider = options.provider ?? 'deepseek'; |
| const makaModel = modelIdForProvider(options.model, provider); |
| const adapter = options.agent ?? 'maka'; |
| const agentModel = adapter === 'opencode' ? modelForOpenCode(options.model, provider) : makaModel; |
| const toolchain = adapter === 'maka' ? undefined : COMPETITOR_TOOLCHAINS[adapter]; |
| const makaInContainer = adapter === 'maka' && options.makaPlacement === 'task-container'; |
| if (makaInContainer && !options.makaNodeToolchainPath) { |
| throw new Error('makaNodeToolchainPath is required for task-container Maka placement'); |
| } |
| if (toolchain) { |
| const toolchainPath = options[toolchain.optionKey]; |
| if (!toolchainPath) { |
| throw new Error(`${toolchain.optionKey} is required for the ${toolchain.label} adapter`); |
| } |
| if (options.agentVersion !== toolchain.version) { |
| throw new Error( |
| `${toolchain.label} adapter version must match toolchain version ${toolchain.version}`, |
| ); |
| } |
| } |
| const mounts: Array<Record<string, unknown>> = [ |
| ...buildAgentRepoMounts(adapter, options.makaRepoPath), |
| ...(toolchain |
| ? [ |
| { |
| type: 'bind', |
| source: options[toolchain.optionKey]!, |
| target: toolchain.containerPath, |
| read_only: true, |
| }, |
| ] |
| : []), |
| ...(makaInContainer |
| ? [ |
| { |
| type: 'bind', |
| source: options.makaNodeToolchainPath!, |
| target: MAKA_NODE_TOOLCHAIN_CONTAINER_PATH, |
| read_only: true, |
| }, |
| ] |
| : []), |
| ]; |
| |
| const agentEnv: Record<string, string> = { |
| MAKA_BACKEND: 'ai-sdk', |
| MAKA_MODEL: makaModel, |
| MAKA_PROVIDER: provider, |
| MAKA_LLM_CONNECTION_SLUG: provider, |
| MAKA_AGENT_TOOLS: input.config.agentTools === true ? 'true' : 'false', |
| // Verbatim — the controller hashes exactly these bytes and verifies the round-trip. |
| MAKA_SYSTEM_PROMPT: input.systemPrompt, |
| }; |
| if (options.reasoningEffort) { |
| agentEnv.MAKA_REASONING_EFFORT = options.reasoningEffort; |
| if (adapter === 'opencode') agentEnv.MAKA_OPENCODE_VARIANT = options.reasoningEffort; |
| } |
| if (toolchain) { |
| agentEnv[toolchain.fingerprintEnvKey] = toolchain.fingerprint; |
| } |
| |
| if (options.pricing) { |
| agentEnv.MAKA_TRIAL_INPUT_USD_PER_1M = String(options.pricing.inputUsdPer1M); |
| agentEnv.MAKA_TRIAL_OUTPUT_USD_PER_1M = String(options.pricing.outputUsdPer1M); |
| if (options.pricing.cacheReadUsdPer1M !== undefined) { |
| agentEnv.MAKA_TRIAL_CACHE_READ_USD_PER_1M = String(options.pricing.cacheReadUsdPer1M); |
| } |
| if (options.pricing.cacheWriteUsdPer1M !== undefined) { |
| agentEnv.MAKA_TRIAL_CACHE_WRITE_USD_PER_1M = String(options.pricing.cacheWriteUsdPer1M); |
| } |
| if (options.pricing.source) { |
| agentEnv.MAKA_TRIAL_PRICING_SOURCE = options.pricing.source; |
| } |
| } |
| |
| if (makaInContainer) { |
| // The adapter re-verifies the mounted toolchain against this before it will |
| // install, and it reads it from the agent env — the same place every other |
| // pinned-toolchain fingerprint is read from. Putting it on the provider |
| // channel instead left the mount in place and the arm unable to start. |
| agentEnv.MAKA_NODE_TOOLCHAIN_FINGERPRINT = MAKA_NODE_TOOLCHAIN_FINGERPRINT; |
| } |
| |
| Object.assign(agentEnv, attemptAgentEnv ?? {}); |
| // Lenient by shared contract with the Python adapter: a malformed value must |
| // fall back (metadata, then the adapter's default) rather than fail the run. |
| const modelBudgetSec = |
| lenientPositiveIntEnv(agentEnv.MAKA_CELL_TIMEOUT_SEC) ?? input.task.metadata?.agentTimeoutSec; |
| // MAKA_CELL_TIMEOUT_SEC is the model budget on every path, so it is passed |
| // through untouched. Maka's cell stops calling the model when it runs out and |
| // then settles its artifacts, so its agent phase — the deadline Harbor kills |
| // at — is one window longer. Native CLIs have nothing to settle and get the |
| // budget itself, which is how every arm ends up with the same model time. |
| const graceSec = settlementGraceSec(adapter, agentEnv); |
| let agentPhaseSec: number | undefined; |
| if (modelBudgetSec !== undefined) { |
| agentPhaseSec = agentPhaseTimeoutSec(adapter, agentEnv, modelBudgetSec); |
| agentEnv.MAKA_CELL_TIMEOUT_SEC = String(modelBudgetSec); |
| if (adapter === 'maka') { |
| agentEnv.MAKA_CELL_SETTLEMENT_GRACE_SEC = String(graceSec); |
| } |
| const streamTimeoutMs = modelBudgetSec * 1_000; |
| if (adapter === 'maka' && Number.isSafeInteger(streamTimeoutMs)) { |
| // Harbor already owns the task-native hard deadline. Keep the runtime's |
| // first-event and between-event watchdogs from imposing a shorter, |
| // benchmark-distorting cutoff on long reasoning turns. |
| agentEnv.MAKA_STREAM_CONNECT_TIMEOUT_MS = String(streamTimeoutMs); |
| agentEnv.MAKA_STREAM_IDLE_TIMEOUT_MS = String(streamTimeoutMs); |
| } |
| } |
| const verifier = verifierPolicy(input.task); |
| |
| return { |
| job_name: options.jobName, |
| jobs_dir: options.jobsDir, |
| n_attempts: 1, |
| n_concurrent_trials: 1, |
| timeout_multiplier: options.timeoutMultiplier ?? 1.0, |
| quiet: true, |
| environment: { |
| type: options.environment ?? 'docker', |
| force_build: false, |
| delete: true, |
| mounts, |
| ...(options.dockerPlatform === 'linux/amd64' |
| ? { |
| extra_docker_compose: [ |
| join( |
| options.makaRepoPath, |
| 'packages/headless/harbor/docker-compose-linux-amd64.yaml', |
| ), |
| ...(options.aptMirrorComposePath ? [options.aptMirrorComposePath] : []), |
| ], |
| } |
| : {}), |
| }, |
| verifier: harborVerifierConfig(verifier), |
| metrics: [{ type: 'mean', kwargs: {} }], |
| agents: [ |
| { |
| ...(adapter === 'maka' ? { name: adapter } : {}), |
| import_path: harnessAgentImportPath(adapter), |
| model_name: agentModel, |
| kwargs: |
| adapter === 'maka' |
| ? { backend: 'ai-sdk' } |
| : options.agentVersion |
| ? { |
| version: options.agentVersion, |
| ...((adapter === 'codex' || adapter === 'claude-code') && options.reasoningEffort |
| ? { reasoning_effort: options.reasoningEffort } |
| : {}), |
| } |
| : {}, |
| env: agentEnv, |
| // override_timeout_sec, not max_timeout_sec: Harbor resolves the agent |
| // phase as `min(override ?? task_declared, max ?? inf)`, so max_ can only |
| // ever lower the task's own timeout. Asking for budget + settlement |
| // through max_ resolved to the task timeout unchanged, which left Maka's |
| // settlement window mathematically unreachable — the cell was SIGKILLed |
| // at the instant it was supposed to start writing. |
| ...(agentPhaseSec !== undefined ? { override_timeout_sec: agentPhaseSec } : {}), |
| }, |
| ], |
| datasets: [], |
| tasks: [{ path: input.task.path, overwrite: false }], |
| artifacts: [], |
| extra_instruction_paths: [], |
| plugins: [], |
| }; |
| } |
| |
| function harborVerifierConfig(verifier: ReturnType<typeof verifierPolicy>) { |
| return { |
| env: {}, |
| disable: false, |
| import_path: HARBOR_ORACLE_EXECUTION_POLICY.verifier.importPath, |
| kwargs: { |
| attempt_timeout_sec: verifier.attemptTimeoutSec, |
| max_attempts: HARBOR_ORACLE_MAX_ATTEMPTS, |
| retry_backoff_sec: HARBOR_ORACLE_EXECUTION_POLICY.verifier.retryBackoffSec, |
| total_timeout_sec: verifier.totalTimeoutSec, |
| }, |
| override_timeout_sec: verifier.outerTimeoutSec, |
| }; |
| } |
| |
| function verifierPolicy(task: TaskRunInput['task']): { |
| attemptTimeoutSec: number; |
| totalTimeoutSec: number; |
| outerTimeoutSec: number; |
| } { |
| const attemptTimeoutSec = |
| task.metadata?.verifierTimeoutSec ?? |
| HARBOR_ORACLE_EXECUTION_POLICY.verifier.defaultAttemptTimeoutSec; |
| const totalTimeoutSec = |
| attemptTimeoutSec * HARBOR_ORACLE_EXECUTION_POLICY.verifier.totalAttemptBudgetMultiplier; |
| return { |
| attemptTimeoutSec, |
| totalTimeoutSec, |
| outerTimeoutSec: totalTimeoutSec + HARBOR_ORACLE_EXECUTION_POLICY.verifier.retryGraceSec, |
| }; |
| } |
| |
| async function hostSideProviderRuntime(options: HarborTaskRunnerOptions): Promise<{ |
| env: Record<string, string>; |
| usage?: () => ProviderTokenUsage | null; |
| telemetry?: () => ProviderRequestTelemetry[]; |
| close?: () => Promise<void>; |
| } | null> { |
| const agent = options.agent ?? 'maka'; |
| const provider = options.provider ?? 'deepseek'; |
| if (usesHostProviderProxy(agent, options.makaPlacement) && provider === 'github-copilot') { |
| // Maka reaches this guard only in task-container placement, where it too |
| // goes through the proxy. Its remedy is the placement it came from, not the |
| // competitors' dead end, so it must not be told an adapter is missing. |
| if (agent === 'maka') { |
| throw new Error( |
| 'GitHub Copilot Harbor runs require the host-bridge Maka placement; the host provider proxy does not carry this provider', |
| ); |
| } |
| const adapter = |
| agent === 'kimi-code' |
| ? 'Kimi Code' |
| : agent === 'codex' |
| ? 'Codex' |
| : agent === 'claude-code' |
| ? 'Claude Code' |
| : 'OpenCode'; |
| throw new Error( |
| `GitHub Copilot Harbor runs use the Maka host agent; the ${adapter} Harbor adapter does not support this provider`, |
| ); |
| } |
| const githubToken = |
| provider === 'github-copilot' |
| ? options.apiKeyFile |
| ? (await readFile(options.apiKeyFile, 'utf8')).trim() |
| : githubCopilotAccountTokenFromEnv(provider, options.agentEnv) |
| : undefined; |
| if ( |
| !options.apiKeyFile && |
| !options.resolveProviderCredential && |
| !githubToken && |
| providerRequiresSecret(provider) |
| ) |
| return null; |
| const providerEnv = providerCredentialEnv(provider); |
| const [primaryBaseUrl] = providerEnv?.baseUrls ?? []; |
| const configuredBaseUrl = |
| (primaryBaseUrl ? options.agentEnv?.[primaryBaseUrl] : undefined) ?? |
| options.agentEnv?.MAKA_BASE_URL ?? |
| providerBaseUrlFromEnv(provider, options.agentEnv ?? {}); |
| const copilotCredential = githubToken |
| ? await resolveGitHubCopilotHostCredential( |
| githubToken, |
| modelIdForProvider(options.model, provider), |
| configuredBaseUrl ?? PROVIDER_DEFAULTS['github-copilot'].baseUrl, |
| options.copilotFetch, |
| ) |
| : undefined; |
| const baseUrl = copilotCredential?.baseUrl ?? configuredBaseUrl; |
| if (options.resolveProviderCredential || usesHostProviderProxy(agent, options.makaPlacement)) { |
| const apiKeyFile = options.apiKeyFile; |
| const resolveProviderCredential = options.resolveProviderCredential; |
| if (!apiKeyFile && !resolveProviderCredential) return null; |
| if (!baseUrl) throw new Error(`${agent} provider ${provider} requires a base URL`); |
| const apiProtocol = providerProxyApiProtocol(agent, options.agentEnv); |
| const proxy = await startProviderAuthProxy({ |
| upstreamBaseUrl: providerProxyUpstreamBaseUrl(baseUrl, provider, apiProtocol), |
| // Loopback is the right address only for a controller on this host. From |
| // inside the task container it names the container, so the arm would dial |
| // itself; competitors set nothing here for exactly that reason. |
| ...(agent === 'maka' && options.makaPlacement !== 'task-container' |
| ? { advertisedHost: '127.0.0.1' } |
| : {}), |
| ...(resolveProviderCredential |
| ? { resolveUpstreamCredential: resolveProviderCredential } |
| : { apiKeyFile: apiKeyFile! }), |
| clientAuthMode: providerProxyClientAuthMode(agent, provider, apiProtocol), |
| upstreamAuthMode: providerProxyUpstreamAuthMode(agent, provider, apiProtocol), |
| usageProtocol: providerProxyUsageProtocol(agent, provider, apiProtocol), |
| }); |
| return { |
| env: |
| // Which channel an arm gets follows where it runs, not which arm it is. |
| // A host-side Maka reaches the proxy directly; in the task container it |
| // is on the far side of the same boundary as every competitor, so it |
| // needs the same client channel they do — `MAKA_HOST_*` names a host |
| // that is not there. |
| agent === 'maka' && options.makaPlacement !== 'task-container' |
| ? { |
| MAKA_HOST_BASE_URL: proxy.baseUrl, |
| MAKA_HOST_API_KEY: proxy.token, |
| } |
| : { |
| MAKA_PROVIDER_PROXY_URL: providerProxyClientBaseUrl(proxy.baseUrl, agent, provider), |
| MAKA_PROVIDER_PROXY_TOKEN: proxy.token, |
| }, |
| usage: proxy.usage, |
| telemetry: proxy.telemetry, |
| close: proxy.close, |
| }; |
| } |
| return { |
| env: { |
| MAKA_HOST_REPO_ROOT: options.makaRepoPath, |
| ...(copilotCredential |
| ? { MAKA_HOST_API_KEY: copilotCredential.accessToken } |
| : options.apiKeyFile |
| ? { MAKA_HOST_API_KEY_FILE: options.apiKeyFile } |
| : {}), |
| ...(!options.apiKeyFile && !copilotCredential ? { MAKA_HOST_NO_AUTH: 'true' } : {}), |
| ...(baseUrl ? { MAKA_HOST_BASE_URL: baseUrl } : {}), |
| ...(copilotCredential ? { MAKA_HOST_MODEL_API_PROTOCOL: copilotCredential.apiProtocol } : {}), |
| }, |
| }; |
| } |
| |
| /** |
| * Whether this arm reaches the provider through the host proxy rather than |
| * holding host credentials itself. That follows the container boundary, not the |
| * arm's name: Maka was exempt because it ran on the host, and an in-container |
| * Maka is on the same side as every competitor, with no host credential to use. |
| */ |
| function usesHostProviderProxy( |
| agent: HarborTaskRunnerOptions['agent'], |
| makaPlacement?: HarborTaskRunnerOptions['makaPlacement'], |
| ): boolean { |
| if (agent === undefined) return false; |
| if (agent === 'maka') return makaPlacement === 'task-container'; |
| return true; |
| } |
| |
| /** Shared cost math across runners: build the cell token summary from proxy-observed usage and per-1M pricing. */ |
| export function providerTokenSummary( |
| usage: ProviderTokenUsage, |
| pricing: HarborTaskPricing, |
| ): NonNullable<HarborCellOutput['tokenSummary']> { |
| const cacheMissInput = Math.max(0, usage.input - usage.cacheRead - usage.cacheWrite); |
| const costUsd = |
| (cacheMissInput * pricing.inputUsdPer1M + |
| usage.cacheRead * (pricing.cacheReadUsdPer1M ?? pricing.inputUsdPer1M) + |
| usage.cacheWrite * (pricing.cacheWriteUsdPer1M ?? pricing.inputUsdPer1M) + |
| usage.output * pricing.outputUsdPer1M) / |
| 1_000_000; |
| return { |
| input: usage.input, |
| output: usage.output, |
| cachedInput: usage.cacheRead, |
| cacheHitInput: usage.cacheRead, |
| cacheMissInput, |
| cacheWriteInput: usage.cacheWrite, |
| cacheMissInputSource: 'explicit', |
| reasoning: usage.reasoning ?? 0, |
| total: usage.input + usage.output, |
| costUsd, |
| pricingSource: 'runtime', |
| }; |
| } |
| |
| /** Match the Maka host connection's protocol authority when configuring its auth proxy. */ |
| export function providerProxyApiProtocol( |
| agent: HarborTaskRunnerOptions['agent'], |
| agentEnv: Record<string, string> | undefined, |
| ): string | undefined { |
| if (agent !== 'maka') return undefined; |
| return agentEnv?.MAKA_HOST_MODEL_API_PROTOCOL || agentEnv?.MAKA_MODEL_API_PROTOCOL || undefined; |
| } |
| |
| async function resolveGitHubCopilotHostCredential( |
| githubToken: string, |
| modelId: string, |
| baseUrl: string, |
| fetchFn?: typeof fetch, |
| ): Promise<{ |
| accessToken: string; |
| baseUrl: string; |
| apiProtocol: 'openai-chat' | 'openai-responses' | 'anthropic-messages'; |
| }> { |
| if (!isSupportedGitHubCopilotAccountToken(githubToken)) { |
| throw new Error( |
| 'GitHub Copilot requires a GitHub OAuth, GitHub App user, or fine-grained account token; classic PATs are not accepted', |
| ); |
| } |
| const models = await fetchGitHubCopilotModels(baseUrl, githubToken, fetchFn); |
| const model = models.find(({ id }) => id === modelId); |
| if (!model?.apiProtocol) |
| throw new Error(`GitHub Copilot account does not expose model ${modelId}`); |
| return { accessToken: githubToken, baseUrl, apiProtocol: model.apiProtocol }; |
| } |
| |
| function githubCopilotAccountTokenFromEnv( |
| provider: string | undefined, |
| agentEnv: Readonly<Record<string, string>> | undefined, |
| ): string | undefined { |
| if (provider !== 'github-copilot') return undefined; |
| for (const name of providerCredentialEnv(provider)?.apiKeys ?? []) { |
| const value = agentEnv?.[name]?.trim(); |
| if (value) return value; |
| } |
| return undefined; |
| } |
| |
| function taskAgentEnvWithoutProviderSecrets( |
| options: HarborTaskRunnerOptions, |
| ): Record<string, string> { |
| const providerEnv = providerCredentialEnv(options.provider ?? 'deepseek'); |
| const result: Record<string, string> = {}; |
| for (const [key, value] of Object.entries(options.agentEnv ?? {})) { |
| if ( |
| providerEnv?.apiKeys.includes(key) || |
| key === providerEnv?.apiKeyFile || |
| providerEnv?.baseUrls.includes(key) |
| ) |
| continue; |
| if (key === 'MAKA_BASE_URL') continue; |
| if (isSensitiveEnvName(key)) continue; |
| result[key] = value; |
| } |
| return result; |
| } |
| |
| /** Shared benchmark invariant: agentEnv must never carry provider secrets. */ |
| export function assertNoProviderSecretsInAgentEnv( |
| agentEnv: Record<string, string> | undefined, |
| allowedHostCredentialEnvNames: ReadonlySet<string> = new Set(), |
| ): void { |
| const forbidden = Object.keys(agentEnv ?? {}).filter( |
| (key) => isSensitiveEnvName(key) && !allowedHostCredentialEnvNames.has(key), |
| ); |
| if (forbidden.length > 0) { |
| throw new Error(`agentEnv must not contain provider secrets: ${forbidden.sort().join(', ')}`); |
| } |
| } |
| |
| /** Shared benchmark invariant: attempt-level agentEnv must never override the |
| * experiment's identity or pricing. Exported so the Pier runner enforces the |
| * exact same key set instead of drifting on a copied list. */ |
| export function assertNoExperimentIdentityOverrides( |
| agentEnv: Record<string, string> | undefined, |
| ): void { |
| const forbidden = Object.keys(agentEnv ?? {}).filter((key) => |
| EXPERIMENT_IDENTITY_ENV_KEYS.has(key), |
| ); |
| if (forbidden.length > 0) { |
| throw new Error( |
| `agentEnv must not override experiment identity: ${forbidden.sort().join(', ')}`, |
| ); |
| } |
| } |
| |
| /** Shared across runners: registry-driven check whether a provider needs a |
| * real credential (keyless providers like ollama/lm-studio run MAKA_HOST_NO_AUTH). */ |
| export function providerRequiresSecret(provider: string | undefined): boolean { |
| const providerType = (provider ?? 'deepseek') as ProviderType; |
| const definition = PROVIDER_DEFAULTS[providerType]; |
| if (!definition) throw new Error(`unsupported MAKA_PROVIDER: ${provider ?? ''}`); |
| return providerAuthRequiresSecret(providerType); |
| } |
| |
| /** Shared across runners: Harbor and Pier lay out job output the same way |
| * (result.json reward_stats/exception_stats trial-name hint, then a |
| * task-name-prefixed directory fallback), so trial-dir discovery must not fork. |
| * `harness`/`infraError` keep the thrown diagnostics naming the calling runner. */ |
| export async function findTrialDir( |
| jobDir: string, |
| taskName: string, |
| harness = 'harbor', |
| infraError: InfraErrorCtor = HarborInfraError, |
| ): Promise<string> { |
| let entries; |
| try { |
| entries = await readdir(jobDir, { withFileTypes: true }); |
| } catch (error) { |
| throw new infraError(`${harness} produced no job output at ${jobDir}`, errorText(error)); |
| } |
| const dirs = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name); |
| const resultTrialName = await readResultTrialName(join(jobDir, 'result.json')); |
| if (resultTrialName && dirs.includes(resultTrialName)) { |
| return join(jobDir, resultTrialName); |
| } |
| const match = |
| dirs.find((name) => name === taskName || name.startsWith(`${taskName}__`)) ?? dirs[0]; |
| if (!match) { |
| throw new infraError( |
| `${harness} produced no trial directory under ${jobDir} for task ${taskName}`, |
| ); |
| } |
| return join(jobDir, match); |
| } |
| |
| async function readResultTrialName(resultPath: string): Promise<string | null> { |
| let raw: string; |
| try { |
| raw = await readFile(resultPath, 'utf8'); |
| } catch { |
| return null; |
| } |
| let parsed: unknown; |
| try { |
| parsed = JSON.parse(raw); |
| } catch { |
| return null; |
| } |
| if (!isRecord(parsed) || !isRecord(parsed.stats) || !isRecord(parsed.stats.evals)) return null; |
| for (const evalResult of Object.values(parsed.stats.evals)) { |
| if (!isRecord(evalResult)) continue; |
| const rewardStats = isRecord(evalResult.reward_stats) ? evalResult.reward_stats : null; |
| const rewards = |
| rewardStats && isRecord(rewardStats.reward) ? Object.values(rewardStats.reward) : []; |
| for (const trialNames of rewards) { |
| const trialName = firstString(trialNames); |
| if (trialName) return trialName; |
| } |
| const exceptionStats = isRecord(evalResult.exception_stats) |
| ? Object.values(evalResult.exception_stats) |
| : []; |
| for (const trialNames of exceptionStats) { |
| const trialName = firstString(trialNames); |
| if (trialName) return trialName; |
| } |
| } |
| return null; |
| } |
| |
| function firstString(value: unknown): string | null { |
| if (typeof value === 'string') return value; |
| if (Array.isArray(value)) { |
| const first = value.find((item): item is string => typeof item === 'string'); |
| return first ?? null; |
| } |
| return null; |
| } |
| |
| async function readReward(rewardPath: string, resultPath: string, taskId: string): Promise<number> { |
| let raw: string; |
| try { |
| raw = await readFile(rewardPath, 'utf8'); |
| } catch (error) { |
| const trialException = await readTrialException(resultPath); |
| if (trialException) { |
| if (classifyTrialTermination(trialException) === 'agent_budget') { |
| throw new FixedPromptBudgetExhaustedError( |
| `host cell budget exhausted for task ${taskId}`, |
| formatTrialException(trialException), |
| ); |
| } |
| throw new HarborInfraError( |
| `Harbor trial failed before verifier reward for task ${taskId}: ${formatTrialException(trialException)}`, |
| errorText(error), |
| ); |
| } |
| throw new HarborInfraError(`missing verifier reward for task ${taskId}`, errorText(error)); |
| } |
| const trimmed = raw.trim(); |
| if (trimmed.length === 0) { |
| throw new HarborInfraError(`empty verifier reward for task ${taskId}`); |
| } |
| const reward = Number(trimmed); |
| if (!Number.isFinite(reward)) { |
| throw new HarborInfraError(`non-numeric verifier reward for task ${taskId}: ${trimmed}`); |
| } |
| return reward; |
| } |
| |
| /** How the agent phase ended, kept structured. Both harnesses record it in the |
| * trial result's `exception_info` in the same shape, and `exception_type` is |
| * Python's `type(e).__name__` — the harness's own exception classes, which are |
| * a stable fact. Flattening this to one string is what forced every downstream |
| * decision to re-derive the type by matching prose. */ |
| export interface TrialException { |
| type: string; |
| message: string; |
| } |
| |
| /** Shared across runners: both harnesses record how the agent phase ended in |
| * the trial result's `exception_info`, in the same shape. */ |
| export async function readTrialException( |
| resultPath: string, |
| fallbackType = 'HarborTrialError', |
| ): Promise<TrialException | null> { |
| let raw: string; |
| try { |
| raw = await readFile(resultPath, 'utf8'); |
| } catch { |
| return null; |
| } |
| let parsed: unknown; |
| try { |
| parsed = JSON.parse(raw); |
| } catch { |
| return null; |
| } |
| if (!isRecord(parsed)) return null; |
| const exceptionInfo = isRecord(parsed.exception_info) ? parsed.exception_info : null; |
| if (!exceptionInfo) return null; |
| const type = |
| typeof exceptionInfo.exception_type === 'string' ? exceptionInfo.exception_type : fallbackType; |
| const message = |
| typeof exceptionInfo.exception_message === 'string' ? exceptionInfo.exception_message : ''; |
| return { type, message }; |
| } |
| |
| /** The `Type: message` rendering used in diagnostics. */ |
| export function formatTrialException(exception: TrialException | null): string | undefined { |
| if (!exception) return undefined; |
| return exception.message ? `${exception.type}: ${exception.message}` : exception.type; |
| } |
| |
| /** Who ended the agent phase — the one fact that decides whether an abnormally |
| * ended trial can be scored at all. |
| * |
| * - `agent_budget`: the agent ran out of its own time. Harbor raises its own |
| * `AgentTimeoutError` class, so the type alone is unambiguous; the host cell's |
| * deadline arrives as a generic `RuntimeError`, so that one still needs its |
| * message — legitimately, because this repo raises it (harbor/maka_agent.py) |
| * and `harbor-adapter.test.ts` pins the two ends together. |
| * - `agent_exit`: the agent's own process ended non-zero. That is the agent's |
| * behavior, so a verifier verdict on the workspace it left is a real result. |
| * - `external`: the harness, container, or anything else ended the run. The |
| * agent never got the run it was given, so no artifact on disk makes it a |
| * fair sample. Unrecognized types land here on purpose: an unknown terminator |
| * is not evidence of a fair trial, and the recognized budget shapes are |
| * matched by type, so nothing regresses when upstream rewords a message. */ |
| export type TrialTerminationOwner = 'agent_budget' | 'agent_exit' | 'external'; |
| |
| const HOST_CELL_DEADLINE_MESSAGE = /^Maka host cell exceeded \d+(?:\.\d+)?s$/; |
| |
| /** Shared across runners. */ |
| export function classifyTrialTermination( |
| exception: TrialException | null, |
| ): TrialTerminationOwner | null { |
| if (!exception) return null; |
| if (exception.type === 'AgentTimeoutError') return 'agent_budget'; |
| if (exception.type === 'RuntimeError' && HOST_CELL_DEADLINE_MESSAGE.test(exception.message)) |
| return 'agent_budget'; |
| if (exception.type === 'NonZeroAgentExitCodeError') return 'agent_exit'; |
| return 'external'; |
| } |
| |
| /** What the verifier wrote about the trial, read straight from its own |
| * artifacts. It exists independently of `maka-cell-output.json`: that file is |
| * the agent's self-report, and an agent that ran out of budget mid-write never |
| * gets to file one, so a budget exhaustion can still carry the verdict Harbor |
| * reached. This only transports what is on disk — whether the two artifacts |
| * agree, and so whether they amount to a grade, is decided once by the |
| * controller's structuredVerifierGrade, the same judge the completed path uses. |
| * Artifacts that do not parse are not evidence and travel as nothing. */ |
| function trialVerifierArtifacts( |
| rewardArtifact: string | null, |
| verifierArtifact: string | null, |
| taskId: string, |
| ): HarborTrialGrade | undefined { |
| if (rewardArtifact === null || verifierArtifact === null) return undefined; |
| const reward = Number(rewardArtifact.trim()); |
| if (rewardArtifact.trim().length === 0 || !Number.isFinite(reward)) return undefined; |
| try { |
| return { reward, verifier: parseVerifierOutcome(JSON.parse(verifierArtifact), taskId) }; |
| } catch { |
| return undefined; |
| } |
| } |
| |
| /** A verifier outcome is authoritative evidence that the trial was graded only |
| * when it reached a verdict. `candidate_timeout` and `infra_failed` say the |
| * verifier itself did not conclude, so they never settle a trial whose agent |
| * phase already ended abnormally. Unreadable or malformed text is treated as no |
| * evidence — the caller's own reader raises the precise diagnosis on the paths |
| * that require a verdict. */ |
| function hasConclusiveVerifierOutcome(raw: string | null): boolean { |
| if (raw === null) return false; |
| let parsed: unknown; |
| try { |
| parsed = JSON.parse(raw); |
| } catch { |
| return false; |
| } |
| if (!isRecord(parsed)) return false; |
| return parsed.outcome === 'passed' || parsed.outcome === 'failed'; |
| } |
| |
| /** Names the trial exception inside an infra error message. The WAL keeps only |
| * the message, so an exception that never reaches it cannot be found by grep. */ |
| export function trialExceptionSuffix(trialException: TrialException | null): string { |
| const rendered = formatTrialException(trialException); |
| if (!rendered) return ''; |
| const collapsed = rendered.replace(/\s+/g, ' ').trim(); |
| const truncated = collapsed.length > 200 ? `${collapsed.slice(0, 200)}…` : collapsed; |
| return ` (trial exception: ${truncated})`; |
| } |
| |
| /** Shared across runners: the same adapters write the same maka-cell-output.json |
| * contract into both harnesses' trial layouts. */ |
| export async function readCellOutput( |
| cellOutputPath: string, |
| taskId: string, |
| infraError: InfraErrorCtor = HarborInfraError, |
| ): Promise<HarborCellOutput> { |
| let raw: string; |
| try { |
| raw = await readFile(cellOutputPath, 'utf8'); |
| } catch (error) { |
| throw new infraError(`maka cell did not write output for task ${taskId}`, errorText(error)); |
| } |
| let parsed: unknown; |
| try { |
| parsed = JSON.parse(raw); |
| } catch (error) { |
| throw new infraError(`maka cell output is not valid JSON for task ${taskId}`, errorText(error)); |
| } |
| try { |
| return validateHarborCellOutput(parsed); |
| } catch (error) { |
| throw new infraError(`maka cell output is malformed for task ${taskId}`, errorText(error)); |
| } |
| } |
| |
| const defaultHarborProcessRunner: HarborProcessRunner = async (request) => { |
| try { |
| const { stdout, stderr } = await execFileAsync(request.harborBin, [...request.args], { |
| cwd: request.cwd, |
| maxBuffer: 64 * 1024 * 1024, |
| ...(request.timeoutMs !== undefined |
| ? { timeout: request.timeoutMs, killSignal: 'SIGKILL' as const } |
| : {}), |
| ...(request.env ? { env: { ...process.env, ...request.env } } : {}), |
| }); |
| return { exitCode: 0, stdout, stderr }; |
| } catch (error) { |
| const exitCode = |
| typeof (error as { code?: unknown }).code === 'number' ? (error as { code: number }).code : 1; |
| return { |
| exitCode, |
| stdout: String((error as { stdout?: unknown }).stdout ?? ''), |
| stderr: String((error as { stderr?: unknown }).stderr ?? '') || errorText(error), |
| timedOut: isExecFileTimeout(error), |
| ...(typeof (error as { signal?: unknown }).signal === 'string' |
| ? { signal: (error as { signal: string }).signal } |
| : {}), |
| }; |
| } |
| }; |
| |
| function isExecFileTimeout(error: unknown): boolean { |
| if (typeof error !== 'object' || error === null) return false; |
| const record = error as { killed?: unknown; signal?: unknown }; |
| return record.killed === true && record.signal === 'SIGKILL'; |
| } |
| |
| /** Shared across runners: identify FixedPromptBudgetExhaustedError across module instances. */ |
| export function isBudgetExhaustedError( |
| error: unknown, |
| ): error is FixedPromptBudgetExhaustedErrorType { |
| return ( |
| error instanceof FixedPromptBudgetExhaustedError || |
| (typeof error === 'object' && |
| error !== null && |
| (error as { name?: unknown }).name === 'FixedPromptBudgetExhaustedError') |
| ); |
| } |
| |
| /** Strip a model's own provider prefix so the native provider receives a bare id |
| * ("deepseek/deepseek-v4-flash" + provider "deepseek" -> "deepseek-v4-flash"). A |
| * gateway provider keeps the slash because the prefix does not match the provider |
| * ("openai-compatible" routing "anthropic/claude-sonnet-4-5"). The cell's |
| * parseModelSpec preserves whatever it receives when a provider is set, so the |
| * stripping must happen here. */ |
| export function modelIdForProvider(model: string, provider: string): string { |
| const prefix = `${provider}/`; |
| return model.startsWith(prefix) ? model.slice(prefix.length) : model; |
| } |
| |
| export function modelForOpenCode(model: string, provider: string): string { |
| return model.includes('/') ? model : `${provider}/${model}`; |
| } |
| |
| function sanitize(value: string): string { |
| return value.replace(/[^A-Za-z0-9._-]/g, '_'); |
| } |
| |
| function tail(text: string, lines = 20): string { |
| return text.split('\n').slice(-lines).join('\n'); |
| } |
| |
| function errorText(error: unknown): string { |
| return error instanceof Error ? error.message : String(error); |
| } |
| |
| function isRecord(value: unknown): value is Record<string, unknown> { |
| return typeof value === 'object' && value !== null && !Array.isArray(value); |
| } |