@mindot/will 0.8.0 → 0.10.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 (142) hide show
  1. package/README.md +15 -11
  2. package/dist/index.d.ts +188 -141
  3. package/dist/index.js +6985 -3337
  4. package/dist/index.js.map +1 -1
  5. package/dist/surface/channels/discord.d.ts +177 -0
  6. package/dist/surface/channels/discord.js +394 -0
  7. package/dist/surface/channels/discord.js.map +1 -0
  8. package/dist/{channels → surface/channels}/whatsapp.d.ts +2 -2
  9. package/dist/{channels → surface/channels}/whatsapp.js +11 -5
  10. package/dist/surface/channels/whatsapp.js.map +1 -0
  11. package/dist/{cli.js → surface/cli.js} +5382 -1573
  12. package/dist/surface/cli.js.map +1 -0
  13. package/dist/{mcp → surface/mcp}/effectors.d.ts +1 -1
  14. package/dist/{mcp → surface/mcp}/effectors.js +10 -4
  15. package/dist/surface/mcp/effectors.js.map +1 -0
  16. package/dist/{will-cS6k4uiJ.d.ts → will-evj9_vrd.d.ts} +6658 -5078
  17. package/package.json +11 -12
  18. package/src/cognition/agency/consequence.ts +356 -1
  19. package/src/cognition/agency/conversation.aim.ts +292 -0
  20. package/src/cognition/agency/engines/action.selector.ts +142 -5
  21. package/src/cognition/agency/engines/affordance.synthesizer.ts +200 -7
  22. package/src/cognition/agency/engines/deliberation.engine.ts +76 -3
  23. package/src/cognition/agency/engines/motor.schema.executor.ts +509 -32
  24. package/src/cognition/agency/engines/reafference.engine.ts +160 -4
  25. package/src/cognition/agency/execution.primitives.ts +17 -4
  26. package/src/cognition/agency/proactive.communicator.ts +19 -3
  27. package/src/cognition/agency/reconcile.learning.ts +5 -4
  28. package/src/cognition/agency/restart.ts +66 -0
  29. package/src/cognition/agency/schemas/innate.ts +96 -1
  30. package/src/cognition/agency/schemas/repertoire.ts +19 -1
  31. package/src/cognition/agency/selection.scoring.ts +49 -0
  32. package/src/cognition/agency/settlement.ts +203 -0
  33. package/src/cognition/agency/types.ts +73 -1
  34. package/src/cognition/cache/composition.ts +232 -0
  35. package/src/cognition/cache/deliberation.cache.ts +219 -0
  36. package/src/cognition/cache/fingerprint.ts +120 -0
  37. package/src/cognition/cache/types.ts +105 -0
  38. package/src/cognition/config.mirror.entities.ts +108 -1
  39. package/src/cognition/event.schemas.ts +22 -8
  40. package/src/cognition/faculties/affective.blender.ts +21 -4
  41. package/src/cognition/faculties/autobiographical.narrator.ts +5 -10
  42. package/src/cognition/faculties/circadian.oscillator.ts +36 -7
  43. package/src/cognition/faculties/episodic.consolidator.ts +59 -3
  44. package/src/cognition/faculties/executive.engine/action.record.ts +129 -0
  45. package/src/cognition/faculties/executive.engine/commands.ts +189 -14
  46. package/src/cognition/faculties/executive.engine/context.ts +106 -46
  47. package/src/cognition/faculties/executive.engine/deliberate.reasoning.ts +1 -1
  48. package/src/cognition/faculties/executive.engine/engine.ts +714 -136
  49. package/src/cognition/faculties/executive.engine/escalation.buffer.ts +230 -45
  50. package/src/cognition/faculties/executive.engine/facet.supervisor.ts +320 -65
  51. package/src/cognition/faculties/executive.engine/facet.ts +81 -26
  52. package/src/cognition/faculties/executive.engine/gating.ts +14 -14
  53. package/src/cognition/faculties/executive.engine/parser.ts +120 -38
  54. package/src/cognition/faculties/executive.engine/prompt.factory.ts +331 -38
  55. package/src/cognition/faculties/executive.engine/types.ts +84 -12
  56. package/src/cognition/faculties/exteroception.ts +99 -98
  57. package/src/cognition/faculties/goal.manager.ts +144 -16
  58. package/src/cognition/faculties/known.entity.tracker.ts +287 -29
  59. package/src/cognition/faculties/moral.evaluator.ts +8 -3
  60. package/src/cognition/faculties/persona.consolidator.ts +141 -0
  61. package/src/cognition/faculties/planning.engine/engine.ts +4 -4
  62. package/src/cognition/faculties/planning.engine/plan.frontier.ts +1 -1
  63. package/src/cognition/faculties/reputation.tracker.ts +66 -2
  64. package/src/cognition/faculties/self.model.updater.ts +19 -12
  65. package/src/cognition/faculties/semantic.engine/integrator.ts +1 -1
  66. package/src/cognition/faculties/social.perception.ts +47 -3
  67. package/src/cognition/faculties/threat.evaluator.ts +7 -0
  68. package/src/cognition/faculties/working.memory.ts +16 -21
  69. package/src/cognition/identity.entity.ts +205 -0
  70. package/src/cognition/index.ts +7 -0
  71. package/src/cognition/instruction.handler.ts +1 -1
  72. package/src/cognition/memory/vector.adapter.ts +12 -3
  73. package/src/cognition/memory/vector.embedder.ts +45 -4
  74. package/src/cognition/percept.entity.ts +122 -0
  75. package/src/cognition/persona.prior.ts +6 -0
  76. package/src/cognition/sense.boundary.ts +176 -0
  77. package/src/cognition/senses/audition.engine/engine.ts +492 -53
  78. package/src/cognition/senses/base.sense.engine.ts +105 -7
  79. package/src/cognition/senses/index.ts +83 -13
  80. package/src/cognition/senses/provenance.ts +128 -0
  81. package/src/cognition/senses/somatosensation.engine.ts +117 -9
  82. package/src/cognition/social.identity.ts +296 -0
  83. package/src/cognition/utilities/token.tracker.ts +94 -10
  84. package/src/core/orchestrator.ts +51 -0
  85. package/src/index.ts +39 -5
  86. package/src/llm/gate.ts +48 -0
  87. package/src/llm/index.ts +127 -33
  88. package/src/llm/routing.ts +6 -0
  89. package/src/llm/summarizer.ts +1 -1
  90. package/src/llm/wire.contracts.ts +57 -0
  91. package/src/pma/index.ts +68 -54
  92. package/src/stem/assembly.audit.ts +1 -0
  93. package/src/stem/guards/identity.coherence.ts +1 -1
  94. package/src/stem/index.ts +171 -23
  95. package/src/stem/mind.ts +198 -57
  96. package/src/stem/policy/arbiter.ts +10 -0
  97. package/src/stem/profiles/built-in.ts +7 -0
  98. package/src/{profiles → stem/profiles}/companion.ts +1 -1
  99. package/src/{profiles → stem/profiles}/company-brain.ts +1 -1
  100. package/src/{profiles → stem/profiles}/customer-service.ts +1 -1
  101. package/src/{profiles → stem/profiles}/game-npc.ts +1 -1
  102. package/src/{profiles → stem/profiles}/index.ts +1 -1
  103. package/src/{profiles → stem/profiles}/smart-home.ts +1 -1
  104. package/src/stem/tracts/effector/escalation.lifecycle.ts +239 -0
  105. package/src/stem/tracts/effector/policy.enforcement.ts +261 -0
  106. package/src/stem/tracts/effector/types.ts +110 -0
  107. package/src/stem/tracts/effector.controller.ts +112 -401
  108. package/src/stem/tracts/outbox.controller.ts +30 -17
  109. package/src/stem/tracts/outbox.writer.ts +40 -2
  110. package/src/stem/tracts/sensory.controller.ts +6 -6
  111. package/src/stem/tracts/session.logger.ts +6 -1
  112. package/src/stem/tracts/transport/types.ts +20 -1
  113. package/src/stem/tracts/transport.controller.ts +33 -9
  114. package/src/surface/channels/discord.ts +609 -0
  115. package/src/{channels → surface/channels}/roster.ts +1 -1
  116. package/src/surface/channels/types.ts +136 -0
  117. package/src/{channels → surface/channels}/whatsapp.ts +22 -10
  118. package/src/{cli.ts → surface/cli.ts} +14 -9
  119. package/src/{host → surface/host}/boot.ts +3 -3
  120. package/src/{host → surface/host}/utterances.ts +2 -2
  121. package/src/{mcp → surface/mcp}/effectors.ts +19 -6
  122. package/src/{mcp → surface/mcp}/server.ts +19 -5
  123. package/src/{sdk → surface/sdk}/will.ts +194 -33
  124. package/src/{serve → surface/serve}/server.ts +11 -4
  125. package/src/types.ts +24 -3
  126. package/dist/channels/discord.d.ts +0 -69
  127. package/dist/channels/discord.js +0 -193
  128. package/dist/channels/discord.js.map +0 -1
  129. package/dist/channels/whatsapp.js.map +0 -1
  130. package/dist/cli.js.map +0 -1
  131. package/dist/mcp/effectors.js.map +0 -1
  132. package/src/channels/discord.ts +0 -214
  133. package/src/channels/types.ts +0 -46
  134. package/src/cognition/faculties/executive.engine/messages.ts +0 -102
  135. package/src/extensions/livestream.ext.ts +0 -570
  136. package/src/extensions/time.ext.ts +0 -339
  137. package/src/profiles/built-in.ts +0 -7
  138. package/src/runners/coherence.runner.ts +0 -49
  139. package/src/runners/outreach.runner.ts +0 -155
  140. package/src/runners/social.runner.ts +0 -179
  141. package/src/runners/thin-shim.runner.ts +0 -205
  142. /package/dist/{cli.d.ts → surface/cli.d.ts} +0 -0
