Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion packages/coding-agent/src/core/evolution-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import {
SkillCandidateRegistryWorker,
StructuredDeltaCurator,
StructuredSkillCurator,
StructuredSkillSynthesizer,
type SuccessfulMaterialization,
selectEvolutionEvidence,
} from "#evolution";
Expand Down Expand Up @@ -351,7 +352,9 @@ export class CodingAgentEvolutionModelExecutor implements EvolutionModelExecutor
? "curation-invalid-response"
: request.stage === "skill-curator"
? "skill-curation-invalid-response"
: "reflection-invalid-response",
: request.stage === "skill-synthesizer"
? "skill-synthesis-invalid-response"
: "reflection-invalid-response",
retryable: false,
diagnostics: { lastCorrection },
});
Expand Down Expand Up @@ -767,6 +770,7 @@ class CodingAgentSnapshotSource implements EvolutionSnapshotSource {
reflector: "reflector-v2",
curator: "curator-v1",
skillCurator: "skill-curator-v1",
skillSynthesizer: "skill-synthesizer-v1",
validator: "validator-v1",
writer: "writer-v1",
},
Expand Down Expand Up @@ -870,6 +874,11 @@ export async function createCodingAgentEvolutionRuntime(
artifacts: persistence,
store: persistence,
});
const skillSynthesizer = new StructuredSkillSynthesizer({
version: VERSION,
artifacts: persistence,
jobs: persistence,
});
const candidateWorker = new SkillCandidateBackgroundWorker({
clock,
source: new PollingSkillCandidateWorkSource({ clock, jobs: persistence, store: persistence }),
Expand All @@ -880,6 +889,8 @@ export async function createCodingAgentEvolutionRuntime(
artifacts: persistence,
store: persistence,
registry: candidateRegistry,
synthesizer: skillSynthesizer,
model,
}),
priority,
});
Expand Down
1 change: 1 addition & 0 deletions packages/evolution/RECOVERY.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Recovery never treats partial output as completed work and never feeds evolution
| Successful rollback selection | The recorded immutable parent is still present and the evolved revision is current. | Select the parent with compare-and-swap and refresh on the next prompt boundary. | Existing in-flight prompts stay pinned to their admitted revision. |
| Unsupported skill or orchestration target | The target registry has no enabled writer route. | Preserve the finding as deferred evidence with no executable payload. | Disabled target kinds cannot reach filesystem mutation or activation. |
| Interrupted skill-candidate consolidation | A completed evolution job has checkpointed candidates but no completed candidate-work event. | Resume from append-only registry events with the same source-job idempotency key; repair missing scores or transitions before completion. | Candidate proposal revisions remain immutable, retries do not add recurrence, and partial registry appends cannot activate a skill. |
| Interrupted skill synthesis | An evidence-qualified group has no draft, or a draft-record event exists without its `drafted` transition. | Resume with the revision-bound synthesis idempotency key; reuse an already recorded immutable draft and repair the missing transition before completing candidate work. | Only evidence-qualified groups invoke synthesis, and a draft remains outside the active harness with no writer or activation path. |

## Operational observability

Expand Down
37 changes: 36 additions & 1 deletion packages/evolution/src/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ export interface EvolutionPromptVersions {
readonly reflector: string;
readonly curator: string;
readonly skillCurator: string;
readonly skillSynthesizer: string;
readonly validator: string;
readonly writer: string;
}
Expand Down Expand Up @@ -584,6 +585,29 @@ export interface SkillCandidatePriorityScore {
readonly contradictingTaskLineageIds: readonly string[];
}

export interface SkillDraft {
readonly artifactKind: "skill-draft";
readonly metadata: EvolutionArtifactMetadata;
readonly draftId: string;
readonly groupId: string;
readonly synthesisIdempotencyKey: string;
readonly proposalRevisionIds: readonly string[];
readonly sourceCandidateIds: readonly string[];
readonly sourceEvidenceIds: readonly string[];
readonly sourceTraceIds: readonly string[];
readonly sourceSkillVersions: readonly {
readonly skillId: string;
readonly version: string;
readonly contentDigest: string;
}[];
readonly repositoryInstructionDigests: readonly string[];
readonly operation: SkillCandidateOperation;
readonly proposedName: string;
readonly existingSkillIds: readonly string[];
readonly workspace: SkillCandidateWorkspaceScope;
readonly content: string;
}

