@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,180 @@
1
+ /**
2
+ * Organism decorators — follow @Agent (reflect-metadata + global registries).
3
+ */
4
+
5
+ import 'reflect-metadata';
6
+ import type {
7
+ AgentGeneDefinition,
8
+ ConstitutionDefinition,
9
+ EnvironmentDefinition,
10
+ MissionDefinition,
11
+ ResourceDefinition,
12
+ } from '../types/organism.types';
13
+
14
+ type Newable = new (...args: unknown[]) => unknown;
15
+
16
+ const MISSION_KEY = Symbol('organism:mission');
17
+ const ORGANISM_KEY = Symbol('organism:organism');
18
+ const GENE_KEY = Symbol('organism:gene');
19
+ const ENVIRONMENT_KEY = Symbol('organism:environment');
20
+ const CONSTITUTION_KEY = Symbol('organism:constitution');
21
+ const RESOURCE_KEY = Symbol('organism:resource');
22
+
23
+ const MISSION_REGISTRY = new Set<Newable>();
24
+ const ORGANISM_REGISTRY = new Set<Newable>();
25
+ const GENE_REGISTRY = new Set<Newable>();
26
+ const ENVIRONMENT_REGISTRY = new Set<Newable>();
27
+ const CONSTITUTION_REGISTRY = new Set<Newable>();
28
+ const RESOURCE_REGISTRY = new Set<Newable>();
29
+
30
+ export interface OrganismDecoratorOptions {
31
+ mission?: Newable | MissionDefinition;
32
+ environment?: Newable | EnvironmentDefinition;
33
+ constitution?: Newable | ConstitutionDefinition;
34
+ genes?: Array<Newable | AgentGeneDefinition>;
35
+ resources?: Newable | ResourceDefinition;
36
+ id?: string;
37
+ }
38
+
39
+ export function Mission(config: MissionDefinition): ClassDecorator {
40
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
41
+ return ((target: Function) => {
42
+ Reflect.defineMetadata(MISSION_KEY, config, target);
43
+ MISSION_REGISTRY.add(target as Newable);
44
+ }) as ClassDecorator;
45
+ }
46
+
47
+ export function Organism(config: OrganismDecoratorOptions = {}): ClassDecorator {
48
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
49
+ return ((target: Function) => {
50
+ Reflect.defineMetadata(ORGANISM_KEY, config, target);
51
+ ORGANISM_REGISTRY.add(target as Newable);
52
+ }) as ClassDecorator;
53
+ }
54
+
55
+ export function AgentGene(config: AgentGeneDefinition): ClassDecorator {
56
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
57
+ return ((target: Function) => {
58
+ Reflect.defineMetadata(GENE_KEY, config, target);
59
+ GENE_REGISTRY.add(target as Newable);
60
+ }) as ClassDecorator;
61
+ }
62
+
63
+ export function Environment(config: EnvironmentDefinition): ClassDecorator {
64
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
65
+ return ((target: Function) => {
66
+ Reflect.defineMetadata(ENVIRONMENT_KEY, config, target);
67
+ ENVIRONMENT_REGISTRY.add(target as Newable);
68
+ }) as ClassDecorator;
69
+ }
70
+
71
+ export function Constitution(config: ConstitutionDefinition): ClassDecorator {
72
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
73
+ return ((target: Function) => {
74
+ Reflect.defineMetadata(CONSTITUTION_KEY, config, target);
75
+ CONSTITUTION_REGISTRY.add(target as Newable);
76
+ }) as ClassDecorator;
77
+ }
78
+
79
+ export function Resource(config: ResourceDefinition): ClassDecorator {
80
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
81
+ return ((target: Function) => {
82
+ Reflect.defineMetadata(RESOURCE_KEY, config, target);
83
+ RESOURCE_REGISTRY.add(target as Newable);
84
+ }) as ClassDecorator;
85
+ }
86
+
87
+ export function getMissionMetadata(target: Newable): MissionDefinition | undefined {
88
+ return Reflect.getMetadata(MISSION_KEY, target);
89
+ }
90
+
91
+ export function getOrganismMetadata(target: Newable): OrganismDecoratorOptions | undefined {
92
+ return Reflect.getMetadata(ORGANISM_KEY, target);
93
+ }
94
+
95
+ export function getAgentGeneMetadata(target: Newable): AgentGeneDefinition | undefined {
96
+ return Reflect.getMetadata(GENE_KEY, target);
97
+ }
98
+
99
+ export function getEnvironmentMetadata(target: Newable): EnvironmentDefinition | undefined {
100
+ return Reflect.getMetadata(ENVIRONMENT_KEY, target);
101
+ }
102
+
103
+ export function getConstitutionMetadata(target: Newable): ConstitutionDefinition | undefined {
104
+ return Reflect.getMetadata(CONSTITUTION_KEY, target);
105
+ }
106
+
107
+ export function getResourceMetadata(target: Newable): ResourceDefinition | undefined {
108
+ return Reflect.getMetadata(RESOURCE_KEY, target);
109
+ }
110
+
111
+ export function getRegisteredMissions(): Newable[] {
112
+ return Array.from(MISSION_REGISTRY);
113
+ }
114
+
115
+ export function getRegisteredOrganisms(): Newable[] {
116
+ return Array.from(ORGANISM_REGISTRY);
117
+ }
118
+
119
+ export function getRegisteredGenes(): Newable[] {
120
+ return Array.from(GENE_REGISTRY);
121
+ }
122
+
123
+ export function getRegisteredEnvironments(): Newable[] {
124
+ return Array.from(ENVIRONMENT_REGISTRY);
125
+ }
126
+
127
+ export function getRegisteredConstitutions(): Newable[] {
128
+ return Array.from(CONSTITUTION_REGISTRY);
129
+ }
130
+
131
+ export function getRegisteredResources(): Newable[] {
132
+ return Array.from(RESOURCE_REGISTRY);
133
+ }
134
+
135
+ export function resolveMission(input: Newable | MissionDefinition): MissionDefinition {
136
+ if (typeof input === 'function') {
137
+ const meta = getMissionMetadata(input);
138
+ if (!meta) throw new Error(`Class ${input.name} is not decorated with @Mission`);
139
+ return meta;
140
+ }
141
+ return input;
142
+ }
143
+
144
+ export function resolveGene(input: Newable | AgentGeneDefinition): AgentGeneDefinition {
145
+ if (typeof input === 'function') {
146
+ const meta = getAgentGeneMetadata(input);
147
+ if (!meta) throw new Error(`Class ${input.name} is not decorated with @AgentGene`);
148
+ return meta;
149
+ }
150
+ return input;
151
+ }
152
+
153
+ export function resolveEnvironment(input: Newable | EnvironmentDefinition): EnvironmentDefinition {
154
+ if (typeof input === 'function') {
155
+ const meta = getEnvironmentMetadata(input);
156
+ if (!meta) throw new Error(`Class ${input.name} is not decorated with @Environment`);
157
+ return meta;
158
+ }
159
+ return input;
160
+ }
161
+
162
+ export function resolveConstitution(
163
+ input: Newable | ConstitutionDefinition
164
+ ): ConstitutionDefinition {
165
+ if (typeof input === 'function') {
166
+ const meta = getConstitutionMetadata(input);
167
+ if (!meta) throw new Error(`Class ${input.name} is not decorated with @Constitution`);
168
+ return meta;
169
+ }
170
+ return input;
171
+ }
172
+
173
+ export function resolveResource(input: Newable | ResourceDefinition): ResourceDefinition {
174
+ if (typeof input === 'function') {
175
+ const meta = getResourceMetadata(input);
176
+ if (!meta) throw new Error(`Class ${input.name} is not decorated with @Resource`);
177
+ return meta;
178
+ }
179
+ return input;
180
+ }
@@ -0,0 +1,324 @@
1
+ /**
2
+ * Phase 4 — internal resource market: bids, clearing, peer negotiation.
3
+ */
4
+
5
+ import type {
6
+ AgentResourceWallet,
7
+ MarketClearingResult,
8
+ MarketConfig,
9
+ NegotiationOffer,
10
+ NegotiationResult,
11
+ OrganismRecord,
12
+ ResourceBid,
13
+ ResourceRequest,
14
+ RuntimeAgentRecord,
15
+ } from '../types/organism.types';
16
+ import { DEFAULT_MARKET_CONFIG } from '../types/organism.types';
17
+ import { OrganismEventEmitter, OrganismEventType } from '../events/organism-events';
18
+ import { ResourceAllocator } from './resource-allocator';
19
+ import { UtilityForecaster } from './utility-forecaster';
20
+
21
+ let bidSeq = 0;
22
+
23
+ export function resetBidSeqForTests(): void {
24
+ bidSeq = 0;
25
+ }
26
+
27
+ /**
28
+ * Deterministic sealed-bid style clearing:
29
+ * rank by forecast net value + price + urgency − opportunity cost.
30
+ */
31
+ export class MarketEngine {
32
+ private openBids = new Map<string, ResourceBid>();
33
+ private history: MarketClearingResult[] = [];
34
+ private readonly config: MarketConfig;
35
+
36
+ constructor(
37
+ private readonly events: OrganismEventEmitter,
38
+ private readonly allocator: ResourceAllocator,
39
+ private readonly forecaster: UtilityForecaster,
40
+ private readonly organismId: string,
41
+ private readonly now: () => Date = () => new Date(),
42
+ config: Partial<MarketConfig> = {}
43
+ ) {
44
+ this.config = { ...DEFAULT_MARKET_CONFIG, ...config };
45
+ }
46
+
47
+ placeBid(input: {
48
+ agentId: string;
49
+ reason: string;
50
+ requested: ResourceRequest;
51
+ expectedValue: number;
52
+ confidence: number;
53
+ urgency?: number;
54
+ bidPrice?: number;
55
+ ttlMs?: number;
56
+ }): ResourceBid {
57
+ bidSeq += 1;
58
+ const createdAt = this.now();
59
+ const ttl = input.ttlMs ?? this.config.defaultBidTtlMs;
60
+ const bid: ResourceBid = {
61
+ id: `bid_${bidSeq}`,
62
+ agentId: input.agentId,
63
+ reason: input.reason,
64
+ requested: { ...input.requested },
65
+ expectedValue: input.expectedValue,
66
+ confidence: input.confidence,
67
+ urgency: input.urgency ?? 0.5,
68
+ bidPrice: input.bidPrice,
69
+ createdAt,
70
+ expiresAt: new Date(createdAt.getTime() + ttl),
71
+ status: 'open',
72
+ };
73
+ this.openBids.set(bid.id, bid);
74
+ void this.events.emit(OrganismEventType.ORGANISM_RESOURCE_BID, this.organismId, bid);
75
+ return bid;
76
+ }
77
+
78
+ listOpenBids(): ResourceBid[] {
79
+ this.expireBids();
80
+ return Array.from(this.openBids.values()).filter((b) => b.status === 'open');
81
+ }
82
+
83
+ cancelBid(bidId: string): boolean {
84
+ const bid = this.openBids.get(bidId);
85
+ if (!bid || bid.status !== 'open') return false;
86
+ bid.status = 'cancelled';
87
+ return true;
88
+ }
89
+
90
+ /**
91
+ * Clear all open (non-expired) bids against the organism pool.
92
+ * Higher clearing score wins until pool is exhausted.
93
+ */
94
+ clearMarket(input: {
95
+ record: OrganismRecord;
96
+ agents: Map<string, RuntimeAgentRecord>;
97
+ }): MarketClearingResult {
98
+ this.expireBids();
99
+ const roundId = `mkt_${Date.now().toString(36)}_${bidSeq}`;
100
+ const open = this.listOpenBids();
101
+
102
+ type Ranked = {
103
+ bid: ResourceBid;
104
+ agent: RuntimeAgentRecord;
105
+ forecast: ReturnType<UtilityForecaster['forecast']>;
106
+ clearingScore: number;
107
+ };
108
+
109
+ const ranked: Ranked[] = [];
110
+ for (const bid of open) {
111
+ const agent = input.agents.get(bid.agentId);
112
+ if (!agent || agent.status === 'terminated') {
113
+ bid.status = 'lost';
114
+ continue;
115
+ }
116
+ const forecast = this.forecaster.forecast({
117
+ agent,
118
+ record: input.record,
119
+ requested: bid.requested,
120
+ expectedValue: bid.expectedValue,
121
+ confidence: bid.confidence,
122
+ urgency: bid.urgency,
123
+ });
124
+ const priceScore = (bid.bidPrice ?? 0) / Math.max(1, bid.expectedValue);
125
+ const clearingScore =
126
+ forecast.netExpectedValue * (1 - this.config.priceWeight) +
127
+ priceScore * bid.expectedValue * this.config.priceWeight +
128
+ bid.urgency * 10 +
129
+ agent.reputation.score * 5;
130
+ ranked.push({ bid, agent, forecast, clearingScore });
131
+ }
132
+
133
+ ranked.sort((a, b) => b.clearingScore - a.clearingScore);
134
+
135
+ const awarded: MarketClearingResult['awarded'] = [];
136
+ const denied: MarketClearingResult['denied'] = [];
137
+ let opportunityCostTotal = 0;
138
+
139
+ for (const item of ranked) {
140
+ if (item.forecast.netExpectedValue < this.config.minNetExpectedValue) {
141
+ item.bid.status = 'lost';
142
+ denied.push({
143
+ bidId: item.bid.id,
144
+ agentId: item.bid.agentId,
145
+ reason: `Net expected value ${item.forecast.netExpectedValue.toFixed(2)} below minimum`,
146
+ forecast: item.forecast,
147
+ });
148
+ continue;
149
+ }
150
+
151
+ const allocation = this.allocator.allocate(input.record, {
152
+ agentId: item.bid.agentId,
153
+ requestedResources: item.bid.requested,
154
+ expectedUtility: item.forecast.expectedUtility,
155
+ confidence: item.bid.confidence,
156
+ urgency: item.bid.urgency,
157
+ reputationScore: item.agent.reputation.score,
158
+ });
159
+
160
+ if (!allocation.approved) {
161
+ item.bid.status = 'lost';
162
+ denied.push({
163
+ bidId: item.bid.id,
164
+ agentId: item.bid.agentId,
165
+ reason: allocation.reason,
166
+ forecast: item.forecast,
167
+ });
168
+ continue;
169
+ }
170
+
171
+ // Charge bid price from agent wallet if present
172
+ if (item.bid.bidPrice && item.bid.bidPrice > 0) {
173
+ const money = item.agent.wallet.moneyRemaining?.amount ?? 0;
174
+ if (money >= item.bid.bidPrice) {
175
+ item.agent.wallet = {
176
+ ...item.agent.wallet,
177
+ moneyRemaining: {
178
+ amount: money - item.bid.bidPrice,
179
+ currency:
180
+ item.agent.wallet.moneyRemaining?.currency ??
181
+ input.record.pool.moneyRemaining.currency,
182
+ },
183
+ };
184
+ input.record.pool.moneyRemaining.amount += item.bid.bidPrice;
185
+ }
186
+ }
187
+
188
+ item.bid.status = 'won';
189
+ opportunityCostTotal += item.forecast.opportunityCost;
190
+ awarded.push({
191
+ bidId: item.bid.id,
192
+ agentId: item.bid.agentId,
193
+ wallet: allocation.wallet,
194
+ forecast: item.forecast,
195
+ });
196
+ }
197
+
198
+ // Mark any remaining open as lost
199
+ for (const bid of this.openBids.values()) {
200
+ if (bid.status === 'open') {
201
+ bid.status = 'lost';
202
+ denied.push({
203
+ bidId: bid.id,
204
+ agentId: bid.agentId,
205
+ reason: 'Not selected in clearing round',
206
+ });
207
+ }
208
+ }
209
+
210
+ const result: MarketClearingResult = {
211
+ roundId,
212
+ awarded,
213
+ denied,
214
+ opportunityCostTotal,
215
+ };
216
+ this.history.push(result);
217
+ if (this.history.length > 100) this.history.splice(0, this.history.length - 100);
218
+
219
+ void this.events.emit(OrganismEventType.ORGANISM_MARKET_CLEARED, this.organismId, result);
220
+ return result;
221
+ }
222
+
223
+ /**
224
+ * Peer negotiation: transfer resources from one agent wallet to another
225
+ * when forecast justifies it and donor has balance.
226
+ */
227
+ negotiate(input: {
228
+ offer: NegotiationOffer;
229
+ from: RuntimeAgentRecord;
230
+ to: RuntimeAgentRecord;
231
+ record: OrganismRecord;
232
+ }): NegotiationResult {
233
+ const { offer, from, to, record } = input;
234
+ if (from.id === to.id) {
235
+ return { approved: false, reason: 'Cannot negotiate with self' };
236
+ }
237
+ if (from.status === 'terminated' || to.status === 'terminated') {
238
+ return { approved: false, reason: 'Agent not active' };
239
+ }
240
+
241
+ const tokens = offer.transfer.tokens ?? 0;
242
+ const money = offer.transfer.money ?? 0;
243
+ const fromTokens = from.wallet.tokensRemaining ?? 0;
244
+ const fromMoney = from.wallet.moneyRemaining?.amount ?? 0;
245
+
246
+ if (tokens > fromTokens || money > fromMoney) {
247
+ const result: NegotiationResult = {
248
+ approved: false,
249
+ reason: 'Donor insufficient resources',
250
+ };
251
+ void this.events.emit(OrganismEventType.ORGANISM_NEGOTIATION, this.organismId, {
252
+ ...offer,
253
+ ...result,
254
+ });
255
+ return result;
256
+ }
257
+
258
+ const forecast = this.forecaster.forecast({
259
+ agent: to,
260
+ record,
261
+ requested: offer.transfer,
262
+ expectedValue: offer.expectedValue ?? tokens * 0.001 + money * 2,
263
+ confidence: offer.confidence ?? 0.6,
264
+ });
265
+
266
+ if (forecast.netExpectedValue < this.config.minNetExpectedValue) {
267
+ const result: NegotiationResult = {
268
+ approved: false,
269
+ reason: `Forecast net value too low (${forecast.netExpectedValue.toFixed(2)})`,
270
+ };
271
+ void this.events.emit(OrganismEventType.ORGANISM_NEGOTIATION, this.organismId, {
272
+ ...offer,
273
+ ...result,
274
+ forecast,
275
+ });
276
+ return result;
277
+ }
278
+
279
+ const currency = from.wallet.moneyRemaining?.currency ?? record.pool.moneyRemaining.currency;
280
+
281
+ from.wallet = {
282
+ ...from.wallet,
283
+ tokensRemaining: fromTokens - tokens,
284
+ moneyRemaining: { amount: fromMoney - money, currency },
285
+ };
286
+ to.wallet = {
287
+ ...to.wallet,
288
+ tokensRemaining: (to.wallet.tokensRemaining ?? 0) + tokens,
289
+ moneyRemaining: {
290
+ amount: (to.wallet.moneyRemaining?.amount ?? 0) + money,
291
+ currency: to.wallet.moneyRemaining?.currency ?? currency,
292
+ },
293
+ };
294
+
295
+ const result: NegotiationResult = {
296
+ approved: true,
297
+ reason: 'Transfer approved',
298
+ transfer: offer.transfer,
299
+ fromWallet: from.wallet,
300
+ toWallet: to.wallet,
301
+ };
302
+ void this.events.emit(OrganismEventType.ORGANISM_NEGOTIATION, this.organismId, {
303
+ ...offer,
304
+ ...result,
305
+ forecast,
306
+ });
307
+ return result;
308
+ }
309
+
310
+ getHistory(limit = 20): MarketClearingResult[] {
311
+ return this.history.slice(-limit);
312
+ }
313
+
314
+ private expireBids(): void {
315
+ const now = this.now().getTime();
316
+ for (const bid of this.openBids.values()) {
317
+ if (bid.status === 'open' && bid.expiresAt && bid.expiresAt.getTime() < now) {
318
+ bid.status = 'expired';
319
+ }
320
+ }
321
+ }
322
+ }
323
+
324
+ export type { ResourceRequest, AgentResourceWallet };
@@ -0,0 +1,188 @@
1
+ import type {
2
+ AgentResourceWallet,
3
+ OrganismLimits,
4
+ OrganismRecord,
5
+ ResourceRequest,
6
+ RuntimeAgentRecord,
7
+ } from '../types/organism.types';
8
+ import { OrganismResourceDeniedError } from '../errors/organism.errors';
9
+ import { OrganismEventEmitter, OrganismEventType } from '../events/organism-events';
10
+
11
+ export interface AllocationRequest {
12
+ agentId: string;
13
+ requestedResources: ResourceRequest;
14
+ expectedUtility: number;
15
+ confidence: number;
16
+ urgency: number;
17
+ reputationScore?: number;
18
+ missionPriority?: number;
19
+ }
20
+
21
+ export interface AllocationResult {
22
+ approved: boolean;
23
+ wallet: AgentResourceWallet;
24
+ score: number;
25
+ reason: string;
26
+ }
27
+
28
+ /**
29
+ * Deterministic weighted resource allocator.
30
+ */
31
+ export class ResourceAllocator {
32
+ private spawnTimestamps: number[] = [];
33
+
34
+ constructor(
35
+ private readonly events: OrganismEventEmitter,
36
+ private readonly organismId: string,
37
+ private readonly now: () => Date = () => new Date()
38
+ ) {}
39
+
40
+ score(request: AllocationRequest): number {
41
+ const utility = clamp(request.expectedUtility, 0, 1);
42
+ const confidence = clamp(request.confidence, 0, 1);
43
+ const urgency = clamp(request.urgency, 0, 1);
44
+ const reputation = clamp(request.reputationScore ?? 0.5, 0, 1);
45
+ const priority = clamp(request.missionPriority ?? 0.5, 0, 1);
46
+ return utility * 0.3 + confidence * 0.2 + urgency * 0.2 + reputation * 0.2 + priority * 0.1;
47
+ }
48
+
49
+ canSpawn(record: OrganismRecord, liveAgentCount: number): { ok: boolean; reason: string } {
50
+ if (record.emergencyStopped) {
51
+ return { ok: false, reason: 'Emergency stop active' };
52
+ }
53
+ if (record.status === 'paused' || record.status === 'terminated') {
54
+ return { ok: false, reason: `Organism is ${record.status}` };
55
+ }
56
+ if (liveAgentCount >= record.limits.maxAgents) {
57
+ return { ok: false, reason: `maxAgents (${record.limits.maxAgents}) reached` };
58
+ }
59
+ const now = this.now().getTime();
60
+ this.spawnTimestamps = this.spawnTimestamps.filter((t) => now - t < 60_000);
61
+ if (this.spawnTimestamps.length >= record.limits.maxSpawnRatePerMinute) {
62
+ return {
63
+ ok: false,
64
+ reason: `maxSpawnRatePerMinute (${record.limits.maxSpawnRatePerMinute}) exceeded`,
65
+ };
66
+ }
67
+ if (record.pool.costSpentThisHour >= record.limits.maxTotalCostPerHour) {
68
+ return {
69
+ ok: false,
70
+ reason: `maxTotalCostPerHour ($${record.limits.maxTotalCostPerHour}) exceeded`,
71
+ };
72
+ }
73
+ return { ok: true, reason: 'ok' };
74
+ }
75
+
76
+ recordSpawn(): void {
77
+ this.spawnTimestamps.push(this.now().getTime());
78
+ }
79
+
80
+ allocate(record: OrganismRecord, request: AllocationRequest): AllocationResult {
81
+ const score = this.score(request);
82
+ const tokens = request.requestedResources.tokens ?? 0;
83
+ const money = request.requestedResources.money ?? 0;
84
+
85
+ if (tokens > record.pool.tokensRemaining) {
86
+ const result: AllocationResult = {
87
+ approved: false,
88
+ wallet: {},
89
+ score,
90
+ reason: `Insufficient tokens (need ${tokens}, have ${record.pool.tokensRemaining})`,
91
+ };
92
+ void this.events.emit(OrganismEventType.ORGANISM_RESOURCE_DENIED, this.organismId, {
93
+ agentId: request.agentId,
94
+ ...result,
95
+ });
96
+ return result;
97
+ }
98
+
99
+ if (money > record.pool.moneyRemaining.amount) {
100
+ const result: AllocationResult = {
101
+ approved: false,
102
+ wallet: {},
103
+ score,
104
+ reason: `Insufficient money (need ${money}, have ${record.pool.moneyRemaining.amount})`,
105
+ };
106
+ void this.events.emit(OrganismEventType.ORGANISM_RESOURCE_DENIED, this.organismId, {
107
+ agentId: request.agentId,
108
+ ...result,
109
+ });
110
+ return result;
111
+ }
112
+
113
+ if (score < 0.15 && (tokens > 0 || money > 0)) {
114
+ const result: AllocationResult = {
115
+ approved: false,
116
+ wallet: {},
117
+ score,
118
+ reason: `Allocation score too low (${score.toFixed(3)})`,
119
+ };
120
+ void this.events.emit(OrganismEventType.ORGANISM_RESOURCE_DENIED, this.organismId, {
121
+ agentId: request.agentId,
122
+ ...result,
123
+ });
124
+ return result;
125
+ }
126
+
127
+ record.pool.tokensRemaining -= tokens;
128
+ record.pool.moneyRemaining.amount -= money;
129
+
130
+ const wallet: AgentResourceWallet = {
131
+ tokensRemaining: tokens || undefined,
132
+ moneyRemaining:
133
+ money > 0 ? { amount: money, currency: record.pool.moneyRemaining.currency } : undefined,
134
+ computeUnitsRemaining: request.requestedResources.computeUnits,
135
+ toolCallsRemaining: request.requestedResources.toolCalls,
136
+ };
137
+
138
+ const result: AllocationResult = {
139
+ approved: true,
140
+ wallet,
141
+ score,
142
+ reason: 'Allocated',
143
+ };
144
+ void this.events.emit(OrganismEventType.ORGANISM_RESOURCE_ALLOCATED, this.organismId, {
145
+ agentId: request.agentId,
146
+ wallet,
147
+ score,
148
+ });
149
+ return result;
150
+ }
151
+
152
+ release(record: OrganismRecord, agent: RuntimeAgentRecord): void {
153
+ const tokens = agent.wallet.tokensRemaining ?? 0;
154
+ const money = agent.wallet.moneyRemaining?.amount ?? 0;
155
+ record.pool.tokensRemaining += tokens;
156
+ record.pool.moneyRemaining.amount += money;
157
+ agent.wallet = {
158
+ tokensRemaining: 0,
159
+ moneyRemaining: {
160
+ amount: 0,
161
+ currency: record.pool.moneyRemaining.currency,
162
+ },
163
+ };
164
+ }
165
+
166
+ assertWithinLimits(_limits: OrganismLimits): void {
167
+ // reserved for future hard asserts
168
+ }
169
+ }
170
+
171
+ function clamp(n: number, min: number, max: number): number {
172
+ return Math.max(min, Math.min(max, n));
173
+ }
174
+
175
+ export function createEmptyWallet(): AgentResourceWallet {
176
+ return {
177
+ tokensRemaining: 0,
178
+ moneyRemaining: { amount: 0, currency: 'USD' },
179
+ computeUnitsRemaining: 0,
180
+ toolCallsRemaining: 0,
181
+ };
182
+ }
183
+
184
+ export function throwIfDenied(result: AllocationResult): void {
185
+ if (!result.approved) {
186
+ throw new OrganismResourceDeniedError(result.reason);
187
+ }
188
+ }