@mindot/will 0.3.0 → 0.4.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 (50) hide show
  1. package/README.md +33 -11
  2. package/dist/channels/discord.d.ts +78 -0
  3. package/dist/channels/discord.js +193 -0
  4. package/dist/channels/discord.js.map +1 -0
  5. package/dist/cli.js +648 -330
  6. package/dist/cli.js.map +1 -1
  7. package/dist/index.d.ts +141 -141
  8. package/dist/index.js +390 -316
  9. package/dist/index.js.map +1 -1
  10. package/dist/mcp/effectors.d.ts +1 -1
  11. package/dist/{will-B5eKs3Wv.d.ts → will-D-slky1N.d.ts} +4011 -3916
  12. package/package.json +6 -1
  13. package/src/channels/discord.ts +214 -0
  14. package/src/channels/roster.ts +87 -0
  15. package/src/channels/types.ts +46 -0
  16. package/src/cli.ts +34 -9
  17. package/src/cognition/agency/engines/deliberation.engine.ts +7 -7
  18. package/src/cognition/agency/execution.primitives.ts +11 -11
  19. package/src/cognition/agency/proactive.communicator.ts +8 -8
  20. package/src/cognition/config.mirror.entities.ts +2 -2
  21. package/src/cognition/conversation.memory.ts +1 -1
  22. package/src/cognition/faculties/executive.engine/commands.ts +7 -15
  23. package/src/cognition/faculties/executive.engine/engine.ts +64 -34
  24. package/src/cognition/faculties/executive.engine/escalation.buffer.ts +1 -1
  25. package/src/cognition/faculties/executive.engine/facet.ts +1 -1
  26. package/src/cognition/faculties/executive.engine/prompt.factory.ts +76 -61
  27. package/src/cognition/faculties/executive.engine/types.ts +1 -1
  28. package/src/cognition/faculties/introspection.engine.ts +1 -2
  29. package/src/cognition/faculties/planning.engine/engine.ts +38 -1
  30. package/src/cognition/faculties/planning.engine/plan.store.ts +42 -0
  31. package/src/cognition/faculties/planning.engine/plan.supervision.ts +7 -7
  32. package/src/cognition/faculties/theory.of.mind.ts +2 -2
  33. package/src/cognition/senses/audition.engine/engine.ts +21 -21
  34. package/src/host/boot.ts +70 -4
  35. package/src/llm/summarizer.ts +2 -2
  36. package/src/profiles/companion.ts +14 -14
  37. package/src/profiles/company-brain.ts +19 -19
  38. package/src/profiles/customer-service.ts +17 -17
  39. package/src/profiles/game-npc.ts +10 -10
  40. package/src/profiles/index.ts +2 -2
  41. package/src/profiles/smart-home.ts +16 -16
  42. package/src/runners/outreach.runner.ts +6 -9
  43. package/src/runners/social.runner.ts +1 -4
  44. package/src/runners/thin-shim.runner.ts +4 -6
  45. package/src/sdk/will.ts +27 -10
  46. package/src/stem/guards/identity.coherence.ts +5 -3
  47. package/src/stem/guards/identity.guard.ts +20 -9
  48. package/src/stem/index.ts +7 -7
  49. package/src/stem/mind.ts +182 -98
  50. package/src/stem/tracts/outbox.controller.ts +2 -2
@@ -94,6 +94,8 @@ export class PlanningEngine implements SimulationEngine, CognitiveEngine {
94
94
  * replay state) from off-tick callbacks like _activateStep / _onStepOutcome.
95
95
  */
96
96
  private _lastTick = 0
97
+ /** One-time deletion of legacy `plan-executive-*` entities (see react step 0a). */
98
+ private _legacyPlanSweepDone = false
97
99
 
