@hazeljs/organism 2.0.6

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 (170) hide show
  1. package/README.md +205 -0
  2. package/dist/__tests__/organism.embedding.test.d.ts +2 -0
  3. package/dist/__tests__/organism.embedding.test.d.ts.map +1 -0
  4. package/dist/__tests__/organism.embedding.test.js +166 -0
  5. package/dist/__tests__/organism.embedding.test.js.map +1 -0
  6. package/dist/__tests__/organism.phase1.test.d.ts +2 -0
  7. package/dist/__tests__/organism.phase1.test.d.ts.map +1 -0
  8. package/dist/__tests__/organism.phase1.test.js +375 -0
  9. package/dist/__tests__/organism.phase1.test.js.map +1 -0
  10. package/dist/__tests__/organism.phase2-3.test.d.ts +2 -0
  11. package/dist/__tests__/organism.phase2-3.test.d.ts.map +1 -0
  12. package/dist/__tests__/organism.phase2-3.test.js +320 -0
  13. package/dist/__tests__/organism.phase2-3.test.js.map +1 -0
  14. package/dist/__tests__/organism.phase4.test.d.ts +2 -0
  15. package/dist/__tests__/organism.phase4.test.d.ts.map +1 -0
  16. package/dist/__tests__/organism.phase4.test.js +217 -0
  17. package/dist/__tests__/organism.phase4.test.js.map +1 -0
  18. package/dist/bridge/signal-bridge.d.ts +46 -0
  19. package/dist/bridge/signal-bridge.d.ts.map +1 -0
  20. package/dist/bridge/signal-bridge.js +75 -0
  21. package/dist/bridge/signal-bridge.js.map +1 -0
  22. package/dist/core/capability-registry.d.ts +16 -0
  23. package/dist/core/capability-registry.d.ts.map +1 -0
  24. package/dist/core/capability-registry.js +71 -0
  25. package/dist/core/capability-registry.js.map +1 -0
  26. package/dist/core/clock.d.ts +17 -0
  27. package/dist/core/clock.d.ts.map +1 -0
  28. package/dist/core/clock.js +63 -0
  29. package/dist/core/clock.js.map +1 -0
  30. package/dist/core/mission.d.ts +4 -0
  31. package/dist/core/mission.d.ts.map +1 -0
  32. package/dist/core/mission.js +74 -0
  33. package/dist/core/mission.js.map +1 -0
  34. package/dist/core/organism-context.d.ts +139 -0
  35. package/dist/core/organism-context.d.ts.map +1 -0
  36. package/dist/core/organism-context.js +62 -0
  37. package/dist/core/organism-context.js.map +1 -0
  38. package/dist/core/organism-runtime.d.ts +199 -0
  39. package/dist/core/organism-runtime.d.ts.map +1 -0
  40. package/dist/core/organism-runtime.js +1016 -0
  41. package/dist/core/organism-runtime.js.map +1 -0
  42. package/dist/decorators/index.d.ts +39 -0
  43. package/dist/decorators/index.d.ts.map +1 -0
  44. package/dist/decorators/index.js +165 -0
  45. package/dist/decorators/index.js.map +1 -0
  46. package/dist/economy/market-engine.d.ts +57 -0
  47. package/dist/economy/market-engine.d.ts.map +1 -0
  48. package/dist/economy/market-engine.js +258 -0
  49. package/dist/economy/market-engine.js.map +1 -0
  50. package/dist/economy/resource-allocator.d.ts +39 -0
  51. package/dist/economy/resource-allocator.d.ts.map +1 -0
  52. package/dist/economy/resource-allocator.js +153 -0
  53. package/dist/economy/resource-allocator.js.map +1 -0
  54. package/dist/economy/utility-engine.d.ts +16 -0
  55. package/dist/economy/utility-engine.d.ts.map +1 -0
  56. package/dist/economy/utility-engine.js +80 -0
  57. package/dist/economy/utility-engine.js.map +1 -0
  58. package/dist/economy/utility-forecaster.d.ts +24 -0
  59. package/dist/economy/utility-forecaster.d.ts.map +1 -0
  60. package/dist/economy/utility-forecaster.js +60 -0
  61. package/dist/economy/utility-forecaster.js.map +1 -0
  62. package/dist/errors/organism.errors.d.ts +23 -0
  63. package/dist/errors/organism.errors.d.ts.map +1 -0
  64. package/dist/errors/organism.errors.js +50 -0
  65. package/dist/errors/organism.errors.js.map +1 -0
  66. package/dist/events/organism-events.d.ts +61 -0
  67. package/dist/events/organism-events.d.ts.map +1 -0
  68. package/dist/events/organism-events.js +95 -0
  69. package/dist/events/organism-events.js.map +1 -0
  70. package/dist/evolution/evolution-engine.d.ts +41 -0
  71. package/dist/evolution/evolution-engine.d.ts.map +1 -0
  72. package/dist/evolution/evolution-engine.js +135 -0
  73. package/dist/evolution/evolution-engine.js.map +1 -0
  74. package/dist/evolution/mutation-engine.d.ts +21 -0
  75. package/dist/evolution/mutation-engine.d.ts.map +1 -0
  76. package/dist/evolution/mutation-engine.js +84 -0
  77. package/dist/evolution/mutation-engine.js.map +1 -0
  78. package/dist/genealogy/genealogy-manager.d.ts +18 -0
  79. package/dist/genealogy/genealogy-manager.d.ts.map +1 -0
  80. package/dist/genealogy/genealogy-manager.js +64 -0
  81. package/dist/genealogy/genealogy-manager.js.map +1 -0
  82. package/dist/governance/constitution.d.ts +20 -0
  83. package/dist/governance/constitution.d.ts.map +1 -0
  84. package/dist/governance/constitution.js +109 -0
  85. package/dist/governance/constitution.js.map +1 -0
  86. package/dist/host/organism-host-registry.d.ts +24 -0
  87. package/dist/host/organism-host-registry.d.ts.map +1 -0
  88. package/dist/host/organism-host-registry.js +65 -0
  89. package/dist/host/organism-host-registry.js.map +1 -0
  90. package/dist/host/organism-host.d.ts +32 -0
  91. package/dist/host/organism-host.d.ts.map +1 -0
  92. package/dist/host/organism-host.js +64 -0
  93. package/dist/host/organism-host.js.map +1 -0
  94. package/dist/index.d.ts +43 -0
  95. package/dist/index.d.ts.map +1 -0
  96. package/dist/index.js +117 -0
  97. package/dist/index.js.map +1 -0
  98. package/dist/integration/create-ops-organism.d.ts +34 -0
  99. package/dist/integration/create-ops-organism.d.ts.map +1 -0
  100. package/dist/integration/create-ops-organism.js +43 -0
  101. package/dist/integration/create-ops-organism.js.map +1 -0
  102. package/dist/lifecycle/birth-engine.d.ts +41 -0
  103. package/dist/lifecycle/birth-engine.d.ts.map +1 -0
  104. package/dist/lifecycle/birth-engine.js +182 -0
  105. package/dist/lifecycle/birth-engine.js.map +1 -0
  106. package/dist/lifecycle/decision-engine.d.ts +19 -0
  107. package/dist/lifecycle/decision-engine.d.ts.map +1 -0
  108. package/dist/lifecycle/decision-engine.js +137 -0
  109. package/dist/lifecycle/decision-engine.js.map +1 -0
  110. package/dist/lifecycle/reproduction-engine.d.ts +58 -0
  111. package/dist/lifecycle/reproduction-engine.d.ts.map +1 -0
  112. package/dist/lifecycle/reproduction-engine.js +207 -0
  113. package/dist/lifecycle/reproduction-engine.js.map +1 -0
  114. package/dist/lifecycle/survival-engine.d.ts +17 -0
  115. package/dist/lifecycle/survival-engine.d.ts.map +1 -0
  116. package/dist/lifecycle/survival-engine.js +83 -0
  117. package/dist/lifecycle/survival-engine.js.map +1 -0
  118. package/dist/observability/metrics.d.ts +37 -0
  119. package/dist/observability/metrics.d.ts.map +1 -0
  120. package/dist/observability/metrics.js +80 -0
  121. package/dist/observability/metrics.js.map +1 -0
  122. package/dist/perception/perception-engine.d.ts +40 -0
  123. package/dist/perception/perception-engine.d.ts.map +1 -0
  124. package/dist/perception/perception-engine.js +126 -0
  125. package/dist/perception/perception-engine.js.map +1 -0
  126. package/dist/persistence/in-memory-organism-repository.d.ts +2 -0
  127. package/dist/persistence/in-memory-organism-repository.d.ts.map +1 -0
  128. package/dist/persistence/in-memory-organism-repository.js +18 -0
  129. package/dist/persistence/in-memory-organism-repository.js.map +1 -0
  130. package/dist/persistence/organism-repository.d.ts +42 -0
  131. package/dist/persistence/organism-repository.d.ts.map +1 -0
  132. package/dist/persistence/organism-repository.js +105 -0
  133. package/dist/persistence/organism-repository.js.map +1 -0
  134. package/dist/types/organism.types.d.ts +482 -0
  135. package/dist/types/organism.types.d.ts.map +1 -0
  136. package/dist/types/organism.types.js +45 -0
  137. package/dist/types/organism.types.js.map +1 -0
  138. package/examples/ecommerce-organism/run.ts +245 -0
  139. package/package.json +73 -0
  140. package/src/bridge/signal-bridge.ts +116 -0
  141. package/src/core/capability-registry.ts +72 -0
  142. package/src/core/clock.ts +58 -0
  143. package/src/core/mission.ts +83 -0
  144. package/src/core/organism-context.ts +176 -0
  145. package/src/core/organism-runtime.ts +1369 -0
  146. package/src/decorators/index.ts +180 -0
  147. package/src/economy/market-engine.ts +324 -0
  148. package/src/economy/resource-allocator.ts +188 -0
  149. package/src/economy/utility-engine.ts +103 -0
  150. package/src/economy/utility-forecaster.ts +88 -0
  151. package/src/errors/organism.errors.ts +47 -0
  152. package/src/events/organism-events.ts +122 -0
  153. package/src/evolution/evolution-engine.ts +167 -0
  154. package/src/evolution/mutation-engine.ts +110 -0
  155. package/src/genealogy/genealogy-manager.ts +70 -0
  156. package/src/governance/constitution.ts +120 -0
  157. package/src/host/organism-host-registry.ts +72 -0
  158. package/src/host/organism-host.ts +103 -0
  159. package/src/index.ts +78 -0
  160. package/src/integration/create-ops-organism.ts +80 -0
  161. package/src/lifecycle/birth-engine.ts +225 -0
  162. package/src/lifecycle/decision-engine.ts +158 -0
  163. package/src/lifecycle/reproduction-engine.ts +289 -0
  164. package/src/lifecycle/survival-engine.ts +95 -0
  165. package/src/observability/metrics.ts +120 -0
  166. package/src/perception/perception-engine.ts +140 -0
  167. package/src/persistence/in-memory-organism-repository.ts +1 -0
  168. package/src/persistence/organism-repository.ts +134 -0
  169. package/src/types/organism.types.ts +591 -0
  170. package/tsconfig.jest.json +8 -0
