@mindot/will 0.6.0 → 0.8.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 +1 -1
- package/dist/channels/whatsapp.d.ts +1 -1
- package/dist/cli.js +11104 -10312
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +972 -213
- package/dist/index.js.map +1 -1
- package/dist/mcp/effectors.d.ts +1 -1
- package/dist/{will-Bikuk4s2.d.ts → will-cS6k4uiJ.d.ts} +510 -86
- package/package.json +1 -1
- package/src/cognition/agency/engines/action.selector.ts +42 -9
- package/src/cognition/agency/engines/affordance.synthesizer.ts +9 -0
- package/src/cognition/agency/engines/motor.schema.executor.ts +4 -0
- package/src/cognition/agency/engines/reafference.engine.ts +40 -5
- package/src/cognition/agency/reconcile.learning.ts +23 -0
- package/src/cognition/agency/schemas/repertoire.ts +114 -7
- package/src/cognition/agency/selection.scoring.ts +7 -1
- package/src/cognition/agency/types.ts +9 -0
- package/src/cognition/config.mirror.entities.ts +1 -1
- package/src/cognition/faculties/executive.engine/engine.ts +136 -58
- package/src/cognition/faculties/executive.engine/facet.ts +10 -2
- package/src/cognition/faculties/executive.engine/prompt.factory.ts +2 -1
- package/src/cognition/index.ts +4 -0
- package/src/cognition/memory/vector.embedder.ts +9 -5
- package/src/cognition/utilities/token.tracker.ts +191 -96
- package/src/host/boot.ts +78 -22
- package/src/index.ts +35 -0
- package/src/llm/index.ts +397 -96
- package/src/llm/routing.ts +198 -0
- package/src/llm/summarizer.ts +5 -1
- package/src/runners/thin-shim.runner.ts +18 -6
- package/src/sdk/will.ts +82 -16
- package/src/stem/guards/identity.coherence.ts +17 -6
- package/src/stem/index.ts +18 -3
- package/src/stem/mind.ts +155 -24
- package/src/stem/policy/arbiter.ts +171 -0
- package/src/stem/policy/rule.table.ts +172 -0
- package/src/stem/policy/verdict.recorder.ts +0 -0
- package/src/stem/tracts/effector.controller.ts +426 -0
package/package.json
CHANGED
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
import { readEffectiveParams, readPersonaPrior } from '#cognition/persona.prior'
|
|
39
39
|
import { RUPTURE_REVOKE_GATE, revocationEntity } from '#agency/revocation'
|
|
40
40
|
import { liveConsequences, matchConsequenceText } from '#agency/consequence'
|
|
41
|
+
import { asFinality } from '#stem/policy/arbiter'
|
|
41
42
|
|
|
42
43
|
/**
|
|
43
44
|
* Activation margin (winner − runner-up) below which the choice is "contested" —
|
|
@@ -294,19 +295,26 @@ export class ActionSelector implements CognitiveEngine {
|
|
|
294
295
|
}
|
|
295
296
|
})
|
|
296
297
|
|
|
297
|
-
// ── Commitment revocation (EXAFFERENCE P4)
|
|
298
|
-
// A
|
|
299
|
-
//
|
|
300
|
-
//
|
|
301
|
-
//
|
|
302
|
-
//
|
|
303
|
-
|
|
298
|
+
// ── Commitment revocation (EXAFFERENCE P4 · POLICY_REAFFERENCE P3) ──
|
|
299
|
+
// A commitment still being weighed is let go for either of two DISTINCT
|
|
300
|
+
// reasons: a hard exafferent rupture (the world surprised us), or a class
|
|
301
|
+
// policy refusal of the very schema we're deliberating (the boundary just
|
|
302
|
+
// declared it forbidden — deliberating our way into it is wasted). The two
|
|
303
|
+
// never mix: the refusal is an outcome, not a percept, so it contributes
|
|
304
|
+
// ZERO to the exafferent scalar. Either way we can't delete the
|
|
305
|
+
// `deliberating` intent here (Deliberation runs after us and would resurrect
|
|
306
|
+
// it set-after-delete), so we drop a tombstone the Deliberation engine +
|
|
307
|
+
// Executor honor next tick. No successor is committed — the field re-forms.
|
|
308
|
+
const policyRevoke = !!deliberating && refusedClassSchemas( state ).has( deliberating.schema )
|
|
309
|
+
if( deliberating && ( rupture >= RUPTURE_REVOKE_GATE || policyRevoke ) ){
|
|
310
|
+
const reason = policyRevoke ? 'policy-refusal' : 'exafferent-rupture'
|
|
311
|
+
const revRupture = policyRevoke ? Math.max( rupture, RUPTURE_REVOKE_GATE ) : rupture
|
|
304
312
|
if( this._bus ){
|
|
305
313
|
try {
|
|
306
314
|
this._bus.publish({
|
|
307
315
|
type: 'agency.commitment.revoked', version: 1, sourceEngine: this.name,
|
|
308
316
|
salience: 0.85,
|
|
309
|
-
payload: { from: deliberating.schema, reason
|
|
317
|
+
payload: { from: deliberating.schema, reason, rupture: revRupture, tick },
|
|
310
318
|
})
|
|
311
319
|
}
|
|
312
320
|
catch( err ){ logger.warn(`[selector] revoked publish failed: ${ err instanceof Error ? err.message : String( err ) }`) }
|
|
@@ -314,11 +322,12 @@ export class ActionSelector implements CognitiveEngine {
|
|
|
314
322
|
this._lastRevoked = { schema: deliberating.schema, tick } // Channel-B: flavor the next deliberation
|
|
315
323
|
return {
|
|
316
324
|
commands: {
|
|
317
|
-
set: [ revocationEntity( deliberating.id, deliberating.schema,
|
|
325
|
+
set: [ revocationEntity( deliberating.id, deliberating.schema, revRupture, tick ) ],
|
|
318
326
|
metrics: [
|
|
319
327
|
[ 'agency.field.eligible', eligible.length ],
|
|
320
328
|
[ 'agency.selection.busy', 1 ],
|
|
321
329
|
[ 'agency.commitment.revoked', 1 ],
|
|
330
|
+
...( policyRevoke ? [ [ 'agency.policy.revoked', 1 ] as [ string, number ] ] : [] ),
|
|
322
331
|
...stabMetrics,
|
|
323
332
|
],
|
|
324
333
|
},
|
|
@@ -632,6 +641,30 @@ function computeRupture(
|
|
|
632
641
|
return clamp01( ( maxSalience - RUPTURE_SALIENCE_GATE ) / ( 1 - RUPTURE_SALIENCE_GATE ) )
|
|
633
642
|
}
|
|
634
643
|
|
|
644
|
+
/**
|
|
645
|
+
* POLICY_REAFFERENCE P3 — schemas hit by a CLASS-final policy refusal visible in
|
|
646
|
+
* frozen state this tick (a refused `agency.outcome` lives exactly one tick).
|
|
647
|
+
*
|
|
648
|
+
* A refusal is an `agency.outcome`, never a `percept`, so it can NEVER feed
|
|
649
|
+
* computeRupture: the mind cannot rupture itself with its own boundary. This is
|
|
650
|
+
* a SEPARATE, explicit trigger — "the boundary just declared this forbidden, let
|
|
651
|
+
* go of any commitment I'm still weighing toward it" — distinct from a
|
|
652
|
+
* world-surprise exafferent rupture, and it carries its own revocation reason.
|
|
653
|
+
* Only `class` finality qualifies: an `instance` refusal means "not with those
|
|
654
|
+
* parameters", not "never", so a still-deliberating attempt may yet succeed.
|
|
655
|
+
*/
|
|
656
|
+
function refusedClassSchemas( state: ReadonlySimulationState ): Set<string> {
|
|
657
|
+
const out = new Set<string>()
|
|
658
|
+
for( const e of state.entities.values() ){
|
|
659
|
+
if( e.type !== 'agency.outcome') continue
|
|
660
|
+
const m = e.metadata
|
|
661
|
+
if( m?.['refused'] !== true || asFinality( m?.['finality'] ) !== 'class') continue
|
|
662
|
+
const schema = str( m?.['schema'] )
|
|
663
|
+
if( schema ) out.add( schema )
|
|
664
|
+
}
|
|
665
|
+
return out
|
|
666
|
+
}
|
|
667
|
+
|
|
635
668
|
/**
|
|
636
669
|
* Competition weights = DEFAULT_WEIGHTS with the two trait-owned ones (risk, novelty)
|
|
637
670
|
* overridden by their developed values (base ⊕ prior). Negative weights are clamped to
|
|
@@ -103,6 +103,9 @@ export class AffordanceSynthesizer implements CognitiveEngine {
|
|
|
103
103
|
// means the executor, which ticks later in the SAME tick, sees a whole
|
|
104
104
|
// repertoire and can expand a restored mid-flight macro. Idempotent.
|
|
105
105
|
this._repertoire?.restoreComposites( state.entities )
|
|
106
|
+
// Availability entries (P2) rehydrate the same way, so a restored Will keeps
|
|
107
|
+
// its learned suppressions instead of re-probing forbidden abilities.
|
|
108
|
+
this._repertoire?.restoreAvailability( state.entities )
|
|
106
109
|
|
|
107
110
|
const schemas = this._repertoire?.schemas() ?? this._schemas
|
|
108
111
|
const skills = this._skills?.() ?? this._repertoire?.skills() ?? null
|
|
@@ -306,6 +309,10 @@ export class AffordanceSynthesizer implements CognitiveEngine {
|
|
|
306
309
|
): Affordance {
|
|
307
310
|
const skill = skills?.get( schema.id )
|
|
308
311
|
|
|
312
|
+
// Policy availability (P2): omitted when fully available (1), so a never-refused
|
|
313
|
+
// Will's affordance field is byte-identical. Present only once a refusal dented it.
|
|
314
|
+
const availability = this._repertoire?.availabilityOf( schema.id ) ?? 1
|
|
315
|
+
|
|
309
316
|
// Learned value if known, else the schema's intrinsic prior mapped to 0..1.
|
|
310
317
|
const expectedReward = skill?.valueEstimate ?? clamp01( ( ( schema.baseValence ?? 0 ) + 1 ) / 2 )
|
|
311
318
|
const expectedValence = schema.baseValence ?? valence
|
|
@@ -333,6 +340,7 @@ export class AffordanceSynthesizer implements CognitiveEngine {
|
|
|
333
340
|
available: this._available( schema.preconditions, ( k ) => metric( state, k, 0 ) ),
|
|
334
341
|
tags: schema.tags ?? [],
|
|
335
342
|
...( schema.description ? { description: schema.description } : {} ),
|
|
343
|
+
...( availability < 1 ? { availability } : {} ),
|
|
336
344
|
planBias: ctx.planBias,
|
|
337
345
|
planId: ctx.planId,
|
|
338
346
|
stepId: ctx.stepId,
|
|
@@ -357,6 +365,7 @@ export class AffordanceSynthesizer implements CognitiveEngine {
|
|
|
357
365
|
available: a.available,
|
|
358
366
|
tags: a.tags,
|
|
359
367
|
description: a.description,
|
|
368
|
+
...( a.availability !== undefined ? { availability: a.availability } : {} ),
|
|
360
369
|
planBias: a.planBias,
|
|
361
370
|
planId: a.planId,
|
|
362
371
|
stepId: a.stepId,
|
|
@@ -179,6 +179,10 @@ export class MotorSchemaExecutor implements CognitiveEngine {
|
|
|
179
179
|
// outcome (which also teaches reafference the action is unreliable here).
|
|
180
180
|
for( const [ id, e ] of state.entities ){
|
|
181
181
|
if( e.type !== 'agency.intent' || str( e.metadata?.['status'] ) !== 'awaiting') continue
|
|
182
|
+
// POLICY_REAFFERENCE P4 — an escalated intent is HELD: the stem owns its
|
|
183
|
+
// lifecycle (extended TTL → approve/deny/expire), so the executor must not
|
|
184
|
+
// time it out at AWAIT_TIMEOUT and reconcile it as a phantom failure.
|
|
185
|
+
if( e.metadata?.['escalated'] === true ) continue
|
|
182
186
|
const dispatchedAt = num( e.metadata?.['dispatchedAt'], tick )
|
|
183
187
|
if( tick - dispatchedAt < AWAIT_TIMEOUT ) continue
|
|
184
188
|
|
|
@@ -28,7 +28,8 @@ import type { CognitiveBus, CognitiveEvent } from '#cognition/bus'
|
|
|
28
28
|
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
|
-
import { schemaEntityId } from '#agency/schemas/repertoire'
|
|
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'
|
|
33
34
|
|
|
34
35
|
const PROC_THRESHOLD = 0.60 // mirror of repertoire's threshold for the habitual-count metric
|
|
@@ -190,10 +191,37 @@ export class ReafferenceEngine implements CognitiveEngine {
|
|
|
190
191
|
// ── 1. Fold each outcome into its skill ──────────────────────
|
|
191
192
|
let updates = 0
|
|
192
193
|
let discovered = 0
|
|
194
|
+
let refused = 0
|
|
193
195
|
for( const { id, meta: m, fromState } of outcomes ){
|
|
194
196
|
const schema = str( m['schema'] )
|
|
195
197
|
if( !schema ){ if( fromState ) del.push( id ); continue }
|
|
196
198
|
|
|
199
|
+
// POLICY_REAFFERENCE P2 — a refusal is NOT a failure. Route it to the
|
|
200
|
+
// availability layer and stop: it must never touch LearnedSkill (value,
|
|
201
|
+
// habit, param priors), or the Will learns it is unskilled at something it
|
|
202
|
+
// is merely forbidden to do. The awaiting intent is still freed, and a
|
|
203
|
+
// refused plan step is signalled unsuccessful so the plan doesn't hang.
|
|
204
|
+
if( m['refused'] === true ){
|
|
205
|
+
const finality = asFinality( m['finality'] )
|
|
206
|
+
|
|
207
|
+
// P5 — 'context' means the refusal was NOT ABOUT THE ACTION (tainted
|
|
208
|
+
// context, an unavailable dependency, the arbiter itself down). Denting
|
|
209
|
+
// availability here would teach a lesson the policy never taught:
|
|
210
|
+
// "reach for this less", when nothing about the ability was the problem.
|
|
211
|
+
// So the intent is freed and the plan step signalled — the mind must not
|
|
212
|
+
// hang — and NOTHING else is written. Not competence, not availability.
|
|
213
|
+
if( finality !== 'context')
|
|
214
|
+
this._repertoire.recordRefusal( schema, finality, tick )
|
|
215
|
+
|
|
216
|
+
if( fromState ) del.push( id )
|
|
217
|
+
const refusedIntent = str( m['intentId'] )
|
|
218
|
+
if( refusedIntent ) del.push( refusedIntent )
|
|
219
|
+
const refusedPlan = str( m['planId'] )
|
|
220
|
+
if( refusedPlan ) this._emitPlanOutcome( refusedPlan, str( m['stepId'] ), schema, false, 0, 0, tick )
|
|
221
|
+
refused++
|
|
222
|
+
continue
|
|
223
|
+
}
|
|
224
|
+
|
|
197
225
|
const { skill, proceduralized } = this._repertoire.recordOutcome({
|
|
198
226
|
schema,
|
|
199
227
|
success: m['success'] === true,
|
|
@@ -235,19 +263,23 @@ export class ReafferenceEngine implements CognitiveEngine {
|
|
|
235
263
|
}
|
|
236
264
|
}
|
|
237
265
|
|
|
238
|
-
// ── 2. Forgetting curve over the competence layer
|
|
266
|
+
// ── 2. Forgetting curve over the competence layer + availability recovery ──
|
|
239
267
|
const dropped = this._repertoire.decay( tick )
|
|
240
|
-
for( const id of dropped ){
|
|
268
|
+
for( const id of dropped.skills ){
|
|
241
269
|
del.push(`agency-skill-${ id }`)
|
|
242
270
|
del.push( schemaEntityId( id ) ) // composite mirror (harmless no-op for primitives)
|
|
243
271
|
}
|
|
272
|
+
for( const id of dropped.availability )
|
|
273
|
+
del.push( availabilityEntityId( id ) ) // fully-recovered ⇒ remove the mirror
|
|
244
274
|
|
|
245
|
-
// ── 3. Mirror learned composite templates
|
|
275
|
+
// ── 3. Mirror learned composite templates + availability ─────
|
|
246
276
|
// Skills become `agency.skill` (above); the invented composite *definitions*
|
|
247
277
|
// must travel too, or a snapshot/restore brings back a skill whose schema is
|
|
248
278
|
// gone and the executor can't expand it. Idempotent re-write each tick, like
|
|
249
279
|
// GoalManager._persistGoals. Empty until a composite is actually learned.
|
|
250
|
-
for( const e of this._repertoire.compositeEntities() )
|
|
280
|
+
for( const e of this._repertoire.compositeEntities() ) set.push( e )
|
|
281
|
+
// Availability entries (P2) mirror the same way — empty until a refusal lands.
|
|
282
|
+
for( const e of this._repertoire.availabilityEntities() ) set.push( e )
|
|
251
283
|
|
|
252
284
|
// ── 4. Telemetry ─────────────────────────────────────────────
|
|
253
285
|
const skills = this._repertoire.skills()
|
|
@@ -259,6 +291,9 @@ export class ReafferenceEngine implements CognitiveEngine {
|
|
|
259
291
|
[ 'agency.habitual.count', habitual ],
|
|
260
292
|
[ 'agency.sensory.confirmed', sensory ],
|
|
261
293
|
)
|
|
294
|
+
// Only emit the refusal metric when it fired — a never-refused Will writes
|
|
295
|
+
// nothing here, preserving the byte-identical quiet path (cf. EXAFFERENCE P3).
|
|
296
|
+
if( refused > 0 ) metrics.push([ 'agency.refused.count', refused ])
|
|
262
297
|
|
|
263
298
|
return { commands: { set, delete: del, metrics } }
|
|
264
299
|
}
|
|
@@ -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
|
|
@@ -24,6 +25,24 @@ export interface HostAckResult {
|
|
|
24
25
|
/** −1..1 felt valence of the outcome. */
|
|
25
26
|
valence?: number
|
|
26
27
|
description?: string
|
|
28
|
+
/**
|
|
29
|
+
* POLICY_REAFFERENCE P2 — this ack is a policy REFUSAL, not a world failure.
|
|
30
|
+
* The ReafferenceEngine routes it to the availability layer (NOT competence):
|
|
31
|
+
* a refusal must never teach the Will it is unskilled at something it is merely
|
|
32
|
+
* forbidden to do. `finality` decides how hard availability is cut.
|
|
33
|
+
*/
|
|
34
|
+
refused?: boolean
|
|
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
|
|
27
46
|
}
|
|
28
47
|
|
|
29
48
|
/**
|
|
@@ -71,6 +90,10 @@ export function reconcileInvocation(
|
|
|
71
90
|
mode: 'external',
|
|
72
91
|
tick,
|
|
73
92
|
reconciled: true,
|
|
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 } : {} ),
|
|
74
97
|
...( provenance.planId ? { planId: provenance.planId } : {} ),
|
|
75
98
|
...( provenance.stepId ? { stepId: provenance.stepId } : {} ),
|
|
76
99
|
},
|
|
@@ -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
|
|
@@ -39,6 +40,23 @@ const IDLE_TICKS = 200 // ticks of disuse before forgetting starts
|
|
|
39
40
|
const DECAY_RATE = 0.02 // habit lost per decay application
|
|
40
41
|
const DROP_HABIT = 0.05 // below this (and learned) the skill is forgotten
|
|
41
42
|
|
|
43
|
+
// ── availability layer (POLICY_REAFFERENCE P2) ────────────────
|
|
44
|
+
// Availability is NOT competence. It answers "may I use this schema", learned
|
|
45
|
+
// from policy refusals, and is kept strictly apart from LearnedSkill so a
|
|
46
|
+
// refusal never teaches the Will it is *unskilled* at something it is merely
|
|
47
|
+
// *forbidden* to do. A `class` refusal ("never, under this policy") drives
|
|
48
|
+
// availability down hard; an `instance` refusal ("not with those parameters")
|
|
49
|
+
// dents it lightly — the Will should keep reaching for the ability, just not
|
|
50
|
+
// that way. Recovery is slow but real, so a policy change is re-discoverable:
|
|
51
|
+
// availability never floors at zero, and it climbs back toward 1 with disuse of
|
|
52
|
+
// the refusal. P2 keys availability by SCHEMA; per-(schema, params) envelope
|
|
53
|
+
// narrowing is a follow-up that belongs at selection time, not fielding time.
|
|
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
|
|
56
|
+
const AVAIL_FLOOR = 0.05 // never zero — re-probe must always be possible
|
|
57
|
+
const AVAIL_RECOVERY = 0.02 // per-decay climb back toward 1
|
|
58
|
+
const AVAIL_RECOVERED = 0.999 // at/above this the entry is dropped (quiet path)
|
|
59
|
+
|
|
42
60
|
export interface OutcomeObservation {
|
|
43
61
|
schema: string
|
|
44
62
|
success: boolean
|
|
@@ -54,6 +72,9 @@ export class SchemaRepertoire {
|
|
|
54
72
|
private _skills = new Map<string, LearnedSkill>()
|
|
55
73
|
/** Tracks which templates were learned at runtime (vs innate) so decay can forget them. */
|
|
56
74
|
private _learned = new Set<string>()
|
|
75
|
+
/** Availability layer (P2): schema → { value 0..1, lastRefusedTick }. Empty until
|
|
76
|
+
* a refusal lands — a never-refused Will writes nothing here (byte-identical). */
|
|
77
|
+
private _availability = new Map<string, { value: number; lastRefusedTick: number }>()
|
|
57
78
|
|
|
58
79
|
constructor( seed: MotorSchema[] = INNATE_SCHEMAS ){
|
|
59
80
|
for( const s of seed ) this._templates.set( s.id, s )
|
|
@@ -85,6 +106,38 @@ export class SchemaRepertoire {
|
|
|
85
106
|
skills(): ReadonlyMap<string, LearnedSkill> { return this._skills }
|
|
86
107
|
getSkill( id: string ): LearnedSkill | undefined { return this._skills.get( id ) }
|
|
87
108
|
|
|
109
|
+
// ── availability (P2) ─────────────────────────────────────────
|
|
110
|
+
availability(): ReadonlyMap<string, { value: number; lastRefusedTick: number }> { return this._availability }
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* How available a schema is right now, 0..1. Absent from the ledger ⇒ 1
|
|
114
|
+
* (fully available — the common case). This is the ONLY value the
|
|
115
|
+
* AffordanceSynthesizer reads; it never touches competence.
|
|
116
|
+
*/
|
|
117
|
+
availabilityOf( schema: string ): number {
|
|
118
|
+
return this._availability.get( schema )?.value ?? 1
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Fold a policy refusal into the availability layer (NOT competence). A
|
|
123
|
+
* `class` refusal cuts availability hard; a `parameter` refusal dents it
|
|
124
|
+
* lightly. Multiplicative so repeated refusals compound toward — but never
|
|
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).
|
|
132
|
+
*/
|
|
133
|
+
recordRefusal( schema: string, finality: Exclude<DenialFinality, 'context'>, tick: number ): number {
|
|
134
|
+
const prev = this._availability.get( schema )?.value ?? 1
|
|
135
|
+
const drop = finality === 'class' ? AVAIL_DROP_CLASS : AVAIL_DROP_PARAMETER
|
|
136
|
+
const value = Math.max( AVAIL_FLOOR, prev * ( 1 - drop ) )
|
|
137
|
+
this._availability.set( schema, { value, lastRefusedTick: tick } )
|
|
138
|
+
return value
|
|
139
|
+
}
|
|
140
|
+
|
|
88
141
|
/**
|
|
89
142
|
* Fold one outcome into the schema's learned skill. Returns the updated skill
|
|
90
143
|
* and whether it just crossed the proceduralization threshold this update.
|
|
@@ -118,12 +171,14 @@ export class SchemaRepertoire {
|
|
|
118
171
|
}
|
|
119
172
|
|
|
120
173
|
/**
|
|
121
|
-
* Forgetting curve over the competence layer
|
|
122
|
-
* lose habit; learned composites
|
|
123
|
-
* entirely (template + skill).
|
|
174
|
+
* Forgetting curve over the competence layer, plus availability recovery.
|
|
175
|
+
* Skills unused for IDLE_TICKS lose habit; learned composites below DROP_HABIT
|
|
176
|
+
* are dropped entirely (template + skill). Availability entries climb back
|
|
177
|
+
* toward 1 and are dropped once fully recovered. Returns the ids that were
|
|
178
|
+
* removed from each layer so their mirrored state entities can be deleted.
|
|
124
179
|
*/
|
|
125
|
-
decay( tick: number ): string[] {
|
|
126
|
-
const
|
|
180
|
+
decay( tick: number ): { skills: string[]; availability: string[] } {
|
|
181
|
+
const skills: string[] = []
|
|
127
182
|
for( const [ id, skill ] of this._skills ){
|
|
128
183
|
if( tick - skill.lastEnactedTick <= IDLE_TICKS ) continue
|
|
129
184
|
|
|
@@ -132,12 +187,23 @@ export class SchemaRepertoire {
|
|
|
132
187
|
this._skills.delete( id )
|
|
133
188
|
this._templates.delete( id )
|
|
134
189
|
this._learned.delete( id )
|
|
135
|
-
|
|
190
|
+
skills.push( id )
|
|
136
191
|
continue
|
|
137
192
|
}
|
|
138
193
|
this._skills.set( id, { ...skill, habitStrength } )
|
|
139
194
|
}
|
|
140
|
-
|
|
195
|
+
|
|
196
|
+
// Availability recovery (P2): each entry climbs slowly back toward 1, so a
|
|
197
|
+
// policy change is re-discoverable. A fully-recovered entry is dropped, so a
|
|
198
|
+
// Will that was refused long ago returns to the byte-identical quiet path.
|
|
199
|
+
const availability: string[] = []
|
|
200
|
+
for( const [ id, avail ] of this._availability ){
|
|
201
|
+
const value = avail.value + AVAIL_RECOVERY * ( 1 - avail.value )
|
|
202
|
+
if( value >= AVAIL_RECOVERED ){ this._availability.delete( id ); availability.push( id ) }
|
|
203
|
+
else this._availability.set( id, { ...avail, value } )
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return { skills, availability }
|
|
141
207
|
}
|
|
142
208
|
|
|
143
209
|
// ── PMA portability (Phase 6 reads these) ─────────────────────
|
|
@@ -188,6 +254,30 @@ export class SchemaRepertoire {
|
|
|
188
254
|
this._learned.add( s.id )
|
|
189
255
|
}
|
|
190
256
|
}
|
|
257
|
+
|
|
258
|
+
/** Availability ledger encoded as `agency.availability` state entities (P2).
|
|
259
|
+
* Empty until a refusal lands, so the quiet path writes nothing. */
|
|
260
|
+
availabilityEntities(): EntityInput[] {
|
|
261
|
+
const out: EntityInput[] = []
|
|
262
|
+
for( const [ schema, a ] of this._availability )
|
|
263
|
+
out.push( availabilityEntity( schema, a.value, a.lastRefusedTick ) )
|
|
264
|
+
return out
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** Rehydrate the availability ledger from state after a restore. Idempotent;
|
|
268
|
+
* keeps whichever value is more restrictive so a concurrent refusal isn't lost. */
|
|
269
|
+
restoreAvailability( entities: ReadonlySimulationState['entities'] ): void {
|
|
270
|
+
for( const e of entities.values() ){
|
|
271
|
+
if( e.type !== AVAILABILITY_ENTITY_TYPE ) continue
|
|
272
|
+
const m = ( e.metadata ?? {} ) as Record<string, unknown>
|
|
273
|
+
const schema = typeof m['schema'] === 'string' ? m['schema'] : ''
|
|
274
|
+
if( !schema ) continue
|
|
275
|
+
const value = typeof m['value'] === 'number' ? m['value'] : 1
|
|
276
|
+
const tick = typeof m['lastRefusedTick'] === 'number' ? m['lastRefusedTick'] : 0
|
|
277
|
+
const prev = this._availability.get( schema )
|
|
278
|
+
if( !prev || value < prev.value ) this._availability.set( schema, { value, lastRefusedTick: tick } )
|
|
279
|
+
}
|
|
280
|
+
}
|
|
191
281
|
}
|
|
192
282
|
|
|
193
283
|
// ─── helpers ─────────────────────────────────────────────────────────────────
|
|
@@ -209,6 +299,23 @@ function clamp01( n: number ): number {
|
|
|
209
299
|
return n < 0 ? 0 : n > 1 ? 1 : n
|
|
210
300
|
}
|
|
211
301
|
|
|
302
|
+
// ─── availability ⇄ state-entity codec (P2) ──────────────────────────────────
|
|
303
|
+
|
|
304
|
+
/** State-entity type for a mirrored availability entry. */
|
|
305
|
+
export const AVAILABILITY_ENTITY_TYPE = 'agency.availability'
|
|
306
|
+
|
|
307
|
+
/** Stable entity id for a schema's availability mirror (idempotent re-writes). */
|
|
308
|
+
export function availabilityEntityId( schema: string ): string { return `agency-availability-${ schema }` }
|
|
309
|
+
|
|
310
|
+
/** Encode one availability entry as a state entity. */
|
|
311
|
+
function availabilityEntity( schema: string, value: number, lastRefusedTick: number ): EntityInput {
|
|
312
|
+
return {
|
|
313
|
+
id: availabilityEntityId( schema ),
|
|
314
|
+
type: AVAILABILITY_ENTITY_TYPE,
|
|
315
|
+
metadata: { schema, value, lastRefusedTick },
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
212
319
|
// ─── composite ⇄ state-entity codec (snapshot/replay) ────────────────────────
|
|
213
320
|
|
|
214
321
|
/** State-entity type for a mirrored composite template. */
|
|
@@ -147,7 +147,7 @@ export function scoreAffordance(
|
|
|
147
147
|
bias: BiasContext,
|
|
148
148
|
w: ScoreWeights = DEFAULT_WEIGHTS,
|
|
149
149
|
): number {
|
|
150
|
-
|
|
150
|
+
const raw = (
|
|
151
151
|
w.goal * goalRelevance( a, bias )
|
|
152
152
|
+ w.reward * a.expectedReward
|
|
153
153
|
+ w.novelty * novelty( a )
|
|
@@ -158,6 +158,12 @@ export function scoreAffordance(
|
|
|
158
158
|
- w.inhib * bias.inhibition
|
|
159
159
|
- w.risk * risk( a, bias )
|
|
160
160
|
)
|
|
161
|
+
// POLICY_REAFFERENCE P2 — policy availability damps a POSITIVE activation only,
|
|
162
|
+
// never flipping its sign: a refused ability competes weakly (so it is rarely
|
|
163
|
+
// chosen) yet is never removed from the field, keeping re-probe alive as
|
|
164
|
+
// availability recovers. Absent ⇒ 1 ⇒ no change (byte-identical quiet path).
|
|
165
|
+
const availability = a.availability ?? 1
|
|
166
|
+
return raw > 0 ? raw * availability : raw
|
|
161
167
|
}
|
|
162
168
|
|
|
163
169
|
/**
|
|
@@ -140,6 +140,15 @@ export interface Affordance {
|
|
|
140
140
|
* competition WITHOUT bypassing it (the plan biases; the field still decides).
|
|
141
141
|
*/
|
|
142
142
|
planBias?: number
|
|
143
|
+
/**
|
|
144
|
+
* Policy availability 0..1 (POLICY_REAFFERENCE P2) — learned from refusals,
|
|
145
|
+
* distinct from `available` (precondition satisfaction) and from competence.
|
|
146
|
+
* Present only when < 1 (a refusal has dented it); absent ⇒ fully available,
|
|
147
|
+
* so a never-refused Will's field is byte-identical. Damps positive activation
|
|
148
|
+
* in the competition without flipping its sign, so a suppressed ability still
|
|
149
|
+
* gets an occasional re-probe and can climb back as availability recovers.
|
|
150
|
+
*/
|
|
151
|
+
availability?: number
|
|
143
152
|
/** Provenance: the plan whose frontier step projected this affordance. */
|
|
144
153
|
planId?: string
|
|
145
154
|
/** Provenance: the frontier step id — flows through to action.outcome so the plan advances. */
|
|
@@ -34,7 +34,7 @@ export function buildEngineConfigEntities( config: WillConfig, executiveInterval
|
|
|
34
34
|
engine: 'system',
|
|
35
35
|
params: {
|
|
36
36
|
anatomy: config.anatomy ?? 'mind',
|
|
37
|
-
model: config.model ?? '',
|
|
37
|
+
model: config.llm?.model ?? '',
|
|
38
38
|
tickIntervalMs: config.tickIntervalMs ?? 1000
|
|
39
39
|
}
|
|
40
40
|
},
|