/** * ArtificerRunner — Implementation plan generator for the Internalization Engine (PRI-111). * * Migrated to extend BasePeerRunner (PRI-302). The shared lease → buildContext → * invoke → poll → fetch → validate → succeed/fail pipeline is now in the base * class. This file only contains Artificer-specific logic. * * Key constraints (ADR-0003): * - Uses PDRuntimeAdapter for all LLM execution (no direct SDK calls) * - Does NOT directly invoke Evaluator/RolloutReviewer (host layer enqueues) * - No plugin-layer imports (core is infrastructure-agnostic) * - Uses RuntimeStateManager for all state operations * * Trust boundary (Artificer is activation-capable, higher risk than upstream runners): * - LLM output enters as `unknown`; only after validateOutput + lineage check * can it be treated as ArtificerRuleOutput * - sourceScribeArtifactId lineage consistency enforced in succeedTask (ERR-004) * - Invalid activation/action/channel cannot succeed commit * - Artifact write failure → retryOrFail, never silent * * Pipeline: * 1. acquireLease — isolated try/catch, lease_conflict is non-mutating * 2. resolve Scribe dependency from dependencyTaskIds * 3. fetch Scribe artifact via PIArtifactStore * 4. startRun with outputSchemaRef: 'artificer-rule-output-v2' * 5. pollUntilTerminal (inherited) * 6. fetchOutput → validate as unknown → cast to ArtificerRuleOutput * 7. checkLineageIntegrity (sourceScribeArtifactId consistency, ERR-008) * 8. updateRunOutput → persist serialized output * 9. write PIArtifact → markTaskSucceeded with artificer:// resultRef * * @see docs/adr/0003-peer-agent-state-machine-orchestration.md * @see BasePeerRunner in runner/base-peer-runner.ts */ import type { RunHandle } from '../runtime-protocol.js'; import type { ArtificerRuleOutput, ArtificerValidator } from './artificer-output.js'; import type { BehaviorExamplePack } from './behavior-example-pack.js'; import type { TaskRecord } from '../task-status.js'; import { type PDErrorCategory } from '../error-categories.js'; import { type RepairPayload, type LastValidatorErrors } from './pitask-metadata.js'; import { type ArtificerDreamerContext, type ArtificerHostSemanticContext } from './artificer-prompt-builder.js'; export type { ArtificerHostSemanticContext }; import { type RepairReplayContext } from './repair-replay-resolver.js'; import { BasePeerRunner } from '../runner/base-peer-runner.js'; import type { PeerRunnerOptions, PeerRunnerDeps, PeerRunnerResult, PeerRunnerValidationResult } from '../runner/peer-runner-types.js'; import type { EffectivePdConfig } from '../config/pd-config-types.js'; /** Context built by ArtificerRunner.buildContext() and consumed by invokeRuntime(). */ interface ArtificerContext { readonly contextHash: string; readonly scribeArtifact: string | null; readonly sourceScribeArtifactId: string | null; /** * Prior adversarial replay failures (PRI-428). Non-null only on Round-2+ * retries inside runAdversarialLoop, read from the task's * PITaskMetadata.adversarialFeedback. Forwarded to the prompt builder so the * LLM can make targeted corrections. */ readonly adversarialFeedback: string | null; /** * P1-1: Rollout-reviewer revision feedback. Non-null when this task was * reopened by rollout needs_revision routing (code_tool_hook channel). * Raw validated text from PITaskMetadata.revisionFeedback; appended to the * prompt so the artificer addresses each requiredChange. */ readonly revisionFeedback?: string | null; /** * Dreamer candidate 5-dim context (PRI-508). Undefined when: * - scribe artifact lacks sourceTrace.dreamerArtifactId (pre-PRI-508 flows) * - dreamer artifact cannot be resolved (best-effort, non-blocking) * - dreamer artifact contentJson fails runtime validation * Lineage: scribe.sourceTrace.dreamerArtifactId → dreamer artifact → candidates[0] (rc-6). */ readonly dreamerContext?: ArtificerDreamerContext; /** * Evaluator repair feedback (PRI-509). Non-null only on artificer repair * tasks seeded by evaluator needs_revision. Constructed by buildContext from * the task's PITaskMetadata.repairPayload (already validated by * isValidRepairPayload in pitask-metadata.ts). Forwarded to the prompt * builder so the LLM addresses each requiredChange instead of regenerating * blind. Null on Round-1 artificer tasks (backward compatible). * * Loop state freshness (rc-7, EP-05): repairPayload.repairIteration is * written at task creation time, never inferred at read. Each repair round * reads the current evaluator's feedback — never a cached value. * * PR B: this is the NO-REPLAY base string (concerns + required changes). * The deterministic replay evidence is carried separately by `replayContext` * so the Shared Information Plane can choose its channel (design §26/§35): * - manifest focused → `replay.*` fields carry it, base string is used; * - flag off/fallback → the PR-A evidence block is appended here, because * the replay evidence MUST survive the flag-off production path. */ readonly repairFeedback: string | null; /** * The current task's validated RepairPayload (PR B). Ephemeral — it is the * already-durable task metadata, re-read here only so invokeRuntime can * re-render the feedback string with the channel-appropriate replay block. * It is never re-persisted and never gains `failedCases` (design §21). */ readonly repairPayload?: RepairPayload; /** * PR-A resolved deterministic replay evidence (PR B). Present only when this * repair round's source replay ran and FAILED. Ephemeral: exists for this * buildContext/invokeRuntime pair, never persisted, never a second fact store * — the durable authority remains the source Evaluator artifact. */ readonly replayContext?: RepairReplayContext; /** * PRI-700 因子 B (Owner 决策 2026-09-07): 上一次 attempt 的 validator * 拒绝全文。从 task.diagnosticJson 顶层 lastValidatorErrors 键读取 * (parseLastValidatorErrors 已做信任边界验证),仅在新鲜度判定通过 * (sourceAttemptCount === 当前 attempt-1,isFreshForNextAttempt)时 * 携带。修复 attempt N+1 的 prompt 因此携带 attempt N 被拒的确切 * 契约原因——不再零新信息重试。Ephemeral,不回写、不进 RepairPayload。 */ readonly priorValidatorErrors?: LastValidatorErrors; } export type ArtificerRunnerResultStatus = 'succeeded' | 'failed' | 'retried'; export interface ArtificerRunnerResult { readonly status: ArtificerRunnerResultStatus; readonly taskId: string; readonly runId?: string; readonly artifactId?: string; readonly resultRef?: string; readonly contextHash?: string; readonly output?: ArtificerRuleOutput; readonly errorCategory?: PDErrorCategory; readonly failureReason?: string; readonly attemptCount: number; } export interface ArtificerRunnerOptions { readonly pollIntervalMs?: number; readonly timeoutMs?: number; readonly defaultMaxAttempts?: number; readonly owner: string; readonly runtimeKind: string; readonly agentId?: string; } export interface ResolvedArtificerRunnerOptions { readonly pollIntervalMs: number; readonly timeoutMs: number; readonly defaultMaxAttempts: number; readonly owner: string; readonly runtimeKind: string; readonly agentId: string; } declare const DEFAULT_ARTIFICER_RUNNER_OPTIONS: Readonly>; export declare function resolveArtificerRunnerOptions(options: ArtificerRunnerOptions): ResolvedArtificerRunnerOptions; export interface ArtificerRunnerDeps extends PeerRunnerDeps { readonly validator: ArtificerValidator; /** * PRI-780: v2-only generation. The pack stays optional at the type level * only because task-driven constructors (consumer cycle, run-once) have no * Owner-labelled pack channel — a missing pack fails the attempt LOUD at * prompt-build time (no v1/action-only fallback exists). */ readonly behaviorExamplePack?: BehaviorExamplePack; } /** Options for the ArtificerRunner. Adds effectiveConfig for feature flag * resolution (Issue 2: `artificer_output_retry`), mirroring the diagnostician * runners' pattern (diag-rootcause/diag-distiller). */ export interface ArtificerRunnerOptions extends PeerRunnerOptions { /** Effective PD config for feature flag resolution (ADR-0019). */ readonly effectiveConfig?: EffectivePdConfig; /** * PRI-741: optional host semantic projection (real host tool names + kinds * from the ToolSemanticRegistry host layer). Forwarded to the prompt builder * so generation anchors on canonicalKind + host-dispatchable tool names. * Undefined = prompt unchanged (backward compatible). */ readonly hostSemanticContext?: ArtificerHostSemanticContext; } export declare class ArtificerRunner extends BasePeerRunner { private readonly validator; private readonly behaviorExamplePack; private readonly hostSemanticContext; constructor(deps: ArtificerRunnerDeps, options: ArtificerRunnerOptions); /** * PRI-700 因子 B(评审 P1 修正):lastValidatorErrors 回喂的新鲜度由 * 持久化来源标识判定——isFreshForNextAttempt(pitask-metadata.ts, * sourceAttemptCount === 当前 attempt-1),取代原先的进程内消费集合: * 运行时错误不清除记录时,attempt N+2 或进程重启后的重试不得复用 * attempt N 的旧错误(EP-05 loop state freshness)。Suppression 在唯一 * 消费点发单个事件(rc-9)。 */ /** * Permanent error categories for the artificer runner. * * Issue 2 (Codex E2E): `output_invalid` (malformed LLM output, e.g. the LLM * never calls submit_rulecode) was unconditionally permanent → the * internalization pipeline dead-ends with no retry/fallback. When the * `artificer_output_retry` flag is ON, `output_invalid` is excluded so the * base runner's retry policy (bounded by task.maxAttempts) retries it. * Flag-off / no effectiveConfig = legacy behavior (permanent failure). */ get permanentErrorCategories(): ReadonlySet; /** * Issue 2: whether the `artificer_output_retry` feature flag is on. * Reads from effectiveConfig; returns false when absent (legacy behavior — * output_invalid stays permanent). Mirrors isDegradationEnabled (ADR-0019). */ private isArtificerOutputRetryEnabled; buildContext(taskId: string): Promise; invokeRuntime(taskId: string, context: ArtificerContext): Promise; /** * EP002-R4: restore Owner-labelled goldenTraceCases from the authoritative * (bounded) pack BEFORE validation. The v2 contract requires the artifact to * carry the Owner-labelled evidence verbatim — but demanding that the MODEL * transcribe multi-KB history windows byte-perfectly rejected honest echoes * on real-sized packs (live evidence: 2/2 L2 submissions succeeded, both * rejected as "was rewritten"). Mechanical restoration is strictly STRONGER * against fabrication: the model cannot rewrite what the code overwrites. * The model's own additional cases (different caseIds) pass through * untouched and still face full validation. */ protected fetchAndParseOutput(runId: string, taskId: string): Promise; validateOutput(output: unknown, taskId: string, context: ArtificerContext): Promise; succeedTask(taskId: string, runId: string, output: ArtificerRuleOutput, task: TaskRecord, contextHash: string, context: ArtificerContext): Promise>; /** * Re-inject taskId if stripped by stripLineageFields (PRI-272 / ERR-008). * Only fill when absent via Object.hasOwn — present-but-falsy values * must reach validation and fail loud (Runtime Contract Rule 3). * * generatedAt override is handled by the base class — subclasses must call * super.postFetchTransform() to inherit it. */ protected postFetchTransform(taskId: string, untrustedOutput: unknown, _context: ArtificerContext): void; protected emitSuccessTelemetry(taskId: string, output: ArtificerRuleOutput): void; } export { DEFAULT_ARTIFICER_RUNNER_OPTIONS }; //# sourceMappingURL=artificer-runner.d.ts.map