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
27 changes: 26 additions & 1 deletion packages/coding-agent/src/core/evolution-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,12 @@ import {
type InteractivePriorityGate,
type JsonObject,
NON_SUBSTANTIVE_RUNTIME_TRACE_REASON,
PersistentSkillCandidateRegistry,
PollingEvolutionWorkSource,
PollingSkillCandidateWorkSource,
RecursiveReflector,
SkillCandidateBackgroundWorker,
SkillCandidateRegistryWorker,
StructuredDeltaCurator,
StructuredSkillCurator,
type SuccessfulMaterialization,
Expand Down Expand Up @@ -861,6 +865,24 @@ export async function createCodingAgentEvolutionRuntime(
runner,
priority,
});
const candidateRegistry = new PersistentSkillCandidateRegistry({
version: VERSION,
artifacts: persistence,
store: persistence,
});
const candidateWorker = new SkillCandidateBackgroundWorker({
clock,
source: new PollingSkillCandidateWorkSource({ clock, jobs: persistence, store: persistence }),
processor: new SkillCandidateRegistryWorker({
version: VERSION,
clock,
jobs: persistence,
artifacts: persistence,
store: persistence,
registry: candidateRegistry,
}),
priority,
});
const control = new EvolutionControlService({
scope,
clock,
Expand Down Expand Up @@ -889,11 +911,14 @@ export async function createCodingAgentEvolutionRuntime(
if (recovered !== null) abandonedJobIds.delete(job.jobId);
}
control.start();
candidateWorker.start();
},
evaluateAutomatic() {
void control.triggerAutomatic().catch(() => {});
},
stop: () => control.stop(),
async stop() {
await Promise.all([control.stop(), candidateWorker.stop()]);
},
};
}

Expand Down
2 changes: 2 additions & 0 deletions packages/evolution/RECOVERY.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Recovery never treats partial output as completed work and never feeds evolution
| Rollback selection conflict | The selected harness changed after the rollback confirmation. | Reject the rollback and refresh the available action from current harness state. | Rollback never replaces an unexpected selected revision. |
| 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. |

## Operational observability

Expand All @@ -36,5 +37,6 @@ These operational records remain outside materialized product-runtime evidence,
## Verification boundaries

The deterministic validator proves provenance, recurrence, immutable closure, score bounds, and structural eligibility.
Candidate priority scoring separately orders evaluation work; it cannot skip later gates, accept a candidate, or activate a skill.
It does not prove that natural-language evidence semantically entails a proposal.
Post-activation outcome evaluation and automatic regression rollback are intentionally outside this recovery contract.
150 changes: 150 additions & 0 deletions packages/evolution/src/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const EVOLUTION_SCHEMA_VERSION = 1 as const;
export const EVOLUTION_MEMORY_ITEM_SCHEMA_VERSION = 1 as const;
export const COUNTER_OBSERVATION_SCHEMA_VERSION = 1 as const;
export const COUNTER_REPORT_SCHEMA_VERSION = 1 as const;
export const SKILL_CANDIDATE_REGISTRY_SCHEMA_VERSION = 1 as const;

export type JsonPrimitive = boolean | number | string | null;
export type JsonValue = JsonPrimitive | JsonObject | readonly JsonValue[];
Expand Down Expand Up @@ -517,6 +518,155 @@ export interface DeferredSkillCandidateFinding {
readonly reason: string;
}

export type SkillCandidateState =
| "proposed"
| "evidence-qualified"
| "drafted"
| "structurally-valid"
| "evaluation-pending"
| "accepted"
| "quarantined"
| "rejected"
| "retired";

export interface SkillCandidateRegistryWorkspace {
readonly repositoryId: string;
readonly directoryId?: string;
readonly evolutionScope: EvolutionScope;
}

export interface SkillCandidateGroupingProfile {
readonly workspace: SkillCandidateRegistryWorkspace;
readonly existingSkillIds: readonly string[];
readonly skillability: SkillabilityClassification;
readonly triggerTerms: readonly string[];
readonly proceduralIntentTerms: readonly string[];
readonly applicabilityTerms: readonly string[];
}

export interface SkillCandidateEvidenceContribution {
readonly supportingEvidenceIds: readonly string[];
readonly contradictingEvidenceIds: readonly string[];
readonly supportingTraceIds: readonly string[];
readonly contradictingTraceIds: readonly string[];
readonly supportingTaskLineageIds: readonly string[];
readonly contradictingTaskLineageIds: readonly string[];
}

