@mindot/will 0.9.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 (110) hide show
  1. package/README.md +15 -11
  2. package/dist/index.d.ts +188 -141
  3. package/dist/index.js +16660 -15206
  4. package/dist/index.js.map +1 -1
  5. package/dist/{channels → surface/channels}/discord.d.ts +50 -3
  6. package/dist/{channels → surface/channels}/discord.js +102 -7
  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 +7 -4
  10. package/dist/surface/channels/whatsapp.js.map +1 -0
  11. package/dist/{cli.js → surface/cli.js} +4406 -2904
  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-DbDj_TEH.d.ts → will-evj9_vrd.d.ts} +5699 -4854
  17. package/package.json +11 -12
  18. package/src/cognition/agency/consequence.ts +237 -3
  19. package/src/cognition/agency/conversation.aim.ts +32 -0
  20. package/src/cognition/agency/engines/action.selector.ts +59 -3
  21. package/src/cognition/agency/engines/affordance.synthesizer.ts +114 -10
  22. package/src/cognition/agency/engines/deliberation.engine.ts +76 -3
  23. package/src/cognition/agency/engines/motor.schema.executor.ts +362 -27
  24. package/src/cognition/agency/engines/reafference.engine.ts +43 -4
  25. package/src/cognition/agency/execution.primitives.ts +17 -4
  26. package/src/cognition/agency/reconcile.learning.ts +5 -4
  27. package/src/cognition/agency/schemas/innate.ts +96 -1
  28. package/src/cognition/agency/schemas/repertoire.ts +19 -1
  29. package/src/cognition/agency/selection.scoring.ts +16 -0
  30. package/src/cognition/agency/settlement.ts +203 -0
  31. package/src/cognition/agency/types.ts +38 -1
  32. package/src/cognition/config.mirror.entities.ts +0 -1
  33. package/src/cognition/event.schemas.ts +0 -8
  34. package/src/cognition/faculties/affective.blender.ts +21 -4
  35. package/src/cognition/faculties/circadian.oscillator.ts +36 -7
  36. package/src/cognition/faculties/executive.engine/action.record.ts +129 -0
  37. package/src/cognition/faculties/executive.engine/context.ts +41 -35
  38. package/src/cognition/faculties/executive.engine/engine.ts +270 -113
  39. package/src/cognition/faculties/executive.engine/escalation.buffer.ts +126 -59
  40. package/src/cognition/faculties/executive.engine/facet.supervisor.ts +10 -0
  41. package/src/cognition/faculties/executive.engine/parser.ts +100 -38
  42. package/src/cognition/faculties/executive.engine/prompt.factory.ts +168 -23
  43. package/src/cognition/faculties/executive.engine/types.ts +18 -15
  44. package/src/cognition/faculties/exteroception.ts +99 -98
  45. package/src/cognition/faculties/goal.manager.ts +50 -2
  46. package/src/cognition/faculties/known.entity.tracker.ts +22 -3
  47. package/src/cognition/faculties/planning.engine/engine.ts +4 -4
  48. package/src/cognition/faculties/planning.engine/plan.frontier.ts +1 -1
  49. package/src/cognition/faculties/semantic.engine/integrator.ts +1 -1
  50. package/src/cognition/faculties/working.memory.ts +6 -1
  51. package/src/cognition/instruction.handler.ts +1 -1
  52. package/src/cognition/memory/vector.embedder.ts +1 -3
  53. package/src/cognition/percept.entity.ts +122 -0
  54. package/src/cognition/sense.boundary.ts +176 -0
  55. package/src/cognition/senses/audition.engine/engine.ts +97 -16
  56. package/src/cognition/senses/base.sense.engine.ts +104 -6
  57. package/src/cognition/senses/index.ts +71 -13
  58. package/src/cognition/senses/provenance.ts +128 -0
  59. package/src/cognition/senses/somatosensation.engine.ts +117 -9
  60. package/src/cognition/social.identity.ts +23 -0
  61. package/src/cognition/utilities/token.tracker.ts +36 -5
  62. package/src/core/orchestrator.ts +13 -0
  63. package/src/index.ts +39 -5
  64. package/src/llm/gate.ts +48 -0
  65. package/src/llm/index.ts +102 -25
  66. package/src/llm/wire.contracts.ts +38 -0
  67. package/src/pma/index.ts +1 -1
  68. package/src/stem/index.ts +92 -21
  69. package/src/stem/mind.ts +26 -2
  70. package/src/stem/policy/arbiter.ts +10 -0
  71. package/src/stem/profiles/built-in.ts +7 -0
  72. package/src/{profiles → stem/profiles}/companion.ts +1 -1
  73. package/src/{profiles → stem/profiles}/company-brain.ts +1 -1
  74. package/src/{profiles → stem/profiles}/customer-service.ts +1 -1
  75. package/src/{profiles → stem/profiles}/game-npc.ts +1 -1
  76. package/src/{profiles → stem/profiles}/index.ts +1 -1
  77. package/src/{profiles → stem/profiles}/smart-home.ts +1 -1
  78. package/src/stem/tracts/effector/escalation.lifecycle.ts +239 -0
  79. package/src/stem/tracts/effector/policy.enforcement.ts +261 -0
  80. package/src/stem/tracts/effector/types.ts +110 -0
  81. package/src/stem/tracts/effector.controller.ts +112 -401
  82. package/src/stem/tracts/outbox.controller.ts +30 -17
  83. package/src/stem/tracts/sensory.controller.ts +6 -6
  84. package/src/stem/tracts/session.logger.ts +6 -1
  85. package/src/stem/tracts/transport/types.ts +20 -1
  86. package/src/stem/tracts/transport.controller.ts +33 -9
  87. package/src/{channels → surface/channels}/discord.ts +224 -7
  88. package/src/{channels → surface/channels}/roster.ts +1 -1
  89. package/src/{channels → surface/channels}/types.ts +1 -1
  90. package/src/{channels → surface/channels}/whatsapp.ts +9 -6
  91. package/src/{cli.ts → surface/cli.ts} +6 -6
  92. package/src/{host → surface/host}/boot.ts +3 -3
  93. package/src/{host → surface/host}/utterances.ts +2 -2
  94. package/src/{mcp → surface/mcp}/effectors.ts +19 -6
  95. package/src/{mcp → surface/mcp}/server.ts +19 -5
  96. package/src/{sdk → surface/sdk}/will.ts +156 -28
  97. package/src/{serve → surface/serve}/server.ts +11 -4
  98. package/src/types.ts +24 -3
  99. package/dist/channels/discord.js.map +0 -1
  100. package/dist/channels/whatsapp.js.map +0 -1
  101. package/dist/cli.js.map +0 -1
  102. package/dist/mcp/effectors.js.map +0 -1
  103. package/src/extensions/livestream.ext.ts +0 -570
  104. package/src/extensions/time.ext.ts +0 -339
  105. package/src/profiles/built-in.ts +0 -7
  106. package/src/runners/coherence.runner.ts +0 -49
  107. package/src/runners/outreach.runner.ts +0 -155
  108. package/src/runners/social.runner.ts +0 -179
  109. package/src/runners/thin-shim.runner.ts +0 -205
  110. /package/dist/{cli.d.ts → surface/cli.d.ts} +0 -0