export interface SkillCandidateDispositionRecord {
readonly eventId: string;
readonly from: SkillCandidateState | null;
Expand All @@ -600,6 +624,7 @@ export interface SkillCandidateGroup {
readonly dispositionHistory: readonly SkillCandidateDispositionRecord[];
readonly evidence: SkillCandidateEvidenceContribution;
readonly priorityScore: EvolutionArtifactReference | null;
readonly draft: EvolutionArtifactReference | null;
readonly synthesisIdempotencyKey: string;
readonly evaluationIdempotencyKey: string;
}
Expand All @@ -625,6 +650,14 @@ export interface SkillCandidatePriorityScoredEvent extends SkillCandidateRegistr
readonly proposalRevisionIds: readonly string[];
}

export interface SkillCandidateDraftRecordedEvent extends SkillCandidateRegistryEventBase {
readonly type: "skill-candidate-draft-recorded";
readonly groupId: string;
readonly draft: EvolutionArtifactReference;
readonly proposalRevisionIds: readonly string[];
readonly idempotencyKey: string;
}

export interface SkillCandidateStateTransitionedEvent extends SkillCandidateRegistryEventBase {
readonly type: "skill-candidate-state-transitioned";
readonly groupId: string;
Expand All @@ -633,7 +666,7 @@ export interface SkillCandidateStateTransitionedEvent extends SkillCandidateRegi
readonly reasonCodes: readonly string[];
}

export type SkillCandidateWorkStage = "consolidating" | "scoring" | "completed";
export type SkillCandidateWorkStage = "consolidating" | "scoring" | "synthesizing" | "completed";