@@ -0,0 +1,225 @@
1
+ import { createAgentClassFromDna, exportAgentDna, type AgentRuntime } from '@hazeljs/agent';
2
+ import type {
3
+ AgentBirthProposal,
4
+ AgentGeneDefinition,
5
+ AgentGenealogy,
6
+ AgentReputation,
7
+ RuntimeAgentRecord,
8
+ UtilityScore,
9
+ } from '../types/organism.types';
10
+ import { createEmptyWallet } from '../economy/resource-allocator';
11
+ import { OrganismEventEmitter, OrganismEventType } from '../events/organism-events';
12
+ import type { OrganismRepository } from '../persistence/organism-repository';
13
+ import { CapabilityRegistry } from '../core/capability-registry';
14
+ import type { AgentResourceWallet } from '../types/organism.types';
15
+
16
+ let agentSeq = 0;
17
+
18
+ function nextAgentId(): string {
19
+ agentSeq += 1;
20
+ return `A${agentSeq}`;
21
+ }
22
+
23
+ export function resetAgentSeqForTests(): void {
24
+ agentSeq = 0;
25
+ }
26
+
27
+ function defaultReputation(agentId: string): AgentReputation {
28
+ return {
29
+ agentId,
30
+ score: 0.5,
31
+ dimensions: {
32
+ usefulness: 0.5,
33
+ reliability: 0.5,
34
+ efficiency: 0.5,
35
+ policyCompliance: 1,
36
+ collaboration: 0.5,
37
+ },
38
+ };
39
+ }
40
+
41
+ function defaultUtility(): UtilityScore {
42
+ return {
43
+ score: 0.5,
44
+ valueGenerated: 0,
45
+ cost: 0,
46
+ riskPenalty: 0,
47
+ confidence: 0.5,
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Birth engine — compiles gene + proposal into DNA and registers with AgentRuntime.
53
+ */
54
+ export class BirthEngine {
55
+ constructor(
56
+ private readonly events: OrganismEventEmitter,
57
+ private readonly repo: OrganismRepository,
58
+ private readonly capabilities: CapabilityRegistry,
59
+ private readonly organismId: string,
60
+ private readonly agentRuntime?: AgentRuntime
61
+ ) {}
62
+
63
+ async spawn(input: {
64
+ proposal: AgentBirthProposal;
65
+ gene: AgentGeneDefinition;
66
+ wallet: AgentResourceWallet;
67
+ generation?: number;
68
+ parentAgentId?: string;
69
+ nameHint?: string;
70
+ }): Promise<RuntimeAgentRecord> {
71
+ const id = nextAgentId();
72
+ const generation = input.generation ?? 1;
73
+ const specialize = input.proposal.specialize ?? [];
74
+ const capabilities = Array.from(
75
+ new Set([...input.gene.capabilities, ...specialize, ...input.proposal.requiredCapabilities])
76
+ );
77
+ const nameHint =
78
+ input.nameHint ??
79
+ `${input.proposal.needId
80
+ .split(/[-_]/)
81
+ .map((p) => p.charAt(0).toUpperCase() + p.slice(1))
82
+ .join('')}Agent`;
83
+ const dnaAgentName = `${nameHint}_${id}`;
84
+ const objective = input.proposal.expectedOutcome;
85
+
86
+ const dna = exportAgentDna({
87
+ name: dnaAgentName,
88
+ description: input.gene.description ?? input.proposal.reason,
89
+ systemPrompt:
90
+ input.gene.initialPrompt ??
91
+ `You are ${dnaAgentName}. Objective: ${objective}. Capabilities: ${capabilities.join(', ')}.`,
92
+ tools: [],
93
+ policies: input.gene.policies,
94
+ mission: { goal: objective },
95
+ metadata: {
96
+ capabilities,
97
+ organismId: this.organismId,
98
+ geneId: input.gene.id,
99
+ needId: input.proposal.needId,
100
+ terminationCriteria: input.proposal.terminationCriteria,
101
+ },
102
+ version: '1.0.0',
103
+ });
104
+
105
+ if (this.agentRuntime) {
106
+ const AgentClass = createAgentClassFromDna(dna);
107
+ this.agentRuntime.registerAgent(AgentClass);
108
+ }
109
+
110
+ const agent: RuntimeAgentRecord = {
111
+ id,
112
+ name: nameHint,
113
+ objective,
114
+ capabilities,
115
+ geneId: input.gene.id,
116
+ parentAgentId: input.parentAgentId ?? input.proposal.parentAgentId,
117
+ generation,
118
+ status: 'active',
119
+ reputation: defaultReputation(id),
120
+ utility: defaultUtility(),
121
+ wallet: { ...input.wallet },
122
+ birthProposal: input.proposal,
123
+ terminationCriteria: input.proposal.terminationCriteria ?? [],
124
+ createdAt: new Date(),
125
+ evaluationCount: 0,
126
+ criticalResponsibility: false,
127
+ costConsumed: 0,
128
+ tokensConsumed: 0,
129
+ valueGenerated: 0,
130
+ specialize,
131
+ dnaAgentName,
132
+ systemPrompt: dna.systemPrompt,
133
+ permissions: [...capabilities],
134
+ strategyConfig: {},
135
+ modelConfig: {},
136
+ mutations: [],
137
+ strategyId: `${input.gene.id}:g${generation}`,
138
+ };
139
+
140
+ const genealogy: AgentGenealogy = {
141
+ agentId: id,
142
+ parentAgentId: agent.parentAgentId,
143
+ rootGeneId: input.gene.id,
144
+ generation,
145
+ children: [],
146
+ createdAt: agent.createdAt,
147
+ };
148
+
149
+ await this.repo.saveAgent(this.organismId, agent);
150
+ await this.repo.saveGenealogy(this.organismId, genealogy);
151
+
152
+ if (agent.parentAgentId) {
153
+ const parentGene = (await this.repo.listGenealogy(this.organismId)).find(
154
+ (g) => g.agentId === agent.parentAgentId
155
+ );
156
+ if (parentGene) {
157
+ parentGene.children = [...parentGene.children, id];
158
+ await this.repo.saveGenealogy(this.organismId, parentGene);
159
+ }
160
+ }
161
+
162
+ this.capabilities.register(id, capabilities);
163
+
164
+ await this.events.emit(OrganismEventType.ORGANISM_AGENT_BORN, this.organismId, {
165
+ organismId: this.organismId,
166
+ agentId: id,
167
+ parentAgentId: agent.parentAgentId,
168
+ reason: input.proposal.reason,
169
+ generation,
170
+ geneId: input.gene.id,
171
+ needId: input.proposal.needId,
172
+ name: nameHint,
173
+ });
174
+
175
+ return agent;
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Termination engine — release resources, retain genealogy, emit death event.
181
+ */
182
+ export class TerminationEngine {
183
+ constructor(
184
+ private readonly events: OrganismEventEmitter,
185
+ private readonly repo: OrganismRepository,
186
+ private readonly capabilities: CapabilityRegistry,
187
+ private readonly organismId: string,
188
+ private readonly releaseWallet: (agent: RuntimeAgentRecord) => void
189
+ ) {}
190
+
191
+ async terminate(agentId: string, reason: string): Promise<RuntimeAgentRecord | undefined> {
192
+ const agent = await this.repo.getAgent(this.organismId, agentId);
193
+ if (!agent || agent.status === 'terminated') return agent;
194
+
195
+ agent.status = 'termination-pending';
196
+ await this.repo.saveAgent(this.organismId, agent);
197
+
198
+ this.releaseWallet(agent);
199
+ this.capabilities.unregister(agentId);
200
+
201
+ agent.status = 'terminated';
202
+ agent.terminatedAt = new Date();
203
+ await this.repo.saveAgent(this.organismId, agent);
204
+
205
+ const genealogy = (await this.repo.listGenealogy(this.organismId)).find(
206
+ (g) => g.agentId === agentId
207
+ );
208
+ if (genealogy) {
209
+ genealogy.terminatedAt = agent.terminatedAt;
210
+ await this.repo.saveGenealogy(this.organismId, genealogy);
211
+ }
212
+
213
+ await this.events.emit(OrganismEventType.ORGANISM_AGENT_TERMINATED, this.organismId, {
214
+ agentId,
215
+ reason,
216
+ finalScore: agent.utility.score,
217
+ totalCost: agent.costConsumed,
218
+ totalValue: agent.valueGenerated,
219
+ });
220
+
221
+ return agent;
222
+ }
223
+ }
224
+
225
+ export { createEmptyWallet };
@@ -0,0 +1,158 @@
1
+ import type {
2
+ AgentBirthProposal,
3
+ DetectedNeed,
4
+ OrganismDecision,
5
+ RuntimeAgentRecord,
6
+ } from '../types/organism.types';
7
+ import { CapabilityRegistry } from '../core/capability-registry';
8
+ import type { AgentGeneDefinition } from '../types/organism.types';
9
+
10
+ /**
11
+ * Deterministic decision engine: reuse → specialize → spawn → observe.
12
+ */
13
+ export class DecisionEngine {
14
+ constructor(private readonly capabilities: CapabilityRegistry) {}
15
+
16
+ decide(input: {
17
+ need: DetectedNeed;
18
+ liveAgents: RuntimeAgentRecord[];
19
+ genes: AgentGeneDefinition[];
20
+ canSpawn: boolean;
21
+ spawnBlockReason?: string;
22
+ }): OrganismDecision {
23
+ const { need, liveAgents, genes, canSpawn, spawnBlockReason } = input;
24
+
25
+ const capable = this.capabilities.findCapableAgents(need.requiredCapabilities, liveAgents);
26
+ if (capable.length > 0) {
27
+ const best = capable.sort((a, b) => b.reputation.score - a.reputation.score)[0];
28
+ return {
29
+ action: 'delegate',
30
+ reasoningSummary: `Capable agent ${best.id} already covers ${need.requiredCapabilities.join(', ')}`,
31
+ confidence: Math.min(0.95, need.confidence + 0.05),
32
+ targetAgentId: best.id,
33
+ requiredCapabilities: need.requiredCapabilities,
34
+ needId: need.need,
35
+ };
36
+ }
37
+
38
+ const partial = this.capabilities.findPartialAgents(need.requiredCapabilities, liveAgents);
39
+ const gene = this.selectGene(need.requiredCapabilities, genes);
40
+
41
+ if (partial.length > 0 && gene) {
42
+ const parent = partial[0];
43
+ const missing = need.requiredCapabilities.filter(
44
+ (c) => !parent.capabilities.map((x) => x.toLowerCase()).includes(c.toLowerCase())
45
+ );
46
+ if (!canSpawn) {
47
+ return {
48
+ action: 'observe',
49
+ reasoningSummary: `Would specialize for ${need.need} but spawn blocked: ${spawnBlockReason}`,
50
+ confidence: 0.4,
51
+ needId: need.need,
52
+ };
53
+ }
54
+ const proposal: AgentBirthProposal = {
55
+ reason: need.reason,
56
+ needId: need.need,
57
+ requiredCapabilities: need.requiredCapabilities,
58
+ expectedOutcome: `Address need ${need.need}`,
59
+ estimatedCost: 1,
60
+ expectedUtility: need.urgency,
61
+ terminationCriteria: [
62
+ `Need ${need.need} resolved`,
63
+ 'Utility score below 0.2 after 5 evaluations',
64
+ 'No related tasks for evaluation window',
65
+ ],
66
+ confidence: need.confidence,
67
+ geneId: gene.id,
68
+ specialize: missing,
69
+ parentAgentId: parent.id,
70
+ };
71
+ return {
72
+ action: 'specialize',
73
+ reasoningSummary: `Partial agent ${parent.id} exists; spawn specialized agent for missing ${missing.join(', ')}`,
74
+ confidence: need.confidence,
75
+ targetAgentId: parent.id,
76
+ requiredCapabilities: need.requiredCapabilities,
77
+ resourceRequest: { tokens: 50_000 },
78
+ birthProposal: proposal,
79
+ needId: need.need,
80
+ };
81
+ }
82
+
83
+ if (!gene) {
84
+ return {
85
+ action: 'observe',
86
+ reasoningSummary: `No gene covers required capabilities ${need.requiredCapabilities.join(', ')}`,
87
+ confidence: 0.5,
88
+ requiredCapabilities: need.requiredCapabilities,
89
+ needId: need.need,
90
+ };
91
+ }
92
+
93
+ if (!canSpawn) {
94
+ return {
95
+ action: 'observe',
96
+ reasoningSummary: `Need ${need.need} requires spawn but blocked: ${spawnBlockReason}`,
97
+ confidence: 0.4,
98
+ needId: need.need,
99
+ };
100
+ }
101
+
102
+ const proposal: AgentBirthProposal = {
103
+ reason: need.reason,
104
+ needId: need.need,
105
+ requiredCapabilities: need.requiredCapabilities,
106
+ expectedOutcome: `Address need ${need.need}`,
107
+ estimatedCost: 1,
108
+ expectedUtility: need.urgency,
109
+ terminationCriteria: [
110
+ `Need ${need.need} resolved`,
111
+ 'Utility score below 0.2 after 5 evaluations',
112
+ 'No related tasks for evaluation window',
113
+ ],
114
+ confidence: need.confidence,
115
+ geneId: gene.id,
116
+ specialize: need.requiredCapabilities.filter(
117
+ (c) => !gene.capabilities.map((g) => g.toLowerCase()).includes(c.toLowerCase())
118
+ ),
119
+ };
120
+
121
+ return {
122
+ action: 'spawn',
123
+ reasoningSummary: `No capable agent for ${need.need}; spawning from gene ${gene.id}`,
124
+ confidence: need.confidence,
125
+ requiredCapabilities: need.requiredCapabilities,
126
+ resourceRequest: { tokens: 50_000 },
127
+ birthProposal: proposal,
128
+ needId: need.need,
129
+ };
130
+ }
131
+
132
+ selectGene(required: string[], genes: AgentGeneDefinition[]): AgentGeneDefinition | undefined {
133
+ if (!genes.length) return undefined;
134
+ const needed = required.map((c) => c.toLowerCase());
135
+ let best: AgentGeneDefinition | undefined;
136
+ let bestScore = -1;
137
+ let bestRatio = -1;
138
+ let bestPrimaryHit = false;
139
+ const primary = needed[0];
140
+ for (const gene of genes) {
141
+ const caps = new Set(gene.capabilities.map((c) => c.toLowerCase()));
142
+ const score = needed.filter((n) => caps.has(n)).length;
143
+ const ratio = score / Math.max(1, gene.capabilities.length);
144
+ const primaryHit = primary ? caps.has(primary) : false;
145
+ const better =
146
+ score > bestScore ||
147
+ (score === bestScore && ratio > bestRatio) ||
148
+ (score === bestScore && ratio === bestRatio && primaryHit && !bestPrimaryHit);
149
+ if (better) {
150
+ bestScore = score;
151
+ bestRatio = ratio;
152
+ bestPrimaryHit = primaryHit;
153
+ best = gene;
154
+ }
155
+ }
156
+ return best ?? genes[0];
157
+ }
158
+ }
@@ -0,0 +1,289 @@
1
+ /**
2
+ * Phase 2 — parent→child reproduction with explicit inheritance policy.
3
+ */
4
+
5
+ import type {
6
+ AgentBirthProposal,
7
+ AgentGeneDefinition,
8
+ AgentResourceWallet,
9
+ InheritancePolicy,
10
+ OrganismLimits,
11
+ ReproduceRequest,
12
+ RuntimeAgentRecord,
13
+ } from '../types/organism.types';
14
+ import { DEFAULT_INHERITANCE_POLICY } from '../types/organism.types';
15
+ import { OrganismLimitError, OrganismStateError } from '../errors/organism.errors';
16
+ import { OrganismEventEmitter, OrganismEventType } from '../events/organism-events';
17
+ import type { OrganismRepository } from '../persistence/organism-repository';
18
+ import { ConstitutionEnforcer } from '../governance/constitution';
19
+ import { BirthEngine } from '../lifecycle/birth-engine';
20
+ import { GenealogyManager } from '../genealogy/genealogy-manager';
21
+
22
+ export interface ReproductionConfig {
23
+ cooldownMs: number;
24
+ }
25
+
26
+ export const DEFAULT_REPRODUCTION_CONFIG: ReproductionConfig = {
27
+ cooldownMs: 30_000,
28
+ };
29
+
30
+ export class ReproductionEngine {
31
+ constructor(
32
+ private readonly events: OrganismEventEmitter,
33
+ private readonly repo: OrganismRepository,
34
+ private readonly birth: BirthEngine,
35
+ private readonly genealogy: GenealogyManager,
36
+ private readonly constitution: ConstitutionEnforcer,
37
+ private readonly organismId: string,
38
+ private readonly now: () => Date = () => new Date(),
39
+ private readonly config: ReproductionConfig = DEFAULT_REPRODUCTION_CONFIG
40
+ ) {}
41
+
42
+ resolveInheritance(partial?: InheritancePolicy): InheritancePolicy {
43
+ return {
44
+ ...DEFAULT_INHERITANCE_POLICY,
45
+ ...partial,
46
+ memory: {
47
+ strategy:
48
+ partial?.memory?.strategy ??
49
+ DEFAULT_INHERITANCE_POLICY.memory?.strategy ??
50
+ 'relevant-only',
51
+ maxItems: partial?.memory?.maxItems ?? DEFAULT_INHERITANCE_POLICY.memory?.maxItems,
52
+ },
53
+ resources: {
54
+ transferFraction:
55
+ partial?.resources?.transferFraction ??
56
+ DEFAULT_INHERITANCE_POLICY.resources?.transferFraction,
57
+ },
58
+ };
59
+ }
60
+
61
+ assertCanReproduce(input: {
62
+ parent: RuntimeAgentRecord;
63
+ gene: AgentGeneDefinition;
64
+ limits: OrganismLimits;
65
+ liveAgents: RuntimeAgentRecord[];
66
+ liveChildCount: number;
67
+ }): void {
68
+ const { parent, gene, limits, liveAgents, liveChildCount } = input;
69
+
70
+ if (parent.status === 'terminated' || parent.status === 'failed') {
71
+ throw new OrganismStateError(`Parent ${parent.id} is not active`);
72
+ }
73
+ if (gene.reproduction && gene.reproduction.enabled === false) {
74
+ throw new OrganismLimitError(`Gene ${gene.id} has reproduction disabled`);
75
+ }
76
+ if (parent.generation >= limits.maxGenerationDepth) {
77
+ throw new OrganismLimitError(
78
+ `maxGenerationDepth (${limits.maxGenerationDepth}) reached by parent`
79
+ );
80
+ }
81
+ const geneMax = gene.reproduction?.maxChildren;
82
+ const maxChildren =
83
+ geneMax != null ? Math.min(geneMax, limits.maxChildrenPerAgent) : limits.maxChildrenPerAgent;
84
+ if (liveChildCount >= maxChildren) {
85
+ throw new OrganismLimitError(
86
+ `maxChildrenPerAgent (${maxChildren}) reached for parent ${parent.id}`
87
+ );
88
+ }
89
+ if (liveAgents.length >= limits.maxAgents) {
90
+ throw new OrganismLimitError(`maxAgents (${limits.maxAgents}) reached`);
91
+ }
92
+ if (parent.lastReproductionAt) {
93
+ const elapsed = this.now().getTime() - parent.lastReproductionAt.getTime();
94
+ if (elapsed < this.config.cooldownMs) {
95
+ throw new OrganismLimitError(
96
+ `Reproduction cooldown active (${elapsed}ms < ${this.config.cooldownMs}ms)`
97
+ );
98
+ }
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Build child capabilities: parent caps + specialization.
104
+ * Permissions must remain a subset of the parent's permission grants.
105
+ */
106
+ resolveChildCapabilities(
107
+ parent: RuntimeAgentRecord,
108
+ specialization: string[],
109
+ policy: InheritancePolicy
110
+ ): { capabilities: string[]; permissions: string[] } {
111
+ const capabilities = Array.from(new Set([...parent.capabilities, ...specialization]));
112
+ let permissions: string[];
113
+ if (policy.permissions === 'none') {
114
+ permissions = [];
115
+ } else {
116
+ // copy and subset both enforce ⊆ parent (never escalate)
117
+ permissions = [...parent.permissions];
118
+ }
119
+ return { capabilities, permissions };
120
+ }
121
+
122
+ transferResources(
123
+ parent: RuntimeAgentRecord,
124
+ policy: InheritancePolicy,
125
+ requestedTokens?: number
126
+ ): { childWallet: AgentResourceWallet; parentWallet: AgentResourceWallet } {
127
+ const fraction = policy.resources?.transferFraction ?? 0.25;
128
+ const parentTokens = parent.wallet.tokensRemaining ?? 0;
129
+ const parentMoney = parent.wallet.moneyRemaining?.amount ?? 0;
130
+ const currency = parent.wallet.moneyRemaining?.currency ?? 'USD';
131
+
132
+ let tokens = Math.floor(parentTokens * fraction);
133
+ if (requestedTokens != null) {
134
+ tokens = Math.min(tokens, requestedTokens, parentTokens);
135
+ }
136
+ const money = Math.floor(parentMoney * fraction);
137
+
138
+ const childWallet: AgentResourceWallet = {
139
+ tokensRemaining: tokens,
140
+ moneyRemaining: { amount: money, currency },
141
+ computeUnitsRemaining: Math.floor((parent.wallet.computeUnitsRemaining ?? 0) * fraction),
142
+ toolCallsRemaining: Math.floor((parent.wallet.toolCallsRemaining ?? 0) * fraction),
143
+ };
144
+
145
+ const parentWallet: AgentResourceWallet = {
146
+ tokensRemaining: parentTokens - tokens,
147
+ moneyRemaining: { amount: parentMoney - money, currency },
148
+ computeUnitsRemaining:
149
+ (parent.wallet.computeUnitsRemaining ?? 0) - (childWallet.computeUnitsRemaining ?? 0),
150
+ toolCallsRemaining:
151
+ (parent.wallet.toolCallsRemaining ?? 0) - (childWallet.toolCallsRemaining ?? 0),
152
+ };
153
+
154
+ return { childWallet, parentWallet };
155
+ }
156
+
157
+ buildProposal(
158
+ parent: RuntimeAgentRecord,
159
+ request: ReproduceRequest,
160
+ capabilities: string[]
161
+ ): AgentBirthProposal {
162
+ const specialization = request.specialization ?? [];
163
+ const needId = request.needId ?? `specialize-${specialization.join('-') || parent.id}`;
164
+ return {
165
+ reason: request.reason,
166
+ needId,
167
+ requiredCapabilities: capabilities,
168
+ expectedOutcome:
169
+ request.objective ??
170
+ `Specialized descendant of ${parent.name} for ${specialization.join(', ') || 'focused work'}`,
171
+ estimatedCost: 1,
172
+ expectedUtility: parent.utility.score,
173
+ terminationCriteria: request.terminationCriteria ?? [
174
+ `Specialization ${specialization.join(', ') || needId} complete`,
175
+ 'Utility score below 0.2 after 5 evaluations',
176
+ 'No related tasks for evaluation window',
177
+ ],
178
+ confidence: request.confidence ?? 0.8,
179
+ geneId: parent.geneId,
180
+ specialize: specialization,
181
+ parentAgentId: parent.id,
182
+ };
183
+ }
184
+
185
+ async reproduce(input: {
186
+ parent: RuntimeAgentRecord;
187
+ gene: AgentGeneDefinition;
188
+ request: ReproduceRequest;
189
+ limits: OrganismLimits;
190
+ liveAgents: RuntimeAgentRecord[];
191
+ /** When true, child wallet comes from organism pool allocation instead of parent. */
192
+ poolWallet?: AgentResourceWallet;
193
+ }): Promise<{ child: RuntimeAgentRecord; parent: RuntimeAgentRecord }> {
194
+ const policy = this.resolveInheritance(input.request.inheritance);
195
+ const liveChildCount = await this.genealogy.countLiveChildren(
196
+ input.parent.id,
197
+ input.liveAgents
198
+ );
199
+ this.assertCanReproduce({
200
+ parent: input.parent,
201
+ gene: input.gene,
202
+ limits: input.limits,
203
+ liveAgents: input.liveAgents,
204
+ liveChildCount,
205
+ });
206
+
207
+ this.constitution.assertAllows('reproduce', {});
208
+
209
+ const specialization = input.request.specialization ?? [];
210
+ const { capabilities, permissions } = this.resolveChildCapabilities(
211
+ input.parent,
212
+ specialization,
213
+ policy
214
+ );
215
+
216
+ let childWallet: AgentResourceWallet;
217
+ let parentWallet = input.parent.wallet;
218
+ if (input.poolWallet) {
219
+ childWallet = input.poolWallet;
220
+ } else {
221
+ const transfer = this.transferResources(input.parent, policy, input.request.tokens);
222
+ childWallet = transfer.childWallet;
223
+ parentWallet = transfer.parentWallet;
224
+ }
225
+
226
+ const proposal = this.buildProposal(input.parent, input.request, capabilities);
227
+ const generation = input.parent.generation + 1;
228
+
229
+ input.parent.status = 'reproducing';
230
+ await this.repo.saveAgent(this.organismId, input.parent);
231
+
232
+ const child = await this.birth.spawn({
233
+ proposal,
234
+ gene: input.gene,
235
+ wallet: childWallet,
236
+ generation,
237
+ parentAgentId: input.parent.id,
238
+ nameHint: specialization.length
239
+ ? `${specialization
240
+ .map((s) =>
241
+ s
242
+ .split(/[-_]/)
243
+ .map((p) => p.charAt(0).toUpperCase() + p.slice(1))
244
+ .join('')
245
+ )
246
+ .join('')}Agent`
247
+ : `${input.parent.name}Child`,
248
+ });
249
+
250
+ // Apply inheritance overlays
251
+ child.permissions = permissions;
252
+ child.systemPrompt =
253
+ policy.strategies !== false
254
+ ? [
255
+ input.parent.systemPrompt ?? input.gene.initialPrompt ?? '',
256
+ specialization.length ? `Specialization: ${specialization.join(', ')}` : '',
257
+ ]
258
+ .filter(Boolean)
259
+ .join('\n')
260
+ : input.gene.initialPrompt;
261
+ if (policy.strategies !== false) {
262
+ child.strategyConfig = { ...input.parent.strategyConfig };
263
+ }
264
+ if (policy.modelSettings !== false) {
265
+ child.modelConfig = { ...input.parent.modelConfig };
266
+ }
267
+ child.strategyId = input.parent.strategyId
268
+ ? `${input.parent.strategyId}:g${generation}`
269
+ : `${input.parent.geneId}:g${generation}`;
270
+
271
+ await this.repo.saveAgent(this.organismId, child);
272
+
273
+ input.parent.wallet = parentWallet;
274
+ input.parent.status = 'active';
275
+ input.parent.lastReproductionAt = this.now();
276
+ await this.repo.saveAgent(this.organismId, input.parent);
277
+
278
+ await this.events.emit(OrganismEventType.ORGANISM_AGENT_REPRODUCED, this.organismId, {
279
+ parentAgentId: input.parent.id,
280
+ childAgentId: child.id,
281
+ generation,
282
+ specialization,
283
+ reason: input.request.reason,
284
+ inheritance: policy,
285
+ });
286
+
287
+ return { child, parent: input.parent };
288
+ }
289
+ }