@mindot/will 0.1.0
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.
- package/LICENSE +202 -0
- package/README.md +777 -0
- package/dist/index.d.ts +8144 -0
- package/dist/index.js +26231 -0
- package/dist/index.js.map +1 -0
- package/package.json +73 -0
- package/src/cognition/agency/access.grants.ts +59 -0
- package/src/cognition/agency/competence.codec.ts +89 -0
- package/src/cognition/agency/engines/action.selector.ts +462 -0
- package/src/cognition/agency/engines/affordance.synthesizer.ts +384 -0
- package/src/cognition/agency/engines/deliberation.engine.ts +241 -0
- package/src/cognition/agency/engines/instruction.intake.ts +54 -0
- package/src/cognition/agency/engines/motor.schema.executor.ts +532 -0
- package/src/cognition/agency/engines/reafference.engine.ts +241 -0
- package/src/cognition/agency/execution.primitives.ts +118 -0
- package/src/cognition/agency/index.ts +58 -0
- package/src/cognition/agency/proactive.communicator.ts +274 -0
- package/src/cognition/agency/reconcile.learning.ts +82 -0
- package/src/cognition/agency/schemas/external.ts +59 -0
- package/src/cognition/agency/schemas/innate.ts +111 -0
- package/src/cognition/agency/schemas/repertoire.ts +246 -0
- package/src/cognition/agency/selection.scoring.ts +191 -0
- package/src/cognition/agency/types.ts +165 -0
- package/src/cognition/bus.ts +391 -0
- package/src/cognition/completion.inbox.ts +86 -0
- package/src/cognition/config.mirror.entities.ts +475 -0
- package/src/cognition/conversation.memory.ts +81 -0
- package/src/cognition/event.log.ts +116 -0
- package/src/cognition/event.schemas.ts +568 -0
- package/src/cognition/faculties/aesthetic.evaluator.ts +313 -0
- package/src/cognition/faculties/affective.blender.ts +562 -0
- package/src/cognition/faculties/attachment.evaluator.ts +432 -0
- package/src/cognition/faculties/attention.allocator.ts +422 -0
- package/src/cognition/faculties/autobiographical.narrator.ts +300 -0
- package/src/cognition/faculties/bias.detector.ts +363 -0
- package/src/cognition/faculties/circadian.oscillator.ts +294 -0
- package/src/cognition/faculties/confidence.calibrator.ts +324 -0
- package/src/cognition/faculties/dream.simulator.ts +251 -0
- package/src/cognition/faculties/empathy.simulator.ts +253 -0
- package/src/cognition/faculties/energy.regulator.ts +320 -0
- package/src/cognition/faculties/episodic.consolidator.ts +728 -0
- package/src/cognition/faculties/executive.engine/commands.ts +459 -0
- package/src/cognition/faculties/executive.engine/config.ts +46 -0
- package/src/cognition/faculties/executive.engine/context.ts +605 -0
- package/src/cognition/faculties/executive.engine/deferred.effects.ts +104 -0
- package/src/cognition/faculties/executive.engine/deliberate.reasoning.ts +60 -0
- package/src/cognition/faculties/executive.engine/effort.gate.ts +114 -0
- package/src/cognition/faculties/executive.engine/engine.ts +1028 -0
- package/src/cognition/faculties/executive.engine/escalation.buffer.ts +90 -0
- package/src/cognition/faculties/executive.engine/facet.supervisor.ts +271 -0
- package/src/cognition/faculties/executive.engine/facet.ts +628 -0
- package/src/cognition/faculties/executive.engine/gating.ts +222 -0
- package/src/cognition/faculties/executive.engine/index.ts +6 -0
- package/src/cognition/faculties/executive.engine/messages.ts +102 -0
- package/src/cognition/faculties/executive.engine/parser.ts +380 -0
- package/src/cognition/faculties/executive.engine/prompt.factory.ts +1053 -0
- package/src/cognition/faculties/executive.engine/types.ts +323 -0
- package/src/cognition/faculties/exteroception.ts +338 -0
- package/src/cognition/faculties/forgetting.curve.ts +202 -0
- package/src/cognition/faculties/frustration.evaluator.ts +280 -0
- package/src/cognition/faculties/goal.manager.ts +1008 -0
- package/src/cognition/faculties/inhibition.controller.ts +351 -0
- package/src/cognition/faculties/interoception.ts +417 -0
- package/src/cognition/faculties/introspection.engine.ts +246 -0
- package/src/cognition/faculties/known.entity.tracker.ts +439 -0
- package/src/cognition/faculties/loss.evaluator.ts +224 -0
- package/src/cognition/faculties/moral.evaluator.ts +437 -0
- package/src/cognition/faculties/novelty.detector.ts +254 -0
- package/src/cognition/faculties/persona.consolidator.ts +845 -0
- package/src/cognition/faculties/planning.engine/engine.ts +859 -0
- package/src/cognition/faculties/planning.engine/plan.frontier.ts +83 -0
- package/src/cognition/faculties/planning.engine/plan.store.ts +174 -0
- package/src/cognition/faculties/planning.engine/plan.supervision.ts +494 -0
- package/src/cognition/faculties/planning.engine/types.ts +143 -0
- package/src/cognition/faculties/reputation.tracker.ts +294 -0
- package/src/cognition/faculties/reward.evaluator.ts +304 -0
- package/src/cognition/faculties/self.model.updater.ts +677 -0
- package/src/cognition/faculties/semantic.engine/clustering.ts +673 -0
- package/src/cognition/faculties/semantic.engine/index.ts +6 -0
- package/src/cognition/faculties/semantic.engine/integrator.ts +750 -0
- package/src/cognition/faculties/semantic.engine/types.ts +53 -0
- package/src/cognition/faculties/sleep.pressure.regulator.ts +262 -0
- package/src/cognition/faculties/social.perception.ts +333 -0
- package/src/cognition/faculties/spaced.repetition.ts +605 -0
- package/src/cognition/faculties/stress.regulator.ts +375 -0
- package/src/cognition/faculties/task.switcher.ts +239 -0
- package/src/cognition/faculties/theory.of.mind.ts +333 -0
- package/src/cognition/faculties/threat.evaluator.ts +312 -0
- package/src/cognition/faculties/working.memory.ts +406 -0
- package/src/cognition/generative.model.ts +344 -0
- package/src/cognition/heartbeat.ts +70 -0
- package/src/cognition/index.ts +291 -0
- package/src/cognition/instruction.handler.ts +146 -0
- package/src/cognition/memory/index.ts +35 -0
- package/src/cognition/memory/vector.adapter.ts +316 -0
- package/src/cognition/memory/vector.content.ts +47 -0
- package/src/cognition/memory/vector.embedder.ts +212 -0
- package/src/cognition/memory/vector.index.ts +459 -0
- package/src/cognition/memory/vector.types.ts +65 -0
- package/src/cognition/orchestrator.ts +142 -0
- package/src/cognition/persona.prior.ts +265 -0
- package/src/cognition/schema.registry.ts +145 -0
- package/src/cognition/senses/audition.engine/engine.ts +976 -0
- package/src/cognition/senses/audition.engine/salience.ts +59 -0
- package/src/cognition/senses/base.sense.engine.ts +117 -0
- package/src/cognition/senses/gustation.engine.ts +24 -0
- package/src/cognition/senses/index.ts +172 -0
- package/src/cognition/senses/olfaction.engine.ts +24 -0
- package/src/cognition/senses/somatosensation.engine.ts +23 -0
- package/src/cognition/senses/vision.engine.ts +23 -0
- package/src/cognition/types.ts +63 -0
- package/src/cognition/utilities/token.tracker.ts +492 -0
- package/src/core/abstracts.ts +60 -0
- package/src/core/async.engine.ts +539 -0
- package/src/core/clock.ts +196 -0
- package/src/core/completion.recorder.ts +122 -0
- package/src/core/conflict.detector.ts +140 -0
- package/src/core/distributed.ts +626 -0
- package/src/core/event.bus.ts +292 -0
- package/src/core/inbound.recorder.ts +100 -0
- package/src/core/index.ts +167 -0
- package/src/core/logger.ts +85 -0
- package/src/core/metrics.ts +107 -0
- package/src/core/orchestrator.ts +625 -0
- package/src/core/replay.ts +589 -0
- package/src/core/scenario.ts +120 -0
- package/src/core/serialization.ts +448 -0
- package/src/core/simulation.ts +197 -0
- package/src/core/snapshot.manager.ts +293 -0
- package/src/core/state.manager.ts +283 -0
- package/src/core/types.ts +274 -0
- package/src/core/utils.ts +82 -0
- package/src/core/wall.clock.ts +23 -0
- package/src/extensions/livestream.ext.ts +570 -0
- package/src/extensions/time.ext.ts +339 -0
- package/src/index.ts +91 -0
- package/src/llm/gate.ts +140 -0
- package/src/llm/index.ts +716 -0
- package/src/llm/summarizer.ts +170 -0
- package/src/llm/wire.contracts.ts +67 -0
- package/src/pma/eval.ts +651 -0
- package/src/pma/index.ts +1082 -0
- package/src/profiles/built-in.ts +7 -0
- package/src/profiles/companion.ts +28 -0
- package/src/profiles/company-brain.ts +54 -0
- package/src/profiles/customer-service.ts +30 -0
- package/src/profiles/game-npc.ts +20 -0
- package/src/profiles/index.ts +42 -0
- package/src/profiles/smart-home.ts +34 -0
- package/src/runners/coherence.runner.ts +49 -0
- package/src/runners/outreach.runner.ts +158 -0
- package/src/runners/social.runner.ts +182 -0
- package/src/runners/thin-shim.runner.ts +195 -0
- package/src/sdk/will.ts +334 -0
- package/src/stem/assembly.audit.ts +88 -0
- package/src/stem/distribution.ts +203 -0
- package/src/stem/guards/identity.coherence.ts +152 -0
- package/src/stem/guards/identity.guard.ts +227 -0
- package/src/stem/index.ts +1145 -0
- package/src/stem/mind.ts +1090 -0
- package/src/stem/tracts/ack.reconciler.ts +55 -0
- package/src/stem/tracts/biography.writer.ts +190 -0
- package/src/stem/tracts/effector.controller.ts +143 -0
- package/src/stem/tracts/health.reporter.ts +113 -0
- package/src/stem/tracts/inbound.queue.ts +54 -0
- package/src/stem/tracts/outbox.controller.ts +167 -0
- package/src/stem/tracts/outbox.writer.ts +133 -0
- package/src/stem/tracts/pma.controller.ts +109 -0
- package/src/stem/tracts/replay.controller.ts +177 -0
- package/src/stem/tracts/sensory.controller.ts +168 -0
- package/src/stem/tracts/session.logger.ts +157 -0
- package/src/stem/tracts/transport/index.ts +38 -0
- package/src/stem/tracts/transport/loopback.transport.ts +86 -0
- package/src/stem/tracts/transport/socketio.transport.ts +200 -0
- package/src/stem/tracts/transport/stream.transport.ts +152 -0
- package/src/stem/tracts/transport/types.ts +193 -0
- package/src/stem/tracts/transport.controller.ts +380 -0
- package/src/types.ts +116 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/core/clock.ts
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Core simulation clock — completely generic.
|
|
7
|
+
* No time-of-day, calendar, or domain-specific logic.
|
|
8
|
+
* Time is just milliseconds since epoch.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { Duration, Tick, Timestamp } from '#core/types'
|
|
12
|
+
import { wallClock } from '#core/wall.clock'
|
|
13
|
+
|
|
14
|
+
export interface SimulationClock {
|
|
15
|
+
readonly now: Timestamp
|
|
16
|
+
readonly currentTick: Tick
|
|
17
|
+
readonly delta: Duration
|
|
18
|
+
readonly isRunning: boolean
|
|
19
|
+
readonly multiplier: number
|
|
20
|
+
|
|
21
|
+
// Driven externally by the Orchestrator
|
|
22
|
+
tick(): void
|
|
23
|
+
|
|
24
|
+
// Lifecycle
|
|
25
|
+
pause(): void
|
|
26
|
+
resume(): void
|
|
27
|
+
reset(): void
|
|
28
|
+
|
|
29
|
+
// Time manipulation
|
|
30
|
+
setTime( time: Timestamp ): void
|
|
31
|
+
setTick( tick: Tick ): void
|
|
32
|
+
setMultiplier( newMultiplier: number ): void
|
|
33
|
+
|
|
34
|
+
// Helpers
|
|
35
|
+
toSimMs( wallMs: number ): number
|
|
36
|
+
toWallMs( simMs: number ): number
|
|
37
|
+
simSleep( simMs: number ): Promise<void>
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ClockConfig {
|
|
41
|
+
startTime?: Timestamp
|
|
42
|
+
startTick?: Tick
|
|
43
|
+
timeScale?: number // 1.0 = real-time, 2.0 = 2× real-time
|
|
44
|
+
/** When set, tick() uses this fixed duration (ms) instead of wall time. */
|
|
45
|
+
fixedDeltaMs?: number
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export class DefaultSimulationClock implements SimulationClock {
|
|
49
|
+
private _now: Timestamp
|
|
50
|
+
private _tick: Tick
|
|
51
|
+
private _delta: Duration = 0
|
|
52
|
+
private _lastTick: Timestamp
|
|
53
|
+
private _isRunning = true
|
|
54
|
+
private _timeScale: number
|
|
55
|
+
private _pausedAt: Timestamp | null = null
|
|
56
|
+
private _fixedDeltaMs: number | null = null
|
|
57
|
+
|
|
58
|
+
// Anchors for accurate now() under dynamic multiplier
|
|
59
|
+
private _startWallMs: Timestamp
|
|
60
|
+
private _startSimMs: Timestamp
|
|
61
|
+
|
|
62
|
+
constructor( config: ClockConfig = {} ){
|
|
63
|
+
this._now = config.startTime ?? wallClock()
|
|
64
|
+
this._tick = config.startTick ?? 0
|
|
65
|
+
this._lastTick = this._now
|
|
66
|
+
this._timeScale = config.timeScale ?? 1.0
|
|
67
|
+
this._fixedDeltaMs = config.fixedDeltaMs ?? null
|
|
68
|
+
|
|
69
|
+
this._startWallMs = wallClock()
|
|
70
|
+
this._startSimMs = this._now
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ── Getters ─────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
get now(): Timestamp {
|
|
76
|
+
// Deterministic mode: time only advances inside tick(); never read wall
|
|
77
|
+
// time, so SimulationState.time reproduces exactly on replay (R2).
|
|
78
|
+
if( this._fixedDeltaMs !== null ) return this._now
|
|
79
|
+
|
|
80
|
+
if( !this._isRunning ) return this._now
|
|
81
|
+
|
|
82
|
+
const wallElapsed = wallClock() - this._startWallMs
|
|
83
|
+
return this._startSimMs + wallElapsed * this._timeScale
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
get currentTick(): Tick { return this._tick }
|
|
87
|
+
get delta(): Duration { return this._delta }
|
|
88
|
+
get isRunning(): boolean { return this._isRunning }
|
|
89
|
+
get multiplier(): number { return this._timeScale }
|
|
90
|
+
|
|
91
|
+
// ── Core tick ───────────────────────────────────────────
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The Orchestrator calls this once per tick as the single
|
|
95
|
+
* source of tick advancement.
|
|
96
|
+
*/
|
|
97
|
+
tick(): void {
|
|
98
|
+
if( !this._isRunning ) return
|
|
99
|
+
|
|
100
|
+
// Deterministic mode: advance by a fixed sim delta, never reading wall
|
|
101
|
+
// time. This is what makes the tick→time mapping reproducible on replay (R2).
|
|
102
|
+
if( this._fixedDeltaMs !== null ){
|
|
103
|
+
this._delta = this._fixedDeltaMs * this._timeScale
|
|
104
|
+
this._now += this._delta
|
|
105
|
+
this._tick += 1
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const
|
|
110
|
+
realNow = wallClock(),
|
|
111
|
+
realDelta = realNow - this._lastTick,
|
|
112
|
+
scaledDelta = realDelta * this._timeScale
|
|
113
|
+
|
|
114
|
+
this._delta = scaledDelta
|
|
115
|
+
this._now = this.now
|
|
116
|
+
this._tick += 1
|
|
117
|
+
this._lastTick = realNow
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ── Lifecycle ───────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
pause(): void {
|
|
123
|
+
if( !this._isRunning ) return
|
|
124
|
+
|
|
125
|
+
this._isRunning = false
|
|
126
|
+
this._pausedAt = wallClock()
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
resume(): void {
|
|
130
|
+
if( this._isRunning ) return
|
|
131
|
+
|
|
132
|
+
this._isRunning = true
|
|
133
|
+
|
|
134
|
+
if( this._pausedAt !== null ){
|
|
135
|
+
const pauseDuration = wallClock() - this._pausedAt
|
|
136
|
+
|
|
137
|
+
// Shift anchors to compensate for pause — maintains continuous time
|
|
138
|
+
this._startWallMs += pauseDuration
|
|
139
|
+
this._lastTick = wallClock() - ( this._pausedAt - this._lastTick )
|
|
140
|
+
this._pausedAt = null
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
reset(): void {
|
|
145
|
+
const now = wallClock()
|
|
146
|
+
|
|
147
|
+
this._now = now
|
|
148
|
+
this._tick = 0
|
|
149
|
+
this._delta = 0
|
|
150
|
+
this._lastTick = now
|
|
151
|
+
this._isRunning = true
|
|
152
|
+
this._pausedAt = null
|
|
153
|
+
this._startWallMs = now
|
|
154
|
+
this._startSimMs = now
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ── Time manipulation ───────────────────────────────────
|
|
158
|
+
|
|
159
|
+
setTime( time: Timestamp ): void {
|
|
160
|
+
this._now = time
|
|
161
|
+
this._startSimMs = time
|
|
162
|
+
this._startWallMs = wallClock()
|
|
163
|
+
this._lastTick = this._startWallMs
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
setTick( tick: Tick ): void {
|
|
167
|
+
this._tick = tick
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
setMultiplier( newMultiplier: number ): void {
|
|
171
|
+
if( newMultiplier <= 0 )
|
|
172
|
+
throw new Error('Multiplier must be positive')
|
|
173
|
+
|
|
174
|
+
// Re-anchor current sim time to prevent jumps
|
|
175
|
+
const simNow = this.now
|
|
176
|
+
|
|
177
|
+
this._startSimMs = simNow
|
|
178
|
+
this._startWallMs = wallClock()
|
|
179
|
+
this._timeScale = newMultiplier
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ── Helpers ─────────────────────────────────────────────
|
|
183
|
+
|
|
184
|
+
toSimMs( wallMs: number ): number {
|
|
185
|
+
return wallMs * this._timeScale
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
toWallMs( simMs: number ): number {
|
|
189
|
+
return simMs / this._timeScale
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async simSleep( simMs: number ): Promise<void> {
|
|
193
|
+
const wallMs = this.toWallMs( simMs )
|
|
194
|
+
return new Promise( resolve => setTimeout( resolve, wallMs ) )
|
|
195
|
+
}
|
|
196
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/core/completion.recorder.ts
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* LLM completion record/replay seam.
|
|
7
|
+
*
|
|
8
|
+
* The LLM is an external, non-deterministic oracle: to re-run a session
|
|
9
|
+
* deterministically (REORIENT R2) the recorder captures each completion's full
|
|
10
|
+
* input (model, params, prompt) and output, and on replay a *source* re-feeds
|
|
11
|
+
* those recorded completions instead of re-calling the model. This module is
|
|
12
|
+
* both halves of that seam — the capture sink (R2-b) and the re-feed source
|
|
13
|
+
* (R2-c). Both registries are keyed by willId so each Will routes to its own
|
|
14
|
+
* recorder/source (unlike the process-global token tracker — see REORIENT R4).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { Tick, Timestamp } from '#core/types'
|
|
18
|
+
|
|
19
|
+
export interface LLMCompletionRecord {
|
|
20
|
+
tick: Tick
|
|
21
|
+
willId: string
|
|
22
|
+
provider: string
|
|
23
|
+
model: string
|
|
24
|
+
maxOutputTokens: number
|
|
25
|
+
systemPrompt: string
|
|
26
|
+
userMessage: string
|
|
27
|
+
text: string
|
|
28
|
+
inputTok: number
|
|
29
|
+
outputTok: number
|
|
30
|
+
/** True when produced by the deterministic mock path (no API call). */
|
|
31
|
+
mock: boolean
|
|
32
|
+
latencyMs: number
|
|
33
|
+
timestamp: Timestamp
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface LLMCompletionSink {
|
|
37
|
+
recordCompletion( record: LLMCompletionRecord ): void
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const _sinks = new Map<string, LLMCompletionSink>()
|
|
41
|
+
|
|
42
|
+
export function setCompletionRecorder( willId: string, sink: LLMCompletionSink ): void {
|
|
43
|
+
_sinks.set( willId, sink )
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function clearCompletionRecorder( willId: string ): void {
|
|
47
|
+
_sinks.delete( willId )
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function getCompletionRecorder( willId: string ): LLMCompletionSink | undefined {
|
|
51
|
+
return _sinks.get( willId )
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ── Replay re-feed source (R2-c) ─────────────────────────────
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Supplies recorded completions back to the LLM director during replay. A
|
|
58
|
+
* registered source short-circuits the live API call: the director returns the
|
|
59
|
+
* recorded output instead of re-invoking the non-deterministic model. Returning
|
|
60
|
+
* `undefined` means "no recorded completion — fall through to a live call"; a
|
|
61
|
+
* strict source (see RecordedCompletionSource) instead throws on a miss so a
|
|
62
|
+
* replay can never silently diverge.
|
|
63
|
+
*/
|
|
64
|
+
export interface LLMCompletionSource {
|
|
65
|
+
nextCompletion(
|
|
66
|
+
tick: number,
|
|
67
|
+
systemPrompt: string,
|
|
68
|
+
userMessage: string,
|
|
69
|
+
): LLMCompletionRecord | undefined
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Strict re-feed source backed by an array of recorded completions. Matches by
|
|
74
|
+
* tick, then by BYTE-IDENTICAL PROMPT within the tick — order-independent.
|
|
75
|
+
*
|
|
76
|
+
* Why prompt-keyed, not FIFO: concurrent LLM chains (the master executive and
|
|
77
|
+
* a deliberation facet) can ISSUE their same-tick calls in either order — that
|
|
78
|
+
* interleaving is scheduler timing, not seed-pinned state, so it may flip
|
|
79
|
+
* between the recording run and the replay run. Positional matching mispairs
|
|
80
|
+
* at the first flip and reports a phantom "prompt diverged". Keying each call
|
|
81
|
+
* to its own record by exact prompt makes the pairing insensitive to issue
|
|
82
|
+
* order while remaining strict: the prompts themselves are pure functions of
|
|
83
|
+
* deterministic state, so a real divergence still has no matching record and
|
|
84
|
+
* throws. Identical duplicate prompts within a tick consume FIFO.
|
|
85
|
+
*/
|
|
86
|
+
export class RecordedCompletionSource implements LLMCompletionSource {
|
|
87
|
+
private _byTick = new Map<number, LLMCompletionRecord[]>()
|
|
88
|
+
|
|
89
|
+
constructor( completions: readonly LLMCompletionRecord[] ){
|
|
90
|
+
for( const c of completions ){
|
|
91
|
+
const queue = this._byTick.get( c.tick )
|
|
92
|
+
if( queue ) queue.push( c )
|
|
93
|
+
else this._byTick.set( c.tick, [ c ] )
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
nextCompletion( tick: number, systemPrompt: string, userMessage: string ): LLMCompletionRecord {
|
|
98
|
+
const queue = this._byTick.get( tick )
|
|
99
|
+
if( !queue || queue.length === 0 )
|
|
100
|
+
throw new Error( `replay: no recorded LLM completion for tick ${tick} (recording is incomplete or call timing diverged)` )
|
|
101
|
+
|
|
102
|
+
const idx = queue.findIndex( r => r.systemPrompt === systemPrompt && r.userMessage === userMessage )
|
|
103
|
+
if( idx === -1 )
|
|
104
|
+
throw new Error( `replay: LLM prompt diverged at tick ${tick} — no recorded completion matches this call (upstream non-determinism)` )
|
|
105
|
+
|
|
106
|
+
return queue.splice( idx, 1 )[ 0 ]!
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const _sources = new Map<string, LLMCompletionSource>()
|
|
111
|
+
|
|
112
|
+
export function setCompletionSource( willId: string, source: LLMCompletionSource ): void {
|
|
113
|
+
_sources.set( willId, source )
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function clearCompletionSource( willId: string ): void {
|
|
117
|
+
_sources.delete( willId )
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function getCompletionSource( willId: string ): LLMCompletionSource | undefined {
|
|
121
|
+
return _sources.get( willId )
|
|
122
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/core/conflict.detector.ts
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Validates async engine reasoning footprints against current state.
|
|
7
|
+
* Detects read-after-write and write-after-write conflicts that occur
|
|
8
|
+
* when an engine reasons on a stale snapshot while the world advances.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
ReasoningFootprint,
|
|
13
|
+
ConflictReport,
|
|
14
|
+
ConflictResolution,
|
|
15
|
+
ConflictStrategy,
|
|
16
|
+
ReadonlySimulationState,
|
|
17
|
+
StateCommands,
|
|
18
|
+
} from '#core/types'
|
|
19
|
+
|
|
20
|
+
export class ConflictDetector {
|
|
21
|
+
/**
|
|
22
|
+
* Check a reasoning footprint against the current state snapshot.
|
|
23
|
+
* Returns a detailed conflict report.
|
|
24
|
+
*/
|
|
25
|
+
detect(
|
|
26
|
+
footprint: ReasoningFootprint,
|
|
27
|
+
currentState: ReadonlySimulationState
|
|
28
|
+
): ConflictReport {
|
|
29
|
+
const
|
|
30
|
+
readConflicts: string[] = [],
|
|
31
|
+
writeConflicts: string[] = []
|
|
32
|
+
|
|
33
|
+
// Read conflicts: entities the engine observed have been modified
|
|
34
|
+
// since the footprint was taken. Compare the entity's last-write tick
|
|
35
|
+
// against `tickObserved` (both are sim ticks). Entities without a tick
|
|
36
|
+
// stamp (e.g. restored from a snapshot) cannot be proven stale → skip.
|
|
37
|
+
for( const id of footprint.entitiesRead ){
|
|
38
|
+
const entity = currentState.entities.get( id )
|
|
39
|
+
const modifiedAtTick = entity?.updatedAtTick
|
|
40
|
+
if( entity && modifiedAtTick !== undefined && modifiedAtTick > footprint.tickObserved )
|
|
41
|
+
readConflicts.push(`Entity '${id}' modified at tick ${modifiedAtTick} (observed at tick ${footprint.tickObserved})`)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Write conflicts: entities the engine wants to modify were modified by others
|
|
45
|
+
for( const id of footprint.entitiesModified ){
|
|
46
|
+
const entity = currentState.entities.get( id )
|
|
47
|
+
const modifiedAtTick = entity?.updatedAtTick
|
|
48
|
+
if( entity && modifiedAtTick !== undefined && modifiedAtTick > footprint.tickObserved )
|
|
49
|
+
writeConflicts.push(`Write conflict on '${id}': modified at tick ${modifiedAtTick} (observed at tick ${footprint.tickObserved})`)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
hasConflicts: readConflicts.length > 0 || writeConflicts.length > 0,
|
|
54
|
+
readConflicts,
|
|
55
|
+
writeConflicts,
|
|
56
|
+
detectedAtTick: currentState.tick,
|
|
57
|
+
footprint,
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Resolve conflicts using the specified strategy.
|
|
63
|
+
*/
|
|
64
|
+
resolve(
|
|
65
|
+
report: ConflictReport,
|
|
66
|
+
strategy: ConflictStrategy = 'REJECT'
|
|
67
|
+
): ConflictResolution {
|
|
68
|
+
switch( strategy ){
|
|
69
|
+
case 'FORCE':
|
|
70
|
+
return {
|
|
71
|
+
strategy: 'FORCE',
|
|
72
|
+
resolvedCommands: report.footprint.intendedCommands,
|
|
73
|
+
shouldRerun: false,
|
|
74
|
+
reason: 'FORCE: applied despite conflicts',
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
case 'MERGE':
|
|
78
|
+
return this._merge( report )
|
|
79
|
+
|
|
80
|
+
case 'REJECT':
|
|
81
|
+
default:
|
|
82
|
+
return {
|
|
83
|
+
strategy: 'REJECT',
|
|
84
|
+
resolvedCommands: null,
|
|
85
|
+
shouldRerun: true,
|
|
86
|
+
reason: `REJECT: ${report.readConflicts.length} read conflicts, ${report.writeConflicts.length} write conflicts`,
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Attempt to apply a non-conflicting subset of commands.
|
|
93
|
+
* Only includes entity sets and metric sets that don't touch conflicted entities.
|
|
94
|
+
*/
|
|
95
|
+
private _merge( report: ConflictReport ): ConflictResolution {
|
|
96
|
+
if( !report.hasConflicts )
|
|
97
|
+
return {
|
|
98
|
+
strategy: 'MERGE',
|
|
99
|
+
resolvedCommands: report.footprint.intendedCommands,
|
|
100
|
+
shouldRerun: false,
|
|
101
|
+
reason: 'MERGE: no conflicts detected, full commit',
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const
|
|
105
|
+
footprint = report.footprint,
|
|
106
|
+
original = footprint.intendedCommands,
|
|
107
|
+
conflictedIds = new Set<string>()
|
|
108
|
+
|
|
109
|
+
// Collect all conflicted entity IDs from write conflict messages
|
|
110
|
+
for( const msg of report.writeConflicts ){
|
|
111
|
+
const match = msg.match( /Write conflict on '([^']+)'/ )
|
|
112
|
+
match && conflictedIds.add( match[1]! )
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const merged: StateCommands = {}
|
|
116
|
+
|
|
117
|
+
// Only include entity sets that don't touch conflicted entities
|
|
118
|
+
if( original.set )
|
|
119
|
+
merged.set = original.set.filter( e => !conflictedIds.has( e.id ) )
|
|
120
|
+
|
|
121
|
+
// Only include deletes that don't touch conflicted entities
|
|
122
|
+
if( original.delete )
|
|
123
|
+
merged.delete = original.delete.filter( id => !conflictedIds.has( id ) )
|
|
124
|
+
|
|
125
|
+
// Metrics are always safe to apply (no entity-level conflicts)
|
|
126
|
+
if( original.metrics )
|
|
127
|
+
merged.metrics = [ ...original.metrics ]
|
|
128
|
+
|
|
129
|
+
const
|
|
130
|
+
appliedCount = ( merged.set?.length ?? 0 ) + ( merged.delete?.length ?? 0 ),
|
|
131
|
+
rejectedCount = conflictedIds.size
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
strategy: 'MERGE',
|
|
135
|
+
resolvedCommands: merged,
|
|
136
|
+
shouldRerun: rejectedCount > 0,
|
|
137
|
+
reason: `MERGE: applied ${appliedCount} operations, rejected ${rejectedCount} conflicts`,
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|