export interface SkillCandidateProposalRevision {
readonly revisionId: string;
readonly candidateId: string;
readonly candidate: EvolutionArtifactReference;
readonly sourceEvolutionJobId: string;
readonly createdAt: string;
readonly evidence: SkillCandidateEvidenceContribution;
}

export interface SkillCandidatePriorityScoreComponents {
readonly independentSupport: number;
readonly evidenceBreadth: number;
readonly proceduralCompleteness: number;
readonly verificationCompleteness: number;
readonly scopeSpecificity: number;
readonly counterevidencePenalty: number;
}

export interface SkillCandidatePriorityScore {
readonly artifactKind: "skill-candidate-priority-score";
readonly metadata: EvolutionArtifactMetadata;
readonly scoreId: string;
readonly groupId: string;
readonly scorerVersion: string;
readonly evidencePriorityScore: number;
readonly scoreComponents: SkillCandidatePriorityScoreComponents;
readonly proposalRevisionIds: readonly string[];
readonly supportingTaskLineageIds: readonly string[];
readonly contradictingTaskLineageIds: readonly string[];
}

export interface SkillCandidateDispositionRecord {
readonly eventId: string;
readonly from: SkillCandidateState | null;
readonly to: SkillCandidateState;
readonly reasonCodes: readonly string[];
readonly createdAt: string;
}

export interface SkillCandidateGroup {
readonly groupId: string;
readonly profile: SkillCandidateGroupingProfile;
readonly proposalRevisions: readonly SkillCandidateProposalRevision[];
readonly state: SkillCandidateState;
readonly dispositionHistory: readonly SkillCandidateDispositionRecord[];
readonly evidence: SkillCandidateEvidenceContribution;
readonly priorityScore: EvolutionArtifactReference | null;
readonly synthesisIdempotencyKey: string;
readonly evaluationIdempotencyKey: string;
}

interface SkillCandidateRegistryEventBase {
readonly schemaVersion: typeof SKILL_CANDIDATE_REGISTRY_SCHEMA_VERSION;
readonly metadata: EvolutionArtifactMetadata;
readonly eventId: string;
readonly registrySequence: number;
}

export interface SkillCandidateProposalRecordedEvent extends SkillCandidateRegistryEventBase {
readonly type: "skill-candidate-proposal-recorded";
readonly groupId: string;
readonly profile: SkillCandidateGroupingProfile;
readonly proposalRevision: SkillCandidateProposalRevision;
}

export interface SkillCandidatePriorityScoredEvent extends SkillCandidateRegistryEventBase {
readonly type: "skill-candidate-priority-scored";
readonly groupId: string;
readonly score: EvolutionArtifactReference;
readonly proposalRevisionIds: readonly string[];
}

export interface SkillCandidateStateTransitionedEvent extends SkillCandidateRegistryEventBase {
readonly type: "skill-candidate-state-transitioned";
readonly groupId: string;
readonly from: SkillCandidateState | null;
readonly to: SkillCandidateState;
readonly reasonCodes: readonly string[];
}

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

export interface SkillCandidateWorkCheckpointedEvent extends SkillCandidateRegistryEventBase {
readonly type: "skill-candidate-work-checkpointed";
readonly workId: string;
readonly sourceEvolutionJobId: string;
readonly stage: SkillCandidateWorkStage;
readonly idempotencyKey: string;
readonly attempt: number;
readonly groupIds: readonly string[];
}

export interface SkillCandidateWorkFailedEvent extends SkillCandidateRegistryEventBase {
readonly type: "skill-candidate-work-failed";
readonly workId: string;
readonly sourceEvolutionJobId: string;
readonly idempotencyKey: string;
readonly attempt: number;
readonly message: string;
readonly retryNotBefore: string | null;
}

export type SkillCandidateRegistryEvent =
| SkillCandidateProposalRecordedEvent
| SkillCandidatePriorityScoredEvent
| SkillCandidateStateTransitionedEvent
| SkillCandidateWorkCheckpointedEvent
| SkillCandidateWorkFailedEvent;

export interface SkillCandidateRegistrySnapshot {
readonly revision: number;
readonly events: readonly SkillCandidateRegistryEvent[];
}

export interface EvolutionMemoryTarget {
readonly kind: "evolution-memory";
readonly expectedHarnessVersion: HarnessVersion;
Expand Down
Loading
Loading