@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.
Files changed (178) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +777 -0
  3. package/dist/index.d.ts +8144 -0
  4. package/dist/index.js +26231 -0
  5. package/dist/index.js.map +1 -0
  6. package/package.json +73 -0
  7. package/src/cognition/agency/access.grants.ts +59 -0
  8. package/src/cognition/agency/competence.codec.ts +89 -0
  9. package/src/cognition/agency/engines/action.selector.ts +462 -0
  10. package/src/cognition/agency/engines/affordance.synthesizer.ts +384 -0
  11. package/src/cognition/agency/engines/deliberation.engine.ts +241 -0
  12. package/src/cognition/agency/engines/instruction.intake.ts +54 -0
  13. package/src/cognition/agency/engines/motor.schema.executor.ts +532 -0
  14. package/src/cognition/agency/engines/reafference.engine.ts +241 -0
  15. package/src/cognition/agency/execution.primitives.ts +118 -0
  16. package/src/cognition/agency/index.ts +58 -0
  17. package/src/cognition/agency/proactive.communicator.ts +274 -0
  18. package/src/cognition/agency/reconcile.learning.ts +82 -0
  19. package/src/cognition/agency/schemas/external.ts +59 -0
  20. package/src/cognition/agency/schemas/innate.ts +111 -0
  21. package/src/cognition/agency/schemas/repertoire.ts +246 -0
  22. package/src/cognition/agency/selection.scoring.ts +191 -0
  23. package/src/cognition/agency/types.ts +165 -0
  24. package/src/cognition/bus.ts +391 -0
  25. package/src/cognition/completion.inbox.ts +86 -0
  26. package/src/cognition/config.mirror.entities.ts +475 -0
  27. package/src/cognition/conversation.memory.ts +81 -0
  28. package/src/cognition/event.log.ts +116 -0
  29. package/src/cognition/event.schemas.ts +568 -0
  30. package/src/cognition/faculties/aesthetic.evaluator.ts +313 -0
  31. package/src/cognition/faculties/affective.blender.ts +562 -0
  32. package/src/cognition/faculties/attachment.evaluator.ts +432 -0
  33. package/src/cognition/faculties/attention.allocator.ts +422 -0
  34. package/src/cognition/faculties/autobiographical.narrator.ts +300 -0
  35. package/src/cognition/faculties/bias.detector.ts +363 -0
  36. package/src/cognition/faculties/circadian.oscillator.ts +294 -0
  37. package/src/cognition/faculties/confidence.calibrator.ts +324 -0
  38. package/src/cognition/faculties/dream.simulator.ts +251 -0
  39. package/src/cognition/faculties/empathy.simulator.ts +253 -0
  40. package/src/cognition/faculties/energy.regulator.ts +320 -0
  41. package/src/cognition/faculties/episodic.consolidator.ts +728 -0
  42. package/src/cognition/faculties/executive.engine/commands.ts +459 -0
  43. package/src/cognition/faculties/executive.engine/config.ts +46 -0
  44. package/src/cognition/faculties/executive.engine/context.ts +605 -0
  45. package/src/cognition/faculties/executive.engine/deferred.effects.ts +104 -0
  46. package/src/cognition/faculties/executive.engine/deliberate.reasoning.ts +60 -0
  47. package/src/cognition/faculties/executive.engine/effort.gate.ts +114 -0
  48. package/src/cognition/faculties/executive.engine/engine.ts +1028 -0
  49. package/src/cognition/faculties/executive.engine/escalation.buffer.ts +90 -0
  50. package/src/cognition/faculties/executive.engine/facet.supervisor.ts +271 -0
  51. package/src/cognition/faculties/executive.engine/facet.ts +628 -0
  52. package/src/cognition/faculties/executive.engine/gating.ts +222 -0
  53. package/src/cognition/faculties/executive.engine/index.ts +6 -0
  54. package/src/cognition/faculties/executive.engine/messages.ts +102 -0
  55. package/src/cognition/faculties/executive.engine/parser.ts +380 -0
  56. package/src/cognition/faculties/executive.engine/prompt.factory.ts +1053 -0
  57. package/src/cognition/faculties/executive.engine/types.ts +323 -0
  58. package/src/cognition/faculties/exteroception.ts +338 -0
  59. package/src/cognition/faculties/forgetting.curve.ts +202 -0
  60. package/src/cognition/faculties/frustration.evaluator.ts +280 -0
  61. package/src/cognition/faculties/goal.manager.ts +1008 -0
  62. package/src/cognition/faculties/inhibition.controller.ts +351 -0
  63. package/src/cognition/faculties/interoception.ts +417 -0
  64. package/src/cognition/faculties/introspection.engine.ts +246 -0
  65. package/src/cognition/faculties/known.entity.tracker.ts +439 -0
  66. package/src/cognition/faculties/loss.evaluator.ts +224 -0
  67. package/src/cognition/faculties/moral.evaluator.ts +437 -0
  68. package/src/cognition/faculties/novelty.detector.ts +254 -0
  69. package/src/cognition/faculties/persona.consolidator.ts +845 -0
  70. package/src/cognition/faculties/planning.engine/engine.ts +859 -0
  71. package/src/cognition/faculties/planning.engine/plan.frontier.ts +83 -0
  72. package/src/cognition/faculties/planning.engine/plan.store.ts +174 -0
  73. package/src/cognition/faculties/planning.engine/plan.supervision.ts +494 -0
  74. package/src/cognition/faculties/planning.engine/types.ts +143 -0
  75. package/src/cognition/faculties/reputation.tracker.ts +294 -0
  76. package/src/cognition/faculties/reward.evaluator.ts +304 -0
  77. package/src/cognition/faculties/self.model.updater.ts +677 -0
  78. package/src/cognition/faculties/semantic.engine/clustering.ts +673 -0
  79. package/src/cognition/faculties/semantic.engine/index.ts +6 -0
  80. package/src/cognition/faculties/semantic.engine/integrator.ts +750 -0
  81. package/src/cognition/faculties/semantic.engine/types.ts +53 -0
  82. package/src/cognition/faculties/sleep.pressure.regulator.ts +262 -0
  83. package/src/cognition/faculties/social.perception.ts +333 -0
  84. package/src/cognition/faculties/spaced.repetition.ts +605 -0
  85. package/src/cognition/faculties/stress.regulator.ts +375 -0
  86. package/src/cognition/faculties/task.switcher.ts +239 -0
  87. package/src/cognition/faculties/theory.of.mind.ts +333 -0
  88. package/src/cognition/faculties/threat.evaluator.ts +312 -0
  89. package/src/cognition/faculties/working.memory.ts +406 -0
  90. package/src/cognition/generative.model.ts +344 -0
  91. package/src/cognition/heartbeat.ts +70 -0
  92. package/src/cognition/index.ts +291 -0
  93. package/src/cognition/instruction.handler.ts +146 -0
  94. package/src/cognition/memory/index.ts +35 -0
  95. package/src/cognition/memory/vector.adapter.ts +316 -0
  96. package/src/cognition/memory/vector.content.ts +47 -0
  97. package/src/cognition/memory/vector.embedder.ts +212 -0
  98. package/src/cognition/memory/vector.index.ts +459 -0
  99. package/src/cognition/memory/vector.types.ts +65 -0
  100. package/src/cognition/orchestrator.ts +142 -0
  101. package/src/cognition/persona.prior.ts +265 -0
  102. package/src/cognition/schema.registry.ts +145 -0
  103. package/src/cognition/senses/audition.engine/engine.ts +976 -0
  104. package/src/cognition/senses/audition.engine/salience.ts +59 -0
  105. package/src/cognition/senses/base.sense.engine.ts +117 -0
  106. package/src/cognition/senses/gustation.engine.ts +24 -0
  107. package/src/cognition/senses/index.ts +172 -0
  108. package/src/cognition/senses/olfaction.engine.ts +24 -0
  109. package/src/cognition/senses/somatosensation.engine.ts +23 -0
  110. package/src/cognition/senses/vision.engine.ts +23 -0
  111. package/src/cognition/types.ts +63 -0
  112. package/src/cognition/utilities/token.tracker.ts +492 -0
  113. package/src/core/abstracts.ts +60 -0
  114. package/src/core/async.engine.ts +539 -0
  115. package/src/core/clock.ts +196 -0
  116. package/src/core/completion.recorder.ts +122 -0
  117. package/src/core/conflict.detector.ts +140 -0
  118. package/src/core/distributed.ts +626 -0
  119. package/src/core/event.bus.ts +292 -0
  120. package/src/core/inbound.recorder.ts +100 -0
  121. package/src/core/index.ts +167 -0
  122. package/src/core/logger.ts +85 -0
  123. package/src/core/metrics.ts +107 -0
  124. package/src/core/orchestrator.ts +625 -0
  125. package/src/core/replay.ts +589 -0
  126. package/src/core/scenario.ts +120 -0
  127. package/src/core/serialization.ts +448 -0
  128. package/src/core/simulation.ts +197 -0
  129. package/src/core/snapshot.manager.ts +293 -0
  130. package/src/core/state.manager.ts +283 -0
  131. package/src/core/types.ts +274 -0
  132. package/src/core/utils.ts +82 -0
  133. package/src/core/wall.clock.ts +23 -0
  134. package/src/extensions/livestream.ext.ts +570 -0
  135. package/src/extensions/time.ext.ts +339 -0
  136. package/src/index.ts +91 -0
  137. package/src/llm/gate.ts +140 -0
  138. package/src/llm/index.ts +716 -0
  139. package/src/llm/summarizer.ts +170 -0
  140. package/src/llm/wire.contracts.ts +67 -0
  141. package/src/pma/eval.ts +651 -0
  142. package/src/pma/index.ts +1082 -0
  143. package/src/profiles/built-in.ts +7 -0
  144. package/src/profiles/companion.ts +28 -0
  145. package/src/profiles/company-brain.ts +54 -0
  146. package/src/profiles/customer-service.ts +30 -0
  147. package/src/profiles/game-npc.ts +20 -0
  148. package/src/profiles/index.ts +42 -0
  149. package/src/profiles/smart-home.ts +34 -0
  150. package/src/runners/coherence.runner.ts +49 -0
  151. package/src/runners/outreach.runner.ts +158 -0
  152. package/src/runners/social.runner.ts +182 -0
  153. package/src/runners/thin-shim.runner.ts +195 -0
  154. package/src/sdk/will.ts +334 -0
  155. package/src/stem/assembly.audit.ts +88 -0
  156. package/src/stem/distribution.ts +203 -0
  157. package/src/stem/guards/identity.coherence.ts +152 -0
  158. package/src/stem/guards/identity.guard.ts +227 -0
  159. package/src/stem/index.ts +1145 -0
  160. package/src/stem/mind.ts +1090 -0
  161. package/src/stem/tracts/ack.reconciler.ts +55 -0
  162. package/src/stem/tracts/biography.writer.ts +190 -0
  163. package/src/stem/tracts/effector.controller.ts +143 -0
  164. package/src/stem/tracts/health.reporter.ts +113 -0
  165. package/src/stem/tracts/inbound.queue.ts +54 -0
  166. package/src/stem/tracts/outbox.controller.ts +167 -0
  167. package/src/stem/tracts/outbox.writer.ts +133 -0
  168. package/src/stem/tracts/pma.controller.ts +109 -0
  169. package/src/stem/tracts/replay.controller.ts +177 -0
  170. package/src/stem/tracts/sensory.controller.ts +168 -0
  171. package/src/stem/tracts/session.logger.ts +157 -0
  172. package/src/stem/tracts/transport/index.ts +38 -0
  173. package/src/stem/tracts/transport/loopback.transport.ts +86 -0
  174. package/src/stem/tracts/transport/socketio.transport.ts +200 -0
  175. package/src/stem/tracts/transport/stream.transport.ts +152 -0
  176. package/src/stem/tracts/transport/types.ts +193 -0
  177. package/src/stem/tracts/transport.controller.ts +380 -0
  178. package/src/types.ts +116 -0