export interface SkillCandidateWorkCheckpointedEvent extends SkillCandidateRegistryEventBase {
readonly type: "skill-candidate-work-checkpointed";
Expand All @@ -658,6 +691,7 @@ export interface SkillCandidateWorkFailedEvent extends SkillCandidateRegistryEve
export type SkillCandidateRegistryEvent =
| SkillCandidateProposalRecordedEvent
| SkillCandidatePriorityScoredEvent
| SkillCandidateDraftRecordedEvent
| SkillCandidateStateTransitionedEvent
| SkillCandidateWorkCheckpointedEvent
| SkillCandidateWorkFailedEvent;
Expand Down Expand Up @@ -943,6 +977,7 @@ export type EvolutionFailureReasonCode =
| "pipeline-invariant"
| "curation-invalid-response"
| "skill-curation-invalid-response"
| "skill-synthesis-invalid-response"
| "reflection-budget-exhausted"
| "reflection-invalid-response"
| "shutdown"
Expand Down
11 changes: 10 additions & 1 deletion packages/evolution/src/filesystem-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const JOB_STAGES = new Set([
]);
const ACTIVE_JOB_STAGES = new Set([...JOB_STAGES].filter((stage) => stage !== "completed"));
const SKILL_CANDIDATE_REGISTRY_EVENT_TYPES = new Set([
"skill-candidate-draft-recorded",
"skill-candidate-priority-scored",
"skill-candidate-proposal-recorded",
"skill-candidate-state-transitioned",
Expand All @@ -65,7 +66,7 @@ const SKILL_CANDIDATE_STATES = new Set([
"rejected",
"retired",
]);
const SKILL_CANDIDATE_WORK_STAGES = new Set(["consolidating", "scoring", "completed"]);
const SKILL_CANDIDATE_WORK_STAGES = new Set(["consolidating", "scoring", "synthesizing", "completed"]);
const JOB_OUTCOMES = new Set([
"active",
"completed",
Expand Down Expand Up @@ -274,6 +275,14 @@ function isCandidateEvidenceContribution(value: unknown): boolean {
}

function isCandidateRegistryEventContent(value: Readonly<Record<string, unknown>>): boolean {
if (value.type === "skill-candidate-draft-recorded") {
return (
typeof value.groupId === "string" &&
isArtifactReference(value.draft) &&
isStringArray(value.proposalRevisionIds) &&
typeof value.idempotencyKey === "string"
);
}
if (value.type === "skill-candidate-priority-scored") {
return (
typeof value.groupId === "string" &&
Expand Down
9 changes: 9 additions & 0 deletions packages/evolution/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ export {
type SkillabilityClassification,
type SkillCandidate,
type SkillCandidateDispositionRecord,
type SkillCandidateDraftRecordedEvent,
type SkillCandidateEvidenceContribution,
type SkillCandidateFailureHandling,
type SkillCandidateGroup,
Expand All @@ -142,6 +143,7 @@ export {
type SkillCandidateWorkStage,
type SkillCandidateWorkspaceScope,
type SkillCuratorToolCallLedgerEvent,
type SkillDraft,
type SkillMemoryRelationshipClassification,
type SkillTarget,
type ValidationIssue,
Expand Down Expand Up @@ -244,6 +246,7 @@ export type {
ReflectionResult,
Reflector,
ReflectorInput,
SkillCandidateDraftRecordingInput,
SkillCandidateRegistry,
SkillCandidateRegistryAppendResult,
SkillCandidateRegistryConsolidationInput,
Expand All @@ -252,6 +255,8 @@ export type {
SkillCurationResult,
SkillCurator,
SkillCuratorInput,
SkillSynthesizer,
SkillSynthesizerInput,
SuccessfulMaterialization,
} from "./ports.ts";
export {
Expand Down Expand Up @@ -319,6 +324,10 @@ export {
SkillCuratorToolRegistry,
type SkillCuratorToolRegistryOptions,
} from "./skill-curator-tools.ts";
export {
StructuredSkillSynthesizer,
type StructuredSkillSynthesizerDependencies,
} from "./skill-synthesizer.ts";
export {
EvolutionTargetAdapterRegistry,
type EvolutionTargetAdapterRegistryOptions,
Expand Down
42 changes: 41 additions & 1 deletion packages/evolution/src/ports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import type {
SkillCandidatePriorityScore,
SkillCandidateRegistryEvent,
SkillCandidateRegistrySnapshot,
SkillDraft,
ValidationReport,
} from "./contracts.ts";

Expand All @@ -57,6 +58,7 @@ export type EvolutionArtifact =
| ScoredProposal
| SkillCandidate
| SkillCandidatePriorityScore
| SkillDraft
| DeferredSkillCandidateFinding
| ValidationReport;

Expand Down Expand Up @@ -123,7 +125,23 @@ export interface SkillCuratorModelRequest {
readonly signal?: AbortSignal;
}

export type EvolutionModelRequest = CuratorModelRequest | ReflectorModelRequest | SkillCuratorModelRequest;
export interface SkillSynthesizerModelRequest {
readonly stage: "skill-synthesizer";
readonly model: EvolutionModelIdentity;
readonly promptVersion: string;
readonly idempotencyKey: string;
readonly input: JsonObject;
readonly outputSchema: JsonObject;
readonly tools: readonly EvolutionModelTool[];
readonly validateOutput?: (output: JsonObject) => void;
readonly signal?: AbortSignal;
}

export type EvolutionModelRequest =
| CuratorModelRequest
| ReflectorModelRequest
| SkillCuratorModelRequest
| SkillSynthesizerModelRequest;

export interface EvolutionModelResponse {
readonly output: JsonObject;
Expand Down Expand Up @@ -288,11 +306,33 @@ export interface SkillCandidateRegistryConsolidationResult {
readonly changedGroupIds: readonly string[];
}

export interface SkillCandidateDraftRecordingInput {
readonly scope: EvolutionScope;
readonly groupId: string;
readonly draft: EvolutionArtifactReference;
readonly proposalRevisionIds: readonly string[];
readonly idempotencyKey: string;
readonly createdAt: string;
}

export interface SkillCandidateRegistry {
consolidate(input: SkillCandidateRegistryConsolidationInput): Promise<SkillCandidateRegistryConsolidationResult>;
recordDraft(input: SkillCandidateDraftRecordingInput): Promise<SkillCandidateGroup>;
list(scope: EvolutionScope): Promise<readonly SkillCandidateGroup[]>;
}

export interface SkillSynthesizerInput {
readonly group: SkillCandidateGroup;
readonly model: EvolutionModelExecutor;
readonly idempotencyKey: string;
readonly createdAt: string;
readonly signal?: AbortSignal;
}

export interface SkillSynthesizer {
synthesize(input: SkillSynthesizerInput): Promise<SkillDraft>;
}

export interface EvolutionJobStore {
create(job: EvolutionJob, lease: EvolutionLeaseToken): Promise<void>;
save(job: EvolutionJob, expectedRevision: number, lease: EvolutionLeaseToken): Promise<void>;
Expand Down
Loading
Loading