@mindot/will 0.4.0 → 0.6.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.
- package/README.md +32 -12
- package/dist/channels/discord.d.ts +2 -11
- package/dist/channels/discord.js.map +1 -1
- package/dist/channels/whatsapp.d.ts +72 -0
- package/dist/channels/whatsapp.js +252 -0
- package/dist/channels/whatsapp.js.map +1 -0
- package/dist/cli.js +837 -91
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +620 -79
- package/dist/index.js.map +1 -1
- package/dist/mcp/effectors.d.ts +1 -1
- package/dist/mcp/effectors.js.map +1 -1
- package/dist/types-E9-HV-SW.d.ts +11 -0
- package/dist/{will-D-slky1N.d.ts → will-Bikuk4s2.d.ts} +56 -12
- package/package.json +12 -3
- package/src/channels/discord.ts +11 -11
- package/src/channels/roster.ts +1 -1
- package/src/channels/types.ts +1 -1
- package/src/channels/whatsapp.ts +318 -0
- package/src/cli.ts +46 -23
- package/src/cognition/acp.ts +28 -0
- package/src/cognition/agency/consequence.ts +174 -0
- package/src/cognition/agency/engines/action.selector.ts +301 -48
- package/src/cognition/agency/engines/affordance.synthesizer.ts +26 -16
- package/src/cognition/agency/engines/deliberation.engine.ts +28 -14
- package/src/cognition/agency/engines/motor.schema.executor.ts +86 -14
- package/src/cognition/agency/engines/reafference.engine.ts +122 -18
- package/src/cognition/agency/execution.primitives.ts +13 -13
- package/src/cognition/agency/proactive.communicator.ts +1 -1
- package/src/cognition/agency/reconcile.learning.ts +1 -1
- package/src/cognition/agency/revocation.ts +79 -0
- package/src/cognition/agency/schemas/repertoire.ts +2 -2
- package/src/cognition/agency/selection.scoring.ts +25 -9
- package/src/cognition/bus.ts +2 -2
- package/src/cognition/completion.inbox.ts +1 -1
- package/src/cognition/config.mirror.entities.ts +5 -5
- package/src/cognition/event.log.ts +2 -2
- package/src/cognition/event.schemas.ts +67 -67
- package/src/cognition/faculties/aesthetic.evaluator.ts +6 -6
- package/src/cognition/faculties/affective.blender.ts +36 -4
- package/src/cognition/faculties/attachment.evaluator.ts +6 -6
- package/src/cognition/faculties/attention.allocator.ts +63 -8
- package/src/cognition/faculties/autobiographical.narrator.ts +3 -3
- package/src/cognition/faculties/bias.detector.ts +7 -7
- package/src/cognition/faculties/circadian.oscillator.ts +3 -3
- package/src/cognition/faculties/confidence.calibrator.ts +7 -7
- package/src/cognition/faculties/dream.simulator.ts +5 -5
- package/src/cognition/faculties/empathy.simulator.ts +7 -7
- package/src/cognition/faculties/energy.regulator.ts +4 -4
- package/src/cognition/faculties/episodic.consolidator.ts +6 -6
- package/src/cognition/faculties/executive.engine/commands.ts +6 -6
- package/src/cognition/faculties/executive.engine/context.ts +42 -42
- package/src/cognition/faculties/executive.engine/deferred.effects.ts +2 -2
- package/src/cognition/faculties/executive.engine/engine.ts +27 -26
- package/src/cognition/faculties/executive.engine/facet.supervisor.ts +12 -12
- package/src/cognition/faculties/executive.engine/facet.ts +18 -18
- package/src/cognition/faculties/executive.engine/gating.ts +2 -2
- package/src/cognition/faculties/executive.engine/messages.ts +3 -3
- package/src/cognition/faculties/executive.engine/parser.ts +11 -11
- package/src/cognition/faculties/executive.engine/prompt.factory.ts +55 -55
- package/src/cognition/faculties/exteroception.ts +99 -5
- package/src/cognition/faculties/forgetting.curve.ts +3 -3
- package/src/cognition/faculties/frustration.evaluator.ts +6 -6
- package/src/cognition/faculties/goal.manager.ts +15 -15
- package/src/cognition/faculties/inhibition.controller.ts +3 -3
- package/src/cognition/faculties/interoception.ts +3 -3
- package/src/cognition/faculties/introspection.engine.ts +3 -3
- package/src/cognition/faculties/known.entity.tracker.ts +15 -15
- package/src/cognition/faculties/loss.evaluator.ts +2 -2
- package/src/cognition/faculties/moral.evaluator.ts +4 -4
- package/src/cognition/faculties/novelty.detector.ts +4 -4
- package/src/cognition/faculties/persona.consolidator.ts +12 -12
- package/src/cognition/faculties/planning.engine/engine.ts +47 -47
- package/src/cognition/faculties/planning.engine/plan.frontier.ts +5 -5
- package/src/cognition/faculties/planning.engine/plan.store.ts +1 -1
- package/src/cognition/faculties/planning.engine/plan.supervision.ts +20 -20
- package/src/cognition/faculties/reputation.tracker.ts +6 -6
- package/src/cognition/faculties/reward.evaluator.ts +4 -4
- package/src/cognition/faculties/self.model.updater.ts +8 -8
- package/src/cognition/faculties/semantic.engine/integrator.ts +15 -15
- package/src/cognition/faculties/sleep.pressure.regulator.ts +4 -4
- package/src/cognition/faculties/social.perception.ts +3 -3
- package/src/cognition/faculties/spaced.repetition.ts +2 -2
- package/src/cognition/faculties/stress.regulator.ts +27 -4
- package/src/cognition/faculties/task.switcher.ts +11 -6
- package/src/cognition/faculties/theory.of.mind.ts +6 -6
- package/src/cognition/faculties/threat.evaluator.ts +3 -3
- package/src/cognition/faculties/working.memory.ts +8 -8
- package/src/cognition/generative.model.ts +2 -2
- package/src/cognition/memory/vector.adapter.ts +5 -5
- package/src/cognition/memory/vector.content.ts +4 -4
- package/src/cognition/memory/vector.embedder.ts +9 -9
- package/src/cognition/persona.prior.ts +1 -1
- package/src/cognition/schema.registry.ts +2 -2
- package/src/cognition/senses/audition.engine/engine.ts +2 -2
- package/src/cognition/senses/base.sense.engine.ts +1 -1
- package/src/cognition/utilities/token.tracker.ts +11 -5
- package/src/core/abstracts.ts +1 -1
- package/src/core/completion.recorder.ts +2 -2
- package/src/core/serialization.ts +3 -3
- package/src/core/snapshot.manager.ts +1 -1
- package/src/core/state.manager.ts +1 -1
- package/src/core/utils.ts +1 -1
- package/src/host/boot.ts +59 -37
- package/src/host/utterances.ts +1 -1
- package/src/llm/index.ts +84 -34
- package/src/llm/summarizer.ts +4 -4
- package/src/llm/wire.contracts.ts +1 -1
- package/src/mcp/effectors.ts +10 -10
- package/src/mcp/server.ts +11 -11
- package/src/pma/eval.ts +1 -1
- package/src/pma/index.ts +21 -21
- package/src/runners/outreach.runner.ts +3 -3
- package/src/runners/social.runner.ts +1 -1
- package/src/runners/thin-shim.runner.ts +1 -1
- package/src/sdk/will.ts +20 -11
- package/src/serve/server.ts +12 -12
- package/src/stem/assembly.audit.ts +3 -3
- package/src/stem/guards/identity.coherence.ts +13 -12
- package/src/stem/guards/identity.guard.ts +22 -22
- package/src/stem/index.ts +11 -11
- package/src/stem/mind.ts +12 -12
- package/src/stem/tracts/biography.writer.ts +3 -3
- package/src/stem/tracts/effector.controller.ts +4 -4
- package/src/stem/tracts/health.reporter.ts +1 -1
- package/src/stem/tracts/outbox.controller.ts +4 -0
- package/src/stem/tracts/outbox.writer.ts +4 -4
- package/src/stem/tracts/pma.controller.ts +1 -1
- package/src/stem/tracts/replay.controller.ts +10 -10
- package/src/stem/tracts/sensory.controller.ts +2 -2
- package/src/stem/tracts/session.logger.ts +4 -4
- package/src/stem/tracts/transport/socketio.transport.ts +7 -7
- package/src/stem/tracts/transport/stream.transport.ts +2 -2
- package/src/stem/tracts/transport.controller.ts +15 -15
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
// The ActionSelector runs every tick with no LLM. It reads the affordance field
|
|
6
6
|
// the synthesizer wrote, assembles the bias signals the affective / drive /
|
|
7
7
|
// inhibition engines already expose, and runs a soft winner-take-all (see
|
|
8
|
-
// scoring.ts). The winner is committed as an `agency.intent` for
|
|
8
|
+
// selection.scoring.ts). The winner is committed as an `agency.intent` for
|
|
9
|
+
// the executor.
|
|
9
10
|
//
|
|
10
11
|
// System 1 is the default: the substrate selects and commits on its own. The
|
|
11
12
|
// executive (System 2, the LLM) is recruited only when the choice is genuinely
|
|
@@ -25,7 +26,7 @@ import type {
|
|
|
25
26
|
Duration, Tick, SimulationContext,
|
|
26
27
|
ReadonlySimulationState, StateCommands, EntityInput,
|
|
27
28
|
} from '#core/types'
|
|
28
|
-
import type { CognitiveBus } from '#cognition/bus'
|
|
29
|
+
import type { CognitiveBus, CognitiveEvent } from '#cognition/bus'
|
|
29
30
|
import type { CognitiveEngine, EngineResult } from '#cognition/types'
|
|
30
31
|
import type { CognitiveEventSchema } from '#cognition/schema.registry'
|
|
31
32
|
import type { Affordance, AffordanceSource, ScoredAffordance } from '#agency/types'
|
|
@@ -35,6 +36,8 @@ import {
|
|
|
35
36
|
type BiasContext, type ScoreWeights,
|
|
36
37
|
} from '#agency/selection.scoring'
|
|
37
38
|
import { readEffectiveParams, readPersonaPrior } from '#cognition/persona.prior'
|
|
39
|
+
import { RUPTURE_REVOKE_GATE, revocationEntity } from '#agency/revocation'
|
|
40
|
+
import { liveConsequences, matchConsequenceText } from '#agency/consequence'
|
|
38
41
|
|
|
39
42
|
/**
|
|
40
43
|
* Activation margin (winner − runner-up) below which the choice is "contested" —
|
|
@@ -94,12 +97,44 @@ const AWAIT_STALE_TICKS = 15
|
|
|
94
97
|
/** How much a fully-stale awaiting incumbent's strength decays (0..1). */
|
|
95
98
|
const STALE_DECAY = 0.5
|
|
96
99
|
|
|
100
|
+
// ── Exafferent rupture (EXAFFERENCE P3) ────────────────────────
|
|
101
|
+
/**
|
|
102
|
+
* The world can revoke engagement, not only win it. `rupture` is a scalar in
|
|
103
|
+
* [0,1] read pull-style from the exafferent percepts P2 tagged: when the world
|
|
104
|
+
* moves on its own strongly enough to seize attention, it softens the Will's
|
|
105
|
+
* commitment to what it was doing — "let go of waiting". Gate = WORKSPACE_THRESHOLD
|
|
106
|
+
* so rupture ≈ "this percept would seize the global workspace". Because only
|
|
107
|
+
* `provenance:'exafferent'` percepts count, the mind can never be ruptured by the
|
|
108
|
+
* echo of its own action (P2 tagged those `reafferent` and attenuated them).
|
|
109
|
+
*/
|
|
110
|
+
const RUPTURE_SALIENCE_GATE = 0.4
|
|
111
|
+
/** Count exafferent percepts this fresh — matches the percept lifespan
|
|
112
|
+
* (Exteroception cleans up percepts older than 2 ticks), so a shock sustains
|
|
113
|
+
* rupture for exactly as long as its percept is alive, then decays. */
|
|
114
|
+
const RUPTURE_WINDOW_TICKS = 2
|
|
115
|
+
/** Per-tick mean-reversion of `situation.stability` back toward 1 (calm). */
|
|
116
|
+
const STABILITY_RECOVERY = 0.05
|
|
117
|
+
/** Snap-to-1 threshold: a settled mind stops re-writing the stability metric,
|
|
118
|
+
* so a never-ruptured run is byte-identical to pre-P3 (the metric is absent). */
|
|
119
|
+
const STABILITY_EPSILON = 1e-4
|
|
120
|
+
/** How many ticks a revocation stays "recent" enough to flavor the next
|
|
121
|
+
* deliberation formed in its wake (Channel-B hint); after this it's forgotten. */
|
|
122
|
+
const REVOKE_HINT_WINDOW = 8
|
|
123
|
+
|
|
97
124
|
export class ActionSelector implements CognitiveEngine {
|
|
98
125
|
readonly name = 'action-selector'
|
|
99
126
|
|
|
100
127
|
private _bus: CognitiveBus | null = null
|
|
101
128
|
private _lastEntropy = 0
|
|
102
129
|
private _lastDeliberate = false
|
|
130
|
+
// EXAFFERENCE P4 follow-up (Channel B): the schema we most recently revoked
|
|
131
|
+
// under rupture, so the *next* deliberation formed in its wake can own the
|
|
132
|
+
// interruption in-character ("something changed — I dropped what I was
|
|
133
|
+
// weighing"). Telemetry-grade instance state, mirroring _lastEntropy.
|
|
134
|
+
private _lastRevoked: { schema: string; tick: number } | null = null
|
|
135
|
+
// ACP §2b: sense-channel percepts buffered off the bus for the next react's
|
|
136
|
+
// rupture computation (they never become entities). Cross-tick ⇒ FN9.
|
|
137
|
+
private _senseBuffer: Array<{ salience: number; text?: string }> = []
|
|
103
138
|
|
|
104
139
|
attachBus( bus: CognitiveBus ): void { this._bus = bus }
|
|
105
140
|
|
|
@@ -108,12 +143,62 @@ export class ActionSelector implements CognitiveEngine {
|
|
|
108
143
|
{ type: 'agency.selection.made', version: 1, validate: () => null },
|
|
109
144
|
{ type: 'agency.selection.ambiguous', version: 1, validate: () => null },
|
|
110
145
|
{ type: 'agency.action.preempted', version: 1, validate: () => null },
|
|
146
|
+
{ type: 'agency.situation.rupture', version: 1, validate: () => null },
|
|
147
|
+
{ type: 'agency.commitment.revoked', version: 1, validate: () => null },
|
|
148
|
+
]
|
|
149
|
+
}
|
|
150
|
+
subscribes(): string[] {
|
|
151
|
+
return [
|
|
152
|
+
'senses.*',
|
|
153
|
+
// Registry #6 (ACP-P3): the model-error term — each carries its
|
|
154
|
+
// engine's prediction-error salience, ALREADY precision-attenuated for
|
|
155
|
+
// self-caused swings by the ACP-P2 consumers, so the echo guard
|
|
156
|
+
// composes by construction: our own action's interoceptive wake
|
|
157
|
+
// arrives below the rupture gate.
|
|
158
|
+
'attention.state.changed', 'affect.state.changed', 'stress.state.changed',
|
|
111
159
|
]
|
|
112
160
|
}
|
|
113
|
-
|
|
114
|
-
|
|
161
|
+
/**
|
|
162
|
+
* Mostly pull-model — but two afferent classes never become entities and
|
|
163
|
+
* would leave rupture blind: sense-channel percepts
|
|
164
|
+
* (ACTION_CONDITIONED_PREDICTION §2b) and the model-error state-change
|
|
165
|
+
* events (registry #6). Buffer both (cross-tick: bus flush at T, consumed
|
|
166
|
+
* by react at T+1 — FN9-snapshotted); the echo guard is applied at read
|
|
167
|
+
* time for texts, and at the SOURCE for model errors (ACP-P2 precision).
|
|
168
|
+
*/
|
|
169
|
+
onCognitiveEvent( e: CognitiveEvent ): void {
|
|
170
|
+
const isSense = e.type.startsWith('senses.') && e.type.endsWith('.percept')
|
|
171
|
+
const isModelError = e.type === 'attention.state.changed'
|
|
172
|
+
|| e.type === 'affect.state.changed' || e.type === 'stress.state.changed'
|
|
173
|
+
if( !isSense && !isModelError ) return
|
|
174
|
+
const p = e.payload as { content?: unknown } | undefined
|
|
175
|
+
this._senseBuffer.push({
|
|
176
|
+
salience: e.salience,
|
|
177
|
+
...( isSense && typeof p?.content === 'string' ? { text: p.content } : {} ),
|
|
178
|
+
})
|
|
179
|
+
}
|
|
115
180
|
snapshot(): Record<string, unknown> {
|
|
116
|
-
return {
|
|
181
|
+
return {
|
|
182
|
+
lastEntropy: this._lastEntropy, lastDeliberate: this._lastDeliberate,
|
|
183
|
+
lastRevoked: this._lastRevoked, senseBuffer: this._senseBuffer,
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/** FN9: `_lastRevoked` has behavioral effect (the Channel-B `revokedBy` stamp),
|
|
187
|
+
* so a restored mind must carry it — a rupture-driven letting-go survives a
|
|
188
|
+
* snapshot boundary instead of silently losing its narrative thread. */
|
|
189
|
+
restore( s: Record<string, unknown> ): void {
|
|
190
|
+
if( typeof s['lastEntropy'] === 'number' ) this._lastEntropy = s['lastEntropy']
|
|
191
|
+
if( typeof s['lastDeliberate'] === 'boolean' ) this._lastDeliberate = s['lastDeliberate']
|
|
192
|
+
const lr = s['lastRevoked'] as { schema?: unknown; tick?: unknown } | null | undefined
|
|
193
|
+
this._lastRevoked = lr && typeof lr === 'object' && typeof lr.schema === 'string' && typeof lr.tick === 'number'
|
|
194
|
+
? { schema: lr.schema, tick: lr.tick }
|
|
195
|
+
: null
|
|
196
|
+
const sb = s['senseBuffer']
|
|
197
|
+
this._senseBuffer = Array.isArray( sb )
|
|
198
|
+
? ( sb as Array<{ salience?: unknown; text?: unknown }> )
|
|
199
|
+
.filter( it => it && typeof it.salience === 'number' )
|
|
200
|
+
.map( it => ({ salience: it.salience as number, ...( typeof it.text === 'string' ? { text: it.text } : {} ) }) )
|
|
201
|
+
: []
|
|
117
202
|
}
|
|
118
203
|
|
|
119
204
|
async react(
|
|
@@ -131,11 +216,14 @@ export class ActionSelector implements CognitiveEngine {
|
|
|
131
216
|
const eligible: Affordance[] = []
|
|
132
217
|
const intents: Array<{ id: string; st: string; parentIntentId?: string; activation: number; schema: string; target: string; dispatchedAt: number }> = []
|
|
133
218
|
const expandingParents = new Set<string>()
|
|
219
|
+
|
|
134
220
|
for( const [ id, e ] of state.entities ){
|
|
135
|
-
if( e.type === 'agency.intent'
|
|
221
|
+
if( e.type === 'agency.intent'){
|
|
136
222
|
const m = ( e.metadata ?? {} ) as Record<string, unknown>
|
|
137
223
|
const st = str( m['status'] ) ?? ''
|
|
138
|
-
|
|
224
|
+
|
|
225
|
+
if( st === 'expanding') expandingParents.add( id )
|
|
226
|
+
|
|
139
227
|
intents.push({
|
|
140
228
|
id, st,
|
|
141
229
|
parentIntentId: str( m['parentIntentId'] ),
|
|
@@ -144,9 +232,12 @@ export class ActionSelector implements CognitiveEngine {
|
|
|
144
232
|
target: str( m['targetEntityId'] ) ?? '',
|
|
145
233
|
dispatchedAt: num( m['dispatchedAt'], tick ),
|
|
146
234
|
})
|
|
235
|
+
|
|
147
236
|
continue
|
|
148
237
|
}
|
|
149
|
-
|
|
238
|
+
|
|
239
|
+
if( e.type !== 'affordance') continue
|
|
240
|
+
|
|
150
241
|
const a = readAffordance( id, e.metadata )
|
|
151
242
|
if( a.available ) eligible.push( a )
|
|
152
243
|
}
|
|
@@ -157,25 +248,96 @@ export class ActionSelector implements CognitiveEngine {
|
|
|
157
248
|
let blocking = false
|
|
158
249
|
let awaiting: { id: string; activation: number; schema: string; target: string; dispatchedAt: number } | null = null
|
|
159
250
|
let composite: { id: string; activation: number; schema: string } | null = null
|
|
251
|
+
let deliberating: { id: string; schema: string } | null = null
|
|
252
|
+
|
|
160
253
|
for( const it of intents ){
|
|
161
|
-
if( it.st === 'deliberating'
|
|
162
|
-
else if( it.st === 'expanding'
|
|
163
|
-
else if( it.st === 'awaiting'
|
|
164
|
-
else if( it.st === 'selected'
|
|
254
|
+
if( it.st === 'deliberating'){ blocking = true; deliberating = { id: it.id, schema: it.schema } }
|
|
255
|
+
else if( it.st === 'expanding') composite = { id: it.id, activation: it.activation, schema: it.schema }
|
|
256
|
+
else if( it.st === 'awaiting') awaiting = { id: it.id, activation: it.activation, schema: it.schema, target: it.target, dispatchedAt: it.dispatchedAt }
|
|
257
|
+
else if( it.st === 'selected'){
|
|
165
258
|
const activeMacroSub = it.parentIntentId !== undefined && expandingParents.has( it.parentIntentId )
|
|
166
259
|
if( !activeMacroSub ) blocking = true // standalone or orphan sub → finish it
|
|
167
260
|
}
|
|
168
261
|
}
|
|
169
262
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
263
|
+
// ── Exafferent rupture (EXAFFERENCE P3) ──────────────────────
|
|
264
|
+
// How hard the world is pulling at us this tick (from P2's exafferent
|
|
265
|
+
// percepts), and the slow-moving `situation.stability` it erodes. Both are
|
|
266
|
+
// pure reads of frozen state; `stabMetrics` is empty on a never-ruptured run
|
|
267
|
+
// so the quiet path stays byte-identical to pre-P3.
|
|
268
|
+
const senseEvents = this._senseBuffer
|
|
269
|
+
this._senseBuffer = [] // consumed exactly once
|
|
270
|
+
const rupture = computeRupture( state, tick, senseEvents )
|
|
271
|
+
const prevStab = metric( state, 'situation.stability', 1 )
|
|
272
|
+
const nextStabRaw = clamp01( prevStab + STABILITY_RECOVERY * ( 1 - prevStab ) - rupture )
|
|
273
|
+
const nextStab = 1 - nextStabRaw < STABILITY_EPSILON ? 1 : nextStabRaw
|
|
274
|
+
const stabMetrics: Array<[ string, number ]> =
|
|
275
|
+
( rupture > 0 || prevStab < 1 ) ? [ [ 'situation.stability', nextStab ] ] : []
|
|
276
|
+
|
|
277
|
+
if( rupture > 0 && this._bus ){
|
|
278
|
+
try {
|
|
279
|
+
this._bus.publish({
|
|
280
|
+
type: 'agency.situation.rupture', version: 1, sourceEngine: this.name,
|
|
281
|
+
salience: rupture, payload: { rupture, stability: nextStab, tick },
|
|
282
|
+
})
|
|
283
|
+
}
|
|
284
|
+
catch( err ){ logger.warn(`[selector] rupture publish failed: ${ err instanceof Error ? err.message : String( err ) }`) }
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const busy = ( n: number ): EngineResult => ({
|
|
288
|
+
commands: {
|
|
289
|
+
metrics: [
|
|
290
|
+
[ 'agency.field.eligible', n ],
|
|
291
|
+
[ 'agency.selection.busy', 1 ],
|
|
292
|
+
...stabMetrics,
|
|
293
|
+
]
|
|
294
|
+
}
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
// ── Commitment revocation (EXAFFERENCE P4) ───────────────────
|
|
298
|
+
// A hard rupture doesn't just soften the switch cost — it lets go of a
|
|
299
|
+
// commitment still being weighed. We can't delete the `deliberating` intent
|
|
300
|
+
// here (Deliberation runs after us and would resurrect it set-after-delete),
|
|
301
|
+
// so we drop a tombstone the Deliberation engine + Executor honor next tick.
|
|
302
|
+
// No successor is committed — the field re-forms and the next tick selects.
|
|
303
|
+
if( deliberating && rupture >= RUPTURE_REVOKE_GATE ){
|
|
304
|
+
if( this._bus ){
|
|
305
|
+
try {
|
|
306
|
+
this._bus.publish({
|
|
307
|
+
type: 'agency.commitment.revoked', version: 1, sourceEngine: this.name,
|
|
308
|
+
salience: 0.85,
|
|
309
|
+
payload: { from: deliberating.schema, reason: 'exafferent-rupture', rupture, tick },
|
|
310
|
+
})
|
|
311
|
+
}
|
|
312
|
+
catch( err ){ logger.warn(`[selector] revoked publish failed: ${ err instanceof Error ? err.message : String( err ) }`) }
|
|
313
|
+
}
|
|
314
|
+
this._lastRevoked = { schema: deliberating.schema, tick } // Channel-B: flavor the next deliberation
|
|
315
|
+
return {
|
|
316
|
+
commands: {
|
|
317
|
+
set: [ revocationEntity( deliberating.id, deliberating.schema, rupture, tick ) ],
|
|
318
|
+
metrics: [
|
|
319
|
+
[ 'agency.field.eligible', eligible.length ],
|
|
320
|
+
[ 'agency.selection.busy', 1 ],
|
|
321
|
+
[ 'agency.commitment.revoked', 1 ],
|
|
322
|
+
...stabMetrics,
|
|
323
|
+
],
|
|
324
|
+
},
|
|
325
|
+
}
|
|
326
|
+
}
|
|
173
327
|
|
|
174
328
|
if( blocking ) return busy( eligible.length )
|
|
175
329
|
|
|
176
330
|
// Nothing afforded → idle (if an action is in flight, keep waiting).
|
|
177
331
|
if( eligible.length === 0 )
|
|
178
|
-
return {
|
|
332
|
+
return {
|
|
333
|
+
commands: {
|
|
334
|
+
metrics: [
|
|
335
|
+
[ 'agency.field.eligible', 0 ],
|
|
336
|
+
[ 'agency.selection.busy',( awaiting || composite ) ? 1 : 0 ],
|
|
337
|
+
...stabMetrics,
|
|
338
|
+
]
|
|
339
|
+
}
|
|
340
|
+
}
|
|
179
341
|
|
|
180
342
|
// ── Run the competition over the current field ────────────────
|
|
181
343
|
const bias = buildBias( state )
|
|
@@ -183,7 +345,12 @@ export class ActionSelector implements CognitiveEngine {
|
|
|
183
345
|
// base (engine-config-action-selector.switchCost ⊕ prior) hardened by how long the
|
|
184
346
|
// Will has been committed (shared task_switch.current_focus_ticks). Computed once;
|
|
185
347
|
// both preemption paths scale it DOWN by the challenger's stakes.
|
|
186
|
-
|
|
348
|
+
// Rupture softens engagement on top of the persona/focus base: an unstable
|
|
349
|
+
// situation makes the Will readier to be pulled off what it's doing. This is
|
|
350
|
+
// ORTHOGONAL to the per-challenger stakes scaling applied at each preemption
|
|
351
|
+
// site below (world-instability vs. challenger-quality) — the two compose,
|
|
352
|
+
// they don't double-count.
|
|
353
|
+
const effSwitchCost = effectiveSwitchCost( state ) * ( 1 - rupture )
|
|
187
354
|
// Competition weights (base ⊕ prior): a steadier Will weighs risk less (bolder), an
|
|
188
355
|
// open Will weighs novelty more (curiosity pulls toward the unpracticed). Other weights
|
|
189
356
|
// stay at DEFAULT_WEIGHTS — only the two with a clean trait owner are developable.
|
|
@@ -195,27 +362,46 @@ export class ActionSelector implements CognitiveEngine {
|
|
|
195
362
|
const winner = scored[0]
|
|
196
363
|
if( !winner ) return busy( eligible.length )
|
|
197
364
|
|
|
198
|
-
// ── Preempt a mid-composite routine (
|
|
199
|
-
// A strong/high-stakes challenger
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
//
|
|
203
|
-
//
|
|
365
|
+
// ── Preempt a mid-composite routine (IMMEDIATE SWITCH) ────────
|
|
366
|
+
// A strong/high-stakes challenger cuts the routine off AND takes the body
|
|
367
|
+
// the same tick. We cannot delete the parent here: the executor runs later
|
|
368
|
+
// this tick off the pre-delete snapshot, and its macro-advance would
|
|
369
|
+
// re-`set` the parent (resurrecting it, set-after-delete) and queue the next
|
|
370
|
+
// sub as 'selected' — two selected intents next tick = double enaction.
|
|
371
|
+
// So we tombstone the parent instead (the P4 mechanism) and commit the
|
|
372
|
+
// challenger: next tick the executor honours the tombstone, dropping the
|
|
373
|
+
// queued sub and the parent, and enacts the challenger alone. One body, one
|
|
374
|
+
// action, one tick saved over the old cancel-only path.
|
|
375
|
+
let compositeTombstone: string | undefined
|
|
376
|
+
let compositeFrom: string | undefined
|
|
204
377
|
if( composite ){
|
|
205
378
|
const different = winner.affordance.schema !== composite.schema
|
|
206
379
|
const switchCost = effSwitchCost * ( 1 - stakes( winner.affordance, bias ) )
|
|
207
|
-
if( different
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
380
|
+
if( !different || winner.activation <= composite.activation + switchCost )
|
|
381
|
+
return busy( eligible.length ) // routine continues
|
|
382
|
+
|
|
383
|
+
if( this._bus )
|
|
384
|
+
try {
|
|
385
|
+
this._bus.publish({
|
|
386
|
+
type: 'agency.action.preempted',
|
|
387
|
+
version: 1,
|
|
388
|
+
sourceEngine: this.name,
|
|
389
|
+
salience: 0.8,
|
|
390
|
+
payload: {
|
|
391
|
+
from: composite.schema,
|
|
392
|
+
to: winner.affordance.schema,
|
|
393
|
+
activation: winner.activation,
|
|
394
|
+
tick
|
|
395
|
+
}
|
|
396
|
+
})
|
|
397
|
+
}
|
|
398
|
+
catch( err ){
|
|
399
|
+
logger.warn(`[selector] preempt publish failed: ${ err instanceof Error ? err.message : String( err ) }`)
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
compositeTombstone = composite.id
|
|
403
|
+
compositeFrom = composite.schema
|
|
404
|
+
// fall through to commit the challenger
|
|
219
405
|
}
|
|
220
406
|
|
|
221
407
|
// ── Preempt an awaiting action (commit the challenger) ────────
|
|
@@ -223,10 +409,12 @@ export class ActionSelector implements CognitiveEngine {
|
|
|
223
409
|
// faces almost none, so salient events interrupt. Deleting an 'awaiting' intent is
|
|
224
410
|
// race-free: the executor never enacts it (only times it out at 15 ticks).
|
|
225
411
|
let preemptDelete: string | undefined
|
|
226
|
-
let preemptedFrom: string | undefined
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
412
|
+
let preemptedFrom: string | undefined = compositeFrom
|
|
413
|
+
// A composite preemption already claimed the body: its tombstone cancels the
|
|
414
|
+
// whole macro (parent + any sub, whatever the sub's status), so the awaiting
|
|
415
|
+
// hysteresis below — which is about a *standalone* awaiting action — is skipped.
|
|
416
|
+
if( awaiting && !compositeTombstone ){
|
|
417
|
+
const sameAction = winner.affordance.schema === awaiting.schema && ( winner.affordance.targetEntityId ?? '') === awaiting.target
|
|
230
418
|
if( sameAction ) return busy( eligible.length ) // field still favours what we await
|
|
231
419
|
|
|
232
420
|
const staleness = Math.min( 1, ( tick - awaiting.dispatchedAt ) / AWAIT_STALE_TICKS )
|
|
@@ -253,7 +441,7 @@ export class ActionSelector implements CognitiveEngine {
|
|
|
253
441
|
// deliberativeness disposition (R1) shifts both gates the OTHER way for an analytical
|
|
254
442
|
// Will — wider margin gate (contest sooner) and lower stakes gate (deliberate at lower
|
|
255
443
|
// stakes). The two forces compose additively, each independent and bounded.
|
|
256
|
-
const deliberativeness = -( readPersonaPrior( state, 'engine-config-executive'
|
|
444
|
+
const deliberativeness = -( readPersonaPrior( state, 'engine-config-executive')['deliberateThreshold'] ?? 0 )
|
|
257
445
|
const marginGate = Math.max( 0, MARGIN_THRESHOLD - relief + deliberativeness * DELIB_MARGIN_SENS )
|
|
258
446
|
const stakesGate = clamp01( BASE_STAKES_THRESHOLD + relief - deliberativeness * DELIB_STAKES_SENS )
|
|
259
447
|
const deliberate = margin < marginGate || stk > stakesGate
|
|
@@ -261,6 +449,17 @@ export class ActionSelector implements CognitiveEngine {
|
|
|
261
449
|
this._lastEntropy = entropy
|
|
262
450
|
this._lastDeliberate = deliberate
|
|
263
451
|
|
|
452
|
+
// Channel-B revocation hint: if we let go of a commitment under rupture in the
|
|
453
|
+
// last few ticks and are now forming a fresh deliberation, tell it so — then
|
|
454
|
+
// forget (consumed once). Stale hints are cleared without stamping.
|
|
455
|
+
let revokedBy: string | undefined
|
|
456
|
+
if( this._lastRevoked ){
|
|
457
|
+
if( deliberate && tick - this._lastRevoked.tick <= REVOKE_HINT_WINDOW )
|
|
458
|
+
revokedBy = this._lastRevoked.schema
|
|
459
|
+
if( revokedBy || tick - this._lastRevoked.tick > REVOKE_HINT_WINDOW )
|
|
460
|
+
this._lastRevoked = null
|
|
461
|
+
}
|
|
462
|
+
|
|
264
463
|
const intent: EntityInput = {
|
|
265
464
|
id: `agency-intent-${ tick }`,
|
|
266
465
|
type: 'agency.intent',
|
|
@@ -305,6 +504,9 @@ export class ActionSelector implements CognitiveEngine {
|
|
|
305
504
|
// interrupted, so the deliberation facet can own the interruption in-character
|
|
306
505
|
// ("I was about to X, but this pulled me away"). Only meaningful while deliberating.
|
|
307
506
|
...( preemptedFrom ? { preemptedFrom } : {} ),
|
|
507
|
+
// Channel B: this deliberation forms in the wake of a rupture-driven
|
|
508
|
+
// revocation — the facet can own "something changed, I let go of X".
|
|
509
|
+
...( revokedBy ? { revokedBy } : {} ),
|
|
308
510
|
tick,
|
|
309
511
|
},
|
|
310
512
|
}
|
|
@@ -318,6 +520,7 @@ export class ActionSelector implements CognitiveEngine {
|
|
|
318
520
|
salience: 0.6,
|
|
319
521
|
payload: { schema: winner.affordance.schema, activation: winner.activation, entropy, tick },
|
|
320
522
|
})
|
|
523
|
+
|
|
321
524
|
if( deliberate )
|
|
322
525
|
this._bus.publish({
|
|
323
526
|
type: 'agency.selection.ambiguous',
|
|
@@ -329,6 +532,7 @@ export class ActionSelector implements CognitiveEngine {
|
|
|
329
532
|
entropy, stakes: stk, tick,
|
|
330
533
|
},
|
|
331
534
|
})
|
|
535
|
+
|
|
332
536
|
if( preemptedFrom )
|
|
333
537
|
this._bus.publish({
|
|
334
538
|
type: 'agency.action.preempted',
|
|
@@ -339,12 +543,16 @@ export class ActionSelector implements CognitiveEngine {
|
|
|
339
543
|
})
|
|
340
544
|
}
|
|
341
545
|
catch( err ){
|
|
342
|
-
logger.warn(
|
|
546
|
+
logger.warn(`[selector] bus publish failed: ${ err instanceof Error ? err.message : String( err ) }`)
|
|
343
547
|
}
|
|
344
548
|
}
|
|
345
549
|
|
|
346
550
|
const commands: StateCommands = {
|
|
347
|
-
|
|
551
|
+
// A composite preemption rides along as a tombstone (never a delete — the
|
|
552
|
+
// executor's in-tick macro-advance would resurrect the parent).
|
|
553
|
+
set: compositeTombstone
|
|
554
|
+
? [ intent, revocationEntity( compositeTombstone, compositeFrom ?? '', rupture, tick ) ]
|
|
555
|
+
: [ intent ],
|
|
348
556
|
...( preemptDelete ? { delete: [ preemptDelete ] } : {} ),
|
|
349
557
|
metrics: [
|
|
350
558
|
[ 'agency.field.eligible', eligible.length ],
|
|
@@ -354,8 +562,10 @@ export class ActionSelector implements CognitiveEngine {
|
|
|
354
562
|
[ 'agency.selection.deliberate', deliberate ? 1 : 0 ],
|
|
355
563
|
[ 'agency.selection.preempted', preemptedFrom ? 1 : 0 ],
|
|
356
564
|
[ 'agency.selection.activation', winner.activation ],
|
|
565
|
+
...stabMetrics,
|
|
357
566
|
],
|
|
358
567
|
}
|
|
568
|
+
|
|
359
569
|
return { commands }
|
|
360
570
|
}
|
|
361
571
|
}
|
|
@@ -373,10 +583,53 @@ export class ActionSelector implements CognitiveEngine {
|
|
|
373
583
|
* TaskSwitcher uses internally. One disposition, two owners, native scales.
|
|
374
584
|
*/
|
|
375
585
|
function effectiveSwitchCost( state: ReadonlySimulationState ): number {
|
|
376
|
-
const params = readEffectiveParams( state, 'engine-config-action-selector'
|
|
586
|
+
const params = readEffectiveParams( state, 'engine-config-action-selector')
|
|
377
587
|
const base = num( params['switchCost'], BASE_SWITCH_COST )
|
|
378
588
|
const focusTicks = metric( state, 'task_switch.current_focus_ticks', 0 )
|
|
379
|
-
|
|
589
|
+
// EXAFFERENCE P3 — focus only hardens under a stable situation. `situation.stability`
|
|
590
|
+
// (absent ⇒ 1 ⇒ pre-P3 behavior) scales the focus-hardening term, so a Will in a
|
|
591
|
+
// destabilized world can't cling to a long-held focus. The fast, same-tick softener
|
|
592
|
+
// is `(1 - rupture)` applied at the call site; this is the slow, persistent one.
|
|
593
|
+
const stability = metric( state, 'situation.stability', 1 )
|
|
594
|
+
return base * ( 1 + focusTicks * FOCUS_GAIN * stability )
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* EXAFFERENCE P3 — the exafferent-rupture scalar. Max salience among the
|
|
599
|
+
* `provenance:'exafferent'` percepts fresh within `RUPTURE_WINDOW_TICKS`,
|
|
600
|
+
* mapped through `RUPTURE_SALIENCE_GATE` into [0,1]. Pure read of frozen state;
|
|
601
|
+
* `reafferent` percepts (our own echo, P2) are excluded by construction, so the
|
|
602
|
+
* mind is never ruptured by itself.
|
|
603
|
+
*/
|
|
604
|
+
function computeRupture(
|
|
605
|
+
state: ReadonlySimulationState,
|
|
606
|
+
tick: Tick,
|
|
607
|
+
senseEvents: ReadonlyArray<{ salience: number; text?: string }> = [],
|
|
608
|
+
): number {
|
|
609
|
+
let maxSalience = 0
|
|
610
|
+
for( const e of state.entities.values() ){
|
|
611
|
+
if( e.type !== 'percept') continue
|
|
612
|
+
const m = e.metadata
|
|
613
|
+
if( str( m?.['provenance'] ) !== 'exafferent') continue
|
|
614
|
+
const pTick = num( m?.['tick'], -1 )
|
|
615
|
+
if( pTick < 0 || tick - pTick > RUPTURE_WINDOW_TICKS ) continue
|
|
616
|
+
const s = num( m?.['salience'], 0 )
|
|
617
|
+
if( s > maxSalience ) maxSalience = s
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// ACP §2b — buffered sense-channel percepts (bus-only; never entities). The
|
|
621
|
+
// echo guard extends to this path: an event whose text matches a live
|
|
622
|
+
// consequence descriptor is our own action coming back and cannot rupture.
|
|
623
|
+
if( senseEvents.length > 0 ){
|
|
624
|
+
const live = liveConsequences( state.entities, tick )
|
|
625
|
+
for( const ev of senseEvents ){
|
|
626
|
+
if( ev.text !== undefined && live.length > 0 && matchConsequenceText( live, ev.text ) ) continue
|
|
627
|
+
if( ev.salience > maxSalience ) maxSalience = ev.salience
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
if( maxSalience <= RUPTURE_SALIENCE_GATE ) return 0
|
|
632
|
+
return clamp01( ( maxSalience - RUPTURE_SALIENCE_GATE ) / ( 1 - RUPTURE_SALIENCE_GATE ) )
|
|
380
633
|
}
|
|
381
634
|
|
|
382
635
|
/**
|
|
@@ -386,7 +639,7 @@ function effectiveSwitchCost( state: ReadonlySimulationState ): number {
|
|
|
386
639
|
* weights have no clean single-trait owner, so they stay fixed (see the TODO catalogue).
|
|
387
640
|
*/
|
|
388
641
|
function effectiveWeights( state: ReadonlySimulationState ): ScoreWeights {
|
|
389
|
-
const p = readEffectiveParams( state, 'engine-config-action-selector'
|
|
642
|
+
const p = readEffectiveParams( state, 'engine-config-action-selector')
|
|
390
643
|
return {
|
|
391
644
|
...DEFAULT_WEIGHTS,
|
|
392
645
|
risk: Math.max( 0, num( p['riskWeight'], DEFAULT_WEIGHTS.risk ) ),
|
|
@@ -404,10 +657,10 @@ function buildBias( state: ReadonlySimulationState ): BiasContext {
|
|
|
404
657
|
let maxGoalPriority = 0
|
|
405
658
|
|
|
406
659
|
for( const e of state.entities.values() ){
|
|
407
|
-
if( e.type !== 'goal'
|
|
660
|
+
if( e.type !== 'goal') continue
|
|
408
661
|
const m = e.metadata
|
|
409
662
|
const status = str( m?.['status'] )
|
|
410
|
-
if( status !== 'active' && status !== 'in_progress'
|
|
663
|
+
if( status !== 'active' && status !== 'in_progress') continue
|
|
411
664
|
maxGoalPriority = Math.max( maxGoalPriority, num( m?.['priority'], 0 ) )
|
|
412
665
|
}
|
|
413
666
|
|
|
@@ -441,7 +694,7 @@ function readAffordance( id: string, m: ReadonlyMap<string, unknown> | Record<st
|
|
|
441
694
|
cost: num( meta['cost'], 0 ),
|
|
442
695
|
habitStrength: num( meta['habitStrength'], 0 ),
|
|
443
696
|
available: meta['available'] === true,
|
|
444
|
-
tags: Array.isArray( meta['tags'] ) ? ( meta['tags'] as unknown[] ).filter( ( t ): t is string => typeof t === 'string'
|
|
697
|
+
tags: Array.isArray( meta['tags'] ) ? ( meta['tags'] as unknown[] ).filter( ( t ): t is string => typeof t === 'string') : [],
|
|
445
698
|
planBias: typeof meta['planBias'] === 'number' ? ( meta['planBias'] as number ) : undefined,
|
|
446
699
|
planId: str( meta['planId'] ),
|
|
447
700
|
stepId: str( meta['stepId'] ),
|
|
@@ -114,14 +114,12 @@ export class AffordanceSynthesizer implements CognitiveEngine {
|
|
|
114
114
|
|
|
115
115
|
// Clear the previous tick's field — affordances are transient.
|
|
116
116
|
for( const [ id, e ] of state.entities )
|
|
117
|
-
if( e.type === 'affordance'
|
|
117
|
+
if( e.type === 'affordance') del.push( id )
|
|
118
118
|
|
|
119
119
|
// ── 1. innate floor — always emitted, never attention-capped ──
|
|
120
|
-
const floor = schemas.filter( s => s.binds === 'none'
|
|
120
|
+
const floor = schemas.filter( s => s.binds === 'none')
|
|
121
121
|
for( const schema of floor )
|
|
122
|
-
set.push( this._toEntity(
|
|
123
|
-
this._build( schema, tick, state, valence, energyLow, skills, {} ),
|
|
124
|
-
) )
|
|
122
|
+
set.push( this._toEntity( this._build( schema, tick, state, valence, energyLow, skills, {} ) ) )
|
|
125
123
|
|
|
126
124
|
// ── 2. perception-bound candidates, attention-budgeted ───────
|
|
127
125
|
// Goal-relevance counts at the CAP, not only at selection: an affordance whose
|
|
@@ -131,14 +129,16 @@ export class AffordanceSynthesizer implements CognitiveEngine {
|
|
|
131
129
|
const candidates: Candidate[] = []
|
|
132
130
|
const goalTargets: Map<string, number> = collectGoalTargets( state )
|
|
133
131
|
|
|
134
|
-
const perceptSchema = schemas.find( s => s.binds === 'percept'
|
|
132
|
+
const perceptSchema = schemas.find( s => s.binds === 'percept')
|
|
135
133
|
if( perceptSchema )
|
|
136
134
|
for( const [ id, e ] of state.entities ){
|
|
137
|
-
if( e.type !== 'percept'
|
|
135
|
+
if( e.type !== 'percept') continue
|
|
136
|
+
|
|
138
137
|
const m = e.metadata
|
|
139
138
|
const salience = num( m?.['salience'], 0 )
|
|
140
139
|
const summary = str( m?.['summary'] ) ?? str( m?.['category'] ) ?? 'something'
|
|
141
140
|
const target = str( m?.['entityId'] ) ?? str( m?.['targetEntityId'] )
|
|
141
|
+
|
|
142
142
|
candidates.push({
|
|
143
143
|
salience: salience + ( target ? goalTargets.get( target ) ?? 0 : 0 ),
|
|
144
144
|
affordance: this._build( perceptSchema, tick, state, valence, energyLow, skills, {
|
|
@@ -155,21 +155,26 @@ export class AffordanceSynthesizer implements CognitiveEngine {
|
|
|
155
155
|
// things — so the Will can direct `give`/`greet` at someone or `use`/`pick-up`
|
|
156
156
|
// at something in particular. Each (schema × target) enters as a candidate at
|
|
157
157
|
// the target's salience and competes through the attention cap.
|
|
158
|
-
const personSchemas = schemas.filter( s => s.binds === 'entity'
|
|
159
|
-
const objectSchemas = schemas.filter( s => s.binds === 'object'
|
|
158
|
+
const personSchemas = schemas.filter( s => s.binds === 'entity')
|
|
159
|
+
const objectSchemas = schemas.filter( s => s.binds === 'object')
|
|
160
|
+
|
|
160
161
|
if( personSchemas.length > 0 || objectSchemas.length > 0 )
|
|
161
162
|
for( const [ id, e ] of state.entities ){
|
|
162
|
-
if( e.type !== 'known-entity'
|
|
163
|
+
if( e.type !== 'known-entity') continue
|
|
164
|
+
|
|
163
165
|
const m = e.metadata
|
|
164
166
|
const kind = str( m?.['kind'] )
|
|
165
167
|
const applicable = kind === 'sentient' ? personSchemas : kind === 'thing' ? objectSchemas : null
|
|
168
|
+
|
|
166
169
|
if( !applicable || applicable.length === 0 ) continue
|
|
170
|
+
|
|
167
171
|
const keid = str( m?.['keid'] ) ?? id
|
|
168
172
|
const fam = num( m?.['familiarity'], 0 )
|
|
169
173
|
const val = num( m?.['valence'], 0 )
|
|
170
174
|
const res = num( m?.['resolutionConfidence'], 0 )
|
|
171
175
|
const salience = fam * 0.6 + Math.max( 0, val ) * 0.3 + res * 0.1 + ( goalTargets.get( keid ) ?? 0 )
|
|
172
176
|
const name = str( m?.['name'] ) ?? keid
|
|
177
|
+
|
|
173
178
|
for( const schema of applicable )
|
|
174
179
|
candidates.push({
|
|
175
180
|
salience,
|
|
@@ -188,11 +193,14 @@ export class AffordanceSynthesizer implements CognitiveEngine {
|
|
|
188
193
|
// it. This is the AffordanceSource.ideomotor leg: executive intention → an
|
|
189
194
|
// affordance that competes like any other.
|
|
190
195
|
for( const [ id, e ] of state.entities ){
|
|
191
|
-
if( e.type !== 'ideomotor.intent'
|
|
196
|
+
if( e.type !== 'ideomotor.intent') continue
|
|
197
|
+
|
|
192
198
|
const m = e.metadata
|
|
193
199
|
const schemaId = str( m?.['schema'] )
|
|
194
200
|
const schema = schemaId ? schemas.find( s => s.id === schemaId ) : undefined
|
|
201
|
+
|
|
195
202
|
if( !schema ) continue
|
|
203
|
+
|
|
196
204
|
candidates.push({
|
|
197
205
|
salience: IDEOMOTOR_BASE_SALIENCE + num( m?.['priority'], 0.8 ),
|
|
198
206
|
affordance: this._build( schema, tick, state, valence, energyLow, skills, {
|
|
@@ -215,11 +223,14 @@ export class AffordanceSynthesizer implements CognitiveEngine {
|
|
|
215
223
|
// next tick. The suggested schema must resolve in the repertoire; if it does not,
|
|
216
224
|
// the prior cannot surface as an action and the plan waits / replans.
|
|
217
225
|
for( const [ id, e ] of state.entities ){
|
|
218
|
-
if( e.type !== 'plan.prior'
|
|
226
|
+
if( e.type !== 'plan.prior') continue
|
|
227
|
+
|
|
219
228
|
const m = e.metadata
|
|
220
229
|
const schemaId = str( m?.['schema'] )
|
|
221
230
|
const schema = schemaId ? schemas.find( s => s.id === schemaId ) : undefined
|
|
231
|
+
|
|
222
232
|
if( !schema ) continue
|
|
233
|
+
|
|
223
234
|
const planBias = clamp01( num( m?.['planBias'], 0.6 ) )
|
|
224
235
|
candidates.push({
|
|
225
236
|
salience: IDEOMOTOR_BASE_SALIENCE + planBias,
|
|
@@ -266,7 +277,7 @@ export class AffordanceSynthesizer implements CognitiveEngine {
|
|
|
266
277
|
})
|
|
267
278
|
}
|
|
268
279
|
catch( err ){
|
|
269
|
-
logger.warn(
|
|
280
|
+
logger.warn(`[affordance] bus publish failed: ${ err instanceof Error ? err.message : String( err ) }`)
|
|
270
281
|
}
|
|
271
282
|
}
|
|
272
283
|
|
|
@@ -296,8 +307,7 @@ export class AffordanceSynthesizer implements CognitiveEngine {
|
|
|
296
307
|
const skill = skills?.get( schema.id )
|
|
297
308
|
|
|
298
309
|
// Learned value if known, else the schema's intrinsic prior mapped to 0..1.
|
|
299
|
-
const expectedReward = skill?.valueEstimate
|
|
300
|
-
?? clamp01( ( ( schema.baseValence ?? 0 ) + 1 ) / 2 )
|
|
310
|
+
const expectedReward = skill?.valueEstimate ?? clamp01( ( ( schema.baseValence ?? 0 ) + 1 ) / 2 )
|
|
301
311
|
const expectedValence = schema.baseValence ?? valence
|
|
302
312
|
const habitStrength = skill?.habitStrength ?? 0
|
|
303
313
|
// Low energy makes everything feel costlier.
|
|
@@ -374,7 +384,7 @@ export class AffordanceSynthesizer implements CognitiveEngine {
|
|
|
374
384
|
}
|
|
375
385
|
|
|
376
386
|
private _attentionCap( state: ReadonlySimulationState ): number {
|
|
377
|
-
const cap = state.metrics.get(
|
|
387
|
+
const cap = state.metrics.get('attention.capacity') ?? this._defaultCap
|
|
378
388
|
return Math.max( 1, Math.min( MAX_ATTENTION_CAP, Math.round( cap ) ) )
|
|
379
389
|
}
|
|
380
390
|
}
|