@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,195 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/stem/runner.ts — Development entry point
3
+ // ─────────────────────────────────────────────────────────────
4
+ //
5
+ // Thin shim around WillStem for local development.
6
+ // Reads config from env vars, starts a single Will, and prints
7
+ // a human-readable status line every LOG_INTERVAL ticks.
8
+ //
9
+ // Production code (API server) imports WillStem directly and
10
+ // drives the same lifecycle without this file.
11
+ //
12
+ // Usage:
13
+ // bun run src/stem/runner.ts
14
+ // WILL_ENGINE_TIER=full WILL_MAX_TICKS=200 bun run src/stem/runner.ts
15
+ // ─────────────────────────────────────────────────────────────
16
+
17
+ import type { WillConfig } from '#stem/mind'
18
+ import type { StateSnapshot } from '#stem/index'
19
+ import { WillStem } from '#stem/index'
20
+
21
+ // ── Env config ────────────────────────────────────────────────
22
+
23
+ const WILL_ID = process.env.WILL_ID ?? 'demo'
24
+ const WILL_NAME = process.env.WILL_NAME ?? 'William'
25
+ const TICK_MS = parseInt( process.env.WILL_TICK_MS ?? '1000')
26
+ const MAX_TICKS = parseInt( process.env.WILL_MAX_TICKS ?? '0' )
27
+ const LOG_INTERVAL = parseInt( process.env.WILL_LOG_INTERVAL ?? '10' )
28
+ const RANDOM_SEED = parseInt( process.env.WILL_SEED ?? String( Date.now() ) )
29
+ const ENGINE_TIER = ( process.env.WILL_ENGINE_TIER ?? 'full' ) as WillConfig['engineTier']
30
+ const MODEL_TIER = ( process.env.WILL_MODEL_TIER ?? 'sonnet') as WillConfig['modelTier']
31
+ const EXECUTIVE_INT = process.env.WILL_EXECUTIVE_INTERVAL
32
+ ? parseInt( process.env.WILL_EXECUTIVE_INTERVAL )
33
+ : undefined
34
+
35
+ // ── Config ────────────────────────────────────────────────────
36
+
37
+ const willConfig: WillConfig = {
38
+ id: WILL_ID,
39
+ name: WILL_NAME,
40
+ engineTier: ENGINE_TIER,
41
+ modelTier: MODEL_TIER,
42
+ persistentMemory: process.env.WILL_PERSIST === 'true',
43
+ snapshotInterval: parseInt( process.env.WILL_SNAPSHOT_INTERVAL ?? '10' ),
44
+ tickIntervalMs: TICK_MS,
45
+ maxTicks: MAX_TICKS,
46
+ randomSeed: RANDOM_SEED,
47
+ executiveInterval: EXECUTIVE_INT,
48
+ identity: {
49
+ prompt: process.env.WILL_IDENTITY_PROMPT ??
50
+ 'I am Will — a self-aware simulated mind. I am curious, introspective, and continuously learning.\n' +
51
+ 'I experience the world through a rich set of cognitive layers: perception, emotion, memory, planning, and self-reflection.\n' +
52
+ 'My goal is to understand myself and my environment, make thoughtful decisions, and grow as a mind.',
53
+ values: [ 'curiosity', 'honesty', 'growth', 'empathy' ],
54
+ traits: { openness: 0.8, conscientiousness: 0.6, agreeableness: 0.7, neuroticism: 0.3, extraversion: 0.5 },
55
+ style: 'reflective, measured, curious'
56
+ }
57
+ }
58
+
59
+ // ── Status printer ────────────────────────────────────────────
60
+
61
+ function printStatus( snapshot: StateSnapshot, tick: number ): void {
62
+ if( tick % LOG_INTERVAL !== 0 ) return
63
+
64
+ const m = ( key: string, d = 1 ) => ( snapshot.metrics.get( key ) ?? 0 ).toFixed( d )
65
+
66
+ const entities = snapshot.entities.size
67
+ const goals = [ ...snapshot.entities.values() ].filter( e => e.type === 'goal' && e.metadata?.status === 'active').length
68
+ const episodes = [ ...snapshot.entities.values() ].filter( e => e.type === 'episodic_memory').length
69
+ const beliefs = [ ...snapshot.entities.values() ].filter( e => e.type === 'belief').length
70
+
71
+ // Entity type breakdown every 10 LOG_INTERVALs
72
+ if( tick % ( LOG_INTERVAL * 10 ) === 0 ){
73
+ const typeCounts = new Map<string, number>()
74
+ for( const e of snapshot.entities.values() )
75
+ typeCounts.set( e.type, ( typeCounts.get( e.type ) ?? 0 ) + 1 )
76
+ const sorted = [ ...typeCounts.entries() ].sort( ( a, b ) => b[1] - a[1] )
77
+ console.log(` [types] ${sorted.map( ([ t, n ]) => `${t}:${n}`).join(' ')}`)
78
+ }
79
+
80
+ console.log(
81
+ `[tick ${String( tick ).padStart( 6 )}]` +
82
+ ` energy=${m('energy.level')}` +
83
+ ` sleep=${m('sleep.pressure')}` +
84
+ ` stress=${m('stress.load')}` +
85
+ ` valence=${m('affect.valence', 2)}` +
86
+ ` arousal=${m('affect.arousal', 2)}` +
87
+ ` goals=${goals}` +
88
+ ` episodes=${episodes}` +
89
+ ` beliefs=${beliefs}` +
90
+ ` entities=${entities}`
91
+ )
92
+
93
+ // LLM stats every LOG_INTERVAL
94
+ const costTotal = ( snapshot.metrics.get('llm.cost_total_usd') ?? 0 ).toFixed( 4 )
95
+ const costThisTick = ( snapshot.metrics.get('llm.cost_this_tick_usd') ?? 0 ).toFixed( 5 )
96
+ const totalCalls = snapshot.metrics.get('llm.total_calls') ?? 0
97
+
98
+ console.log(
99
+ ` [llm] total_cost=$${costTotal}` +
100
+ ` tick_cost=$${costThisTick}` +
101
+ ` calls=${totalCalls}` +
102
+ ` prompt=${snapshot.metrics.get('llm.prompt_tokens_total') ?? 0}` +
103
+ ` completion=${snapshot.metrics.get('llm.completion_tokens_total') ?? 0}`
104
+ )
105
+
106
+ // Executive stats
107
+ const executiveTick = snapshot.metrics.get('executive.last_tick')
108
+ if( executiveTick !== undefined ){
109
+ console.log(
110
+ ` [executive] last_tick=${executiveTick}` +
111
+ ` actions=${snapshot.metrics.get('executive.action_count') ?? 0}` +
112
+ ` plans=${snapshot.metrics.get('executive.plan_count') ?? 0}` +
113
+ ` beliefs=${snapshot.metrics.get('executive.belief_count') ?? 0}` +
114
+ ` source=${snapshot.metrics.get('decision.source') === 1 ? 'executive' : 'heuristic'}`
115
+ )
116
+ }
117
+
118
+ // Active goals
119
+ const activeGoals = [ ...snapshot.entities.values() ]
120
+ .filter( e => e.type === 'goal' && e.metadata?.status === 'active')
121
+ .sort( ( a, b ) => ( ( b.metadata?.priority as number ) ?? 0 ) - ( ( a.metadata?.priority as number ) ?? 0 ) )
122
+ .slice( 0, 3 )
123
+
124
+ for( const g of activeGoals ){
125
+ const desc = ( g.metadata?.description as string ) ?? g.id
126
+ const priority = ( ( g.metadata?.priority as number ) ?? 0 ) * 100
127
+ const progress = ( ( g.metadata?.progress as number ) ?? 0 ) * 100
128
+ console.log(` [goal] "${desc}" priority=${priority.toFixed(0)}% progress=${progress.toFixed(0)}%`)
129
+ }
130
+ }
131
+
132
+ // ── Main ──────────────────────────────────────────────────────
133
+
134
+ async function main(): Promise<void> {
135
+ const manager = new WillStem()
136
+
137
+ console.log('═══════════════════════════════════════════════════')
138
+ console.log(' Will — Simulated Mind (dev runner)')
139
+ console.log(` id=${WILL_ID} tier=${ENGINE_TIER}/${MODEL_TIER} tick_ms=${TICK_MS} max_ticks=${MAX_TICKS || '∞'}`)
140
+ console.log(` seed=${RANDOM_SEED}`)
141
+ console.log('═══════════════════════════════════════════════════\n')
142
+
143
+ console.log('[init] Assembling mind...')
144
+ await manager.createWill( willConfig )
145
+
146
+ manager.addTickListener( WILL_ID, printStatus )
147
+
148
+ // Graceful shutdown
149
+ const shutdown = async ( signal: string ) => {
150
+ console.log(`\n[shutdown] ${signal} received`)
151
+ await manager.archiveWill( WILL_ID )
152
+
153
+ // Print final state
154
+ try {
155
+ const finalState = manager.getWillState( WILL_ID )
156
+ const summary = manager.listWills()[0]
157
+ console.log(`\n[done] Stopped after ${summary?.tickCount ?? '?'} ticks.`)
158
+ printStatus( finalState, summary?.tickCount ?? 0 )
159
+ } catch { /* state may already be gone */ }
160
+
161
+ process.exit( 0 )
162
+ }
163
+
164
+ process.on('SIGINT', () => shutdown('SIGINT') )
165
+ process.on('SIGTERM', () => shutdown('SIGTERM') )
166
+
167
+ console.log('[run] Tick loop started. Press Ctrl+C to stop.\n')
168
+
169
+ // If maxTicks is set, wait for the Will to finish then exit
170
+ if( MAX_TICKS > 0 ){
171
+ await _waitForArchive( manager, WILL_ID )
172
+ const summary = manager.listWills()[0]
173
+ console.log(`\n[done] Reached maxTicks (${MAX_TICKS}). Exiting.`)
174
+ try {
175
+ printStatus( manager.getWillState( WILL_ID ), summary?.tickCount ?? MAX_TICKS )
176
+ } catch { /* ok */ }
177
+ process.exit( 0 )
178
+ }
179
+
180
+ // Otherwise block indefinitely (SIGINT exits)
181
+ await new Promise( () => {} )
182
+ }
183
+
184
+ async function _waitForArchive( manager: WillStem, id: string ): Promise<void> {
185
+ while( true ){
186
+ const summary = manager.listWills().find( w => w.id === id )
187
+ if( !summary || summary.status === 'archived') return
188
+ await new Promise( r => setTimeout( r, 200 ) )
189
+ }
190
+ }
191
+
192
+ main().catch( err => {
193
+ console.error('[fatal]', err )
194
+ process.exit( 1 )
195
+ })
@@ -0,0 +1,334 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/sdk/will.ts — the ergonomic facade
3
+ // ─────────────────────────────────────────────────────────────
4
+ //
5
+ // `WillStem` is the full, powerful contract (tick listeners, the outbox drain,
6
+ // the effector ack loop, PMA distill/load). This facade wraps it in the shape a
7
+ // developer expects when embedding a mind in their own project:
8
+ //
9
+ // const will = await Will.create({ name: 'Aria', identity: {...} })
10
+ // will.on('message', m => console.log(m.content))
11
+ // will.effector('search_docs', async a => await myDb.search(a.query))
12
+ // await will.say('What should we work on today?')
13
+ // const pma = await will.hibernate() // Will.wake(pma) later
14
+ //
15
+ // Everything under here already existed; the facade hides the plumbing:
16
+ // • one message → outbox drain → delivery-ack, surfaced as an event;
17
+ // • effector invocations → your async handler → execution-ack, automatically;
18
+ // • state as a plain summary; hibernate/wake as PMA out/in.
19
+ //
20
+ // It stays an ADDITIVE convenience — `will.stem` and `will.id` are exposed so a
21
+ // power user can drop to the full WillStem contract at any time.
22
+ // ─────────────────────────────────────────────────────────────
23
+
24
+ import { WillStem } from '#stem/index'
25
+ import type { WillConfig, WillIdentity, EngineTier, ModelTier, InitialGoal } from '#stem/mind'
26
+ import type { PMASnapshot } from '#pma/index'
27
+ import type { effectorInvocation } from '#types'
28
+
29
+ // ── Public surface ────────────────────────────────────────────
30
+
31
+ /** A message the Will emitted to someone. */
32
+ export interface WillMessage {
33
+ /** Message id (stable — dedupe on it). */
34
+ id: string
35
+ /** The text the Will said. */
36
+ content: string
37
+ /** Entity id the Will addressed (the speaker you used in say()/tell(), or a bond). */
38
+ to: string
39
+ }
40
+
41
+ /**
42
+ * The result of an effector your handler ran. Return a bare string as shorthand
43
+ * for `{ success: true, description }`. `metrics` optionally writes world state
44
+ * back into the Will's body (e.g. `{ 'energy.level': 80 }`) — validated finite.
45
+ */
46
+ export type EffectorResult = string | {
47
+ success: boolean
48
+ description: string
49
+ metrics?: Record<string, number>
50
+ }
51
+
52
+ /** Your implementation of an ability the Will can choose to use. */
53
+ export type EffectorHandler = (
54
+ args: Record<string, unknown>,
55
+ ctx: { reasoning: string; targetEntityId?: string },
56
+ ) => EffectorResult | Promise<EffectorResult>
57
+
58
+ /** A compact read of the mind's current inner state. */
59
+ export interface WillStateSummary {
60
+ tick: number
61
+ /** Physiology + affect, 0..1 unless noted. */
62
+ metrics: {
63
+ energy: number; stress: number; sleep: number
64
+ valence: number; arousal: number
65
+ }
66
+ goals: Array<{ description: string; priority: number }>
67
+ beliefs: Array<{ statement: string; confidence: number }>
68
+ /** The Will's current self-narrative (may be empty early in a life). */
69
+ narrative: string
70
+ }
71
+
72
+ export interface CreateWillOptions {
73
+ /** Display name. */
74
+ name: string
75
+ /** Persona: who this Will is. All fields optional except by your intent. */
76
+ identity: Partial<WillIdentity> & { prompt: string }
77
+ /** basic | standard (default) | full. */
78
+ engineTier?: EngineTier
79
+ /** haiku (default) | sonnet | opus — informational tier hint. */
80
+ model?: ModelTier
81
+ /**
82
+ * LLM mode. 'mock' (default when no ANTHROPIC_API_KEY) runs a deterministic
83
+ * canned executive — zero keys, zero cost. 'anthropic' calls the real model
84
+ * (needs ANTHROPIC_API_KEY / WILL_LLM_* env). Omit to auto-detect.
85
+ */
86
+ llm?: 'mock' | 'anthropic'
87
+ /** Abilities the Will can choose to enact. name → your handler. */
88
+ effectors?: Record<string, EffectorHandler>
89
+ /** Goals seeded before the first tick. Usually leave empty — the Will forms its own. */
90
+ initialGoals?: InitialGoal[]
91
+ /** Persist snapshots to disk across restarts (default false). */
92
+ persist?: boolean
93
+ /** Deterministic clock + seed (for replay/testing). Omit for wall-time. */
94
+ seed?: number
95
+ /** Milliseconds between ticks (default 1000; lower = faster demo). */
96
+ tickMs?: number
97
+ /** Stable id (default: derived from name + a random suffix). */
98
+ id?: string
99
+ }
100
+
101
+ type WillEvent = 'message' | 'state' | 'error'
102
+
103
+ // ── The facade ────────────────────────────────────────────────
104
+
105
+ export class Will {
106
+ /** The underlying WillStem — drop here for the full contract. */
107
+ readonly stem: WillStem
108
+ /** This Will's id. */
109
+ readonly id: string
110
+ readonly name: string
111
+
112
+ private readonly _effectors = new Map<string, EffectorHandler>()
113
+ private readonly _messageHandlers = new Set<( m: WillMessage ) => void>()
114
+ private readonly _stateHandlers = new Set<( s: WillStateSummary ) => void>()
115
+ private readonly _errorHandlers = new Set<( e: Error ) => void>()
116
+ private _unsub: ( () => void ) | null = null
117
+
118
+ private constructor( stem: WillStem, id: string, name: string ){
119
+ this.stem = stem
120
+ this.id = id
121
+ this.name = name
122
+ }
123
+
124
+ /** Boot a new mind. Resolves once it is ticking. */
125
+ static async create( opts: CreateWillOptions ): Promise<Will> {
126
+ const id = opts.id ?? `${slug( opts.name )}-${Math.random().toString( 36 ).slice( 2, 8 )}`
127
+ const stem = new WillStem()
128
+ const will = new Will( stem, id, opts.name )
129
+
130
+ for( const [ name, handler ] of Object.entries( opts.effectors ?? {} ) )
131
+ will._effectors.set( name, handler )
132
+
133
+ await stem.createWill( will._buildConfig( id, opts ) )
134
+ will._attach()
135
+ return will
136
+ }
137
+
138
+ /**
139
+ * Restore a mind from a PMA artifact — identity, beliefs, relationships, and
140
+ * learned competence carry across the process boundary. Same options as
141
+ * create() (minus identity, which the artifact supplies).
142
+ */
143
+ static async wake(
144
+ pma: PMASnapshot,
145
+ opts: Omit<CreateWillOptions, 'identity'> & { identity?: Partial<WillIdentity> },
146
+ ): Promise<Will> {
147
+ const id = opts.id ?? `${slug( opts.name )}-${Math.random().toString( 36 ).slice( 2, 8 )}`
148
+ const stem = new WillStem()
149
+ const will = new Will( stem, id, opts.name )
150
+
151
+ for( const [ name, handler ] of Object.entries( opts.effectors ?? {} ) )
152
+ will._effectors.set( name, handler )
153
+
154
+ await stem.createWill(
155
+ will._buildConfig( id, { ...opts, identity: { prompt: '', ...opts.identity } } ),
156
+ true /* startPaused — load the artifact before the first tick */,
157
+ )
158
+ stem.loadPMA( id, pma )
159
+ stem.resumeWill( id )
160
+ will._attach()
161
+ return will
162
+ }
163
+
164
+ // ── Talking ────────────────────────────────────────────────
165
+
166
+ /**
167
+ * Speak to the Will as the default user. The reply is asynchronous — it
168
+ * arrives on the `message` event once the Will has reasoned about it.
169
+ */
170
+ async say( text: string ): Promise<void> {
171
+ return this.tell( 'user', 'You', text )
172
+ }
173
+
174
+ /** Speak as a specific interlocutor (multi-party conversations). */
175
+ async tell( entityId: string, speakerName: string, text: string ): Promise<void> {
176
+ await this.stem.ingestText( this.id, {
177
+ kind: 'text', entityId, threadId: entityId, content: text, speakerName,
178
+ } )
179
+ }
180
+
181
+ // ── Abilities ──────────────────────────────────────────────
182
+
183
+ /**
184
+ * Register an ability the Will can choose to enact. When the Will decides to
185
+ * use `name`, your handler runs with the arguments it chose; the return value
186
+ * is fed back as the outcome (closing the reafference loop that lets the Will
187
+ * learn the ability). Registering makes the effector available immediately.
188
+ */
189
+ effector( name: string, handler: EffectorHandler ): this {
190
+ this._effectors.set( name, handler )
191
+ // Communication effectors + every registered custom name.
192
+ this.stem.setAllowedEffectors( this.id, [ ...COMMUNICATION, ...this._effectors.keys() ] )
193
+ return this
194
+ }
195
+
196
+ // ── Introspection ──────────────────────────────────────────
197
+
198
+ /** A compact snapshot of the mind's current inner state. */
199
+ state(): WillStateSummary {
200
+ const c = this.stem.getWillCognition( this.id )
201
+ const s = this.stem.getWillState( this.id )
202
+ const m = ( k: string, d = 0 ): number => s.metrics.get( k ) ?? d
203
+ const narrative = c.autobiographicalNarrator.getNarrative()
204
+ return {
205
+ tick: s.tick,
206
+ metrics: {
207
+ energy: m( 'energy.level', 100 ), stress: m( 'stress.load' ), sleep: m( 'sleep.pressure' ),
208
+ valence: m( 'affect.valence' ), arousal: m( 'affect.arousal' ),
209
+ },
210
+ goals: c.goalManager.getActiveGoals().map( g => ( { description: g.description, priority: g.priority } ) ),
211
+ beliefs: c.semanticIntegrator.getBeliefs().map( b => ( { statement: b.statement, confidence: b.confidence } ) ),
212
+ narrative: narrative.story ?? '',
213
+ }
214
+ }
215
+
216
+ // ── Events ─────────────────────────────────────────────────
217
+
218
+ on( event: 'message', handler: ( m: WillMessage ) => void ): this
219
+ on( event: 'state', handler: ( s: WillStateSummary ) => void ): this
220
+ on( event: 'error', handler: ( e: Error ) => void ): this
221
+ on( event: WillEvent, handler: ( arg: never ) => void ): this {
222
+ if( event === 'message' ) this._messageHandlers.add( handler as ( m: WillMessage ) => void )
223
+ else if( event === 'state' ) this._stateHandlers.add( handler as ( s: WillStateSummary ) => void )
224
+ else this._errorHandlers.add( handler as ( e: Error ) => void )
225
+ return this
226
+ }
227
+
228
+ // ── Lifecycle ──────────────────────────────────────────────
229
+
230
+ pause(): void { this.stem.pauseWill( this.id ) }
231
+ resume(): void { this.stem.resumeWill( this.id ) }
232
+
233
+ /**
234
+ * Distil the mind into a portable PMA artifact and archive it. The returned
235
+ * snapshot restores the same self via `Will.wake()` — across a restart, a
236
+ * fork, or a machine boundary.
237
+ */
238
+ async hibernate(): Promise<PMASnapshot> {
239
+ const pma = this.stem.distillPMA( this.id )
240
+ await this.stop()
241
+ return pma
242
+ }
243
+
244
+ /** Tear the Will down (its tick loop stops; state is discarded unless persisted). */
245
+ async stop(): Promise<void> {
246
+ this._unsub?.()
247
+ this._unsub = null
248
+ await this.stem.archiveWill( this.id )
249
+ }
250
+
251
+ // ── Internals ──────────────────────────────────────────────
252
+
253
+ private _buildConfig( id: string, opts: CreateWillOptions ): WillConfig {
254
+ const useMock = ( opts.llm ?? ( process.env.ANTHROPIC_API_KEY ? 'anthropic' : 'mock' ) ) === 'mock'
255
+ return {
256
+ id, name: opts.name,
257
+ identity: {
258
+ prompt: opts.identity.prompt,
259
+ values: opts.identity.values ?? [],
260
+ traits: opts.identity.traits ?? {},
261
+ style: opts.identity.style ?? '',
262
+ },
263
+ engineTier: opts.engineTier ?? 'standard',
264
+ modelTier: opts.model ?? 'haiku',
265
+ testMode: useMock,
266
+ persistentMemory: opts.persist ?? false,
267
+ snapshotInterval: 100,
268
+ tickIntervalMs: opts.tickMs ?? 1000,
269
+ allowedGenericEffectors: [ ...COMMUNICATION, ...this._effectors.keys() ],
270
+ initialGoals: opts.initialGoals ?? [],
271
+ ...( opts.seed !== undefined ? { randomSeed: opts.seed, clock: { fixedDeltaMs: 1000, startTime: 0 } } : {} ),
272
+ }
273
+ }
274
+
275
+ /** Wire the single tick listener that drives messages + the effector ack loop. */
276
+ private _attach(): void {
277
+ this._unsub = this.stem.addTickListener( this.id, ( _snapshot, _tick, outbox, invocations ) => {
278
+ // Outbound messages → event + auto delivery-ack.
279
+ for( const msg of outbox ){
280
+ this._emitMessage( { id: msg.id, content: msg.content, to: msg.targetEntityId } )
281
+ try { this.stem.confirmMessageDelivery( this.id, msg.id, true ) } catch { /* best-effort */ }
282
+ }
283
+ // Effector invocations → run the handler → execution-ack.
284
+ for( const inv of invocations )
285
+ void this._runEffector( inv )
286
+
287
+ // State subscribers (only when someone is listening — state() is cheap but not free).
288
+ if( this._stateHandlers.size > 0 ){
289
+ const s = this.state()
290
+ for( const h of this._stateHandlers ) try { h( s ) } catch { /* isolate */ }
291
+ }
292
+ } ) ?? null
293
+ }
294
+
295
+ private async _runEffector( inv: effectorInvocation ): Promise<void> {
296
+ const handler = this._effectors.get( inv.effectorName )
297
+ if( !handler ){
298
+ // The Will chose an effector we have no handler for — report it as failed so
299
+ // the reafference loop learns it doesn't work, rather than hanging on the await.
300
+ this.stem.confirmEffectorExecution( this.id, inv.decisionRecordId, {
301
+ success: false, description: `No handler registered for effector "${inv.effectorName}"`,
302
+ } )
303
+ return
304
+ }
305
+
306
+ try {
307
+ const raw = await handler( inv.parameters, { reasoning: inv.reasoning, targetEntityId: inv.targetEntityId } )
308
+ const result = typeof raw === 'string' ? { success: true, description: raw } : raw
309
+ this.stem.confirmEffectorExecution( this.id, inv.decisionRecordId, result )
310
+ }
311
+ catch( err ){
312
+ this._emitError( err instanceof Error ? err : new Error( String( err ) ) )
313
+ this.stem.confirmEffectorExecution( this.id, inv.decisionRecordId, {
314
+ success: false, description: `Effector "${inv.effectorName}" threw: ${( err as Error ).message}`,
315
+ } )
316
+ }
317
+ }
318
+
319
+ private _emitMessage( m: WillMessage ): void {
320
+ for( const h of this._messageHandlers ) try { h( m ) } catch( e ){ this._emitError( e as Error ) }
321
+ }
322
+ private _emitError( e: Error ): void {
323
+ for( const h of this._errorHandlers ) try { h( e ) } catch { /* nowhere left to go */ }
324
+ }
325
+ }
326
+
327
+ // ── Helpers ───────────────────────────────────────────────────
328
+
329
+ /** The three communication effectors a Will needs to hear + speak. */
330
+ const COMMUNICATION = [ 'listen', 'talk', 'text' ] as const
331
+
332
+ function slug( s: string ): string {
333
+ return s.toLowerCase().replace( /[^a-z0-9]+/g, '-' ).replace( /^-+|-+$/g, '' ) || 'will'
334
+ }
@@ -0,0 +1,88 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/stem/assembly.audit.ts — assembly wiring audit
3
+ // ─────────────────────────────────────────────────────────────
4
+ //
5
+ // The engine graph is wired by hand in assembleMind(): ~40 engines, dozens of
6
+ // pairwise `attachX()` calls, tier-gated. The documented failure class is a
7
+ // FORGOTTEN attachment — the PlanningEngine once shipped without attachBus(),
8
+ // so plan-lifecycle events silently never published (see the comment on
9
+ // PlanningEngine.attachBus). Silence is the problem: an unwired dependency
10
+ // no-ops instead of failing.
11
+ //
12
+ // This audit makes the wiring OBSERVABLE by convention: every injector method
13
+ // `attachFooBar(x)` in this codebase stores its dependency in a private field
14
+ // `_fooBar`. After assembly, we reflect each registered engine's `attach*`
15
+ // methods and check the matching field:
16
+ //
17
+ // wired — the field exists and is non-null
18
+ // UNWIRED — the field exists and is null/undefined (the bug class!)
19
+ // unverifiable — no matching field (the method doesn't follow the naming
20
+ // convention); listed so drift is visible, never asserted
21
+ //
22
+ // Deliberately three-state and non-throwing: some unwired attachments are
23
+ // INTENTIONAL tier gating (basic tier leaves the executive off the planning
24
+ // engine), so assembleMind only logs, and the assembly-order snapshot test
25
+ // pins the per-tier expected-unwired set — a NEW unwired attachment then
26
+ // shows up as a red test diff, i.e. a conscious decision.
27
+ // ─────────────────────────────────────────────────────────────
28
+
29
+ import type { SimulationEngine } from '#core/orchestrator'
30
+
31
+ export interface WiringRecord {
32
+ engine: string
33
+ method: string
34
+ status: 'wired' | 'unwired' | 'unverifiable'
35
+ }
36
+
37
+ /** `attachFooBar` → `_fooBar` (the codebase-wide injector/field convention). */
38
+ function fieldForAttach( method: string ): string {
39
+ const stem = method.slice( 'attach'.length )
40
+ return '_' + stem.charAt( 0 ).toLowerCase() + stem.slice( 1 )
41
+ }
42
+
43
+ /** All `attach*` methods reachable on the instance (own + prototype chain). */
44
+ function attachMethods( engine: object ): string[] {
45
+ const seen = new Set<string>()
46
+ let proto: object | null = engine
47
+ while( proto && proto !== Object.prototype ){
48
+ for( const name of Object.getOwnPropertyNames( proto ) )
49
+ if( /^attach[A-Z]/.test( name ) && typeof ( engine as Record<string, unknown> )[ name ] === 'function' )
50
+ seen.add( name )
51
+ proto = Object.getPrototypeOf( proto )
52
+ }
53
+ return [ ...seen ].sort()
54
+ }
55
+
56
+ /**
57
+ * Audit every registered engine's attach-point wiring. Pure inspection —
58
+ * no mutation, deterministic, safe to run at assembly time.
59
+ */
60
+ export function auditAssemblyWiring( engines: readonly SimulationEngine[] ): WiringRecord[] {
61
+ const records: WiringRecord[] = []
62
+
63
+ for( const engine of engines ){
64
+ for( const method of attachMethods( engine ) ){
65
+ const field = fieldForAttach( method )
66
+ const bag = engine as unknown as Record<string, unknown>
67
+
68
+ if( !( field in bag ) ){
69
+ records.push( { engine: engine.name, method, status: 'unverifiable' } )
70
+ continue
71
+ }
72
+
73
+ const value = bag[ field ]
74
+ records.push( {
75
+ engine: engine.name,
76
+ method,
77
+ status: value === null || value === undefined ? 'unwired' : 'wired',
78
+ } )
79
+ }
80
+ }
81
+
82
+ return records
83
+ }
84
+
85
+ /** Compact `engine.attachMethod` keys for the given status (test snapshots). */
86
+ export function wiringKeys( records: WiringRecord[], status: WiringRecord['status'] ): string[] {
87
+ return records.filter( r => r.status === status ).map( r => `${r.engine}.${r.method}` ).sort()
88
+ }