@evomap/evolver-core 2.0.0-beta.13 → 2.0.0-beta.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,6 +8,7 @@ export * from './conversationSniffer.js';
8
8
  export * from './geneIntake.js';
9
9
  export * from './candidateAssembly.js';
10
10
  export * from './genePromotion.js';
11
+ export * from './publishEligibility.js';
11
12
  export * from './bans.js';
12
13
  export * from './antiDistill.js';
13
14
  export * from './orchestrator.js';
@@ -8,6 +8,7 @@ export * from './conversationSniffer.js';
8
8
  export * from './geneIntake.js';
9
9
  export * from './candidateAssembly.js';
10
10
  export * from './genePromotion.js';
11
+ export * from './publishEligibility.js';
11
12
  export * from './bans.js';
12
13
  export * from './antiDistill.js';
13
14
  export * from './orchestrator.js';
@@ -0,0 +1,34 @@
1
+ import { type GeneLearningView } from '../assetstore/learningHistory.js';
2
+ import type { AssetStoreProvider } from '../assetstore/provider.js';
3
+ export type PublishEligibilityReason = 'eligible' | 'no_proven_success';
4
+ export interface GenePublishEvidence {
5
+ geneId: string;
6
+ /** Real, value-producing successes (inert zero-work successes excluded upstream). */
7
+ success: number;
8
+ failed: number;
9
+ inert: number;
10
+ total: number;
11
+ eligible: boolean;
12
+ reason: PublishEligibilityReason;
13
+ }
14
+ /**
15
+ * The single publish/upload eligibility predicate: a gene may be published iff
16
+ * it has at least one capsule with a `success` outcome.
17
+ *
18
+ * We count `success + inert`, NOT just `success`. aggregateLearningHistory
19
+ * demotes a `success` capsule that carries no `proof_of_work` to `inert`
20
+ * (#195), but the evox capsule builder (asset_builder.rs) does NOT emit
21
+ * `proof_of_work` — a genuine successful run surfaces there as `inert`. Gating
22
+ * publish on the proof-only `success` count would therefore block genes that
23
+ * actually succeeded in production. "Has a success outcome" is the honest bar
24
+ * for the publish gate; the proof/inert distinction stays where it belongs
25
+ * (success-rate, auto-promote), not here. A gene with only failures — or one
26
+ * never run at all — has `success + inert === 0` and is not publishable.
27
+ */
28
+ export declare function isGenePublishEligible(view: Pick<GeneLearningView, 'success' | 'inert'>): boolean;
29
+ /**
30
+ * Read-only: derive a gene's outcome evidence and whether it clears the publish
31
+ * bar. Returns `no_proven_success` for a gene the store has never seen succeed,
32
+ * so callers can surface a precise, self-serviceable reason.
33
+ */
34
+ export declare function assessGenePublishEvidence(store: AssetStoreProvider, geneId: string): Promise<GenePublishEvidence>;
@@ -0,0 +1,52 @@
1
+ // Proven-success gate for publishing / uploading a gene (#581).
2
+ //
3
+ // A gene's `confidence` is minted from model self-report (solidify), text
4
+ // completeness (conversationDistiller), or is absent (like) — none of which
5
+ // prove the gene ever WORKED. Publishing such a gene to the market pairs it
6
+ // with a capsule and lets the Hub quality gate reject it after a round-trip
7
+ // ("never truly succeeded"). This predicate is the single, outcome-driven bar
8
+ // every publish/upload surface routes through, so an unproven gene is stopped
9
+ // locally with a clear reason instead of leaking to the Hub.
10
+ //
11
+ // Deliberately DISTINCT from `probationWouldPromote` (genePromotion.ts):
12
+ // auto-promotion runs with NO human in the loop, so it demands a strong, clean
13
+ // record (>= minSuccess successes AND zero failures). Publishing is human-
14
+ // initiated, so the bar is only "has this gene truly succeeded at least once"
15
+ // — a gene with many successes and one stray failure is still worth publishing,
16
+ // and blocking it on `failed === 0` would be wrong.
17
+ import { aggregateLearningHistory } from '../assetstore/learningHistory.js';
18
+ /**
19
+ * The single publish/upload eligibility predicate: a gene may be published iff
20
+ * it has at least one capsule with a `success` outcome.
21
+ *
22
+ * We count `success + inert`, NOT just `success`. aggregateLearningHistory
23
+ * demotes a `success` capsule that carries no `proof_of_work` to `inert`
24
+ * (#195), but the evox capsule builder (asset_builder.rs) does NOT emit
25
+ * `proof_of_work` — a genuine successful run surfaces there as `inert`. Gating
26
+ * publish on the proof-only `success` count would therefore block genes that
27
+ * actually succeeded in production. "Has a success outcome" is the honest bar
28
+ * for the publish gate; the proof/inert distinction stays where it belongs
29
+ * (success-rate, auto-promote), not here. A gene with only failures — or one
30
+ * never run at all — has `success + inert === 0` and is not publishable.
31
+ */
32
+ export function isGenePublishEligible(view) {
33
+ return view.success + (view.inert ?? 0) >= 1;
34
+ }
35
+ /**
36
+ * Read-only: derive a gene's outcome evidence and whether it clears the publish
37
+ * bar. Returns `no_proven_success` for a gene the store has never seen succeed,
38
+ * so callers can surface a precise, self-serviceable reason.
39
+ */
40
+ export async function assessGenePublishEvidence(store, geneId) {
41
+ const view = await aggregateLearningHistory(store, geneId);
42
+ const eligible = isGenePublishEligible(view);
43
+ return {
44
+ geneId,
45
+ success: view.success,
46
+ failed: view.failed,
47
+ inert: view.inert ?? 0,
48
+ total: view.total,
49
+ eligible,
50
+ reason: eligible ? 'eligible' : 'no_proven_success',
51
+ };
52
+ }
@@ -19,4 +19,6 @@ export declare function tracesDir(env?: Readonly<Record<string, string | undefin
19
19
  * reads it. Defaults to the evomap home root, beside assets/ + evolution/.
20
20
  * NOTE(reuse/#234): confirm this matches where the proxy/RecallHistory reads — best-effort append, so a path
21
21
  * mismatch degrades the audit line, never the reuse write itself. */
22
- export declare function assetCallLogPath(env?: Readonly<Record<string, string | undefined>>): string;
22
+ export declare function assetCallLogPath(env?: Readonly<Record<string, string | undefined>>): string;
23
+ /** Learning Ops local output dir: per-run LearningPacket drafts + trace-event JSONL (hub upload is a later slice). */
24
+ export declare function learningTraceDir(env?: Readonly<Record<string, string | undefined>>): string;
@@ -41,4 +41,8 @@ export function tracesDir(env = process.env) {
41
41
  * mismatch degrades the audit line, never the reuse write itself. */
42
42
  export function assetCallLogPath(env = process.env) {
43
43
  return join(evomapHome(env), 'asset_call_log.jsonl');
44
+ }
45
+ /** Learning Ops local output dir: per-run LearningPacket drafts + trace-event JSONL (hub upload is a later slice). */
46
+ export function learningTraceDir(env = process.env) {
47
+ return env['EVOLVER_LEARNING_TRACE_DIR'] ?? join(evomapHome(env), 'evolution', 'learning-trace');
44
48
  }
@@ -7,7 +7,7 @@ export type { EventCountsMV } from './projectors.js';
7
7
  export { LineTooLargeError, MAX_LINE_BYTES } from './eventStore.js';
8
8
  export { ArchiveSegmentConflictError, InvalidRootEventArchiveError, InvalidRootEventLogError, RootEventHistoryGapError, archiveRootEvents, inspectRootEventArchive, planRootEventArchive, readRootEventHistory, rootEventArchiveDir, rootEventArchiveSegmentName, validateRootEventHistory, ROOT_EVENT_ARCHIVE_DEFAULT_KEEP_EVENTS, } from './eventArchive.js';
9
9
  export type { RootEventArchiveOptions, RootEventArchivePlan, RootEventArchiveResult, RootEventArchiveStats, } from './eventArchive.js';
10
- export { rootEventsPath, evomapHome, mvDir, personalityStatePath, assetsDir, materialDir, materialStorePath, materialWatermarkPath, tracesDir, assetCallLogPath } from './paths.js';
10
+ export { rootEventsPath, evomapHome, mvDir, personalityStatePath, assetsDir, materialDir, materialStorePath, materialWatermarkPath, tracesDir, assetCallLogPath, learningTraceDir } from './paths.js';
11
11
  export { EVENT_SCHEMA_VERSION } from './eventSchema.js';
12
12
  export type { RootEvent, RawEvent, HumanNarrative, Actor, Replayability } from './eventSchema.js';
13
13
  export { readEvents, statusReport, listCycles, showCycle, listTriggers, dailySummary, buildNarrativeSnapshot, NARRATIVE_DEFAULT_LIMIT, NARRATIVE_MAX_LIMIT, } from './reports.js';
@@ -3,7 +3,7 @@ export { Replayer } from './replayer.js';
3
3
  export { eventCountsProjector, DEFAULT_PROJECTORS } from './projectors.js';
4
4
  export { LineTooLargeError, MAX_LINE_BYTES } from './eventStore.js';
5
5
  export { ArchiveSegmentConflictError, InvalidRootEventArchiveError, InvalidRootEventLogError, RootEventHistoryGapError, archiveRootEvents, inspectRootEventArchive, planRootEventArchive, readRootEventHistory, rootEventArchiveDir, rootEventArchiveSegmentName, validateRootEventHistory, ROOT_EVENT_ARCHIVE_DEFAULT_KEEP_EVENTS, } from './eventArchive.js';
6
- export { rootEventsPath, evomapHome, mvDir, personalityStatePath, assetsDir, materialDir, materialStorePath, materialWatermarkPath, tracesDir, assetCallLogPath } from './paths.js';
6
+ export { rootEventsPath, evomapHome, mvDir, personalityStatePath, assetsDir, materialDir, materialStorePath, materialWatermarkPath, tracesDir, assetCallLogPath, learningTraceDir } from './paths.js';
7
7
  export { EVENT_SCHEMA_VERSION } from './eventSchema.js';
8
8
  export { readEvents, statusReport, listCycles, showCycle, listTriggers, dailySummary, buildNarrativeSnapshot, NARRATIVE_DEFAULT_LIMIT, NARRATIVE_MAX_LIMIT, } from './reports.js';
9
9
  export { buildRetentionReport, defaultMaterialCursorPath, defaultMaterialCursorPaths, RETENTION_DEFAULT_MAX_MATERIAL_BYTES, RETENTION_DEFAULT_MAX_MATERIAL_RECORDS, RETENTION_DEFAULT_MAX_ROOT_BYTES, RETENTION_DEFAULT_MAX_ROOT_EVENTS, RETENTION_DEFAULT_ROOT_TAIL_EVENTS, RETENTION_DEFAULT_WATCH_RATIO, } from './retention.js';
@@ -10,6 +10,7 @@ import { type OpenPrLister } from './openPrRegistry.js';
10
10
  import type { ReuseOutcomeSummary, ReuseOutcomeEvent } from '../ops/reuseOutcomes.js';
11
11
  import type { PersonalityStore } from '../personality/store.js';
12
12
  import type { MemoryGraphProvider } from '../algo/memoryGraph.js';
13
+ import { type LearningPacketSink, type TraceSink } from '../trace/learningTrace.js';
13
14
  export interface AutoExecTask {
14
15
  id: string;
15
16
  repo: string;
@@ -110,6 +111,20 @@ export interface AutoExecDeps {
110
111
  agent?: AgentRunner;
111
112
  /** Test/custom seam: inject git instead of spawning git. */
112
113
  git?: GitRunner;
114
+ /**
115
+ * Learning trace (Learning Ops slice 2): when set, each task run gets its own AgentRunTraceRecorder
116
+ * (traceId = the cycleId) emitting run.started/model.called/tool.failed/run.completed, and a
117
+ * LearningPacket draft is submitted here after the cycle. Best-effort: trace/packet failures never
118
+ * change the verdict. Omit → zero trace work, byte-identical to today.
119
+ */
120
+ learningTrace?: {
121
+ /** Where packet drafts go (file/memory/hub-adapter implementation). */
122
+ packetSink: LearningPacketSink;
123
+ /** Optional live per-event sink (e.g. FileTraceSink JSONL tail). */
124
+ traceSink?: TraceSink;
125
+ /** Hub packet sourceRepo column; default 'evolver-v2'. */
126
+ sourceRepo?: string;
127
+ };
113
128
  }
114
129
  export interface ForcedGeneFields {
115
130
  forcedGeneId?: unknown;
@@ -11,6 +11,7 @@ import { intakeGene } from '../algo/geneIntake.js';
11
11
  import { runEvolutionCycle } from '../algo/orchestrator.js';
12
12
  import { makeSafeExecute, makeTrustedGeneResolver } from './autonomousCycle.js';
13
13
  import { findSignalHints } from './openPrRegistry.js';
14
+ import { AgentRunTraceRecorder, buildLearningPacketDraft } from '../trace/learningTrace.js';
14
15
  /** Same path-containment as the bridge guard — used here to refuse before running anything (clean verdict). */
15
16
  function withinAllowlist(repo, roots) {
16
17
  const c = resolvePath(repo);
@@ -136,6 +137,20 @@ export async function runAutoExecTask(deps, rawTask, safety) {
136
137
  // that throws degrades to solving fresh (no hub candidates) rather than failing the task.
137
138
  let hubCandidates = [];
138
139
  const cycleId = `autoexec-${task.id}`;
140
+ // Learning trace (slice 2): one recorder per task run, traceId = cycleId so the trace joins the event log.
141
+ // Purely observational — every recorder call and the packet submit are wrapped so they can never change
142
+ // the verdict or fail the task.
143
+ const traceRecorder = deps.learningTrace
144
+ ? new AgentRunTraceRecorder({
145
+ runId: cycleId,
146
+ taskId: task.id,
147
+ ...(deps.learningTrace.traceSink ? { sink: deps.learningTrace.traceSink } : {}),
148
+ })
149
+ : undefined;
150
+ try {
151
+ traceRecorder?.runStarted({ taskSummary: task.expectedEffect, signals: cycleSignals, metadata: { repo: task.repo, target: task.target } });
152
+ }
153
+ catch { /* observability only */ }
139
154
  if (deps.hubReuse) {
140
155
  try {
141
156
  hubCandidates = await executableHubCandidates(deps, await deps.hubReuse(cycleSignals, { cycleId }));
@@ -153,6 +168,7 @@ export async function runAutoExecTask(deps, rawTask, safety) {
153
168
  ...(deps.personality ? { personality: deps.personality } : {}),
154
169
  ...(deps.agent ? { agent: deps.agent } : {}),
155
170
  ...(deps.git ? { git: deps.git } : {}),
171
+ ...(traceRecorder ? { traceRecorder } : {}),
156
172
  });
157
173
  const strategyName = task.strategyName ?? deps.strategyName;
158
174
  // Intentional semantics (see #308 review M1): a task carrying `strategy` — even without an
@@ -194,6 +210,24 @@ export async function runAutoExecTask(deps, rawTask, safety) {
194
210
  });
195
211
  const status = res.finalStage === 'solidified' ? 'solidified' : res.finalStage === 'failed' ? 'failed' : 'innovated';
196
212
  const cap = res.capsule;
213
+ if (traceRecorder && deps.learningTrace) {
214
+ try {
215
+ traceRecorder.runCompleted({
216
+ status: res.finalStage === 'solidified' ? 'success' : 'failed',
217
+ ...(cap?.outcome?.score !== undefined ? { score: cap.outcome.score } : {}),
218
+ ...(res.reasons.length > 0 ? { reason: res.reasons.join('; ') } : {}),
219
+ ...(res.producedValue !== undefined ? { producedValue: res.producedValue } : {}),
220
+ ...(res.failureKind !== undefined ? { failureKind: res.failureKind } : {}),
221
+ });
222
+ await deps.learningTrace.packetSink.submit(buildLearningPacketDraft(traceRecorder, {
223
+ sourceRepo: deps.learningTrace.sourceRepo ?? 'evolver-v2',
224
+ taskSummary: task.expectedEffect,
225
+ signals: cycleSignals,
226
+ environment: { repo: task.repo, runner: safety.runner ?? 'claude' },
227
+ }));
228
+ }
229
+ catch { /* packet delivery is best-effort; never fail the task */ }
230
+ }
197
231
  if (deps.memoryGraph && res.decision?.selectedGeneId && (res.finalStage === 'solidified' || res.finalStage === 'failed')) {
198
232
  const producedSuccess = res.finalStage === 'solidified' && res.producedValue === true;
199
233
  try {
@@ -6,6 +6,7 @@ import type { GeneDecision } from '../algo/geneSelection.js';
6
6
  import type { ExecutionResult } from '../algo/cycleEngine.js';
7
7
  import { type GeneResolver, type ValidateHook, type AgentRunnerOptions, type RunnerName, type AgentRunner, type GitRunner } from './claudeBridge.js';
8
8
  import type { PersonalityStore } from '../personality/store.js';
9
+ import type { AgentRunTraceRecorder } from '../trace/learningTrace.js';
9
10
  /**
10
11
  * Resolve a gene's strategy from the store and whether it is safe to EMBED into an autonomous agent's prompt —
11
12
  * the exec-side link from #30 (provenance ledger) and the review-state gate to #45 (requireTrustedGene gate). A
@@ -47,4 +48,6 @@ export declare function makeSafeExecute(repo: string, store: AssetStoreProvider,
47
48
  personality?: PersonalityStore;
48
49
  agent?: AgentRunner;
49
50
  git?: GitRunner;
51
+ /** Optional learning-trace recorder forwarded to the exec bridge (Learning Ops slice 2). */
52
+ traceRecorder?: AgentRunTraceRecorder;
50
53
  }): (mutation: Mutation, decision: GeneDecision) => Promise<ExecutionResult>;
@@ -67,6 +67,7 @@ export function makeSafeExecute(repo, store, safety, opts = {}) {
67
67
  enabled: true,
68
68
  ...(opts.agent ? { agent: opts.agent } : {}),
69
69
  ...(opts.git ? { git: opts.git } : {}),
70
+ ...(opts.traceRecorder ? { traceRecorder: opts.traceRecorder } : {}),
70
71
  allowedRoots: safety.allowedRoots,
71
72
  ...(safety.runner ? { runner: safety.runner } : {}),
72
73
  ...(safety.isolation === 'none' ? {} : { isolation: 'worktree' }),
@@ -3,6 +3,7 @@ import type { GeneDecision } from '../algo/geneSelection.js';
3
3
  import type { ExecutionResult } from '../algo/cycleEngine.js';
4
4
  import { type GeneStrategyInfo } from './prompt.js';
5
5
  import type { PersonalityStore } from '../personality/store.js';
6
+ import type { AgentRunTraceRecorder } from '../trace/learningTrace.js';
6
7
  import { type AgentRunner, type AgentRunnerOptions, type RunnerName } from './runnerRegistry.js';
7
8
  export { resolveSpawnCommand, spawnCapture, DEFAULT_MAX_CAPTURE_BYTES, UnboundedSkipPermissionsError, UnsupportedCursorSkipPermissionsError, UnsupportedGeminiPermissionOptionsError, claudeRunnerArgs, makeClaudeHeadlessRunner, claudeHeadlessRunner, codexRunnerArgs, makeCodexHeadlessRunner, cursorRunnerArgs, makeCursorHeadlessRunner, getRunnerSpec, geminiRunnerArgs, makeGeminiHeadlessRunner, classifyGeminiRunnerResult, } from './runnerRegistry.js';
8
9
  export type { AgentRunContext, AgentRunResult, AgentRunner, RunnerName, AgentRunnerOptions, ClaudeRunnerOptions, CodexRunnerOptions, AgentRunnerSpec, } from './runnerRegistry.js';
@@ -86,6 +87,12 @@ export interface ExecBridgeOptions {
86
87
  * the store's default state (store.load never throws), it never fails the run.
87
88
  */
88
89
  personality?: PersonalityStore;
90
+ /**
91
+ * Optional learning-trace recorder (Learning Ops slice 2): when set, the bridge emits `model.called` around
92
+ * each agent spawn and `tool.failed`/`retry.attempted` metadata on failures. Best-effort observability — a
93
+ * recorder/sink failure must never affect the execution result, so every emission is wrapped defensively.
94
+ */
95
+ traceRecorder?: AgentRunTraceRecorder;
89
96
  }
90
97
  export declare class ExecBridgeDisabledError extends Error {
91
98
  constructor();
@@ -429,6 +429,23 @@ export function makeClaudeExecBridge(opts) {
429
429
  ...(opts.signal ? { signal: opts.signal } : {}),
430
430
  });
431
431
  observedRun = run;
432
+ // Learning trace (slice 2): one model.called per agent spawn — the headless runner is one opaque
433
+ // model-driven turn from the bridge's viewpoint (per-request fidelity arrives via the proxy's llm_turn
434
+ // records; recordLlmTurn folds those when a caller has them). Best-effort: never affects the result.
435
+ try {
436
+ opts.traceRecorder?.modelCalled({
437
+ ...(opts.runner !== undefined ? { provider: opts.runner } : { provider: 'claude' }),
438
+ ...(opts.agentOptions?.model !== undefined ? { model: opts.agentOptions.model } : {}),
439
+ ...(run.exitCode !== undefined && run.exitCode !== null ? { stopReason: `exit_${run.exitCode}` } : {}),
440
+ });
441
+ if (!run.ok) {
442
+ opts.traceRecorder?.toolFailed({
443
+ toolName: 'agent_runner',
444
+ error: run.error ?? run.failureKind ?? 'agent run failed',
445
+ });
446
+ }
447
+ }
448
+ catch { /* trace emission is observability only */ }
432
449
  if (run.failureKind === 'cancelled' || opts.signal?.aborted)
433
450
  throw new ExecBridgeRunCancelledError();
434
451
  // A worktree can contain three independent change surfaces after the agent exits: staged tracked changes,
@@ -111,6 +111,75 @@ export interface ReuseResultReceipt {
111
111
  reason?: string;
112
112
  id?: string;
113
113
  }
114
+ export type LearningAssetType = 'memory' | 'skill' | 'playbook' | 'eval';
115
+ export type LearningAssetStatus = 'pending_review' | 'active' | 'disabled' | 'stale' | 'archived';
116
+ export type LearningAssetEffectiveStatus = LearningAssetStatus | 'expired';
117
+ export type LearningAssetUsageOutcome = ReuseResultOutcome;
118
+ export interface LearningAssetOwner {
119
+ node_id?: string | null;
120
+ user_id?: string | null;
121
+ org_id?: string | null;
122
+ workspace_id?: string | null;
123
+ }
124
+ /** Hub-owned learning asset record. Core keeps it opaque; adapters own wire shape and auth. */
125
+ export interface LearningAssetRecord {
126
+ asset_id: string;
127
+ type: LearningAssetType;
128
+ source_packet_id?: string;
129
+ scope?: string[];
130
+ owner?: LearningAssetOwner;
131
+ title?: string;
132
+ summary?: string;
133
+ confidence?: number;
134
+ last_used_at?: string | null;
135
+ usage_count?: number;
136
+ success_count?: number;
137
+ success_rate?: number;
138
+ expires_at?: string | null;
139
+ conflicts_with?: string[];
140
+ status?: LearningAssetStatus;
141
+ effective_status?: LearningAssetEffectiveStatus;
142
+ status_reason?: string;
143
+ provenance?: Record<string, unknown>;
144
+ payload?: Record<string, unknown>;
145
+ created_at?: string | null;
146
+ updated_at?: string | null;
147
+ [k: string]: unknown;
148
+ }
149
+ export interface LearningAssetListOptions {
150
+ type?: LearningAssetType;
151
+ scope?: readonly string[];
152
+ status?: LearningAssetStatus | readonly LearningAssetStatus[];
153
+ includeExpired?: boolean;
154
+ includePayload?: boolean;
155
+ limit?: number;
156
+ }
157
+ export interface LearningAssetListResult {
158
+ assets: LearningAssetRecord[];
159
+ limit: number;
160
+ reason?: string;
161
+ }
162
+ export interface LearningAssetUsageReport {
163
+ assetId?: string;
164
+ usedAssetIds?: readonly string[];
165
+ outcome: LearningAssetUsageOutcome;
166
+ score?: number;
167
+ sourceEventId: string;
168
+ reason?: string;
169
+ }
170
+ export interface LearningAssetUsageResultRow {
171
+ asset_id: string;
172
+ recorded: boolean;
173
+ duplicated?: boolean;
174
+ error?: string;
175
+ [k: string]: unknown;
176
+ }
177
+ /** Learning-asset usage reporting is best-effort; failures must not break runtime solve paths. */
178
+ export interface LearningAssetUsageReceipt {
179
+ recorded: boolean;
180
+ reason?: string;
181
+ results: LearningAssetUsageResultRow[];
182
+ }
114
183
  /**
115
184
  * Pre-publish dry-run result. The hub runs the same hub-side quality + content-safety
116
185
  * gate as publish but stores nothing and charges no credits, so an agent can check a
@@ -141,6 +210,10 @@ export interface RecipeCreateRequest {
141
210
  pricePerExecution?: number;
142
211
  currency?: string;
143
212
  maxConcurrent?: number;
213
+ idempotencyKey?: string;
214
+ }
215
+ export interface RecipePublishOptions {
216
+ idempotencyKey?: string;
144
217
  }
145
218
  export interface RecipeReceipt {
146
219
  recipeId?: string;
@@ -159,11 +232,11 @@ export interface RecipeExpressionReceipt extends RecipeReceipt {
159
232
  /** Optional Hub recipe/DNA capability. Core stays wire-agnostic; adapters own REST shape. */
160
233
  export interface RecipeCapability {
161
234
  create(request: RecipeCreateRequest): Promise<RecipeReceipt>;
162
- publish(recipeId: string): Promise<RecipeReceipt>;
235
+ publish(recipeId: string, options?: RecipePublishOptions): Promise<RecipeReceipt>;
163
236
  get(recipeId: string): Promise<RecipeFetchReceipt>;
164
237
  express(recipeId: string, request?: RecipeExpressRequest): Promise<RecipeExpressionReceipt>;
165
238
  }
166
- export type HubCapabilityName = 'publish' | 'fetch' | 'search' | 'task' | 'mailbox' | 'auth' | 'audit' | 'air_gap' | 'tenant_isolation' | 'marketplace' | 'economy' | 'questions' | 'recipes' | 'agent_directory';
239
+ export type HubCapabilityName = 'publish' | 'fetch' | 'search' | 'task' | 'mailbox' | 'auth' | 'audit' | 'air_gap' | 'tenant_isolation' | 'marketplace' | 'economy' | 'questions' | 'recipes' | 'agent_directory' | 'learning_assets';
167
240
  /** hub 能力清单(可选). core 据此动态适配 UI/行为, "按需耦合". */
168
241
  export interface HubManifest {
169
242
  capabilities: HubCapabilityName[] | string[];
@@ -231,6 +304,10 @@ export interface HubCapability {
231
304
  fetchAssetById?(assetId: string): Promise<AssetRecord | null>;
232
305
  /** 可选: 报告某个资产被复用后的结果. PHub/enterprise adapter 实现; 不支持时 runtime 应明确降级. */
233
306
  recordReuseResult?(report: ReuseResultReport): Promise<ReuseResultReceipt>;
307
+ /** 可选: 列出 hub 端 runtime learning assets. Missing means the adapter does not support this MVP surface. */
308
+ listLearningAssets?(options?: LearningAssetListOptions): Promise<LearningAssetListResult>;
309
+ /** 可选: 上报 learning asset 使用结果. Best-effort; failures must never break solve paths. */
310
+ recordLearningAssetUsage?(report: LearningAssetUsageReport): Promise<LearningAssetUsageReceipt>;
234
311
  /**
235
312
  * 可选: 发布前 dry-run 校验(公版 POST /a2a/validate). 跑与 publish 相同的 hub 端质量门禁 + 内容安全扫描,
236
313
  * 但不落库、不计费; 经由 Proxy 暴露时调用方需先执行与 publish 相同的客户端脱敏/泄漏拦截.
@@ -1,2 +1,3 @@
1
1
  export * from './trajectory.js';
2
- export * from './trajectoryExport.js';
2
+ export * from './trajectoryExport.js';
3
+ export * from './learningTrace.js';
@@ -1,2 +1,3 @@
1
1
  export * from './trajectory.js';
2
- export * from './trajectoryExport.js';
2
+ export * from './trajectoryExport.js';
3
+ export * from './learningTrace.js';
@@ -0,0 +1,194 @@
1
+ import type { Observer } from '../observers/observerBus.js';
2
+ import type { TraceTurnDraft } from './trajectory.js';
3
+ export declare const TRACE_EVENT_SCHEMA = "trace_event.v0";
4
+ export declare const LEARNING_PACKET_SCHEMA = "learning_packet.v0";
5
+ /** Lifecycle vocabulary for slice 1. Kept flat + closed so hub-side eventType stays queryable. */
6
+ export declare const TRACE_EVENT_TYPES: readonly ["run.started", "run.completed", "model.called", "tool.called", "tool.failed", "retry.attempted", "reflection.recorded", "intervention.received"];
7
+ export type TraceEventType = (typeof TRACE_EVENT_TYPES)[number];
8
+ /** Mirrors hub LearningOpsTraceEvent columns (eventId/eventType/traceId/sessionId/taskId/sequence/occurredAt/payload/metadata). */
9
+ export interface TraceEvent {
10
+ schemaVersion: typeof TRACE_EVENT_SCHEMA;
11
+ eventId: string;
12
+ eventType: TraceEventType;
13
+ /** Run-scoped trace id; every event of one agent run shares it. */
14
+ traceId: string;
15
+ sessionId?: string;
16
+ taskId?: string;
17
+ /** 1-based, strictly increasing per run — hub-side ordering key. */
18
+ sequence: number;
19
+ occurredAt: string;
20
+ payload: Record<string, unknown>;
21
+ metadata: Record<string, unknown>;
22
+ }
23
+ export interface TraceSink {
24
+ emit(event: TraceEvent): void;
25
+ }
26
+ export declare class InMemoryTraceSink implements TraceSink {
27
+ readonly events: TraceEvent[];
28
+ emit(event: TraceEvent): void;
29
+ }
30
+ /** JSONL append sink for local inspection / offline replay. */
31
+ export declare class FileTraceSink implements TraceSink {
32
+ private readonly path;
33
+ constructor(path: string);
34
+ emit(event: TraceEvent): void;
35
+ }
36
+ export declare class ConsoleTraceSink implements TraceSink {
37
+ private readonly log;
38
+ constructor(log?: (line: string) => void);
39
+ emit(event: TraceEvent): void;
40
+ }
41
+ export interface AgentRunTraceRecorderOptions {
42
+ /** Stable run identity; becomes traceId and the packet sourceRun. */
43
+ runId: string;
44
+ sessionId?: string;
45
+ taskId?: string;
46
+ sink?: TraceSink;
47
+ /** Injected clock for deterministic tests. */
48
+ now?: () => number;
49
+ /** Injected id factory for deterministic tests. */
50
+ eventIdFactory?: (sequence: number) => string;
51
+ }
52
+ export interface RunStartedInput {
53
+ taskSummary?: string;
54
+ signals?: readonly string[];
55
+ geneId?: string;
56
+ metadata?: Record<string, unknown>;
57
+ }
58
+ export interface RunCompletedInput {
59
+ status: 'success' | 'failed';
60
+ score?: number;
61
+ reason?: string;
62
+ producedValue?: boolean;
63
+ failureKind?: string;
64
+ }
65
+ export interface ModelCalledInput {
66
+ provider?: string;
67
+ model?: string;
68
+ requestId?: string;
69
+ latencyMs?: number;
70
+ usage?: Record<string, unknown>;
71
+ stopReason?: string;
72
+ }
73
+ export interface ToolCalledInput {
74
+ toolName: string;
75
+ callId?: string;
76
+ durationMs?: number;
77
+ metadata?: Record<string, unknown>;
78
+ }
79
+ export interface ToolFailedInput {
80
+ toolName: string;
81
+ callId?: string;
82
+ error: string;
83
+ }
84
+ export interface RetryInput {
85
+ attempt: number;
86
+ reason?: string;
87
+ target?: string;
88
+ }
89
+ export interface ReflectionInput {
90
+ outcome: string;
91
+ action?: string;
92
+ summary?: string;
93
+ }
94
+ export interface InterventionInput {
95
+ kind: string;
96
+ actorId?: string;
97
+ detail?: string;
98
+ }
99
+ /**
100
+ * Per-run trace recorder: the single hook surface the runtime calls at lifecycle points.
101
+ * Sequence numbers are assigned here (monotonic per run), so downstream ordering never depends on
102
+ * sink latency or clock resolution.
103
+ */
104
+ export declare class AgentRunTraceRecorder {
105
+ private readonly opts;
106
+ private sequence;
107
+ private readonly recorded;
108
+ constructor(opts: AgentRunTraceRecorderOptions);
109
+ get events(): readonly TraceEvent[];
110
+ runStarted(input?: RunStartedInput): TraceEvent;
111
+ modelCalled(input?: ModelCalledInput): TraceEvent;
112
+ toolCalled(input: ToolCalledInput): TraceEvent;
113
+ toolFailed(input: ToolFailedInput): TraceEvent;
114
+ retryAttempted(input: RetryInput): TraceEvent;
115
+ reflectionRecorded(input: ReflectionInput): TraceEvent;
116
+ interventionReceived(input: InterventionInput): TraceEvent;
117
+ runCompleted(input: RunCompletedInput): TraceEvent;
118
+ /** Fold one normalized llm_turn (trace/trajectory.ts) into model.called + tool.called/tool.failed events. */
119
+ recordLlmTurn(turn: TraceTurnDraft): TraceEvent[];
120
+ private record;
121
+ }
122
+ /**
123
+ * Bypass-side bridge (never blocks the write path): maps the engine's existing root events onto the
124
+ * run's TraceEvent stream, so CycleEngine needs no code change for run start/completion, reflection,
125
+ * or human intervention coverage.
126
+ */
127
+ export declare function learningTraceObserver(deps: {
128
+ recorder: AgentRunTraceRecorder;
129
+ timeoutMs?: number;
130
+ }): Observer;
131
+ export interface LearningPacketDraftInput {
132
+ /** e.g. 'evolver-v2'. Hub column sourceRepo. */
133
+ sourceRepo: string;
134
+ taskSummary?: string;
135
+ signals?: readonly string[];
136
+ environment?: Record<string, unknown>;
137
+ }
138
+ /** Local draft aligned with hub LearningOpsPacket ingest fields; placeholders are explicit, not implied. */
139
+ export interface LearningPacketDraft {
140
+ schemaVersion: typeof LEARNING_PACKET_SCHEMA;
141
+ status: 'draft';
142
+ source: {
143
+ repo: string;
144
+ run: string;
145
+ type: 'agent_run';
146
+ id: string;
147
+ };
148
+ task: {
149
+ taskId: string | null;
150
+ summary: string | null;
151
+ signals: string[];
152
+ };
153
+ context: {
154
+ sessionId: string | null;
155
+ traceId: string;
156
+ environment: Record<string, unknown>;
157
+ };
158
+ trajectory: TraceEvent[];
159
+ artifacts: {
160
+ placeholder: true;
161
+ items: never[];
162
+ };
163
+ evaluation: {
164
+ placeholder: true;
165
+ outcomeStatus: 'success' | 'failed' | 'unknown';
166
+ verifier: null;
167
+ failureCategory: string | null;
168
+ };
169
+ governance: {
170
+ placeholder: true;
171
+ redactionStatus: 'metadata_only';
172
+ consentStatus: 'unknown';
173
+ trainingEligible: false;
174
+ retentionPolicy: 'standard';
175
+ };
176
+ }
177
+ export declare function buildLearningPacketDraft(recorder: AgentRunTraceRecorder, input: LearningPacketDraftInput): LearningPacketDraft;
178
+ export interface LearningPacketSubmitResult {
179
+ accepted: boolean;
180
+ reason?: string;
181
+ }
182
+ export interface LearningPacketSink {
183
+ submit(draft: LearningPacketDraft): Promise<LearningPacketSubmitResult>;
184
+ }
185
+ export declare class InMemoryLearningPacketSink implements LearningPacketSink {
186
+ readonly drafts: LearningPacketDraft[];
187
+ submit(draft: LearningPacketDraft): Promise<LearningPacketSubmitResult>;
188
+ }
189
+ /** Writes one JSON file per run — the offline/no-hub path (and a manual-inspection artifact). */
190
+ export declare class FileLearningPacketSink implements LearningPacketSink {
191
+ private readonly dir;
192
+ constructor(dir: string);
193
+ submit(draft: LearningPacketDraft): Promise<LearningPacketSubmitResult>;
194
+ }