@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,95 @@
1
+ import type { RuntimeAgentRecord, SurvivalConfig, SurvivalState } from '../types/organism.types';
2
+ import { DEFAULT_SURVIVAL_CONFIG } from '../types/organism.types';
3
+
4
+ export interface SurvivalVerdict {
5
+ agentId: string;
6
+ state: SurvivalState;
7
+ reason: string;
8
+ shouldTerminate: boolean;
9
+ }
10
+
11
+ /**
12
+ * Survival engine — never kill on a single poor outcome.
13
+ */
14
+ export class SurvivalEngine {
15
+ constructor(
16
+ private readonly config: SurvivalConfig = DEFAULT_SURVIVAL_CONFIG,
17
+ private readonly now: () => Date = () => new Date()
18
+ ) {}
19
+
20
+ evaluate(agent: RuntimeAgentRecord): SurvivalVerdict {
21
+ if (agent.status === 'terminated') {
22
+ return {
23
+ agentId: agent.id,
24
+ state: 'terminated',
25
+ reason: 'Already terminated',
26
+ shouldTerminate: false,
27
+ };
28
+ }
29
+
30
+ if (agent.criticalResponsibility) {
31
+ return {
32
+ agentId: agent.id,
33
+ state: 'healthy',
34
+ reason: 'Critical responsibility protected',
35
+ shouldTerminate: false,
36
+ };
37
+ }
38
+
39
+ const ageMs = this.now().getTime() - agent.createdAt.getTime();
40
+ if (ageMs < this.config.minimumEvaluationAgeMs) {
41
+ return {
42
+ agentId: agent.id,
43
+ state: 'healthy',
44
+ reason: `Too young to evaluate (${ageMs}ms < ${this.config.minimumEvaluationAgeMs}ms)`,
45
+ shouldTerminate: false,
46
+ };
47
+ }
48
+
49
+ if (agent.evaluationCount < this.config.minimumSampleSize) {
50
+ return {
51
+ agentId: agent.id,
52
+ state: 'watch',
53
+ reason: `Insufficient samples (${agent.evaluationCount} < ${this.config.minimumSampleSize})`,
54
+ shouldTerminate: false,
55
+ };
56
+ }
57
+
58
+ if (agent.lastEvaluatedAt) {
59
+ const since = this.now().getTime() - agent.lastEvaluatedAt.getTime();
60
+ if (since < this.config.cooldownMs && agent.utility.score >= this.config.minimumUtility) {
61
+ return {
62
+ agentId: agent.id,
63
+ state: 'healthy',
64
+ reason: 'Within cooldown',
65
+ shouldTerminate: false,
66
+ };
67
+ }
68
+ }
69
+
70
+ if (agent.utility.score < this.config.minimumUtility) {
71
+ return {
72
+ agentId: agent.id,
73
+ state: 'candidate-for-termination',
74
+ reason: `Utility ${agent.utility.score.toFixed(3)} below minimum ${this.config.minimumUtility}`,
75
+ shouldTerminate: true,
76
+ };
77
+ }
78
+
79
+ if (agent.utility.score < this.config.minimumUtility + 0.15) {
80
+ return {
81
+ agentId: agent.id,
82
+ state: 'watch',
83
+ reason: 'Utility near threshold',
84
+ shouldTerminate: false,
85
+ };
86
+ }
87
+
88
+ return {
89
+ agentId: agent.id,
90
+ state: 'healthy',
91
+ reason: 'Utility healthy',
92
+ shouldTerminate: false,
93
+ };
94
+ }
95
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Optional OpenTelemetry-compatible metrics for organism runtime.
3
+ */
4
+
5
+ export interface OrganismMetricsSnapshot {
6
+ activeAgents: number;
7
+ agentsSpawnedTotal: number;
8
+ agentsTerminatedTotal: number;
9
+ reproductionsTotal: number;
10
+ mutationsTotal: number;
11
+ tokenUsage: number;
12
+ cost: number;
13
+ missionProgress: number;
14
+ constitutionViolations: number;
15
+ }
16
+
17
+ export class OrganismMetrics {
18
+ private spawned = 0;
19
+ private terminated = 0;
20
+ private reproductions = 0;
21
+ private mutations = 0;
22
+ private tokens = 0;
23
+ private cost = 0;
24
+ private violations = 0;
25
+ private active = 0;
26
+ private missionProgress = 0;
27
+
28
+ recordSpawn(): void {
29
+ this.spawned += 1;
30
+ this.active += 1;
31
+ }
32
+
33
+ recordReproduction(): void {
34
+ this.reproductions += 1;
35
+ this.spawned += 1;
36
+ this.active += 1;
37
+ }
38
+
39
+ recordMutation(): void {
40
+ this.mutations += 1;
41
+ }
42
+
43
+ recordTermination(): void {
44
+ this.terminated += 1;
45
+ this.active = Math.max(0, this.active - 1);
46
+ }
47
+
48
+ recordTokens(n: number): void {
49
+ this.tokens += n;
50
+ }
51
+
52
+ recordCost(n: number): void {
53
+ this.cost += n;
54
+ }
55
+
56
+ recordViolation(): void {
57
+ this.violations += 1;
58
+ }
59
+
60
+ setMissionProgress(ratio: number): void {
61
+ this.missionProgress = ratio;
62
+ }
63
+
64
+ snapshot(): OrganismMetricsSnapshot {
65
+ return {
66
+ activeAgents: this.active,
67
+ agentsSpawnedTotal: this.spawned,
68
+ agentsTerminatedTotal: this.terminated,
69
+ reproductionsTotal: this.reproductions,
70
+ mutationsTotal: this.mutations,
71
+ tokenUsage: this.tokens,
72
+ cost: this.cost,
73
+ missionProgress: this.missionProgress,
74
+ constitutionViolations: this.violations,
75
+ };
76
+ }
77
+
78
+ /** Soft-bind to OpenTelemetry if available. */
79
+ exportToOtel(meterName = 'hazel.organism'): void {
80
+ try {
81
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
82
+ const otel = require('@opentelemetry/api') as {
83
+ metrics?: {
84
+ getMeter: (name: string) => {
85
+ createObservableGauge: (
86
+ name: string,
87
+ opts: { description: string },
88
+ cb: (obs: { observe: (v: number) => void }) => void
89
+ ) => void;
90
+ };
91
+ };
92
+ };
93
+ const meter = otel.metrics?.getMeter(meterName);
94
+ if (!meter) return;
95
+ const snap = (): OrganismMetricsSnapshot => this.snapshot();
96
+ meter.createObservableGauge(
97
+ 'hazel.organism.active_agents',
98
+ { description: 'Active organism agents' },
99
+ (obs) => obs.observe(snap().activeAgents)
100
+ );
101
+ meter.createObservableGauge(
102
+ 'hazel.organism.agents_spawned_total',
103
+ { description: 'Total agents spawned' },
104
+ (obs) => obs.observe(snap().agentsSpawnedTotal)
105
+ );
106
+ meter.createObservableGauge(
107
+ 'hazel.organism.token_usage',
108
+ { description: 'Organism token usage' },
109
+ (obs) => obs.observe(snap().tokenUsage)
110
+ );
111
+ meter.createObservableGauge(
112
+ 'hazel.organism.mission_progress',
113
+ { description: 'Mission progress ratio' },
114
+ (obs) => obs.observe(snap().missionProgress)
115
+ );
116
+ } catch {
117
+ // optional peer
118
+ }
119
+ }
120
+ }
@@ -0,0 +1,140 @@
1
+ import type {
2
+ DetectedNeed,
3
+ EnvironmentSignal,
4
+ MissionDefinition,
5
+ SignalNeedMapping,
6
+ } from '../types/organism.types';
7
+
8
+ export interface PerceptionResult {
9
+ accepted: boolean;
10
+ signal: EnvironmentSignal;
11
+ reason: string;
12
+ }
13
+
14
+ /**
15
+ * Normalize + relevance-filter environment signals.
16
+ */
17
+ export class PerceptionEngine {
18
+ constructor(
19
+ private readonly relevanceThreshold = 0.3,
20
+ private readonly severityThreshold = 0.2
21
+ ) {}
22
+
23
+ normalize(
24
+ raw: Partial<EnvironmentSignal> & Pick<EnvironmentSignal, 'type' | 'source'>
25
+ ): EnvironmentSignal {
26
+ return {
27
+ id: raw.id ?? `sig_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
28
+ type: raw.type,
29
+ source: raw.source,
30
+ timestamp: raw.timestamp ?? new Date(),
31
+ data: raw.data ?? {},
32
+ severity: raw.severity,
33
+ relevance: raw.relevance,
34
+ };
35
+ }
36
+
37
+ filter(signal: EnvironmentSignal, mission: MissionDefinition): PerceptionResult {
38
+ const relevance = signal.relevance ?? this.inferRelevance(signal, mission);
39
+ const severity = signal.severity ?? 0.5;
40
+ const accepted = relevance >= this.relevanceThreshold || severity >= this.severityThreshold;
41
+ return {
42
+ accepted,
43
+ signal: { ...signal, relevance, severity },
44
+ reason: accepted
45
+ ? `Accepted (relevance=${relevance.toFixed(2)}, severity=${severity.toFixed(2)})`
46
+ : `Ignored (relevance=${relevance.toFixed(2)}, severity=${severity.toFixed(2)})`,
47
+ };
48
+ }
49
+
50
+ private inferRelevance(signal: EnvironmentSignal, mission: MissionDefinition): number {
51
+ const text = `${mission.objective} ${(mission.constraints ?? []).join(' ')}`.toLowerCase();
52
+ const typeParts = signal.type.toLowerCase().split(/[._-]/);
53
+ let hits = 0;
54
+ for (const part of typeParts) {
55
+ if (part.length > 2 && text.includes(part)) hits += 1;
56
+ }
57
+ if (hits === 0) return 0.4;
58
+ return Math.min(1, 0.5 + hits * 0.15);
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Deterministic need detector using configurable signal→need mappings.
64
+ *
65
+ * Domain-specific capability routing belongs in `signalNeedMappings`
66
+ * (e.g. ecommerce refund → analytics). The unmapped fallback only
67
+ * treats generic anomaly suffixes as needs and derives capability
68
+ * hints from the signal type subject tokens — never from a fixed
69
+ * industry vocabulary.
70
+ */
71
+ export class NeedDetector {
72
+ /** Tokens that describe change/anomaly, not a capability subject. */
73
+ private static readonly ANOMALY_TOKENS = new Set([
74
+ 'increased',
75
+ 'decreased',
76
+ 'spike',
77
+ 'drop',
78
+ 'failure',
79
+ 'failed',
80
+ 'shortage',
81
+ 'delay',
82
+ 'delayed',
83
+ 'rate',
84
+ 'volume',
85
+ 'high',
86
+ 'low',
87
+ ]);
88
+
89
+ constructor(private readonly mappings: SignalNeedMapping[] = []) {}
90
+
91
+ detect(signal: EnvironmentSignal, _mission: MissionDefinition): DetectedNeed | undefined {
92
+ const mapping = this.mappings.find((m) => m.signalType === signal.type);
93
+ if (!mapping) {
94
+ if (!this.looksLikeAnomaly(signal.type)) {
95
+ return undefined;
96
+ }
97
+ const need = signal.type.replace(/\./g, '-');
98
+ return {
99
+ need,
100
+ reason: `Signal ${signal.type} indicates intervention may be required`,
101
+ requiredCapabilities: this.capabilitiesFromSignalType(signal.type),
102
+ urgency: signal.severity ?? 0.7,
103
+ confidence: 0.6,
104
+ signalId: signal.id,
105
+ };
106
+ }
107
+
108
+ const data = signal.data as { baseline?: number; current?: number } | undefined;
109
+ let reason = `Signal ${signal.type} mapped to need ${mapping.need}`;
110
+ if (data?.baseline != null && data?.current != null) {
111
+ reason = `${mapping.need}: ${signal.type} from ${data.baseline} to ${data.current}`;
112
+ }
113
+
114
+ return {
115
+ need: mapping.need,
116
+ reason,
117
+ requiredCapabilities: [...mapping.requiredCapabilities],
118
+ urgency: mapping.urgency ?? signal.severity ?? 0.7,
119
+ confidence: mapping.confidence ?? 0.9,
120
+ signalId: signal.id,
121
+ };
122
+ }
123
+
124
+ private looksLikeAnomaly(signalType: string): boolean {
125
+ return /(increased|decreased|spike|drop|failure|shortage|delay)/i.test(signalType);
126
+ }
127
+
128
+ /**
129
+ * Derive capability hints from signal type subjects.
130
+ * e.g. `orders.fulfillment.delay` → `['orders', 'fulfillment']`
131
+ */
132
+ private capabilitiesFromSignalType(signalType: string): string[] {
133
+ const parts = signalType
134
+ .toLowerCase()
135
+ .split(/[._-]/)
136
+ .filter((p) => p.length > 1 && !NeedDetector.ANOMALY_TOKENS.has(p));
137
+ if (parts.length === 0) return ['general'];
138
+ return Array.from(new Set(parts));
139
+ }
140
+ }
@@ -0,0 +1 @@
1
+ export * from './organism-repository';
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Persistence abstractions for organism state.
3
+ */
4
+
5
+ import type {
6
+ AgentGenealogy,
7
+ EnvironmentSignal,
8
+ OrganismDecision,
9
+ OrganismRecord,
10
+ RuntimeAgentRecord,
11
+ } from '../types/organism.types';
12
+
13
+ export interface OrganismRepository {
14
+ saveOrganism(record: OrganismRecord): Promise<void>;
15
+ getOrganism(id: string): Promise<OrganismRecord | undefined>;
16
+ listOrganisms(): Promise<OrganismRecord[]>;
17
+ deleteOrganism(id: string): Promise<boolean>;
18
+
19
+ saveAgent(organismId: string, agent: RuntimeAgentRecord): Promise<void>;
20
+ getAgent(organismId: string, agentId: string): Promise<RuntimeAgentRecord | undefined>;
21
+ listAgents(organismId: string): Promise<RuntimeAgentRecord[]>;
22
+ deleteAgent(organismId: string, agentId: string): Promise<boolean>;
23
+
24
+ saveGenealogy(organismId: string, genealogy: AgentGenealogy): Promise<void>;
25
+ listGenealogy(organismId: string): Promise<AgentGenealogy[]>;
26
+
27
+ appendSignal(organismId: string, signal: EnvironmentSignal): Promise<void>;
28
+ listSignals(organismId: string, limit?: number): Promise<EnvironmentSignal[]>;
29
+
30
+ appendDecision(organismId: string, decision: OrganismDecision): Promise<void>;
31
+ listDecisions(organismId: string, limit?: number): Promise<OrganismDecision[]>;
32
+ }
33
+
34
+ export class InMemoryOrganismRepository implements OrganismRepository {
35
+ private organisms = new Map<string, OrganismRecord>();
36
+ private agents = new Map<string, Map<string, RuntimeAgentRecord>>();
37
+ private genealogy = new Map<string, Map<string, AgentGenealogy>>();
38
+ private signals = new Map<string, EnvironmentSignal[]>();
39
+ private decisions = new Map<string, OrganismDecision[]>();
40
+
41
+ async saveOrganism(record: OrganismRecord): Promise<void> {
42
+ this.organisms.set(record.id, { ...record, updatedAt: new Date() });
43
+ if (!this.agents.has(record.id)) this.agents.set(record.id, new Map());
44
+ if (!this.genealogy.has(record.id)) this.genealogy.set(record.id, new Map());
45
+ if (!this.signals.has(record.id)) this.signals.set(record.id, []);
46
+ if (!this.decisions.has(record.id)) this.decisions.set(record.id, []);
47
+ }
48
+
49
+ async getOrganism(id: string): Promise<OrganismRecord | undefined> {
50
+ const record = this.organisms.get(id);
51
+ return record ? { ...record } : undefined;
52
+ }
53
+
54
+ async listOrganisms(): Promise<OrganismRecord[]> {
55
+ return Array.from(this.organisms.values()).map((r) => ({ ...r }));
56
+ }
57
+
58
+ async deleteOrganism(id: string): Promise<boolean> {
59
+ this.agents.delete(id);
60
+ this.genealogy.delete(id);
61
+ this.signals.delete(id);
62
+ this.decisions.delete(id);
63
+ return this.organisms.delete(id);
64
+ }
65
+
66
+ async saveAgent(organismId: string, agent: RuntimeAgentRecord): Promise<void> {
67
+ let map = this.agents.get(organismId);
68
+ if (!map) {
69
+ map = new Map();
70
+ this.agents.set(organismId, map);
71
+ }
72
+ map.set(agent.id, { ...agent });
73
+ }
74
+
75
+ async getAgent(organismId: string, agentId: string): Promise<RuntimeAgentRecord | undefined> {
76
+ const agent = this.agents.get(organismId)?.get(agentId);
77
+ return agent ? { ...agent } : undefined;
78
+ }
79
+
80
+ async listAgents(organismId: string): Promise<RuntimeAgentRecord[]> {
81
+ const map = this.agents.get(organismId);
82
+ if (!map) return [];
83
+ return Array.from(map.values()).map((a) => ({ ...a }));
84
+ }
85
+
86
+ async deleteAgent(organismId: string, agentId: string): Promise<boolean> {
87
+ return this.agents.get(organismId)?.delete(agentId) ?? false;
88
+ }
89
+
90
+ async saveGenealogy(organismId: string, genealogy: AgentGenealogy): Promise<void> {
91
+ let map = this.genealogy.get(organismId);
92
+ if (!map) {
93
+ map = new Map();
94
+ this.genealogy.set(organismId, map);
95
+ }
96
+ map.set(genealogy.agentId, {
97
+ ...genealogy,
98
+ children: [...genealogy.children],
99
+ });
100
+ }
101
+
102
+ async listGenealogy(organismId: string): Promise<AgentGenealogy[]> {
103
+ const map = this.genealogy.get(organismId);
104
+ if (!map) return [];
105
+ return Array.from(map.values()).map((g) => ({
106
+ ...g,
107
+ children: [...g.children],
108
+ }));
109
+ }
110
+
111
+ async appendSignal(organismId: string, signal: EnvironmentSignal): Promise<void> {
112
+ const list = this.signals.get(organismId) ?? [];
113
+ list.push({ ...signal });
114
+ if (list.length > 200) list.splice(0, list.length - 200);
115
+ this.signals.set(organismId, list);
116
+ }
117
+
118
+ async listSignals(organismId: string, limit = 50): Promise<EnvironmentSignal[]> {
119
+ const list = this.signals.get(organismId) ?? [];
120
+ return list.slice(-limit).map((s) => ({ ...s }));
121
+ }
122
+
123
+ async appendDecision(organismId: string, decision: OrganismDecision): Promise<void> {
124
+ const list = this.decisions.get(organismId) ?? [];
125
+ list.push({ ...decision });
126
+ if (list.length > 200) list.splice(0, list.length - 200);
127
+ this.decisions.set(organismId, list);
128
+ }
129
+
130
+ async listDecisions(organismId: string, limit = 50): Promise<OrganismDecision[]> {
131
+ const list = this.decisions.get(organismId) ?? [];
132
+ return list.slice(-limit).map((d) => ({ ...d }));
133
+ }
134
+ }