@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,384 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/agency/affordance.synthesizer.ts — head of the agency pipeline
3
+ // ─────────────────────────────────────────────────────────────
4
+ //
5
+ // The AffordanceSynthesizer is the Gibsonian move: instead of a static catalog
6
+ // the executive selects from, perception *emits* the field of what is possible
7
+ // right now, for this body, in this state. It runs every tick with no LLM.
8
+ //
9
+ // Each tick it draws on:
10
+ // • the innate floor — reflexive, objectless possibilities (always present)
11
+ // • salient percepts — "inspect this", bound to what perception surfaced
12
+ // • known sentient minds — "reach out to them", bound to the relationship
13
+ // • (Phase 3) the repertoire of learned composite skills
14
+ //
15
+ // Two properties make it mind-shaped, not framework-shaped:
16
+ // 1. Parameters are bound *at perception time* from the evoking thing, so an
17
+ // affordance can never arrive at execution with empty arguments.
18
+ // 2. Attention gates the WIDTH of the field — a depleted or unfocused mind
19
+ // literally has fewer possibilities available to it. The body shapes the
20
+ // option set, not just the choice.
21
+ //
22
+ // Output: transient `affordance` entities (the field is rebuilt every tick),
23
+ // plus metrics and an `affordance.field.synthesized` bus event for the selector
24
+ // and telemetry. See AGENCY_PIPELINE_TODO.md.
25
+ // ─────────────────────────────────────────────────────────────
26
+
27
+ import { logger } from '#core/logger'
28
+ import type {
29
+ Duration, Tick, SimulationContext,
30
+ ReadonlySimulationState, StateCommands, EntityInput,
31
+ } from '#core/types'
32
+ import type { CognitiveBus } from '#cognition/bus'
33
+ import type { CognitiveEngine, EngineResult } from '#cognition/types'
34
+ import type { CognitiveEventSchema } from '#cognition/schema.registry'
35
+ import type { Affordance, AffordanceSource, MotorSchema, LearnedSkill, SchemaPrecondition } from '#agency/types'
36
+ import type { SchemaRepertoire } from '#agency/schemas/repertoire'
37
+ import { INNATE_SCHEMAS } from '#agency/schemas/innate'
38
+ import { collectGoalTargets } from '#agency/selection.scoring'
39
+
40
+ /** Default field width for non-innate affordances when no attention metric exists. */
41
+ const DEFAULT_ATTENTION_CAP = 5
42
+ /** Hard ceiling so a flood of percepts can never explode the field. */
43
+ const MAX_ATTENTION_CAP = 12
44
+ /** Pre-activation floor for an ideomotor candidate — the executive deliberately willed
45
+ * it, so it should reliably reach the field, but it still competes in the selector. */
46
+ const IDEOMOTOR_BASE_SALIENCE = 0.5
47
+
48
+ type SkillAccessor = () => ReadonlyMap<string, LearnedSkill>
49
+
50
+ interface Candidate {
51
+ /** Coarse evoke-salience used only for attention budgeting (selection scores later). */
52
+ salience: number
53
+ affordance: Affordance
54
+ }
55
+
56
+ export class AffordanceSynthesizer implements CognitiveEngine {
57
+ readonly name = 'affordance-synthesizer'
58
+
59
+ private _schemas: MotorSchema[]
60
+ private _skills: SkillAccessor | null = null
61
+ private _repertoire: SchemaRepertoire | null = null
62
+ private _bus: CognitiveBus | null = null
63
+ private _defaultCap: number
64
+ private _lastFieldSize = 0
65
+
66
+ constructor( schemas: MotorSchema[] = INNATE_SCHEMAS, defaultCap = DEFAULT_ATTENTION_CAP ){
67
+ this._schemas = schemas
68
+ this._defaultCap = defaultCap
69
+ }
70
+
71
+ // ── wiring ────────────────────────────────────────────────────
72
+ attachBus( bus: CognitiveBus ): void { this._bus = bus }
73
+ /** Inject the repertoire's learned-skill accessor (Phase 3). */
74
+ attachSkills( accessor: SkillAccessor ): void { this._skills = accessor }
75
+ /**
76
+ * Attach the live repertoire — its templates (innate floor + learned composites)
77
+ * become the schema set, and its skills feed the affordance priors. This is how
78
+ * a newly-learned composite shows up in the field without a restart.
79
+ */
80
+ attachRepertoire( repertoire: SchemaRepertoire ): void { this._repertoire = repertoire }
81
+ /** Register an additional schema template (e.g. a learned composite). */
82
+ registerSchema( schema: MotorSchema ): void { this._schemas.push( schema ) }
83
+
84
+ // ── CognitiveEngine interface ─────────────────────────────────
85
+ publishes(): CognitiveEventSchema[] {
86
+ return [ { type: 'affordance.field.synthesized', version: 1, validate: () => null } ]
87
+ }
88
+ subscribes(): string[] { return [] }
89
+ onCognitiveEvent(): void { /* pull model — reads frozen state each tick */ }
90
+ snapshot(): Record<string, unknown> { return { lastFieldSize: this._lastFieldSize } }
91
+
92
+ // ── react ─────────────────────────────────────────────────────
93
+ async react(
94
+ _delta: Duration,
95
+ tick: Tick,
96
+ state: ReadonlySimulationState,
97
+ _context: SimulationContext,
98
+ ): Promise<EngineResult> {
99
+ // Rehydrate learned composites the executor needs to resolve. After a
100
+ // snapshot/restore the repertoire is rebuilt innate-only; the composite
101
+ // *definitions* come back as `agency.schema` entities (written by the
102
+ // ReafferenceEngine). Doing this here — the first agency engine each tick —
103
+ // means the executor, which ticks later in the SAME tick, sees a whole
104
+ // repertoire and can expand a restored mid-flight macro. Idempotent.
105
+ this._repertoire?.restoreComposites( state.entities )
106
+
107
+ const schemas = this._repertoire?.schemas() ?? this._schemas
108
+ const skills = this._skills?.() ?? this._repertoire?.skills() ?? null
109
+ const valence = metric( state, 'affect.valence', 0 )
110
+ const energyLow = metric( state, 'energy.level', 0 ) < 30
111
+
112
+ const set: EntityInput[] = []
113
+ const del: string[] = []
114
+
115
+ // Clear the previous tick's field — affordances are transient.
116
+ for( const [ id, e ] of state.entities )
117
+ if( e.type === 'affordance' ) del.push( id )
118
+
119
+ // ── 1. innate floor — always emitted, never attention-capped ──
120
+ const floor = schemas.filter( s => s.binds === 'none' )
121
+ for( const schema of floor )
122
+ set.push( this._toEntity(
123
+ this._build( schema, tick, state, valence, energyLow, skills, {} ),
124
+ ) )
125
+
126
+ // ── 2. perception-bound candidates, attention-budgeted ───────
127
+ // Goal-relevance counts at the CAP, not only at selection: an affordance whose
128
+ // target an active goal is directed at is lifted here so goal-driven outreach
129
+ // isn't out-competed by ambient rumination before the selector (which also
130
+ // scores goalRelevance) ever sees it. Same `goalTargets` notion as the selector.
131
+ const candidates: Candidate[] = []
132
+ const goalTargets: Map<string, number> = collectGoalTargets( state )
133
+
134
+ const perceptSchema = schemas.find( s => s.binds === 'percept' )
135
+ if( perceptSchema )
136
+ for( const [ id, e ] of state.entities ){
137
+ if( e.type !== 'percept' ) continue
138
+ const m = e.metadata
139
+ const salience = num( m?.['salience'], 0 )
140
+ const summary = str( m?.['summary'] ) ?? str( m?.['category'] ) ?? 'something'
141
+ const target = str( m?.['entityId'] ) ?? str( m?.['targetEntityId'] )
142
+ candidates.push({
143
+ salience: salience + ( target ? goalTargets.get( target ) ?? 0 : 0 ),
144
+ affordance: this._build( perceptSchema, tick, state, valence, energyLow, skills, {
145
+ evokedBy: id,
146
+ targetEntityId: target,
147
+ parameters: { focus: summary },
148
+ } ),
149
+ })
150
+ }
151
+
152
+ const entitySchema = schemas.find( s => s.binds === 'entity' )
153
+ if( entitySchema )
154
+ for( const [ id, e ] of state.entities ){
155
+ if( e.type !== 'known-entity' ) continue
156
+ const m = e.metadata
157
+ if( str( m?.['kind'] ) !== 'sentient' ) continue
158
+ const keid = str( m?.['keid'] ) ?? id
159
+ const fam = num( m?.['familiarity'], 0 )
160
+ const val = num( m?.['valence'], 0 )
161
+ const res = num( m?.['resolutionConfidence'], 0 )
162
+ candidates.push({
163
+ salience: fam * 0.6 + Math.max( 0, val ) * 0.3 + res * 0.1 + ( goalTargets.get( keid ) ?? 0 ),
164
+ affordance: this._build( entitySchema, tick, state, valence, energyLow, skills, {
165
+ evokedBy: id,
166
+ targetEntityId: keid,
167
+ parameters: { targetEntityName: str( m?.['name'] ) ?? keid },
168
+ } ),
169
+ })
170
+ }
171
+
172
+ // ── ideomotor candidates: the executive's imagined actions, pre-activated ──
173
+ // The executive writes `ideomotor.intent` entities for the actions it imagines
174
+ // (its "what if I…"). They enter the field as HIGH-salience candidates — it
175
+ // deliberately willed them — but still COMPETE in the selector; they never bypass
176
+ // it. This is the AffordanceSource.ideomotor leg: executive intention → an
177
+ // affordance that competes like any other.
178
+ for( const [ id, e ] of state.entities ){
179
+ if( e.type !== 'ideomotor.intent' ) continue
180
+ const m = e.metadata
181
+ const schemaId = str( m?.['schema'] )
182
+ const schema = schemaId ? schemas.find( s => s.id === schemaId ) : undefined
183
+ if( !schema ) continue
184
+ candidates.push({
185
+ salience: IDEOMOTOR_BASE_SALIENCE + num( m?.['priority'], 0.8 ),
186
+ affordance: this._build( schema, tick, state, valence, energyLow, skills, {
187
+ evokedBy: id,
188
+ targetEntityId: str( m?.['targetEntityId'] ),
189
+ parameters: ( m?.['parameters'] as Record<string, unknown> ) ?? {},
190
+ source: 'ideomotor',
191
+ } ),
192
+ })
193
+ }
194
+
195
+ // ── plan priors: an executing plan's frontier step, pre-activated ──────────
196
+ // A plan does not dispatch its steps; it BIASES the competition toward the
197
+ // action its current frontier needs. The PlanningEngine writes one `plan.prior`
198
+ // per ready frontier step; each enters the field as a HIGH-salience candidate
199
+ // (the frontier is willed) carrying a `planBias` the scorer weighs and the
200
+ // planId/stepId provenance that flows through to action.outcome so the plan
201
+ // advances when the field actually enacts it. Like ideomotor, it never bypasses
202
+ // the selector — if a more pressing affordance wins, the plan simply re-projects
203
+ // next tick. The suggested schema must resolve in the repertoire; if it does not,
204
+ // the prior cannot surface as an action and the plan waits / replans.
205
+ for( const [ id, e ] of state.entities ){
206
+ if( e.type !== 'plan.prior' ) continue
207
+ const m = e.metadata
208
+ const schemaId = str( m?.['schema'] )
209
+ const schema = schemaId ? schemas.find( s => s.id === schemaId ) : undefined
210
+ if( !schema ) continue
211
+ const planBias = clamp01( num( m?.['planBias'], 0.6 ) )
212
+ candidates.push({
213
+ salience: IDEOMOTOR_BASE_SALIENCE + planBias,
214
+ affordance: this._build( schema, tick, state, valence, energyLow, skills, {
215
+ evokedBy: id,
216
+ targetEntityId: str( m?.['targetEntityId'] ),
217
+ parameters: ( m?.['parameters'] as Record<string, unknown> ) ?? {},
218
+ source: 'plan',
219
+ planBias,
220
+ planId: str( m?.['planId'] ),
221
+ stepId: str( m?.['stepId'] ),
222
+ } ),
223
+ })
224
+ }
225
+
226
+ // Attention gates the WIDTH of the field: keep only the top-salient few.
227
+ const cap = this._attentionCap( state )
228
+ candidates.sort( ( a, b ) => b.salience - a.salience )
229
+ for( const c of candidates.slice( 0, cap ) )
230
+ set.push( this._toEntity( c.affordance ) )
231
+
232
+ // ── 3. metrics + telemetry ───────────────────────────────────
233
+ const fieldSize = set.length
234
+ const availableCount = set.reduce( ( n, e ) => n + ( e.metadata?.['available'] ? 1 : 0 ), 0 )
235
+ this._lastFieldSize = fieldSize
236
+
237
+ const commands: StateCommands = {
238
+ set,
239
+ delete: del,
240
+ metrics: [
241
+ [ 'affordance.field_size', fieldSize ],
242
+ [ 'affordance.available_count', availableCount ],
243
+ ],
244
+ }
245
+
246
+ if( this._bus ){
247
+ try {
248
+ this._bus.publish({
249
+ type: 'affordance.field.synthesized',
250
+ version: 1,
251
+ sourceEngine: this.name,
252
+ salience: 0.3,
253
+ payload: { size: fieldSize, availableCount, tick },
254
+ })
255
+ }
256
+ catch( err ){
257
+ logger.warn( `[affordance] bus publish failed: ${ err instanceof Error ? err.message : String( err ) }` )
258
+ }
259
+ }
260
+
261
+ return { commands }
262
+ }
263
+
264
+ // ── internals ─────────────────────────────────────────────────
265
+
266
+ /** Compose an Affordance from a schema + the evoking context, folding in learned priors. */
267
+ private _build(
268
+ schema: MotorSchema,
269
+ tick: Tick,
270
+ state: ReadonlySimulationState,
271
+ valence: number,
272
+ energyLow: boolean,
273
+ skills: ReadonlyMap<string, LearnedSkill> | null,
274
+ ctx: {
275
+ evokedBy?: string
276
+ targetEntityId?: string
277
+ parameters?: Record<string, unknown>
278
+ source?: AffordanceSource
279
+ planBias?: number
280
+ planId?: string
281
+ stepId?: string
282
+ },
283
+ ): Affordance {
284
+ const skill = skills?.get( schema.id )
285
+
286
+ // Learned value if known, else the schema's intrinsic prior mapped to 0..1.
287
+ const expectedReward = skill?.valueEstimate
288
+ ?? clamp01( ( ( schema.baseValence ?? 0 ) + 1 ) / 2 )
289
+ const expectedValence = schema.baseValence ?? valence
290
+ const habitStrength = skill?.habitStrength ?? 0
291
+ // Low energy makes everything feel costlier.
292
+ const cost = clamp01( schema.cost * ( energyLow ? 1.5 : 1 ) )
293
+
294
+ const key = ctx.targetEntityId ?? ctx.evokedBy ?? schema.id
295
+ const source = ctx.source ?? schema.source
296
+ // A non-default-source candidate (ideomotor) gets a distinct id so it can coexist
297
+ // with a same-schema/target candidate evoked from another source (e.g. entity-bound).
298
+ const idTag = ctx.source && ctx.source !== schema.source ? `-${ ctx.source }` : ''
299
+
300
+ return {
301
+ id: `affordance-${ tick }-${ schema.id }-${ key }${ idTag }`,
302
+ schema: schema.id,
303
+ source,
304
+ parameters: { ...( skill?.paramPriors ?? {} ), ...( ctx.parameters ?? {} ) },
305
+ targetEntityId: ctx.targetEntityId,
306
+ evokedBy: ctx.evokedBy,
307
+ expectedValence,
308
+ expectedReward,
309
+ cost,
310
+ habitStrength,
311
+ available: this._available( schema.preconditions, ( k ) => metric( state, k, 0 ) ),
312
+ tags: schema.tags ?? [],
313
+ planBias: ctx.planBias,
314
+ planId: ctx.planId,
315
+ stepId: ctx.stepId,
316
+ tick,
317
+ }
318
+ }
319
+
320
+ private _toEntity( a: Affordance ): EntityInput {
321
+ return {
322
+ id: a.id,
323
+ type: 'affordance',
324
+ metadata: {
325
+ schema: a.schema,
326
+ source: a.source,
327
+ parameters: a.parameters,
328
+ targetEntityId: a.targetEntityId,
329
+ evokedBy: a.evokedBy,
330
+ expectedValence: a.expectedValence,
331
+ expectedReward: a.expectedReward,
332
+ cost: a.cost,
333
+ habitStrength: a.habitStrength,
334
+ available: a.available,
335
+ tags: a.tags,
336
+ planBias: a.planBias,
337
+ planId: a.planId,
338
+ stepId: a.stepId,
339
+ tick: a.tick,
340
+ },
341
+ }
342
+ }
343
+
344
+ private _available(
345
+ preconditions: SchemaPrecondition[] | undefined,
346
+ read: ( metric: string ) => number,
347
+ ): boolean {
348
+ if( !preconditions ) return true
349
+ return preconditions.every( pc => {
350
+ const cur = read( pc.metric )
351
+ switch( pc.op ){
352
+ case 'gt': return cur > pc.value
353
+ case 'lt': return cur < pc.value
354
+ case 'gte': return cur >= pc.value
355
+ case 'lte': return cur <= pc.value
356
+ case 'eq': return cur === pc.value
357
+ default: return false
358
+ }
359
+ })
360
+ }
361
+
362
+ private _attentionCap( state: ReadonlySimulationState ): number {
363
+ const cap = state.metrics.get( 'attention.capacity' ) ?? this._defaultCap
364
+ return Math.max( 1, Math.min( MAX_ATTENTION_CAP, Math.round( cap ) ) )
365
+ }
366
+ }
367
+
368
+ // ─── module helpers ──────────────────────────────────────────────────────────
369
+
370
+ function metric( state: ReadonlySimulationState, key: string, fallback: number ): number {
371
+ return state.metrics.get( key ) ?? fallback
372
+ }
373
+
374
+ function num( v: unknown, fallback: number ): number {
375
+ return typeof v === 'number' && Number.isFinite( v ) ? v : fallback
376
+ }
377
+
378
+ function str( v: unknown ): string | undefined {
379
+ return typeof v === 'string' ? v : undefined
380
+ }
381
+
382
+ function clamp01( n: number ): number {
383
+ return n < 0 ? 0 : n > 1 ? 1 : n
384
+ }
@@ -0,0 +1,241 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/agency/deliberation.engine.ts — the System-2 seam
3
+ // ─────────────────────────────────────────────────────────────
4
+ //
5
+ // The DeliberationEngine is the only place the LLM touches the agency pipeline,
6
+ // and it does so through the SAME unified-inference path as everything else: it
7
+ // spawns a facet — "a focused instance of the executive consciousness" — which
8
+ // builds its prompt from the unified PromptFactory (persona, identity, live
9
+ // self-context). There is NO bespoke prompt here. This is the project-wide rule:
10
+ // every LLM call shares one self, so the Will never fractures / hallucinates a
11
+ // different identity when it deliberates vs. converses vs. narrates.
12
+ //
13
+ // It is recruited only when the selector marks a choice 'deliberating' (ambiguous
14
+ // or high-stakes). It then biases the competition — choosing among the candidate
15
+ // affordances the substrate already surfaced (ideomotor: imagining outcomes
16
+ // pre-activates one) — and writes the result back as the now-'selected' intent.
17
+ // It never invents actions outside the field. Message *content* authoring stays
18
+ // with the unified conversation facet path (AuditionEngine), not duplicated here.
19
+ //
20
+ // With no executive attached (basic tier) or the facet budget full, it confirms
21
+ // the substrate's provisional winner — graceful System-1 degradation, never a
22
+ // stall, and never an un-grounded LLM call.
23
+ // ─────────────────────────────────────────────────────────────
24
+
25
+ import { logger } from '#core/logger'
26
+ import type {
27
+ Duration, Tick, SimulationContext,
28
+ ReadonlySimulationState, StateCommands, EntityInput,
29
+ } from '#core/types'
30
+ import type { CognitiveBus } from '#cognition/bus'
31
+ import type { CognitiveEngine, EngineResult } from '#cognition/types'
32
+ import type { CognitiveEventSchema } from '#cognition/schema.registry'
33
+
34
+ // ── Minimal structural view of the executive's facet machinery ───────────────
35
+ // Kept structural (not an import of the executive engine) so the agency module
36
+ // stays decoupled; ExecutiveEngine is assignable to DeliberationFacetProvider.
37
+
38
+ interface DeliberationFacetHandle {
39
+ setFocus( focus: { title: string; content: string; instructions?: string; function?: string } ): void
40
+ report( report: { type: string; payload: unknown } ): Promise<void> | void
41
+ subscribe( listener: ( decision: { decision: unknown } ) => void ): () => void
42
+ destroy(): void
43
+ }
44
+ export interface DeliberationFacetProvider {
45
+ spawnFacet(): { attention: 'available' | 'full'; handle?: DeliberationFacetHandle }
46
+ }
47
+
48
+ interface Candidate {
49
+ schema: string
50
+ targetEntityId?: string
51
+ parameters?: Record<string, unknown>
52
+ activation?: number
53
+ /** Channel B: this candidate is an active plan's current frontier step. */
54
+ fromPlan?: boolean
55
+ }
56
+
57
+ const DELIBERATION_INSTRUCTIONS =
58
+ 'Automatic action-selection was uncertain or the stakes were high. From the candidate actions ' +
59
+ 'listed above, choose the ONE that best fits who you are and your situation. Do not invent ' +
60
+ 'actions that are not listed. Put your chosen action as your single action; its "type" must be ' +
61
+ 'exactly one of the candidate names.'
62
+
63
+ export class DeliberationEngine implements CognitiveEngine {
64
+ readonly name = 'deliberation'
65
+
66
+ private _provider: DeliberationFacetProvider | null = null
67
+ private _handle: DeliberationFacetHandle | null = null
68
+ private _bus: CognitiveBus | null = null
69
+ private _willName = 'the mind'
70
+ private _deliberations = 0
71
+
72
+ attachBus( bus: CognitiveBus ): void { this._bus = bus }
73
+ /**
74
+ * Attach the executive (facet provider). Absent ⇒ the engine confirms the
75
+ * substrate's winner (System 1) — used at basic tier where no LLM runs.
76
+ */
77
+ attachExecutive( provider: DeliberationFacetProvider ): void { this._provider = provider }
78
+ setWillName( name: string ): void { this._willName = name }
79
+
80
+ publishes(): CognitiveEventSchema[] {
81
+ return [ { type: 'agency.deliberated', version: 1, validate: () => null } ]
82
+ }
83
+ subscribes(): string[] { return [] }
84
+ onCognitiveEvent(): void { /* pull model — reads deliberating intents from frozen state */ }
85
+ snapshot(): Record<string, unknown> { return { deliberations: this._deliberations } }
86
+
87
+ async react(
88
+ _delta: Duration,
89
+ tick: Tick,
90
+ state: ReadonlySimulationState,
91
+ _context: SimulationContext,
92
+ ): Promise<EngineResult> {
93
+ // One deliberation per tick (the serial bottleneck).
94
+ let target: { id: string; meta: Record<string, unknown> } | null = null
95
+ for( const [ id, e ] of state.entities ){
96
+ if( e.type !== 'agency.intent' ) continue
97
+ if( str( e.metadata?.['status'] ) !== 'deliberating' ) continue
98
+ const meta = ( e.metadata ?? {} ) as Record<string, unknown>
99
+ if( !target || id < target.id ) target = { id, meta } // stable pick
100
+ }
101
+ if( !target ) return { commands: {} }
102
+
103
+ const { id, meta } = target
104
+ const provisional = str( meta['schema'] ) ?? 'wait'
105
+ const candidates: Candidate[] = Array.isArray( meta['candidates'] ) ? ( meta['candidates'] as Candidate[] ) : []
106
+
107
+ // No executive → confirm the substrate's winner (graceful System-1).
108
+ if( !this._provider )
109
+ return { commands: { set: [ this._commit( id, meta, provisional, candidates, 'no-executive' ) ] } }
110
+
111
+ // Deliberate through a UNIFIED facet (same persona/context as the master).
112
+ const chosen = await this._deliberate( state, candidates, provisional, meta )
113
+
114
+ this._deliberations++
115
+ if( this._bus ){
116
+ try {
117
+ this._bus.publish({
118
+ type: 'agency.deliberated', version: 1, sourceEngine: this.name,
119
+ salience: 0.6, payload: { intentId: id, chosen, tick },
120
+ })
121
+ }
122
+ catch { /* unregistered schema is telemetry-only */ }
123
+ }
124
+
125
+ return {
126
+ commands: {
127
+ set: [ this._commit( id, meta, chosen, candidates, 'facet' ) ],
128
+ metrics: [ [ 'agency.deliberation.count', 1 ] ],
129
+ },
130
+ }
131
+ }
132
+
133
+ // ── internals ─────────────────────────────────────────────────
134
+
135
+ /** Run one unified-inference deliberation. Returns the chosen schema (or the provisional winner on any failure). */
136
+ private async _deliberate(
137
+ state: ReadonlySimulationState,
138
+ candidates: Candidate[],
139
+ provisional: string,
140
+ meta: Record<string, unknown>,
141
+ ): Promise<string> {
142
+ try {
143
+ if( !this._handle ){
144
+ const spawned = this._provider!.spawnFacet()
145
+ if( spawned.attention === 'full' || !spawned.handle ){
146
+ logger.info( '[deliberation] facet budget full — confirming substrate winner' )
147
+ return provisional
148
+ }
149
+ this._handle = spawned.handle
150
+ }
151
+
152
+ let capturedDecision: unknown = undefined
153
+ const unsub = this._handle.subscribe( d => { capturedDecision = d.decision } )
154
+
155
+ this._handle.setFocus({
156
+ title: 'Deliberation',
157
+ function: 'deliberation',
158
+ content: this._buildFocusContent( state, candidates, meta ),
159
+ instructions: DELIBERATION_INSTRUCTIONS,
160
+ })
161
+ await this._handle.report({ type: 'deliberation', payload: { candidateSchemas: candidates.map( c => c.schema ) } })
162
+ unsub()
163
+
164
+ const picked = extractChosen( capturedDecision, candidates )
165
+ return picked ?? provisional
166
+ }
167
+ catch( err ){
168
+ // Director/state not ready (e.g. executive hasn't run yet) or LLM failure →
169
+ // fall back to the substrate winner. Drop a possibly-dead handle.
170
+ this._handle = null
171
+ logger.warn( `[deliberation] facet unavailable, confirming winner: ${ err instanceof Error ? err.message : String( err ) }` )
172
+ return provisional
173
+ }
174
+ }
175
+
176
+ /** Write the deliberating intent back as 'selected' with the chosen action. */
177
+ private _commit(
178
+ id: string,
179
+ meta: Record<string, unknown>,
180
+ chosen: string,
181
+ candidates: Candidate[],
182
+ via: string,
183
+ ): EntityInput {
184
+ const cand = candidates.find( c => c.schema === chosen )
185
+ return {
186
+ id, type: 'agency.intent',
187
+ metadata: {
188
+ ...meta,
189
+ schema: chosen,
190
+ targetEntityId: cand?.targetEntityId ?? meta['targetEntityId'],
191
+ parameters: { ...( ( meta['parameters'] as Record<string, unknown> ) ?? {} ), ...( cand?.parameters ?? {} ) },
192
+ status: 'selected',
193
+ deliberated: true,
194
+ deliberatedVia: via,
195
+ },
196
+ }
197
+ }
198
+
199
+ /** The deliberation focus body — the candidate actions the substrate surfaced. */
200
+ private _buildFocusContent(
201
+ state: ReadonlySimulationState,
202
+ candidates: Candidate[],
203
+ meta: Record<string, unknown>,
204
+ ): string {
205
+ const lines: string[] = []
206
+ // Channel B: if this choice arose by interrupting a pending action, name it so the
207
+ // facet owns the interruption in-character rather than reasoning in a vacuum.
208
+ const preemptedFrom = str( meta['preemptedFrom'] )
209
+ if( preemptedFrom )
210
+ lines.push( `You just broke off a pending action ("${ preemptedFrom }") because something more pressing pulled at you. Decide what to do now:` )
211
+ else
212
+ lines.push( 'Your automatic action-selection was uncertain. Candidate actions:' )
213
+ candidates.forEach( ( c, i ) => {
214
+ const to = c.targetEntityId ? ` toward ${ c.targetEntityId }` : ''
215
+ // Channel B: name the plan link so the facet chooses as the self pursuing it,
216
+ // not blindly among labels ("this one is the next step of the plan I'm on").
217
+ const plan = c.fromPlan ? " — your current plan's next step" : ''
218
+ lines.push( `${ i + 1 }. ${ c.schema }${ to }${ plan }` )
219
+ })
220
+ return lines.join( '\n' )
221
+ }
222
+ }
223
+
224
+ // ─── decoding ────────────────────────────────────────────────────────────────
225
+
226
+ /** Pull the chosen schema from a facet decision, validated against the candidates. */
227
+ function extractChosen( decision: unknown, candidates: Candidate[] ): string | undefined {
228
+ if( !decision || typeof decision !== 'object' ) return undefined
229
+ const actions = ( decision as Record<string, unknown> )['actions']
230
+ if( !Array.isArray( actions ) ) return undefined
231
+ const valid = new Set( candidates.map( c => c.schema ) )
232
+ for( const a of actions ){
233
+ const type = ( a as Record<string, unknown> )?.['type']
234
+ if( typeof type === 'string' && valid.has( type ) ) return type
235
+ }
236
+ return undefined
237
+ }
238
+
239
+ function str( v: unknown ): string | undefined {
240
+ return typeof v === 'string' ? v : undefined
241
+ }
@@ -0,0 +1,54 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/agency/instruction.intake.ts — instruction → goal
3
+ // ─────────────────────────────────────────────────────────────
4
+ //
5
+ // External instructions (developer/host directives injected into state) become
6
+ // the Will's own goals here. This role used to live inside the legacy
7
+ // ActionExecutor; on the agency cutover it moves to a small dedicated intake so
8
+ // the legacy executor can be retired. Behaviour is preserved verbatim: each tick
9
+ // it drains pending instructions and converts them to goals via the same
10
+ // InstructionHandler + GoalManager.
11
+ // ─────────────────────────────────────────────────────────────
12
+
13
+ import type {
14
+ Duration, Tick, SimulationContext, ReadonlySimulationState,
15
+ } from '#core/types'
16
+ import type { CognitiveEngine, EngineResult } from '#cognition/types'
17
+ import type { CognitiveEventSchema } from '#cognition/schema.registry'
18
+ import type { InstructionHandler } from '#cognition/instruction.handler'
19
+ import type { GoalManager } from '#faculties/goal.manager'
20
+
21
+ export class InstructionIntake implements CognitiveEngine {
22
+ readonly name = 'instruction-intake'
23
+
24
+ private _instructionHandler: InstructionHandler | null = null
25
+ private _goalManager: GoalManager | null = null
26
+
27
+ attachInstructionHandler( h: InstructionHandler ): void { this._instructionHandler = h }
28
+ attachGoalManager( gm: GoalManager ): void { this._goalManager = gm }
29
+
30
+ publishes(): CognitiveEventSchema[] { return [] }
31
+ subscribes(): string[] { return [] }
32
+ onCognitiveEvent(): void { /* pull model — reads pending instructions from state */ }
33
+ snapshot(): Record<string, unknown> {
34
+ return { pending: this._instructionHandler?.getPending().length ?? 0 }
35
+ }
36
+
37
+ async react(
38
+ _delta: Duration,
39
+ _tick: Tick,
40
+ state: ReadonlySimulationState,
41
+ _context: SimulationContext,
42
+ ): Promise<EngineResult> {
43
+ const handler = this._instructionHandler
44
+ const goals = this._goalManager
45
+ if( !handler || !goals ) return { commands: {} }
46
+
47
+ for( const instruction of handler.evaluatePending( state, [] ) ){
48
+ const g = handler.convertToGoal( instruction )
49
+ goals.addGoal( g.description, g.priority, g.tags, undefined, undefined, g.completionType, g.completionCondition )
50
+ }
51
+
52
+ return { commands: {} }
53
+ }
54
+ }