@@ -13,6 +13,7 @@ import {
13
13
  type ModelPrice,
14
14
  } from '#cognition/utilities/token.tracker'
15
15
 
16
+ import type { VectorMemoryAdapter } from '#memory/vector.adapter'
16
17
  import { EnergyRegulator, type EnergyRegulatorConfig } from '#faculties/energy.regulator'
17
18
  import { SleepPressureRegulator, type SleepPressureConfig } from '#faculties/sleep.pressure.regulator'
18
19
  import { AttentionAllocator, type AttentionAllocatorConfig } from '#faculties/attention.allocator'
@@ -256,6 +257,12 @@ export type EngineRegistry = {
256
257
  affectiveBlender: AffectiveBlender
257
258
  workingMemory: WorkingMemory
258
259
  episodicConsolidator: EpisodicConsolidator
260
+ /**
261
+ * The vector index, when semantic recall is configured. Exposed so shutdown can
262
+ * FLUSH it: it lives outside the state snapshot and previously persisted only from
263
+ * a debounce timer no shutdown path awaited, so it died with the process.
264
+ */
265
+ vectorMemory: VectorMemoryAdapter | null
259
266
  semanticIntegrator: SemanticIntegrator
260
267
  spacedRepetition: SpacedRepetition
261
268
  forgettingCurve: ForgettingCurve
@@ -18,7 +18,7 @@ export interface Instruction {
18
18
  deadline?: number
19
19
  /** Constraints on how to execute */
20
20
  constraints?: string[]
21
- /** Context/provenance — why this instruction was given */
21
+ /** Context — why this instruction was given */
22
22
  context: string
23
23
  /** Whether the Will can refuse */
24
24
  isOverridable: boolean
@@ -302,15 +302,24 @@ export class DefaultVectorMemoryAdapter implements VectorMemoryAdapter {
302
302
  if( victims.length > 0 ) this._dirty = true
303
303
  }
304
304
 
305
+ /**
306
+ * Throttle, NOT a debounce. The previous version cleared and re-armed the timer on
307
+ * every index(), so it only ever fired after 5s of complete inactivity — and a mind
308
+ * consolidating steadily indexes far more often than that, so the write was pushed
309
+ * back indefinitely and the index never reached disk while it was awake.
310
+ *
311
+ * A pending timer is now left alone: the first write after a quiet period sets the
312
+ * deadline, and everything indexed within the window rides along on it. Persist is
313
+ * bounded at 5s from the FIRST pending change rather than the last.
314
+ */
305
315
  private _schedulePersist(): void {
306
- if( this._persistDebounceTimer )
307
- clearTimeout( this._persistDebounceTimer )
316
+ if( this._persistDebounceTimer ) return
308
317
 
309
318
  this._persistDebounceTimer = setTimeout( () => {
319
+ this._persistDebounceTimer = null
310
320
  this.persist().catch( err => {
311
321
  logger.error(`[VectorMemoryAdapter] Persist failed:`, err )
312
322
  } )
313
- this._persistDebounceTimer = null
314
323
  }, 5000 )
315
324
  }
316
325
  }
@@ -13,6 +13,7 @@
13
13
 
14
14
  import type { TokenTracker } from '#cognition/utilities/token.tracker'
15
15
  import type { LLMCallFunction } from '#cognition/utilities/token.tracker'
16
+ import { LLMSemaphore, withGate } from '#llm/gate'
16
17
 
17
18
  /** Embedding is only ever a read or a write. */
18
19
  export type EmbedFunction = Extract<LLMCallFunction, 'recall' | 'index'>
@@ -44,16 +45,28 @@ export class OpenAICompatibleEmbedder implements EmbeddingProvider {
44
45
  private _maxConcurrency: number
45
46
  private _timeoutMs: number
46
47
  private _tokenTracker: TokenTracker | null
48
+ /**
49
+ * Own gate — the same LLMSemaphore the LLM calls use, on a separate instance so
50
+ * embeddings and reasoning do not compete for one another's slots. It bounds the
51
+ * fan-out that produced the 10.7s tail, and `withGate` additionally retries a 429
52
+ * with backoff, which a bare `embed()` previously surfaced as a hard failure.
53
+ */
54
+ private _gate: LLMSemaphore
47
55
 
48
56
  constructor( config: {
49
57
  modelName: string
50
58
  dimensions: number
51
59
  apiUrl: string
52
60
  apiKey?: string | null
53
- /** Max embedding requests in flight at once for embedBatch(). Default 8. */
61
+ /**
62
+ * Max embedding requests in flight at once — across ALL callers, not just one
63
+ * embedBatch(). Default 4, chosen from measured provider behaviour rather than
64
+ * taste: gemini-embedding-001 answers a lone request in ~1.1s, but queues hard
65
+ * under fan-out — at 8 in flight the slowest three took 10.7s (all HTTP 200, no
66
+ * 429, simply serialized). That tail is what made recall exceed its 5s budget
67
+ * and return "no recall" while a mind with six live facets was asking.
68
+ */
54
69
  maxConcurrency?: number
55
- /** @deprecated use maxConcurrency — kept as its fallback for back-compat. */
56
- batchSize?: number
57
70
  /** Per-request timeout in ms before the connection is aborted. Default 30s. */
58
71
  timeoutMs?: number
59
72
  /**
@@ -67,12 +80,24 @@ export class OpenAICompatibleEmbedder implements EmbeddingProvider {
67
80
  this.dimensions = config.dimensions
68
81
  this._apiUrl = config.apiUrl
69
82
  this._apiKey = config.apiKey ?? null
70
- this._maxConcurrency = Math.max( 1, config.maxConcurrency ?? config.batchSize ?? 8 )
83
+ this._maxConcurrency = Math.max( 1, config.maxConcurrency ?? 4 )
84
+ this._gate = new LLMSemaphore( this._maxConcurrency )
71
85
  this._timeoutMs = config.timeoutMs ?? 30_000
72
86
  this._tokenTracker = config.tokenTracker ?? null
73
87
  }
74
88
 
89
+ /**
90
+ * Embed one item, gated. Every caller funnels through here — a facet building a
91
+ * prompt, the master recalling, the consolidator indexing — so the gate is the
92
+ * only place total in-flight fan-out is bounded. Waiting for a slot is strictly
93
+ * better than the alternative it replaces: an ungated request that returns after
94
+ * the recall budget has already expired is a request whose answer is thrown away.
95
+ */
75
96
  async embed( content: unknown, fn: EmbedFunction = 'recall'): Promise<number[]> {
97
+ return withGate( () => this._embedOnce( content, fn ), `embed:${ this.modelName }`, this._gate )
98
+ }
99
+
100
+ private async _embedOnce( content: unknown, fn: EmbedFunction ): Promise<number[]> {
76
101
  let response: Response
77
102
  try {
78
103
  response = await fetch(`${this._apiUrl}/embeddings`, {
@@ -110,6 +135,21 @@ export class OpenAICompatibleEmbedder implements EmbeddingProvider {
110
135
  if( !Array.isArray( embedding ) || embedding.length === 0 )
111
136
  throw new Error(`Embedding response was empty or malformed for model ${this.modelName}`)
112
137
 
138
+ // The configured width must match what the provider actually returns. This class
139
+ // sends no `dimensions` param, so the index is sized from config alone — and a
140
+ // wrong number does not fail, it silently builds an index that can never match.
141
+ // Providers differ per model family and change defaults between versions, so the
142
+ // number is checked against reality once rather than trusted. Failing here is safe:
143
+ // indexing degrades to "deferred" and recall to "no recall", both already
144
+ // best-effort, and the message carries the value to set.
145
+ if( embedding.length !== this.dimensions )
146
+ throw new Error(
147
+ `Embedding width mismatch for ${ this.modelName }: provider returned ${ embedding.length } ` +
148
+ `dimensions, index is configured for ${ this.dimensions }. ` +
149
+ `Set WILL_EMBEDDING_DIMENSIONS=${ embedding.length } (and delete any existing ` +
150
+ `vector_index built at the old width).`
151
+ )
152
+
113
153
  // Meter embedding token usage (input-only — embeddings have no completion).
114
154
  // Recorded under the 'embedding' category so per-Will dashboards can split
115
155
  // memory-vector spend from LLM reasoning spend.
@@ -122,6 +162,7 @@ export class OpenAICompatibleEmbedder implements EmbeddingProvider {
122
162
  totalTokens: usedTok,
123
163
  category: 'embedding',
124
164
  attribute: 'memory',
165
+ process: 'cog', // background: no deliberation happens here
125
166
  function: fn, // 'recall' (query) | 'index' (write)
126
167
  scope: this.modelName,
127
168
  tick: 0,
@@ -0,0 +1,122 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/cognition/percept.entity.ts — one shape for "something reached me"
3
+ // ─────────────────────────────────────────────────────────────
4
+ //
5
+ // A `percept` entity is the staging area every downstream consumer of afference
6
+ // reads. Five places write one and none of them agreed on what one is:
7
+ //
8
+ // exteroception tick ✓ provenance ✓ the complete one
9
+ // outbox.controller tick ✗ provenance ✓
10
+ // escalation.buffer tick ✓ provenance ✗ (x2)
11
+ // stem/index.ts tick ✗ provenance ✗ the wake percept
12
+ //
13
+ // Both omissions are silent and both cost something real:
14
+ //
15
+ // • NO `tick` — `exteroception._collectStalePerceptIds` is the ONLY sweeper of
16
+ // this type, and it collects only entities whose `metadata.tick` is a
17
+ // number. Everything else is immortal. `msg-delivered-<id>` therefore leaks
18
+ // one entity per message the mind ever successfully sends, and the wake
19
+ // percept tells the executive "I was offline for 3 hours" for the rest of
20
+ // the mind's life.
21
+ // • NO `provenance` — `action.selector`'s rupture gate counts only percepts
22
+ // tagged `'exafferent'`, so an untagged percept can never rupture a
23
+ // commitment, exactly as the mind's own echo cannot. A mind waking after
24
+ // hours offline cannot be ruptured by noticing that.
25
+ //
26
+ // Neither is a bug in the writers. They are a bug in there being no shape to
27
+ // write: every one of them hand-rolled a metadata literal, and a literal cannot
28
+ // forget a field it was never asked for. This is the shape. It makes `tick` and
29
+ // `provenance` REQUIRED, so the sweeper's precondition and the rupture gate's
30
+ // precondition are both structural rather than remembered.
31
+ //
32
+ // SIGNAL_BOUNDARY P0. Own the contract before widening it — the sense door is
33
+ // about to become a sixth writer, and it must not invent a sixth variant.
34
+
35
+ import type { SignalProvenance } from '#senses/provenance'
36
+
37
+ export const PERCEPT_TYPE = 'percept'
38
+
39
+ /**
40
+ * How long a `percept` survives before `Exteroception` sweeps it. Not a
41
+ * durability window — a *staging* window. Persistence happens downstream
42
+ * (WorkingMemory → EpisodicConsolidator → vector); a percept's job is to be
43
+ * seen once by the faculties that run each tick, then get out of the way.
44
+ */
45
+ export const PERCEPT_STALE_AFTER_TICKS = 2
46
+
47
+ /**
48
+ * How much of a percept's `summary` survives.
49
+ *
50
+ * 100 because that is what `exteroception._summarizeEntity` has always used —
51
+ * adopted rather than chosen, so naming it changes no behaviour. It is now one
52
+ * constant instead of a literal per writer, which is the point: the sizing
53
+ * question is real and open (SIGNAL_BOUNDARY §4 asks it of 120/300/700 too, and
54
+ * notes that none of those was ever a budget decision), and answering it should
55
+ * be a one-line change in one place rather than an archaeology exercise.
56
+ */
57
+ export const PERCEPT_SUMMARY_CAP = 100
58
+
59
+ /** What every percept must say, whatever wrote it. */
60
+ export interface PerceptFacts {
61
+ /** Deterministic and unique. Never `wallClock()` — this lives in state (R2). */
62
+ id: string
63
+ /** The tick it arrived on. REQUIRED: without it the entity is never swept. */
64
+ tick: number
65
+ /** 0–1. The rupture gate reads this. */
66
+ salience: number
67
+ /** Coarse kind, for grouping — 'message-delivery', 'system', 'undertaking'… */
68
+ category: string
69
+ /** The one field the executive prompt renders. If it is not here, it is unread. */
70
+ summary: string
71
+ /** REQUIRED: untagged means unrupturable. See `SignalProvenance`. */
72
+ provenance: SignalProvenance
73
+ /** The intent this is the consequence of, when `provenance` is `'reafferent'`. */
74
+ sourceIntentId?: string
75
+ /** The world entity this is about, where there is one. */
76
+ entityId?: string
77
+ /** What happened to it — 'delivered', 'removed', 'changed'… */
78
+ changeType?: string
79
+ /** What it FELT like, and how much that says about it (registry #5). */
80
+ valence?: number
81
+ valenceSource?: string
82
+ /**
83
+ * What a host sent, whole. Carried into state so it survives to working
84
+ * memory, recall and the prompt — `summary` is a label the engine wrote, and
85
+ * a mind reasoning only from labels is reasoning from somebody else's
86
+ * conclusions.
87
+ */
88
+ data?: unknown
89
+ }
90
+
91
+ /** The write-side entity shape `stateManager.setEntity` accepts. */
92
+ export interface PerceptEntity {
93
+ id: string
94
+ type: typeof PERCEPT_TYPE
95
+ metadata: Record<string, unknown>
96
+ }
97
+
98
+ /**
99
+ * Build a `percept` entity. `extra` carries a writer's own fields (`messageId`,
100
+ * `facetId`, `offlineMs`…) and cannot overwrite the core: a writer that could
101
+ * clobber its own `tick` or `provenance` is back where it started.
102
+ */
103
+ export function perceptEntity( facts: PerceptFacts, extra: Record<string, unknown> = {} ): PerceptEntity {
104
+ return {
105
+ id: facts.id,
106
+ type: PERCEPT_TYPE,
107
+ metadata: {
108
+ ...extra,
109
+ tick: facts.tick,
110
+ salience: facts.salience,
111
+ category: facts.category,
112
+ summary: facts.summary,
113
+ provenance: facts.provenance,
114
+ ...( facts.sourceIntentId !== undefined ? { sourceIntentId: facts.sourceIntentId } : {} ),
115
+ ...( facts.entityId !== undefined ? { entityId: facts.entityId } : {} ),
116
+ ...( facts.changeType !== undefined ? { changeType: facts.changeType } : {} ),
117
+ ...( facts.valence !== undefined ? { valence: facts.valence } : {} ),
118
+ ...( facts.valenceSource !== undefined ? { valenceSource: facts.valenceSource } : {} ),
119
+ ...( facts.data !== undefined ? { data: facts.data } : {} ),
120
+ },
121
+ }
122
+ }
@@ -118,6 +118,7 @@ const PRIOR_DESCRIPTIONS: Record<string, { lower: string; raise: string }> = {
118
118
  'engine-config-inhibition.baseInhibitionStrength': { lower: 'acting on impulse more freely', raise: 'holding myself in check more firmly before acting' },
119
119
  'engine-config-self-model.minNewExperiences': { lower: 're-evaluating who I am on less new experience', raise: 'requiring more new experience before re-evaluating who I am' },
120
120
  'engine-config-attention.shiftInertia': { lower: 'shifting my attention more readily', raise: 'holding my attention more fixedly' },
121
+ 'engine-config-attention.maxFoci': { lower: 'attending to one thing at a time, narrowly', raise: 'keeping several things in view at once' },
121
122
  'engine-config-goal-manager.gritPriority': { lower: 'refusing to give up on more of what matters to me', raise: 'letting go of goals more readily' },
122
123
  'engine-config-goal-manager.gritPatienceScale': { lower: 'losing patience with stuck goals sooner', raise: 'staying the course on hard goals far longer' },
123
124
  'engine-config-goal-manager.frustrationTolerance': { lower: 'giving up faster when frustrated', raise: 'holding steady through frustration' },
@@ -129,6 +130,7 @@ const PRIOR_DESCRIPTIONS: Record<string, { lower: string; raise: string }> = {
129
130
  'engine-config-executive.riskTolerance': { lower: 'playing it safer', raise: 'taking bigger risks' },
130
131
  'engine-config-frustration.irritabilityRate': { lower: 'letting frustration snowball into a bad mood more slowly', raise: 'getting wound up into a bad mood faster' },
131
132
  'engine-config-executive.deliberateThreshold': { lower: 'stopping to think things through more readily', raise: 'going with my gut more, deliberating less' },
133
+ 'engine-config-executive.maxFacets': { lower: 'keeping fewer things going at once, one at a time', raise: 'holding more conversations and threads at once' },
132
134
  'engine-config-reward.socialWeight': { lower: 'caring less about social warmth', raise: 'finding warmth and connection more rewarding' },
133
135
  'engine-config-frustration.angerReactivity': { lower: 'letting slights go, yielding rather than bristling', raise: 'bristling harder when I feel wronged' },
134
136
  'engine-config-novelty.significanceThreshold': { lower: 'noticing what is new and unusual more readily', raise: 'needing more for something to strike me as new' },
@@ -143,6 +145,10 @@ const PRIOR_DESCRIPTIONS: Record<string, { lower: string; raise: string }> = {
143
145
  'engine-config-action-selector.switchCost': { lower: 'dropping what I am doing for a new pull more easily', raise: 'seeing an action through, harder to knock off course once underway' },
144
146
  'engine-config-action-selector.riskWeight': { lower: 'weighing the downside of what I do less — acting bolder', raise: 'weighing what could go wrong more heavily before I act' },
145
147
  'engine-config-action-selector.noveltyWeight': { lower: 'feeling less pull toward the untried', raise: 'drawn more strongly to do the untried and unpracticed' },
148
+ 'engine-config-action-selector.repeatDamping': { lower: 'following up sooner when I have had no answer', raise: 'giving people room after I have said my piece' },
149
+ 'engine-config-action-selector.repeatWindowTicks': { lower: 'coming back to something sooner after I have said it', raise: 'letting what I have said stand for longer before returning to it' },
150
+ 'engine-config-action-selector.replyWindowTicks': { lower: 'reading a silence as an answer sooner', raise: 'giving someone longer before I take their quiet as telling me anything' },
151
+ 'engine-config-action-selector.socialWeight': { lower: 'reaching for people regardless of whether they answer me', raise: 'drawn toward the people who actually answer me' },
146
152
  'engine-config-moral.eventThreshold': { lower: 'holding myself to my principles more sharply', raise: 'letting moral lapses weigh on me less' },
147
153
  'engine-config-frustration.decayRate': { lower: 'staying frustrated for longer after a setback', raise: 'shaking off frustration and bouncing back faster' },
148
154
  'engine-config-reward.socialWarmthBoost': { lower: 'being warmed less by a kind exchange', raise: 'being warmed more by every kind exchange' },
@@ -0,0 +1,176 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/cognition/sense.boundary.ts — where I end and the world begins
3
+ // ─────────────────────────────────────────────────────────────
4
+ //
5
+ // A body needs no rule saying "do not see your own premotor cortex." You do not
6
+ // see it because the retina sits ON the boundary and motor planning is inside
7
+ // it. Efference reaches you as a different signal, in a different modality,
8
+ // already marked as yours — which this engine knows: that is what EXAFFERENCE
9
+ // P1/P2 built.
10
+ //
11
+ // Will has no such boundary. Cognition and world share one `state.entities` map,
12
+ // so the mind's own bookkeeping sits in the same place as the things it is
13
+ // bookkeeping ABOUT, and Exteroception — the outward sense — walks the lot.
14
+ //
15
+ // THE COST, MEASURED. A mind booted offline for 300 quiet ticks — no host, no
16
+ // channel, nothing outside it at all — produced 36,721 percepts, every one about
17
+ // itself:
18
+ //
19
+ // 17802 removed its own affordances being torn down each tick
20
+ // 16950 affordance its own affordances being rebuilt each tick
21
+ // 546 calibration.state
22
+ // 496 agency.intent
23
+ // 287 agency.outcome
24
+ // 286 agency.skill
25
+ // 135 action.unresolved
26
+ // 120 engine.config its own configuration
27
+ // 96 attention.demand
28
+ // 3 will.identity its own identity entity
29
+ //
30
+ // `maxPerceptsPerTick` is 50. The affordance field alone churns ~116 entities a
31
+ // tick, so the sensory cap was consumed by self-noise before the world said
32
+ // anything — and nothing downstream filters by category, so working memory, the
33
+ // novelty detector, the attention allocator and episodic consolidation all ate
34
+ // it. The visible symptom was elsewhere: a fresh Will proceduralized `inspect`
35
+ // to habit 0.64 within fifteen ticks and spent five of its first eight decisions
36
+ // examining its own affordance entities, because a percept-bound act offered
37
+ // against phantom percepts is a real act aimed at nothing.
38
+ //
39
+ // WHY IT DRIFTED. The boundary was a denylist living inside the sense
40
+ // (`internalTypes`), correct for the faculties it shipped beside in v0.1.0 and
41
+ // silently wrong for every entity type invented after. `git log -L` on that
42
+ // block shows two edits in five weeks — EXAFFERENCE P2 and P4 — each adding the
43
+ // one agency type in front of its author at that moment. A hand-kept list is
44
+ // correct for whatever you were last thinking about.
45
+ //
46
+ // THE ASYMMETRY THAT MAKES THIS TRACTABLE. The mind's own types are a CLOSED
47
+ // set: it knows its own anatomy. The world's types are OPEN and unknowable —
48
+ // that is the whole point of a container anything can rent. So enumerate the
49
+ // self, and let everything else be world by default. Openness is preserved
50
+ // exactly where it must be: a host introduces any entity type, any shape, and
51
+ // the mind perceives it, with no act narrowed and no schema gated.
52
+ //
53
+ // Two sources, unioned:
54
+ // • MIND_OWN_ENTITY_TYPES — the anatomy this package ships.
55
+ // • each registered engine's `writes` — so a tenant that brings its OWN
56
+ // cognitive engine declares its own machinery beside the code that writes
57
+ // it, and gets this right without editing the container.
58
+ //
59
+ // The guard that keeps it true is behavioural, not a source scanner:
60
+ // `sense.boundary.test.ts` boots a mind into an empty world and asserts it
61
+ // perceives nothing. A new engine writing a new undeclared type fails it.
62
+ // ─────────────────────────────────────────────────────────────
63
+
64
+ import { CONSEQUENCE_TYPE, ENACTED_TYPE } from '#agency/consequence'
65
+ import { AVAILABILITY_ENTITY_TYPE } from '#agency/schemas/repertoire'
66
+ import { REVOCATION_TYPE } from '#agency/revocation'
67
+ import { SETTLEMENT_TYPE } from '#agency/settlement'
68
+ import { ACTION_RECORD_TYPE } from '#faculties/executive.engine/action.record'
69
+
70
+ /**
71
+ * Entity types that ARE the mind — written by its own engines about its own
72
+ * operation. Not perceptible: a mind does not encounter its own machinery as an
73
+ * event in the world.
74
+ *
75
+ * Grouped by the faculty that writes them so a reader can check the list
76
+ * against the anatomy. Everything absent here is world.
77
+ */
78
+ export const MIND_OWN_ENTITY_TYPES: ReadonlySet<string> = new Set([
79
+ // ── perception ────────────────────────────────────────────────
80
+ // Percepts about percepts are the original feedback loop this guarded.
81
+ 'percept', 'percept.social',
82
+
83
+ // ── attention, interoception, control ─────────────────────────
84
+ 'interoception', 'attention.focus', 'attention.demand',
85
+ 'task.focus', 'decision.record', 'self_observation',
86
+ // What became of what it did. Undeclared, a mind perceives its own history as
87
+ // events in the world — which is how #127 was caught, recursively.
88
+ ACTION_RECORD_TYPE,
89
+
90
+ // ── memory ────────────────────────────────────────────────────
91
+ 'working_memory.item', 'episodic_memory', 'spaced_repetition_record',
92
+ 'belief', 'belief.integrate',
93
+
94
+ // ── deliberative structure ────────────────────────────────────
95
+ 'goal', 'plan', 'plan.prior',
96
+
97
+ // ── narrative + metacognition ─────────────────────────────────
98
+ 'narrative_chapter', 'introspection', 'self_narrative', 'cognitive_bias',
99
+ 'calibration.state', 'executive.summary', 'executive.cache',
100
+
101
+ // ── affect + social models ────────────────────────────────────
102
+ // A dossier is the mind's record OF someone, not the someone. The person
103
+ // arrives through audition and social perception; the dossier is what the
104
+ // mind then holds about them, and re-perceiving it double-counts.
105
+ 'affect.blends', 'empathic_state', 'attachment.bond',
106
+ 'theory_of_mind', 'reputation',
107
+ 'known-entity', 'known-entity-alias',
108
+
109
+ // ── conversation records ──────────────────────────────────────
110
+ // SocialPerception is the sense that owns these (it reads
111
+ // `conversation.received` and emits `percept.social` from it, then sweeps it).
112
+ // Exteroception perceiving them too was the same turn counted twice.
113
+ 'conversation.received', 'conversation.sent',
114
+
115
+ // ── agency ────────────────────────────────────────────────────
116
+ // The whole pipeline: the field it synthesizes, what it committed to, what
117
+ // came back, what it learned, and what it merely imagined.
118
+ 'affordance', 'agency.intent', 'agency.outcome', 'agency.skill',
119
+ 'agency.schema', 'ideomotor.intent',
120
+ // What it HAS DONE — the enaction footprint satiation reads to know it has
121
+ // already tried this. Undeclared, it was the loudest thing in the mind's
122
+ // perceptual field: measured on a live Will, `New agency.enacted: agency-
123
+ // enacted-discord_lookup_…` sat at salience 0.5, ABOVE every real percept,
124
+ // and each enaction produced two of them — one when the record appeared, one
125
+ // when the retention sweep removed it. A mind watching its own footprints
126
+ // being laid down and swept away, and calling that the world.
127
+ ENACTED_TYPE,
128
+ // What it may currently do, and why not. The availability ledger is empty
129
+ // until a refusal lands (POLICY_REAFFERENCE P2), which is exactly why nothing
130
+ // caught it: a mind that has never been told no writes none of these, so the
131
+ // quiet path and every test that stays on it are blind to the omission. Give
132
+ // a Will a PolicyArbiter that refuses, and it starts perceiving its own
133
+ // permissions changing as events in the world.
134
+ AVAILABILITY_ENTITY_TYPE,
135
+ 'action.unresolved', 'action.unaddressed',
136
+ CONSEQUENCE_TYPE, // forward-model records (EXAFFERENCE P1/P2)
137
+ REVOCATION_TYPE, // commitment tombstones (EXAFFERENCE P4)
138
+ SETTLEMENT_TYPE, // verdicts System 2 reached — having thought about it
139
+
140
+ // ── substrate ─────────────────────────────────────────────────
141
+ // Its configuration and its identity are constitutive of it, not events in
142
+ // its world. It had been perceiving both.
143
+ 'engine.config', 'will.identity', 'effector.created',
144
+ 'dream.activity',
145
+ ])
146
+
147
+ /** An engine that declares the entity types it writes about the mind itself. */
148
+ export interface DeclaresWrites {
149
+ /**
150
+ * Entity types this engine writes as part of the mind's own operation.
151
+ *
152
+ * Declare here anything the mind should NOT re-encounter through its outward
153
+ * senses. Omit it for engines that write about the WORLD — a host engine
154
+ * maintaining rooms, documents, or sensor readings wants those perceived, and
155
+ * silence is the right default for them.
156
+ */
157
+ readonly writes?: readonly string[]
158
+ }
159
+
160
+ /**
161
+ * The full endogenous set for a given assembly: the shipped anatomy plus
162
+ * whatever the registered engines declare.
163
+ *
164
+ * Cheap enough to call per tick, but Exteroception memoizes on the engine list
165
+ * so a 50-engine union isn't rebuilt in the perceptual hot path.
166
+ */
167
+ export function endogenousTypes( engines: readonly DeclaresWrites[] ): ReadonlySet<string> {
168
+ let extra: Set<string> | null = null
169
+ for( const e of engines )
170
+ for( const t of e.writes ?? [] )
171
+ if( !MIND_OWN_ENTITY_TYPES.has( t ) )
172
+ ( extra ??= new Set() ).add( t )
173
+
174
+ if( !extra ) return MIND_OWN_ENTITY_TYPES
175
+ return new Set([ ...MIND_OWN_ENTITY_TYPES, ...extra ])
176
+ }