@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/cli.js
CHANGED
|
@@ -3625,11 +3625,19 @@ var PROC_THRESHOLD = 0.6;
|
|
|
3625
3625
|
var IDLE_TICKS = 200;
|
|
3626
3626
|
var DECAY_RATE = 0.02;
|
|
3627
3627
|
var DROP_HABIT = 0.05;
|
|
3628
|
+
var AVAIL_DROP_CLASS = 0.5;
|
|
3629
|
+
var AVAIL_DROP_INSTANCE = 0.12;
|
|
3630
|
+
var AVAIL_FLOOR = 0.05;
|
|
3631
|
+
var AVAIL_RECOVERY = 0.02;
|
|
3632
|
+
var AVAIL_RECOVERED = 0.999;
|
|
3628
3633
|
var SchemaRepertoire = class {
|
|
3629
3634
|
_templates = /* @__PURE__ */ new Map();
|
|
3630
3635
|
_skills = /* @__PURE__ */ new Map();
|
|
3631
3636
|
/** Tracks which templates were learned at runtime (vs innate) so decay can forget them. */
|
|
3632
3637
|
_learned = /* @__PURE__ */ new Set();
|
|
3638
|
+
/** Availability layer (P2): schema → { value 0..1, lastRefusedTick }. Empty until
|
|
3639
|
+
* a refusal lands — a never-refused Will writes nothing here (byte-identical). */
|
|
3640
|
+
_availability = /* @__PURE__ */ new Map();
|
|
3633
3641
|
constructor(seed = INNATE_SCHEMAS) {
|
|
3634
3642
|
for (const s of seed) this._templates.set(s.id, s);
|
|
3635
3643
|
}
|
|
@@ -3663,6 +3671,31 @@ var SchemaRepertoire = class {
|
|
|
3663
3671
|
getSkill(id) {
|
|
3664
3672
|
return this._skills.get(id);
|
|
3665
3673
|
}
|
|
3674
|
+
// ── availability (P2) ─────────────────────────────────────────
|
|
3675
|
+
availability() {
|
|
3676
|
+
return this._availability;
|
|
3677
|
+
}
|
|
3678
|
+
/**
|
|
3679
|
+
* How available a schema is right now, 0..1. Absent from the ledger ⇒ 1
|
|
3680
|
+
* (fully available — the common case). This is the ONLY value the
|
|
3681
|
+
* AffordanceSynthesizer reads; it never touches competence.
|
|
3682
|
+
*/
|
|
3683
|
+
availabilityOf(schema) {
|
|
3684
|
+
return this._availability.get(schema)?.value ?? 1;
|
|
3685
|
+
}
|
|
3686
|
+
/**
|
|
3687
|
+
* Fold a policy refusal into the availability layer (NOT competence). A
|
|
3688
|
+
* `class` refusal cuts availability hard; an `instance` refusal dents it
|
|
3689
|
+
* lightly. Multiplicative so repeated refusals compound toward — but never
|
|
3690
|
+
* reach — zero, keeping re-probe alive.
|
|
3691
|
+
*/
|
|
3692
|
+
recordRefusal(schema, finality, tick) {
|
|
3693
|
+
const prev = this._availability.get(schema)?.value ?? 1;
|
|
3694
|
+
const drop = finality === "class" ? AVAIL_DROP_CLASS : AVAIL_DROP_INSTANCE;
|
|
3695
|
+
const value = Math.max(AVAIL_FLOOR, prev * (1 - drop));
|
|
3696
|
+
this._availability.set(schema, { value, lastRefusedTick: tick });
|
|
3697
|
+
return value;
|
|
3698
|
+
}
|
|
3666
3699
|
/**
|
|
3667
3700
|
* Fold one outcome into the schema's learned skill. Returns the updated skill
|
|
3668
3701
|
* and whether it just crossed the proceduralization threshold this update.
|
|
@@ -3689,12 +3722,14 @@ var SchemaRepertoire = class {
|
|
|
3689
3722
|
return { skill, proceduralized: !wasProceduralized && habitStrength >= PROC_THRESHOLD };
|
|
3690
3723
|
}
|
|
3691
3724
|
/**
|
|
3692
|
-
* Forgetting curve over the competence layer
|
|
3693
|
-
* lose habit; learned composites
|
|
3694
|
-
* entirely (template + skill).
|
|
3725
|
+
* Forgetting curve over the competence layer, plus availability recovery.
|
|
3726
|
+
* Skills unused for IDLE_TICKS lose habit; learned composites below DROP_HABIT
|
|
3727
|
+
* are dropped entirely (template + skill). Availability entries climb back
|
|
3728
|
+
* toward 1 and are dropped once fully recovered. Returns the ids that were
|
|
3729
|
+
* removed from each layer so their mirrored state entities can be deleted.
|
|
3695
3730
|
*/
|
|
3696
3731
|
decay(tick) {
|
|
3697
|
-
const
|
|
3732
|
+
const skills = [];
|
|
3698
3733
|
for (const [id, skill] of this._skills) {
|
|
3699
3734
|
if (tick - skill.lastEnactedTick <= IDLE_TICKS) continue;
|
|
3700
3735
|
const habitStrength = clamp01(skill.habitStrength - DECAY_RATE);
|
|
@@ -3702,12 +3737,20 @@ var SchemaRepertoire = class {
|
|
|
3702
3737
|
this._skills.delete(id);
|
|
3703
3738
|
this._templates.delete(id);
|
|
3704
3739
|
this._learned.delete(id);
|
|
3705
|
-
|
|
3740
|
+
skills.push(id);
|
|
3706
3741
|
continue;
|
|
3707
3742
|
}
|
|
3708
3743
|
this._skills.set(id, { ...skill, habitStrength });
|
|
3709
3744
|
}
|
|
3710
|
-
|
|
3745
|
+
const availability = [];
|
|
3746
|
+
for (const [id, avail] of this._availability) {
|
|
3747
|
+
const value = avail.value + AVAIL_RECOVERY * (1 - avail.value);
|
|
3748
|
+
if (value >= AVAIL_RECOVERED) {
|
|
3749
|
+
this._availability.delete(id);
|
|
3750
|
+
availability.push(id);
|
|
3751
|
+
} else this._availability.set(id, { ...avail, value });
|
|
3752
|
+
}
|
|
3753
|
+
return { skills, availability };
|
|
3711
3754
|
}
|
|
3712
3755
|
// ── PMA portability (Phase 6 reads these) ─────────────────────
|
|
3713
3756
|
/** Learned composite templates + all skills above a confidence floor. */
|
|
@@ -3756,6 +3799,28 @@ var SchemaRepertoire = class {
|
|
|
3756
3799
|
this._learned.add(s.id);
|
|
3757
3800
|
}
|
|
3758
3801
|
}
|
|
3802
|
+
/** Availability ledger encoded as `agency.availability` state entities (P2).
|
|
3803
|
+
* Empty until a refusal lands, so the quiet path writes nothing. */
|
|
3804
|
+
availabilityEntities() {
|
|
3805
|
+
const out = [];
|
|
3806
|
+
for (const [schema, a] of this._availability)
|
|
3807
|
+
out.push(availabilityEntity(schema, a.value, a.lastRefusedTick));
|
|
3808
|
+
return out;
|
|
3809
|
+
}
|
|
3810
|
+
/** Rehydrate the availability ledger from state after a restore. Idempotent;
|
|
3811
|
+
* keeps whichever value is more restrictive so a concurrent refusal isn't lost. */
|
|
3812
|
+
restoreAvailability(entities) {
|
|
3813
|
+
for (const e of entities.values()) {
|
|
3814
|
+
if (e.type !== AVAILABILITY_ENTITY_TYPE) continue;
|
|
3815
|
+
const m = e.metadata ?? {};
|
|
3816
|
+
const schema = typeof m["schema"] === "string" ? m["schema"] : "";
|
|
3817
|
+
if (!schema) continue;
|
|
3818
|
+
const value = typeof m["value"] === "number" ? m["value"] : 1;
|
|
3819
|
+
const tick = typeof m["lastRefusedTick"] === "number" ? m["lastRefusedTick"] : 0;
|
|
3820
|
+
const prev = this._availability.get(schema);
|
|
3821
|
+
if (!prev || value < prev.value) this._availability.set(schema, { value, lastRefusedTick: tick });
|
|
3822
|
+
}
|
|
3823
|
+
}
|
|
3759
3824
|
};
|
|
3760
3825
|
function freshSkill(schema, value, tick) {
|
|
3761
3826
|
return {
|
|
@@ -3772,6 +3837,17 @@ function freshSkill(schema, value, tick) {
|
|
|
3772
3837
|
function clamp01(n) {
|
|
3773
3838
|
return n < 0 ? 0 : n > 1 ? 1 : n;
|
|
3774
3839
|
}
|
|
3840
|
+
var AVAILABILITY_ENTITY_TYPE = "agency.availability";
|
|
3841
|
+
function availabilityEntityId(schema) {
|
|
3842
|
+
return `agency-availability-${schema}`;
|
|
3843
|
+
}
|
|
3844
|
+
function availabilityEntity(schema, value, lastRefusedTick) {
|
|
3845
|
+
return {
|
|
3846
|
+
id: availabilityEntityId(schema),
|
|
3847
|
+
type: AVAILABILITY_ENTITY_TYPE,
|
|
3848
|
+
metadata: { schema, value, lastRefusedTick }
|
|
3849
|
+
};
|
|
3850
|
+
}
|
|
3775
3851
|
var SCHEMA_ENTITY_TYPE = "agency.schema";
|
|
3776
3852
|
function schemaEntityId(schemaId) {
|
|
3777
3853
|
return `agency-schema-${schemaId}`;
|
|
@@ -20051,7 +20127,9 @@ function risk(a, bias) {
|
|
|
20051
20127
|
return clamp016(Math.max(0, -a.expectedValence) * 0.5 + bias.threat * 0.5);
|
|
20052
20128
|
}
|
|
20053
20129
|
function scoreAffordance(a, bias, w = DEFAULT_WEIGHTS) {
|
|
20054
|
-
|
|
20130
|
+
const raw = w.goal * goalRelevance(a, bias) + w.reward * a.expectedReward + w.novelty * novelty(a) + w.drive * driveUrgency(a, bias) + w.habit * a.habitStrength + w.plan * (a.planBias ?? 0) - w.cost * a.cost - w.inhib * bias.inhibition - w.risk * risk(a, bias);
|
|
20131
|
+
const availability = a.availability ?? 1;
|
|
20132
|
+
return raw > 0 ? raw * availability : raw;
|
|
20055
20133
|
}
|
|
20056
20134
|
function stakes(winner, bias) {
|
|
20057
20135
|
return clamp016(Math.max(
|
|
@@ -20130,6 +20208,7 @@ var AffordanceSynthesizer = class {
|
|
|
20130
20208
|
// ── react ─────────────────────────────────────────────────────
|
|
20131
20209
|
async react(_delta, tick, state, _context) {
|
|
20132
20210
|
this._repertoire?.restoreComposites(state.entities);
|
|
20211
|
+
this._repertoire?.restoreAvailability(state.entities);
|
|
20133
20212
|
const schemas = this._repertoire?.schemas() ?? this._schemas;
|
|
20134
20213
|
const skills = this._skills?.() ?? this._repertoire?.skills() ?? null;
|
|
20135
20214
|
const valence = metric(state, "affect.valence", 0);
|
|
@@ -20255,6 +20334,7 @@ var AffordanceSynthesizer = class {
|
|
|
20255
20334
|
/** Compose an Affordance from a schema + the evoking context, folding in learned priors. */
|
|
20256
20335
|
_build(schema, tick, state, valence, energyLow, skills, ctx) {
|
|
20257
20336
|
const skill = skills?.get(schema.id);
|
|
20337
|
+
const availability = this._repertoire?.availabilityOf(schema.id) ?? 1;
|
|
20258
20338
|
const expectedReward = skill?.valueEstimate ?? clamp017(((schema.baseValence ?? 0) + 1) / 2);
|
|
20259
20339
|
const expectedValence = schema.baseValence ?? valence;
|
|
20260
20340
|
const habitStrength = skill?.habitStrength ?? 0;
|
|
@@ -20276,6 +20356,7 @@ var AffordanceSynthesizer = class {
|
|
|
20276
20356
|
available: this._available(schema.preconditions, (k) => metric(state, k, 0)),
|
|
20277
20357
|
tags: schema.tags ?? [],
|
|
20278
20358
|
...schema.description ? { description: schema.description } : {},
|
|
20359
|
+
...availability < 1 ? { availability } : {},
|
|
20279
20360
|
planBias: ctx.planBias,
|
|
20280
20361
|
planId: ctx.planId,
|
|
20281
20362
|
stepId: ctx.stepId,
|
|
@@ -20299,6 +20380,7 @@ var AffordanceSynthesizer = class {
|
|
|
20299
20380
|
available: a.available,
|
|
20300
20381
|
tags: a.tags,
|
|
20301
20382
|
description: a.description,
|
|
20383
|
+
...a.availability !== void 0 ? { availability: a.availability } : {},
|
|
20302
20384
|
planBias: a.planBias,
|
|
20303
20385
|
planId: a.planId,
|
|
20304
20386
|
stepId: a.stepId,
|
|
@@ -20502,7 +20584,10 @@ var ActionSelector = class {
|
|
|
20502
20584
|
]
|
|
20503
20585
|
}
|
|
20504
20586
|
});
|
|
20505
|
-
|
|
20587
|
+
const policyRevoke = !!deliberating && refusedClassSchemas(state).has(deliberating.schema);
|
|
20588
|
+
if (deliberating && (rupture >= RUPTURE_REVOKE_GATE || policyRevoke)) {
|
|
20589
|
+
const reason = policyRevoke ? "policy-refusal" : "exafferent-rupture";
|
|
20590
|
+
const revRupture = policyRevoke ? Math.max(rupture, RUPTURE_REVOKE_GATE) : rupture;
|
|
20506
20591
|
if (this._bus) {
|
|
20507
20592
|
try {
|
|
20508
20593
|
this._bus.publish({
|
|
@@ -20510,7 +20595,7 @@ var ActionSelector = class {
|
|
|
20510
20595
|
version: 1,
|
|
20511
20596
|
sourceEngine: this.name,
|
|
20512
20597
|
salience: 0.85,
|
|
20513
|
-
payload: { from: deliberating.schema, reason
|
|
20598
|
+
payload: { from: deliberating.schema, reason, rupture: revRupture, tick }
|
|
20514
20599
|
});
|
|
20515
20600
|
} catch (err) {
|
|
20516
20601
|
logger.warn(`[selector] revoked publish failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -20519,11 +20604,12 @@ var ActionSelector = class {
|
|
|
20519
20604
|
this._lastRevoked = { schema: deliberating.schema, tick };
|
|
20520
20605
|
return {
|
|
20521
20606
|
commands: {
|
|
20522
|
-
set: [revocationEntity(deliberating.id, deliberating.schema,
|
|
20607
|
+
set: [revocationEntity(deliberating.id, deliberating.schema, revRupture, tick)],
|
|
20523
20608
|
metrics: [
|
|
20524
20609
|
["agency.field.eligible", eligible.length],
|
|
20525
20610
|
["agency.selection.busy", 1],
|
|
20526
20611
|
["agency.commitment.revoked", 1],
|
|
20612
|
+
...policyRevoke ? [["agency.policy.revoked", 1]] : [],
|
|
20527
20613
|
...stabMetrics
|
|
20528
20614
|
]
|
|
20529
20615
|
}
|
|
@@ -20734,6 +20820,17 @@ function computeRupture(state, tick, senseEvents = []) {
|
|
|
20734
20820
|
if (maxSalience <= RUPTURE_SALIENCE_GATE) return 0;
|
|
20735
20821
|
return clamp018((maxSalience - RUPTURE_SALIENCE_GATE) / (1 - RUPTURE_SALIENCE_GATE));
|
|
20736
20822
|
}
|
|
20823
|
+
function refusedClassSchemas(state) {
|
|
20824
|
+
const out = /* @__PURE__ */ new Set();
|
|
20825
|
+
for (const e of state.entities.values()) {
|
|
20826
|
+
if (e.type !== "agency.outcome") continue;
|
|
20827
|
+
const m = e.metadata;
|
|
20828
|
+
if (m?.["refused"] !== true || str2(m?.["finality"]) !== "class") continue;
|
|
20829
|
+
const schema = str2(m?.["schema"]);
|
|
20830
|
+
if (schema) out.add(schema);
|
|
20831
|
+
}
|
|
20832
|
+
return out;
|
|
20833
|
+
}
|
|
20737
20834
|
function effectiveWeights(state) {
|
|
20738
20835
|
const p = readEffectiveParams(state, "engine-config-action-selector");
|
|
20739
20836
|
return {
|
|
@@ -21121,6 +21218,7 @@ var MotorSchemaExecutor = class {
|
|
|
21121
21218
|
}
|
|
21122
21219
|
for (const [id, e] of state.entities) {
|
|
21123
21220
|
if (e.type !== "agency.intent" || str5(e.metadata?.["status"]) !== "awaiting") continue;
|
|
21221
|
+
if (e.metadata?.["escalated"] === true) continue;
|
|
21124
21222
|
const dispatchedAt = num3(e.metadata?.["dispatchedAt"], tick);
|
|
21125
21223
|
if (tick - dispatchedAt < AWAIT_TIMEOUT) continue;
|
|
21126
21224
|
const intent = readIntent(id, e.metadata);
|
|
@@ -21631,12 +21729,24 @@ var ReafferenceEngine = class {
|
|
|
21631
21729
|
}
|
|
21632
21730
|
let updates = 0;
|
|
21633
21731
|
let discovered = 0;
|
|
21732
|
+
let refused = 0;
|
|
21634
21733
|
for (const { id, meta: m, fromState } of outcomes) {
|
|
21635
21734
|
const schema = str6(m["schema"]);
|
|
21636
21735
|
if (!schema) {
|
|
21637
21736
|
if (fromState) del.push(id);
|
|
21638
21737
|
continue;
|
|
21639
21738
|
}
|
|
21739
|
+
if (m["refused"] === true) {
|
|
21740
|
+
const finality = str6(m["finality"]) === "class" ? "class" : "instance";
|
|
21741
|
+
this._repertoire.recordRefusal(schema, finality, tick);
|
|
21742
|
+
if (fromState) del.push(id);
|
|
21743
|
+
const refusedIntent = str6(m["intentId"]);
|
|
21744
|
+
if (refusedIntent) del.push(refusedIntent);
|
|
21745
|
+
const refusedPlan = str6(m["planId"]);
|
|
21746
|
+
if (refusedPlan) this._emitPlanOutcome(refusedPlan, str6(m["stepId"]), schema, false, 0, 0, tick);
|
|
21747
|
+
refused++;
|
|
21748
|
+
continue;
|
|
21749
|
+
}
|
|
21640
21750
|
const { skill, proceduralized } = this._repertoire.recordOutcome({
|
|
21641
21751
|
schema,
|
|
21642
21752
|
success: m["success"] === true,
|
|
@@ -21663,11 +21773,14 @@ var ReafferenceEngine = class {
|
|
|
21663
21773
|
}
|
|
21664
21774
|
}
|
|
21665
21775
|
const dropped = this._repertoire.decay(tick);
|
|
21666
|
-
for (const id of dropped) {
|
|
21776
|
+
for (const id of dropped.skills) {
|
|
21667
21777
|
del.push(`agency-skill-${id}`);
|
|
21668
21778
|
del.push(schemaEntityId(id));
|
|
21669
21779
|
}
|
|
21780
|
+
for (const id of dropped.availability)
|
|
21781
|
+
del.push(availabilityEntityId(id));
|
|
21670
21782
|
for (const e of this._repertoire.compositeEntities()) set.push(e);
|
|
21783
|
+
for (const e of this._repertoire.availabilityEntities()) set.push(e);
|
|
21671
21784
|
const skills = this._repertoire.skills();
|
|
21672
21785
|
const habitual = [...skills.values()].filter((s) => s.habitStrength >= PROC_THRESHOLD2).length;
|
|
21673
21786
|
metrics.push(
|
|
@@ -21677,6 +21790,7 @@ var ReafferenceEngine = class {
|
|
|
21677
21790
|
["agency.habitual.count", habitual],
|
|
21678
21791
|
["agency.sensory.confirmed", sensory]
|
|
21679
21792
|
);
|
|
21793
|
+
if (refused > 0) metrics.push(["agency.refused.count", refused]);
|
|
21680
21794
|
return { commands: { set, delete: del, metrics } };
|
|
21681
21795
|
}
|
|
21682
21796
|
_emitProceduralized(skill, tick) {
|
|
@@ -24915,6 +25029,7 @@ function reconcileInvocation(intentId, schema, result, tick, predicted = { rewar
|
|
|
24915
25029
|
mode: "external",
|
|
24916
25030
|
tick,
|
|
24917
25031
|
reconciled: true,
|
|
25032
|
+
...result.refused ? { refused: true, finality: result.finality ?? "instance" } : {},
|
|
24918
25033
|
...provenance.planId ? { planId: provenance.planId } : {},
|
|
24919
25034
|
...provenance.stepId ? { stepId: provenance.stepId } : {}
|
|
24920
25035
|
}
|
|
@@ -24924,8 +25039,56 @@ function clamp0112(n) {
|
|
|
24924
25039
|
return n < 0 ? 0 : n > 1 ? 1 : n;
|
|
24925
25040
|
}
|
|
24926
25041
|
|
|
25042
|
+
// src/stem/policy/arbiter.ts
|
|
25043
|
+
var ALLOW = Object.freeze({ decision: "allow" });
|
|
25044
|
+
var NULL_ARBITER = {
|
|
25045
|
+
name: "null",
|
|
25046
|
+
evaluate() {
|
|
25047
|
+
return ALLOW;
|
|
25048
|
+
}
|
|
25049
|
+
};
|
|
25050
|
+
function isNullArbiter(arbiter) {
|
|
25051
|
+
return !arbiter || arbiter === NULL_ARBITER;
|
|
25052
|
+
}
|
|
25053
|
+
|
|
25054
|
+
// src/stem/policy/verdict.recorder.ts
|
|
25055
|
+
var _sinks3 = /* @__PURE__ */ new Map();
|
|
25056
|
+
function getVerdictRecorder(willId) {
|
|
25057
|
+
return _sinks3.get(willId);
|
|
25058
|
+
}
|
|
25059
|
+
var _sources3 = /* @__PURE__ */ new Map();
|
|
25060
|
+
function getVerdictSource(willId) {
|
|
25061
|
+
return _sources3.get(willId);
|
|
25062
|
+
}
|
|
25063
|
+
|
|
24927
25064
|
// src/stem/tracts/effector.controller.ts
|
|
25065
|
+
var ESCALATION_TTL_TICKS = 30;
|
|
24928
25066
|
var effectorController = class {
|
|
25067
|
+
/** The Policy Decision Point consulted before an invocation reaches the world.
|
|
25068
|
+
* Defaults to the no-op arbiter, so an unconfigured Will is byte-identical. */
|
|
25069
|
+
_arbiter = NULL_ARBITER;
|
|
25070
|
+
/**
|
|
25071
|
+
* Denials queued during a step's flush, drained at the NEXT tick boundary
|
|
25072
|
+
* (POLICY_REAFFERENCE P1). Keyed by willId — harness state, exactly like
|
|
25073
|
+
* `pendingEffectorInvocations`; never simulation state, so it does not touch
|
|
25074
|
+
* `simulation.step` determinism and is regenerated on any re-execution.
|
|
25075
|
+
*/
|
|
25076
|
+
_pendingRefusals = /* @__PURE__ */ new Map();
|
|
25077
|
+
/** Escalations awaiting their first application (mark intent + voice the ask). */
|
|
25078
|
+
_newEscalations = /* @__PURE__ */ new Map();
|
|
25079
|
+
/** Escalations currently held, keyed by intent id — the resolvable set. */
|
|
25080
|
+
_activeEscalations = /* @__PURE__ */ new Map();
|
|
25081
|
+
/** Host answers awaiting application at the next tick boundary. */
|
|
25082
|
+
_pendingResolutions = /* @__PURE__ */ new Map();
|
|
25083
|
+
/**
|
|
25084
|
+
* Install a Policy Decision Point (POLICY_REAFFERENCE P0). Passing null
|
|
25085
|
+
* restores the no-op default. The arbiter sees only the proposed act — never
|
|
25086
|
+
* simulation state — and its verdict decides whether the invocation is
|
|
25087
|
+
* handed to the host at all.
|
|
25088
|
+
*/
|
|
25089
|
+
setArbiter(arbiter) {
|
|
25090
|
+
this._arbiter = arbiter ?? NULL_ARBITER;
|
|
25091
|
+
}
|
|
24929
25092
|
/**
|
|
24930
25093
|
* Update the set of allowed communication effectors at runtime via AccessGrants
|
|
24931
25094
|
* (the permission / sense gate the senses + reply path read).
|
|
@@ -24941,6 +25104,224 @@ var effectorController = class {
|
|
|
24941
25104
|
* echoes it on its result-ack, and `confirmExecution` uses it to find the intent.
|
|
24942
25105
|
*/
|
|
24943
25106
|
bufferInvocation(instance, payload) {
|
|
25107
|
+
const willId = instance.config.id;
|
|
25108
|
+
const source = getVerdictSource(willId);
|
|
25109
|
+
if (source) {
|
|
25110
|
+
const invocation2 = toPolicyInvocation(instance, payload);
|
|
25111
|
+
const record = source.verdictFor(invocation2.tick, invocation2.intentId);
|
|
25112
|
+
if (record) this._applyVerdict(instance, payload, invocation2, recordToVerdict(record));
|
|
25113
|
+
else this._buffer(instance, payload);
|
|
25114
|
+
return;
|
|
25115
|
+
}
|
|
25116
|
+
if (isNullArbiter(this._arbiter)) {
|
|
25117
|
+
this._buffer(instance, payload);
|
|
25118
|
+
return;
|
|
25119
|
+
}
|
|
25120
|
+
const invocation = toPolicyInvocation(instance, payload);
|
|
25121
|
+
let verdict;
|
|
25122
|
+
try {
|
|
25123
|
+
verdict = this._arbiter.evaluate(invocation);
|
|
25124
|
+
} catch (err) {
|
|
25125
|
+
logger.error(`[policy] arbiter "${this._arbiter.name}" threw for "${invocation.schema}" \u2014 failing closed:`, err);
|
|
25126
|
+
return;
|
|
25127
|
+
}
|
|
25128
|
+
if (verdict instanceof Promise) {
|
|
25129
|
+
void verdict.then(
|
|
25130
|
+
(v) => this._recordAndApply(instance, payload, invocation, v),
|
|
25131
|
+
(err) => logger.error(`[policy] arbiter "${this._arbiter.name}" rejected for "${invocation.schema}" \u2014 failing closed:`, err)
|
|
25132
|
+
);
|
|
25133
|
+
return;
|
|
25134
|
+
}
|
|
25135
|
+
this._recordAndApply(instance, payload, invocation, verdict);
|
|
25136
|
+
}
|
|
25137
|
+
/** Capture the verdict on the tape (if a recorder is attached), then enforce it. */
|
|
25138
|
+
_recordAndApply(instance, payload, invocation, verdict) {
|
|
25139
|
+
const sink = getVerdictRecorder(instance.config.id);
|
|
25140
|
+
sink?.recordVerdict({
|
|
25141
|
+
tick: invocation.tick,
|
|
25142
|
+
willId: instance.config.id,
|
|
25143
|
+
intentId: invocation.intentId,
|
|
25144
|
+
schema: invocation.schema,
|
|
25145
|
+
arbiter: this._arbiter.name,
|
|
25146
|
+
decision: verdict.decision,
|
|
25147
|
+
...verdict.reasonCode ? { reasonCode: verdict.reasonCode } : {},
|
|
25148
|
+
...verdict.finality ? { finality: verdict.finality } : {},
|
|
25149
|
+
...verdict.counterfactual ? { counterfactual: verdict.counterfactual } : {},
|
|
25150
|
+
timestamp: Date.now()
|
|
25151
|
+
});
|
|
25152
|
+
this._applyVerdict(instance, payload, invocation, verdict);
|
|
25153
|
+
}
|
|
25154
|
+
/**
|
|
25155
|
+
* Enforce a verdict (POLICY_REAFFERENCE P1).
|
|
25156
|
+
*
|
|
25157
|
+
* • allow → hand the invocation to the world.
|
|
25158
|
+
* • deny → queue a refusal ack, applied at the next tick boundary via
|
|
25159
|
+
* `confirmExecution` — the same lifecycle as a host rejection,
|
|
25160
|
+
* so the mind meets *world resistance*, not a permission dialog.
|
|
25161
|
+
* • escalate → raise a held escalation (POLICY_REAFFERENCE P4): the intent is
|
|
25162
|
+
* held (the executor stops timing it out), the Will voices a
|
|
25163
|
+
* first-person ask once, and a host resolution later approves
|
|
25164
|
+
* (dispatch) or denies (refuse). Unresolved, it degrades to a
|
|
25165
|
+
* refusal at ESCALATION_TTL_TICKS.
|
|
25166
|
+
*
|
|
25167
|
+
* P1's refusal reconciles as a plain FAILURE — safe, but the wrong learning
|
|
25168
|
+
* signal (forbidden ≠ unskilled). P2 routes it to affordance AVAILABILITY
|
|
25169
|
+
* instead of competence.
|
|
25170
|
+
*/
|
|
25171
|
+
_applyVerdict(instance, payload, invocation, verdict) {
|
|
25172
|
+
if (verdict.decision === "allow") {
|
|
25173
|
+
this._buffer(instance, payload);
|
|
25174
|
+
return;
|
|
25175
|
+
}
|
|
25176
|
+
const cf = verdict.counterfactual;
|
|
25177
|
+
logger.info(
|
|
25178
|
+
`[policy] ${verdict.decision.toUpperCase()} "${invocation.schema}" intent "${invocation.intentId}" \u2014 ${verdict.reasonCode ?? "no reason code"}` + (verdict.finality ? ` (${verdict.finality})` : "") + (cf ? ` [${cf.field}: requested ${JSON.stringify(cf.requested)}, allowed ${JSON.stringify(cf.allowed)}]` : "")
|
|
25179
|
+
);
|
|
25180
|
+
if (verdict.decision === "deny") {
|
|
25181
|
+
const queue = this._pendingRefusals.get(instance.config.id) ?? [];
|
|
25182
|
+
queue.push({
|
|
25183
|
+
intentId: invocation.intentId,
|
|
25184
|
+
schema: invocation.schema,
|
|
25185
|
+
reasonCode: verdict.reasonCode ?? "POLICY_DENIED",
|
|
25186
|
+
finality: verdict.finality ?? "instance"
|
|
25187
|
+
});
|
|
25188
|
+
this._pendingRefusals.set(instance.config.id, queue);
|
|
25189
|
+
return;
|
|
25190
|
+
}
|
|
25191
|
+
const escalations = this._newEscalations.get(instance.config.id) ?? [];
|
|
25192
|
+
escalations.push({
|
|
25193
|
+
intentId: invocation.intentId,
|
|
25194
|
+
schema: invocation.schema,
|
|
25195
|
+
reasonCode: verdict.reasonCode ?? "APPROVAL_REQUIRED",
|
|
25196
|
+
payload,
|
|
25197
|
+
expiresAt: 0
|
|
25198
|
+
// stamped when applied (we don't have the current tick here)
|
|
25199
|
+
});
|
|
25200
|
+
this._newEscalations.set(instance.config.id, escalations);
|
|
25201
|
+
}
|
|
25202
|
+
/**
|
|
25203
|
+
* Record a host's answer to an escalation (POLICY_REAFFERENCE P4). Applied at
|
|
25204
|
+
* the next tick boundary so every simulation-state write stays on the boundary:
|
|
25205
|
+
* approve dispatches the held invocation to the world; deny refuses it. A
|
|
25206
|
+
* no-op if the intent id is not (or no longer) an active escalation.
|
|
25207
|
+
*/
|
|
25208
|
+
resolveEscalation(instance, intentId, approved) {
|
|
25209
|
+
const queue = this._pendingResolutions.get(instance.config.id) ?? [];
|
|
25210
|
+
queue.push({ intentId, approved });
|
|
25211
|
+
this._pendingResolutions.set(instance.config.id, queue);
|
|
25212
|
+
}
|
|
25213
|
+
/**
|
|
25214
|
+
* Apply queued policy refusals as failure acks (POLICY_REAFFERENCE P1).
|
|
25215
|
+
* Called by the tick loop at the same boundary as inbound acks — BEFORE the
|
|
25216
|
+
* step, stamped to this tick — so a denial reconciled here is the exact
|
|
25217
|
+
* lifecycle of a host rejection that arrived between ticks.
|
|
25218
|
+
*/
|
|
25219
|
+
applyPolicyOutcomes(instance) {
|
|
25220
|
+
const tick = instance.tickCount;
|
|
25221
|
+
this._applyResolutions(instance);
|
|
25222
|
+
this._expireEscalations(instance, tick);
|
|
25223
|
+
this._applyNewEscalations(instance, tick);
|
|
25224
|
+
this._applyRefusals(instance);
|
|
25225
|
+
}
|
|
25226
|
+
/** Drain queued refusals into failure acks (POLICY_REAFFERENCE P1). */
|
|
25227
|
+
_applyRefusals(instance) {
|
|
25228
|
+
const queue = this._pendingRefusals.get(instance.config.id);
|
|
25229
|
+
if (!queue || queue.length === 0) return;
|
|
25230
|
+
this._pendingRefusals.set(instance.config.id, []);
|
|
25231
|
+
for (const refusal of queue)
|
|
25232
|
+
this.confirmExecution(instance, refusal.intentId, {
|
|
25233
|
+
success: false,
|
|
25234
|
+
refused: true,
|
|
25235
|
+
finality: refusal.finality === "class" ? "class" : "instance",
|
|
25236
|
+
description: `refused by policy: ${refusal.reasonCode} (${refusal.finality})`
|
|
25237
|
+
});
|
|
25238
|
+
}
|
|
25239
|
+
/** Raise each newly-escalated intent (POLICY_REAFFERENCE P4): mark it held in
|
|
25240
|
+
* simulation state, voice the ask ONCE, and move it to the resolvable set. */
|
|
25241
|
+
_applyNewEscalations(instance, tick) {
|
|
25242
|
+
const pending = this._newEscalations.get(instance.config.id);
|
|
25243
|
+
if (!pending || pending.length === 0) return;
|
|
25244
|
+
this._newEscalations.set(instance.config.id, []);
|
|
25245
|
+
const active = this._activeEscalations.get(instance.config.id) ?? /* @__PURE__ */ new Map();
|
|
25246
|
+
for (const esc of pending) {
|
|
25247
|
+
esc.expiresAt = tick + ESCALATION_TTL_TICKS;
|
|
25248
|
+
this._markEscalated(instance, esc.intentId, esc.expiresAt);
|
|
25249
|
+
this._voiceEscalation(instance, esc);
|
|
25250
|
+
active.set(esc.intentId, esc);
|
|
25251
|
+
}
|
|
25252
|
+
this._activeEscalations.set(instance.config.id, active);
|
|
25253
|
+
}
|
|
25254
|
+
/** Apply host answers to active escalations (POLICY_REAFFERENCE P4). */
|
|
25255
|
+
_applyResolutions(instance) {
|
|
25256
|
+
const queue = this._pendingResolutions.get(instance.config.id);
|
|
25257
|
+
if (!queue || queue.length === 0) return;
|
|
25258
|
+
this._pendingResolutions.set(instance.config.id, []);
|
|
25259
|
+
const active = this._activeEscalations.get(instance.config.id);
|
|
25260
|
+
for (const { intentId, approved } of queue) {
|
|
25261
|
+
const esc = active?.get(intentId);
|
|
25262
|
+
if (!esc) continue;
|
|
25263
|
+
active.delete(intentId);
|
|
25264
|
+
this._clearEscalated(instance, intentId);
|
|
25265
|
+
if (approved) {
|
|
25266
|
+
this._buffer(instance, esc.payload);
|
|
25267
|
+
logger.info(`[policy] escalation APPROVED \u2192 dispatching "${esc.schema}" intent "${intentId}"`);
|
|
25268
|
+
} else {
|
|
25269
|
+
this._queueRefusal(instance, esc.intentId, esc.schema, esc.reasonCode, "class");
|
|
25270
|
+
logger.info(`[policy] escalation DENIED \u2192 refusing "${esc.schema}" intent "${intentId}"`);
|
|
25271
|
+
}
|
|
25272
|
+
}
|
|
25273
|
+
}
|
|
25274
|
+
/** Degrade escalations no one answered in time into instance-refusals (P4). */
|
|
25275
|
+
_expireEscalations(instance, tick) {
|
|
25276
|
+
const active = this._activeEscalations.get(instance.config.id);
|
|
25277
|
+
if (!active || active.size === 0) return;
|
|
25278
|
+
for (const [intentId, esc] of active) {
|
|
25279
|
+
if (tick < esc.expiresAt) continue;
|
|
25280
|
+
active.delete(intentId);
|
|
25281
|
+
this._clearEscalated(instance, intentId);
|
|
25282
|
+
this._queueRefusal(instance, esc.intentId, esc.schema, "ESCALATION_EXPIRED", "instance");
|
|
25283
|
+
logger.info(`[policy] escalation EXPIRED \u2192 refusing "${esc.schema}" intent "${intentId}"`);
|
|
25284
|
+
}
|
|
25285
|
+
}
|
|
25286
|
+
/** Push a refusal onto the queue drained by _applyRefusals this same tick. */
|
|
25287
|
+
_queueRefusal(instance, intentId, schema, reasonCode, finality) {
|
|
25288
|
+
const queue = this._pendingRefusals.get(instance.config.id) ?? [];
|
|
25289
|
+
queue.push({ intentId, schema, reasonCode, finality });
|
|
25290
|
+
this._pendingRefusals.set(instance.config.id, queue);
|
|
25291
|
+
}
|
|
25292
|
+
/** Mark the awaiting intent held: the executor stops timing it out (P4). */
|
|
25293
|
+
_markEscalated(instance, intentId, expiresAt) {
|
|
25294
|
+
const intent = instance.simulation.stateManager.snapshot().entities.get(intentId);
|
|
25295
|
+
if (!intent || intent.type !== "agency.intent") return;
|
|
25296
|
+
instance.simulation.stateManager.setEntity({
|
|
25297
|
+
id: intent.id,
|
|
25298
|
+
type: intent.type,
|
|
25299
|
+
metadata: { ...intent.metadata ?? {}, escalated: true, escalationExpiresAt: expiresAt }
|
|
25300
|
+
});
|
|
25301
|
+
}
|
|
25302
|
+
/** Release the hold so the executor resumes normal timeout for this intent. */
|
|
25303
|
+
_clearEscalated(instance, intentId) {
|
|
25304
|
+
const intent = instance.simulation.stateManager.snapshot().entities.get(intentId);
|
|
25305
|
+
if (!intent || intent.type !== "agency.intent") return;
|
|
25306
|
+
const meta = { ...intent.metadata ?? {} };
|
|
25307
|
+
delete meta["escalated"];
|
|
25308
|
+
delete meta["escalationExpiresAt"];
|
|
25309
|
+
instance.simulation.stateManager.setEntity({ id: intent.id, type: intent.type, metadata: meta });
|
|
25310
|
+
}
|
|
25311
|
+
/** Voice the escalation as a first-person broadcast ask — once, at raise time. */
|
|
25312
|
+
_voiceEscalation(instance, esc) {
|
|
25313
|
+
try {
|
|
25314
|
+
instance.cognition.outboxWriter.enqueue({
|
|
25315
|
+
targetEntityId: "*",
|
|
25316
|
+
content: escalationAsk(esc.schema, esc.reasonCode),
|
|
25317
|
+
effectorName: "broadcast"
|
|
25318
|
+
});
|
|
25319
|
+
} catch (err) {
|
|
25320
|
+
logger.warn(`[policy] escalation voice failed for "${esc.schema}": ${errMsg2(err)}`);
|
|
25321
|
+
}
|
|
25322
|
+
}
|
|
25323
|
+
/** Queue an approved invocation for the delivery layer. */
|
|
25324
|
+
_buffer(instance, payload) {
|
|
24944
25325
|
const intentId = payload.intentId ?? "";
|
|
24945
25326
|
instance.pendingEffectorInvocations.push({
|
|
24946
25327
|
id: intentId,
|
|
@@ -25015,6 +25396,38 @@ var effectorController = class {
|
|
|
25015
25396
|
function num5(v, fallback) {
|
|
25016
25397
|
return typeof v === "number" && Number.isFinite(v) ? v : fallback;
|
|
25017
25398
|
}
|
|
25399
|
+
function escalationAsk(schema, reasonCode) {
|
|
25400
|
+
const meaning = ESCALATION_MEANINGS[reasonCode] ?? "I need your approval before I can do this";
|
|
25401
|
+
return `I want to ${schema}, but ${meaning}. May I go ahead?`;
|
|
25402
|
+
}
|
|
25403
|
+
var ESCALATION_MEANINGS = {
|
|
25404
|
+
APPROVAL_REQUIRED: "I need your approval before I can on my own",
|
|
25405
|
+
WRITE_REQUIRES_APPROVAL: "it writes to the world and I shouldn't on my own",
|
|
25406
|
+
PAYMENT_REQUIRES_APPROVAL: "it moves money and I must not do that unattended",
|
|
25407
|
+
DEPLOY_REQUIRES_APPROVAL: "it ships something and needs a human to sign off"
|
|
25408
|
+
};
|
|
25409
|
+
function errMsg2(err) {
|
|
25410
|
+
return err instanceof Error ? err.message : String(err);
|
|
25411
|
+
}
|
|
25412
|
+
function recordToVerdict(record) {
|
|
25413
|
+
return {
|
|
25414
|
+
decision: record.decision,
|
|
25415
|
+
...record.reasonCode ? { reasonCode: record.reasonCode } : {},
|
|
25416
|
+
...record.finality ? { finality: record.finality } : {},
|
|
25417
|
+
...record.counterfactual ? { counterfactual: record.counterfactual } : {}
|
|
25418
|
+
};
|
|
25419
|
+
}
|
|
25420
|
+
function toPolicyInvocation(instance, payload) {
|
|
25421
|
+
return {
|
|
25422
|
+
willId: instance.config.id,
|
|
25423
|
+
intentId: payload.intentId ?? "",
|
|
25424
|
+
schema: payload.schema ?? "",
|
|
25425
|
+
parameters: payload.parameters ?? {},
|
|
25426
|
+
...typeof payload.targetEntityId === "string" ? { targetEntityId: payload.targetEntityId } : {},
|
|
25427
|
+
...typeof payload.description === "string" ? { description: payload.description } : {},
|
|
25428
|
+
tick: payload.tick ?? 0
|
|
25429
|
+
};
|
|
25430
|
+
}
|
|
25018
25431
|
|
|
25019
25432
|
// src/stem/tracts/sensory.controller.ts
|
|
25020
25433
|
var SensoryController = class {
|
|
@@ -25817,6 +26230,15 @@ var WillStem = class {
|
|
|
25817
26230
|
confirmEffectorExecution(id, invocationId, result) {
|
|
25818
26231
|
this._effector.confirmExecution(this._get(id), invocationId, result);
|
|
25819
26232
|
}
|
|
26233
|
+
/**
|
|
26234
|
+
* Resolve a policy escalation the Will raised (POLICY_REAFFERENCE P4).
|
|
26235
|
+
* `approved` dispatches the held invocation to the world; otherwise it is
|
|
26236
|
+
* refused. Applied at the next tick boundary. `invocationId` is the awaiting
|
|
26237
|
+
* `agency.intent` id the escalation ask referenced.
|
|
26238
|
+
*/
|
|
26239
|
+
resolveEscalation(id, invocationId, approved) {
|
|
26240
|
+
this._effector.resolveEscalation(this._get(id), invocationId, approved);
|
|
26241
|
+
}
|
|
25820
26242
|
// ── Messaging / outbox (11.1) ────────────────────────────────────────────
|
|
25821
26243
|
// Delegates to OutboxController (R5-c). `_get(id)` validates the Will exists
|
|
25822
26244
|
// and supplies the WillInstance; the outbox ops touch only instance fields.
|
|
@@ -25947,6 +26369,7 @@ var WillStem = class {
|
|
|
25947
26369
|
outbox: this._outbox,
|
|
25948
26370
|
sensory: this._sensory
|
|
25949
26371
|
});
|
|
26372
|
+
this._effector.applyPolicyOutcomes(instance);
|
|
25950
26373
|
await instance.simulation.step(1);
|
|
25951
26374
|
instance.tickCount++;
|
|
25952
26375
|
instance.lastTickAt = /* @__PURE__ */ new Date();
|