@mindot/will 0.5.0 → 0.7.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/dist/channels/discord.d.ts +1 -1
- package/dist/channels/discord.js.map +1 -1
- package/dist/channels/whatsapp.d.ts +1 -1
- package/dist/channels/whatsapp.js.map +1 -1
- package/dist/cli.js +998 -59
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +998 -59
- 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/{will-BDq-TMQr.d.ts → will-DAW0l-lY.d.ts} +90 -10
- package/package.json +1 -1
- 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 +25 -25
- package/src/cli.ts +20 -20
- package/src/cognition/acp.ts +28 -0
- package/src/cognition/agency/consequence.ts +174 -0
- package/src/cognition/agency/engines/action.selector.ts +333 -48
- package/src/cognition/agency/engines/affordance.synthesizer.ts +35 -16
- package/src/cognition/agency/engines/deliberation.engine.ts +28 -14
- package/src/cognition/agency/engines/motor.schema.executor.ts +90 -14
- package/src/cognition/agency/engines/reafference.engine.ts +152 -23
- 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 +10 -1
- package/src/cognition/agency/revocation.ts +79 -0
- package/src/cognition/agency/schemas/repertoire.ts +109 -9
- package/src/cognition/agency/selection.scoring.ts +32 -10
- package/src/cognition/agency/types.ts +9 -0
- 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 +25 -25
- 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 +5 -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 +30 -30
- package/src/host/utterances.ts +1 -1
- package/src/llm/index.ts +9 -9
- 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 +5 -5
- package/src/serve/server.ts +12 -12
- package/src/stem/assembly.audit.ts +3 -3
- package/src/stem/guards/identity.coherence.ts +10 -10
- package/src/stem/guards/identity.guard.ts +22 -22
- package/src/stem/index.ts +26 -11
- package/src/stem/mind.ts +12 -12
- package/src/stem/policy/arbiter.ts +136 -0
- package/src/stem/policy/rule.table.ts +172 -0
- package/src/stem/policy/verdict.recorder.ts +0 -0
- package/src/stem/tracts/biography.writer.ts +3 -3
- package/src/stem/tracts/effector.controller.ts +383 -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 },
|
|
111
148
|
]
|
|
112
149
|
}
|
|
113
|
-
subscribes(): string[] {
|
|
114
|
-
|
|
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',
|
|
159
|
+
]
|
|
160
|
+
}
|
|
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,104 @@ 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 · POLICY_REAFFERENCE P3) ──
|
|
298
|
+
// A commitment still being weighed is let go for either of two DISTINCT
|
|
299
|
+
// reasons: a hard exafferent rupture (the world surprised us), or a class
|
|
300
|
+
// policy refusal of the very schema we're deliberating (the boundary just
|
|
301
|
+
// declared it forbidden — deliberating our way into it is wasted). The two
|
|
302
|
+
// never mix: the refusal is an outcome, not a percept, so it contributes
|
|
303
|
+
// ZERO to the exafferent scalar. Either way we can't delete the
|
|
304
|
+
// `deliberating` intent here (Deliberation runs after us and would resurrect
|
|
305
|
+
// it set-after-delete), so we drop a tombstone the Deliberation engine +
|
|
306
|
+
// Executor honor next tick. No successor is committed — the field re-forms.
|
|
307
|
+
const policyRevoke = !!deliberating && refusedClassSchemas( state ).has( deliberating.schema )
|
|
308
|
+
if( deliberating && ( rupture >= RUPTURE_REVOKE_GATE || policyRevoke ) ){
|
|
309
|
+
const reason = policyRevoke ? 'policy-refusal' : 'exafferent-rupture'
|
|
310
|
+
const revRupture = policyRevoke ? Math.max( rupture, RUPTURE_REVOKE_GATE ) : rupture
|
|
311
|
+
if( this._bus ){
|
|
312
|
+
try {
|
|
313
|
+
this._bus.publish({
|
|
314
|
+
type: 'agency.commitment.revoked', version: 1, sourceEngine: this.name,
|
|
315
|
+
salience: 0.85,
|
|
316
|
+
payload: { from: deliberating.schema, reason, rupture: revRupture, tick },
|
|
317
|
+
})
|
|
318
|
+
}
|
|
319
|
+
catch( err ){ logger.warn(`[selector] revoked publish failed: ${ err instanceof Error ? err.message : String( err ) }`) }
|
|
320
|
+
}
|
|
321
|
+
this._lastRevoked = { schema: deliberating.schema, tick } // Channel-B: flavor the next deliberation
|
|
322
|
+
return {
|
|
323
|
+
commands: {
|
|
324
|
+
set: [ revocationEntity( deliberating.id, deliberating.schema, revRupture, tick ) ],
|
|
325
|
+
metrics: [
|
|
326
|
+
[ 'agency.field.eligible', eligible.length ],
|
|
327
|
+
[ 'agency.selection.busy', 1 ],
|
|
328
|
+
[ 'agency.commitment.revoked', 1 ],
|
|
329
|
+
...( policyRevoke ? [ [ 'agency.policy.revoked', 1 ] as [ string, number ] ] : [] ),
|
|
330
|
+
...stabMetrics,
|
|
331
|
+
],
|
|
332
|
+
},
|
|
333
|
+
}
|
|
334
|
+
}
|
|
173
335
|
|
|
174
336
|
if( blocking ) return busy( eligible.length )
|
|
175
337
|
|
|
176
338
|
// Nothing afforded → idle (if an action is in flight, keep waiting).
|
|
177
339
|
if( eligible.length === 0 )
|
|
178
|
-
return {
|
|
340
|
+
return {
|
|
341
|
+
commands: {
|
|
342
|
+
metrics: [
|
|
343
|
+
[ 'agency.field.eligible', 0 ],
|
|
344
|
+
[ 'agency.selection.busy',( awaiting || composite ) ? 1 : 0 ],
|
|
345
|
+
...stabMetrics,
|
|
346
|
+
]
|
|
347
|
+
}
|
|
348
|
+
}
|
|
179
349
|
|
|
180
350
|
// ── Run the competition over the current field ────────────────
|
|
181
351
|
const bias = buildBias( state )
|
|
@@ -183,7 +353,12 @@ export class ActionSelector implements CognitiveEngine {
|
|
|
183
353
|
// base (engine-config-action-selector.switchCost ⊕ prior) hardened by how long the
|
|
184
354
|
// Will has been committed (shared task_switch.current_focus_ticks). Computed once;
|
|
185
355
|
// both preemption paths scale it DOWN by the challenger's stakes.
|
|
186
|
-
|
|
356
|
+
// Rupture softens engagement on top of the persona/focus base: an unstable
|
|
357
|
+
// situation makes the Will readier to be pulled off what it's doing. This is
|
|
358
|
+
// ORTHOGONAL to the per-challenger stakes scaling applied at each preemption
|
|
359
|
+
// site below (world-instability vs. challenger-quality) — the two compose,
|
|
360
|
+
// they don't double-count.
|
|
361
|
+
const effSwitchCost = effectiveSwitchCost( state ) * ( 1 - rupture )
|
|
187
362
|
// Competition weights (base ⊕ prior): a steadier Will weighs risk less (bolder), an
|
|
188
363
|
// open Will weighs novelty more (curiosity pulls toward the unpracticed). Other weights
|
|
189
364
|
// stay at DEFAULT_WEIGHTS — only the two with a clean trait owner are developable.
|
|
@@ -195,27 +370,46 @@ export class ActionSelector implements CognitiveEngine {
|
|
|
195
370
|
const winner = scored[0]
|
|
196
371
|
if( !winner ) return busy( eligible.length )
|
|
197
372
|
|
|
198
|
-
// ── Preempt a mid-composite routine (
|
|
199
|
-
// A strong/high-stakes challenger
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
//
|
|
203
|
-
//
|
|
373
|
+
// ── Preempt a mid-composite routine (IMMEDIATE SWITCH) ────────
|
|
374
|
+
// A strong/high-stakes challenger cuts the routine off AND takes the body
|
|
375
|
+
// the same tick. We cannot delete the parent here: the executor runs later
|
|
376
|
+
// this tick off the pre-delete snapshot, and its macro-advance would
|
|
377
|
+
// re-`set` the parent (resurrecting it, set-after-delete) and queue the next
|
|
378
|
+
// sub as 'selected' — two selected intents next tick = double enaction.
|
|
379
|
+
// So we tombstone the parent instead (the P4 mechanism) and commit the
|
|
380
|
+
// challenger: next tick the executor honours the tombstone, dropping the
|
|
381
|
+
// queued sub and the parent, and enacts the challenger alone. One body, one
|
|
382
|
+
// action, one tick saved over the old cancel-only path.
|
|
383
|
+
let compositeTombstone: string | undefined
|
|
384
|
+
let compositeFrom: string | undefined
|
|
204
385
|
if( composite ){
|
|
205
386
|
const different = winner.affordance.schema !== composite.schema
|
|
206
387
|
const switchCost = effSwitchCost * ( 1 - stakes( winner.affordance, bias ) )
|
|
207
|
-
if( different
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
388
|
+
if( !different || winner.activation <= composite.activation + switchCost )
|
|
389
|
+
return busy( eligible.length ) // routine continues
|
|
390
|
+
|
|
391
|
+
if( this._bus )
|
|
392
|
+
try {
|
|
393
|
+
this._bus.publish({
|
|
394
|
+
type: 'agency.action.preempted',
|
|
395
|
+
version: 1,
|
|
396
|
+
sourceEngine: this.name,
|
|
397
|
+
salience: 0.8,
|
|
398
|
+
payload: {
|
|
399
|
+
from: composite.schema,
|
|
400
|
+
to: winner.affordance.schema,
|
|
401
|
+
activation: winner.activation,
|
|
402
|
+
tick
|
|
403
|
+
}
|
|
404
|
+
})
|
|
405
|
+
}
|
|
406
|
+
catch( err ){
|
|
407
|
+
logger.warn(`[selector] preempt publish failed: ${ err instanceof Error ? err.message : String( err ) }`)
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
compositeTombstone = composite.id
|
|
411
|
+
compositeFrom = composite.schema
|
|
412
|
+
// fall through to commit the challenger
|
|
219
413
|
}
|
|
220
414
|
|
|
221
415
|
// ── Preempt an awaiting action (commit the challenger) ────────
|
|
@@ -223,10 +417,12 @@ export class ActionSelector implements CognitiveEngine {
|
|
|
223
417
|
// faces almost none, so salient events interrupt. Deleting an 'awaiting' intent is
|
|
224
418
|
// race-free: the executor never enacts it (only times it out at 15 ticks).
|
|
225
419
|
let preemptDelete: string | undefined
|
|
226
|
-
let preemptedFrom: string | undefined
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
420
|
+
let preemptedFrom: string | undefined = compositeFrom
|
|
421
|
+
// A composite preemption already claimed the body: its tombstone cancels the
|
|
422
|
+
// whole macro (parent + any sub, whatever the sub's status), so the awaiting
|
|
423
|
+
// hysteresis below — which is about a *standalone* awaiting action — is skipped.
|
|
424
|
+
if( awaiting && !compositeTombstone ){
|
|
425
|
+
const sameAction = winner.affordance.schema === awaiting.schema && ( winner.affordance.targetEntityId ?? '') === awaiting.target
|
|
230
426
|
if( sameAction ) return busy( eligible.length ) // field still favours what we await
|
|
231
427
|
|
|
232
428
|
const staleness = Math.min( 1, ( tick - awaiting.dispatchedAt ) / AWAIT_STALE_TICKS )
|
|
@@ -253,7 +449,7 @@ export class ActionSelector implements CognitiveEngine {
|
|
|
253
449
|
// deliberativeness disposition (R1) shifts both gates the OTHER way for an analytical
|
|
254
450
|
// Will — wider margin gate (contest sooner) and lower stakes gate (deliberate at lower
|
|
255
451
|
// stakes). The two forces compose additively, each independent and bounded.
|
|
256
|
-
const deliberativeness = -( readPersonaPrior( state, 'engine-config-executive'
|
|
452
|
+
const deliberativeness = -( readPersonaPrior( state, 'engine-config-executive')['deliberateThreshold'] ?? 0 )
|
|
257
453
|
const marginGate = Math.max( 0, MARGIN_THRESHOLD - relief + deliberativeness * DELIB_MARGIN_SENS )
|
|
258
454
|
const stakesGate = clamp01( BASE_STAKES_THRESHOLD + relief - deliberativeness * DELIB_STAKES_SENS )
|
|
259
455
|
const deliberate = margin < marginGate || stk > stakesGate
|
|
@@ -261,6 +457,17 @@ export class ActionSelector implements CognitiveEngine {
|
|
|
261
457
|
this._lastEntropy = entropy
|
|
262
458
|
this._lastDeliberate = deliberate
|
|
263
459
|
|
|
460
|
+
// Channel-B revocation hint: if we let go of a commitment under rupture in the
|
|
461
|
+
// last few ticks and are now forming a fresh deliberation, tell it so — then
|
|
462
|
+
// forget (consumed once). Stale hints are cleared without stamping.
|
|
463
|
+
let revokedBy: string | undefined
|
|
464
|
+
if( this._lastRevoked ){
|
|
465
|
+
if( deliberate && tick - this._lastRevoked.tick <= REVOKE_HINT_WINDOW )
|
|
466
|
+
revokedBy = this._lastRevoked.schema
|
|
467
|
+
if( revokedBy || tick - this._lastRevoked.tick > REVOKE_HINT_WINDOW )
|
|
468
|
+
this._lastRevoked = null
|
|
469
|
+
}
|
|
470
|
+
|
|
264
471
|
const intent: EntityInput = {
|
|
265
472
|
id: `agency-intent-${ tick }`,
|
|
266
473
|
type: 'agency.intent',
|
|
@@ -305,6 +512,9 @@ export class ActionSelector implements CognitiveEngine {
|
|
|
305
512
|
// interrupted, so the deliberation facet can own the interruption in-character
|
|
306
513
|
// ("I was about to X, but this pulled me away"). Only meaningful while deliberating.
|
|
307
514
|
...( preemptedFrom ? { preemptedFrom } : {} ),
|
|
515
|
+
// Channel B: this deliberation forms in the wake of a rupture-driven
|
|
516
|
+
// revocation — the facet can own "something changed, I let go of X".
|
|
517
|
+
...( revokedBy ? { revokedBy } : {} ),
|
|
308
518
|
tick,
|
|
309
519
|
},
|
|
310
520
|
}
|
|
@@ -318,6 +528,7 @@ export class ActionSelector implements CognitiveEngine {
|
|
|
318
528
|
salience: 0.6,
|
|
319
529
|
payload: { schema: winner.affordance.schema, activation: winner.activation, entropy, tick },
|
|
320
530
|
})
|
|
531
|
+
|
|
321
532
|
if( deliberate )
|
|
322
533
|
this._bus.publish({
|
|
323
534
|
type: 'agency.selection.ambiguous',
|
|
@@ -329,6 +540,7 @@ export class ActionSelector implements CognitiveEngine {
|
|
|
329
540
|
entropy, stakes: stk, tick,
|
|
330
541
|
},
|
|
331
542
|
})
|
|
543
|
+
|
|
332
544
|
if( preemptedFrom )
|
|
333
545
|
this._bus.publish({
|
|
334
546
|
type: 'agency.action.preempted',
|
|
@@ -339,12 +551,16 @@ export class ActionSelector implements CognitiveEngine {
|
|
|
339
551
|
})
|
|
340
552
|
}
|
|
341
553
|
catch( err ){
|
|
342
|
-
logger.warn(
|
|
554
|
+
logger.warn(`[selector] bus publish failed: ${ err instanceof Error ? err.message : String( err ) }`)
|
|
343
555
|
}
|
|
344
556
|
}
|
|
345
557
|
|
|
346
558
|
const commands: StateCommands = {
|
|
347
|
-
|
|
559
|
+
// A composite preemption rides along as a tombstone (never a delete — the
|
|
560
|
+
// executor's in-tick macro-advance would resurrect the parent).
|
|
561
|
+
set: compositeTombstone
|
|
562
|
+
? [ intent, revocationEntity( compositeTombstone, compositeFrom ?? '', rupture, tick ) ]
|
|
563
|
+
: [ intent ],
|
|
348
564
|
...( preemptDelete ? { delete: [ preemptDelete ] } : {} ),
|
|
349
565
|
metrics: [
|
|
350
566
|
[ 'agency.field.eligible', eligible.length ],
|
|
@@ -354,8 +570,10 @@ export class ActionSelector implements CognitiveEngine {
|
|
|
354
570
|
[ 'agency.selection.deliberate', deliberate ? 1 : 0 ],
|
|
355
571
|
[ 'agency.selection.preempted', preemptedFrom ? 1 : 0 ],
|
|
356
572
|
[ 'agency.selection.activation', winner.activation ],
|
|
573
|
+
...stabMetrics,
|
|
357
574
|
],
|
|
358
575
|
}
|
|
576
|
+
|
|
359
577
|
return { commands }
|
|
360
578
|
}
|
|
361
579
|
}
|
|
@@ -373,10 +591,77 @@ export class ActionSelector implements CognitiveEngine {
|
|
|
373
591
|
* TaskSwitcher uses internally. One disposition, two owners, native scales.
|
|
374
592
|
*/
|
|
375
593
|
function effectiveSwitchCost( state: ReadonlySimulationState ): number {
|
|
376
|
-
const params = readEffectiveParams( state, 'engine-config-action-selector'
|
|
594
|
+
const params = readEffectiveParams( state, 'engine-config-action-selector')
|
|
377
595
|
const base = num( params['switchCost'], BASE_SWITCH_COST )
|
|
378
596
|
const focusTicks = metric( state, 'task_switch.current_focus_ticks', 0 )
|
|
379
|
-
|
|
597
|
+
// EXAFFERENCE P3 — focus only hardens under a stable situation. `situation.stability`
|
|
598
|
+
// (absent ⇒ 1 ⇒ pre-P3 behavior) scales the focus-hardening term, so a Will in a
|
|
599
|
+
// destabilized world can't cling to a long-held focus. The fast, same-tick softener
|
|
600
|
+
// is `(1 - rupture)` applied at the call site; this is the slow, persistent one.
|
|
601
|
+
const stability = metric( state, 'situation.stability', 1 )
|
|
602
|
+
return base * ( 1 + focusTicks * FOCUS_GAIN * stability )
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* EXAFFERENCE P3 — the exafferent-rupture scalar. Max salience among the
|
|
607
|
+
* `provenance:'exafferent'` percepts fresh within `RUPTURE_WINDOW_TICKS`,
|
|
608
|
+
* mapped through `RUPTURE_SALIENCE_GATE` into [0,1]. Pure read of frozen state;
|
|
609
|
+
* `reafferent` percepts (our own echo, P2) are excluded by construction, so the
|
|
610
|
+
* mind is never ruptured by itself.
|
|
611
|
+
*/
|
|
612
|
+
function computeRupture(
|
|
613
|
+
state: ReadonlySimulationState,
|
|
614
|
+
tick: Tick,
|
|
615
|
+
senseEvents: ReadonlyArray<{ salience: number; text?: string }> = [],
|
|
616
|
+
): number {
|
|
617
|
+
let maxSalience = 0
|
|
618
|
+
for( const e of state.entities.values() ){
|
|
619
|
+
if( e.type !== 'percept') continue
|
|
620
|
+
const m = e.metadata
|
|
621
|
+
if( str( m?.['provenance'] ) !== 'exafferent') continue
|
|
622
|
+
const pTick = num( m?.['tick'], -1 )
|
|
623
|
+
if( pTick < 0 || tick - pTick > RUPTURE_WINDOW_TICKS ) continue
|
|
624
|
+
const s = num( m?.['salience'], 0 )
|
|
625
|
+
if( s > maxSalience ) maxSalience = s
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// ACP §2b — buffered sense-channel percepts (bus-only; never entities). The
|
|
629
|
+
// echo guard extends to this path: an event whose text matches a live
|
|
630
|
+
// consequence descriptor is our own action coming back and cannot rupture.
|
|
631
|
+
if( senseEvents.length > 0 ){
|
|
632
|
+
const live = liveConsequences( state.entities, tick )
|
|
633
|
+
for( const ev of senseEvents ){
|
|
634
|
+
if( ev.text !== undefined && live.length > 0 && matchConsequenceText( live, ev.text ) ) continue
|
|
635
|
+
if( ev.salience > maxSalience ) maxSalience = ev.salience
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
if( maxSalience <= RUPTURE_SALIENCE_GATE ) return 0
|
|
640
|
+
return clamp01( ( maxSalience - RUPTURE_SALIENCE_GATE ) / ( 1 - RUPTURE_SALIENCE_GATE ) )
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* POLICY_REAFFERENCE P3 — schemas hit by a CLASS-final policy refusal visible in
|
|
645
|
+
* frozen state this tick (a refused `agency.outcome` lives exactly one tick).
|
|
646
|
+
*
|
|
647
|
+
* A refusal is an `agency.outcome`, never a `percept`, so it can NEVER feed
|
|
648
|
+
* computeRupture: the mind cannot rupture itself with its own boundary. This is
|
|
649
|
+
* a SEPARATE, explicit trigger — "the boundary just declared this forbidden, let
|
|
650
|
+
* go of any commitment I'm still weighing toward it" — distinct from a
|
|
651
|
+
* world-surprise exafferent rupture, and it carries its own revocation reason.
|
|
652
|
+
* Only `class` finality qualifies: an `instance` refusal means "not with those
|
|
653
|
+
* parameters", not "never", so a still-deliberating attempt may yet succeed.
|
|
654
|
+
*/
|
|
655
|
+
function refusedClassSchemas( state: ReadonlySimulationState ): Set<string> {
|
|
656
|
+
const out = new Set<string>()
|
|
657
|
+
for( const e of state.entities.values() ){
|
|
658
|
+
if( e.type !== 'agency.outcome') continue
|
|
659
|
+
const m = e.metadata
|
|
660
|
+
if( m?.['refused'] !== true || str( m?.['finality'] ) !== 'class') continue
|
|
661
|
+
const schema = str( m?.['schema'] )
|
|
662
|
+
if( schema ) out.add( schema )
|
|
663
|
+
}
|
|
664
|
+
return out
|
|
380
665
|
}
|
|
381
666
|
|
|
382
667
|
/**
|
|
@@ -386,7 +671,7 @@ function effectiveSwitchCost( state: ReadonlySimulationState ): number {
|
|
|
386
671
|
* weights have no clean single-trait owner, so they stay fixed (see the TODO catalogue).
|
|
387
672
|
*/
|
|
388
673
|
function effectiveWeights( state: ReadonlySimulationState ): ScoreWeights {
|
|
389
|
-
const p = readEffectiveParams( state, 'engine-config-action-selector'
|
|
674
|
+
const p = readEffectiveParams( state, 'engine-config-action-selector')
|
|
390
675
|
return {
|
|
391
676
|
...DEFAULT_WEIGHTS,
|
|
392
677
|
risk: Math.max( 0, num( p['riskWeight'], DEFAULT_WEIGHTS.risk ) ),
|
|
@@ -404,10 +689,10 @@ function buildBias( state: ReadonlySimulationState ): BiasContext {
|
|
|
404
689
|
let maxGoalPriority = 0
|
|
405
690
|
|
|
406
691
|
for( const e of state.entities.values() ){
|
|
407
|
-
if( e.type !== 'goal'
|
|
692
|
+
if( e.type !== 'goal') continue
|
|
408
693
|
const m = e.metadata
|
|
409
694
|
const status = str( m?.['status'] )
|
|
410
|
-
if( status !== 'active' && status !== 'in_progress'
|
|
695
|
+
if( status !== 'active' && status !== 'in_progress') continue
|
|
411
696
|
maxGoalPriority = Math.max( maxGoalPriority, num( m?.['priority'], 0 ) )
|
|
412
697
|
}
|
|
413
698
|
|
|
@@ -441,7 +726,7 @@ function readAffordance( id: string, m: ReadonlyMap<string, unknown> | Record<st
|
|
|
441
726
|
cost: num( meta['cost'], 0 ),
|
|
442
727
|
habitStrength: num( meta['habitStrength'], 0 ),
|
|
443
728
|
available: meta['available'] === true,
|
|
444
|
-
tags: Array.isArray( meta['tags'] ) ? ( meta['tags'] as unknown[] ).filter( ( t ): t is string => typeof t === 'string'
|
|
729
|
+
tags: Array.isArray( meta['tags'] ) ? ( meta['tags'] as unknown[] ).filter( ( t ): t is string => typeof t === 'string') : [],
|
|
445
730
|
planBias: typeof meta['planBias'] === 'number' ? ( meta['planBias'] as number ) : undefined,
|
|
446
731
|
planId: str( meta['planId'] ),
|
|
447
732
|
stepId: str( meta['stepId'] ),
|