@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,246 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/agency/schemas/repertoire.ts — the store of what the Will can do
3
+ // ─────────────────────────────────────────────────────────────
4
+ //
5
+ // The repertoire holds two things: the schema TEMPLATES the Will can enact
6
+ // (innate floor + learned composites) and the per-schema LearnedSkill stats that
7
+ // accrete from experience. It is the competence layer — the part of a Will that
8
+ // makes it *act like itself*, and the part the PMA must carry across re-embodiment
9
+ // (Phase 6). It is an in-memory manager (like GoalManager); the ReafferenceEngine
10
+ // mirrors per-schema skills into `agency.skill` state entities AND learned
11
+ // composite templates into `agency.schema` entities, so both survive a
12
+ // snapshot/restore. The skills carry habit/value; the composites carry the
13
+ // invented multi-step *programs* the executor must resolve to run a macro.
14
+ // restoreComposites() rehydrates those templates after a restore reconstructs
15
+ // the repertoire innate-only — analogous to GoalManager rehydrating `goal`s.
16
+ //
17
+ // Learning rules (the proceduralization curve):
18
+ // valueEstimate — EMA toward observed outcome quality
19
+ // habitStrength — rises only on CONFIDENT success (low prediction error);
20
+ // a surprising or failed outcome erodes it. This is the
21
+ // instrumental→habitual gradient: an action becomes a habit
22
+ // by working reliably AND predictably, not merely often.
23
+ // avgPredictionError — EMA of |predicted − actual|; the destabilizer
24
+ // paramPriors — last known-good parameters become defaults
25
+ // ─────────────────────────────────────────────────────────────
26
+
27
+ import type { MotorSchema, LearnedSkill } from '#agency/types'
28
+ import type { EntityInput, ReadonlySimulationState } from '#core/types'
29
+ import { INNATE_SCHEMAS } from '#agency/schemas/innate'
30
+
31
+ const VALUE_ALPHA = 0.2 // value EMA rate
32
+ const ERROR_BETA = 0.2 // prediction-error EMA rate
33
+ const HABIT_GAIN = 0.12 // habit growth on confident success
34
+ const HABIT_FAIL_DECAY = 0.10 // habit erosion on surprise / failure
35
+ const CONFIDENT_ERROR = 0.30 // prediction error below this counts as "predictable"
36
+ const PROC_THRESHOLD = 0.60 // habit at/above this is "proceduralized"
37
+
38
+ const IDLE_TICKS = 200 // ticks of disuse before forgetting starts
39
+ const DECAY_RATE = 0.02 // habit lost per decay application
40
+ const DROP_HABIT = 0.05 // below this (and learned) the skill is forgotten
41
+
42
+ export interface OutcomeObservation {
43
+ schema: string
44
+ success: boolean
45
+ outcomeQuality: number
46
+ predictedReward: number
47
+ /** Last-known-good parameters to fold into priors (only kept on success). */
48
+ params?: Record<string, unknown>
49
+ tick: number
50
+ }
51
+
52
+ export class SchemaRepertoire {
53
+ private _templates = new Map<string, MotorSchema>()
54
+ private _skills = new Map<string, LearnedSkill>()
55
+ /** Tracks which templates were learned at runtime (vs innate) so decay can forget them. */
56
+ private _learned = new Set<string>()
57
+
58
+ constructor( seed: MotorSchema[] = INNATE_SCHEMAS ){
59
+ for( const s of seed ) this._templates.set( s.id, s )
60
+ }
61
+
62
+ // ── templates ─────────────────────────────────────────────────
63
+ schemas(): MotorSchema[] { return [ ...this._templates.values() ] }
64
+ getSchema( id: string ): MotorSchema | undefined { return this._templates.get( id ) }
65
+
66
+ /** Register a learned composite skill template (starts with no habit). */
67
+ registerComposite( schema: MotorSchema ): void {
68
+ this._templates.set( schema.id, schema )
69
+ this._learned.add( schema.id )
70
+ if( !this._skills.has( schema.id ) )
71
+ this._skills.set( schema.id, freshSkill( schema.id, 0.4, 0 ) )
72
+ }
73
+
74
+ // ── skills ────────────────────────────────────────────────────
75
+ skills(): ReadonlyMap<string, LearnedSkill> { return this._skills }
76
+ getSkill( id: string ): LearnedSkill | undefined { return this._skills.get( id ) }
77
+
78
+ /**
79
+ * Fold one outcome into the schema's learned skill. Returns the updated skill
80
+ * and whether it just crossed the proceduralization threshold this update.
81
+ */
82
+ recordOutcome( o: OutcomeObservation ): { skill: LearnedSkill; proceduralized: boolean } {
83
+ const prior = this._skills.get( o.schema ) ?? freshSkill( o.schema, o.predictedReward, o.tick )
84
+ const wasProceduralized = prior.habitStrength >= PROC_THRESHOLD
85
+
86
+ const error = clamp01( Math.abs( o.predictedReward - o.outcomeQuality ) )
87
+ const confident = o.success && error < CONFIDENT_ERROR
88
+
89
+ const habitStrength = clamp01(
90
+ confident
91
+ ? prior.habitStrength + HABIT_GAIN * ( 1 - prior.habitStrength )
92
+ : prior.habitStrength - HABIT_FAIL_DECAY * prior.habitStrength,
93
+ )
94
+
95
+ const skill: LearnedSkill = {
96
+ schema: o.schema,
97
+ habitStrength,
98
+ valueEstimate: clamp01( prior.valueEstimate + VALUE_ALPHA * ( o.outcomeQuality - prior.valueEstimate ) ),
99
+ paramPriors: o.success && o.params ? { ...prior.paramPriors, ...o.params } : prior.paramPriors,
100
+ enactments: prior.enactments + 1,
101
+ successes: prior.successes + ( o.success ? 1 : 0 ),
102
+ avgPredictionError: prior.avgPredictionError + ERROR_BETA * ( error - prior.avgPredictionError ),
103
+ lastEnactedTick: o.tick,
104
+ }
105
+
106
+ this._skills.set( o.schema, skill )
107
+ return { skill, proceduralized: !wasProceduralized && habitStrength >= PROC_THRESHOLD }
108
+ }
109
+
110
+ /**
111
+ * Forgetting curve over the competence layer. Skills unused for IDLE_TICKS
112
+ * lose habit; learned composites that fall below DROP_HABIT are dropped
113
+ * entirely (template + skill). Returns the schema ids that were forgotten.
114
+ */
115
+ decay( tick: number ): string[] {
116
+ const dropped: string[] = []
117
+ for( const [ id, skill ] of this._skills ){
118
+ if( tick - skill.lastEnactedTick <= IDLE_TICKS ) continue
119
+
120
+ const habitStrength = clamp01( skill.habitStrength - DECAY_RATE )
121
+ if( this._learned.has( id ) && habitStrength < DROP_HABIT ){
122
+ this._skills.delete( id )
123
+ this._templates.delete( id )
124
+ this._learned.delete( id )
125
+ dropped.push( id )
126
+ continue
127
+ }
128
+ this._skills.set( id, { ...skill, habitStrength } )
129
+ }
130
+ return dropped
131
+ }
132
+
133
+ // ── PMA portability (Phase 6 reads these) ─────────────────────
134
+ /** Learned composite templates + all skills above a confidence floor. */
135
+ export( minHabit = 0.0 ): { composites: MotorSchema[]; skills: LearnedSkill[] } {
136
+ return {
137
+ composites: [ ...this._learned ].map( id => this._templates.get( id ) ).filter( ( s ): s is MotorSchema => !!s ),
138
+ skills: [ ...this._skills.values() ].filter( s => s.habitStrength >= minHabit ),
139
+ }
140
+ }
141
+
142
+ import( data: { composites?: MotorSchema[]; skills?: LearnedSkill[] } ): void {
143
+ for( const c of data.composites ?? [] ){ this._templates.set( c.id, c ); this._learned.add( c.id ) }
144
+ for( const s of data.skills ?? [] ) this._skills.set( s.schema, s )
145
+ }
146
+
147
+ // ── snapshot / replay portability (deterministic state path) ──
148
+ // Skills mirror to `agency.skill`; the invented composite *definitions* must
149
+ // travel too, or a snapshot/restore brings back an `agency.skill` whose schema
150
+ // is gone and the MotorSchemaExecutor can't expand it (it misroutes the intent
151
+ // to the host and times out). compositeEntities() is the write side (the
152
+ // ReafferenceEngine pushes these each tick); restoreComposites() is the read
153
+ // side, called from the first agency engine after a restore.
154
+
155
+ /** Learned composite templates encoded as `agency.schema` state entities. */
156
+ compositeEntities(): EntityInput[] {
157
+ const out: EntityInput[] = []
158
+ for( const id of this._learned ){
159
+ const s = this._templates.get( id )
160
+ if( s && s.kind === 'composite' ) out.push( schemaEntity( s ) )
161
+ }
162
+ return out
163
+ }
164
+
165
+ /**
166
+ * Re-register learned composites from `agency.schema` state entities after a
167
+ * restore rebuilt the repertoire innate-only. Idempotent — skips composites
168
+ * already present and never seeds a fresh skill (the restored `agency.skill`
169
+ * entity and future outcomes own the habit; seeding here would reset it).
170
+ * Mirrors GoalManager._syncFromStateGoals.
171
+ */
172
+ restoreComposites( entities: ReadonlySimulationState['entities'] ): void {
173
+ for( const e of entities.values() ){
174
+ if( e.type !== SCHEMA_ENTITY_TYPE ) continue
175
+ const s = readSchema( e.metadata as Record<string, unknown> | undefined )
176
+ if( !s || this._templates.has( s.id ) ) continue
177
+ this._templates.set( s.id, s )
178
+ this._learned.add( s.id )
179
+ }
180
+ }
181
+ }
182
+
183
+ // ─── helpers ─────────────────────────────────────────────────────────────────
184
+
185
+ function freshSkill( schema: string, value: number, tick: number ): LearnedSkill {
186
+ return {
187
+ schema,
188
+ habitStrength: 0,
189
+ valueEstimate: clamp01( value ),
190
+ paramPriors: {},
191
+ enactments: 0,
192
+ successes: 0,
193
+ avgPredictionError: 0,
194
+ lastEnactedTick: tick,
195
+ }
196
+ }
197
+
198
+ function clamp01( n: number ): number {
199
+ return n < 0 ? 0 : n > 1 ? 1 : n
200
+ }
201
+
202
+ // ─── composite ⇄ state-entity codec (snapshot/replay) ────────────────────────
203
+
204
+ /** State-entity type for a mirrored composite template. */
205
+ export const SCHEMA_ENTITY_TYPE = 'agency.schema'
206
+
207
+ /** Stable entity id for a schema's mirror (no timestamp → idempotent re-writes). */
208
+ export function schemaEntityId( schemaId: string ): string { return `agency-schema-${ schemaId }` }
209
+
210
+ /** Encode a composite template as a state entity (round-trips via readSchema). */
211
+ function schemaEntity( s: MotorSchema ): EntityInput {
212
+ return {
213
+ id: schemaEntityId( s.id ),
214
+ type: SCHEMA_ENTITY_TYPE,
215
+ metadata: {
216
+ id: s.id,
217
+ kind: s.kind,
218
+ source: s.source,
219
+ cost: s.cost,
220
+ binds: s.binds,
221
+ preconditions: s.preconditions,
222
+ composedOf: s.composedOf,
223
+ baseValence: s.baseValence,
224
+ tags: s.tags,
225
+ },
226
+ }
227
+ }
228
+
229
+ /** Decode a mirrored schema entity back into a MotorSchema (undefined if malformed). */
230
+ function readSchema( m: Record<string, unknown> | undefined ): MotorSchema | undefined {
231
+ const meta = m ?? {}
232
+ const id = typeof meta['id'] === 'string' ? meta['id'] as string : undefined
233
+ const kind = meta['kind']
234
+ if( !id || ( kind !== 'composite' && kind !== 'primitive' ) ) return undefined
235
+ return {
236
+ id,
237
+ kind,
238
+ source: ( meta['source'] as MotorSchema['source'] ) ?? 'repertoire',
239
+ cost: typeof meta['cost'] === 'number' ? meta['cost'] as number : 0,
240
+ binds: ( meta['binds'] as MotorSchema['binds'] ) ?? 'none',
241
+ preconditions: meta['preconditions'] as MotorSchema['preconditions'],
242
+ composedOf: Array.isArray( meta['composedOf'] ) ? meta['composedOf'] as string[] : undefined,
243
+ baseValence: typeof meta['baseValence'] === 'number' ? meta['baseValence'] as number : undefined,
244
+ tags: Array.isArray( meta['tags'] ) ? meta['tags'] as string[] : undefined,
245
+ }
246
+ }
@@ -0,0 +1,191 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/agency/selection.scoring.ts — the competition's activation function
3
+ // ─────────────────────────────────────────────────────────────
4
+ //
5
+ // A mind does not pick a tool off a menu; affordances *compete*, and the
6
+ // competition is biased by what the body needs, what it wants, what it has
7
+ // learned, and what it fears. This is the basal-ganglia Go/NoGo arithmetic,
8
+ // kept pure so it is trivially testable and replay-deterministic.
9
+ //
10
+ // activation = goalRelevance + expectedReward + novelty + driveUrgency
11
+ // + habitStrength·W (additive habit bonus)
12
+ // + planBias·W (top-down prior)
13
+ // − cost − inhibition − risk
14
+ //
15
+ // The habit term is additive (per design decision): an overlearned schema wins
16
+ // cheaply AND lowers its own deliberation threshold (see selector.ts), which is
17
+ // how the instrumental→habitual gradient cashes out as falling LLM spend.
18
+ // ─────────────────────────────────────────────────────────────
19
+
20
+ import type { Affordance } from '#agency/types'
21
+ import type { ReadonlySimulationState } from '#core/types'
22
+
23
+ /** Bias signals assembled from live state + the cognitive bus each tick. */
24
+ export interface BiasContext {
25
+ /** Entity ids that an active goal is directed at. */
26
+ goalTargets: ReadonlySet<string>
27
+ /** Priority of the highest-priority active goal (0..1). */
28
+ maxGoalPriority: number
29
+ /** Homeostatic drive pressures, each 0..1 (0 = satisfied). */
30
+ drives: { energy: number; sleep: number; stress: number; social: number }
31
+ /** Ambient threat 0..1. */
32
+ threat: number
33
+ /** Global NoGo pressure 0..1 from the InhibitionController. */
34
+ inhibition: number
35
+ }
36
+
37
+ export interface ScoreWeights {
38
+ goal: number
39
+ reward: number
40
+ novelty: number
41
+ drive: number
42
+ habit: number
43
+ /** Top-down planning prior — how strongly an executing plan biases the competition. */
44
+ plan: number
45
+ cost: number
46
+ inhib: number
47
+ risk: number
48
+ }
49
+
50
+ export const DEFAULT_WEIGHTS: ScoreWeights = {
51
+ goal: 0.30,
52
+ reward: 0.25,
53
+ novelty: 0.10,
54
+ drive: 0.25,
55
+ habit: 0.20,
56
+ plan: 0.30,
57
+ cost: 0.20,
58
+ inhib: 0.30,
59
+ risk: 0.20,
60
+ }
61
+
62
+ /**
63
+ * Decision temperature for the entropy softmax. Activations live in a narrow
64
+ * band (~0..1), so a low temperature is needed for a real activation gap to read
65
+ * as a confident winner rather than near-uniform noise. Lower = sharper / more
66
+ * decisive; this is the knob a future arousal signal would modulate.
67
+ */
68
+ export const DEFAULT_TEMPERATURE = 0.15
69
+
70
+ /**
71
+ * Active-goal targets → the highest priority directed at each entity. Relevance is
72
+ * structural (target match), never lexical: a goal links to an entity EITHER via
73
+ * its `targetEntityId`/`requestingEntityId` metadata OR via a `keid:<id>` tag —
74
+ * the convention KnownEntityTracker uses for the curiosity goals it forms. Shared
75
+ * by the synthesizer (to lift goal-relevant affordances at the attention cap) and
76
+ * the selector (to score goalRelevance), so both stages see the same links.
77
+ */
78
+ export function collectGoalTargets( state: ReadonlySimulationState ): Map<string, number> {
79
+ const targets = new Map<string, number>()
80
+ for( const e of state.entities.values() ){
81
+ if( e.type !== 'goal' ) continue
82
+ const m = e.metadata as Record<string, unknown> | undefined
83
+ const status = typeof m?.['status'] === 'string' ? m['status'] : undefined
84
+ if( status !== 'active' && status !== 'in_progress' ) continue
85
+ const priority = typeof m?.['priority'] === 'number' ? m['priority'] as number : 0
86
+ const link = ( id: unknown ): void => {
87
+ if( typeof id !== 'string' || !id ) return
88
+ targets.set( id, Math.max( targets.get( id ) ?? 0, priority ) )
89
+ }
90
+ link( m?.['targetEntityId'] )
91
+ link( m?.['requestingEntityId'] )
92
+ if( Array.isArray( m?.['tags'] ) )
93
+ for( const t of m['tags'] as unknown[] )
94
+ if( typeof t === 'string' && t.startsWith( 'keid:' ) ) link( t.slice( 5 ) )
95
+ }
96
+ return targets
97
+ }
98
+
99
+ /** Goal relevance: structural (target match), not lexical. 0 when the affordance has no goal-linked target. */
100
+ export function goalRelevance( a: Affordance, bias: BiasContext ): number {
101
+ if( a.targetEntityId && bias.goalTargets.has( a.targetEntityId ) )
102
+ return bias.maxGoalPriority
103
+ return 0
104
+ }
105
+
106
+ /** Homeostatic urgency routed to the affordance by its schema tags. */
107
+ export function driveUrgency( a: Affordance, bias: BiasContext ): number {
108
+ const t = a.tags
109
+ if( t.includes( 'regulatory' ) || t.includes( 'self-care' ) || t.includes( 'rest' ) )
110
+ return Math.max( bias.drives.energy, bias.drives.sleep )
111
+ if( t.includes( 'social' ) )
112
+ return bias.drives.social
113
+ if( t.includes( 'self-protection' ) )
114
+ return Math.max( bias.drives.stress, bias.threat )
115
+ return 0
116
+ }
117
+
118
+ /** Curiosity pull toward the unpracticed — proxied by the inverse of habit until a novelty engine feeds in. */
119
+ export function novelty( a: Affordance ): number {
120
+ return clamp01( 1 - a.habitStrength )
121
+ }
122
+
123
+ /** Risk from anticipated negative affect and ambient threat. */
124
+ export function risk( a: Affordance, bias: BiasContext ): number {
125
+ return clamp01( Math.max( 0, -a.expectedValence ) * 0.5 + bias.threat * 0.5 )
126
+ }
127
+
128
+ /** Full competition activation for one affordance. */
129
+ export function scoreAffordance(
130
+ a: Affordance,
131
+ bias: BiasContext,
132
+ w: ScoreWeights = DEFAULT_WEIGHTS,
133
+ ): number {
134
+ return (
135
+ w.goal * goalRelevance( a, bias )
136
+ + w.reward * a.expectedReward
137
+ + w.novelty * novelty( a )
138
+ + w.drive * driveUrgency( a, bias )
139
+ + w.habit * a.habitStrength
140
+ + w.plan * ( a.planBias ?? 0 )
141
+ - w.cost * a.cost
142
+ - w.inhib * bias.inhibition
143
+ - w.risk * risk( a, bias )
144
+ )
145
+ }
146
+
147
+ /**
148
+ * Stakes of committing to `winner` — how consequential the leading choice is.
149
+ * High stakes recruit deliberation even when the field is not ambiguous.
150
+ */
151
+ export function stakes( winner: Affordance, bias: BiasContext ): number {
152
+ // Consequence, not unfamiliarity: ambient threat and the affective charge of
153
+ // the leading choice dominate. Novelty contributes only weakly — a brand-new
154
+ // schema is novel (1) but that alone should not force the LLM on every action
155
+ // before any habit has formed; curiosity already lives in the activation.
156
+ return clamp01( Math.max(
157
+ bias.threat,
158
+ Math.abs( winner.expectedValence ),
159
+ novelty( winner ) * 0.5,
160
+ ) )
161
+ }
162
+
163
+ /**
164
+ * Normalized competition entropy (0..1). 0 = a single dominant winner; 1 = a
165
+ * flat field with no clear choice. Softmax over activations, Shannon-normalized
166
+ * by log(n). This is the ambiguity signal that recruits the executive.
167
+ */
168
+ export function competitionEntropy(
169
+ activations: number[],
170
+ temperature: number = DEFAULT_TEMPERATURE,
171
+ ): number {
172
+ const n = activations.length
173
+ if( n <= 1 ) return 0
174
+
175
+ const t = temperature > 0 ? temperature : DEFAULT_TEMPERATURE
176
+ const max = Math.max( ...activations )
177
+ const exps = activations.map( a => Math.exp( ( a - max ) / t ) )
178
+ const sum = exps.reduce( ( s, e ) => s + e, 0 )
179
+ if( sum === 0 ) return 1
180
+
181
+ let h = 0
182
+ for( const e of exps ){
183
+ const p = e / sum
184
+ if( p > 0 ) h -= p * Math.log( p )
185
+ }
186
+ return clamp01( h / Math.log( n ) )
187
+ }
188
+
189
+ function clamp01( n: number ): number {
190
+ return n < 0 ? 0 : n > 1 ? 1 : n
191
+ }
@@ -0,0 +1,165 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/agency/types.ts — core contracts for the agency pipeline
3
+ // ─────────────────────────────────────────────────────────────
4
+ //
5
+ // The vocabulary of a mind that *finds* its actions in the situation rather
6
+ // than looking them up in a catalog. An Affordance is a possibility the current
7
+ // state affords; a MotorSchema is the control program that would enact it; a
8
+ // LearnedSkill is what the Will has accreted about running that schema; a
9
+ // SchemaOutcome is the reafference that teaches it. Nothing here owns behaviour —
10
+ // these are the stable nouns every agency engine references.
11
+ //
12
+ // See AGENCY_PIPELINE_TODO.md for the end-to-end design.
13
+ // ─────────────────────────────────────────────────────────────
14
+
15
+ /** Where an affordance came from — its evoking origin in the perception field. */
16
+ export type AffordanceSource =
17
+ | 'innate' // always-on floor; needs no object (orient, rest, reflect, wait)
18
+ | 'perceptual' // evoked by a salient percept (inspect this, attend to that)
19
+ | 'social' // evoked by a known sentient entity (reach out to them)
20
+ | 'repertoire' // a learned composite skill whose trigger matches now
21
+ | 'ideomotor' // pre-activated by the executive imagining the outcome
22
+ | 'plan' // pre-activated by an executing plan's current frontier step (top-down prior)
23
+ | 'external' // a host-owned domain effector (move, attack, trade …) dispatched to the world
24
+
25
+ /** A body-state gate. If any precondition fails, the affordance is unavailable. */
26
+ export interface SchemaPrecondition {
27
+ metric: string
28
+ op: 'gt' | 'lt' | 'gte' | 'lte' | 'eq'
29
+ value: number
30
+ }
31
+
32
+ /** What kind of target a schema binds when it becomes an affordance. */
33
+ export type SchemaBinding = 'none' | 'entity' | 'percept'
34
+
35
+ /**
36
+ * A MotorSchema — a parameterized control program, not a flat effector row.
37
+ * `kind: 'primitive'` runs a body directly (an internal stance, a communication,
38
+ * or an external host invocation). `kind: 'composite'` expands into an ordered
39
+ * policy over sub-schemas (this is where a *created* skill actually executes).
40
+ */
41
+ export interface MotorSchema {
42
+ id: string
43
+ kind: 'primitive' | 'composite'
44
+ source: AffordanceSource
45
+ /** Normalized intrinsic cost 0..1 (effort / energy demand). */
46
+ cost: number
47
+ /** What this schema binds to when afforded. */
48
+ binds: SchemaBinding
49
+ /** Body-state gates evaluated against live metrics. */
50
+ preconditions?: SchemaPrecondition[]
51
+ /** Sub-schema ids, in policy order — composite schemas only. */
52
+ composedOf?: string[]
53
+ /** Intrinsic affective prior (−1..1) before any learning has occurred. */
54
+ baseValence?: number
55
+ tags?: string[]
56
+ }
57
+
58
+ /**
59
+ * An Affordance — a possibility the current state affords *for this body now*,
60
+ * with its parameters already bound from the thing that evoked it. Parameters
61
+ * are never separated from the situation that supplies them, so there is no
62
+ * "empty params" failure mode. Affordances are transient: the field is rebuilt
63
+ * every tick from live perception.
64
+ */
65
+ export interface Affordance {
66
+ id: string
67
+ /** The MotorSchema this would enact. */
68
+ schema: string
69
+ source: AffordanceSource
70
+ /** Parameters bound from the evoking context (e.g. focus text, target name). */
71
+ parameters: Record<string, unknown>
72
+ /** Bound target entity id, when the schema binds an entity. */
73
+ targetEntityId?: string
74
+ /** Entity id of the percept / known-entity that evoked this (provenance). */
75
+ evokedBy?: string
76
+ /** Anticipated affective outcome −1..1 (learned, falling back to schema prior). */
77
+ expectedValence: number
78
+ /** Learned value estimate 0..1 for this schema in like contexts. */
79
+ expectedReward: number
80
+ /** Normalized cost 0..1 in the current body state. */
81
+ cost: number
82
+ /** Proceduralization 0..1 of the underlying schema (0 = deliberate, 1 = habit). */
83
+ habitStrength: number
84
+ /** Preconditions satisfied in the current state. */
85
+ available: boolean
86
+ /** Schema tags, carried so selection can route drives/risk without the registry. */
87
+ tags: string[]
88
+ /**
89
+ * Top-down planning bias 0..1 — set when an executing plan's frontier step
90
+ * projected this affordance (source 'plan'). It lifts activation in the
91
+ * competition WITHOUT bypassing it (the plan biases; the field still decides).
92
+ */
93
+ planBias?: number
94
+ /** Provenance: the plan whose frontier step projected this affordance. */
95
+ planId?: string
96
+ /** Provenance: the frontier step id — flows through to action.outcome so the plan advances. */
97
+ stepId?: string
98
+ tick: number
99
+ }
100
+
101
+ /**
102
+ * EfferenceCopy — the forward model's prediction of an action's own consequences,
103
+ * emitted *before* enaction so reafference has something to compare against.
104
+ */
105
+ export interface EfferenceCopy {
106
+ expectedValence: number
107
+ expectedReward: number
108
+ /** Optional predicted metric deltas the schema expects to cause. */
109
+ predictedMetricDeltas?: Record<string, number>
110
+ }
111
+
112
+ /**
113
+ * SchemaOutcome — reafference. The actual result of an enaction paired with what
114
+ * was predicted, yielding the prediction error that drives all learning.
115
+ */
116
+ export interface SchemaOutcome {
117
+ schema: string
118
+ affordanceId: string
119
+ success: boolean
120
+ /** Actual outcome quality 0..1. */
121
+ outcomeQuality: number
122
+ predicted: EfferenceCopy
123
+ /** |predicted − actual| — the teaching signal. */
124
+ surprise: number
125
+ tick: number
126
+ }
127
+
128
+ /**
129
+ * LearnedSkill — the persisted competence unit. This, not the transient
130
+ * affordance field, is what travels in the PMA and makes a grown Will *act like
131
+ * itself* after re-embodiment.
132
+ */
133
+ export interface LearnedSkill {
134
+ schema: string
135
+ /** Proceduralization 0..1 — rises with low-error success, decays with disuse. */
136
+ habitStrength: number
137
+ /** Expected reward 0..1 (EMA over outcomes). */
138
+ valueEstimate: number
139
+ /** Learned default parameters for this schema. */
140
+ paramPriors: Record<string, unknown>
141
+ enactments: number
142
+ successes: number
143
+ /** Rolling mean |prediction error|. */
144
+ avgPredictionError: number
145
+ lastEnactedTick: number
146
+ }
147
+
148
+ /** An affordance with its computed competition activation. */
149
+ export interface ScoredAffordance {
150
+ affordance: Affordance
151
+ activation: number
152
+ }
153
+
154
+ /**
155
+ * SelectionResult — the outcome of the competition. `needsDeliberation` is set
156
+ * when the field is ambiguous (high entropy) or the stakes are high, recruiting
157
+ * the executive (LLM); otherwise the winner is enacted directly (System 1).
158
+ */
159
+ export interface SelectionResult {
160
+ winner: ScoredAffordance | null
161
+ field: ScoredAffordance[]
162
+ /** 0..1 ambiguity of the competition (1 = flat field, no clear winner). */
163
+ entropy: number
164
+ needsDeliberation: boolean
165
+ }