@mindot/will 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/channels/discord.d.ts +67 -6
- package/dist/channels/discord.js +112 -6
- package/dist/channels/discord.js.map +1 -1
- package/dist/channels/whatsapp.d.ts +1 -1
- package/dist/channels/whatsapp.js +4 -1
- package/dist/channels/whatsapp.js.map +1 -1
- package/dist/cli.js +3174 -867
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3236 -1042
- package/dist/index.js.map +1 -1
- package/dist/mcp/effectors.d.ts +1 -1
- package/dist/{will-cS6k4uiJ.d.ts → will-DbDj_TEH.d.ts} +752 -17
- package/package.json +1 -1
- package/src/channels/discord.ts +189 -11
- package/src/channels/types.ts +90 -0
- package/src/channels/whatsapp.ts +13 -4
- package/src/cli.ts +9 -4
- package/src/cognition/agency/consequence.ts +122 -1
- package/src/cognition/agency/conversation.aim.ts +260 -0
- package/src/cognition/agency/engines/action.selector.ts +83 -2
- package/src/cognition/agency/engines/affordance.synthesizer.ts +90 -1
- package/src/cognition/agency/engines/motor.schema.executor.ts +152 -10
- package/src/cognition/agency/engines/reafference.engine.ts +117 -0
- package/src/cognition/agency/proactive.communicator.ts +19 -3
- package/src/cognition/agency/restart.ts +66 -0
- package/src/cognition/agency/selection.scoring.ts +33 -0
- package/src/cognition/agency/types.ts +35 -0
- package/src/cognition/cache/composition.ts +232 -0
- package/src/cognition/cache/deliberation.cache.ts +219 -0
- package/src/cognition/cache/fingerprint.ts +120 -0
- package/src/cognition/cache/types.ts +105 -0
- package/src/cognition/config.mirror.entities.ts +108 -0
- package/src/cognition/event.schemas.ts +22 -0
- package/src/cognition/faculties/autobiographical.narrator.ts +5 -10
- package/src/cognition/faculties/episodic.consolidator.ts +59 -3
- package/src/cognition/faculties/executive.engine/commands.ts +189 -14
- package/src/cognition/faculties/executive.engine/context.ts +67 -13
- package/src/cognition/faculties/executive.engine/deliberate.reasoning.ts +1 -1
- package/src/cognition/faculties/executive.engine/engine.ts +552 -131
- package/src/cognition/faculties/executive.engine/escalation.buffer.ts +162 -44
- package/src/cognition/faculties/executive.engine/facet.supervisor.ts +310 -65
- package/src/cognition/faculties/executive.engine/facet.ts +81 -26
- package/src/cognition/faculties/executive.engine/gating.ts +14 -14
- package/src/cognition/faculties/executive.engine/parser.ts +21 -1
- package/src/cognition/faculties/executive.engine/prompt.factory.ts +167 -19
- package/src/cognition/faculties/executive.engine/types.ts +69 -0
- package/src/cognition/faculties/goal.manager.ts +94 -14
- package/src/cognition/faculties/known.entity.tracker.ts +267 -28
- package/src/cognition/faculties/moral.evaluator.ts +8 -3
- package/src/cognition/faculties/persona.consolidator.ts +141 -0
- package/src/cognition/faculties/reputation.tracker.ts +66 -2
- package/src/cognition/faculties/self.model.updater.ts +19 -12
- package/src/cognition/faculties/social.perception.ts +47 -3
- package/src/cognition/faculties/threat.evaluator.ts +7 -0
- package/src/cognition/faculties/working.memory.ts +10 -20
- package/src/cognition/identity.entity.ts +205 -0
- package/src/cognition/index.ts +7 -0
- package/src/cognition/memory/vector.adapter.ts +12 -3
- package/src/cognition/memory/vector.embedder.ts +45 -2
- package/src/cognition/persona.prior.ts +6 -0
- package/src/cognition/senses/audition.engine/engine.ts +404 -46
- package/src/cognition/senses/base.sense.engine.ts +1 -1
- package/src/cognition/senses/index.ts +12 -0
- package/src/cognition/social.identity.ts +273 -0
- package/src/cognition/utilities/token.tracker.ts +58 -5
- package/src/core/orchestrator.ts +38 -0
- package/src/llm/index.ts +25 -8
- package/src/llm/routing.ts +6 -0
- package/src/llm/summarizer.ts +1 -1
- package/src/llm/wire.contracts.ts +19 -0
- package/src/pma/index.ts +67 -53
- package/src/sdk/will.ts +39 -6
- package/src/stem/assembly.audit.ts +1 -0
- package/src/stem/guards/identity.coherence.ts +1 -1
- package/src/stem/index.ts +79 -2
- package/src/stem/mind.ts +172 -55
- package/src/stem/tracts/outbox.writer.ts +40 -2
- package/src/cognition/faculties/executive.engine/messages.ts +0 -102
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/cognition/cache/composition.ts
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Compositional operators for cache interpolation.
|
|
7
|
+
*
|
|
8
|
+
* Design principle (from the research sketch): interpolate what is safe to
|
|
9
|
+
* interpolate, copy verbatim what is not. Executive actions carry free-text
|
|
10
|
+
* fields (`reasoning`, `expectedOutcome`) that would turn to gibberish under
|
|
11
|
+
* blending, so the ACTIONS block uses a weighted *vote* over action type and
|
|
12
|
+
* then copies the winning neighbor's action objects verbatim. Numeric scalars
|
|
13
|
+
* (goal priority, belief confidence) are the only fields we mean-blend.
|
|
14
|
+
*
|
|
15
|
+
* Every operator is deterministic: inputs arrive pre-sorted from the cache and
|
|
16
|
+
* ties are resolved by neighbor order (already deterministic), never by hashing.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { ExecutiveOutputFull } from '#faculties/executive.engine/types'
|
|
20
|
+
import type { ScoredNeighbor, CacheScope } from './types'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Compose a valid ExecutiveOutputFull from scored neighbors. `neighbors` must be
|
|
24
|
+
* non-empty and ordered best-first (as the cache returns them). The three
|
|
25
|
+
* required fields (actions, reasoning, confidence) are always populated.
|
|
26
|
+
*/
|
|
27
|
+
export function composeOutput(
|
|
28
|
+
neighbors: ScoredNeighbor[],
|
|
29
|
+
tau: number,
|
|
30
|
+
scopes: CacheScope[],
|
|
31
|
+
): ExecutiveOutputFull {
|
|
32
|
+
const weights = _softmaxWeights( neighbors, tau )
|
|
33
|
+
|
|
34
|
+
// The best neighbor anchors required fields when a scope leaves them empty.
|
|
35
|
+
const anchor = neighbors[ 0 ]!.pattern.output
|
|
36
|
+
|
|
37
|
+
const out: ExecutiveOutputFull = {
|
|
38
|
+
actions: [],
|
|
39
|
+
reasoning: anchor.reasoning ?? '',
|
|
40
|
+
confidence: anchor.confidence ?? 0.5,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if( scopes.includes('actions') ){
|
|
44
|
+
const composed = _composeActions( neighbors, weights )
|
|
45
|
+
out.actions = composed.actions
|
|
46
|
+
out.reasoning = composed.reasoning
|
|
47
|
+
out.confidence = composed.confidence
|
|
48
|
+
} else {
|
|
49
|
+
// Actions are required downstream — fall back to the anchor's verbatim actions.
|
|
50
|
+
out.actions = _clone( anchor.actions ?? [] )
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if( scopes.includes('goals') )
|
|
54
|
+
out.newGoals = _composeGoals( neighbors, weights )
|
|
55
|
+
|
|
56
|
+
if( scopes.includes('beliefs') )
|
|
57
|
+
out.newBeliefs = _composeBeliefs( neighbors, weights )
|
|
58
|
+
|
|
59
|
+
return out
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ── Weight computation ─────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
function _softmaxWeights( neighbors: ScoredNeighbor[], tau: number ): number[] {
|
|
65
|
+
const t = tau > 0 ? tau : 1e-6
|
|
66
|
+
const sims = neighbors.map( n => n.similarity )
|
|
67
|
+
const maxSim = Math.max( ...sims )
|
|
68
|
+
const exps = sims.map( s => Math.exp( ( s - maxSim ) / t ) ) // shift for stability
|
|
69
|
+
const sum = exps.reduce( ( a, b ) => a + b, 0 )
|
|
70
|
+
return exps.map( e => ( sum === 0 ? 1 / exps.length : e / sum ) )
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ── ACTIONS: weighted type vote + verbatim copy ────────────
|
|
74
|
+
|
|
75
|
+
function _composeActions(
|
|
76
|
+
neighbors: ScoredNeighbor[],
|
|
77
|
+
weights: number[],
|
|
78
|
+
): { actions: ExecutiveOutputFull['actions']; reasoning: string; confidence: number } {
|
|
79
|
+
// Vote on each neighbor's primary (first) action type.
|
|
80
|
+
const typeWeight = new Map<string, number>()
|
|
81
|
+
for( let i = 0; i < neighbors.length; i++ ){
|
|
82
|
+
const acts = neighbors[ i ]!.pattern.output.actions ?? []
|
|
83
|
+
const primary = acts[ 0 ]?.type
|
|
84
|
+
if( primary === undefined ) continue
|
|
85
|
+
typeWeight.set( primary, ( typeWeight.get( primary ) ?? 0 ) + ( weights[ i ] ?? 0 ) )
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Winning type — deterministic: highest weight, ties broken by neighbor order
|
|
89
|
+
// (the first neighbor to reach the max keeps it, and neighbors are best-first).
|
|
90
|
+
let winningType: string | null = null
|
|
91
|
+
let bestWeight = -1
|
|
92
|
+
for( let i = 0; i < neighbors.length; i++ ){
|
|
93
|
+
const primary = neighbors[ i ]!.pattern.output.actions?.[ 0 ]?.type
|
|
94
|
+
if( primary === undefined ) continue
|
|
95
|
+
const w = typeWeight.get( primary ) ?? 0
|
|
96
|
+
if( w > bestWeight ){ bestWeight = w; winningType = primary }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Copy the full action list from the highest-weight neighbor whose primary
|
|
100
|
+
// action type matches the winner — verbatim, so text fields stay coherent.
|
|
101
|
+
let source = neighbors[ 0 ]!
|
|
102
|
+
for( let i = 0; i < neighbors.length; i++ ){
|
|
103
|
+
if( neighbors[ i ]!.pattern.output.actions?.[ 0 ]?.type === winningType ){
|
|
104
|
+
source = neighbors[ i ]!
|
|
105
|
+
break
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const src = source.pattern.output
|
|
110
|
+
return {
|
|
111
|
+
actions: _clone( src.actions ?? [] ),
|
|
112
|
+
reasoning: src.reasoning ?? '',
|
|
113
|
+
confidence: src.confidence ?? 0.5,
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ── GOALS: merge by description, mean priority (Phase 2) ────
|
|
118
|
+
|
|
119
|
+
function _composeGoals(
|
|
120
|
+
neighbors: ScoredNeighbor[],
|
|
121
|
+
weights: number[],
|
|
122
|
+
): ExecutiveOutputFull['newGoals'] {
|
|
123
|
+
type Agg = {
|
|
124
|
+
weight: number
|
|
125
|
+
priority: number
|
|
126
|
+
tags: string[]
|
|
127
|
+
completionType: string
|
|
128
|
+
completionCondition?: string
|
|
129
|
+
bestW: number
|
|
130
|
+
}
|
|
131
|
+
const byDesc = new Map<string, Agg>()
|
|
132
|
+
|
|
133
|
+
for( let i = 0; i < neighbors.length; i++ ){
|
|
134
|
+
const w = weights[ i ] ?? 0
|
|
135
|
+
for( const g of neighbors[ i ]!.pattern.output.newGoals ?? [] ){
|
|
136
|
+
const cur = byDesc.get( g.description )
|
|
137
|
+
if( cur ){
|
|
138
|
+
cur.weight += w
|
|
139
|
+
cur.priority += g.priority * w
|
|
140
|
+
if( w > cur.bestW ){
|
|
141
|
+
cur.bestW = w
|
|
142
|
+
cur.tags = g.tags
|
|
143
|
+
cur.completionType = g.completionType
|
|
144
|
+
cur.completionCondition = g.completionCondition
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
byDesc.set( g.description, {
|
|
148
|
+
weight: w,
|
|
149
|
+
priority: g.priority * w,
|
|
150
|
+
tags: g.tags,
|
|
151
|
+
completionType: g.completionType,
|
|
152
|
+
completionCondition: g.completionCondition,
|
|
153
|
+
bestW: w,
|
|
154
|
+
} )
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const result: NonNullable<ExecutiveOutputFull['newGoals']> = []
|
|
160
|
+
for( const [ description, a ] of byDesc ){
|
|
161
|
+
result.push( {
|
|
162
|
+
description,
|
|
163
|
+
priority: a.weight === 0 ? 0 : a.priority / a.weight,
|
|
164
|
+
tags: [ ...a.tags ],
|
|
165
|
+
completionType: a.completionType,
|
|
166
|
+
...( a.completionCondition !== undefined ? { completionCondition: a.completionCondition } : {} ),
|
|
167
|
+
} )
|
|
168
|
+
}
|
|
169
|
+
result.sort( ( x, y ) => y.priority - x.priority )
|
|
170
|
+
return result.slice( 0, 3 )
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ── BELIEFS: merge by statement, mean confidence (Phase 3) ─
|
|
174
|
+
|
|
175
|
+
function _composeBeliefs(
|
|
176
|
+
neighbors: ScoredNeighbor[],
|
|
177
|
+
weights: number[],
|
|
178
|
+
): ExecutiveOutputFull['newBeliefs'] {
|
|
179
|
+
type Agg = {
|
|
180
|
+
weight: number
|
|
181
|
+
confidence: number
|
|
182
|
+
category: string
|
|
183
|
+
evidence: 'single_observation' | 'recurring_pattern' | 'strong_pattern'
|
|
184
|
+
tags: string[]
|
|
185
|
+
bestW: number
|
|
186
|
+
}
|
|
187
|
+
const byStmt = new Map<string, Agg>()
|
|
188
|
+
|
|
189
|
+
for( let i = 0; i < neighbors.length; i++ ){
|
|
190
|
+
const w = weights[ i ] ?? 0
|
|
191
|
+
for( const b of neighbors[ i ]!.pattern.output.newBeliefs ?? [] ){
|
|
192
|
+
const cur = byStmt.get( b.statement )
|
|
193
|
+
if( cur ){
|
|
194
|
+
cur.weight += w
|
|
195
|
+
cur.confidence += b.confidence * w
|
|
196
|
+
if( w > cur.bestW ){
|
|
197
|
+
cur.bestW = w
|
|
198
|
+
cur.category = b.category
|
|
199
|
+
cur.evidence = b.evidence
|
|
200
|
+
cur.tags = b.tags
|
|
201
|
+
}
|
|
202
|
+
} else {
|
|
203
|
+
byStmt.set( b.statement, {
|
|
204
|
+
weight: w,
|
|
205
|
+
confidence: b.confidence * w,
|
|
206
|
+
category: b.category,
|
|
207
|
+
evidence: b.evidence,
|
|
208
|
+
tags: b.tags,
|
|
209
|
+
bestW: w,
|
|
210
|
+
} )
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const result: NonNullable<ExecutiveOutputFull['newBeliefs']> = []
|
|
216
|
+
for( const [ statement, a ] of byStmt ){
|
|
217
|
+
result.push( {
|
|
218
|
+
statement,
|
|
219
|
+
category: a.category,
|
|
220
|
+
confidence: a.weight === 0 ? 0 : a.confidence / a.weight,
|
|
221
|
+
evidence: a.evidence,
|
|
222
|
+
tags: [ ...a.tags ],
|
|
223
|
+
} )
|
|
224
|
+
}
|
|
225
|
+
return result
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ── Deterministic deep clone for verbatim-copied blocks ────
|
|
229
|
+
|
|
230
|
+
function _clone<T>( v: T ): T {
|
|
231
|
+
return JSON.parse( JSON.stringify( v ) ) as T
|
|
232
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/cognition/cache/deliberation.cache.ts
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* DeliberationCache — deterministic fast-path for executive reasoning.
|
|
7
|
+
*
|
|
8
|
+
* Stores past (fingerprint, output) pairs and composes a new output by
|
|
9
|
+
* interpolating neighbors of the *query* fingerprint. Pure, R2-safe, bounded.
|
|
10
|
+
*
|
|
11
|
+
* Determinism discipline:
|
|
12
|
+
* - Patterns live in a flat array; retrieval is a linear scan (k is small and
|
|
13
|
+
* the array is bounded, so determinism beats micro-optimisation).
|
|
14
|
+
* - The similarity that drives confidence, weighting and competence updates is
|
|
15
|
+
* always computed against the query fingerprint the caller passes in — never
|
|
16
|
+
* a self-similarity proxy.
|
|
17
|
+
* - Sort keys are scalars; ties break on storedAtTick (older first).
|
|
18
|
+
* - No hash maps over fingerprints, no Set iteration, no wall-clock, no RNG.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type { ExecutiveOutputFull } from '#faculties/executive.engine/types'
|
|
22
|
+
import type { Tick } from '#core/types'
|
|
23
|
+
import type {
|
|
24
|
+
DeliberationCacheConfig,
|
|
25
|
+
CachePattern,
|
|
26
|
+
CacheResult,
|
|
27
|
+
ScoredNeighbor,
|
|
28
|
+
DeliberationCacheSnapshot,
|
|
29
|
+
} from './types'
|
|
30
|
+
import { FINGERPRINT_VERSION, fingerprintSimilarity } from './fingerprint'
|
|
31
|
+
import { composeOutput } from './composition'
|
|
32
|
+
|
|
33
|
+
const DEFAULT_CONFIG: Required<DeliberationCacheConfig> = {
|
|
34
|
+
maxPatterns: 5000,
|
|
35
|
+
k: 5,
|
|
36
|
+
minSimilarity: 0.75,
|
|
37
|
+
theta: 0.70,
|
|
38
|
+
tau: 0.5,
|
|
39
|
+
eta: 0.1,
|
|
40
|
+
decayPerCycle: 0.999,
|
|
41
|
+
verifyEveryNHits: 5,
|
|
42
|
+
scopes: [ 'actions' ],
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export class DeliberationCache {
|
|
46
|
+
readonly name = 'deliberation-cache'
|
|
47
|
+
|
|
48
|
+
private _patterns: CachePattern[] = []
|
|
49
|
+
private _config: Required<DeliberationCacheConfig>
|
|
50
|
+
private _hitCount = 0
|
|
51
|
+
private _missCount = 0
|
|
52
|
+
private _verifyCounter = 0
|
|
53
|
+
|
|
54
|
+
constructor( config: DeliberationCacheConfig = {} ){
|
|
55
|
+
this._config = { ...DEFAULT_CONFIG, ...config }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
get size(): number { return this._patterns.length }
|
|
59
|
+
get hitCount(): number { return this._hitCount }
|
|
60
|
+
get missCount(): number { return this._missCount }
|
|
61
|
+
|
|
62
|
+
// ── Retrieval + composition ──────────────────────────────
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Retrieve neighbors of `queryFp` and, if confident, compose an output.
|
|
66
|
+
* Confidence ρ = max over neighbors of (competence × similarity), per the
|
|
67
|
+
* research sketch §2.2 — a diffuse cloud of weak matches never triggers a hit.
|
|
68
|
+
*/
|
|
69
|
+
retrieve( queryFp: Float32Array, _tick: Tick ): CacheResult {
|
|
70
|
+
const neighbors = this._retrieveNeighbors( queryFp )
|
|
71
|
+
if( neighbors.length === 0 ){
|
|
72
|
+
this._missCount++
|
|
73
|
+
return { output: null, confidence: 0, neighbors: [], hit: false }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let confidence = 0
|
|
77
|
+
for( const n of neighbors ){
|
|
78
|
+
const score = n.pattern.competence * n.similarity
|
|
79
|
+
if( score > confidence ) confidence = score
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const hit = confidence >= this._config.theta
|
|
83
|
+
if( hit ) this._hitCount++
|
|
84
|
+
else this._missCount++
|
|
85
|
+
|
|
86
|
+
const output = hit
|
|
87
|
+
? composeOutput( neighbors, this._config.tau, this._config.scopes )
|
|
88
|
+
: null
|
|
89
|
+
|
|
90
|
+
return { output, confidence, neighbors, hit }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Store a new (fingerprint, output) pair from the slow (LLM) path. */
|
|
94
|
+
learn( queryFp: Float32Array, output: ExecutiveOutputFull, tick: Tick ): void {
|
|
95
|
+
this._evictIfFull( tick )
|
|
96
|
+
this._patterns.push( {
|
|
97
|
+
fingerprint: new Float32Array( queryFp ),
|
|
98
|
+
output,
|
|
99
|
+
competence: 0.5,
|
|
100
|
+
storedAtTick: tick,
|
|
101
|
+
retrievalCount: 0,
|
|
102
|
+
successCount: 0,
|
|
103
|
+
} )
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Update the competence of the pattern nearest to `queryFp`, from a reafference
|
|
108
|
+
* reward in [0,1]. Called after an action outcome is confirmed.
|
|
109
|
+
*/
|
|
110
|
+
updateCompetence( queryFp: Float32Array, reward: number, _tick: Tick ): void {
|
|
111
|
+
const best = this._findBestMatch( queryFp )
|
|
112
|
+
if( !best ) return
|
|
113
|
+
const r = Math.max( 0, Math.min( 1, reward ) )
|
|
114
|
+
best.retrievalCount++
|
|
115
|
+
if( r > 0.5 ) best.successCount++
|
|
116
|
+
const a = this._config.eta
|
|
117
|
+
best.competence = Math.max( 0, Math.min( 1, best.competence * ( 1 - a ) + r * a ) )
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Decay all competences one executive cycle. Slowly forgets stale patterns. */
|
|
121
|
+
decay(): void {
|
|
122
|
+
const f = this._config.decayPerCycle
|
|
123
|
+
if( f >= 1 ) return
|
|
124
|
+
for( const p of this._patterns ) p.competence *= f
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Deterministic 1-in-N verify schedule. Increments a counter each call. */
|
|
128
|
+
shouldVerify(): boolean {
|
|
129
|
+
if( this._config.verifyEveryNHits <= 0 ) return false
|
|
130
|
+
this._verifyCounter++
|
|
131
|
+
return this._verifyCounter % this._config.verifyEveryNHits === 0
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ── Snapshot / restore (entity persistence + tests) ──────
|
|
135
|
+
|
|
136
|
+
snapshot(): DeliberationCacheSnapshot {
|
|
137
|
+
return {
|
|
138
|
+
version: FINGERPRINT_VERSION,
|
|
139
|
+
patterns: this._patterns.map( p => ( {
|
|
140
|
+
fingerprint: Array.from( p.fingerprint ),
|
|
141
|
+
output: p.output,
|
|
142
|
+
competence: p.competence,
|
|
143
|
+
storedAtTick: p.storedAtTick,
|
|
144
|
+
retrievalCount: p.retrievalCount,
|
|
145
|
+
successCount: p.successCount,
|
|
146
|
+
} ) ),
|
|
147
|
+
hitCount: this._hitCount,
|
|
148
|
+
missCount: this._missCount,
|
|
149
|
+
verifyCounter: this._verifyCounter,
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
restore( snap: DeliberationCacheSnapshot ): void {
|
|
154
|
+
// Version guard: a fingerprint layout change invalidates stored vectors.
|
|
155
|
+
if( snap.version !== FINGERPRINT_VERSION ) return
|
|
156
|
+
this._patterns = snap.patterns.map( p => ( {
|
|
157
|
+
fingerprint: new Float32Array( p.fingerprint ),
|
|
158
|
+
output: p.output,
|
|
159
|
+
competence: p.competence,
|
|
160
|
+
storedAtTick: p.storedAtTick,
|
|
161
|
+
retrievalCount: p.retrievalCount,
|
|
162
|
+
successCount: p.successCount,
|
|
163
|
+
} ) )
|
|
164
|
+
this._hitCount = snap.hitCount ?? 0
|
|
165
|
+
this._missCount = snap.missCount ?? 0
|
|
166
|
+
this._verifyCounter = snap.verifyCounter ?? 0
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ── Internal ─────────────────────────────────────────────
|
|
170
|
+
|
|
171
|
+
private _retrieveNeighbors( queryFp: Float32Array ): ScoredNeighbor[] {
|
|
172
|
+
const scored: ScoredNeighbor[] = []
|
|
173
|
+
for( const p of this._patterns ){
|
|
174
|
+
const similarity = fingerprintSimilarity( queryFp, p.fingerprint )
|
|
175
|
+
if( similarity >= this._config.minSimilarity )
|
|
176
|
+
scored.push( { pattern: p, similarity } )
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Rank by (similarity × competence) desc; ties → older pattern first.
|
|
180
|
+
scored.sort( ( a, b ) => {
|
|
181
|
+
const sa = a.similarity * a.pattern.competence
|
|
182
|
+
const sb = b.similarity * b.pattern.competence
|
|
183
|
+
if( sa !== sb ) return sb - sa
|
|
184
|
+
return a.pattern.storedAtTick - b.pattern.storedAtTick
|
|
185
|
+
} )
|
|
186
|
+
|
|
187
|
+
return scored.slice( 0, this._config.k )
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private _findBestMatch( queryFp: Float32Array ): CachePattern | null {
|
|
191
|
+
let best: CachePattern | null = null
|
|
192
|
+
let bestSim = -1
|
|
193
|
+
for( const p of this._patterns ){
|
|
194
|
+
const sim = fingerprintSimilarity( queryFp, p.fingerprint )
|
|
195
|
+
if( sim > bestSim ){ bestSim = sim; best = p }
|
|
196
|
+
}
|
|
197
|
+
return best
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private _evictIfFull( _tick: Tick ): void {
|
|
201
|
+
if( this._patterns.length < this._config.maxPatterns ) return
|
|
202
|
+
// Evict the LEAST competent pattern; ties break on the oldest (lowest
|
|
203
|
+
// storedAtTick). Age is handled by decay() — stale patterns bleed competence
|
|
204
|
+
// toward 0 each cycle, so competence alone already ranks them lowest. Folding
|
|
205
|
+
// recency INTO the score inverts value: a high-competence *old* pattern would
|
|
206
|
+
// score below a low-competence *new* one and be wrongly evicted.
|
|
207
|
+
let evictIdx = 0
|
|
208
|
+
let best = this._patterns[ 0 ]!
|
|
209
|
+
for( let i = 1; i < this._patterns.length; i++ ){
|
|
210
|
+
const p = this._patterns[ i ]!
|
|
211
|
+
if( p.competence < best.competence ||
|
|
212
|
+
( p.competence === best.competence && p.storedAtTick < best.storedAtTick ) ){
|
|
213
|
+
best = p
|
|
214
|
+
evictIdx = i
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
this._patterns.splice( evictIdx, 1 )
|
|
218
|
+
}
|
|
219
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/cognition/cache/fingerprint.ts
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* CognitiveFingerprint — extracts a deterministic scalar vector from the
|
|
7
|
+
* frozen simulation state. No wall-clock, no RNG, no iteration-order
|
|
8
|
+
* dependence (values are sorted before they enter fixed slots). The vector
|
|
9
|
+
* captures the operating context that most strongly conditions executive
|
|
10
|
+
* reasoning — the same scalars the prompt's `## Current State` block renders.
|
|
11
|
+
*
|
|
12
|
+
* Dimension layout (fixed order — never reorder without bumping the version):
|
|
13
|
+
* 0–2 physiology energy.level, sleep.pressure, stress.load (metrics, 0–100 → 0–1)
|
|
14
|
+
* 3–5 PAD affect valence (−1..1 → 0..1), arousal, dominance (metrics, 0–1)
|
|
15
|
+
* 6–15 goal priorities top 10 `goal` entities by metadata.priority (0..1)
|
|
16
|
+
* 16–25 belief confidences top 10 `belief` entities by metadata.confidence (0..1)
|
|
17
|
+
* 26–35 wm activations top 10 `working_memory.item` by metadata.activation (0..1)
|
|
18
|
+
*
|
|
19
|
+
* Total: 36 dimensions. Verified against the live writers:
|
|
20
|
+
* - interoception.ts writes energy.level / sleep.pressure (0–100)
|
|
21
|
+
* - stress.regulator.ts writes stress.load
|
|
22
|
+
* - affective.blender.ts writes affect.valence (−1..1) / arousal / dominance (0..1)
|
|
23
|
+
* - goal.manager.ts persists goal entities with metadata.priority
|
|
24
|
+
* - executive.engine/commands.ts + semantic integrator persist belief entities
|
|
25
|
+
* with metadata.confidence
|
|
26
|
+
* - working.memory.ts persists working_memory.item entities with metadata.activation
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import type { ReadonlySimulationState } from '#core/types'
|
|
30
|
+
|
|
31
|
+
export const FINGERPRINT_DIM = 36
|
|
32
|
+
export const FINGERPRINT_VERSION = 1
|
|
33
|
+
|
|
34
|
+
export function extractFingerprint( state: ReadonlySimulationState ): Float32Array {
|
|
35
|
+
const vec = new Float32Array( FINGERPRINT_DIM )
|
|
36
|
+
let idx = 0
|
|
37
|
+
|
|
38
|
+
// ── Physiology (metrics are 0–100) ───────────────────────
|
|
39
|
+
vec[ idx++ ] = _norm( _metric( state, 'energy.level', 50 ), 0, 100 )
|
|
40
|
+
vec[ idx++ ] = _norm( _metric( state, 'sleep.pressure', 0 ), 0, 100 )
|
|
41
|
+
vec[ idx++ ] = _norm( _metric( state, 'stress.load', 0 ), 0, 100 )
|
|
42
|
+
|
|
43
|
+
// ── PAD affect ───────────────────────────────────────────
|
|
44
|
+
// valence is written on [-1, 1]; map to [0, 1] so a distressed state is not
|
|
45
|
+
// silently clamped to neutral. arousal / dominance are already [0, 1].
|
|
46
|
+
vec[ idx++ ] = _clamp01( ( _metric( state, 'affect.valence', 0 ) + 1 ) / 2 )
|
|
47
|
+
vec[ idx++ ] = _clamp01( _metric( state, 'affect.arousal', 0.3 ) )
|
|
48
|
+
vec[ idx++ ] = _clamp01( _metric( state, 'affect.dominance', 0.5 ) )
|
|
49
|
+
|
|
50
|
+
// ── Top-10 goal priorities ───────────────────────────────
|
|
51
|
+
idx = _topEntityScalars( state, 'goal', 'priority', 0, vec, idx, 10 )
|
|
52
|
+
|
|
53
|
+
// ── Top-10 belief confidences ────────────────────────────
|
|
54
|
+
idx = _topEntityScalars( state, 'belief', 'confidence', 0.5, vec, idx, 10 )
|
|
55
|
+
|
|
56
|
+
// ── Top-10 working-memory activations ────────────────────
|
|
57
|
+
idx = _topEntityScalars( state, 'working_memory.item', 'activation', 0, vec, idx, 10 )
|
|
58
|
+
|
|
59
|
+
// Defensive: zero-fill any remaining slots (never expected).
|
|
60
|
+
while( idx < FINGERPRINT_DIM ) vec[ idx++ ] = 0
|
|
61
|
+
|
|
62
|
+
return vec
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Collect a metadata scalar from every entity of `type`, sort descending,
|
|
67
|
+
* and write the top `count` (clamped to [0,1]) into `vec` starting at `idx`.
|
|
68
|
+
* Sorting makes the slice order deterministic regardless of Map iteration.
|
|
69
|
+
* Returns the next free index.
|
|
70
|
+
*/
|
|
71
|
+
function _topEntityScalars(
|
|
72
|
+
state: ReadonlySimulationState,
|
|
73
|
+
type: string,
|
|
74
|
+
field: string,
|
|
75
|
+
fallback: number,
|
|
76
|
+
vec: Float32Array,
|
|
77
|
+
idx: number,
|
|
78
|
+
count: number,
|
|
79
|
+
): number {
|
|
80
|
+
const vals: number[] = []
|
|
81
|
+
for( const e of state.entities.values() ){
|
|
82
|
+
if( e.type !== type ) continue
|
|
83
|
+
const raw = e.metadata?.[ field ]
|
|
84
|
+
vals.push( typeof raw === 'number' ? raw : fallback )
|
|
85
|
+
}
|
|
86
|
+
vals.sort( ( a, b ) => b - a )
|
|
87
|
+
for( let i = 0; i < count; i++ )
|
|
88
|
+
vec[ idx++ ] = _clamp01( vals[ i ] ?? 0 )
|
|
89
|
+
return idx
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function _metric( state: ReadonlySimulationState, key: string, fallback: number ): number {
|
|
93
|
+
const v = state.metrics.get( key )
|
|
94
|
+
return typeof v === 'number' ? v : fallback
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Normalise a value from [min,max] → [0,1]. */
|
|
98
|
+
function _norm( v: number, min: number, max: number ): number {
|
|
99
|
+
if( max === min ) return 0.5
|
|
100
|
+
return Math.max( 0, Math.min( 1, ( v - min ) / ( max - min ) ) )
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function _clamp01( v: number ): number {
|
|
104
|
+
if( Number.isNaN( v ) ) return 0
|
|
105
|
+
return Math.max( 0, Math.min( 1, v ) )
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Cosine similarity between two fingerprint vectors. */
|
|
109
|
+
export function fingerprintSimilarity( a: Float32Array, b: Float32Array ): number {
|
|
110
|
+
let dot = 0, na = 0, nb = 0
|
|
111
|
+
for( let i = 0; i < FINGERPRINT_DIM; i++ ){
|
|
112
|
+
const ai = a[ i ] ?? 0
|
|
113
|
+
const bi = b[ i ] ?? 0
|
|
114
|
+
dot += ai * bi
|
|
115
|
+
na += ai * ai
|
|
116
|
+
nb += bi * bi
|
|
117
|
+
}
|
|
118
|
+
const denom = Math.sqrt( na ) * Math.sqrt( nb )
|
|
119
|
+
return denom === 0 ? 0 : dot / denom
|
|
120
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/cognition/cache/types.ts
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* DeliberationCache — types and contracts.
|
|
7
|
+
*
|
|
8
|
+
* The cache stores past executive outputs keyed by a deterministic
|
|
9
|
+
* cognitive fingerprint. It is pure, deterministic, and R2-safe:
|
|
10
|
+
* the same state + same history ⇒ same retrieval + same composition.
|
|
11
|
+
*
|
|
12
|
+
* Scope note: Phase 1 caches the ACTIONS block only. The composed output
|
|
13
|
+
* is a valid `ExecutiveOutputFull` carrying the three required fields
|
|
14
|
+
* (actions, reasoning, confidence) plus whatever optional blocks the
|
|
15
|
+
* enabled scopes cover. Everything else stays undefined and the existing
|
|
16
|
+
* downstream (`buildStateCommands`) treats it as "nothing to do", which is
|
|
17
|
+
* exactly the intended Phase-1 behaviour.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { ExecutiveOutputFull } from '#faculties/executive.engine/types'
|
|
21
|
+
|
|
22
|
+
/** Which blocks of the executive output the cache may synthesise. */
|
|
23
|
+
export type CacheScope =
|
|
24
|
+
| 'actions'
|
|
25
|
+
| 'goals'
|
|
26
|
+
| 'beliefs'
|
|
27
|
+
|
|
28
|
+
export interface DeliberationCacheConfig {
|
|
29
|
+
/** Maximum patterns to retain. Lowest (competence × recency) evicted when full. */
|
|
30
|
+
maxPatterns?: number
|
|
31
|
+
/** Neighbors retrieved for composition. */
|
|
32
|
+
k?: number
|
|
33
|
+
/** Minimum similarity for a stored pattern to count as a neighbor. */
|
|
34
|
+
minSimilarity?: number
|
|
35
|
+
/** Confidence threshold θ — cache hit requires ρ ≥ θ. Start conservative. */
|
|
36
|
+
theta?: number
|
|
37
|
+
/** Temperature for softmax weights over neighbors. */
|
|
38
|
+
tau?: number
|
|
39
|
+
/** Learning rate (EMA) for competence updates. */
|
|
40
|
+
eta?: number
|
|
41
|
+
/** Competence decay per executive cycle (applied via decay()). */
|
|
42
|
+
decayPerCycle?: number
|
|
43
|
+
/** Verify 1-in-N cache hits against the LLM (0 = never). */
|
|
44
|
+
verifyEveryNHits?: number
|
|
45
|
+
/** Which output blocks to synthesise. Phase 1 default: ['actions']. */
|
|
46
|
+
scopes?: CacheScope[]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** A stored pattern in the cache. */
|
|
50
|
+
export interface CachePattern {
|
|
51
|
+
/** Deterministic fingerprint vector (length FINGERPRINT_DIM). */
|
|
52
|
+
fingerprint: Float32Array
|
|
53
|
+
/** The executive output produced for this fingerprint (verbatim from the LLM). */
|
|
54
|
+
output: ExecutiveOutputFull
|
|
55
|
+
/** Competence score (0–1), learned from reafference. */
|
|
56
|
+
competence: number
|
|
57
|
+
/** Tick when stored. */
|
|
58
|
+
storedAtTick: number
|
|
59
|
+
/** Number of times this pattern won a retrieval. */
|
|
60
|
+
retrievalCount: number
|
|
61
|
+
/** Number of retrievals that were followed by a positive reafference. */
|
|
62
|
+
successCount: number
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** A neighbor plus its similarity to the query fingerprint. */
|
|
66
|
+
export interface ScoredNeighbor {
|
|
67
|
+
pattern: CachePattern
|
|
68
|
+
/** Cosine similarity of this pattern's fingerprint to the query. */
|
|
69
|
+
similarity: number
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Result of a cache retrieve (+ compose when hit). */
|
|
73
|
+
export interface CacheResult {
|
|
74
|
+
/** The composed output — only meaningful when `hit` is true. */
|
|
75
|
+
output: ExecutiveOutputFull | null
|
|
76
|
+
/** Confidence ρ = max_i (competence_i × sim_i). Used for gating. */
|
|
77
|
+
confidence: number
|
|
78
|
+
/** The scored neighbors that were considered. */
|
|
79
|
+
neighbors: ScoredNeighbor[]
|
|
80
|
+
/** Whether this was a cache hit (confidence ≥ θ). */
|
|
81
|
+
hit: boolean
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Serialisable snapshot for deterministic persistence / restore.
|
|
86
|
+
*
|
|
87
|
+
* Persistence follows the same pattern as the rolling summarizer: the engine
|
|
88
|
+
* writes this into a state entity (`executive-deliberation-cache`) via
|
|
89
|
+
* StateCommands and rehydrates it on the first tick. Float arrays are stored
|
|
90
|
+
* as plain number[] so they survive JSON round-tripping through state.
|
|
91
|
+
*/
|
|
92
|
+
export interface DeliberationCacheSnapshot {
|
|
93
|
+
version: number
|
|
94
|
+
patterns: Array<{
|
|
95
|
+
fingerprint: number[]
|
|
96
|
+
output: ExecutiveOutputFull
|
|
97
|
+
competence: number
|
|
98
|
+
storedAtTick: number
|
|
99
|
+
retrievalCount: number
|
|
100
|
+
successCount: number
|
|
101
|
+
}>
|
|
102
|
+
hitCount: number
|
|
103
|
+
missCount: number
|
|
104
|
+
verifyCounter: number
|
|
105
|
+
}
|