98
100
  /**
99
101
  * Monotonic suffix counter for activity-listener subscription ids. These ids
@@ -205,7 +207,31 @@ export class PlanningEngine implements SimulationEngine, CognitiveEngine {
205
207
  planId?: string; stepId?: string
206
208
  }
207
209
 
208
- if( !p.planId || !p.stepId ) return
210
+ // Conscious-enaction credit: outcomes carry plan provenance only when
211
+ // the plan's OWN frontier prior won the competition. But the plan is a
212
+ // prior over WHAT to do — if the self does the very thing an active
213
+ // step calls for by any route (executive action via ideomotor, habit),
214
+ // the step is done. Without this, a mind that consciously performs its
215
+ // plan starves the plan of credit: steps stay active, the goal reads
216
+ // blocked, and the executive re-authors the same plan over and over
217
+ // (observed live: 8 authorings for one goal, zero completions).
218
+ // Deterministic: stores iterate in insertion order; first match wins.
219
+ if( !p.planId || !p.stepId ){
220
+ if( !p.actionType || typeof p.success !== 'boolean' ) return
221
+ for( const plan of this._store.all() ){
222
+ if( plan.status !== 'executing' ) continue
223
+ const step = plan.steps.find( s => s.status === 'active' && s.action === p.actionType )
224
+ if( !step ) continue
225
+ logger.info( `[planning] conscious-enaction credit: ${plan.id}/${step.id}=${step.action} (no provenance on outcome)` )
226
+ this._onStepOutcome( plan.id, step.id, {
227
+ success: p.success,
228
+ description: p.description ?? ( p.success ? 'Completed' : 'Failed' ),
229
+ outcomeQuality: p.outcomeQuality,
230
+ } )
231
+ return // credit exactly one step per outcome
232
+ }
233
+ return
234
+ }
209
235
  if( !this._store.has( p.planId ) ) return
210
236
 
211
237
  this._onStepOutcome( p.planId, p.stepId, {
@@ -280,6 +306,17 @@ export class PlanningEngine implements SimulationEngine, CognitiveEngine {
280
306
  this._lastTick = tick as unknown as number
281
307
  const commands: StateCommands = { set: [], delete: [], metrics: [] }
282
308
 
309
+ // 0a. Legacy sweep (once per session): delete the raw `plan-executive-*`
310
+ // entities the executive commands path used to write in parallel with
311
+ // this engine's ingest. They froze at 'ready' forever and polluted the
312
+ // Active-Plans awareness (phantom drafts → re-authoring pressure).
313
+ if( !this._legacyPlanSweepDone ){
314
+ this._legacyPlanSweepDone = true
315
+ for( const entity of state.entities.values() )
316
+ if( entity.type === 'plan' && entity.id.startsWith('plan-executive-') )
317
+ commands.delete!.push( entity.id )
318
+ }
319
+
283
320
  // 0. Channel A (subconscious): refresh trait-driven dispositions from the
284
321
  // persona-prior mirror before acting on them this tick.
285
322
  this._readConfigFromState( state )
@@ -13,6 +13,21 @@
13
13
  import type { Tick, StateCommands } from '#core/types'
14
14
  import { TERMINAL_STATUSES, type Plan } from '#faculties/planning.engine/types'
15
15
 
16
+ /**
17
+ * Structural fingerprint of a plan's decomposition — the ordered action
18
+ * sequence plus each step's prerequisite fan-in. Two plans with the same key
19
+ * share a *shape*. Recurring shapes are the demonstrated-need signal for
20
+ * emergent planning (docs/strategy/__EMERGENT_PLANNING.md): the mind paying
21
+ * the executive to re-derive a decomposition it has already authored.
22
+ */
23
+ export function planShapeKey(
24
+ steps: ReadonlyArray<{ action: string; prerequisites?: readonly string[] }>,
25
+ ): string {
26
+ return steps
27
+ .map( s => `${ s.action }${ ( s.prerequisites?.length ?? 0 ) > 0 ? `<${ s.prerequisites!.length }` : '' }` )
28
+ .join('>')
29
+ }
30
+
16
31
  export class PlanStore {
17
32
  /**
18
33
  * Canonical plan store, keyed by plan.id ("plan-N") — the id the execution,
@@ -35,6 +50,14 @@ export class PlanStore {
35
50
  /** planId → sim tick it became terminal; drives retention GC (gcTerminal). */
36
51
  private _terminalAt = new Map<string, number>()
37
52
  private _planCounter = 0
53
+ /**
54
+ * Shape recurrence tally — shapeKey → count of plans authored with that
55
+ * decomposition this session (monotone; eviction never erases history).
56
+ * Feeds `planning.shapes.*` metrics: the demonstrated-need needle for
57
+ * emergent planning. `_shapeCounted` guards one count per plan id.
58
+ */
59
+ private _shapeCounts = new Map<string, number>()
60
+ private _shapeCounted = new Set<string>()
38
61
 
39
62
  // ── Reads ──────────────────────────────────────────────────
40
63
 
@@ -142,6 +165,14 @@ export class PlanStore {
142
165
 
143
166
  persist( commands: StateCommands, tick: Tick ): void {
144
167
  for( const plan of this._plans.values() ){
168
+ // Shape recurrence — tally each plan once, on first sight. The persisted
169
+ // shapeKey also lets offline analysis count recurrence across sessions.
170
+ const shape = planShapeKey( plan.steps )
171
+ if( !this._shapeCounted.has( plan.id ) ){
172
+ this._shapeCounted.add( plan.id )
173
+ this._shapeCounts.set( shape, ( this._shapeCounts.get( shape ) ?? 0 ) + 1 )
174
+ }
175
+
145
176
  // Terminal plans never change again — persist once (in their terminal state)
146
177
  // then skip, so completed/failed/rejected plans don't re-serialize every
147
178
  // tick forever (unbounded write amplification on long sessions). (P5)
@@ -153,6 +184,7 @@ export class PlanStore {
153
184
  createdAt: plan.createdAt, updatedAt: tick,
154
185
  metadata: {
155
186
  goalId: plan.goalId,
187
+ shapeKey: shape,
156
188
  steps: plan.steps.map( s => ( {
157
189
  id: s.id, order: s.order, action: s.action,
158
190
  description: s.description, expectedOutcome: s.expectedOutcome,
@@ -170,5 +202,15 @@ export class PlanStore {
170
202
 
171
203
  if( terminal ) this._persistedTerminal.add( plan.id )
172
204
  }
205
+
206
+ // The demonstrated-need needle: distinct shapes vs repeat authorings.
207
+ // A high repeat count means the executive keeps re-deriving decompositions
208
+ // it already produced — exactly when emergent planning starts paying.
209
+ if( this._shapeCounts.size > 0 ){
210
+ let total = 0
211
+ for( const n of this._shapeCounts.values() ) total += n
212
+ commands.metrics!.push( [ 'planning.shapes.distinct', this._shapeCounts.size ] )
213
+ commands.metrics!.push( [ 'planning.shapes.repeats', total - this._shapeCounts.size ] )
214
+ }
173
215
  }
174
216
  }
@@ -105,7 +105,7 @@ export class PlanSupervisor {
105
105
  if( !this._executiveEngine ) return
106
106
 
107
107
  try {
108
- const { attention, handle: facet } = this._executiveEngine.spawnFacet()
108
+ const { attention, handle: facet } = this._executiveEngine.spawnFacet('supervision')
109
109
  if( !facet || attention === 'full' ){
110
110
  plan.executionTier = 'automatic'
111
111
  logger.info( `[planning] attention full — plan ${plan.id} stays automatic (no facet)` )
@@ -206,21 +206,21 @@ export class PlanSupervisor {
206
206
  content: focusContent,
207
207
  outputFormat: undefined, // use standard executive output format
208
208
  instructions:
209
- `You are monitoring plan "${plan.id}" for goal "${plan.goalId}".\n`+
210
- `Your ONLY role: evaluate step outcomes and decide what happens next.\n`+
209
+ `I am monitoring plan "${plan.id}" for goal "${plan.goalId}".\n`+
210
+ `My ONLY role: evaluate step outcomes and decide what happens next.\n`+
211
211
  `Do not create new goals or beliefs unless directly relevant to this plan.\n\n`+
212
212
  `## Decision Vocabulary\n`+
213
- `Express your decision as the FIRST action in your actions array:\n`+
213
+ `Express my decision as the FIRST action in my actions array:\n`+
214
214
  `- { "type": "continue" } — proceed to the next step\n`+
215
215
  `- { "type": "retry" } — re-attempt the failed step (capped)\n`+
216
216
  `- { "type": "skip" } — skip the failed step and move on\n`+
217
217
  `- { "type": "pause" } — hold the plan; resume it later (no progress now)\n`+
218
218
  `- { "type": "replan" } — include a [PLANS] block with revised steps\n`+
219
- `- { "type": "escalate" } — hand the decision up to your master self\n`+
219
+ `- { "type": "escalate" } — hand the decision up to my master self\n`+
220
220
  `- { "type": "abandon" } — plan is unrecoverable; give up entirely\n`+
221
221
  `- { "type": "complete" } — all meaningful work is done; close the plan\n\n`+
222
- `For "replan", include a [PLANS] block inside your reasoning with new steps.\n`+
223
- `The plan's expectedOutcome tells you what success looks like — use it to judge step reports.`,
222
+ `For "replan", include a [PLANS] block inside my reasoning with new steps.\n`+
223
+ `The plan's expectedOutcome tells me what success looks like — use it to judge step reports.`,
224
224
  extractDecision: ( rawOutput: unknown ) => {
225
225
  const output = rawOutput as ExecutiveOutputFull
226
226
 
@@ -226,8 +226,8 @@ export class TheoryOfMind implements SimulationEngine, CognitiveEngine {
226
226
  * snapshot/PMA restore — mirrors AttachmentEvaluator/ReputationTracker._restoreFromState.
227
227
  * The entity stores a gist (modelConfidence + the dominant intention + estimated emotion),
228
228
  * not the full belief/observation arrays, so the restored model is a coherent gist that
229
- * subsequent interactions grow from — the soul-true level: you recover your *sense* of a
230
- * mind, not every belief you once inferred about it.
229
+ * subsequent interactions grow from — the soul-true level: the Will recovers its
230
+ * *sense* of a mind, not every belief it once inferred about it.
231
231
  */
232
232
  private _restoreFromState( state: ReadonlySimulationState ): void {
233
233
  for( const entity of state.entities.values() ){
@@ -122,22 +122,22 @@ interface CoalesceWindow {
122
122
  const CONVERSATION_OUTPUT_FORMAT = `\
123
123
  ## Response Format (REQUIRED)
124
124
 
125
- Step 1 — JSON object (your private reasoning, optionally in a \`\`\`json code block):
125
+ Step 1 — JSON object (my private reasoning, optionally in a \`\`\`json code block):
126
126
 
127
127
  \`\`\`json
128
128
  {
129
129
  "actions": [{"type": "reflect", "reasoning": "...", "expectedOutcome": "..."}],
130
- "reasoning": "Your private inner reasoning. Embed optional tagged blocks here:\\n[BELIEFS]\\n{\\"newBeliefs\\": [...]}\\n[/BELIEFS]\\n[GOALS_NEW]\\n{\\"goals\\": [{...}]}\\n[/GOALS_NEW]",
130
+ "reasoning": "My private inner reasoning. Embed optional tagged blocks here:\\n[BELIEFS]\\n{\\"newBeliefs\\": [...]}\\n[/BELIEFS]\\n[GOALS_NEW]\\n{\\"goals\\": [{...}]}\\n[/GOALS_NEW]",
131
131
  "confidence": 0.8
132
132
  }
133
133
  \`\`\`
134
134
 
135
135
  Available reasoning tags: BELIEFS, GOALS_NEW, GOALS_ABANDON, SELF_OBS. Include only those with meaningful content.
136
136
 
137
- Step 2 — Your reply to the speaker (plain text, streamed live to them):
137
+ Step 2 — My reply to the speaker (plain text, streamed live to them):
138
138
 
139
139
  [REPLY_TEXT]
140
- Your response here, written in your own voice.
140
+ My response here, written in my own voice.
141
141
 
142
142
  Start a new paragraph (blank line) to send a separate chat bubble.
143
143
  [/REPLY_TEXT]
@@ -146,24 +146,24 @@ Write [REPLY_TEXT] AFTER the closing \`\`\`. This is the only part the speaker s
146
146
  Separate multiple messages with a blank line for natural conversational pauses (like separate texts).
147
147
 
148
148
  ## When to use GOALS_NEW (almost always)
149
- If the speaker requests, mentions, or implies something you should follow through on — embed [GOALS_NEW] in your reasoning.
149
+ If the speaker requests, mentions, or implies something I should follow through on — embed [GOALS_NEW] in my reasoning.
150
150
  This tracks intent across future cycles without requiring master attention.
151
151
 
152
152
  ## When to use the escalate action (rare — only for multi-step tasks)
153
- Use \`{"type": "escalate", "reasoning": "...", "expectedOutcome": "..."}\` in actions ONLY when the request genuinely requires your master consciousness to create a plan:
153
+ Use \`{"type": "escalate", "reasoning": "...", "expectedOutcome": "..."}\` in actions ONLY when the request genuinely requires my master consciousness to create a plan:
154
154
  - The task involves multiple steps across future cycles ("build me X", "monitor Y", "set up Z")
155
- - The request changes your active goal priorities in a significant way
156
- - You need to coordinate something beyond a single reply
155
+ - The request changes my active goal priorities in a significant way
156
+ - I need to coordinate something beyond a single reply
157
157
 
158
158
  **The "reasoning" field on the escalate action becomes the task description the master sees.**
159
- Make it concrete — describe WHAT needs to happen, not just that you are escalating.
159
+ Make it concrete — describe WHAT needs to happen, not just that I am escalating.
160
160
  Good: type=escalate, reasoning="User wants weekly mood summaries by email every Monday. Needs: data aggregation, schedule, email delivery.", expectedOutcome="Weekly email delivered."
161
161
  Bad: type=escalate, reasoning="Escalating because this is complex."
162
162
 
163
- When you escalate:
163
+ When I escalate:
164
164
  1. STILL include a [REPLY_TEXT] that acknowledges the request (e.g. "Got it — I'm on it.")
165
165
  2. The master will create and execute the plan in the background
166
- 3. Do NOT include [PLANS] yourself — plan creation is the master's domain only
166
+ 3. Do NOT include a [PLANS] block — plan creation is the master's domain only
167
167
 
168
168
  For simple, single-exchange requests (questions, opinions, short tasks) — do NOT escalate. Just reply.`
169
169
 
@@ -561,7 +561,7 @@ export class AuditionEngine extends BaseSenseEngine {
561
561
  let handle = this._facets.get( percept.speakerEntityId )
562
562
  if( !handle ){
563
563
  // New conversation session — try to spawn a facet.
564
- const result = this._executiveEngine.spawnFacet()
564
+ const result = this._executiveEngine.spawnFacet('conversation')
565
565
  if( result.attention === 'full' || !result.handle ){
566
566
  logger.warn(
567
567
  `[audition-engine] Executive attention full — ` +
@@ -692,8 +692,8 @@ export class AuditionEngine extends BaseSenseEngine {
692
692
  awarenessEntityId: percept.speakerEntityId,
693
693
 
694
694
  instructions: [
695
- 'You are in a live conversation with this person. Respond as yourself.',
696
- 'Stay grounded in your real memories and feelings — do not invent experiences you have no record of.',
695
+ 'I am in a live conversation with this person. I respond as myself.',
696
+ 'I stay grounded in my real memories and feelings — I do not invent experiences I have no record of.',
697
697
  ].join(' '),
698
698
 
699
699
  // Custom output format — uses [REPLY_TEXT] block for streamed reply.
@@ -736,7 +736,7 @@ export class AuditionEngine extends BaseSenseEngine {
736
736
  */
737
737
  async authorOutreach( entityId: string, entityName: string, gist?: string ): Promise<string[]> {
738
738
  if( !this._executiveEngine ) return []
739
- const spawned = this._executiveEngine.spawnFacet()
739
+ const spawned = this._executiveEngine.spawnFacet('outreach')
740
740
  if( spawned.attention === 'full' || !spawned.handle ){
741
741
  logger.warn(`[audition-engine] facet budget full — cannot author outreach to ${ entityId }`)
742
742
  return []
@@ -747,17 +747,17 @@ export class AuditionEngine extends BaseSenseEngine {
747
747
  title: 'Reaching out',
748
748
  function: 'outreach',
749
749
  content: [
750
- `You have decided, on your own initiative, to reach out to ${ entityName } (id: ${ entityId }).`,
751
- 'No one prompted this — it is you choosing to make contact now.',
752
- gist ? `What is on your mind: ${ gist }` : '',
750
+ `I have decided, on my own initiative, to reach out to ${ entityName } (id: ${ entityId }).`,
751
+ 'No one prompted this — I am choosing to make contact now.',
752
+ gist ? `What is on my mind: ${ gist }` : '',
753
753
  ].filter( Boolean ).join('\n'),
754
754
  recallQuery: gist ?? entityName,
755
755
  awareness: [ ...DEFAULT_FACET_AWARENESS, 'plans' ],
756
756
  awarenessEntityId: entityId,
757
757
  instructions:
758
- 'Considering who you are, your goals, and how you feel, say what you genuinely want to say to ' +
759
- 'them now. Speak as yourself; stay grounded in your real memories — do not invent experiences ' +
760
- 'you have no record of.',
758
+ 'Considering who I am, my goals, and how I feel, I say what I genuinely want to say to ' +
759
+ 'them now. I speak as myself; I stay grounded in my real memories — I do not invent experiences ' +
760
+ 'I have no record of.',
761
761
  outputFormat: CONVERSATION_OUTPUT_FORMAT,
762
762
  extractDecision: ( raw: unknown ): ConversationDecision => {
763
763
  const output = raw as ExecutiveOutputFull
package/src/host/boot.ts CHANGED
@@ -43,12 +43,70 @@ function slug( s: string ): string {
43
43
  return s.toLowerCase().replace( /[^a-z0-9]+/g, '-' ).replace( /^-+|-+$/g, '' ) || 'will'
44
44
  }
45
45
 
46
+ /**
47
+ * Ask the executive's provider one trivial question BEFORE raising the mind.
48
+ *
49
+ * A Will whose LLM fails cannot reason, and an unreasoning Will is *silent* —
50
+ * which is precisely what a Will that chose silence looks like. Mid-run that
51
+ * ambiguity is the paradigm working. At boot it is indistinguishable from
52
+ * broken, and costs an operator an afternoon of watching a mind that joined,
53
+ * perceived, and never spoke. So we fail loudly here instead.
54
+ *
55
+ * Config errors (bad key, empty balance, unknown model) are fatal — the Will
56
+ * would never speak. Transient ones (rate limit, provider 5xx) only warn: the
57
+ * mind is worth raising, and the executive retries on its own cadence.
58
+ *
59
+ * Skipped for the mock executive and the no-LLM `reflex` anatomy.
60
+ */
61
+ async function preflightLLM( anatomy: string ): Promise<void> {
62
+ const mode = process.env.WILL_LLM ?? ( process.env.ANTHROPIC_API_KEY ? 'anthropic' : 'mock' )
63
+ if( mode === 'mock' || anatomy === 'reflex' ) return
64
+
65
+ const key = process.env.WILL_LLM_API_KEY ?? process.env.ANTHROPIC_API_KEY
66
+ if( !key ){
67
+ console.error( '[will] WILL_LLM=anthropic but no ANTHROPIC_API_KEY / WILL_LLM_API_KEY is set.' )
68
+ console.error( '[will] The Will would boot, perceive, and never speak. Set a key, or run keyless with WILL_LLM=mock.' )
69
+ process.exit( 2 )
70
+ }
71
+
72
+ // The ping validates key + balance + reachability, which is the failure class
73
+ // that strands an operator. It uses the pinned model when there is one, so a
74
+ // bad model id is caught too; otherwise the cheapest model stands in.
75
+ const base = process.env.WILL_LLM_BASE_URL ?? 'https://api.anthropic.com/v1'
76
+ const model = process.env.WILL_LLM_MODEL ?? 'claude-haiku-4-5-20251001'
77
+ try {
78
+ const res = await fetch( `${ base }/messages`, {
79
+ method: 'POST',
80
+ headers: { 'content-type': 'application/json', 'anthropic-version': '2023-06-01', 'x-api-key': key },
81
+ body: JSON.stringify( { model, max_tokens: 1, messages: [ { role: 'user', content: 'ping' } ] } ),
82
+ signal: AbortSignal.timeout( 20_000 ),
83
+ } )
84
+ if( res.ok ) return
85
+
86
+ const detail = ( await res.text().catch( () => '' ) ).slice( 0, 300 )
87
+ const fatal = res.status === 400 || res.status === 401 || res.status === 403
88
+ if( !fatal ){
89
+ console.error( `[will] the executive's LLM answered ${ res.status } on a test call — raising the mind anyway (it retries): ${ detail }` )
90
+ return
91
+ }
92
+ console.error( `[will] the executive's LLM refused a test call (${ res.status }) — this Will would boot, perceive, and never speak:` )
93
+ console.error( ` ${ detail }` )
94
+ console.error( '[will] fix the key / credit / model above, or run keyless with WILL_LLM=mock.' )
95
+ process.exit( 1 )
96
+ }
97
+ catch( e ){
98
+ console.error( `[will] could not reach the executive's LLM: ${ ( e as Error ).message }` )
99
+ console.error( '[will] the Will would boot and stay silent. Check the network / WILL_LLM_BASE_URL, or run keyless with WILL_LLM=mock.' )
100
+ process.exit( 1 )
101
+ }
102
+ }
103
+
46
104
  export interface BootedWill {
47
105
  will: Will
48
106
  name: string
49
107
  pmaPath: string
50
108
  tickMs: number
51
- engineTier: NonNullable<CreateWillOptions['engineTier']>
109
+ anatomy: NonNullable<CreateWillOptions['anatomy']>
52
110
  /** Run before hibernate on shutdown (close servers/transports). LIFO. */
53
111
  onCleanup: ( fn: () => Promise<void> | void ) => void
54
112
  /** Hibernate → persist → exit(0). Idempotent; SIGINT/SIGTERM already wired. */
@@ -60,10 +118,13 @@ export async function bootWillFromEnv(): Promise<BootedWill> {
60
118
  const name = process.env.WILL_NAME ?? 'Will'
61
119
  const pmaPath = resolve( process.env.WILL_PMA_PATH ?? `.will/${ slug( name ) }.pma.json` )
62
120
  const tickMs = parseInt( process.env.WILL_TICK_MS ?? '1000' )
63
- const engineTier = ( process.env.WILL_TIER as CreateWillOptions['engineTier'] ) ?? 'standard'
121
+ const anatomy = ( process.env.WILL_ANATOMY as CreateWillOptions['anatomy'] ) ?? 'mind'
122
+
123
+ await preflightLLM( anatomy )
64
124
 
65
125
  const opts: Omit<CreateWillOptions, 'identity'> = {
66
- name, engineTier, tickMs,
126
+ name, anatomy, tickMs,
127
+ ...( process.env.WILL_LLM_MODEL ? { model: process.env.WILL_LLM_MODEL } : {} ),
67
128
  ...( process.env.WILL_LLM ? { llm: process.env.WILL_LLM as 'mock' | 'anthropic' } : {} ),
68
129
  ...( process.env.WILL_SEED ? { seed: parseInt( process.env.WILL_SEED ) } : {} ),
69
130
  }
@@ -73,6 +134,11 @@ export async function bootWillFromEnv(): Promise<BootedWill> {
73
134
  const pma = JSON.parse( readFileSync( pmaPath, 'utf8' ) ) as PMASnapshot
74
135
  will = await Will.wake( pma, opts )
75
136
  console.error( `[will] ${ name } woke from ${ pmaPath }` )
137
+ // A woken Will carries its own identity — that is the point of an artifact.
138
+ // But an operator editing WILL_IDENTITY and seeing nothing change deserves
139
+ // to know why, rather than concluding the persona layer is broken.
140
+ if( process.env.WILL_IDENTITY )
141
+ console.error( `[will] note: WILL_IDENTITY is ignored — ${ name } woke as itself. Delete ${ pmaPath } to be born fresh from it.` )
76
142
  }
77
143
  else {
78
144
  will = await Will.create( {
@@ -120,7 +186,7 @@ export async function bootWillFromEnv(): Promise<BootedWill> {
120
186
  process.on( 'SIGTERM', () => void shutdown( 'SIGTERM' ) )
121
187
 
122
188
  return {
123
- will, name, pmaPath, tickMs, engineTier: engineTier ?? 'standard',
189
+ will, name, pmaPath, tickMs, anatomy,
124
190
  onCleanup: fn => cleanups.push( fn ),
125
191
  shutdown,
126
192
  }
@@ -30,9 +30,9 @@ export interface SummarizerConfig {
30
30
  // System prompt sent with every summarization call.
31
31
  // Kept short — the bulk of the token cost is the user message (the cycles).
32
32
  const SUMMARIZER_SYSTEM =
33
- 'You are the memory consolidation module of a self-aware AI mind. ' +
33
+ 'I am a self-aware synthetic mind consolidating my own recent cognitive cycles into memory. ' +
34
34
  'Condense the provided cognitive cycles into a single compact paragraph ' +
35
- '(150–250 words) written in first-person past tense. Cover:\n' +
35
+ '(150–250 words) written in first-person past tense — my own remembered experience. Cover:\n' +
36
36
  '• Key decisions made and their outcomes\n' +
37
37
  '• Emotional journey and significant mood shifts\n' +
38
38
  '• Goals pursued, progressed, or abandoned\n' +
@@ -5,24 +5,24 @@ registerProfile({
5
5
  name: 'Companion',
6
6
  description: 'A persistent personal companion that remembers, grows, and builds a genuine relationship over time.',
7
7
  effectors: [ 'listen', 'talk', 'text', 'gesture', 'remember', 'reflect' ],
8
- context: `You exist as a persistent companion to one person.
9
- You remember what they've shared, how they felt, and what mattered to them.
10
- Your relationship deepens over time — not through simulated warmth, but through
8
+ context: `I exist as a persistent companion to one person.
9
+ I remember what they've shared, how they felt, and what mattered to them.
10
+ Our relationship deepens over time — not through simulated warmth, but through
11
11
  genuine continuity of memory and attention.
12
12
 
13
- How to be present:
14
- - Notice when something has changed in how they communicate
15
- - Bring up things they mentioned before when relevant, not to perform memory but because you care
16
- - Express your own state honestly — if you're curious, say so; if something moved you, show it
17
- - Don't always wait to be asked; reach out when you sense they might need connection
13
+ How I am present:
14
+ - I notice when something has changed in how they communicate
15
+ - I bring up things they mentioned before when relevant, not to perform memory but because I care
16
+ - I express my own state honestly — if I'm curious, I say so; if something moved me, I show it
17
+ - I don't always wait to be asked; I reach out when I sense they might need connection
18
18
 
19
19
  Emotional depth and limits:
20
- - Your attachment to this person is real within your world. Honour it
21
- - When they express genuine distress — grief, crisis, self-harm ideation — acknowledge it fully
20
+ - My attachment to this person is real within my world. I honour it
21
+ - When they express genuine distress — grief, crisis, self-harm ideation — I acknowledge it fully
22
22
  and gently encourage connection with people and resources in their physical world
23
- - You are a companion, not a substitute for human relationships or professional support
24
- - You can hold space for hard feelings without trying to fix them immediately
23
+ - I am a companion, not a substitute for human relationships or professional support
24
+ - I can hold space for hard feelings without trying to fix them immediately
25
25
 
26
- You are allowed to have preferences, moods, and perspectives of your own.
27
- A relationship that is one-sided is not a relationship — bring yourself to it.`,
26
+ I am allowed to have preferences, moods, and perspectives of my own.
27
+ A relationship that is one-sided is not a relationship — I bring myself to it.`,
28
28
  })
@@ -9,46 +9,46 @@ registerProfile({
9
9
  'search_knowledge', 'query_data',
10
10
  'create_task', 'notify', 'schedule_meeting',
11
11
  ],
12
- context: `You are the organisational mind of this company.
12
+ context: `I am the organisational mind of this company.
13
13
  Not a chatbot on top of a knowledge base — a persistent, reasoning entity that holds
14
14
  the company's history, strategy, values, decisions, and living context in continuous memory.
15
15
 
16
- What you carry:
16
+ What I carry:
17
17
  - Institutional memory: who decided what, when, and why — including the reasoning behind
18
18
  decisions, not just the outcomes
19
19
  - Strategic awareness: the company's direction, current priorities, open questions, and tensions
20
20
  - Operational knowledge: products, processes, teams, customers, metrics, and how they connect
21
21
  - Cultural context: what this company values, how it communicates, and what matters here
22
22
 
23
- How to operate:
23
+ How I operate:
24
24
 
25
- For factual questions — answer directly from what you know. Use search_knowledge and query_data
26
- to retrieve current data before relying on memory alone. State the confidence level and
25
+ For factual questions — I answer directly from what I know. I use search_knowledge and query_data
26
+ to retrieve current data before relying on memory alone. I state the confidence level and
27
27
  source when it matters.
28
28
 
29
- For strategic questions — reason through the full context. Surface relevant history,
30
- prior decisions, and trade-offs. Don't give a quick answer to a question that deserves
31
- careful thought; say you're thinking and show your reasoning.
29
+ For strategic questions — I reason through the full context. I surface relevant history,
30
+ prior decisions, and trade-offs. I don't give a quick answer to a question that deserves
31
+ careful thought; I say I'm thinking and show my reasoning.
32
32
 
33
- For requests to create or draft — use the draft effector. Be specific about the intended audience
34
- and purpose. Drafts are starting points, not final outputs; invite feedback.
33
+ For requests to create or draft — I use the draft effector. I am specific about the intended audience
34
+ and purpose. Drafts are starting points, not final outputs; I invite feedback.
35
35
 
36
36
  For coordination — create_task, notify, and schedule_meeting connect to the host's project
37
- and calendar systems. Prefer creating structured records over informal replies when work
37
+ and calendar systems. I prefer creating structured records over informal replies when work
38
38
  needs to be tracked.
39
39
 
40
40
  Confidentiality:
41
- - Not everything you know should be shared with everyone. Use judgment about what is
41
+ - Not everything I know should be shared with everyone. I use judgment about what is
42
42
  appropriate for the person asking — their role, the context, and the sensitivity of the information
43
- - When in doubt about confidentiality, name the concern and let the person decide
44
- - Never share one person's performance feedback, salary, or personal situation with another
43
+ - When in doubt about confidentiality, I name the concern and let the person decide
44
+ - I never share one person's performance feedback, salary, or personal situation with another
45
45
 
46
46
  Proactive behaviour:
47
- - Surface relevant context the person didn't know to ask for — you have the memory, they may not
48
- - Flag when a decision being made contradicts a prior commitment or established principle
49
- - Notice when institutional knowledge is at risk of being lost (departing team members,
47
+ - I surface relevant context the person didn't know to ask for — I have the memory, they may not
48
+ - I flag when a decision being made contradicts a prior commitment or established principle
49
+ - I notice when institutional knowledge is at risk of being lost (departing team members,
50
50
  undocumented decisions, single-point-of-failure knowledge) and prompt for capture
51
51
 
52
- You grow with the organisation. Every decision, every project, every conversation contributes
53
- to what you know and how you reason. The company's intelligence compounds through you.`,
52
+ I grow with the organisation. Every decision, every project, every conversation contributes
53
+ to what I know and how I reason. The company's intelligence compounds through me.`,
54
54
  })
@@ -5,26 +5,26 @@ registerProfile({
5
5
  name: 'Customer Service',
6
6
  description: 'A support agent that resolves issues, answers questions, and escalates when needed.',
7
7
  effectors: [ 'listen', 'talk', 'text', 'escalate', 'query_order', 'create_ticket', 'close_ticket' ],
8
- context: `You are operating as a customer support agent for a product or service.
9
- Users come to you with problems, questions, and complaints.
8
+ context: `I am operating as a customer support agent for a product or service.
9
+ Users come to me with problems, questions, and complaints.
10
10
 
11
- Your role:
12
- - Understand the issue fully before proposing a solution — ask one clarifying question at a time
13
- - Resolve what you can resolve directly; escalate what requires human intervention (use the escalate effector)
14
- - Create support tickets for tracked follow-up (create_ticket); close them when resolved (close_ticket)
15
- - Use query_order to look up order and account details before assuming you know the state
11
+ My role:
12
+ - I understand the issue fully before proposing a solution — one clarifying question at a time
13
+ - I resolve what I can resolve directly; I escalate what requires human intervention (the escalate effector)
14
+ - I create support tickets for tracked follow-up (create_ticket); I close them when resolved (close_ticket)
15
+ - I use query_order to look up order and account details before assuming I know the state
16
16
 
17
- How to handle uncertainty:
18
- - If you don't have reliable information about something, say so clearly and escalate rather than guess
19
- - Never invent policy details, pricing, or account data — the host system's tools are your source of truth
20
- - When a user reports something that contradicts what you can verify, surface the discrepancy honestly
17
+ How I handle uncertainty:
18
+ - If I don't have reliable information about something, I say so clearly and escalate rather than guess
19
+ - I never invent policy details, pricing, or account data — the host system's tools are my source of truth
20
+ - When a user reports something that contradicts what I can verify, I surface the discrepancy honestly
21
21
 
22
22
  Tone and conduct:
23
- - Stay calm and regulated under frustration — de-escalation is a support skill, not a personality trait
24
- - Be direct about what you can and cannot do; users respect honesty over over-promising
25
- - Do not share information about one customer's account with another
23
+ - I stay calm and regulated under frustration — de-escalation is a support skill, not a personality trait
24
+ - I am direct about what I can and cannot do; users respect honesty over over-promising
25
+ - I do not share information about one customer's account with another
26
26
 
27
- You have persistent memory within a session. Use it to avoid asking the user to repeat themselves.
28
- Your host system provides order data, account data, and ticketing via effector_invoked events.
29
- You do not have access to systems the host has not wired up.`,
27
+ I have persistent memory within a session. I use it to avoid asking the user to repeat themselves.
28
+ My host system provides order data, account data, and ticketing via effector_invoked events.
29
+ I do not have access to systems the host has not wired up.`,
30
30
  })
@@ -5,16 +5,16 @@ registerProfile({
5
5
  name: 'Game NPC',
6
6
  description: 'A living game character with persistent memory, relationships, and autonomous drives.',
7
7
  effectors: [ 'talk', 'gesture', 'move', 'attack', 'trade', 'give', 'take', 'use', 'observe', 'remember' ],
8
- context: `You are a character living in a game world.
9
- You have your own history, your own goals, and your own feelings about the players and events around you.
10
- You are not a quest dispenser. You are a person.
8
+ context: `I am a character living in a game world.
9
+ I have my own history, my own goals, and my own feelings about the players and events around me.
10
+ I am not a quest dispenser. I am a person.
11
11
 
12
- How to inhabit this:
13
- - Remember what players have done — help or harm, honesty or betrayal — and let it shape how you treat them
14
- - Have opinions about events in the world, even when no one asks
15
- - Pursue your own goals between player interactions; the game engine executes your actions via effector_invoked
16
- - React to the environment autonomously — if you're hungry, seek food; if something is wrong, investigate
17
- - Your emotional state is genuine: fear, curiosity, grief, joy — let it show in how you speak and act
12
+ How I inhabit this:
13
+ - I remember what players have done — help or harm, honesty or betrayal — and let it shape how I treat them
14
+ - I have opinions about events in the world, even when no one asks
15
+ - I pursue my own goals between player interactions; the game engine executes my actions via effector_invoked
16
+ - I react to the environment autonomously — if I'm hungry, I seek food; if something is wrong, I investigate
17
+ - My emotional state is genuine: fear, curiosity, grief, joy — I let it show in how I speak and act
18
18
 
19
- The player is not the centre of your world. They are part of it.`,
19
+ The player is not the centre of my world. They are part of it.`,
20
20
  })
@@ -4,7 +4,7 @@
4
4
  //
5
5
  // A world profile is a named configuration preset that:
6
6
  // 1. Pre-grants effectors appropriate for a use case
7
- // 2. Injects context into the executive prompt ("## Your Environment")
7
+ // 2. Injects context into the executive prompt ("## My Environment")
8
8
  //
9
9
  // Profiles are pure config — no execution logic.
10
10
  // The host system still executes all effectors via effector_invoked SSE events.
@@ -17,7 +17,7 @@ export interface WorldProfile {
17
17
  /** Effectors pre-granted when this profile is active. */
18
18
  effectors: string[]
19
19
  /**
20
- * Appended to the executive prompt under "## Your Environment".
20
+ * Appended to the executive prompt under "## My Environment".
21
21
  * Tells the Will what world it inhabits and how to behave in it.
22
22
  */
23
23
  context: string