@@ -0,0 +1,265 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/cognition/persona.prior.ts
3
+ // ─────────────────────────────────────────────────────────────
4
+
5
+ /**
6
+ * PersonaPrior — the accommodation layer of the metacognition cycle.
7
+ *
8
+ * The Will's engine configuration is seeded once as immutable `engine-config-*`
9
+ * entities (the *base*). This module adds a learned, durable *prior* layer on
10
+ * top, so introspection can write back into the apparatus that perceives and
11
+ * reasons without ever mutating the base:
12
+ *
13
+ * effective_params = base_config ⊕ persona_prior (additive deltas)
14
+ *
15
+ * Design (see METACOGNITION_CYCLE_TODO.md, Phase 2 — Option B):
16
+ * - The prior lives in a single `persona-prior` entity. Because entity state
17
+ * is the persistence path that's actually wired at boot, the prior survives
18
+ * restarts and replays for free — that's the whole point of choosing an
19
+ * entity over an engine-internal field.
20
+ * - The base entity is never mutated; the prior is a separate, reversible
21
+ * adjustment. A prior of 0 (or absent) ⇒ pure base, so the loop degrades
22
+ * gracefully and a persona can always decay back toward its base.
23
+ * - This reader is pure and deterministic (R2): same state ⇒ same effective
24
+ * params, no wall-clock, no RNG.
25
+ * - **Bounding lives in the writer, not here.** The reader applies whatever
26
+ * delta it finds. The Phase 3 consolidator owns the stability–plasticity
27
+ * policy (per-param clamp + decay) when it *produces* the deltas, because
28
+ * sane bounds depend on each param's scale (a 200-tick interval vs a 0.02
29
+ * rate).
30
+ */
31
+
32
+ import type { ReadonlySimulationState } from '#core/types'
33
+
34
+ /** Entity id of the singleton persona-prior. */
35
+ export const PERSONA_PRIOR_ID = 'persona-prior'
36
+
37
+ /** Entity `type` discriminator for the persona-prior. */
38
+ export const PERSONA_PRIOR_TYPE = 'persona.prior'
39
+
40
+ /**
41
+ * Shape of `persona-prior` entity metadata. `priors` maps an engine-config
42
+ * entity id to a set of additive per-parameter deltas.
43
+ *
44
+ * priors['engine-config-self-model'] = { minIntervalTicks: -40 }
45
+ */
46
+ export interface PersonaPriorMeta {
47
+ priors: Record<string, Record<string, number>>
48
+ /** Monotonic write counter (observability / debugging). */
49
+ version: number
50
+ /** Sim tick of the last consolidation that touched the prior (R2-friendly). */
51
+ updatedAtTick: number
52
+ }
53
+
54
+ /**
55
+ * The additive prior deltas for one engine-config id (empty if none).
56
+ */
57
+ export function readPersonaPrior(
58
+ state: ReadonlySimulationState,
59
+ engineConfigId: string
60
+ ): Record<string, number> {
61
+ const meta = state.entities.get( PERSONA_PRIOR_ID )?.metadata as PersonaPriorMeta | undefined
62
+ return meta?.priors?.[ engineConfigId ] ?? {}
63
+ }
64
+
65
+ /**
66
+ * Effective engine params = base `engine-config-*` params ⊕ persona-prior.
67
+ *
68
+ * Only numeric base params are adjusted, and only by numeric deltas; anything
69
+ * else passes through untouched. Returns a fresh object — never mutates state.
70
+ */
71
+ export function readEffectiveParams(
72
+ state: ReadonlySimulationState,
73
+ engineConfigId: string
74
+ ): Record<string, number> {
75
+ const base = ( state.entities.get( engineConfigId )?.metadata as { params?: Record<string, number> } | undefined )?.params ?? {}
76
+ const prior = readPersonaPrior( state, engineConfigId )
77
+
78
+ const out: Record<string, number> = { ...base }
79
+ for( const [ key, delta ] of Object.entries( prior ) )
80
+ if( typeof out[ key ] === 'number' && typeof delta === 'number' )
81
+ out[ key ] = out[ key ] + delta
82
+
83
+ return out
84
+ }
85
+
86
+ /** Base (un-modulated) params for an engine-config entity, or empty. */
87
+ export function readBaseParams(
88
+ state: ReadonlySimulationState,
89
+ engineConfigId: string
90
+ ): Record<string, number> {
91
+ return ( state.entities.get( engineConfigId )?.metadata as { params?: Record<string, number> } | undefined )?.params ?? {}
92
+ }
93
+
94
+ /** The full persona-prior metadata, or undefined if not yet written. */
95
+ export function readPersonaPriorMeta( state: ReadonlySimulationState ): PersonaPriorMeta | undefined {
96
+ return state.entities.get( PERSONA_PRIOR_ID )?.metadata as PersonaPriorMeta | undefined
97
+ }
98
+
99
+ /** A human-readable description of one active persona-prior adjustment. */
100
+ export interface PersonaAdjustment {
101
+ /** `<engineConfigId>.<param>` the prior is tuning. */
102
+ key: string
103
+ /** First-person phrase for the executive's context (how the Will has adapted itself). */
104
+ description: string
105
+ /** The signed delta currently applied. */
106
+ delta: number
107
+ }
108
+
109
+ // How each tunable reads as a first-person self-observation. `lower`/`raise`
110
+ // describe a negative/positive delta respectively (all current edges push
111
+ // negative — "do X more often / sooner / less tightly").
112
+ const PRIOR_DESCRIPTIONS: Record<string, { lower: string; raise: string }> = {
113
+ 'engine-config-self-model.minIntervalTicks': { lower: 're-examining who I am more often', raise: 're-examining who I am less often' },
114
+ 'engine-config-introspection.cooldownTicks': { lower: 'introspecting more often', raise: 'introspecting less often' },
115
+ 'engine-config-narrator.minIntervalTicks': { lower: 'updating my life-story more often', raise: 'updating my life-story less often' },
116
+ 'engine-config-semantic.beliefStalenessThreshold': { lower: 're-examining my beliefs sooner', raise: 'holding my beliefs longer before review' },
117
+ 'engine-config-working-memory.attentionProtection': { lower: 'holding recent impressions less tightly', raise: 'holding recent impressions more firmly' },
118
+ 'engine-config-inhibition.baseInhibitionStrength': { lower: 'acting on impulse more freely', raise: 'holding myself in check more firmly before acting' },
119
+ 'engine-config-self-model.minNewExperiences': { lower: 're-evaluating who I am on less new experience', raise: 'requiring more new experience before re-evaluating who I am' },
120
+ 'engine-config-attention.shiftInertia': { lower: 'shifting my attention more readily', raise: 'holding my attention more fixedly' },
121
+ 'engine-config-goal-manager.gritPriority': { lower: 'refusing to give up on more of what matters to me', raise: 'letting go of goals more readily' },
122
+ 'engine-config-goal-manager.gritPatienceScale': { lower: 'losing patience with stuck goals sooner', raise: 'staying the course on hard goals far longer' },
123
+ 'engine-config-goal-manager.frustrationTolerance': { lower: 'giving up faster when frustrated', raise: 'holding steady through frustration' },
124
+ 'engine-config-planning.maxStepRetries': { lower: 'giving up on stuck steps sooner', raise: 'following through on stuck steps more doggedly' },
125
+ 'engine-config-planning.surpriseOutcomeQuality': { lower: 'letting lower-quality results slide', raise: 'watching my work more closely, stepping in on smaller dips' },
126
+ 'engine-config-planning.planBiasGain': { lower: 'letting my plans be pulled off course by whatever pulls at me', raise: 'holding to my plans against distraction, pushing them through' },
127
+ 'engine-config-executive.explorationRate': { lower: 'sticking with what I know more', raise: 'venturing into new approaches more' },
128
+ 'engine-config-executive.impulsivity': { lower: 'pausing to think before I act more', raise: 'acting on impulse more readily' },
129
+ 'engine-config-executive.riskTolerance': { lower: 'playing it safer', raise: 'taking bigger risks' },
130
+ 'engine-config-frustration.irritabilityRate': { lower: 'letting frustration snowball into a bad mood more slowly', raise: 'getting wound up into a bad mood faster' },
131
+ 'engine-config-executive.deliberateThreshold': { lower: 'stopping to think things through more readily', raise: 'going with my gut more, deliberating less' },
132
+ 'engine-config-reward.socialWeight': { lower: 'caring less about social warmth', raise: 'finding warmth and connection more rewarding' },
133
+ 'engine-config-frustration.angerReactivity': { lower: 'letting slights go, yielding rather than bristling', raise: 'bristling harder when I feel wronged' },
134
+ 'engine-config-novelty.significanceThreshold': { lower: 'noticing what is new and unusual more readily', raise: 'needing more for something to strike me as new' },
135
+ 'engine-config-aesthetic.aweThreshold': { lower: 'being moved to wonder by beauty more easily', raise: 'being harder to move to wonder' },
136
+ 'engine-config-threat.fearEventThreshold': { lower: 'taking alarm more easily', raise: 'staying unalarmed unless a threat is real and large' },
137
+ 'engine-config-stress.baseDecayRate': { lower: 'carrying stress for longer', raise: 'shedding stress and settling faster' },
138
+ 'engine-config-empathy.resonanceStrength': { lower: 'feeling others’ emotions more faintly', raise: 'feeling what others feel more strongly' },
139
+ 'engine-config-attachment.attachmentGrowthRate': { lower: 'forming bonds more slowly', raise: 'growing attached to people more readily' },
140
+ 'engine-config-reward.goalWeight': { lower: 'caring less about finishing what I start', raise: 'finding real satisfaction in completing my goals' },
141
+ 'engine-config-reputation.trustGrowthStep': { lower: 'being slower to trust', raise: 'extending trust and the benefit of the doubt more readily' },
142
+ 'engine-config-task-switcher.baseSwitchCost': { lower: 'flitting between things more easily', raise: 'staying on one thing, harder to pull away' },
143
+ 'engine-config-action-selector.switchCost': { lower: 'dropping what I am doing for a new pull more easily', raise: 'seeing an action through, harder to knock off course once underway' },
144
+ 'engine-config-action-selector.riskWeight': { lower: 'weighing the downside of what I do less — acting bolder', raise: 'weighing what could go wrong more heavily before I act' },
145
+ 'engine-config-action-selector.noveltyWeight': { lower: 'feeling less pull toward the untried', raise: 'drawn more strongly to do the untried and unpracticed' },
146
+ 'engine-config-moral.eventThreshold': { lower: 'holding myself to my principles more sharply', raise: 'letting moral lapses weigh on me less' },
147
+ 'engine-config-frustration.decayRate': { lower: 'staying frustrated for longer after a setback', raise: 'shaking off frustration and bouncing back faster' },
148
+ 'engine-config-reward.socialWarmthBoost': { lower: 'being warmed less by a kind exchange', raise: 'being warmed more by every kind exchange' },
149
+ 'engine-config-reward.socialDecayRate': { lower: 'holding the glow of connection longer', raise: 'letting the glow of connection fade faster' },
150
+ 'engine-config-known-entity.familiarityGrowthRate': { lower: 'taking longer to feel I know someone', raise: 'getting a feel for someone more quickly' },
151
+ 'engine-config-known-entity.curiosityGain': { lower: 'feeling less pull to know the half-known', raise: 'itching to get to know whoever I half-know' },
152
+ 'engine-config-known-entity.reliabilityRate': { lower: 'being slower to revise whether I can rely on something', raise: 'quickly updating whether I can rely on something' },
153
+ }
154
+
155
+ /**
156
+ * Summarise the active persona-prior as first-person self-observations — the
157
+ * accommodation the Will has made to itself, surfaced so its deliberate self
158
+ * (the executive) can see how it has been self-tuning. Empty when no prior.
159
+ */
160
+ export function summarizePersonaPrior( state: ReadonlySimulationState ): PersonaAdjustment[] {
161
+ const meta = readPersonaPriorMeta( state )
162
+ if( !meta?.priors ) return []
163
+
164
+ const out: PersonaAdjustment[] = []
165
+ for( const [ engineConfigId, params ] of Object.entries( meta.priors ) )
166
+ for( const [ param, delta ] of Object.entries( params ) ){
167
+ if( !delta ) continue
168
+
169
+ const key = `${engineConfigId}.${param}`
170
+ const desc = PRIOR_DESCRIPTIONS[ key ]
171
+ const description = desc
172
+ ? ( delta < 0 ? desc.lower : desc.raise )
173
+ : `${key} ${delta < 0 ? 'decreased' : 'increased'}`
174
+
175
+ out.push({ key, description, delta })
176
+ }
177
+
178
+ return out
179
+ }
180
+
181
+ // ── Write side (the consolidator's bounded mutation) ──────────
182
+
183
+ /** Below this magnitude a delta is treated as 0 (returned-to-base) and dropped. */
184
+ const EPSILON_PRIOR = 1e-6
185
+
186
+ export interface ConsolidateOpts {
187
+ /** Cumulative |delta| cap, as a fraction of |base|. Default 0.5. */
188
+ maxRelMagnitude?: number
189
+ /** Per-step |delta| cap, as a fraction of |base|. Default 0.15. */
190
+ maxRelStep?: number
191
+ /** Multiplicative decay toward base applied to every delta each call. Default 0.98. */
192
+ decayFactor?: number
193
+ }
194
+
195
+ /**
196
+ * One requested adjustment to a single engine-config param. `base` is that
197
+ * param's base value, used to scale the bounds (so one policy fits a 200-tick
198
+ * interval and a 0.02 rate). `proposedDelta === 0` (or a 0/non-numeric base)
199
+ * means "no push" — the decay still applies globally.
200
+ */
201
+ export interface PriorAdjustment {
202
+ engineConfigId: string
203
+ param: string
204
+ base: number
205
+ proposedDelta: number
206
+ }
207
+
208
+ /**
209
+ * Produce the next persona-prior from a consolidation pass (the write counterpart
210
+ * of `readEffectiveParams`). Pure and deterministic — returns a fresh
211
+ * `PersonaPriorMeta` for the caller to emit as a `set` command (R2-safe).
212
+ *
213
+ * Two stability-preserving steps (the stability–plasticity dilemma):
214
+ * 1. **Decay** — every existing delta is multiplied toward 0 (toward base) once
215
+ * per pass, so a prior that stops being reinforced fades back to the seeded
216
+ * baseline rather than ossifying.
217
+ * 2. **Bounded additive steps** — each adjustment's delta is clamped per-step
218
+ * *and* cumulatively, both relative to that param's base magnitude. A single
219
+ * consolidation can never lurch the persona, and no accumulated delta can
220
+ * exceed `maxRelMagnitude × |base|`.
221
+ *
222
+ * Multiple adjustments are applied in a single pass (decay once, N bounded steps,
223
+ * one version bump) so the consolidator can close several edges per tick into one
224
+ * coherent persona-prior. Pass `[]` for a pure decay pass.
225
+ */
226
+ export function consolidatePrior(
227
+ current: PersonaPriorMeta | undefined,
228
+ adjustments: PriorAdjustment[],
229
+ tick: number,
230
+ opts: ConsolidateOpts = {}
231
+ ): PersonaPriorMeta {
232
+ const maxRel = opts.maxRelMagnitude ?? 0.5
233
+ const maxStep = opts.maxRelStep ?? 0.15
234
+ const decay = opts.decayFactor ?? 0.98
235
+
236
+ // 1. Decay every existing delta toward base once; drop ones that fall to ~0.
237
+ const priors: PersonaPriorMeta['priors'] = {}
238
+ for( const [ engineId, params ] of Object.entries( current?.priors ?? {} ) ){
239
+ const kept: Record<string, number> = {}
240
+ for( const [ p, v ] of Object.entries( params ) ){
241
+ const nv = v * decay
242
+ if( Math.abs( nv ) >= EPSILON_PRIOR ) kept[ p ] = nv
243
+ }
244
+
245
+ if( Object.keys( kept ).length > 0 ) priors[ engineId ] = kept
246
+ }
247
+
248
+ // 2. Apply each bounded additive step (relative to its param's |base|).
249
+ for( const adj of adjustments ){
250
+ if( typeof adj.base !== 'number' || adj.base === 0 || adj.proposedDelta === 0 ) continue
251
+ const scale = Math.abs( adj.base )
252
+ const step = Math.max( -maxStep * scale, Math.min( maxStep * scale, adj.proposedDelta ) )
253
+ const existing = priors[ adj.engineConfigId ]?.[ adj.param ] ?? 0
254
+ const next = Math.max( -maxRel * scale, Math.min( maxRel * scale, existing + step ) )
255
+
256
+ const engineMap = priors[ adj.engineConfigId ] ?? ( priors[ adj.engineConfigId ] = {} )
257
+ if( Math.abs( next ) < EPSILON_PRIOR ) delete engineMap[ adj.param ]
258
+ else engineMap[ adj.param ] = next
259
+
260
+ if( Object.keys( engineMap ).length === 0 )
261
+ delete priors[ adj.engineConfigId ]
262
+ }
263
+
264
+ return { priors, version: ( current?.version ?? 0 ) + 1, updatedAtTick: tick }
265
+ }
@@ -0,0 +1,145 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/cognition/schema.registry.ts
3
+ // ─────────────────────────────────────────────────────────────
4
+
5
+ export interface CognitiveEventSchema {
6
+ readonly type: string
7
+ readonly version: number
8
+ validate( payload: unknown ): string | null // null = valid, string = error message
9
+ }
10
+
11
+ // Migration transform: takes old payload, returns upgraded payload or error string
12
+ export type MigrationTransform = ( payload: unknown ) => { ok: true; payload: unknown } | { ok: false; error: string }
13
+
14
+ interface Migration {
15
+ fromVersion: number
16
+ toVersion: number
17
+ transform: MigrationTransform
18
+ }
19
+
20
+ export class SchemaRegistry {
21
+ private _schemas = new Map<string, Map<number, CognitiveEventSchema>>()
22
+ private _migrations = new Map<string, Migration[]>()
23
+
24
+ register( schema: CognitiveEventSchema ): void {
25
+ let versions = this._schemas.get( schema.type )
26
+ if( !versions ){
27
+ versions = new Map()
28
+ this._schemas.set( schema.type, versions )
29
+ }
30
+
31
+ if( versions.has( schema.version ) )
32
+ throw new Error(`Schema already registered: ${schema.type} v${schema.version}`)
33
+
34
+ versions.set( schema.version, schema )
35
+ }
36
+
37
+ /**
38
+ * Register a migration path from one schema version to the next.
39
+ * Migrations must be registered in ascending order and cover contiguous versions.
40
+ * The bus will auto-upgrade old payloads to the latest version on validate().
41
+ */
42
+ registerMigration( type: string, fromVersion: number, toVersion: number, transform: MigrationTransform ): void {
43
+ if( toVersion !== fromVersion + 1 )
44
+ throw new Error(`Migration must be to the next consecutive version (got ${fromVersion}→${toVersion})`)
45
+
46
+ let migrations = this._migrations.get( type )
47
+ if( !migrations ){
48
+ migrations = []
49
+ this._migrations.set( type, migrations )
50
+ }
51
+ migrations.push({ fromVersion, toVersion, transform })
52
+ migrations.sort( ( a, b ) => a.fromVersion - b.fromVersion )
53
+ }
54
+
55
+ /**
56
+ * Validate payload at a given version. If the exact version is not registered
57
+ * but a migration path exists to a newer version, auto-upgrade and validate
58
+ * against the latest registered version.
59
+ */
60
+ validate( type: string, version: number, payload: unknown ): string | null {
61
+ const versions = this._schemas.get( type )
62
+ if( !versions ) return `Unknown event type: ${type}`
63
+
64
+ // Exact version match — fast path
65
+ const exact = versions.get( version )
66
+ if( exact ) return exact.validate( payload )
67
+
68
+ // Try to migrate up to a known version
69
+ const migrated = this._migrate( type, version, payload )
70
+ if( !migrated.ok ) return migrated.error
71
+
72
+ const target = versions.get( migrated.targetVersion )
73
+ if( !target ) return `Unknown version ${version} for event type: ${type} (migration failed to find target)`
74
+
75
+ return target.validate( migrated.payload )
76
+ }
77
+
78
+ /**
79
+ * Public migration API used by the bus for per-subscriber version filtering.
80
+ * Attempts to migrate payload from fromVersion up to a registered version.
81
+ */
82
+ tryMigrate(
83
+ type: string,
84
+ fromVersion: number,
85
+ payload: unknown
86
+ ): { ok: true; payload: unknown; targetVersion: number } | { ok: false; error: string } {
87
+ return this._migrate( type, fromVersion, payload )
88
+ }
89
+
90
+ private _migrate(
91
+ type: string,
92
+ fromVersion: number,
93
+ payload: unknown
94
+ ): { ok: true; payload: unknown; targetVersion: number } | { ok: false; error: string } {
95
+ const migrations = this._migrations.get( type )
96
+ if( !migrations || migrations.length === 0 )
97
+ return { ok: false, error: `Unknown version ${fromVersion} for event type: ${type}` }
98
+
99
+ let current = payload
100
+ let version = fromVersion
101
+
102
+ for( const migration of migrations ){
103
+ if( migration.fromVersion !== version ) continue
104
+ const result = migration.transform( current )
105
+ if( !result.ok ) return { ok: false, error: `Migration ${type} v${version}→v${migration.toVersion} failed: ${result.error}` }
106
+ current = result.payload
107
+ version = migration.toVersion
108
+ }
109
+
110
+ return { ok: true, payload: current, targetVersion: version }
111
+ }
112
+
113
+ hasType( type: string ): boolean {
114
+ return this._schemas.has( type )
115
+ }
116
+
117
+ getVersions( type: string ): number[] {
118
+ const versions = this._schemas.get( type )
119
+ return versions ? [ ...versions.keys() ] : []
120
+ }
121
+
122
+ registeredTypes(): string[] {
123
+ return [ ...this._schemas.keys() ]
124
+ }
125
+ }
126
+
127
+ export const globalSchemaRegistry = new SchemaRegistry()
128
+
129
+ // ── Built-in schemas ─────────────────────────────────────────
130
+
131
+ function isRecord( v: unknown ): v is Record<string, unknown> {
132
+ return typeof v === 'object' && v !== null && !Array.isArray( v )
133
+ }
134
+
135
+ globalSchemaRegistry.register({
136
+ type: 'clock.tick',
137
+ version: 1,
138
+ validate( payload ){
139
+ if( !isRecord( payload ) ) return 'clock.tick payload must be an object'
140
+ if( typeof payload['tick'] !== 'number' ) return 'clock.tick requires numeric tick'
141
+ if( typeof payload['delta'] !== 'number' ) return 'clock.tick requires numeric delta'
142
+
143
+ return null
144
+ }
145
+ })