@mindot/will 0.1.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/LICENSE +202 -0
- package/README.md +777 -0
- package/dist/index.d.ts +8144 -0
- package/dist/index.js +26231 -0
- package/dist/index.js.map +1 -0
- package/package.json +73 -0
- package/src/cognition/agency/access.grants.ts +59 -0
- package/src/cognition/agency/competence.codec.ts +89 -0
- package/src/cognition/agency/engines/action.selector.ts +462 -0
- package/src/cognition/agency/engines/affordance.synthesizer.ts +384 -0
- package/src/cognition/agency/engines/deliberation.engine.ts +241 -0
- package/src/cognition/agency/engines/instruction.intake.ts +54 -0
- package/src/cognition/agency/engines/motor.schema.executor.ts +532 -0
- package/src/cognition/agency/engines/reafference.engine.ts +241 -0
- package/src/cognition/agency/execution.primitives.ts +118 -0
- package/src/cognition/agency/index.ts +58 -0
- package/src/cognition/agency/proactive.communicator.ts +274 -0
- package/src/cognition/agency/reconcile.learning.ts +82 -0
- package/src/cognition/agency/schemas/external.ts +59 -0
- package/src/cognition/agency/schemas/innate.ts +111 -0
- package/src/cognition/agency/schemas/repertoire.ts +246 -0
- package/src/cognition/agency/selection.scoring.ts +191 -0
- package/src/cognition/agency/types.ts +165 -0
- package/src/cognition/bus.ts +391 -0
- package/src/cognition/completion.inbox.ts +86 -0
- package/src/cognition/config.mirror.entities.ts +475 -0
- package/src/cognition/conversation.memory.ts +81 -0
- package/src/cognition/event.log.ts +116 -0
- package/src/cognition/event.schemas.ts +568 -0
- package/src/cognition/faculties/aesthetic.evaluator.ts +313 -0
- package/src/cognition/faculties/affective.blender.ts +562 -0
- package/src/cognition/faculties/attachment.evaluator.ts +432 -0
- package/src/cognition/faculties/attention.allocator.ts +422 -0
- package/src/cognition/faculties/autobiographical.narrator.ts +300 -0
- package/src/cognition/faculties/bias.detector.ts +363 -0
- package/src/cognition/faculties/circadian.oscillator.ts +294 -0
- package/src/cognition/faculties/confidence.calibrator.ts +324 -0
- package/src/cognition/faculties/dream.simulator.ts +251 -0
- package/src/cognition/faculties/empathy.simulator.ts +253 -0
- package/src/cognition/faculties/energy.regulator.ts +320 -0
- package/src/cognition/faculties/episodic.consolidator.ts +728 -0
- package/src/cognition/faculties/executive.engine/commands.ts +459 -0
- package/src/cognition/faculties/executive.engine/config.ts +46 -0
- package/src/cognition/faculties/executive.engine/context.ts +605 -0
- package/src/cognition/faculties/executive.engine/deferred.effects.ts +104 -0
- package/src/cognition/faculties/executive.engine/deliberate.reasoning.ts +60 -0
- package/src/cognition/faculties/executive.engine/effort.gate.ts +114 -0
- package/src/cognition/faculties/executive.engine/engine.ts +1028 -0
- package/src/cognition/faculties/executive.engine/escalation.buffer.ts +90 -0
- package/src/cognition/faculties/executive.engine/facet.supervisor.ts +271 -0
- package/src/cognition/faculties/executive.engine/facet.ts +628 -0
- package/src/cognition/faculties/executive.engine/gating.ts +222 -0
- package/src/cognition/faculties/executive.engine/index.ts +6 -0
- package/src/cognition/faculties/executive.engine/messages.ts +102 -0
- package/src/cognition/faculties/executive.engine/parser.ts +380 -0
- package/src/cognition/faculties/executive.engine/prompt.factory.ts +1053 -0
- package/src/cognition/faculties/executive.engine/types.ts +323 -0
- package/src/cognition/faculties/exteroception.ts +338 -0
- package/src/cognition/faculties/forgetting.curve.ts +202 -0
- package/src/cognition/faculties/frustration.evaluator.ts +280 -0
- package/src/cognition/faculties/goal.manager.ts +1008 -0
- package/src/cognition/faculties/inhibition.controller.ts +351 -0
- package/src/cognition/faculties/interoception.ts +417 -0
- package/src/cognition/faculties/introspection.engine.ts +246 -0
- package/src/cognition/faculties/known.entity.tracker.ts +439 -0
- package/src/cognition/faculties/loss.evaluator.ts +224 -0
- package/src/cognition/faculties/moral.evaluator.ts +437 -0
- package/src/cognition/faculties/novelty.detector.ts +254 -0
- package/src/cognition/faculties/persona.consolidator.ts +845 -0
- package/src/cognition/faculties/planning.engine/engine.ts +859 -0
- package/src/cognition/faculties/planning.engine/plan.frontier.ts +83 -0
- package/src/cognition/faculties/planning.engine/plan.store.ts +174 -0
- package/src/cognition/faculties/planning.engine/plan.supervision.ts +494 -0
- package/src/cognition/faculties/planning.engine/types.ts +143 -0
- package/src/cognition/faculties/reputation.tracker.ts +294 -0
- package/src/cognition/faculties/reward.evaluator.ts +304 -0
- package/src/cognition/faculties/self.model.updater.ts +677 -0
- package/src/cognition/faculties/semantic.engine/clustering.ts +673 -0
- package/src/cognition/faculties/semantic.engine/index.ts +6 -0
- package/src/cognition/faculties/semantic.engine/integrator.ts +750 -0
- package/src/cognition/faculties/semantic.engine/types.ts +53 -0
- package/src/cognition/faculties/sleep.pressure.regulator.ts +262 -0
- package/src/cognition/faculties/social.perception.ts +333 -0
- package/src/cognition/faculties/spaced.repetition.ts +605 -0
- package/src/cognition/faculties/stress.regulator.ts +375 -0
- package/src/cognition/faculties/task.switcher.ts +239 -0
- package/src/cognition/faculties/theory.of.mind.ts +333 -0
- package/src/cognition/faculties/threat.evaluator.ts +312 -0
- package/src/cognition/faculties/working.memory.ts +406 -0
- package/src/cognition/generative.model.ts +344 -0
- package/src/cognition/heartbeat.ts +70 -0
- package/src/cognition/index.ts +291 -0
- package/src/cognition/instruction.handler.ts +146 -0
- package/src/cognition/memory/index.ts +35 -0
- package/src/cognition/memory/vector.adapter.ts +316 -0
- package/src/cognition/memory/vector.content.ts +47 -0
- package/src/cognition/memory/vector.embedder.ts +212 -0
- package/src/cognition/memory/vector.index.ts +459 -0
- package/src/cognition/memory/vector.types.ts +65 -0
- package/src/cognition/orchestrator.ts +142 -0
- package/src/cognition/persona.prior.ts +265 -0
- package/src/cognition/schema.registry.ts +145 -0
- package/src/cognition/senses/audition.engine/engine.ts +976 -0
- package/src/cognition/senses/audition.engine/salience.ts +59 -0
- package/src/cognition/senses/base.sense.engine.ts +117 -0
- package/src/cognition/senses/gustation.engine.ts +24 -0
- package/src/cognition/senses/index.ts +172 -0
- package/src/cognition/senses/olfaction.engine.ts +24 -0
- package/src/cognition/senses/somatosensation.engine.ts +23 -0
- package/src/cognition/senses/vision.engine.ts +23 -0
- package/src/cognition/types.ts +63 -0
- package/src/cognition/utilities/token.tracker.ts +492 -0
- package/src/core/abstracts.ts +60 -0
- package/src/core/async.engine.ts +539 -0
- package/src/core/clock.ts +196 -0
- package/src/core/completion.recorder.ts +122 -0
- package/src/core/conflict.detector.ts +140 -0
- package/src/core/distributed.ts +626 -0
- package/src/core/event.bus.ts +292 -0
- package/src/core/inbound.recorder.ts +100 -0
- package/src/core/index.ts +167 -0
- package/src/core/logger.ts +85 -0
- package/src/core/metrics.ts +107 -0
- package/src/core/orchestrator.ts +625 -0
- package/src/core/replay.ts +589 -0
- package/src/core/scenario.ts +120 -0
- package/src/core/serialization.ts +448 -0
- package/src/core/simulation.ts +197 -0
- package/src/core/snapshot.manager.ts +293 -0
- package/src/core/state.manager.ts +283 -0
- package/src/core/types.ts +274 -0
- package/src/core/utils.ts +82 -0
- package/src/core/wall.clock.ts +23 -0
- package/src/extensions/livestream.ext.ts +570 -0
- package/src/extensions/time.ext.ts +339 -0
- package/src/index.ts +91 -0
- package/src/llm/gate.ts +140 -0
- package/src/llm/index.ts +716 -0
- package/src/llm/summarizer.ts +170 -0
- package/src/llm/wire.contracts.ts +67 -0
- package/src/pma/eval.ts +651 -0
- package/src/pma/index.ts +1082 -0
- package/src/profiles/built-in.ts +7 -0
- package/src/profiles/companion.ts +28 -0
- package/src/profiles/company-brain.ts +54 -0
- package/src/profiles/customer-service.ts +30 -0
- package/src/profiles/game-npc.ts +20 -0
- package/src/profiles/index.ts +42 -0
- package/src/profiles/smart-home.ts +34 -0
- package/src/runners/coherence.runner.ts +49 -0
- package/src/runners/outreach.runner.ts +158 -0
- package/src/runners/social.runner.ts +182 -0
- package/src/runners/thin-shim.runner.ts +195 -0
- package/src/sdk/will.ts +334 -0
- package/src/stem/assembly.audit.ts +88 -0
- package/src/stem/distribution.ts +203 -0
- package/src/stem/guards/identity.coherence.ts +152 -0
- package/src/stem/guards/identity.guard.ts +227 -0
- package/src/stem/index.ts +1145 -0
- package/src/stem/mind.ts +1090 -0
- package/src/stem/tracts/ack.reconciler.ts +55 -0
- package/src/stem/tracts/biography.writer.ts +190 -0
- package/src/stem/tracts/effector.controller.ts +143 -0
- package/src/stem/tracts/health.reporter.ts +113 -0
- package/src/stem/tracts/inbound.queue.ts +54 -0
- package/src/stem/tracts/outbox.controller.ts +167 -0
- package/src/stem/tracts/outbox.writer.ts +133 -0
- package/src/stem/tracts/pma.controller.ts +109 -0
- package/src/stem/tracts/replay.controller.ts +177 -0
- package/src/stem/tracts/sensory.controller.ts +168 -0
- package/src/stem/tracts/session.logger.ts +157 -0
- package/src/stem/tracts/transport/index.ts +38 -0
- package/src/stem/tracts/transport/loopback.transport.ts +86 -0
- package/src/stem/tracts/transport/socketio.transport.ts +200 -0
- package/src/stem/tracts/transport/stream.transport.ts +152 -0
- package/src/stem/tracts/transport/types.ts +193 -0
- package/src/stem/tracts/transport.controller.ts +380 -0
- package/src/types.ts +116 -0
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/agency/action.selector.ts — the biased, gated competition
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
//
|
|
5
|
+
// The ActionSelector runs every tick with no LLM. It reads the affordance field
|
|
6
|
+
// the synthesizer wrote, assembles the bias signals the affective / drive /
|
|
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 the executor.
|
|
9
|
+
//
|
|
10
|
+
// System 1 is the default: the substrate selects and commits on its own. The
|
|
11
|
+
// executive (System 2, the LLM) is recruited only when the choice is genuinely
|
|
12
|
+
// uncertain or consequential — `deliberate` is set when competition entropy or
|
|
13
|
+
// stakes exceed threshold. Per design, an overlearned schema both wins more
|
|
14
|
+
// cheaply (additive habit bonus in scoring) AND raises its own deliberation
|
|
15
|
+
// thresholds (habit relief below), so proceduralized actions skip the LLM —
|
|
16
|
+
// the instrumental→habitual gradient cashing out as falling inference spend.
|
|
17
|
+
//
|
|
18
|
+
// The committed intent always carries the `deliberate` flag, so a tier without
|
|
19
|
+
// an executive still acts on the substrate's best guess; when an executive is
|
|
20
|
+
// present it may revise a deliberate intent before the executor runs next tick.
|
|
21
|
+
// ─────────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
import { logger } from '#core/logger'
|
|
24
|
+
import type {
|
|
25
|
+
Duration, Tick, SimulationContext,
|
|
26
|
+
ReadonlySimulationState, StateCommands, EntityInput,
|
|
27
|
+
} from '#core/types'
|
|
28
|
+
import type { CognitiveBus } from '#cognition/bus'
|
|
29
|
+
import type { CognitiveEngine, EngineResult } from '#cognition/types'
|
|
30
|
+
import type { CognitiveEventSchema } from '#cognition/schema.registry'
|
|
31
|
+
import type { Affordance, AffordanceSource, ScoredAffordance } from '#agency/types'
|
|
32
|
+
import {
|
|
33
|
+
scoreAffordance, competitionEntropy, stakes, collectGoalTargets,
|
|
34
|
+
DEFAULT_WEIGHTS,
|
|
35
|
+
type BiasContext, type ScoreWeights,
|
|
36
|
+
} from '#agency/selection.scoring'
|
|
37
|
+
import { readEffectiveParams, readPersonaPrior } from '#cognition/persona.prior'
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Activation margin (winner − runner-up) below which the choice is "contested" —
|
|
41
|
+
* a genuine contender sits next to the winner, so System 2 is worth recruiting.
|
|
42
|
+
* Uses the top-2 margin, NOT full-field entropy: a long tail of low-activation
|
|
43
|
+
* options (e.g. the ever-present innate floor) must not by itself trigger
|
|
44
|
+
* deliberation — only a real rival near the top does.
|
|
45
|
+
*/
|
|
46
|
+
const MARGIN_THRESHOLD = 0.06
|
|
47
|
+
/** Base stakes above which the choice is consequential enough to recruit the LLM. */
|
|
48
|
+
const BASE_STAKES_THRESHOLD = 0.60
|
|
49
|
+
/** How much a fully-proceduralized winner relaxes both gates (habit relief). */
|
|
50
|
+
const HABIT_RELIEF = 0.25
|
|
51
|
+
|
|
52
|
+
// ── Shared deliberativeness (R1) ───────────────────────────────
|
|
53
|
+
// The selector's deliberation gate and the executive's EffortGate are the two consumers
|
|
54
|
+
// of ONE deliberativeness disposition: the persona-prior on the executive's
|
|
55
|
+
// `deliberateThreshold` (analytical ↓ → deliberate more; decisiveness ↑ → less). Reading
|
|
56
|
+
// that same signal here keeps a Will from being impulsive in reasoning yet over-deliberate
|
|
57
|
+
// in action (or vice-versa). The signal is the deviation from baseline (the prior delta
|
|
58
|
+
// itself), so it is 0 — and these terms vanish — until a disposition actually develops.
|
|
59
|
+
// Each gate lives at its own native scale, so the deviation is mapped through a per-gate
|
|
60
|
+
// sensitivity (mirrors R2's "one disposition, two owners, native scales").
|
|
61
|
+
/** Margin-gate widening per unit deliberativeness (more deliberative ⇒ contest on larger margins). */
|
|
62
|
+
const DELIB_MARGIN_SENS = 0.15
|
|
63
|
+
/** Stakes-gate lowering per unit deliberativeness (more deliberative ⇒ deliberate at lower stakes). */
|
|
64
|
+
const DELIB_STAKES_SENS = 0.40
|
|
65
|
+
|
|
66
|
+
// ── Preemption (the "smarter serializer") ──────────────────────
|
|
67
|
+
/**
|
|
68
|
+
* Switch-cost hysteresis: while an action is *awaiting*, the Will re-competes
|
|
69
|
+
* every tick, but a challenger must beat the incumbent's activation by at least
|
|
70
|
+
* this to interrupt it — preventing thrashing over marginally-better options.
|
|
71
|
+
* The cost is scaled DOWN by the challenger's stakes, so a salient/urgent event
|
|
72
|
+
* (a threat) faces almost no hysteresis and overrides immediately. This mirrors
|
|
73
|
+
* basal-ganglia action selection: continuous, with a maintenance bias on the
|
|
74
|
+
* ongoing action that salient interrupts can override.
|
|
75
|
+
*
|
|
76
|
+
* This is the *fallback* base only. The live base comes from
|
|
77
|
+
* `engine-config-action-selector.switchCost` (base ⊕ persona-prior), so a
|
|
78
|
+
* conscientious Will develops a higher switch resistance — the SAME disposition
|
|
79
|
+
* the TaskSwitcher develops in attention space (R2: one switch-resistance trait,
|
|
80
|
+
* two owners at their native scales). See `effectiveSwitchCost` in react().
|
|
81
|
+
*/
|
|
82
|
+
const BASE_SWITCH_COST = 0.15
|
|
83
|
+
/**
|
|
84
|
+
* Per-focus-tick growth of switch resistance: the longer the Will has been
|
|
85
|
+
* committed (shared `task_switch.current_focus_ticks`), the costlier to be pulled
|
|
86
|
+
* off. Mirrors the TaskSwitcher's own internal `baseSwitchCost·(1+focusTicks·0.01)`
|
|
87
|
+
* so both owners harden with focus identically — mechanism timing, not a
|
|
88
|
+
* disposition, so it stays a module constant (not a persona-tunable param).
|
|
89
|
+
*/
|
|
90
|
+
const FOCUS_GAIN = 0.01
|
|
91
|
+
/** Ticks over which an awaiting action goes fully stale (matches the executor's
|
|
92
|
+
* await timeout) — a stale incumbent weakens and yields more readily. */
|
|
93
|
+
const AWAIT_STALE_TICKS = 15
|
|
94
|
+
/** How much a fully-stale awaiting incumbent's strength decays (0..1). */
|
|
95
|
+
const STALE_DECAY = 0.5
|
|
96
|
+
|
|
97
|
+
export class ActionSelector implements CognitiveEngine {
|
|
98
|
+
readonly name = 'action-selector'
|
|
99
|
+
|
|
100
|
+
private _bus: CognitiveBus | null = null
|
|
101
|
+
private _lastEntropy = 0
|
|
102
|
+
private _lastDeliberate = false
|
|
103
|
+
|
|
104
|
+
attachBus( bus: CognitiveBus ): void { this._bus = bus }
|
|
105
|
+
|
|
106
|
+
publishes(): CognitiveEventSchema[] {
|
|
107
|
+
return [
|
|
108
|
+
{ type: 'agency.selection.made', version: 1, validate: () => null },
|
|
109
|
+
{ type: 'agency.selection.ambiguous', version: 1, validate: () => null },
|
|
110
|
+
{ type: 'agency.action.preempted', version: 1, validate: () => null },
|
|
111
|
+
]
|
|
112
|
+
}
|
|
113
|
+
subscribes(): string[] { return [] }
|
|
114
|
+
onCognitiveEvent(): void { /* pull model — reads the field from frozen state */ }
|
|
115
|
+
snapshot(): Record<string, unknown> {
|
|
116
|
+
return { lastEntropy: this._lastEntropy, lastDeliberate: this._lastDeliberate }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async react(
|
|
120
|
+
_delta: Duration,
|
|
121
|
+
tick: Tick,
|
|
122
|
+
state: ReadonlySimulationState,
|
|
123
|
+
_context: SimulationContext,
|
|
124
|
+
): Promise<EngineResult> {
|
|
125
|
+
// ── Gather the field + classify the in-flight action ──────────
|
|
126
|
+
// The Will acts serially (one body). But it is not frozen while busy: an
|
|
127
|
+
// action merely *awaiting* a slow host/delivery can be PREEMPTED by a
|
|
128
|
+
// sufficiently stronger / higher-stakes want (re-compete each tick, below). A
|
|
129
|
+
// short-lived 'selected' (one tick) or a mid-composite 'expanding' is left to
|
|
130
|
+
// finish — they resolve within a tick or carry their own progress.
|
|
131
|
+
const eligible: Affordance[] = []
|
|
132
|
+
const intents: Array<{ id: string; st: string; parentIntentId?: string; activation: number; schema: string; target: string; dispatchedAt: number }> = []
|
|
133
|
+
const expandingParents = new Set<string>()
|
|
134
|
+
for( const [ id, e ] of state.entities ){
|
|
135
|
+
if( e.type === 'agency.intent' ){
|
|
136
|
+
const m = ( e.metadata ?? {} ) as Record<string, unknown>
|
|
137
|
+
const st = str( m['status'] ) ?? ''
|
|
138
|
+
if( st === 'expanding' ) expandingParents.add( id )
|
|
139
|
+
intents.push({
|
|
140
|
+
id, st,
|
|
141
|
+
parentIntentId: str( m['parentIntentId'] ),
|
|
142
|
+
activation: num( m['activation'], 0 ),
|
|
143
|
+
schema: str( m['schema'] ) ?? '',
|
|
144
|
+
target: str( m['targetEntityId'] ) ?? '',
|
|
145
|
+
dispatchedAt: num( m['dispatchedAt'], tick ),
|
|
146
|
+
})
|
|
147
|
+
continue
|
|
148
|
+
}
|
|
149
|
+
if( e.type !== 'affordance' ) continue
|
|
150
|
+
const a = readAffordance( id, e.metadata )
|
|
151
|
+
if( a.available ) eligible.push( a )
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Classify in-flight: 'awaiting' and an 'expanding' composite are PREEMPTIBLE;
|
|
155
|
+
// a standalone 'selected', an orphan macro sub (its parent already cancelled), and
|
|
156
|
+
// 'deliberating' BLOCK — let them finish (race-safe).
|
|
157
|
+
let blocking = false
|
|
158
|
+
let awaiting: { id: string; activation: number; schema: string; target: string; dispatchedAt: number } | null = null
|
|
159
|
+
let composite: { id: string; activation: number; schema: string } | null = null
|
|
160
|
+
for( const it of intents ){
|
|
161
|
+
if( it.st === 'deliberating' ) blocking = true
|
|
162
|
+
else if( it.st === 'expanding' ) composite = { id: it.id, activation: it.activation, schema: it.schema }
|
|
163
|
+
else if( it.st === 'awaiting' ) awaiting = { id: it.id, activation: it.activation, schema: it.schema, target: it.target, dispatchedAt: it.dispatchedAt }
|
|
164
|
+
else if( it.st === 'selected' ){
|
|
165
|
+
const activeMacroSub = it.parentIntentId !== undefined && expandingParents.has( it.parentIntentId )
|
|
166
|
+
if( !activeMacroSub ) blocking = true // standalone or orphan sub → finish it
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const busy = ( n: number ): EngineResult => ({ commands: { metrics: [
|
|
171
|
+
[ 'agency.field.eligible', n ], [ 'agency.selection.busy', 1 ],
|
|
172
|
+
] } })
|
|
173
|
+
|
|
174
|
+
if( blocking ) return busy( eligible.length )
|
|
175
|
+
|
|
176
|
+
// Nothing afforded → idle (if an action is in flight, keep waiting).
|
|
177
|
+
if( eligible.length === 0 )
|
|
178
|
+
return { commands: { metrics: [ [ 'agency.field.eligible', 0 ], [ 'agency.selection.busy', ( awaiting || composite ) ? 1 : 0 ] ] } }
|
|
179
|
+
|
|
180
|
+
// ── Run the competition over the current field ────────────────
|
|
181
|
+
const bias = buildBias( state )
|
|
182
|
+
// Switch resistance = f(focus duration, conscientiousness): the persona-developed
|
|
183
|
+
// base (engine-config-action-selector.switchCost ⊕ prior) hardened by how long the
|
|
184
|
+
// Will has been committed (shared task_switch.current_focus_ticks). Computed once;
|
|
185
|
+
// both preemption paths scale it DOWN by the challenger's stakes.
|
|
186
|
+
const effSwitchCost = effectiveSwitchCost( state )
|
|
187
|
+
// Competition weights (base ⊕ prior): a steadier Will weighs risk less (bolder), an
|
|
188
|
+
// open Will weighs novelty more (curiosity pulls toward the unpracticed). Other weights
|
|
189
|
+
// stay at DEFAULT_WEIGHTS — only the two with a clean trait owner are developable.
|
|
190
|
+
const weights = effectiveWeights( state )
|
|
191
|
+
const scored: ScoredAffordance[] = eligible
|
|
192
|
+
.map( a => ({ affordance: a, activation: scoreAffordance( a, bias, weights ) }) )
|
|
193
|
+
.sort( ( x, y ) => y.activation - x.activation )
|
|
194
|
+
|
|
195
|
+
const winner = scored[0]
|
|
196
|
+
if( !winner ) return busy( eligible.length )
|
|
197
|
+
|
|
198
|
+
// ── Preempt a mid-composite routine (cancel-only) ─────────────
|
|
199
|
+
// A strong/high-stakes challenger CANCELS the routine: delete the parent so the
|
|
200
|
+
// executor's `_advance` guard (`if(!parent) return`) stops queueing sub-steps. We
|
|
201
|
+
// do NOT commit the challenger this tick — that would race the executor's in-tick
|
|
202
|
+
// macro-advance and double-enact. The current sub drains, then the next free tick
|
|
203
|
+
// selects. (Immediate-switch composite preemption needs deferred macro-advance.)
|
|
204
|
+
if( composite ){
|
|
205
|
+
const different = winner.affordance.schema !== composite.schema
|
|
206
|
+
const switchCost = effSwitchCost * ( 1 - stakes( winner.affordance, bias ) )
|
|
207
|
+
if( different && winner.activation > composite.activation + switchCost ){
|
|
208
|
+
if( this._bus ) try {
|
|
209
|
+
this._bus.publish({ type: 'agency.action.preempted', version: 1, sourceEngine: this.name,
|
|
210
|
+
salience: 0.8, payload: { from: composite.schema, to: winner.affordance.schema, activation: winner.activation, tick } })
|
|
211
|
+
} catch( err ){ logger.warn( `[selector] preempt publish failed: ${ err instanceof Error ? err.message : String( err ) }` ) }
|
|
212
|
+
return { commands: { delete: [ composite.id ], metrics: [
|
|
213
|
+
[ 'agency.field.eligible', eligible.length ],
|
|
214
|
+
[ 'agency.selection.busy', 1 ],
|
|
215
|
+
[ 'agency.selection.preempted', 1 ],
|
|
216
|
+
] } }
|
|
217
|
+
}
|
|
218
|
+
return busy( eligible.length ) // routine continues
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ── Preempt an awaiting action (commit the challenger) ────────
|
|
222
|
+
// The incumbent carries a switch-cost bonus (hysteresis); a high-stakes challenger
|
|
223
|
+
// faces almost none, so salient events interrupt. Deleting an 'awaiting' intent is
|
|
224
|
+
// race-free: the executor never enacts it (only times it out at 15 ticks).
|
|
225
|
+
let preemptDelete: string | undefined
|
|
226
|
+
let preemptedFrom: string | undefined
|
|
227
|
+
if( awaiting ){
|
|
228
|
+
const sameAction = winner.affordance.schema === awaiting.schema
|
|
229
|
+
&& ( winner.affordance.targetEntityId ?? '' ) === awaiting.target
|
|
230
|
+
if( sameAction ) return busy( eligible.length ) // field still favours what we await
|
|
231
|
+
|
|
232
|
+
const staleness = Math.min( 1, ( tick - awaiting.dispatchedAt ) / AWAIT_STALE_TICKS )
|
|
233
|
+
const incumbentStrength = awaiting.activation * ( 1 - staleness * STALE_DECAY )
|
|
234
|
+
const switchCost = effSwitchCost * ( 1 - stakes( winner.affordance, bias ) )
|
|
235
|
+
|
|
236
|
+
if( winner.activation <= incumbentStrength + switchCost )
|
|
237
|
+
return busy( eligible.length ) // not worth interrupting — keep waiting
|
|
238
|
+
|
|
239
|
+
preemptDelete = awaiting.id // PREEMPT — fall through and commit the challenger
|
|
240
|
+
preemptedFrom = awaiting.schema
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ── Commit the winner (fresh selection or preempting challenger) ──
|
|
244
|
+
const entropy = competitionEntropy( scored.map( s => s.activation ) ) // telemetry only
|
|
245
|
+
const second = scored[1]?.activation
|
|
246
|
+
const margin = second !== undefined ? winner.activation - second : 1
|
|
247
|
+
const stk = stakes( winner.affordance, bias )
|
|
248
|
+
const habit = winner.affordance.habitStrength
|
|
249
|
+
const relief = Math.min( 1, habit ) * HABIT_RELIEF
|
|
250
|
+
|
|
251
|
+
// Recruit System 2 when the top choice is genuinely contested (close runner-up)
|
|
252
|
+
// or consequential. A strong habit relaxes both gates (the gradient); the shared
|
|
253
|
+
// deliberativeness disposition (R1) shifts both gates the OTHER way for an analytical
|
|
254
|
+
// Will — wider margin gate (contest sooner) and lower stakes gate (deliberate at lower
|
|
255
|
+
// stakes). The two forces compose additively, each independent and bounded.
|
|
256
|
+
const deliberativeness = -( readPersonaPrior( state, 'engine-config-executive' )['deliberateThreshold'] ?? 0 )
|
|
257
|
+
const marginGate = Math.max( 0, MARGIN_THRESHOLD - relief + deliberativeness * DELIB_MARGIN_SENS )
|
|
258
|
+
const stakesGate = clamp01( BASE_STAKES_THRESHOLD + relief - deliberativeness * DELIB_STAKES_SENS )
|
|
259
|
+
const deliberate = margin < marginGate || stk > stakesGate
|
|
260
|
+
|
|
261
|
+
this._lastEntropy = entropy
|
|
262
|
+
this._lastDeliberate = deliberate
|
|
263
|
+
|
|
264
|
+
const intent: EntityInput = {
|
|
265
|
+
id: `agency-intent-${ tick }`,
|
|
266
|
+
type: 'agency.intent',
|
|
267
|
+
metadata: {
|
|
268
|
+
schema: winner.affordance.schema,
|
|
269
|
+
affordanceId: winner.affordance.id,
|
|
270
|
+
targetEntityId: winner.affordance.targetEntityId,
|
|
271
|
+
parameters: winner.affordance.parameters,
|
|
272
|
+
source: winner.affordance.source,
|
|
273
|
+
// Plan provenance (when a plan's frontier-step prior won the competition) —
|
|
274
|
+
// flows through the executor's action.outcome so the PlanningEngine advances.
|
|
275
|
+
...( winner.affordance.planId ? { planId: winner.affordance.planId } : {} ),
|
|
276
|
+
...( winner.affordance.stepId ? { stepId: winner.affordance.stepId } : {} ),
|
|
277
|
+
// forward-model priors carried so the executor can emit an efference copy
|
|
278
|
+
// without depending on the (now-cleared) transient affordance entity.
|
|
279
|
+
expectedReward: winner.affordance.expectedReward,
|
|
280
|
+
expectedValence: winner.affordance.expectedValence,
|
|
281
|
+
habitStrength: winner.affordance.habitStrength,
|
|
282
|
+
activation: winner.activation,
|
|
283
|
+
entropy,
|
|
284
|
+
stakes: stk,
|
|
285
|
+
deliberate,
|
|
286
|
+
// Ambiguous/high-stakes → hand to the Deliberator (System 2), which resolves
|
|
287
|
+
// it back to 'selected'. The executor ignores 'deliberating' intents. The
|
|
288
|
+
// candidate set is carried so the Deliberator can choose without the field.
|
|
289
|
+
status: deliberate ? 'deliberating' : 'selected',
|
|
290
|
+
candidates: deliberate
|
|
291
|
+
? scored.slice( 0, 3 ).map( s => ({
|
|
292
|
+
schema: s.affordance.schema,
|
|
293
|
+
targetEntityId: s.affordance.targetEntityId,
|
|
294
|
+
parameters: s.affordance.parameters,
|
|
295
|
+
activation: s.activation,
|
|
296
|
+
// Channel B: flag a candidate that is an active plan's frontier step, so
|
|
297
|
+
// the deliberation facet can own "this is my plan's next step" in-character.
|
|
298
|
+
...( s.affordance.source === 'plan' ? { fromPlan: true } : {} ),
|
|
299
|
+
}) )
|
|
300
|
+
: undefined,
|
|
301
|
+
// Channel B: a preempting challenger that's ALSO deliberating carries what it
|
|
302
|
+
// interrupted, so the deliberation facet can own the interruption in-character
|
|
303
|
+
// ("I was about to X, but this pulled me away"). Only meaningful while deliberating.
|
|
304
|
+
...( preemptedFrom ? { preemptedFrom } : {} ),
|
|
305
|
+
tick,
|
|
306
|
+
},
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if( this._bus ){
|
|
310
|
+
try {
|
|
311
|
+
this._bus.publish({
|
|
312
|
+
type: 'agency.selection.made',
|
|
313
|
+
version: 1,
|
|
314
|
+
sourceEngine: this.name,
|
|
315
|
+
salience: 0.6,
|
|
316
|
+
payload: { schema: winner.affordance.schema, activation: winner.activation, entropy, tick },
|
|
317
|
+
})
|
|
318
|
+
if( deliberate )
|
|
319
|
+
this._bus.publish({
|
|
320
|
+
type: 'agency.selection.ambiguous',
|
|
321
|
+
version: 1,
|
|
322
|
+
sourceEngine: this.name,
|
|
323
|
+
salience: 0.75,
|
|
324
|
+
payload: {
|
|
325
|
+
topSchemas: scored.slice( 0, 3 ).map( s => s.affordance.schema ),
|
|
326
|
+
entropy, stakes: stk, tick,
|
|
327
|
+
},
|
|
328
|
+
})
|
|
329
|
+
if( preemptedFrom )
|
|
330
|
+
this._bus.publish({
|
|
331
|
+
type: 'agency.action.preempted',
|
|
332
|
+
version: 1,
|
|
333
|
+
sourceEngine: this.name,
|
|
334
|
+
salience: 0.8,
|
|
335
|
+
payload: { from: preemptedFrom, to: winner.affordance.schema, activation: winner.activation, tick },
|
|
336
|
+
})
|
|
337
|
+
}
|
|
338
|
+
catch( err ){
|
|
339
|
+
logger.warn( `[selector] bus publish failed: ${ err instanceof Error ? err.message : String( err ) }` )
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const commands: StateCommands = {
|
|
344
|
+
set: [ intent ],
|
|
345
|
+
...( preemptDelete ? { delete: [ preemptDelete ] } : {} ),
|
|
346
|
+
metrics: [
|
|
347
|
+
[ 'agency.field.eligible', eligible.length ],
|
|
348
|
+
[ 'agency.selection.busy', 0 ],
|
|
349
|
+
[ 'agency.selection.entropy', entropy ],
|
|
350
|
+
[ 'agency.selection.margin', margin ],
|
|
351
|
+
[ 'agency.selection.deliberate', deliberate ? 1 : 0 ],
|
|
352
|
+
[ 'agency.selection.preempted', preemptedFrom ? 1 : 0 ],
|
|
353
|
+
[ 'agency.selection.activation', winner.activation ],
|
|
354
|
+
],
|
|
355
|
+
}
|
|
356
|
+
return { commands }
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// ─── switch resistance ───────────────────────────────────────────────────────
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Effective preemption hysteresis = persona-developed base ⊕ focus hardening.
|
|
364
|
+
*
|
|
365
|
+
* R2 reconciliation: the selector and the TaskSwitcher are the two owners of one
|
|
366
|
+
* "switch resistance" disposition. They live at different scales (activation vs.
|
|
367
|
+
* goal-priority), so the selector keeps its own `switchCost` base — but develops
|
|
368
|
+
* it from the SAME conscientiousness driver and hardens it with the SAME focus
|
|
369
|
+
* signal (`task_switch.current_focus_ticks`) using the SAME formula shape the
|
|
370
|
+
* TaskSwitcher uses internally. One disposition, two owners, native scales.
|
|
371
|
+
*/
|
|
372
|
+
function effectiveSwitchCost( state: ReadonlySimulationState ): number {
|
|
373
|
+
const params = readEffectiveParams( state, 'engine-config-action-selector' )
|
|
374
|
+
const base = num( params['switchCost'], BASE_SWITCH_COST )
|
|
375
|
+
const focusTicks = metric( state, 'task_switch.current_focus_ticks', 0 )
|
|
376
|
+
return base * ( 1 + focusTicks * FOCUS_GAIN )
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Competition weights = DEFAULT_WEIGHTS with the two trait-owned ones (risk, novelty)
|
|
381
|
+
* overridden by their developed values (base ⊕ prior). Negative weights are clamped to
|
|
382
|
+
* 0 (a prior can soften a weight to indifference but never invert its sign). The other
|
|
383
|
+
* weights have no clean single-trait owner, so they stay fixed (see the TODO catalogue).
|
|
384
|
+
*/
|
|
385
|
+
function effectiveWeights( state: ReadonlySimulationState ): ScoreWeights {
|
|
386
|
+
const p = readEffectiveParams( state, 'engine-config-action-selector' )
|
|
387
|
+
return {
|
|
388
|
+
...DEFAULT_WEIGHTS,
|
|
389
|
+
risk: Math.max( 0, num( p['riskWeight'], DEFAULT_WEIGHTS.risk ) ),
|
|
390
|
+
novelty: Math.max( 0, num( p['noveltyWeight'], DEFAULT_WEIGHTS.novelty ) ),
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// ─── bias assembly ───────────────────────────────────────────────────────────
|
|
395
|
+
|
|
396
|
+
function buildBias( state: ReadonlySimulationState ): BiasContext {
|
|
397
|
+
// Entity links recognized from `targetEntityId`/`requestingEntityId` metadata AND
|
|
398
|
+
// `keid:` tags — shared with the synthesizer via collectGoalTargets so both stages
|
|
399
|
+
// see the same goal→entity links (incl. KnownEntityTracker's curiosity goals).
|
|
400
|
+
const goalTargets = new Set<string>( collectGoalTargets( state ).keys() )
|
|
401
|
+
let maxGoalPriority = 0
|
|
402
|
+
|
|
403
|
+
for( const e of state.entities.values() ){
|
|
404
|
+
if( e.type !== 'goal' ) continue
|
|
405
|
+
const m = e.metadata
|
|
406
|
+
const status = str( m?.['status'] )
|
|
407
|
+
if( status !== 'active' && status !== 'in_progress' ) continue
|
|
408
|
+
maxGoalPriority = Math.max( maxGoalPriority, num( m?.['priority'], 0 ) )
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return {
|
|
412
|
+
goalTargets,
|
|
413
|
+
maxGoalPriority: clamp01( maxGoalPriority ),
|
|
414
|
+
drives: {
|
|
415
|
+
energy: clamp01( ( 100 - metric( state, 'energy.level', 100 ) ) / 100 ),
|
|
416
|
+
sleep: clamp01( metric( state, 'sleep.pressure', 0 ) / 100 ),
|
|
417
|
+
stress: clamp01( metric( state, 'stress.load', 0 ) / 100 ),
|
|
418
|
+
social: clamp01( metric( state, 'drive.social', 0 ) ),
|
|
419
|
+
},
|
|
420
|
+
threat: clamp01( metric( state, 'threat.level', 0 ) ),
|
|
421
|
+
inhibition: clamp01( metric( state, 'inhibition.level', 0 ) ),
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// ─── entity decoding ─────────────────────────────────────────────────────────
|
|
426
|
+
|
|
427
|
+
function readAffordance( id: string, m: ReadonlyMap<string, unknown> | Record<string, unknown> | undefined ): Affordance {
|
|
428
|
+
const meta = ( m ?? {} ) as Record<string, unknown>
|
|
429
|
+
return {
|
|
430
|
+
id,
|
|
431
|
+
schema: str( meta['schema'] ) ?? '',
|
|
432
|
+
source: ( str( meta['source'] ) as AffordanceSource ) ?? 'innate',
|
|
433
|
+
parameters: ( meta['parameters'] as Record<string, unknown> ) ?? {},
|
|
434
|
+
targetEntityId: str( meta['targetEntityId'] ),
|
|
435
|
+
evokedBy: str( meta['evokedBy'] ),
|
|
436
|
+
expectedValence: num( meta['expectedValence'], 0 ),
|
|
437
|
+
expectedReward: num( meta['expectedReward'], 0 ),
|
|
438
|
+
cost: num( meta['cost'], 0 ),
|
|
439
|
+
habitStrength: num( meta['habitStrength'], 0 ),
|
|
440
|
+
available: meta['available'] === true,
|
|
441
|
+
tags: Array.isArray( meta['tags'] ) ? ( meta['tags'] as unknown[] ).filter( ( t ): t is string => typeof t === 'string' ) : [],
|
|
442
|
+
planBias: typeof meta['planBias'] === 'number' ? ( meta['planBias'] as number ) : undefined,
|
|
443
|
+
planId: str( meta['planId'] ),
|
|
444
|
+
stepId: str( meta['stepId'] ),
|
|
445
|
+
tick: num( meta['tick'], 0 ),
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// ─── helpers ─────────────────────────────────────────────────────────────────
|
|
450
|
+
|
|
451
|
+
function metric( state: ReadonlySimulationState, key: string, fallback: number ): number {
|
|
452
|
+
return state.metrics.get( key ) ?? fallback
|
|
453
|
+
}
|
|
454
|
+
function num( v: unknown, fallback: number ): number {
|
|
455
|
+
return typeof v === 'number' && Number.isFinite( v ) ? v : fallback
|
|
456
|
+
}
|
|
457
|
+
function str( v: unknown ): string | undefined {
|
|
458
|
+
return typeof v === 'string' ? v : undefined
|
|
459
|
+
}
|
|
460
|
+
function clamp01( n: number ): number {
|
|
461
|
+
return n < 0 ? 0 : n > 1 ? 1 : n
|
|
462
|
+
}
|