@mindot/will 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/channels/discord.d.ts +1 -1
- package/dist/channels/whatsapp.d.ts +1 -1
- package/dist/cli.js +434 -11
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +434 -11
- package/dist/index.js.map +1 -1
- package/dist/mcp/effectors.d.ts +1 -1
- package/dist/{will-Bikuk4s2.d.ts → will-DAW0l-lY.d.ts} +42 -4
- package/package.json +1 -1
- package/src/cognition/agency/engines/action.selector.ts +41 -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 +30 -5
- package/src/cognition/agency/reconcile.learning.ts +9 -0
- package/src/cognition/agency/schemas/repertoire.ts +107 -7
- package/src/cognition/agency/selection.scoring.ts +7 -1
- package/src/cognition/agency/types.ts +9 -0
- package/src/stem/index.ts +15 -0
- package/src/stem/policy/arbiter.ts +136 -0
- package/src/stem/policy/rule.table.ts +172 -0
- package/src/stem/policy/verdict.recorder.ts +0 -0
- package/src/stem/tracts/effector.controller.ts +379 -0
package/dist/mcp/effectors.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
|
-
import { E as EffectorHandler, W as Will } from '../will-
|
|
2
|
+
import { E as EffectorHandler, W as Will } from '../will-DAW0l-lY.js';
|
|
3
3
|
|
|
4
4
|
/** Where the tools live: spawn a local server, reach a remote one, or bring a connected client. */
|
|
5
5
|
type McpToolsSource = {
|
|
@@ -6026,6 +6026,9 @@ declare class SchemaRepertoire {
|
|
|
6026
6026
|
private _skills;
|
|
6027
6027
|
/** Tracks which templates were learned at runtime (vs innate) so decay can forget them. */
|
|
6028
6028
|
private _learned;
|
|
6029
|
+
/** Availability layer (P2): schema → { value 0..1, lastRefusedTick }. Empty until
|
|
6030
|
+
* a refusal lands — a never-refused Will writes nothing here (byte-identical). */
|
|
6031
|
+
private _availability;
|
|
6029
6032
|
constructor(seed?: MotorSchema[]);
|
|
6030
6033
|
schemas(): MotorSchema[];
|
|
6031
6034
|
getSchema(id: string): MotorSchema | undefined;
|
|
@@ -6040,6 +6043,23 @@ declare class SchemaRepertoire {
|
|
|
6040
6043
|
registerExternal(schema: MotorSchema): void;
|
|
6041
6044
|
skills(): ReadonlyMap<string, LearnedSkill>;
|
|
6042
6045
|
getSkill(id: string): LearnedSkill | undefined;
|
|
6046
|
+
availability(): ReadonlyMap<string, {
|
|
6047
|
+
value: number;
|
|
6048
|
+
lastRefusedTick: number;
|
|
6049
|
+
}>;
|
|
6050
|
+
/**
|
|
6051
|
+
* How available a schema is right now, 0..1. Absent from the ledger ⇒ 1
|
|
6052
|
+
* (fully available — the common case). This is the ONLY value the
|
|
6053
|
+
* AffordanceSynthesizer reads; it never touches competence.
|
|
6054
|
+
*/
|
|
6055
|
+
availabilityOf(schema: string): number;
|
|
6056
|
+
/**
|
|
6057
|
+
* Fold a policy refusal into the availability layer (NOT competence). A
|
|
6058
|
+
* `class` refusal cuts availability hard; an `instance` refusal dents it
|
|
6059
|
+
* lightly. Multiplicative so repeated refusals compound toward — but never
|
|
6060
|
+
* reach — zero, keeping re-probe alive.
|
|
6061
|
+
*/
|
|
6062
|
+
recordRefusal(schema: string, finality: 'class' | 'instance', tick: number): number;
|
|
6043
6063
|
/**
|
|
6044
6064
|
* Fold one outcome into the schema's learned skill. Returns the updated skill
|
|
6045
6065
|
* and whether it just crossed the proceduralization threshold this update.
|
|
@@ -6049,11 +6069,16 @@ declare class SchemaRepertoire {
|
|
|
6049
6069
|
proceduralized: boolean;
|
|
6050
6070
|
};
|
|
6051
6071
|
/**
|
|
6052
|
-
* Forgetting curve over the competence layer
|
|
6053
|
-
* lose habit; learned composites
|
|
6054
|
-
* entirely (template + skill).
|
|
6072
|
+
* Forgetting curve over the competence layer, plus availability recovery.
|
|
6073
|
+
* Skills unused for IDLE_TICKS lose habit; learned composites below DROP_HABIT
|
|
6074
|
+
* are dropped entirely (template + skill). Availability entries climb back
|
|
6075
|
+
* toward 1 and are dropped once fully recovered. Returns the ids that were
|
|
6076
|
+
* removed from each layer so their mirrored state entities can be deleted.
|
|
6055
6077
|
*/
|
|
6056
|
-
decay(tick: number):
|
|
6078
|
+
decay(tick: number): {
|
|
6079
|
+
skills: string[];
|
|
6080
|
+
availability: string[];
|
|
6081
|
+
};
|
|
6057
6082
|
/** Learned composite templates + all skills above a confidence floor. */
|
|
6058
6083
|
export(minHabit?: number): {
|
|
6059
6084
|
composites: MotorSchema[];
|
|
@@ -6073,6 +6098,12 @@ declare class SchemaRepertoire {
|
|
|
6073
6098
|
* Mirrors GoalManager._syncFromStateGoals.
|
|
6074
6099
|
*/
|
|
6075
6100
|
restoreComposites(entities: ReadonlySimulationState['entities']): void;
|
|
6101
|
+
/** Availability ledger encoded as `agency.availability` state entities (P2).
|
|
6102
|
+
* Empty until a refusal lands, so the quiet path writes nothing. */
|
|
6103
|
+
availabilityEntities(): EntityInput[];
|
|
6104
|
+
/** Rehydrate the availability ledger from state after a restore. Idempotent;
|
|
6105
|
+
* keeps whichever value is more restrictive so a concurrent refusal isn't lost. */
|
|
6106
|
+
restoreAvailability(entities: ReadonlySimulationState['entities']): void;
|
|
6076
6107
|
}
|
|
6077
6108
|
|
|
6078
6109
|
type SkillAccessor = () => ReadonlyMap<string, LearnedSkill>;
|
|
@@ -7695,6 +7726,13 @@ declare class WillStem {
|
|
|
7695
7726
|
description: string;
|
|
7696
7727
|
metrics?: Record<string, number>;
|
|
7697
7728
|
}): void;
|
|
7729
|
+
/**
|
|
7730
|
+
* Resolve a policy escalation the Will raised (POLICY_REAFFERENCE P4).
|
|
7731
|
+
* `approved` dispatches the held invocation to the world; otherwise it is
|
|
7732
|
+
* refused. Applied at the next tick boundary. `invocationId` is the awaiting
|
|
7733
|
+
* `agency.intent` id the escalation ask referenced.
|
|
7734
|
+
*/
|
|
7735
|
+
resolveEscalation(id: string, invocationId: string, approved: boolean): void;
|
|
7698
7736
|
/**
|
|
7699
7737
|
* Confirm a message was received by the target entity. Writes a
|
|
7700
7738
|
* message.delivery percept ("ear hears the word you spoke") and updates the
|
package/package.json
CHANGED
|
@@ -294,19 +294,26 @@ export class ActionSelector implements CognitiveEngine {
|
|
|
294
294
|
}
|
|
295
295
|
})
|
|
296
296
|
|
|
297
|
-
// ── Commitment revocation (EXAFFERENCE P4)
|
|
298
|
-
// A
|
|
299
|
-
//
|
|
300
|
-
//
|
|
301
|
-
//
|
|
302
|
-
//
|
|
303
|
-
|
|
297
|
+
// ── Commitment revocation (EXAFFERENCE P4 · POLICY_REAFFERENCE P3) ──
|
|
298
|
+
// A commitment still being weighed is let go for either of two DISTINCT
|
|
299
|
+
// reasons: a hard exafferent rupture (the world surprised us), or a class
|
|
300
|
+
// policy refusal of the very schema we're deliberating (the boundary just
|
|
301
|
+
// declared it forbidden — deliberating our way into it is wasted). The two
|
|
302
|
+
// never mix: the refusal is an outcome, not a percept, so it contributes
|
|
303
|
+
// ZERO to the exafferent scalar. Either way we can't delete the
|
|
304
|
+
// `deliberating` intent here (Deliberation runs after us and would resurrect
|
|
305
|
+
// it set-after-delete), so we drop a tombstone the Deliberation engine +
|
|
306
|
+
// Executor honor next tick. No successor is committed — the field re-forms.
|
|
307
|
+
const policyRevoke = !!deliberating && refusedClassSchemas( state ).has( deliberating.schema )
|
|
308
|
+
if( deliberating && ( rupture >= RUPTURE_REVOKE_GATE || policyRevoke ) ){
|
|
309
|
+
const reason = policyRevoke ? 'policy-refusal' : 'exafferent-rupture'
|
|
310
|
+
const revRupture = policyRevoke ? Math.max( rupture, RUPTURE_REVOKE_GATE ) : rupture
|
|
304
311
|
if( this._bus ){
|
|
305
312
|
try {
|
|
306
313
|
this._bus.publish({
|
|
307
314
|
type: 'agency.commitment.revoked', version: 1, sourceEngine: this.name,
|
|
308
315
|
salience: 0.85,
|
|
309
|
-
payload: { from: deliberating.schema, reason
|
|
316
|
+
payload: { from: deliberating.schema, reason, rupture: revRupture, tick },
|
|
310
317
|
})
|
|
311
318
|
}
|
|
312
319
|
catch( err ){ logger.warn(`[selector] revoked publish failed: ${ err instanceof Error ? err.message : String( err ) }`) }
|
|
@@ -314,11 +321,12 @@ export class ActionSelector implements CognitiveEngine {
|
|
|
314
321
|
this._lastRevoked = { schema: deliberating.schema, tick } // Channel-B: flavor the next deliberation
|
|
315
322
|
return {
|
|
316
323
|
commands: {
|
|
317
|
-
set: [ revocationEntity( deliberating.id, deliberating.schema,
|
|
324
|
+
set: [ revocationEntity( deliberating.id, deliberating.schema, revRupture, tick ) ],
|
|
318
325
|
metrics: [
|
|
319
326
|
[ 'agency.field.eligible', eligible.length ],
|
|
320
327
|
[ 'agency.selection.busy', 1 ],
|
|
321
328
|
[ 'agency.commitment.revoked', 1 ],
|
|
329
|
+
...( policyRevoke ? [ [ 'agency.policy.revoked', 1 ] as [ string, number ] ] : [] ),
|
|
322
330
|
...stabMetrics,
|
|
323
331
|
],
|
|
324
332
|
},
|
|
@@ -632,6 +640,30 @@ function computeRupture(
|
|
|
632
640
|
return clamp01( ( maxSalience - RUPTURE_SALIENCE_GATE ) / ( 1 - RUPTURE_SALIENCE_GATE ) )
|
|
633
641
|
}
|
|
634
642
|
|
|
643
|
+
/**
|
|
644
|
+
* POLICY_REAFFERENCE P3 — schemas hit by a CLASS-final policy refusal visible in
|
|
645
|
+
* frozen state this tick (a refused `agency.outcome` lives exactly one tick).
|
|
646
|
+
*
|
|
647
|
+
* A refusal is an `agency.outcome`, never a `percept`, so it can NEVER feed
|
|
648
|
+
* computeRupture: the mind cannot rupture itself with its own boundary. This is
|
|
649
|
+
* a SEPARATE, explicit trigger — "the boundary just declared this forbidden, let
|
|
650
|
+
* go of any commitment I'm still weighing toward it" — distinct from a
|
|
651
|
+
* world-surprise exafferent rupture, and it carries its own revocation reason.
|
|
652
|
+
* Only `class` finality qualifies: an `instance` refusal means "not with those
|
|
653
|
+
* parameters", not "never", so a still-deliberating attempt may yet succeed.
|
|
654
|
+
*/
|
|
655
|
+
function refusedClassSchemas( state: ReadonlySimulationState ): Set<string> {
|
|
656
|
+
const out = new Set<string>()
|
|
657
|
+
for( const e of state.entities.values() ){
|
|
658
|
+
if( e.type !== 'agency.outcome') continue
|
|
659
|
+
const m = e.metadata
|
|
660
|
+
if( m?.['refused'] !== true || str( m?.['finality'] ) !== 'class') continue
|
|
661
|
+
const schema = str( m?.['schema'] )
|
|
662
|
+
if( schema ) out.add( schema )
|
|
663
|
+
}
|
|
664
|
+
return out
|
|
665
|
+
}
|
|
666
|
+
|
|
635
667
|
/**
|
|
636
668
|
* Competition weights = DEFAULT_WEIGHTS with the two trait-owned ones (risk, novelty)
|
|
637
669
|
* 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,7 @@ 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
32
|
import { AWAIT_TIMEOUT } from '#agency/engines/motor.schema.executor'
|
|
33
33
|
|
|
34
34
|
const PROC_THRESHOLD = 0.60 // mirror of repertoire's threshold for the habitual-count metric
|
|
@@ -190,10 +190,28 @@ export class ReafferenceEngine implements CognitiveEngine {
|
|
|
190
190
|
// ── 1. Fold each outcome into its skill ──────────────────────
|
|
191
191
|
let updates = 0
|
|
192
192
|
let discovered = 0
|
|
193
|
+
let refused = 0
|
|
193
194
|
for( const { id, meta: m, fromState } of outcomes ){
|
|
194
195
|
const schema = str( m['schema'] )
|
|
195
196
|
if( !schema ){ if( fromState ) del.push( id ); continue }
|
|
196
197
|
|
|
198
|
+
// POLICY_REAFFERENCE P2 — a refusal is NOT a failure. Route it to the
|
|
199
|
+
// availability layer and stop: it must never touch LearnedSkill (value,
|
|
200
|
+
// habit, param priors), or the Will learns it is unskilled at something it
|
|
201
|
+
// is merely forbidden to do. The awaiting intent is still freed, and a
|
|
202
|
+
// refused plan step is signalled unsuccessful so the plan doesn't hang.
|
|
203
|
+
if( m['refused'] === true ){
|
|
204
|
+
const finality = str( m['finality'] ) === 'class' ? 'class' : 'instance'
|
|
205
|
+
this._repertoire.recordRefusal( schema, finality, tick )
|
|
206
|
+
if( fromState ) del.push( id )
|
|
207
|
+
const refusedIntent = str( m['intentId'] )
|
|
208
|
+
if( refusedIntent ) del.push( refusedIntent )
|
|
209
|
+
const refusedPlan = str( m['planId'] )
|
|
210
|
+
if( refusedPlan ) this._emitPlanOutcome( refusedPlan, str( m['stepId'] ), schema, false, 0, 0, tick )
|
|
211
|
+
refused++
|
|
212
|
+
continue
|
|
213
|
+
}
|
|
214
|
+
|
|
197
215
|
const { skill, proceduralized } = this._repertoire.recordOutcome({
|
|
198
216
|
schema,
|
|
199
217
|
success: m['success'] === true,
|
|
@@ -235,19 +253,23 @@ export class ReafferenceEngine implements CognitiveEngine {
|
|
|
235
253
|
}
|
|
236
254
|
}
|
|
237
255
|
|
|
238
|
-
// ── 2. Forgetting curve over the competence layer
|
|
256
|
+
// ── 2. Forgetting curve over the competence layer + availability recovery ──
|
|
239
257
|
const dropped = this._repertoire.decay( tick )
|
|
240
|
-
for( const id of dropped ){
|
|
258
|
+
for( const id of dropped.skills ){
|
|
241
259
|
del.push(`agency-skill-${ id }`)
|
|
242
260
|
del.push( schemaEntityId( id ) ) // composite mirror (harmless no-op for primitives)
|
|
243
261
|
}
|
|
262
|
+
for( const id of dropped.availability )
|
|
263
|
+
del.push( availabilityEntityId( id ) ) // fully-recovered ⇒ remove the mirror
|
|
244
264
|
|
|
245
|
-
// ── 3. Mirror learned composite templates
|
|
265
|
+
// ── 3. Mirror learned composite templates + availability ─────
|
|
246
266
|
// Skills become `agency.skill` (above); the invented composite *definitions*
|
|
247
267
|
// must travel too, or a snapshot/restore brings back a skill whose schema is
|
|
248
268
|
// gone and the executor can't expand it. Idempotent re-write each tick, like
|
|
249
269
|
// GoalManager._persistGoals. Empty until a composite is actually learned.
|
|
250
|
-
for( const e of this._repertoire.compositeEntities() )
|
|
270
|
+
for( const e of this._repertoire.compositeEntities() ) set.push( e )
|
|
271
|
+
// Availability entries (P2) mirror the same way — empty until a refusal lands.
|
|
272
|
+
for( const e of this._repertoire.availabilityEntities() ) set.push( e )
|
|
251
273
|
|
|
252
274
|
// ── 4. Telemetry ─────────────────────────────────────────────
|
|
253
275
|
const skills = this._repertoire.skills()
|
|
@@ -259,6 +281,9 @@ export class ReafferenceEngine implements CognitiveEngine {
|
|
|
259
281
|
[ 'agency.habitual.count', habitual ],
|
|
260
282
|
[ 'agency.sensory.confirmed', sensory ],
|
|
261
283
|
)
|
|
284
|
+
// Only emit the refusal metric when it fired — a never-refused Will writes
|
|
285
|
+
// nothing here, preserving the byte-identical quiet path (cf. EXAFFERENCE P3).
|
|
286
|
+
if( refused > 0 ) metrics.push([ 'agency.refused.count', refused ])
|
|
262
287
|
|
|
263
288
|
return { commands: { set, delete: del, metrics } }
|
|
264
289
|
}
|
|
@@ -24,6 +24,14 @@ export interface HostAckResult {
|
|
|
24
24
|
/** −1..1 felt valence of the outcome. */
|
|
25
25
|
valence?: number
|
|
26
26
|
description?: string
|
|
27
|
+
/**
|
|
28
|
+
* POLICY_REAFFERENCE P2 — this ack is a policy REFUSAL, not a world failure.
|
|
29
|
+
* The ReafferenceEngine routes it to the availability layer (NOT competence):
|
|
30
|
+
* a refusal must never teach the Will it is unskilled at something it is merely
|
|
31
|
+
* forbidden to do. `finality` decides how hard availability is cut.
|
|
32
|
+
*/
|
|
33
|
+
refused?: boolean
|
|
34
|
+
finality?: 'class' | 'instance'
|
|
27
35
|
}
|
|
28
36
|
|
|
29
37
|
/**
|
|
@@ -71,6 +79,7 @@ export function reconcileInvocation(
|
|
|
71
79
|
mode: 'external',
|
|
72
80
|
tick,
|
|
73
81
|
reconciled: true,
|
|
82
|
+
...( result.refused ? { refused: true, finality: result.finality ?? 'instance' } : {} ),
|
|
74
83
|
...( provenance.planId ? { planId: provenance.planId } : {} ),
|
|
75
84
|
...( provenance.stepId ? { stepId: provenance.stepId } : {} ),
|
|
76
85
|
},
|
|
@@ -39,6 +39,23 @@ const IDLE_TICKS = 200 // ticks of disuse before forgetting starts
|
|
|
39
39
|
const DECAY_RATE = 0.02 // habit lost per decay application
|
|
40
40
|
const DROP_HABIT = 0.05 // below this (and learned) the skill is forgotten
|
|
41
41
|
|
|
42
|
+
// ── availability layer (POLICY_REAFFERENCE P2) ────────────────
|
|
43
|
+
// Availability is NOT competence. It answers "may I use this schema", learned
|
|
44
|
+
// from policy refusals, and is kept strictly apart from LearnedSkill so a
|
|
45
|
+
// refusal never teaches the Will it is *unskilled* at something it is merely
|
|
46
|
+
// *forbidden* to do. A `class` refusal ("never, under this policy") drives
|
|
47
|
+
// availability down hard; an `instance` refusal ("not with those parameters")
|
|
48
|
+
// dents it lightly — the Will should keep reaching for the ability, just not
|
|
49
|
+
// that way. Recovery is slow but real, so a policy change is re-discoverable:
|
|
50
|
+
// availability never floors at zero, and it climbs back toward 1 with disuse of
|
|
51
|
+
// the refusal. P2 keys availability by SCHEMA; per-(schema, params) envelope
|
|
52
|
+
// narrowing is a follow-up that belongs at selection time, not fielding time.
|
|
53
|
+
const AVAIL_DROP_CLASS = 0.50 // multiplicative cut on a class-final refusal
|
|
54
|
+
const AVAIL_DROP_INSTANCE = 0.12 // lighter cut on an instance-final refusal
|
|
55
|
+
const AVAIL_FLOOR = 0.05 // never zero — re-probe must always be possible
|
|
56
|
+
const AVAIL_RECOVERY = 0.02 // per-decay climb back toward 1
|
|
57
|
+
const AVAIL_RECOVERED = 0.999 // at/above this the entry is dropped (quiet path)
|
|
58
|
+
|
|
42
59
|
export interface OutcomeObservation {
|
|
43
60
|
schema: string
|
|
44
61
|
success: boolean
|
|
@@ -54,6 +71,9 @@ export class SchemaRepertoire {
|
|
|
54
71
|
private _skills = new Map<string, LearnedSkill>()
|
|
55
72
|
/** Tracks which templates were learned at runtime (vs innate) so decay can forget them. */
|
|
56
73
|
private _learned = new Set<string>()
|
|
74
|
+
/** Availability layer (P2): schema → { value 0..1, lastRefusedTick }. Empty until
|
|
75
|
+
* a refusal lands — a never-refused Will writes nothing here (byte-identical). */
|
|
76
|
+
private _availability = new Map<string, { value: number; lastRefusedTick: number }>()
|
|
57
77
|
|
|
58
78
|
constructor( seed: MotorSchema[] = INNATE_SCHEMAS ){
|
|
59
79
|
for( const s of seed ) this._templates.set( s.id, s )
|
|
@@ -85,6 +105,32 @@ export class SchemaRepertoire {
|
|
|
85
105
|
skills(): ReadonlyMap<string, LearnedSkill> { return this._skills }
|
|
86
106
|
getSkill( id: string ): LearnedSkill | undefined { return this._skills.get( id ) }
|
|
87
107
|
|
|
108
|
+
// ── availability (P2) ─────────────────────────────────────────
|
|
109
|
+
availability(): ReadonlyMap<string, { value: number; lastRefusedTick: number }> { return this._availability }
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* How available a schema is right now, 0..1. Absent from the ledger ⇒ 1
|
|
113
|
+
* (fully available — the common case). This is the ONLY value the
|
|
114
|
+
* AffordanceSynthesizer reads; it never touches competence.
|
|
115
|
+
*/
|
|
116
|
+
availabilityOf( schema: string ): number {
|
|
117
|
+
return this._availability.get( schema )?.value ?? 1
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Fold a policy refusal into the availability layer (NOT competence). A
|
|
122
|
+
* `class` refusal cuts availability hard; an `instance` refusal dents it
|
|
123
|
+
* lightly. Multiplicative so repeated refusals compound toward — but never
|
|
124
|
+
* reach — zero, keeping re-probe alive.
|
|
125
|
+
*/
|
|
126
|
+
recordRefusal( schema: string, finality: 'class' | 'instance', tick: number ): number {
|
|
127
|
+
const prev = this._availability.get( schema )?.value ?? 1
|
|
128
|
+
const drop = finality === 'class' ? AVAIL_DROP_CLASS : AVAIL_DROP_INSTANCE
|
|
129
|
+
const value = Math.max( AVAIL_FLOOR, prev * ( 1 - drop ) )
|
|
130
|
+
this._availability.set( schema, { value, lastRefusedTick: tick } )
|
|
131
|
+
return value
|
|
132
|
+
}
|
|
133
|
+
|
|
88
134
|
/**
|
|
89
135
|
* Fold one outcome into the schema's learned skill. Returns the updated skill
|
|
90
136
|
* and whether it just crossed the proceduralization threshold this update.
|
|
@@ -118,12 +164,14 @@ export class SchemaRepertoire {
|
|
|
118
164
|
}
|
|
119
165
|
|
|
120
166
|
/**
|
|
121
|
-
* Forgetting curve over the competence layer
|
|
122
|
-
* lose habit; learned composites
|
|
123
|
-
* entirely (template + skill).
|
|
167
|
+
* Forgetting curve over the competence layer, plus availability recovery.
|
|
168
|
+
* Skills unused for IDLE_TICKS lose habit; learned composites below DROP_HABIT
|
|
169
|
+
* are dropped entirely (template + skill). Availability entries climb back
|
|
170
|
+
* toward 1 and are dropped once fully recovered. Returns the ids that were
|
|
171
|
+
* removed from each layer so their mirrored state entities can be deleted.
|
|
124
172
|
*/
|
|
125
|
-
decay( tick: number ): string[] {
|
|
126
|
-
const
|
|
173
|
+
decay( tick: number ): { skills: string[]; availability: string[] } {
|
|
174
|
+
const skills: string[] = []
|
|
127
175
|
for( const [ id, skill ] of this._skills ){
|
|
128
176
|
if( tick - skill.lastEnactedTick <= IDLE_TICKS ) continue
|
|
129
177
|
|
|
@@ -132,12 +180,23 @@ export class SchemaRepertoire {
|
|
|
132
180
|
this._skills.delete( id )
|
|
133
181
|
this._templates.delete( id )
|
|
134
182
|
this._learned.delete( id )
|
|
135
|
-
|
|
183
|
+
skills.push( id )
|
|
136
184
|
continue
|
|
137
185
|
}
|
|
138
186
|
this._skills.set( id, { ...skill, habitStrength } )
|
|
139
187
|
}
|
|
140
|
-
|
|
188
|
+
|
|
189
|
+
// Availability recovery (P2): each entry climbs slowly back toward 1, so a
|
|
190
|
+
// policy change is re-discoverable. A fully-recovered entry is dropped, so a
|
|
191
|
+
// Will that was refused long ago returns to the byte-identical quiet path.
|
|
192
|
+
const availability: string[] = []
|
|
193
|
+
for( const [ id, avail ] of this._availability ){
|
|
194
|
+
const value = avail.value + AVAIL_RECOVERY * ( 1 - avail.value )
|
|
195
|
+
if( value >= AVAIL_RECOVERED ){ this._availability.delete( id ); availability.push( id ) }
|
|
196
|
+
else this._availability.set( id, { ...avail, value } )
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return { skills, availability }
|
|
141
200
|
}
|
|
142
201
|
|
|
143
202
|
// ── PMA portability (Phase 6 reads these) ─────────────────────
|
|
@@ -188,6 +247,30 @@ export class SchemaRepertoire {
|
|
|
188
247
|
this._learned.add( s.id )
|
|
189
248
|
}
|
|
190
249
|
}
|
|
250
|
+
|
|
251
|
+
/** Availability ledger encoded as `agency.availability` state entities (P2).
|
|
252
|
+
* Empty until a refusal lands, so the quiet path writes nothing. */
|
|
253
|
+
availabilityEntities(): EntityInput[] {
|
|
254
|
+
const out: EntityInput[] = []
|
|
255
|
+
for( const [ schema, a ] of this._availability )
|
|
256
|
+
out.push( availabilityEntity( schema, a.value, a.lastRefusedTick ) )
|
|
257
|
+
return out
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** Rehydrate the availability ledger from state after a restore. Idempotent;
|
|
261
|
+
* keeps whichever value is more restrictive so a concurrent refusal isn't lost. */
|
|
262
|
+
restoreAvailability( entities: ReadonlySimulationState['entities'] ): void {
|
|
263
|
+
for( const e of entities.values() ){
|
|
264
|
+
if( e.type !== AVAILABILITY_ENTITY_TYPE ) continue
|
|
265
|
+
const m = ( e.metadata ?? {} ) as Record<string, unknown>
|
|
266
|
+
const schema = typeof m['schema'] === 'string' ? m['schema'] : ''
|
|
267
|
+
if( !schema ) continue
|
|
268
|
+
const value = typeof m['value'] === 'number' ? m['value'] : 1
|
|
269
|
+
const tick = typeof m['lastRefusedTick'] === 'number' ? m['lastRefusedTick'] : 0
|
|
270
|
+
const prev = this._availability.get( schema )
|
|
271
|
+
if( !prev || value < prev.value ) this._availability.set( schema, { value, lastRefusedTick: tick } )
|
|
272
|
+
}
|
|
273
|
+
}
|
|
191
274
|
}
|
|
192
275
|
|
|
193
276
|
// ─── helpers ─────────────────────────────────────────────────────────────────
|
|
@@ -209,6 +292,23 @@ function clamp01( n: number ): number {
|
|
|
209
292
|
return n < 0 ? 0 : n > 1 ? 1 : n
|
|
210
293
|
}
|
|
211
294
|
|
|
295
|
+
// ─── availability ⇄ state-entity codec (P2) ──────────────────────────────────
|
|
296
|
+
|
|
297
|
+
/** State-entity type for a mirrored availability entry. */
|
|
298
|
+
export const AVAILABILITY_ENTITY_TYPE = 'agency.availability'
|
|
299
|
+
|
|
300
|
+
/** Stable entity id for a schema's availability mirror (idempotent re-writes). */
|
|
301
|
+
export function availabilityEntityId( schema: string ): string { return `agency-availability-${ schema }` }
|
|
302
|
+
|
|
303
|
+
/** Encode one availability entry as a state entity. */
|
|
304
|
+
function availabilityEntity( schema: string, value: number, lastRefusedTick: number ): EntityInput {
|
|
305
|
+
return {
|
|
306
|
+
id: availabilityEntityId( schema ),
|
|
307
|
+
type: AVAILABILITY_ENTITY_TYPE,
|
|
308
|
+
metadata: { schema, value, lastRefusedTick },
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
212
312
|
// ─── composite ⇄ state-entity codec (snapshot/replay) ────────────────────────
|
|
213
313
|
|
|
214
314
|
/** 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. */
|
package/src/stem/index.ts
CHANGED
|
@@ -807,6 +807,16 @@ export class WillStem {
|
|
|
807
807
|
this._effector.confirmExecution( this._get( id ), invocationId, result )
|
|
808
808
|
}
|
|
809
809
|
|
|
810
|
+
/**
|
|
811
|
+
* Resolve a policy escalation the Will raised (POLICY_REAFFERENCE P4).
|
|
812
|
+
* `approved` dispatches the held invocation to the world; otherwise it is
|
|
813
|
+
* refused. Applied at the next tick boundary. `invocationId` is the awaiting
|
|
814
|
+
* `agency.intent` id the escalation ask referenced.
|
|
815
|
+
*/
|
|
816
|
+
resolveEscalation( id: string, invocationId: string, approved: boolean ): void {
|
|
817
|
+
this._effector.resolveEscalation( this._get( id ), invocationId, approved )
|
|
818
|
+
}
|
|
819
|
+
|
|
810
820
|
// ── Messaging / outbox (11.1) ────────────────────────────────────────────
|
|
811
821
|
// Delegates to OutboxController (R5-c). `_get(id)` validates the Will exists
|
|
812
822
|
// and supplies the WillInstance; the outbox ops touch only instance fields.
|
|
@@ -963,6 +973,11 @@ export class WillStem {
|
|
|
963
973
|
sensory: this._sensory,
|
|
964
974
|
} )
|
|
965
975
|
|
|
976
|
+
// Apply policy refusals queued during the previous step's flush, at the
|
|
977
|
+
// same boundary and for the same reason (POLICY_REAFFERENCE P1): a denial
|
|
978
|
+
// reconciles as a host-rejection-shaped failure ack the step then sees.
|
|
979
|
+
this._effector.applyPolicyOutcomes( instance )
|
|
980
|
+
|
|
966
981
|
await instance.simulation.step( 1 )
|
|
967
982
|
|
|
968
983
|
instance.tickCount++
|