@evomap/evolver-core 2.0.0-beta.2 → 2.0.0-beta.22

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.
Files changed (240) hide show
  1. package/assets/gep/genes.jsonl +5 -5
  2. package/dist/algo/bans.d.ts +10 -1
  3. package/dist/algo/bans.js +56 -6
  4. package/dist/algo/candidateAssembly.d.ts +25 -2
  5. package/dist/algo/candidateAssembly.js +171 -26
  6. package/dist/algo/capabilityCandidates.js +10 -0
  7. package/dist/algo/conversationSniffer.js +25 -8
  8. package/dist/algo/cycleEngine.d.ts +63 -5
  9. package/dist/algo/cycleEngine.js +320 -42
  10. package/dist/algo/cycleFailureClassifier.d.ts +1 -1
  11. package/dist/algo/evolutionEvent.d.ts +26 -0
  12. package/dist/algo/evolutionEvent.js +32 -0
  13. package/dist/algo/exploration.d.ts +7 -0
  14. package/dist/algo/exploration.js +16 -3
  15. package/dist/algo/geneHealth.d.ts +36 -3
  16. package/dist/algo/geneHealth.js +47 -4
  17. package/dist/algo/geneIntake.d.ts +46 -1
  18. package/dist/algo/geneIntake.js +121 -10
  19. package/dist/algo/geneSelection.d.ts +126 -6
  20. package/dist/algo/geneSelection.js +479 -36
  21. package/dist/algo/index.d.ts +6 -1
  22. package/dist/algo/index.js +6 -1
  23. package/dist/algo/kautoProjection.d.ts +41 -0
  24. package/dist/algo/kautoProjection.js +95 -0
  25. package/dist/algo/kautoValidator.d.ts +68 -0
  26. package/dist/algo/kautoValidator.js +256 -0
  27. package/dist/algo/memoryGraph.d.ts +62 -0
  28. package/dist/algo/memoryGraph.js +86 -0
  29. package/dist/algo/orchestrator.d.ts +17 -1
  30. package/dist/algo/orchestrator.js +30 -4
  31. package/dist/algo/publishEligibility.d.ts +34 -0
  32. package/dist/algo/publishEligibility.js +52 -0
  33. package/dist/algo/solidify.d.ts +11 -2
  34. package/dist/algo/solidify.js +37 -7
  35. package/dist/algo/ucb1.d.ts +53 -0
  36. package/dist/algo/ucb1.js +156 -0
  37. package/dist/assetrepair/hubRejection.d.ts +12 -0
  38. package/dist/assetrepair/hubRejection.js +109 -0
  39. package/dist/assetrepair/index.d.ts +2 -0
  40. package/dist/assetrepair/index.js +2 -0
  41. package/dist/assetrepair/repair.d.ts +33 -0
  42. package/dist/assetrepair/repair.js +155 -0
  43. package/dist/assetstore/assetSidecarRecords.d.ts +28 -0
  44. package/dist/assetstore/assetSidecarRecords.js +384 -0
  45. package/dist/assetstore/assetSidecarRecovery.d.ts +48 -0
  46. package/dist/assetstore/assetSidecarRecovery.js +288 -0
  47. package/dist/assetstore/assetStoreHealth.d.ts +81 -0
  48. package/dist/assetstore/assetStoreHealth.js +319 -0
  49. package/dist/assetstore/assetStoreLayout.d.ts +2 -0
  50. package/dist/assetstore/assetStoreLayout.js +6 -0
  51. package/dist/assetstore/assetStoreStorage.d.ts +42 -0
  52. package/dist/assetstore/assetStoreStorage.js +336 -0
  53. package/dist/assetstore/assetSyncLedger.d.ts +91 -1
  54. package/dist/assetstore/assetSyncLedger.js +718 -59
  55. package/dist/assetstore/foreignJsonlSource.d.ts +48 -0
  56. package/dist/assetstore/foreignJsonlSource.js +150 -0
  57. package/dist/assetstore/index.d.ts +5 -0
  58. package/dist/assetstore/index.js +5 -0
  59. package/dist/assetstore/learningHistory.js +3 -3
  60. package/dist/assetstore/localAssetStoreSnapshot.d.ts +51 -0
  61. package/dist/assetstore/localAssetStoreSnapshot.js +329 -0
  62. package/dist/assetstore/localJsonl.d.ts +12 -1
  63. package/dist/assetstore/localJsonl.js +251 -42
  64. package/dist/assetstore/provenance.d.ts +93 -4
  65. package/dist/assetstore/provenance.js +368 -84
  66. package/dist/assetstore/provider.d.ts +63 -0
  67. package/dist/assetstore/provider.js +97 -6
  68. package/dist/assetstore/reviewFilter.d.ts +19 -1
  69. package/dist/assetstore/reviewFilter.js +39 -1
  70. package/dist/assetstore/reviewLedger.d.ts +8 -2
  71. package/dist/assetstore/reviewLedger.js +71 -45
  72. package/dist/assetstore/unionReadStore.d.ts +25 -0
  73. package/dist/assetstore/unionReadStore.js +119 -0
  74. package/dist/benchmark/antiGeneBenchmark.d.ts +2 -0
  75. package/dist/benchmark/antiGeneBenchmark.js +4 -3
  76. package/dist/benchmark/antiGeneRollout.d.ts +2 -0
  77. package/dist/benchmark/antiGeneRollout.js +4 -3
  78. package/dist/benchmark/index.d.ts +3 -1
  79. package/dist/benchmark/index.js +3 -1
  80. package/dist/benchmark/selectionFlatAbstention.d.ts +152 -0
  81. package/dist/benchmark/selectionFlatAbstention.js +481 -0
  82. package/dist/benchmark/triggerShift.d.ts +62 -0
  83. package/dist/benchmark/triggerShift.js +106 -0
  84. package/dist/bootstrap/envFingerprint.d.ts +9 -0
  85. package/dist/bootstrap/envFingerprint.js +5 -0
  86. package/dist/bootstrap/index.d.ts +3 -1
  87. package/dist/bootstrap/index.js +3 -1
  88. package/dist/bootstrap/lifecycleBootstrap.d.ts +111 -0
  89. package/dist/bootstrap/lifecycleBootstrap.js +433 -0
  90. package/dist/bootstrap/v1EnvCompat.d.ts +113 -0
  91. package/dist/bootstrap/v1EnvCompat.js +303 -0
  92. package/dist/events/eventArchive.d.ts +2 -0
  93. package/dist/events/eventArchive.js +13 -3
  94. package/dist/events/eventSchema.d.ts +7 -7
  95. package/dist/events/eventStore.d.ts +2 -0
  96. package/dist/events/eventStore.js +5 -1
  97. package/dist/events/ingest.d.ts +2 -1
  98. package/dist/events/ingest.js +15 -0
  99. package/dist/events/paths.d.ts +12 -10
  100. package/dist/events/paths.js +24 -20
  101. package/dist/events/public.d.ts +2 -2
  102. package/dist/events/public.js +2 -2
  103. package/dist/events/reports.d.ts +2 -0
  104. package/dist/events/reports.js +4 -0
  105. package/dist/exec/autoExec.d.ts +68 -4
  106. package/dist/exec/autoExec.js +392 -33
  107. package/dist/exec/autonomousCycle.d.ts +31 -4
  108. package/dist/exec/autonomousCycle.js +71 -13
  109. package/dist/exec/claudeBridge.d.ts +80 -15
  110. package/dist/exec/claudeBridge.js +845 -76
  111. package/dist/exec/executionBinding.d.ts +414 -0
  112. package/dist/exec/executionBinding.js +588 -0
  113. package/dist/exec/index.d.ts +1 -0
  114. package/dist/exec/index.js +1 -0
  115. package/dist/exec/openPrRegistry.d.ts +8 -2
  116. package/dist/exec/openPrRegistry.js +32 -22
  117. package/dist/exec/prompt.js +14 -1
  118. package/dist/exec/proofOfWork.d.ts +1 -1
  119. package/dist/exec/proofOfWork.js +2 -2
  120. package/dist/exec/runnerRegistry.d.ts +153 -36
  121. package/dist/exec/runnerRegistry.js +848 -65
  122. package/dist/exec/selfPr.js +1 -7
  123. package/dist/feedback/envelope.d.ts +61 -0
  124. package/dist/feedback/envelope.js +168 -0
  125. package/dist/feedback/index.d.ts +1 -0
  126. package/dist/feedback/index.js +1 -0
  127. package/dist/hooks/hooks.js +1 -0
  128. package/dist/hub/assetCallLog.d.ts +35 -1
  129. package/dist/hub/assetCallLog.js +124 -1
  130. package/dist/hub/bindings.d.ts +8 -1
  131. package/dist/hub/bindings.js +29 -8
  132. package/dist/hub/capability.d.ts +130 -4
  133. package/dist/hub/conversationDistiller.d.ts +19 -0
  134. package/dist/hub/conversationDistiller.js +115 -37
  135. package/dist/hub/fake.d.ts +3 -2
  136. package/dist/hub/fake.js +2 -1
  137. package/dist/hub/index.d.ts +1 -0
  138. package/dist/hub/index.js +1 -0
  139. package/dist/hub/questionGenerator.d.ts +5 -1
  140. package/dist/hub/questionGenerator.js +8 -6
  141. package/dist/hub/recipeCompose.d.ts +27 -0
  142. package/dist/hub/recipeCompose.js +90 -0
  143. package/dist/hub/sanitize.js +122 -7
  144. package/dist/index.d.ts +6 -1
  145. package/dist/index.js +7 -1
  146. package/dist/issueReporter/index.d.ts +156 -0
  147. package/dist/issueReporter/index.js +1688 -0
  148. package/dist/mailbox/dispatch.d.ts +1 -1
  149. package/dist/mailbox/dispatch.js +22 -6
  150. package/dist/mailbox/envelope.d.ts +7 -1
  151. package/dist/mailbox/envelope.js +9 -2
  152. package/dist/mailbox/ipcServer.d.ts +12 -2
  153. package/dist/mailbox/ipcServer.js +183 -13
  154. package/dist/mailbox/store.d.ts +89 -3
  155. package/dist/mailbox/store.js +895 -41
  156. package/dist/modelCompatibility.d.ts +164 -0
  157. package/dist/modelCompatibility.js +309 -0
  158. package/dist/observers/valueDigestObserver.d.ts +9 -0
  159. package/dist/observers/valueDigestObserver.js +35 -2
  160. package/dist/ops/cleanup.js +1 -1
  161. package/dist/ops/evolutionGraphProjection.d.ts +20 -0
  162. package/dist/ops/evolutionGraphProjection.js +315 -0
  163. package/dist/ops/index.d.ts +2 -1
  164. package/dist/ops/index.js +2 -1
  165. package/dist/ops/savingsCore.js +1 -2
  166. package/dist/ops/selfUpdate.d.ts +18 -1
  167. package/dist/ops/selfUpdate.js +88 -23
  168. package/dist/ops/valueOutreach.d.ts +3 -1
  169. package/dist/ops/valueOutreach.js +5 -1
  170. package/dist/personality/schema.d.ts +24 -24
  171. package/dist/schema/evolutionGraph.d.ts +784 -0
  172. package/dist/schema/evolutionGraph.js +187 -0
  173. package/dist/schema/index.d.ts +1 -0
  174. package/dist/schema/index.js +1 -0
  175. package/dist/schema/proofOfWork.d.ts +125 -6
  176. package/dist/schema/proofOfWork.js +102 -4
  177. package/dist/schema/signal.d.ts +3 -3
  178. package/dist/schema/signal.js +1 -1
  179. package/dist/shadow/shadowHub.js +1 -0
  180. package/dist/signals/curriculum.d.ts +55 -0
  181. package/dist/signals/curriculum.js +202 -0
  182. package/dist/signals/cycleHistoryFromEvents.js +17 -8
  183. package/dist/signals/expand.d.ts +15 -1
  184. package/dist/signals/expand.js +169 -1
  185. package/dist/signals/extractor.d.ts +2 -2
  186. package/dist/signals/extractor.js +31 -6
  187. package/dist/signals/index.d.ts +4 -1
  188. package/dist/signals/index.js +4 -1
  189. package/dist/signals/metaSignals.d.ts +4 -0
  190. package/dist/signals/metaSignals.js +42 -0
  191. package/dist/signals/scopeVocabulary.d.ts +75 -0
  192. package/dist/signals/scopeVocabulary.js +91 -0
  193. package/dist/signals/signalGate.js +1 -1
  194. package/dist/signals/taskDomain.d.ts +22 -0
  195. package/dist/signals/taskDomain.js +43 -0
  196. package/dist/strategy/constraintAblation.d.ts +64 -0
  197. package/dist/strategy/constraintAblation.js +2820 -0
  198. package/dist/strategy/constraintAblationPredicates.d.ts +31 -0
  199. package/dist/strategy/constraintAblationPredicates.js +339 -0
  200. package/dist/strategy/index.d.ts +2 -1
  201. package/dist/strategy/index.js +2 -1
  202. package/dist/trace/index.d.ts +3 -1
  203. package/dist/trace/index.js +3 -1
  204. package/dist/trace/learningTrace.d.ts +216 -0
  205. package/dist/trace/learningTrace.js +298 -0
  206. package/dist/trace/proxyTurns.d.ts +31 -0
  207. package/dist/trace/proxyTurns.js +137 -0
  208. package/dist/trace/trajectory.d.ts +8 -0
  209. package/dist/trace/trajectory.js +14 -2
  210. package/dist/util/fetchPort.d.ts +1 -0
  211. package/dist/util/fetchPort.js +11 -0
  212. package/dist/util/fileLock.d.ts +113 -7
  213. package/dist/util/fileLock.js +1035 -80
  214. package/dist/util/index.d.ts +3 -1
  215. package/dist/util/index.js +2 -1
  216. package/dist/verify/index.d.ts +2 -1
  217. package/dist/verify/index.js +1 -1
  218. package/dist/verify/sandboxRunner.d.ts +30 -0
  219. package/dist/verify/sandboxRunner.js +370 -27
  220. package/dist/verify/sandboxedValidation.d.ts +16 -2
  221. package/dist/verify/sandboxedValidation.js +200 -22
  222. package/dist/verify/validation.d.ts +35 -4
  223. package/dist/verify/validation.js +164 -16
  224. package/dist/wire/geneHints.d.ts +88 -16
  225. package/dist/wire/geneHints.js +124 -15
  226. package/dist/wire/index.d.ts +8 -3
  227. package/dist/wire/index.js +2 -2
  228. package/dist/wire/schemaGate.d.ts +21 -0
  229. package/dist/wire/schemaGate.js +119 -10
  230. package/dist/workflow/dsl.d.ts +24 -3
  231. package/dist/workflow/dsl.js +4 -0
  232. package/dist/workflow/engine.d.ts +5 -1
  233. package/dist/workflow/engine.js +3 -0
  234. package/dist/workflow/index.d.ts +3 -1
  235. package/dist/workflow/index.js +3 -1
  236. package/dist/workflow/runtime.d.ts +110 -0
  237. package/dist/workflow/runtime.js +1298 -0
  238. package/dist/workflow/stateStore.d.ts +172 -0
  239. package/dist/workflow/stateStore.js +1044 -0
  240. package/package.json +12 -4
