@mindot/will 0.7.0 → 0.9.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 +87 -22
- package/dist/channels/discord.d.ts +67 -6
- package/dist/channels/discord.js +112 -6
- package/dist/channels/discord.js.map +1 -1
- package/dist/channels/whatsapp.d.ts +1 -1
- package/dist/channels/whatsapp.js +4 -1
- package/dist/channels/whatsapp.js.map +1 -1
- package/dist/cli.js +13733 -11057
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3729 -1199
- package/dist/index.js.map +1 -1
- package/dist/mcp/effectors.d.ts +1 -1
- package/dist/{will-DAW0l-lY.d.ts → will-DbDj_TEH.d.ts} +1214 -93
- package/package.json +1 -1
- package/src/channels/discord.ts +189 -11
- package/src/channels/types.ts +90 -0
- package/src/channels/whatsapp.ts +13 -4
- package/src/cli.ts +9 -4
- package/src/cognition/agency/consequence.ts +122 -1
- package/src/cognition/agency/conversation.aim.ts +260 -0
- package/src/cognition/agency/engines/action.selector.ts +85 -3
- package/src/cognition/agency/engines/affordance.synthesizer.ts +90 -1
- package/src/cognition/agency/engines/motor.schema.executor.ts +152 -10
- package/src/cognition/agency/engines/reafference.engine.ts +129 -2
- package/src/cognition/agency/proactive.communicator.ts +19 -3
- package/src/cognition/agency/reconcile.learning.ts +16 -2
- package/src/cognition/agency/restart.ts +66 -0
- package/src/cognition/agency/schemas/repertoire.ts +12 -5
- package/src/cognition/agency/selection.scoring.ts +33 -0
- package/src/cognition/agency/types.ts +35 -0
- package/src/cognition/cache/composition.ts +232 -0
- package/src/cognition/cache/deliberation.cache.ts +219 -0
- package/src/cognition/cache/fingerprint.ts +120 -0
- package/src/cognition/cache/types.ts +105 -0
- package/src/cognition/config.mirror.entities.ts +109 -1
- package/src/cognition/event.schemas.ts +22 -0
- package/src/cognition/faculties/autobiographical.narrator.ts +5 -10
- package/src/cognition/faculties/episodic.consolidator.ts +59 -3
- package/src/cognition/faculties/executive.engine/commands.ts +189 -14
- package/src/cognition/faculties/executive.engine/context.ts +67 -13
- package/src/cognition/faculties/executive.engine/deliberate.reasoning.ts +1 -1
- package/src/cognition/faculties/executive.engine/engine.ts +686 -187
- package/src/cognition/faculties/executive.engine/escalation.buffer.ts +162 -44
- package/src/cognition/faculties/executive.engine/facet.supervisor.ts +310 -65
- package/src/cognition/faculties/executive.engine/facet.ts +89 -26
- package/src/cognition/faculties/executive.engine/gating.ts +14 -14
- package/src/cognition/faculties/executive.engine/parser.ts +21 -1
- package/src/cognition/faculties/executive.engine/prompt.factory.ts +169 -20
- package/src/cognition/faculties/executive.engine/types.ts +69 -0
- package/src/cognition/faculties/goal.manager.ts +94 -14
- package/src/cognition/faculties/known.entity.tracker.ts +267 -28
- package/src/cognition/faculties/moral.evaluator.ts +8 -3
- package/src/cognition/faculties/persona.consolidator.ts +141 -0
- package/src/cognition/faculties/reputation.tracker.ts +66 -2
- package/src/cognition/faculties/self.model.updater.ts +19 -12
- package/src/cognition/faculties/social.perception.ts +47 -3
- package/src/cognition/faculties/threat.evaluator.ts +7 -0
- package/src/cognition/faculties/working.memory.ts +10 -20
- package/src/cognition/identity.entity.ts +205 -0
- package/src/cognition/index.ts +11 -0
- package/src/cognition/memory/vector.adapter.ts +12 -3
- package/src/cognition/memory/vector.embedder.ts +54 -7
- package/src/cognition/persona.prior.ts +6 -0
- package/src/cognition/senses/audition.engine/engine.ts +404 -46
- package/src/cognition/senses/base.sense.engine.ts +1 -1
- package/src/cognition/senses/index.ts +12 -0
- package/src/cognition/social.identity.ts +273 -0
- package/src/cognition/utilities/token.tracker.ts +246 -98
- package/src/core/orchestrator.ts +38 -0
- package/src/host/boot.ts +78 -22
- package/src/index.ts +35 -0
- package/src/llm/index.ts +415 -97
- package/src/llm/routing.ts +204 -0
- package/src/llm/summarizer.ts +5 -1
- package/src/llm/wire.contracts.ts +19 -0
- package/src/pma/index.ts +67 -53
- package/src/runners/thin-shim.runner.ts +18 -6
- package/src/sdk/will.ts +121 -22
- package/src/stem/assembly.audit.ts +1 -0
- package/src/stem/guards/identity.coherence.ts +17 -6
- package/src/stem/index.ts +82 -5
- package/src/stem/mind.ts +327 -79
- package/src/stem/policy/arbiter.ts +49 -14
- package/src/stem/policy/rule.table.ts +2 -2
- package/src/stem/tracts/effector.controller.ts +56 -9
- package/src/stem/tracts/outbox.writer.ts +40 -2
- package/src/cognition/faculties/executive.engine/messages.ts +0 -102
|
@@ -71,6 +71,11 @@ interface Intent {
|
|
|
71
71
|
*/
|
|
72
72
|
planId?: string
|
|
73
73
|
planStepId?: string
|
|
74
|
+
/**
|
|
75
|
+
* The entity that evoked this — for an ideomotor winner, the `ideomotor.intent`
|
|
76
|
+
* the executive wrote. Read so enaction can retire it (see _dischargeWill).
|
|
77
|
+
*/
|
|
78
|
+
evokedBy?: string
|
|
74
79
|
}
|
|
75
80
|
|
|
76
81
|
/** Authors the words for a self-initiated communicate the agency selected (no inbound triggered it). */
|
|
@@ -88,6 +93,22 @@ export class MotorSchemaExecutor implements CognitiveEngine {
|
|
|
88
93
|
private _grants: AccessGrants | null = null
|
|
89
94
|
private _bus: CognitiveBus | null = null
|
|
90
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Two-phase outreach authoring. A facet cannot be awaited from inside a tick:
|
|
98
|
+
* `ExecutiveFacet.report()` only QUEUES in tick-discipline mode and the reasoning
|
|
99
|
+
* launches from `pump()`, which the ExecutiveEngine calls once per tick — so an
|
|
100
|
+
* in-tick `await` blocks the very loop that would produce the answer. Observed
|
|
101
|
+
* live: a 61s freeze of the whole mind inside one tick, then an empty result.
|
|
102
|
+
* (It passes unit tests because bare facets have no inbox and author inline.)
|
|
103
|
+
*
|
|
104
|
+
* So `_deliver` REQUESTS words and returns false (the intent holds 'awaiting'),
|
|
105
|
+
* the facet answers off-tick, and a later tick delivers. Process-local by design:
|
|
106
|
+
* an authoring call in flight cannot survive a restart, and the intent would
|
|
107
|
+
* simply re-request.
|
|
108
|
+
*/
|
|
109
|
+
private _authoring = new Set<string>()
|
|
110
|
+
private _authored = new Map<string, string[]>()
|
|
111
|
+
|
|
91
112
|
constructor( schemas: MotorSchema[] = INNATE_SCHEMAS ){
|
|
92
113
|
for( const s of schemas ) this._schemas.set( s.id, s )
|
|
93
114
|
}
|
|
@@ -173,18 +194,76 @@ export class MotorSchemaExecutor implements CognitiveEngine {
|
|
|
173
194
|
else if( parentId && revoked.has( parentId ) ) del.push( id )
|
|
174
195
|
}
|
|
175
196
|
|
|
197
|
+
// ── Words that landed off-tick → deliver now ─────────────────
|
|
198
|
+
// Second half of two-phase authoring: a previous tick asked a facet for the words
|
|
199
|
+
// and held the intent 'awaiting'; the facet answered between ticks. Runs BEFORE
|
|
200
|
+
// the timeout sweep so words arriving on the same tick the clock would expire are
|
|
201
|
+
// still spoken rather than discarded.
|
|
202
|
+
const spokeThisTick = new Set<string>()
|
|
203
|
+
for( const [ id, e ] of state.entities ){
|
|
204
|
+
if( e.type !== 'agency.intent' || str( e.metadata?.['status'] ) !== 'awaiting') continue
|
|
205
|
+
if( !this._authored.has( id ) ) continue
|
|
206
|
+
|
|
207
|
+
const intent = readIntent( id, e.metadata )
|
|
208
|
+
const predicted: EfferenceCopy = {
|
|
209
|
+
expectedReward: num( e.metadata?.['predictedReward'], intent.expectedReward ),
|
|
210
|
+
expectedValence: num( e.metadata?.['predictedValence'], intent.expectedValence ),
|
|
211
|
+
}
|
|
212
|
+
if( await this._deliver( id, intent, predicted, state, tick, set, del, metrics ) )
|
|
213
|
+
spokeThisTick.add( id )
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Words whose intent no longer exists (revoked, timed out, superseded) are words
|
|
217
|
+
// that will never be said — drop them so the map cannot grow without bound.
|
|
218
|
+
for( const id of this._authored.keys() )
|
|
219
|
+
if( !state.entities.has( id ) ) this._authored.delete( id )
|
|
220
|
+
|
|
176
221
|
// ── Timeout stranded async intents ───────────────────────────
|
|
177
222
|
// An 'awaiting' intent whose host/delivery never returned would block the
|
|
178
223
|
// serial Will forever. After AWAIT_TIMEOUT ticks, abandon it as a failed
|
|
179
224
|
// outcome (which also teaches reafference the action is unreliable here).
|
|
180
225
|
for( const [ id, e ] of state.entities ){
|
|
181
226
|
if( e.type !== 'agency.intent' || str( e.metadata?.['status'] ) !== 'awaiting') continue
|
|
227
|
+
// Already delivered above — its outcome and deletion are queued for this tick.
|
|
228
|
+
if( spokeThisTick.has( id ) ) continue
|
|
229
|
+
// Words are being authored right now. The clock PAUSES rather than extends: a
|
|
230
|
+
// facet LLM call is 10–30s of latency we do not control, and any fixed budget
|
|
231
|
+
// large enough to cover a slow one is also large enough to strand a dead one.
|
|
232
|
+
// While a call is genuinely in flight the world has not failed to answer. The
|
|
233
|
+
// pause is bounded, not open-ended: `authorOutreach` always settles (its own 60s
|
|
234
|
+
// timeout resolves empty), so `_authoring` always clears and the clock resumes.
|
|
235
|
+
if( this._authoring.has( id ) ) continue
|
|
182
236
|
// POLICY_REAFFERENCE P4 — an escalated intent is HELD: the stem owns its
|
|
183
237
|
// lifecycle (extended TTL → approve/deny/expire), so the executor must not
|
|
184
238
|
// time it out at AWAIT_TIMEOUT and reconcile it as a phantom failure.
|
|
185
239
|
if( e.metadata?.['escalated'] === true ) continue
|
|
186
240
|
const dispatchedAt = num( e.metadata?.['dispatchedAt'], tick )
|
|
187
|
-
|
|
241
|
+
const age = tick - dispatchedAt
|
|
242
|
+
|
|
243
|
+
// Dispatched LATER than now ⇒ it was in flight when the mind went to sleep.
|
|
244
|
+
// Intents snapshot with the state and the tick counter restarts at 1 on wake,
|
|
245
|
+
// so a restored `awaiting` intent has a NEGATIVE age — and every guard here
|
|
246
|
+
// inverts on it: `age < AWAIT_TIMEOUT` is trivially true for -589, so it never
|
|
247
|
+
// timed out, and the selector's staleness went negative, INFLATING the
|
|
248
|
+
// incumbent's strength instead of decaying it. Measured on a live Will:
|
|
249
|
+
// incumbent 9.742 against challengers of 0.52, unpreemptable and immortal.
|
|
250
|
+
// One person's stale intent held the channel and every attempt to contact
|
|
251
|
+
// anyone else was refused, indefinitely, across restarts.
|
|
252
|
+
//
|
|
253
|
+
// Cleared WITHOUT a failure outcome: hibernating is not the world declining
|
|
254
|
+
// to answer. Reconciling it as a timeout would teach reafference that
|
|
255
|
+
// reaching that person does not work, which is a lesson about the process
|
|
256
|
+
// lifecycle, not about them.
|
|
257
|
+
if( age < 0 ){
|
|
258
|
+
del.push( id )
|
|
259
|
+
logger.info(
|
|
260
|
+
`[motor] cleared "${ str( e.metadata?.['schema'] ) ?? 'intent' }" left awaiting ` +
|
|
261
|
+
`across a restart (dispatched at tick ${ dispatchedAt }, now ${ tick })`
|
|
262
|
+
)
|
|
263
|
+
continue
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if( age < AWAIT_TIMEOUT ) continue
|
|
188
267
|
|
|
189
268
|
const intent = readIntent( id, e.metadata )
|
|
190
269
|
const predicted: EfferenceCopy = {
|
|
@@ -437,17 +516,16 @@ export class MotorSchemaExecutor implements CognitiveEngine {
|
|
|
437
516
|
return true
|
|
438
517
|
}
|
|
439
518
|
|
|
440
|
-
// Content authored upstream when present (host / host-facet)
|
|
441
|
-
//
|
|
442
|
-
//
|
|
519
|
+
// Content authored upstream when present (host / host-facet), else words a facet
|
|
520
|
+
// authored off-tick and landed since a previous tick asked for them. Neither ⇒
|
|
521
|
+
// request authoring (never awaited here — see `_authoring`) and hold 'awaiting'.
|
|
443
522
|
const authored = str( intent.parameters['content'] ) ?? firstMessage( intent.parameters['messages'] )
|
|
444
|
-
let bubbles: string[] = authored ? [ authored ] : []
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
523
|
+
let bubbles: string[] = authored ? [ authored ] : ( this._authored.get( id ) ?? [] )
|
|
524
|
+
this._authored.delete( id )
|
|
525
|
+
if( bubbles.length === 0 ){
|
|
526
|
+
this._requestAuthoring( id, intent )
|
|
527
|
+
return false // nothing to send yet → await
|
|
449
528
|
}
|
|
450
|
-
if( bubbles.length === 0 ) return false // nothing authored to send → await
|
|
451
529
|
|
|
452
530
|
const request: ActionRequest = {
|
|
453
531
|
effector,
|
|
@@ -477,6 +555,16 @@ export class MotorSchemaExecutor implements CognitiveEngine {
|
|
|
477
555
|
text: bubbles.join('\n'),
|
|
478
556
|
expiresAt: tick + CONSEQUENCE_TTL_TICKS, tick,
|
|
479
557
|
}) )
|
|
558
|
+
// The words are out. Whatever willed them is DONE being an intention.
|
|
559
|
+
//
|
|
560
|
+
// Discharged here rather than at dispatch on purpose: `_deliver` holds the
|
|
561
|
+
// intent 'awaiting' while a facet authors, and that authoring can time out or
|
|
562
|
+
// return nothing. Clearing the will at dispatch would lose the intention
|
|
563
|
+
// silently — the mind would have decided to say something and simply never
|
|
564
|
+
// have, which is the failure `commands.ts` calls "the whole intention
|
|
565
|
+
// evaporated without a trace". Enaction is the discharge; a request is not.
|
|
566
|
+
if( result.success ) this._dischargeWill( intent, del )
|
|
567
|
+
|
|
480
568
|
this._emitEnacted( intent, out, predicted, tick )
|
|
481
569
|
this._emitActionOutcome( intent, result.success, result.feedback.outcomeQuality,
|
|
482
570
|
clamp01( Math.abs( predicted.expectedReward - result.feedback.outcomeQuality ) ), tick )
|
|
@@ -484,6 +572,27 @@ export class MotorSchemaExecutor implements CognitiveEngine {
|
|
|
484
572
|
return true
|
|
485
573
|
}
|
|
486
574
|
|
|
575
|
+
/**
|
|
576
|
+
* Ask a facet for the words, off-tick. Fire-and-forget on purpose: awaiting this
|
|
577
|
+
* from inside `react()` deadlocks the tick loop against the facet pump. Idempotent
|
|
578
|
+
* per intent — a request already in flight is not duplicated, so the intent may sit
|
|
579
|
+
* 'awaiting' across many ticks with exactly one LLM call behind it.
|
|
580
|
+
*/
|
|
581
|
+
private _requestAuthoring( id: string, intent: Intent ): void {
|
|
582
|
+
if( !this._author || this._authoring.has( id ) ) return
|
|
583
|
+
|
|
584
|
+
const name = str( intent.parameters['targetEntityName'] ) ?? intent.targetEntityId ?? 'them'
|
|
585
|
+
this._authoring.add( id )
|
|
586
|
+
void this._author
|
|
587
|
+
.authorOutreach( intent.targetEntityId ?? '', name, str( intent.parameters['gist'] ) )
|
|
588
|
+
.then( bubbles => {
|
|
589
|
+
if( bubbles.length > 0 ) this._authored.set( id, bubbles )
|
|
590
|
+
else logger.warn(`[motor] outreach authoring returned nothing for "${ intent.schema }"`)
|
|
591
|
+
} )
|
|
592
|
+
.catch( err => { logger.warn(`[motor] outreach authoring failed: ${ errMsg( err ) }`) } )
|
|
593
|
+
.finally( () => { this._authoring.delete( id ) } )
|
|
594
|
+
}
|
|
595
|
+
|
|
487
596
|
// ── bus emission ─────────────────────────────────────────────
|
|
488
597
|
|
|
489
598
|
private _emitEnacted( intent: Intent, enaction: Enaction, predicted: EfferenceCopy, tick: Tick ): void {
|
|
@@ -506,6 +615,38 @@ export class MotorSchemaExecutor implements CognitiveEngine {
|
|
|
506
615
|
* RewardEvaluator reads it as a reward signal. `confidence` carries the agency's
|
|
507
616
|
* own forward-model prior so calibration has a real prediction to score.
|
|
508
617
|
*/
|
|
618
|
+
/**
|
|
619
|
+
* Retire the `ideomotor.intent` that produced this act.
|
|
620
|
+
*
|
|
621
|
+
* Nothing deleted these. They were cleared only when the executive next ran and
|
|
622
|
+
* declined to name the same action again — and the executive runs on an interval,
|
|
623
|
+
* so between cycles a willed reach-out STOOD in state, was rebuilt into an
|
|
624
|
+
* affordance every single tick, and competed every single tick. Observed as
|
|
625
|
+
* dozens of identical lines:
|
|
626
|
+
*
|
|
627
|
+
* [selector] willed reach-out → … NOT selected: 0.297 < inspect… 0.340
|
|
628
|
+
*
|
|
629
|
+
* losing by four thousandths, over and over, until it won — twice. Fabrice got
|
|
630
|
+
* the same message byte-for-byte 25 ticks apart, two outbox ids.
|
|
631
|
+
*
|
|
632
|
+
* `justEnacted` was built to hold this line and cannot: it is a DECAYING
|
|
633
|
+
* quantity, capped at `repeatDamping` (0.30), and a standing intent outlasts it
|
|
634
|
+
* by construction. Damping a permanent pull only ever delays it. So the intention
|
|
635
|
+
* is discharged by being acted on, which is what an intention is — you meant to
|
|
636
|
+
* tell someone something, you told them, and it is finished. If the mind still
|
|
637
|
+
* wants to say more, the next executive cycle forms a new one, now seeing "I said
|
|
638
|
+
* this 25 ticks ago and have had no answer" in front of it.
|
|
639
|
+
*
|
|
640
|
+
* Satiation stays exactly as it was, and still earns its keep: it damps saying
|
|
641
|
+
* the same thing again for reasons that did NOT come from a standing will.
|
|
642
|
+
*/
|
|
643
|
+
private _dischargeWill( intent: Intent, del: string[] ): void {
|
|
644
|
+
const willId = intent.evokedBy
|
|
645
|
+
if( !willId || !willId.startsWith('ideomotor-') ) return
|
|
646
|
+
del.push( willId )
|
|
647
|
+
logger.info(`[motor] discharged the will behind "${ intent.schema }" (${ willId })`)
|
|
648
|
+
}
|
|
649
|
+
|
|
509
650
|
private _emitActionOutcome(
|
|
510
651
|
intent: Intent, success: boolean, outcomeQuality: number, surprise: number, tick: Tick,
|
|
511
652
|
): void {
|
|
@@ -588,6 +729,7 @@ function readIntent( id: string, m: ReadonlyMap<string, unknown> | Record<string
|
|
|
588
729
|
stepIndex: typeof meta['stepIndex'] === 'number' ? ( meta['stepIndex'] as number ) : undefined,
|
|
589
730
|
planId: str( meta['planId'] ),
|
|
590
731
|
planStepId: str( meta['stepId'] ),
|
|
732
|
+
evokedBy: str( meta['evokedBy'] ),
|
|
591
733
|
}
|
|
592
734
|
}
|
|
593
735
|
|
|
@@ -29,7 +29,12 @@ import type { CognitiveEngine, EngineResult } from '#cognition/types'
|
|
|
29
29
|
import type { CognitiveEventSchema } from '#cognition/schema.registry'
|
|
30
30
|
import type { SchemaRepertoire } from '#agency/schemas/repertoire'
|
|
31
31
|
import { schemaEntityId, availabilityEntityId } from '#agency/schemas/repertoire'
|
|
32
|
+
import { asFinality } from '#stem/policy/arbiter'
|
|
32
33
|
import { AWAIT_TIMEOUT } from '#agency/engines/motor.schema.executor'
|
|
34
|
+
import { readEffectiveParams } from '#cognition/persona.prior'
|
|
35
|
+
import {
|
|
36
|
+
SENT_TYPE, DEFAULT_REPLY_WINDOW_TICKS, resolveReplyExpectations,
|
|
37
|
+
} from '#agency/conversation.aim'
|
|
33
38
|
|
|
34
39
|
const PROC_THRESHOLD = 0.60 // mirror of repertoire's threshold for the habitual-count metric
|
|
35
40
|
|
|
@@ -80,6 +85,11 @@ export class ReafferenceEngine implements CognitiveEngine {
|
|
|
80
85
|
// action.outcome the PlanningEngine advances on. (The executor is the emitter
|
|
81
86
|
// for sync/timeout outcomes; this is its async counterpart — one emitter each.)
|
|
82
87
|
{ type: 'action.outcome', version: 1, validate: () => null },
|
|
88
|
+
// Whether a person answers the mind when it speaks to them. Distinct from
|
|
89
|
+
// `interaction.occurred`, which reports something someone DID — a silence is
|
|
90
|
+
// not an act and so can never appear there, which is why "they never answer
|
|
91
|
+
// me" was unlearnable despite `socialStanding` being built to carry it.
|
|
92
|
+
{ type: 'social.responsiveness', version: 1, validate: () => null },
|
|
83
93
|
]
|
|
84
94
|
}
|
|
85
95
|
/** Creation seam: register a composite proposed by the executive/deliberation facet. */
|
|
@@ -201,8 +211,17 @@ export class ReafferenceEngine implements CognitiveEngine {
|
|
|
201
211
|
// is merely forbidden to do. The awaiting intent is still freed, and a
|
|
202
212
|
// refused plan step is signalled unsuccessful so the plan doesn't hang.
|
|
203
213
|
if( m['refused'] === true ){
|
|
204
|
-
const finality =
|
|
205
|
-
|
|
214
|
+
const finality = asFinality( m['finality'] )
|
|
215
|
+
|
|
216
|
+
// P5 — 'context' means the refusal was NOT ABOUT THE ACTION (tainted
|
|
217
|
+
// context, an unavailable dependency, the arbiter itself down). Denting
|
|
218
|
+
// availability here would teach a lesson the policy never taught:
|
|
219
|
+
// "reach for this less", when nothing about the ability was the problem.
|
|
220
|
+
// So the intent is freed and the plan step signalled — the mind must not
|
|
221
|
+
// hang — and NOTHING else is written. Not competence, not availability.
|
|
222
|
+
if( finality !== 'context')
|
|
223
|
+
this._repertoire.recordRefusal( schema, finality, tick )
|
|
224
|
+
|
|
206
225
|
if( fromState ) del.push( id )
|
|
207
226
|
const refusedIntent = str( m['intentId'] )
|
|
208
227
|
if( refusedIntent ) del.push( refusedIntent )
|
|
@@ -271,6 +290,19 @@ export class ReafferenceEngine implements CognitiveEngine {
|
|
|
271
290
|
// Availability entries (P2) mirror the same way — empty until a refusal lands.
|
|
272
291
|
for( const e of this._repertoire.availabilityEntities() ) set.push( e )
|
|
273
292
|
|
|
293
|
+
// ── 3.5 Did the words achieve what they were for? ────────────
|
|
294
|
+
// The motor loop above graded whether each act EXECUTED. For a communicative
|
|
295
|
+
// act that is the smaller half of the question: the outbox accepting a message
|
|
296
|
+
// is not the world answering it. Both were the same fact until now, which is
|
|
297
|
+
// how `reach-out` came to sit at 28 enactments / 28 successes while the person
|
|
298
|
+
// it kept reaching had said nothing back.
|
|
299
|
+
//
|
|
300
|
+
// Folded here rather than in a new engine because this is the same question
|
|
301
|
+
// this engine already exists to ask, one layer out — reafference over a social
|
|
302
|
+
// act instead of a motor one. The resolution itself is pure (conversation.aim);
|
|
303
|
+
// all that happens here is writing it down and saying it out loud.
|
|
304
|
+
const replied = this._resolveReplies( tick, state, set )
|
|
305
|
+
|
|
274
306
|
// ── 4. Telemetry ─────────────────────────────────────────────
|
|
275
307
|
const skills = this._repertoire.skills()
|
|
276
308
|
const habitual = [ ...skills.values() ].filter( s => s.habitStrength >= PROC_THRESHOLD ).length
|
|
@@ -281,6 +313,10 @@ export class ReafferenceEngine implements CognitiveEngine {
|
|
|
281
313
|
[ 'agency.habitual.count', habitual ],
|
|
282
314
|
[ 'agency.sensory.confirmed', sensory ],
|
|
283
315
|
)
|
|
316
|
+
// Quiet path stays byte-identical: a Will that has spoken to nobody, or whose
|
|
317
|
+
// every turn was answered, writes nothing here (cf. EXAFFERENCE P3).
|
|
318
|
+
if( replied.answered > 0 ) metrics.push([ 'social.answered.count', replied.answered ])
|
|
319
|
+
if( replied.unanswered > 0 ) metrics.push([ 'social.unanswered.count', replied.unanswered ])
|
|
284
320
|
// Only emit the refusal metric when it fired — a never-refused Will writes
|
|
285
321
|
// nothing here, preserving the byte-identical quiet path (cf. EXAFFERENCE P3).
|
|
286
322
|
if( refused > 0 ) metrics.push([ 'agency.refused.count', refused ])
|
|
@@ -288,6 +324,97 @@ export class ReafferenceEngine implements CognitiveEngine {
|
|
|
288
324
|
return { commands: { set, delete: del, metrics } }
|
|
289
325
|
}
|
|
290
326
|
|
|
327
|
+
/**
|
|
328
|
+
* Latch each open turn's fate onto its own record and announce it once.
|
|
329
|
+
*
|
|
330
|
+
* Two rules earn their keep here:
|
|
331
|
+
*
|
|
332
|
+
* • MERGE, never replace. `StateManager.setEntity` overwrites the whole entity,
|
|
333
|
+
* and a `conversation.sent` carries `outboxMessageIds` — the sole key by which
|
|
334
|
+
* a later delivery ack can find it. Rewriting the record with only the fields
|
|
335
|
+
* this method cares about would sever that, silently, for every turn that got
|
|
336
|
+
* an answer.
|
|
337
|
+
*
|
|
338
|
+
* • Latch, don't recompute. `answeredAt`/`unansweredAt` persist, so the event
|
|
339
|
+
* fires on the one tick the fact changed rather than every tick for the rest
|
|
340
|
+
* of the session — which for an unanswered turn would be thousands of
|
|
341
|
+
* identical reputation hits against one person for one silence.
|
|
342
|
+
*/
|
|
343
|
+
private _resolveReplies(
|
|
344
|
+
tick: Tick,
|
|
345
|
+
state: ReadonlySimulationState,
|
|
346
|
+
set: EntityInput[],
|
|
347
|
+
): { answered: number; unanswered: number } {
|
|
348
|
+
// A trait, not a constant: how long a silence takes to mean something differs
|
|
349
|
+
// between minds, so it tunes through the persona prior like every other
|
|
350
|
+
// developable parameter. Absent ⇒ the default ⇒ the pre-seam behaviour.
|
|
351
|
+
//
|
|
352
|
+
// Read from the SELECTOR's config despite being applied here, because it and
|
|
353
|
+
// `repeatWindowTicks` are two readings of one disposition — how long this mind
|
|
354
|
+
// sits with something it has said. Held apart, a mind could tune itself into
|
|
355
|
+
// saying a thing again while still calling the silence too fresh to count.
|
|
356
|
+
const window = Math.max(
|
|
357
|
+
1,
|
|
358
|
+
Math.round(
|
|
359
|
+
readEffectiveParams( state, 'engine-config-action-selector').replyWindowTicks
|
|
360
|
+
?? DEFAULT_REPLY_WINDOW_TICKS
|
|
361
|
+
),
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
const { answered, unanswered } = resolveReplyExpectations( state.entities, tick, window )
|
|
365
|
+
if( answered.length === 0 && unanswered.length === 0 ) return { answered: 0, unanswered: 0 }
|
|
366
|
+
|
|
367
|
+
const merge = ( id: string, patch: Record<string, unknown> ): void => {
|
|
368
|
+
const existing = state.entities.get( id )
|
|
369
|
+
if( !existing ) return
|
|
370
|
+
set.push({
|
|
371
|
+
id, type: SENT_TYPE,
|
|
372
|
+
metadata: { ...( existing.metadata as Record<string, unknown> ?? {} ), ...patch },
|
|
373
|
+
})
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
for( const { turn, at, with: said } of answered ){
|
|
377
|
+
// Their words, not merely the fact of them — see SpokenTurn.answeredWith.
|
|
378
|
+
merge( turn.entityId, { answeredAt: at, ...( said ? { answeredWith: said } : {} ) } )
|
|
379
|
+
this._emitResponsiveness( turn.targetEntityId, true, at - turn.tick, tick )
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
for( const turn of unanswered ){
|
|
383
|
+
merge( turn.entityId, { unansweredAt: tick } )
|
|
384
|
+
this._emitResponsiveness( turn.targetEntityId, false, tick - turn.tick, tick )
|
|
385
|
+
logger.info(
|
|
386
|
+
`[reafference] no answer from ${ turn.targetEntityName ?? turn.targetEntityId } ` +
|
|
387
|
+
`after ${ tick - turn.tick } ticks — "${ turn.preview.slice( 0, 60 ) }"`
|
|
388
|
+
)
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return { answered: answered.length, unanswered: unanswered.length }
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Publish how a person responded to being spoken to.
|
|
396
|
+
*
|
|
397
|
+
* Deliberately NOT an `interaction.occurred`: that event means "someone did
|
|
398
|
+
* something toward us" and carries a valence for what they did. A silence is
|
|
399
|
+
* nobody doing anything, and forcing it through that channel would have the
|
|
400
|
+
* ReputationTracker book a hostile *act* where there was only an absence — the
|
|
401
|
+
* mind would come to think it was being rebuffed rather than simply not
|
|
402
|
+
* answered yet. Separate signal, separate meaning, one consumer decides what
|
|
403
|
+
* either is worth.
|
|
404
|
+
*/
|
|
405
|
+
private _emitResponsiveness( keid: string, answered: boolean, waitedTicks: number, tick: Tick ): void {
|
|
406
|
+
if( !this._bus ) return
|
|
407
|
+
try {
|
|
408
|
+
this._bus.publish({
|
|
409
|
+
type: 'social.responsiveness', version: 1, sourceEngine: this.name,
|
|
410
|
+
// An answer is ordinary; being ignored is the one worth interrupting for.
|
|
411
|
+
salience: answered ? 0.3 : 0.55,
|
|
412
|
+
payload: { keid, answered, waitedTicks, tick },
|
|
413
|
+
})
|
|
414
|
+
}
|
|
415
|
+
catch( err ){ logger.warn(`[reafference] responsiveness publish failed: ${ err instanceof Error ? err.message : String( err ) }`) }
|
|
416
|
+
}
|
|
417
|
+
|
|
291
418
|
private _emitProceduralized( skill: LearnedSkill, tick: Tick ): void {
|
|
292
419
|
if( !this._bus ) return
|
|
293
420
|
try {
|
|
@@ -190,7 +190,17 @@ export class ProactiveCommunicator {
|
|
|
190
190
|
// Generate outbox message IDs first so we can embed them in conversation.sent
|
|
191
191
|
// and correlate deliveries back to this intent (TODO 11.3).
|
|
192
192
|
const originalMessage = ( request.parameters?.originalMessage as string ) ?? ''
|
|
193
|
-
|
|
193
|
+
// `decidedAt` is the enacting tick and is always set by the caller; the
|
|
194
|
+
// `parameters.tick` fallback is for hosts that build a request by hand.
|
|
195
|
+
//
|
|
196
|
+
// This defaulted to 0 for every agency-path delivery, because MotorSchemaExecutor
|
|
197
|
+
// builds `parameters` from the intent and never put a tick in it. Two
|
|
198
|
+
// consequences, both silent: every `conversation.sent` was stamped tick 0, so
|
|
199
|
+
// anything comparing against it (satiation, undertaking discharge) compared
|
|
200
|
+
// against zero and never fired; and the id `conv-sent-<target>-0` was CONSTANT,
|
|
201
|
+
// so each send to a person overwrote the record of the last one — the mind kept
|
|
202
|
+
// exactly one memory of having spoken to each person, forever.
|
|
203
|
+
const deliveryTick = ( request.parameters?.tick as number ) ?? request.decidedAt ?? 0
|
|
194
204
|
const replyToMessageId = ( request.parameters?.replyToMessageId as string ) ?? undefined
|
|
195
205
|
const isAck = ( request.parameters?.isAck as boolean ) ?? false
|
|
196
206
|
|
|
@@ -264,9 +274,15 @@ export class ProactiveCommunicator {
|
|
|
264
274
|
description: `I reach out to ${targetEntityName}: "${fullReply.slice( 0, 80 )}${fullReply.length > 80 ? '…' : ''}"`,
|
|
265
275
|
commands,
|
|
266
276
|
feedback: {
|
|
267
|
-
|
|
277
|
+
// NOT a success yet. This reports the TRANSPORT, and the act's point is to be
|
|
278
|
+
// answered — an outcome not yet known at this moment. Scoring delivery as 0.85
|
|
279
|
+
// taught the mind that reaching out works every time, including the times it
|
|
280
|
+
// was ignored, which is why it re-asked the same question 19 times in two
|
|
281
|
+
// minutes. Neutral here; the unanswered/answered signal in outreach.silence.ts
|
|
282
|
+
// is what actually moves the pull toward this person.
|
|
283
|
+
outcomeQuality: 0.5,
|
|
268
284
|
surprise: 0.15,
|
|
269
|
-
lessons: [ `My
|
|
285
|
+
lessons: [ `My words are on their way to ${targetEntityName}. Whether they land is not yet known.` ],
|
|
270
286
|
},
|
|
271
287
|
}
|
|
272
288
|
}
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
// ─────────────────────────────────────────────────────────────
|
|
17
17
|
|
|
18
18
|
import type { EntityInput, Tick } from '#core/types'
|
|
19
|
+
import type { DenialFinality, PolicyCounterfactual } from '#stem/policy/arbiter'
|
|
19
20
|
|
|
20
21
|
export interface HostAckResult {
|
|
21
22
|
success: boolean
|
|
@@ -31,7 +32,17 @@ export interface HostAckResult {
|
|
|
31
32
|
* forbidden to do. `finality` decides how hard availability is cut.
|
|
32
33
|
*/
|
|
33
34
|
refused?: boolean
|
|
34
|
-
finality?:
|
|
35
|
+
finality?: DenialFinality
|
|
36
|
+
/**
|
|
37
|
+
* ENVELOPE_NARROWING P0 — what WOULD have been permitted, e.g.
|
|
38
|
+
* `{ field: 'amount', requested: 500, allowed: 100 }`.
|
|
39
|
+
*
|
|
40
|
+
* Carried onto the outcome so the tape and the thing the mind actually learns
|
|
41
|
+
* from agree about what happened. Until ENVELOPE_NARROWING P1 lands nothing
|
|
42
|
+
* READS it — that fold is blocked upstream on whether a scalar `allowed` is a
|
|
43
|
+
* ceiling or a floor — but dropping it here is what made the two disagree.
|
|
44
|
+
*/
|
|
45
|
+
counterfactual?: PolicyCounterfactual
|
|
35
46
|
}
|
|
36
47
|
|
|
37
48
|
/**
|
|
@@ -79,7 +90,10 @@ export function reconcileInvocation(
|
|
|
79
90
|
mode: 'external',
|
|
80
91
|
tick,
|
|
81
92
|
reconciled: true,
|
|
82
|
-
...( result.refused ? { refused: true, finality: result.finality ?? '
|
|
93
|
+
...( result.refused ? { refused: true, finality: result.finality ?? 'parameter' } : {} ),
|
|
94
|
+
// Only when the arbiter actually reported a bound — a refusal without one
|
|
95
|
+
// writes no key at all, so the quiet path is unchanged.
|
|
96
|
+
...( result.refused && result.counterfactual ? { counterfactual: result.counterfactual } : {} ),
|
|
83
97
|
...( provenance.planId ? { planId: provenance.planId } : {} ),
|
|
84
98
|
...( provenance.stepId ? { stepId: provenance.stepId } : {} ),
|
|
85
99
|
},
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/cognition/agency/restart.ts — the restart boundary
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
//
|
|
5
|
+
// Two different things happen when a Will wakes from a snapshot, and conflating
|
|
6
|
+
// them is what made this a recurring class of bug rather than a single one:
|
|
7
|
+
//
|
|
8
|
+
// 1. TIME must not go backwards. Entities come back stamped with the tick they
|
|
9
|
+
// were written at, so the clock has to resume from the snapshot's tick
|
|
10
|
+
// rather than restart at 0 (WillStem.createWill does this via
|
|
11
|
+
// `clock.setTick`). Otherwise every `tick - stampedTick` in the codebase —
|
|
12
|
+
// 42 comparison sites across 19 files — computes a NEGATIVE age, and the
|
|
13
|
+
// guards built on them inverted spectacularly: an awaiting intent whose age
|
|
14
|
+
// read -589 could never time out, and the selector's staleness decay
|
|
15
|
+
// `1 - staleness × STALE_DECAY` became `1 + 19.6`, AMPLIFYING an incumbent
|
|
16
|
+
// from 0.47 to 9.74 and making it permanently unpreemptable.
|
|
17
|
+
//
|
|
18
|
+
// 2. Work that was IN FLIGHT does not resume. Resuming the clock fixes the
|
|
19
|
+
// arithmetic but says nothing about the semantics: an action dispatched
|
|
20
|
+
// moments before hibernation is not still dispatched hours later, and the
|
|
21
|
+
// window in which the world might have echoed our own words has closed.
|
|
22
|
+
// Leaving these behind is worse under a resumed clock than under a reset
|
|
23
|
+
// one, because they now look plausibly RECENT — an awaiting intent would be
|
|
24
|
+
// reconciled as a genuine timeout, teaching the mind that reaching that
|
|
25
|
+
// person does not work, when all that happened is that it slept.
|
|
26
|
+
//
|
|
27
|
+
// This module owns (2): what the agency considers in-flight, and therefore what
|
|
28
|
+
// must not cross the boundary. It is deliberately a short, explicit list rather
|
|
29
|
+
// than a heuristic — anything the agency stamps with a tick and expects to
|
|
30
|
+
// resolve within a few of them belongs here.
|
|
31
|
+
// ─────────────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
import { CONSEQUENCE_TYPE } from '#agency/consequence'
|
|
34
|
+
|
|
35
|
+
/** Entity type of a committed action awaiting the world's answer. */
|
|
36
|
+
const INTENT_TYPE = 'agency.intent'
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Ids of the restored entities that represent work in flight when the mind went
|
|
40
|
+
* to sleep. Pure — the caller drops them.
|
|
41
|
+
*
|
|
42
|
+
* • `agency.intent` with status 'awaiting' — dispatched, never answered. It is
|
|
43
|
+
* ABANDONED, not failed: sleeping is not the world declining to answer, and
|
|
44
|
+
* recording a failure here would teach reafference a lesson about the process
|
|
45
|
+
* lifecycle rather than about the action.
|
|
46
|
+
* • `agency.consequence` — the expected sensory footprint of an act, used both
|
|
47
|
+
* to recognise our own echo (P2) and to damp repeating ourselves (P5). Its
|
|
48
|
+
* TTL is a handful of ticks; across a sleep the echo will never arrive, and a
|
|
49
|
+
* stale one both mis-attributes genuine replies as self-caused and suppresses
|
|
50
|
+
* contact that should now be free to happen.
|
|
51
|
+
*
|
|
52
|
+
* A `selected` intent is deliberately NOT cleared: it has not been dispatched, so
|
|
53
|
+
* it is still an intention the mind holds rather than an action in flight.
|
|
54
|
+
*/
|
|
55
|
+
export function inFlightOnRestore(
|
|
56
|
+
entities: ReadonlyMap<string, { type: string; metadata?: Record<string, unknown> }>,
|
|
57
|
+
): string[] {
|
|
58
|
+
const drop: string[] = []
|
|
59
|
+
|
|
60
|
+
for( const [ id, e ] of entities ){
|
|
61
|
+
if( e.type === CONSEQUENCE_TYPE ){ drop.push( id ); continue }
|
|
62
|
+
if( e.type === INTENT_TYPE && e.metadata?.['status'] === 'awaiting') drop.push( id )
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return drop
|
|
66
|
+
}
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
|
|
27
27
|
import type { MotorSchema, LearnedSkill } from '#agency/types'
|
|
28
28
|
import type { EntityInput, ReadonlySimulationState } from '#core/types'
|
|
29
|
+
import type { DenialFinality } from '#stem/policy/arbiter'
|
|
29
30
|
import { INNATE_SCHEMAS } from '#agency/schemas/innate'
|
|
30
31
|
|
|
31
32
|
const VALUE_ALPHA = 0.2 // value EMA rate
|
|
@@ -50,8 +51,8 @@ const DROP_HABIT = 0.05 // below this (and learned) the skill is forgott
|
|
|
50
51
|
// availability never floors at zero, and it climbs back toward 1 with disuse of
|
|
51
52
|
// the refusal. P2 keys availability by SCHEMA; per-(schema, params) envelope
|
|
52
53
|
// narrowing is a follow-up that belongs at selection time, not fielding time.
|
|
53
|
-
const AVAIL_DROP_CLASS
|
|
54
|
-
const
|
|
54
|
+
const AVAIL_DROP_CLASS = 0.50 // multiplicative cut on a class-final refusal
|
|
55
|
+
const AVAIL_DROP_PARAMETER = 0.12 // lighter cut when only the arguments were refused
|
|
55
56
|
const AVAIL_FLOOR = 0.05 // never zero — re-probe must always be possible
|
|
56
57
|
const AVAIL_RECOVERY = 0.02 // per-decay climb back toward 1
|
|
57
58
|
const AVAIL_RECOVERED = 0.999 // at/above this the entry is dropped (quiet path)
|
|
@@ -119,13 +120,19 @@ export class SchemaRepertoire {
|
|
|
119
120
|
|
|
120
121
|
/**
|
|
121
122
|
* Fold a policy refusal into the availability layer (NOT competence). A
|
|
122
|
-
* `class` refusal cuts availability hard;
|
|
123
|
+
* `class` refusal cuts availability hard; a `parameter` refusal dents it
|
|
123
124
|
* lightly. Multiplicative so repeated refusals compound toward — but never
|
|
124
125
|
* reach — zero, keeping re-probe alive.
|
|
126
|
+
*
|
|
127
|
+
* `context` is EXCLUDED FROM THE SIGNATURE, not handled inside: a refusal
|
|
128
|
+
* that was not about the action must never reach the availability layer at
|
|
129
|
+
* all, and making that a type error rather than a convention means a future
|
|
130
|
+
* caller cannot quietly re-introduce the dent. The routing decision lives in
|
|
131
|
+
* the ReafferenceEngine's refused branch (P5).
|
|
125
132
|
*/
|
|
126
|
-
recordRefusal( schema: string, finality:
|
|
133
|
+
recordRefusal( schema: string, finality: Exclude<DenialFinality, 'context'>, tick: number ): number {
|
|
127
134
|
const prev = this._availability.get( schema )?.value ?? 1
|
|
128
|
-
const drop = finality === 'class' ? AVAIL_DROP_CLASS :
|
|
135
|
+
const drop = finality === 'class' ? AVAIL_DROP_CLASS : AVAIL_DROP_PARAMETER
|
|
129
136
|
const value = Math.max( AVAIL_FLOOR, prev * ( 1 - drop ) )
|
|
130
137
|
this._availability.set( schema, { value, lastRefusedTick: tick } )
|
|
131
138
|
return value
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
// + driveUrgency
|
|
14
14
|
// + habitStrength·W (additive habit bonus)
|
|
15
15
|
// + planBias·W (top-down prior)
|
|
16
|
+
// + willBias·W (deliberate volition)
|
|
17
|
+
// + socialPrior·W (learned read on WHO)
|
|
16
18
|
// − cost
|
|
17
19
|
// − inhibition
|
|
18
20
|
// − risk
|
|
@@ -47,9 +49,34 @@ export interface ScoreWeights {
|
|
|
47
49
|
habit: number
|
|
48
50
|
/** Top-down planning prior — how strongly an executing plan biases the competition. */
|
|
49
51
|
plan: number
|
|
52
|
+
/**
|
|
53
|
+
* Volitional prior — how strongly a DELIBERATE executive decision biases the
|
|
54
|
+
* competition. Peer of `plan`: a plan's frontier is a standing prior, a willed act
|
|
55
|
+
* is a prior formed this cycle. Strong enough that no habit (≤1) can out-weigh a
|
|
56
|
+
* confident decision, weak enough that inhibition, threat, or a genuinely more
|
|
57
|
+
* pressing affordance still can — willing is not a rubber stamp.
|
|
58
|
+
*/
|
|
59
|
+
will: number
|
|
60
|
+
/**
|
|
61
|
+
* How strongly the mind's LEARNED read on an addressee biases acting toward them.
|
|
62
|
+
* Peer of `goal` in magnitude: who someone has shown themselves to be should weigh
|
|
63
|
+
* about as much as a goal pointed at them. Signed — a person the mind has come to
|
|
64
|
+
* trust pulls it toward contact, one who never answers pulls it away — and it
|
|
65
|
+
* reaches zero for anyone unknown, so a mind that knows no one scores as before.
|
|
66
|
+
*/
|
|
67
|
+
social: number
|
|
50
68
|
cost: number
|
|
51
69
|
inhib: number
|
|
52
70
|
risk: number
|
|
71
|
+
/**
|
|
72
|
+
* How strongly an act's own live footprint damps doing it again (EXAFFERENCE
|
|
73
|
+
* P5). Satiation, not a lock: it decays with the descriptor, so the pull
|
|
74
|
+
* returns once the window in which the world could have answered has passed.
|
|
75
|
+
* Peer of `will` in magnitude — a freshly-delivered message should roughly
|
|
76
|
+
* cancel the volitional prior that produced it, and no more, so a genuinely
|
|
77
|
+
* pressing reason to speak again still wins.
|
|
78
|
+
*/
|
|
79
|
+
repeat: number
|
|
53
80
|
}
|
|
54
81
|
|
|
55
82
|
export const DEFAULT_WEIGHTS: ScoreWeights = {
|
|
@@ -59,9 +86,12 @@ export const DEFAULT_WEIGHTS: ScoreWeights = {
|
|
|
59
86
|
drive: 0.25,
|
|
60
87
|
habit: 0.20,
|
|
61
88
|
plan: 0.30,
|
|
89
|
+
will: 0.30,
|
|
90
|
+
social: 0.30,
|
|
62
91
|
cost: 0.20,
|
|
63
92
|
inhib: 0.30,
|
|
64
93
|
risk: 0.20,
|
|
94
|
+
repeat: 0.30,
|
|
65
95
|
}
|
|
66
96
|
|
|
67
97
|
/**
|
|
@@ -154,9 +184,12 @@ export function scoreAffordance(
|
|
|
154
184
|
+ w.drive * driveUrgency( a, bias )
|
|
155
185
|
+ w.habit * a.habitStrength
|
|
156
186
|
+ w.plan * ( a.planBias ?? 0 )
|
|
187
|
+
+ w.will * ( a.willBias ?? 0 )
|
|
188
|
+
+ w.social * ( a.socialPrior ?? 0 )
|
|
157
189
|
- w.cost * a.cost
|
|
158
190
|
- w.inhib * bias.inhibition
|
|
159
191
|
- w.risk * risk( a, bias )
|
|
192
|
+
- w.repeat * ( a.justEnacted ?? 0 )
|
|
160
193
|
)
|
|
161
194
|
// POLICY_REAFFERENCE P2 — policy availability damps a POSITIVE activation only,
|
|
162
195
|
// never flipping its sign: a refused ability competes weakly (so it is rarely
|