@@ -693,7 +693,12 @@ export class GoalManager implements SimulationEngine, CognitiveEngine {
693
693
  .find( g => {
694
694
  return g.tags.includes( mapping.tags[0]! )
695
695
  && g.tags.includes( mapping.tags[1]! )
696
- && g.status === 'active'
696
+ // `pending` counts as existing. It did not, and a
697
+ // goal demoted for capacity therefore stopped
698
+ // blocking its own respawn — the drive minted a
699
+ // fresh copy every time its threshold was crossed.
700
+ // Measured: 92 identical copies of one goal.
701
+ && ( g.status === 'active' || g.status === 'pending')
697
702
  })
698
703
 
699
704
  // Never spawn a goal that is *already satisfied* — it would complete on its
@@ -720,7 +725,9 @@ export class GoalManager implements SimulationEngine, CognitiveEngine {
720
725
  // Dedup by the referent (keid: tag) when present, else by description.
721
726
  const keidTag = tags.find( t => t.startsWith('keid:') )
722
727
  const existing = Array.from( this._goals.values() ).find( g =>
723
- g.status === 'active' && ( keidTag ? g.tags.includes( keidTag ) : g.description === desc ) )
728
+ // Same rule as the drive path: a waiting goal still exists.
729
+ ( g.status === 'active' || g.status === 'pending')
730
+ && ( keidTag ? g.tags.includes( keidTag ) : g.description === desc ) )
724
731
 
725
732
  // Don't spawn a goal that's already satisfied (born-done guard — see _activateFromDrives).
726
733
  if( !existing && !this._isConditionMet( cCond, state ) )
@@ -805,6 +812,32 @@ export class GoalManager implements SimulationEngine, CognitiveEngine {
805
812
  return 0
806
813
  }
807
814
 
815
+ /**
816
+ * Reconcile the active set to capacity — in BOTH directions.
817
+ *
818
+ * Demotion alone made `pending` a one-way door, and a mind fell through it.
819
+ * Nothing in this file promoted a goal back (the only other write of `'active'`
820
+ * is `pending_verification`'s, a different status), so a goal demoted for
821
+ * capacity was demoted for good. Worse, `pending` is excluded from every other
822
+ * mechanism that could have retired it: it is not in `getActiveGoals()`, not
823
+ * progress-updated, not reachable by the patience/grit sweep (which skips any
824
+ * status but `'active'`) — yet it IS re-persisted every tick and rehydrated on
825
+ * every restore. Inert and immortal at once.
826
+ *
827
+ * Measured on a live COO: 96 goal entities, of which **83 pending, 0 active**,
828
+ * and 92 of the 96 the same drive-spawned goal. `activeGoalCount` sat at 0, so
829
+ * `goalless_crisis` fired every 20 ticks forever while she carried 83 goals —
830
+ * and the `goal` term, the joint-largest weight in the affordance competition,
831
+ * contributed nothing to any choice she made.
832
+ *
833
+ * Promotion also restores GC reachability, which is the quieter half of the
834
+ * fix: an over-patient stale goal can only be abandoned while it is active.
835
+ *
836
+ * `activatedAt` is deliberately NOT refreshed on promotion. Refreshing it would
837
+ * let a goal cycle demote→promote and reset its own patience each time, making
838
+ * it unretirable — the immortality this fix exists to end, reintroduced by the
839
+ * back door. A goal's age is when it was taken up, not when it last got a slot.
840
+ */
808
841
  private _resolveConflicts(): void {
809
842
  const active = this.getActiveGoals()
810
843
 
@@ -813,6 +846,21 @@ export class GoalManager implements SimulationEngine, CognitiveEngine {
813
846
  const lowest = active.pop()
814
847
  if( lowest ) lowest.status = 'pending'
815
848
  }
849
+
850
+ // ...and take the best waiting goals back up when there is room. Only ever
851
+ // reached when UNDER capacity, so this cannot undo the demotion above:
852
+ // the loop leaves the set at exactly capacity, and this one starts below it.
853
+ if( active.length >= this._maxActiveGoals ) return
854
+
855
+ const waiting = Array.from( this._goals.values() )
856
+ .filter( g => g.status === 'pending')
857
+ .sort( ( a, b ) => b.priority - a.priority )
858
+
859
+ for( const goal of waiting ){
860
+ if( active.length >= this._maxActiveGoals ) break
861
+ goal.status = 'active'
862
+ active.push( goal )
863
+ }
816
864
  }
817
865
 
818
866
  // ── Internal: progress ───────────────────────────────────
@@ -108,7 +108,16 @@ const SENTIENT_DOMAINS = new Set<string>([ 'audition' ])
108
108
  // specific "get to know them" pull; it subsides once resolution reaches RESOLVED.
109
109
  const CURIOUS_FAMILIARITY = 0.5
110
110
  const CURIOUS_RESOLUTION = 0.4
111
- const CURIOUS_RESOLVED = 0.6
111
+ /**
112
+ * Resolved enough to stop wondering — the point the curiosity goal completes at.
113
+ *
114
+ * Exported because the AffordanceSynthesizer offers `inspect` against referents
115
+ * BELOW it. Two readers, one constant, deliberately: if the act that satisfies
116
+ * curiosity and the goal that measures it disagreed about when something is
117
+ * known, a mind could keep being offered a look at what it had already placed —
118
+ * or stop being offered one while the goal it holds is still open.
119
+ */
120
+ export const CURIOUS_RESOLVED = 0.6
112
121
 
113
122
  // Forgetting (Phase 4). (Reliability rate is now a Channel-A developable field — analytical.)
114
123
  const FORGET_FLOOR = 0.02 // below this familiarity, an unidentified blip is forgotten
@@ -154,6 +163,8 @@ export class KnownEntityTracker implements SimulationEngine, CognitiveEngine {
154
163
  keid: string; domain: string; name?: string
155
164
  /** The room this encounter happened in, and whether it was a private one. */
156
165
  thread?: string; direct?: boolean
166
+ /** What that room is called, where the channel offered a label. */
167
+ threadName?: string
157
168
  }> = []
158
169
  // Buffered from known.entity.learned (the conscious / reasoning write-path, Phase 2.2).
159
170
  private _pendingConscious: Array<{
@@ -247,12 +258,15 @@ export class KnownEntityTracker implements SimulationEngine, CognitiveEngine {
247
258
  // `direct` is the one bit the Discord edge computed (`isDM`) and threw away
248
259
  // before the mind could see it — the single fact that decides whether a room
249
260
  // is the right place for a given utterance.
250
- const raw = p?.raw as { speakerName?: unknown; threadId?: unknown; direct?: unknown } | undefined
261
+ const raw = p?.raw as { speakerName?: unknown; threadId?: unknown; direct?: unknown; threadName?: unknown } | undefined
251
262
  const name = typeof raw?.speakerName === 'string' ? raw.speakerName : undefined
252
263
  const thread = typeof raw?.threadId === 'string' ? raw.threadId : undefined
253
264
  const direct = typeof raw?.direct === 'boolean' ? raw.direct : undefined
265
+ // What the room is CALLED. A place has had a dossier since 0.9.0 and no way
266
+ // to be named, so every room the mind knew rendered as "something".
267
+ const threadName = typeof raw?.threadName === 'string' ? raw.threadName : undefined
254
268
 
255
- this._pendingEncounters.push({ keid, domain: p!.domain, name, thread, direct })
269
+ this._pendingEncounters.push({ keid, domain: p!.domain, name, thread, direct, threadName })
256
270
  }
257
271
 
258
272
  snapshot(): Record<string, unknown> {
@@ -319,6 +333,11 @@ export class KnownEntityTracker implements SimulationEngine, CognitiveEngine {
319
333
  place.encounterCount += 1
320
334
  place.familiarity = Math.min( 1, place.familiarity + this._growthRate * ( 1 - place.familiarity ) )
321
335
  place.lastSeenTick = tick
336
+ // A room learns its name the same way a person does — from what the
337
+ // channel offers, and only while it has none. Re-taken every encounter
338
+ // it would churn the cached prompt on a rename; more to the point, a
339
+ // name the mind already holds is the mind's, not the platform's.
340
+ if( enc.threadName && !place.name ) place.name = enc.threadName
322
341
  place.resolutionConfidence = this._resolution( place )
323
342
  }
324
343
  }
@@ -20,7 +20,7 @@
20
20
  * competition: each tick it projects its ready frontier step(s) as `plan.prior`
21
21
  * entities the AffordanceSynthesizer turns into competing affordances (top-down
22
22
  * prior, never a bypass — see PLANNING_AS_PRIOR_TODO.md). The ordinary selector
23
- * enacts the winner; the executor emits `action.outcome{planId,stepId}` (provenance
23
+ * enacts the winner; the executor emits `action.outcome{planId,stepId}` (the plan link
24
24
  * carried through the affordance→intent chain), which this engine consumes to
25
25
  * advance the frontier. Resolves the prerequisite DAG to choose the ready frontier.
26
26
  *
@@ -207,7 +207,7 @@ export class PlanningEngine implements SimulationEngine, CognitiveEngine {
207
207
  planId?: string; stepId?: string
208
208
  }
209
209
 
210
- // Conscious-enaction credit: outcomes carry plan provenance only when
210
+ // Conscious-enaction credit: outcomes carry a plan link only when
211
211
  // the plan's OWN frontier prior won the competition. But the plan is a
212
212
  // prior over WHAT to do — if the self does the very thing an active
213
213
  // step calls for by any route (executive action via ideomotor, habit),
@@ -222,7 +222,7 @@ export class PlanningEngine implements SimulationEngine, CognitiveEngine {
222
222
  if( plan.status !== 'executing') continue
223
223
  const step = plan.steps.find( s => s.status === 'active' && s.action === p.actionType )
224
224
  if( !step ) continue
225
- logger.info(`[planning] conscious-enaction credit: ${plan.id}/${step.id}=${step.action} (no provenance on outcome)`)
225
+ logger.info(`[planning] conscious-enaction credit: ${plan.id}/${step.id}=${step.action} (no plan link on outcome)`)
226
226
  this._onStepOutcome( plan.id, step.id, {
227
227
  success: p.success,
228
228
  description: p.description ?? ( p.success ? 'Completed' : 'Failed'),
@@ -656,7 +656,7 @@ export class PlanningEngine implements SimulationEngine, CognitiveEngine {
656
656
  // pause/escalate, or a terminal goal — it stops projecting its frontier. But an
657
657
  // `agency.intent` the competition had ALREADY committed from a frontier prior can
658
658
  // still resolve a few ticks later (sync this tick, or an awaiting one timing out).
659
- // That outcome carries this plan's provenance, so it lands here. We must NOT act on
659
+ // That outcome carries this plan's link, so it lands here. We must NOT act on
660
660
  // it: completion is the executive's call, not the late arrival of a step it has
661
661
  // already moved past — and re-running supervision below could re-spawn the facet
662
662
  // we just tore down. The enaction itself still happened (and still taught the
@@ -37,7 +37,7 @@ export function computeReadySet( plan: Plan ): PlanStep[] {
37
37
  * entities — the top-down bias the AffordanceSynthesizer reads. Rebuilt each tick
38
38
  * (cleared then re-emitted, like the affordance field), so a frontier that
39
39
  * advances or a plan that ends stops biasing automatically. The prior carries the
40
- * planId/stepId provenance that flows affordance → intent → action.outcome, and a
40
+ * planId/stepId plan link that flows affordance → intent → action.outcome, and a
41
41
  * `planBias` strength from the goal's importance ⊕ the plan's confidence. It never
42
42
  * forces an action — if a more pressing affordance wins, the plan re-projects next
43
43
  * tick (no orphaning).
@@ -634,7 +634,7 @@ export class SemanticIntegrator implements SimulationEngine, CognitiveEngine {
634
634
 
635
635
  // Seed or preserve history.
636
636
  // If the belief arrives with an existing history (e.g. from a PMM load or
637
- // snapshot restore), preserve it and append the load event so provenance
637
+ // snapshot restore), preserve it and append the load event so the lineage
638
638
  // is traceable without losing the full trajectory.
639
639
  // For brand-new beliefs (no prior history) seed a single creation entry.
640
640
  if( newBelief.history && newBelief.history.length > 0 ){
@@ -280,7 +280,12 @@ export class WorkingMemory implements SimulationEngine, CognitiveEngine {
280
280
  this._items.push({
281
281
  id: `wm-percept-${entity.id}`,
282
282
  type: 'percept',
283
- content: { summary, entityId: entity.id },
283
+ // The data too, not only the label. A percept entity is swept after 2
284
+ // ticks; WM is where it is remembered, and remembering a sentence about
285
+ // the evidence instead of the evidence is how a mind ends up unable to
286
+ // answer a question it already had the answer to.
287
+ content: { summary, entityId: entity.id,
288
+ ...( entity.metadata?.data !== undefined ? { data: entity.metadata.data } : {} ) },
284
289
  activation: 0.75,
285
290
  attendedAt: [],
286
291
  createdAt: tick,
@@ -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
@@ -67,8 +67,6 @@ export class OpenAICompatibleEmbedder implements EmbeddingProvider {
67
67
  * and return "no recall" while a mind with six live facets was asking.
68
68
  */
69
69
  maxConcurrency?: number
70
- /** @deprecated use maxConcurrency — kept as its fallback for back-compat. */
71
- batchSize?: number
72
70
  /** Per-request timeout in ms before the connection is aborted. Default 30s. */
73
71
  timeoutMs?: number
74
72
  /**
@@ -82,7 +80,7 @@ export class OpenAICompatibleEmbedder implements EmbeddingProvider {
82
80
  this.dimensions = config.dimensions
83
81
  this._apiUrl = config.apiUrl
84
82
  this._apiKey = config.apiKey ?? null
85
- this._maxConcurrency = Math.max( 1, config.maxConcurrency ?? config.batchSize ?? 4 )
83
+ this._maxConcurrency = Math.max( 1, config.maxConcurrency ?? 4 )
86
84
  this._gate = new LLMSemaphore( this._maxConcurrency )
87
85
  this._timeoutMs = config.timeoutMs ?? 30_000
88
86
  this._tokenTracker = config.tokenTracker ?? null
@@ -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
+ }
@@ -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
+ }