@@ -1,14 +1,19 @@
1
1
  import type { AssetStoreProvider } from '../assetstore/provider.js';
2
2
  import { type ProvenanceStore } from '../assetstore/provenance.js';
3
3
  import type { ReviewLedger } from '../assetstore/reviewLedger.js';
4
- import type { GeneCandidateInput } from '../algo/geneSelection.js';
5
- import type { CycleEngine, SolidifyPermitGate } from '../algo/cycleEngine.js';
4
+ import type { GeneCandidateInput, SelectionGuardMode } from '../algo/geneSelection.js';
5
+ import type { CycleEngine, ExecutionFailureKind, ExecutionResult, SolidifyPermitGate } from '../algo/cycleEngine.js';
6
6
  import { type AutonomousSafety } from './autonomousCycle.js';
7
- import type { GitRunner, ValidateHook } from './claudeBridge.js';
7
+ import { type GitRunner, type ValidateHook } from './claudeBridge.js';
8
8
  import type { AgentRunner } from './runnerRegistry.js';
9
9
  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
+ import type { MemoryGraphProvider } from '../algo/memoryGraph.js';
13
+ import type { SelectionPolicy } from '../algo/ucb1.js';
14
+ import { type LearningPacketSink, type TraceSink } from '../trace/learningTrace.js';
15
+ import type { TraceReadOptions } from '../trace/trajectoryExport.js';
16
+ import { ExecutionBindingJournal, type BindingAuthorityChecks, type ExecutionBindingInput } from './executionBinding.js';
12
17
  export interface AutoExecTask {
13
18
  id: string;
14
19
  repo: string;
@@ -28,10 +33,18 @@ export interface AutoExecTask {
28
33
  /** Optional explicit strategy preset name for this task; wins over daemon default and meta-signal auto-detection. */
29
34
  strategyName?: string;
30
35
  validationCmds?: readonly string[];
36
+ /** Frozen external execution binding; bound tasks never fall back to unbound execution. */
37
+ execution_binding?: ExecutionBindingInput;
31
38
  }
32
39
  export interface AutoExecVerdict {
33
40
  taskId: string;
34
- status: 'refused' | 'skipped' | 'solidified' | 'failed' | 'innovated';
41
+ status: 'refused' | 'skipped' | 'solidified' | 'failed' | 'innovated' | 'unsafe_to_replay';
42
+ binding_digest?: `sha256:${string}`;
43
+ run_id?: string;
44
+ execution_terminal?: ExecutionResult['executionTerminal'];
45
+ hub_lifecycle?: {
46
+ state: 'not_submitted';
47
+ };
35
48
  reason?: string;
36
49
  finalStage?: string;
37
50
  outcome?: {
@@ -39,6 +52,8 @@ export interface AutoExecVerdict {
39
52
  score: number;
40
53
  };
41
54
  proofOfWork?: unknown;
55
+ failureKind?: ExecutionFailureKind;
56
+ exitCode?: number | null;
42
57
  usedAssetIds?: readonly string[];
43
58
  }
44
59
  /**
@@ -80,6 +95,15 @@ export interface AutoExecDeps {
80
95
  validate?: (task: AutoExecTask) => ValidateHook;
81
96
  /** Optional adapter/runtime permit gate. Core owns only the seam; adapters own the verification policy. */
82
97
  solidifyPermit?: SolidifyPermitGate;
98
+ /** Bound execution uses the existing root-event journal; composition supplies its authoritative external checks. */
99
+ executionBinding?: {
100
+ journal: ExecutionBindingJournal;
101
+ authority: BindingAuthorityChecks;
102
+ now?: () => number;
103
+ maxRuntimeMs?: number;
104
+ maxFiles?: number;
105
+ maxLines?: number;
106
+ };
83
107
  /**
84
108
  * Optional open-PR dedup: when set, before spawning an agent the task's signals are compared against open PR
85
109
  * titles/branches; a strong overlap yields a 'skipped' verdict so the daemon never re-implements work that is
@@ -97,14 +121,54 @@ export interface AutoExecDeps {
97
121
  /** Observed `value.recall` events (#274 slice 3): folded into the same soft re-order as reuseOutcomes (lower
98
122
  * weight) so transcript-observed recall influences selection. Forwarded to runEvolutionCycle. Omit → none. */
99
123
  recallEvents?: readonly ReuseOutcomeEvent[];
124
+ /** Scoped local MemoryGraph seam. Queries and records structured outcome data only. */
125
+ memoryGraph?: MemoryGraphProvider;
100
126
  /** Optional daemon-level explicit strategy preset name, e.g. EVOLVE_STRATEGY. */
101
127
  strategyName?: string;
128
+ /** Emergency rollback for semantic IDF selection. Omit to keep IDF enabled. */
129
+ disableSemanticIdf?: boolean;
130
+ /** Experimental plateau selection policy. Omit to preserve engine-health + legacy drift. */
131
+ selectionPolicy?: SelectionPolicy;
132
+ /** Production relevance-guard rollout. Omit only for legacy/custom composition. */
133
+ selectionGuard?: SelectionGuardMode;
134
+ /** Optional score floor canary. Omit to keep the selector default of zero. */
135
+ selectionFloor?: number;
102
136
  /** Optional evolvable personality store shared with CycleEngine and the exec prompt. */
103
137
  personality?: PersonalityStore;
104
138
  /** Test/custom seam: inject a runner instead of spawning a real agent. */
105
139
  agent?: AgentRunner;
106
140
  /** Test/custom seam: inject git instead of spawning git. */
107
141
  git?: GitRunner;
142
+ /**
143
+ * Learning trace (Learning Ops slice 2): when set, each task run gets its own AgentRunTraceRecorder
144
+ * (traceId = the cycleId) emitting run.started/model.called/tool.failed/run.completed, and a
145
+ * LearningPacket draft is submitted here after the cycle. Best-effort: trace/packet failures never
146
+ * change the verdict. Omit → zero trace work, byte-identical to today.
147
+ */
148
+ learningTrace?: {
149
+ /** Where packet drafts go (file/memory/hub-adapter implementation). */
150
+ packetSink: LearningPacketSink;
151
+ /** Optional live per-event sink (e.g. FileTraceSink JSONL tail). */
152
+ traceSink?: TraceSink;
153
+ /** Hub packet sourceRepo column; default 'evolver-v2'. */
154
+ sourceRepo?: string;
155
+ /**
156
+ * Proxy llm_turn fold (Learning Ops slice 5): when set, after the cycle (and BEFORE run.completed, so
157
+ * sequence order holds) the run's wall-clock window of proxy trace records is read from `dir`
158
+ * (llm-trace-*.jsonl day-files) and folded into the recorder via recordLlmTurn — real per-request
159
+ * model.called + tool.called/tool.failed detail instead of only the bridge's coarse spawn event.
160
+ * Correlation is the time window + session-first-turn heuristic (see trace/proxyTurns.ts). Best-effort:
161
+ * a missing dir / unreadable file / no proxy degrades to zero folded turns, never a verdict change.
162
+ */
163
+ proxyTraces?: {
164
+ /** Proxy trace day-file dir (events/paths.ts tracesDir()). */
165
+ dir: string;
166
+ /** Decryption material for encrypted trace envelopes (allowPartial is always forced on). */
167
+ readOptions?: TraceReadOptions;
168
+ /** Injected clock for deterministic tests. Default Date.now. */
169
+ now?: () => number;
170
+ };
171
+ };
108
172
  }
109
173
  export interface ForcedGeneFields {
110
174
  forcedGeneId?: unknown;
@@ -1,20 +1,18 @@
1
- // Autonomous-exec task runner — the productized per-task kernel a resident daemon polls (the deployment form
2
- // of the scratch autoexec daemon). One task → one fully-hardened evolution cycle, with a deny-by-default
3
- // allowlist pre-check that yields a clean "refused" verdict WITHOUT running anything when the repo is not
4
- // allowlisted. Secure by construction: the execute is built by makeSafeExecute (all six controls); callers
5
- // cannot bypass the safety composition.
6
- import { resolve as resolvePath, sep } from 'node:path';
7
1
  import { normalizeForPut } from '../assetstore/provider.js';
8
2
  import { ingestUntrusted } from '../assetstore/provenance.js';
9
3
  import { mergePendingSignalsForStore } from '../assetstore/pendingSignals.js';
10
4
  import { intakeGene } from '../algo/geneIntake.js';
11
5
  import { runEvolutionCycle } from '../algo/orchestrator.js';
12
6
  import { makeSafeExecute, makeTrustedGeneResolver } from './autonomousCycle.js';
7
+ import { defaultGitRunner, isWithinRoot } from './claudeBridge.js';
13
8
  import { findSignalHints } from './openPrRegistry.js';
9
+ import { AgentRunTraceRecorder, buildLearningPacketDraft } from '../trace/learningTrace.js';
10
+ import { collectRunLlmTurns } from '../trace/proxyTurns.js';
11
+ import { uniqueSessionId } from '../trace/trajectory.js';
12
+ import { ExecutionBindingError, ExecutionBindingJournal, freezeExecutionBinding, preflightExecutionBinding, } from './executionBinding.js';
14
13
  /** Same path-containment as the bridge guard — used here to refuse before running anything (clean verdict). */
15
14
  function withinAllowlist(repo, roots) {
16
- const c = resolvePath(repo);
17
- return roots.some((root) => { const r = resolvePath(root); return c === r || c.startsWith(r.endsWith(sep) ? r : r + sep); });
15
+ return roots.some((root) => isWithinRoot(repo, root));
18
16
  }
19
17
  function cleanForcedGeneId(value) {
20
18
  if (typeof value !== 'string')
@@ -96,6 +94,78 @@ export async function runAutoExecTask(deps, rawTask, safety) {
96
94
  if (!withinAllowlist(task.repo, safety.allowedRoots)) {
97
95
  return { taskId: task.id, status: 'refused', reason: `repo not in allowlist: ${task.repo}` };
98
96
  }
97
+ const cycleId = `autoexec-${task.id}`;
98
+ let binding;
99
+ let bindingClaimed = false;
100
+ let baseValidate;
101
+ if (task.execution_binding !== undefined) {
102
+ try {
103
+ binding = freezeExecutionBinding(task.execution_binding);
104
+ if (!deps.executionBinding)
105
+ throw new ExecutionBindingError('binding_missing', 'execution binding journal and authority checks are required');
106
+ const recovery = deps.executionBinding.journal.recover(task.id, binding.binding_digest);
107
+ if (recovery.kind === 'terminal') {
108
+ const recoveredStatus = recovery.terminal.disposition === 'unsafe_to_replay'
109
+ ? 'unsafe_to_replay'
110
+ : recovery.terminal.disposition === 'completed' && recovery.terminal.final_stage === 'solidified'
111
+ ? 'solidified'
112
+ : recovery.terminal.disposition === 'completed' && recovery.terminal.final_stage === 'innovated'
113
+ ? 'innovated'
114
+ : recovery.terminal.disposition === 'denied'
115
+ ? 'refused'
116
+ : 'failed';
117
+ return {
118
+ taskId: task.id,
119
+ status: recoveredStatus,
120
+ binding_digest: recovery.binding_digest,
121
+ run_id: binding.correlation.run_id,
122
+ execution_terminal: {
123
+ status: recovery.terminal.outcome?.status === 'success' ? 'success' : 'failed',
124
+ disposition: recovery.terminal.disposition,
125
+ },
126
+ hub_lifecycle: { state: 'not_submitted' },
127
+ ...(recovery.terminal.outcome ? { outcome: recovery.terminal.outcome } : {}),
128
+ };
129
+ }
130
+ if (recovery.kind === 'unsafe_to_replay' || recovery.kind === 'mismatched') {
131
+ return { taskId: task.id, status: 'unsafe_to_replay', binding_digest: recovery.binding_digest, run_id: binding.correlation.run_id, reason: recovery.reason };
132
+ }
133
+ await preflightExecutionBinding(binding, {
134
+ taskId: task.id,
135
+ runId: binding.correlation.run_id,
136
+ cycleId,
137
+ repoPath: task.repo,
138
+ target: task.target,
139
+ expectedEffect: task.expectedEffect,
140
+ allowedRoots: safety.allowedRoots,
141
+ now: deps.executionBinding.now ?? Date.now,
142
+ currentRevision: binding.target_descriptor.base_revision === null
143
+ ? null
144
+ : (deps.git ?? defaultGitRunner)(['rev-parse', 'HEAD'], task.repo).then((revision) => revision.trim() || null),
145
+ maxRuntimeMs: deps.executionBinding.maxRuntimeMs ?? safety.timeoutMs,
146
+ maxFiles: deps.executionBinding.maxFiles,
147
+ maxLines: deps.executionBinding.maxLines,
148
+ requiredConsentScope: 'execution',
149
+ authoritative: deps.executionBinding.authority,
150
+ });
151
+ if (!deps.validate && binding.resource_grant.validation_commands.length > 0) {
152
+ throw new ExecutionBindingError('binding_acceptance_invalid', 'bound execution requires the declared validation plan to be wired to a validator');
153
+ }
154
+ baseValidate = deps.validate?.({ ...task, validationCmds: binding.resource_grant.validation_commands });
155
+ if (baseValidate && baseValidate.length < 4) {
156
+ throw new ExecutionBindingError('binding_acceptance_invalid', 'bound execution validator must accept AbortSignal');
157
+ }
158
+ if (recovery.kind === 'new')
159
+ await deps.executionBinding.journal.recordCreated(binding);
160
+ if (recovery.kind === 'claimed')
161
+ bindingClaimed = true;
162
+ }
163
+ catch (error) {
164
+ const detail = error instanceof Error ? error.message : String(error);
165
+ const unsafe = error instanceof ExecutionBindingError && error.unsafeToReplay;
166
+ return { taskId: task.id, status: unsafe ? 'unsafe_to_replay' : 'refused', ...(binding ? { binding_digest: binding.binding_digest, run_id: binding.correlation.run_id } : {}), reason: detail };
167
+ }
168
+ }
99
169
  // Open-PR dedup (opt-in): if an open PR already covers this task's signals, skip before spawning an agent
100
170
  // so the daemon doesn't re-implement in-flight work. Graceful — a lister that returns [] is a no-op.
101
171
  if (deps.prLister) {
@@ -135,7 +205,24 @@ export async function runAutoExecTask(deps, rawTask, safety) {
135
205
  // in the same selection pool as local genes. Reuse is an optimization, never a hard dependency — a seam
136
206
  // that throws degrades to solving fresh (no hub candidates) rather than failing the task.
137
207
  let hubCandidates = [];
138
- const cycleId = `autoexec-${task.id}`;
208
+ // Learning trace (slice 2): one recorder per task run, traceId = cycleId so the trace joins the event log.
209
+ // Purely observational — every recorder call and the packet submit are wrapped so they can never change
210
+ // the verdict or fail the task.
211
+ const traceRecorder = deps.learningTrace
212
+ ? new AgentRunTraceRecorder({
213
+ runId: cycleId,
214
+ taskId: task.id,
215
+ ...(deps.learningTrace.traceSink ? { sink: deps.learningTrace.traceSink } : {}),
216
+ })
217
+ : undefined;
218
+ // Wall-clock window of this run, used to correlate the proxy's llm_turn records (slice 5). Captured
219
+ // unconditionally-cheaply only when the fold is configured.
220
+ const proxyTraceClock = deps.learningTrace?.proxyTraces?.now ?? Date.now;
221
+ let runStartMs = 0;
222
+ try {
223
+ traceRecorder?.runStarted({ taskSummary: task.expectedEffect, signals: cycleSignals, metadata: { repo: task.repo, target: task.target } });
224
+ }
225
+ catch { /* observability only */ }
139
226
  if (deps.hubReuse) {
140
227
  try {
141
228
  hubCandidates = await executableHubCandidates(deps, await deps.hubReuse(cycleSignals, { cycleId }));
@@ -144,16 +231,119 @@ export async function runAutoExecTask(deps, rawTask, safety) {
144
231
  hubCandidates = [];
145
232
  }
146
233
  }
147
- const execute = makeSafeExecute(task.repo, deps.store, safety, {
234
+ runStartMs = deps.learningTrace?.proxyTraces ? proxyTraceClock() : 0;
235
+ // evaluation fill-in (slice 6): the validate hook is the run's external verifier (sandboxed validation
236
+ // commands), so its result — when it actually RAN — becomes the packet's evaluation.verification
237
+ // (verifier 'automated_test'). Observation is a pass-through wrapper: the hook's result reaches the
238
+ // bridge unchanged, and a run where validation never fired keeps the evaluation placeholder.
239
+ let observedVerification;
240
+ baseValidate ??= deps.validate?.(task);
241
+ const observingValidate = baseValidate
242
+ ? async (mutation, decision, cwd, signal) => {
243
+ const v = await baseValidate(mutation, decision, cwd, signal);
244
+ observedVerification = { verifier: 'automated_test', passed: v.passed, ...(v.score !== undefined ? { score: v.score } : {}) };
245
+ return v;
246
+ }
247
+ : undefined;
248
+ const observedProvenance = {
249
+ gene_ids: [], capsule_ids: [], tool_decisions: [],
250
+ policy_decisions: [], validator: null,
251
+ result_asset_refs: [], proof_refs: [], terminal_disposition: 'crashed',
252
+ };
253
+ const executionObserver = binding ? {
254
+ onToolDecision: (decision) => { observedProvenance.tool_decisions.push(decision); },
255
+ onPolicyDecision: (decision) => { observedProvenance.policy_decisions.push(decision); },
256
+ onValidatorDecision: (decision) => { observedProvenance.validator = decision; },
257
+ onProofReference: (reference) => { observedProvenance.proof_refs.push(reference); },
258
+ } : undefined;
259
+ const safeExecute = makeSafeExecute(task.repo, deps.store, safety, {
148
260
  ...(deps.provenance ? { provenance: deps.provenance } : {}),
149
261
  ...(deps.review ? { review: deps.review } : {}),
150
262
  ...(deps.includeProbation ? { includeProbation: true } : {}),
151
263
  ...(task.validationCmds ? { validationCmds: task.validationCmds } : {}),
152
- ...(deps.validate ? { validate: deps.validate(task) } : {}),
264
+ ...(observingValidate ? { validate: observingValidate } : {}),
153
265
  ...(deps.personality ? { personality: deps.personality } : {}),
154
266
  ...(deps.agent ? { agent: deps.agent } : {}),
155
267
  ...(deps.git ? { git: deps.git } : {}),
268
+ ...(traceRecorder ? { traceRecorder } : {}),
269
+ ...(executionObserver ? { executionObserver } : {}),
270
+ ...(binding ? { executionLimits: {
271
+ maxRuntimeMs: binding.resource_grant.max_runtime_ms,
272
+ maxFiles: binding.resource_grant.max_files,
273
+ maxLines: binding.resource_grant.max_lines,
274
+ } } : {}),
156
275
  });
276
+ const execute = binding && deps.executionBinding
277
+ ? async (mutation, decision) => {
278
+ const selectedContext = binding.selected_context;
279
+ try {
280
+ if (selectedContext.capsule_id !== null || selectedContext.capsule_asset_id !== null) {
281
+ throw new ExecutionBindingError('binding_mismatched', 'preselected capsule context is not supported by the CycleEngine execution seam');
282
+ }
283
+ if (selectedContext.gene_id !== null && decision.selectedGeneId !== selectedContext.gene_id) {
284
+ throw new ExecutionBindingError('binding_mismatched', 'selected gene_id does not match frozen binding context');
285
+ }
286
+ if (selectedContext.gene_asset_id !== null && decision.selectedAssetId !== selectedContext.gene_asset_id) {
287
+ throw new ExecutionBindingError('binding_mismatched', 'selected gene_asset_id does not match frozen binding context');
288
+ }
289
+ if (!bindingClaimed) {
290
+ await preflightExecutionBinding(binding, {
291
+ taskId: task.id,
292
+ runId: binding.correlation.run_id,
293
+ cycleId,
294
+ repoPath: task.repo,
295
+ target: task.target,
296
+ expectedEffect: task.expectedEffect,
297
+ allowedRoots: safety.allowedRoots,
298
+ now: deps.executionBinding.now ?? Date.now,
299
+ currentRevision: binding.target_descriptor.base_revision === null
300
+ ? null
301
+ : (deps.git ?? defaultGitRunner)(['rev-parse', 'HEAD'], task.repo).then((revision) => revision.trim() || null),
302
+ maxRuntimeMs: deps.executionBinding.maxRuntimeMs ?? safety.timeoutMs,
303
+ maxFiles: deps.executionBinding.maxFiles,
304
+ maxLines: deps.executionBinding.maxLines,
305
+ requiredConsentScope: 'execution',
306
+ authoritative: deps.executionBinding.authority,
307
+ });
308
+ const started = await deps.executionBinding.journal.claim(binding);
309
+ if (started.kind !== 'claimed')
310
+ throw new ExecutionBindingError('binding_replay_unsafe', `binding execution claim ended in ${started.kind}`, true);
311
+ bindingClaimed = true;
312
+ }
313
+ const result = await safeExecute(mutation, decision);
314
+ const policyDenied = observedProvenance.policy_decisions.some((policy) => !policy.allowed);
315
+ const disposition = result.outcome.status === 'success'
316
+ ? 'completed'
317
+ : result.failureKind === 'timeout'
318
+ ? 'timed_out'
319
+ : result.failureKind === 'cancelled'
320
+ ? 'cancelled'
321
+ : policyDenied ? 'denied' : 'rejected';
322
+ observedProvenance.terminal_disposition = disposition;
323
+ return {
324
+ ...result,
325
+ provenance: { ...observedProvenance, terminal_disposition: disposition },
326
+ bindingCorrelation: { bindingDigest: binding.binding_digest, runId: binding.correlation.run_id },
327
+ executionTerminal: { status: result.outcome.status, disposition },
328
+ hubLifecycle: { state: 'not_submitted' },
329
+ };
330
+ }
331
+ catch (error) {
332
+ const message = error instanceof Error ? error.message : String(error);
333
+ const disposition = error instanceof ExecutionBindingError
334
+ ? error.unsafeToReplay ? 'unsafe_to_replay' : 'denied'
335
+ : 'crashed';
336
+ observedProvenance.terminal_disposition = disposition;
337
+ return {
338
+ outcome: { status: 'failed', score: 0, reason: message },
339
+ bindingCorrelation: { bindingDigest: binding.binding_digest, runId: binding.correlation.run_id },
340
+ executionTerminal: { status: 'failed', disposition },
341
+ provenance: { ...observedProvenance, terminal_disposition: disposition },
342
+ hubLifecycle: { state: 'not_submitted' },
343
+ };
344
+ }
345
+ }
346
+ : safeExecute;
157
347
  const strategyName = task.strategyName ?? deps.strategyName;
158
348
  // Intentional semantics (see #308 review M1): a task carrying `strategy` — even without an
159
349
  // explicit forcedGeneId — force-selects the gene seeded from that strategy above, rather than
@@ -161,29 +351,192 @@ export async function runAutoExecTask(deps, rawTask, safety) {
161
351
  // The forced pick still passes every hard gate downstream (candidate pool, ban, epigenetic
162
352
  // suppression), so this never bypasses trust/review/inert filtering.
163
353
  const cycleForcedGeneId = task.forcedGeneId ?? seededStrategyGeneId;
164
- const res = await runEvolutionCycle(deps.engine, deps.store, {
165
- ...(deps.provenance ? { provenance: deps.provenance } : {}),
166
- ...(deps.review ? { review: deps.review } : {}),
167
- ...(deps.includeProbation ? { includeProbation: true } : {}),
168
- ...(hubCandidates.length > 0 ? { hubCandidates } : {}),
169
- ...(deps.solidifyPermit ? { solidifyPermit: deps.solidifyPermit } : {}),
170
- ...(deps.reuseOutcomes ? { reuseOutcomes: deps.reuseOutcomes } : {}),
171
- ...(deps.recallEvents ? { recallEvents: deps.recallEvents } : {}),
172
- ...(strategyName !== undefined ? { strategyName } : {}),
173
- ...(cycleForcedGeneId !== undefined ? { forcedGeneId: cycleForcedGeneId } : {}),
174
- cycleId,
175
- problem: {
176
- id: task.id, signature: `sig:${task.id}`, signatureV: 1,
177
- firstSeenAt: new Date(0).toISOString(), lastSeenAt: new Date(0).toISOString(),
178
- occurrences: 1, linkedSignals: cycleSignals, resolvedBy: null, status: 'open',
179
- value: { severity: 0.7, reach: 1, strategicFit: 0.9, novelty: 0, costEst: 0.2 },
180
- consecutiveFailures: 0, cooldownUntil: null, extensions: {},
181
- },
182
- signals: cycleSignals, category: 'repair', target: task.target, expectedEffect: task.expectedEffect,
183
- summary: `autonomous: ${task.id}`, confidence: 0.85, execute, consumePendingSignals: false,
184
- });
185
- const status = res.finalStage === 'solidified' ? 'solidified' : res.finalStage === 'failed' ? 'failed' : 'innovated';
354
+ let memoryGraphAdvice;
355
+ if (deps.memoryGraph) {
356
+ try {
357
+ memoryGraphAdvice = await deps.memoryGraph.query({ workspace: task.repo, signals: cycleSignals });
358
+ }
359
+ catch {
360
+ memoryGraphAdvice = undefined;
361
+ }
362
+ }
363
+ let res;
364
+ try {
365
+ res = await runEvolutionCycle(deps.engine, deps.store, {
366
+ ...(deps.provenance ? { provenance: deps.provenance } : {}),
367
+ ...(deps.review ? { review: deps.review } : {}),
368
+ ...(deps.includeProbation ? { includeProbation: true } : {}),
369
+ ...(hubCandidates.length > 0 ? { hubCandidates } : {}),
370
+ ...(deps.solidifyPermit ? { solidifyPermit: deps.solidifyPermit } : {}),
371
+ ...(binding ? { executionBinding: binding } : {}),
372
+ ...(deps.reuseOutcomes ? { reuseOutcomes: deps.reuseOutcomes } : {}),
373
+ ...(deps.recallEvents ? { recallEvents: deps.recallEvents } : {}),
374
+ ...(memoryGraphAdvice ? { memoryGraphAdvice } : {}),
375
+ ...(strategyName !== undefined ? { strategyName } : {}),
376
+ ...(deps.disableSemanticIdf ? { disableSemanticIdf: true } : {}),
377
+ ...(deps.selectionPolicy ? { selectionPolicy: deps.selectionPolicy } : {}),
378
+ ...(deps.selectionGuard ? { selectionGuard: deps.selectionGuard } : {}),
379
+ ...(deps.selectionFloor !== undefined ? { selectionFloor: deps.selectionFloor } : {}),
380
+ ...(cycleForcedGeneId !== undefined ? { forcedGeneId: cycleForcedGeneId } : {}),
381
+ cycleId,
382
+ problem: {
383
+ id: task.id, signature: `sig:${task.id}`, signatureV: 1,
384
+ firstSeenAt: new Date(0).toISOString(), lastSeenAt: new Date(0).toISOString(),
385
+ occurrences: 1, linkedSignals: cycleSignals, resolvedBy: null, status: 'open',
386
+ value: { severity: 0.7, reach: 1, strategicFit: 0.9, novelty: 0, costEst: 0.2 },
387
+ consecutiveFailures: 0, cooldownUntil: null, extensions: {},
388
+ },
389
+ signals: cycleSignals, category: 'repair', target: task.target, expectedEffect: task.expectedEffect,
390
+ summary: `autonomous: ${task.id}`, confidence: 0.85, execute, consumePendingSignals: false,
391
+ });
392
+ }
393
+ catch (error) {
394
+ if (binding && deps.executionBinding) {
395
+ const recovery = deps.executionBinding.journal.recover(task.id, binding.binding_digest);
396
+ if (recovery.kind === 'unsafe_to_replay') {
397
+ try {
398
+ await deps.executionBinding.journal.recordTerminal({
399
+ binding_digest: binding.binding_digest,
400
+ task_id: task.id,
401
+ run_id: binding.correlation.run_id,
402
+ disposition: 'unsafe_to_replay',
403
+ outcome: { status: 'failed', score: 0, reason: recovery.reason },
404
+ provenance: { ...observedProvenance, terminal_disposition: 'unsafe_to_replay' },
405
+ });
406
+ }
407
+ catch { /* the existing started marker remains fail-closed on recovery */ }
408
+ return { taskId: task.id, status: 'unsafe_to_replay', binding_digest: binding.binding_digest, run_id: binding.correlation.run_id, reason: `binding_replay_unsafe: ${recovery.reason}` };
409
+ }
410
+ }
411
+ const detail = error instanceof Error ? error.message : String(error);
412
+ return { taskId: task.id, status: 'failed', ...(binding ? { binding_digest: binding.binding_digest, run_id: binding.correlation.run_id } : {}), reason: detail };
413
+ }
414
+ if (binding && deps.executionBinding) {
415
+ const execution = res.execution;
416
+ if (!execution?.provenance || !execution.executionTerminal) {
417
+ const deniedReason = res.reasons.join('; ') || `cycle ended at ${res.finalStage} before execution`;
418
+ try {
419
+ await deps.executionBinding.journal.recordTerminal({
420
+ binding_digest: binding.binding_digest,
421
+ task_id: task.id,
422
+ run_id: binding.correlation.run_id,
423
+ disposition: 'denied',
424
+ final_stage: res.finalStage,
425
+ outcome: { status: 'failed', score: 0, reason: deniedReason },
426
+ provenance: { ...observedProvenance, terminal_disposition: 'denied' },
427
+ });
428
+ }
429
+ catch (error) {
430
+ const detail = error instanceof Error ? error.message : String(error);
431
+ return { taskId: task.id, status: 'failed', binding_digest: binding.binding_digest, run_id: binding.correlation.run_id, reason: `binding_provenance_unavailable: ${detail}` };
432
+ }
433
+ return {
434
+ taskId: task.id,
435
+ status: 'refused',
436
+ binding_digest: binding.binding_digest,
437
+ run_id: binding.correlation.run_id,
438
+ execution_terminal: { status: 'failed', disposition: 'denied' },
439
+ hub_lifecycle: { state: 'not_submitted' },
440
+ reason: deniedReason,
441
+ };
442
+ }
443
+ const terminal = {
444
+ binding_digest: binding.binding_digest,
445
+ task_id: task.id,
446
+ run_id: binding.correlation.run_id,
447
+ disposition: execution.executionTerminal.disposition,
448
+ final_stage: res.finalStage,
449
+ outcome: execution.outcome,
450
+ ...(execution.proofOfWork ? { proof_of_work: execution.proofOfWork } : {}),
451
+ provenance: {
452
+ ...execution.provenance,
453
+ gene_ids: res.decision?.selectedGeneId ? [res.decision.selectedGeneId] : [],
454
+ capsule_ids: res.capsule?.asset_id ? [res.capsule.asset_id] : [],
455
+ result_asset_refs: res.capsule?.asset_id ? [res.capsule.asset_id] : [],
456
+ proof_refs: execution.provenance.proof_refs.length > 0
457
+ ? execution.provenance.proof_refs
458
+ : execution.proofOfWork?.kind === 'git_diff' && execution.proofOfWork.git_diff?.patch_ref
459
+ ? [{ kind: 'git_diff', ref: execution.proofOfWork.git_diff.patch_ref }]
460
+ : execution.provenance.proof_refs,
461
+ },
462
+ };
463
+ try {
464
+ await deps.executionBinding.journal.recordTerminal(terminal);
465
+ }
466
+ catch (error) {
467
+ const detail = error instanceof Error ? error.message : String(error);
468
+ return { taskId: task.id, status: 'failed', binding_digest: binding.binding_digest, run_id: binding.correlation.run_id, reason: `binding_provenance_unavailable: ${detail}` };
469
+ }
470
+ }
471
+ const status = res.execution?.executionTerminal?.disposition === 'unsafe_to_replay'
472
+ ? 'unsafe_to_replay'
473
+ : res.execution?.executionTerminal?.disposition === 'denied'
474
+ ? 'refused'
475
+ : res.finalStage === 'solidified' ? 'solidified' : res.finalStage === 'failed' ? 'failed' : res.finalStage === 'aborted' ? 'refused' : 'innovated';
186
476
  const cap = res.capsule;
477
+ if (traceRecorder && deps.learningTrace) {
478
+ // Proxy llm_turn fold (slice 5): fold the run window's per-request turns BEFORE run.completed so the
479
+ // trajectory stays sequence-ordered (model/tool detail inside the run, completion last). Own try — a
480
+ // throwing sink mid-fold must not cost the run its completion event or packet draft.
481
+ try {
482
+ const proxyTraces = deps.learningTrace.proxyTraces;
483
+ if (proxyTraces) {
484
+ const turns = collectRunLlmTurns(proxyTraces.dir, { startMs: runStartMs, endMs: proxyTraceClock() }, proxyTraces.readOptions ? { readOptions: proxyTraces.readOptions } : {});
485
+ // Exact-join key for Darwin: bind the unique proxy session before folding turns so
486
+ // every subsequent event (and backfilled earlier ones) carries the same sessionId.
487
+ // Fail closed when 0 or >1 sessions appear — never invent a correlation key.
488
+ const sid = uniqueSessionId(turns);
489
+ if (sid !== null) {
490
+ try {
491
+ traceRecorder.bindSessionId(sid);
492
+ }
493
+ catch { /* observability only */ }
494
+ }
495
+ for (const turn of turns)
496
+ traceRecorder.recordLlmTurn(turn);
497
+ }
498
+ }
499
+ catch { /* observability only */ }
500
+ try {
501
+ traceRecorder.runCompleted({
502
+ status: res.finalStage === 'solidified' ? 'success' : 'failed',
503
+ ...(cap?.outcome?.score !== undefined ? { score: cap.outcome.score } : {}),
504
+ ...(res.reasons.length > 0 ? { reason: res.reasons.join('; ') } : {}),
505
+ ...(res.producedValue !== undefined ? { producedValue: res.producedValue } : {}),
506
+ ...(res.failureKind !== undefined ? { failureKind: res.failureKind } : {}),
507
+ });
508
+ await deps.learningTrace.packetSink.submit(buildLearningPacketDraft(traceRecorder, {
509
+ sourceRepo: deps.learningTrace.sourceRepo ?? 'evolver-v2',
510
+ taskSummary: task.expectedEffect,
511
+ signals: cycleSignals,
512
+ environment: {
513
+ repo: task.repo,
514
+ runner: safety.runner ?? (deps.agent ? 'claude' : 'codex'),
515
+ },
516
+ ...(observedVerification !== undefined ? { verification: observedVerification } : {}),
517
+ }));
518
+ }
519
+ catch { /* packet delivery is best-effort; never fail the task */ }
520
+ }
521
+ if (deps.memoryGraph && res.decision?.selectedGeneId && (res.finalStage === 'solidified' || res.finalStage === 'failed')) {
522
+ const producedSuccess = res.finalStage === 'solidified' && res.producedValue === true;
523
+ try {
524
+ await deps.memoryGraph.recordOutcome({
525
+ workspace: task.repo,
526
+ signals: cycleSignals,
527
+ geneId: res.decision.selectedGeneId,
528
+ // MemoryGraph has no inert status yet, so record a no-op as conservative failed evidence instead of reward.
529
+ status: producedSuccess ? 'success' : 'failed',
530
+ score: res.finalStage === 'solidified' && !res.producedValue
531
+ ? 0
532
+ : cap?.outcome?.score ?? (producedSuccess ? 1 : 0),
533
+ at: new Date().toISOString(),
534
+ });
535
+ }
536
+ catch {
537
+ // Memory persistence is advisory and must never fail the autonomous task.
538
+ }
539
+ }
187
540
  const hubAssetIds = new Set(hubCandidates.map((c) => c.assetId).filter((id) => typeof id === 'string' && id.length > 0));
188
541
  const selectedAssetId = res.decision?.selectedAssetId;
189
542
  const usedAssetIds = selectedAssetId && hubAssetIds.has(selectedAssetId) ? [selectedAssetId] : [];
@@ -192,6 +545,12 @@ export async function runAutoExecTask(deps, rawTask, safety) {
192
545
  ...(res.reasons.length > 0 && status !== 'solidified' ? { reason: res.reasons.join('; ') } : {}),
193
546
  ...(cap?.outcome ? { outcome: cap.outcome } : {}),
194
547
  ...(cap?.proof_of_work ? { proofOfWork: cap.proof_of_work } : {}),
548
+ ...(res.failureKind !== undefined ? { failureKind: res.failureKind } : {}),
549
+ ...(res.exitCode !== undefined ? { exitCode: res.exitCode } : {}),
550
+ ...(res.bindingDigest ? { binding_digest: res.bindingDigest } : {}),
551
+ ...(res.runId ? { run_id: res.runId } : {}),
552
+ ...(res.execution ? { execution_terminal: res.execution.executionTerminal } : {}),
553
+ ...(res.execution?.hubLifecycle ? { hub_lifecycle: res.execution.hubLifecycle } : {}),
195
554
  ...(usedAssetIds.length > 0 ? { usedAssetIds } : {}),
196
555
  };
197
556
  }