@mindot/will 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -3
- package/dist/index.d.ts +52 -7760
- package/dist/index.js +374 -86
- package/dist/index.js.map +1 -1
- package/dist/mcp/cli.d.ts +1 -0
- package/dist/mcp/cli.js +26048 -0
- package/dist/mcp/cli.js.map +1 -0
- package/dist/mcp/effectors.d.ts +55 -0
- package/dist/mcp/effectors.js +76 -0
- package/dist/mcp/effectors.js.map +1 -0
- package/dist/will-B5eKs3Wv.d.ts +7903 -0
- package/package.json +38 -30
- package/src/cognition/agency/engines/action.selector.ts +3 -0
- package/src/cognition/agency/engines/affordance.synthesizer.ts +26 -12
- package/src/cognition/agency/engines/deliberation.engine.ts +7 -2
- package/src/cognition/agency/engines/motor.schema.executor.ts +2 -0
- package/src/cognition/agency/schemas/external.ts +27 -10
- package/src/cognition/agency/schemas/repertoire.ts +12 -0
- package/src/cognition/agency/types.ts +51 -2
- package/src/cognition/faculties/executive.engine/commands.ts +47 -11
- package/src/cognition/faculties/executive.engine/context.ts +28 -0
- package/src/cognition/faculties/executive.engine/facet.supervisor.ts +15 -1
- package/src/cognition/faculties/executive.engine/facet.ts +32 -6
- package/src/cognition/faculties/executive.engine/prompt.factory.ts +11 -1
- package/src/cognition/faculties/executive.engine/types.ts +24 -2
- package/src/cognition/index.ts +1 -1
- package/src/core/abstracts.ts +39 -12
- package/src/index.ts +6 -0
- package/src/mcp/cli.ts +129 -0
- package/src/mcp/effectors.ts +159 -0
- package/src/mcp/server.ts +167 -0
- package/src/sdk/will.ts +269 -44
- package/src/stem/index.ts +16 -0
- package/src/stem/mind.ts +11 -4
- package/src/stem/tracts/effector.controller.ts +1 -0
- package/src/types.ts +2 -0
package/package.json
CHANGED
|
@@ -1,21 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mindot/will",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Will — an engine for persistent machine minds. A Will is a continuously-ticking synthetic mind built from 38 cognitive faculties, a learning agency pipeline, and a dual-process executive — deterministic, persistent, and portable (PMA).",
|
|
3
|
+
"version": "0.2.0",
|
|
5
4
|
"author": "Fabrice <fabrice8@github.com>",
|
|
6
|
-
"license": "Apache-2.0",
|
|
7
|
-
"packageManager": "bun@1.3.9",
|
|
8
|
-
"engines": {
|
|
9
|
-
"bun": ">=1.0.0"
|
|
10
|
-
},
|
|
11
|
-
"homepage": "https://github.com/mindot-ai/will#readme",
|
|
12
5
|
"repository": {
|
|
13
6
|
"type": "git",
|
|
14
7
|
"url": "git+https://github.com/mindot-ai/will.git"
|
|
15
8
|
},
|
|
9
|
+
"bin": {
|
|
10
|
+
"will": "dist/mcp/cli.js"
|
|
11
|
+
},
|
|
12
|
+
"main": "./dist/index.js",
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"@types/bun": "latest",
|
|
15
|
+
"tsup": "^8.5.1",
|
|
16
|
+
"typescript": "^5.9.0",
|
|
17
|
+
"vitest": "^4.1.5"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
21
|
+
"zod": "^4.0.0"
|
|
22
|
+
},
|
|
23
|
+
"optionalDependencies": {
|
|
24
|
+
"socket.io-client": "^4.8.1"
|
|
25
|
+
},
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"import": "./dist/index.js",
|
|
29
|
+
"types": "./dist/index.d.ts"
|
|
30
|
+
},
|
|
31
|
+
"./mcp": {
|
|
32
|
+
"import": "./dist/mcp/effectors.js",
|
|
33
|
+
"types": "./dist/mcp/effectors.d.ts"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
16
36
|
"bugs": {
|
|
17
37
|
"url": "https://github.com/mindot-ai/will/issues"
|
|
18
38
|
},
|
|
39
|
+
"description": "Will — an engine for persistent machine minds. A Will is a continuously-ticking synthetic mind built from 38 cognitive faculties, a learning agency pipeline, and a dual-process executive — deterministic, persistent, and portable (PMA).",
|
|
40
|
+
"engines": {
|
|
41
|
+
"bun": ">=1.0.0"
|
|
42
|
+
},
|
|
43
|
+
"files": ["dist", "src"],
|
|
44
|
+
"homepage": "https://github.com/mindot-ai/will#readme",
|
|
19
45
|
"keywords": [
|
|
20
46
|
"ai",
|
|
21
47
|
"agent",
|
|
@@ -30,19 +56,11 @@
|
|
|
30
56
|
"mind",
|
|
31
57
|
"deterministic"
|
|
32
58
|
],
|
|
59
|
+
"license": "Apache-2.0",
|
|
60
|
+
"packageManager": "bun@1.3.9",
|
|
33
61
|
"publishConfig": {
|
|
34
62
|
"access": "public"
|
|
35
63
|
},
|
|
36
|
-
"type": "module",
|
|
37
|
-
"main": "./dist/index.js",
|
|
38
|
-
"types": "./dist/index.d.ts",
|
|
39
|
-
"exports": {
|
|
40
|
-
".": {
|
|
41
|
-
"import": "./dist/index.js",
|
|
42
|
-
"types": "./dist/index.d.ts"
|
|
43
|
-
}
|
|
44
|
-
},
|
|
45
|
-
"files": ["dist", "src"],
|
|
46
64
|
"scripts": {
|
|
47
65
|
"build": "tsup",
|
|
48
66
|
"build:types": "tsc --project tsconfig.build.json",
|
|
@@ -58,16 +76,6 @@
|
|
|
58
76
|
"test:watch": "vitest watch",
|
|
59
77
|
"test:integration": "vitest run src/tests/integration"
|
|
60
78
|
},
|
|
61
|
-
"
|
|
62
|
-
|
|
63
|
-
},
|
|
64
|
-
"optionalDependencies": {
|
|
65
|
-
"socket.io-client": "^4.8.1"
|
|
66
|
-
},
|
|
67
|
-
"devDependencies": {
|
|
68
|
-
"@types/bun": "latest",
|
|
69
|
-
"tsup": "^8.5.1",
|
|
70
|
-
"typescript": "^5.9.0",
|
|
71
|
-
"vitest": "^4.1.5"
|
|
72
|
-
}
|
|
79
|
+
"type": "module",
|
|
80
|
+
"types": "./dist/index.d.ts"
|
|
73
81
|
}
|
|
@@ -293,6 +293,9 @@ export class ActionSelector implements CognitiveEngine {
|
|
|
293
293
|
targetEntityId: s.affordance.targetEntityId,
|
|
294
294
|
parameters: s.affordance.parameters,
|
|
295
295
|
activation: s.activation,
|
|
296
|
+
// Carry the ability's meaning so the Deliberator weighs what each
|
|
297
|
+
// option is FOR, not bare labels.
|
|
298
|
+
...( s.affordance.description ? { description: s.affordance.description } : {} ),
|
|
296
299
|
// Channel B: flag a candidate that is an active plan's frontier step, so
|
|
297
300
|
// the deliberation facet can own "this is my plan's next step" in-character.
|
|
298
301
|
...( s.affordance.source === 'plan' ? { fromPlan: true } : {} ),
|
|
@@ -149,24 +149,36 @@ export class AffordanceSynthesizer implements CognitiveEngine {
|
|
|
149
149
|
})
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
|
|
153
|
-
|
|
152
|
+
// Target-bound schemas are bound against each perceived known-entity of the
|
|
153
|
+
// matching kind: `binds: 'entity'` schemas (innate `reach-out` + host
|
|
154
|
+
// person-effectors) to sentient entities, `binds: 'object'` schemas to
|
|
155
|
+
// things — so the Will can direct `give`/`greet` at someone or `use`/`pick-up`
|
|
156
|
+
// at something in particular. Each (schema × target) enters as a candidate at
|
|
157
|
+
// the target's salience and competes through the attention cap.
|
|
158
|
+
const personSchemas = schemas.filter( s => s.binds === 'entity' )
|
|
159
|
+
const objectSchemas = schemas.filter( s => s.binds === 'object' )
|
|
160
|
+
if( personSchemas.length > 0 || objectSchemas.length > 0 )
|
|
154
161
|
for( const [ id, e ] of state.entities ){
|
|
155
162
|
if( e.type !== 'known-entity' ) continue
|
|
156
|
-
const m
|
|
157
|
-
|
|
163
|
+
const m = e.metadata
|
|
164
|
+
const kind = str( m?.['kind'] )
|
|
165
|
+
const applicable = kind === 'sentient' ? personSchemas : kind === 'thing' ? objectSchemas : null
|
|
166
|
+
if( !applicable || applicable.length === 0 ) continue
|
|
158
167
|
const keid = str( m?.['keid'] ) ?? id
|
|
159
168
|
const fam = num( m?.['familiarity'], 0 )
|
|
160
169
|
const val = num( m?.['valence'], 0 )
|
|
161
170
|
const res = num( m?.['resolutionConfidence'], 0 )
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
171
|
+
const salience = fam * 0.6 + Math.max( 0, val ) * 0.3 + res * 0.1 + ( goalTargets.get( keid ) ?? 0 )
|
|
172
|
+
const name = str( m?.['name'] ) ?? keid
|
|
173
|
+
for( const schema of applicable )
|
|
174
|
+
candidates.push({
|
|
175
|
+
salience,
|
|
176
|
+
affordance: this._build( schema, tick, state, valence, energyLow, skills, {
|
|
177
|
+
evokedBy: id,
|
|
178
|
+
targetEntityId: keid,
|
|
179
|
+
parameters: { targetEntityName: name },
|
|
180
|
+
} ),
|
|
181
|
+
})
|
|
170
182
|
}
|
|
171
183
|
|
|
172
184
|
// ── ideomotor candidates: the executive's imagined actions, pre-activated ──
|
|
@@ -310,6 +322,7 @@ export class AffordanceSynthesizer implements CognitiveEngine {
|
|
|
310
322
|
habitStrength,
|
|
311
323
|
available: this._available( schema.preconditions, ( k ) => metric( state, k, 0 ) ),
|
|
312
324
|
tags: schema.tags ?? [],
|
|
325
|
+
...( schema.description ? { description: schema.description } : {} ),
|
|
313
326
|
planBias: ctx.planBias,
|
|
314
327
|
planId: ctx.planId,
|
|
315
328
|
stepId: ctx.stepId,
|
|
@@ -333,6 +346,7 @@ export class AffordanceSynthesizer implements CognitiveEngine {
|
|
|
333
346
|
habitStrength: a.habitStrength,
|
|
334
347
|
available: a.available,
|
|
335
348
|
tags: a.tags,
|
|
349
|
+
description: a.description,
|
|
336
350
|
planBias: a.planBias,
|
|
337
351
|
planId: a.planId,
|
|
338
352
|
stepId: a.stepId,
|
|
@@ -50,6 +50,8 @@ interface Candidate {
|
|
|
50
50
|
targetEntityId?: string
|
|
51
51
|
parameters?: Record<string, unknown>
|
|
52
52
|
activation?: number
|
|
53
|
+
/** The ability's declared meaning (external effectors) — what it is for. */
|
|
54
|
+
description?: string
|
|
53
55
|
/** Channel B: this candidate is an active plan's current frontier step. */
|
|
54
56
|
fromPlan?: boolean
|
|
55
57
|
}
|
|
@@ -212,10 +214,13 @@ export class DeliberationEngine implements CognitiveEngine {
|
|
|
212
214
|
lines.push( 'Your automatic action-selection was uncertain. Candidate actions:' )
|
|
213
215
|
candidates.forEach( ( c, i ) => {
|
|
214
216
|
const to = c.targetEntityId ? ` toward ${ c.targetEntityId }` : ''
|
|
217
|
+
// The ability's meaning, so the facet weighs what each option IS FOR rather
|
|
218
|
+
// than choosing among bare labels ("give" vs "attack" is a real difference).
|
|
219
|
+
const what = c.description ? ` — ${ c.description }` : ''
|
|
215
220
|
// Channel B: name the plan link so the facet chooses as the self pursuing it,
|
|
216
221
|
// not blindly among labels ("this one is the next step of the plan I'm on").
|
|
217
|
-
const plan = c.fromPlan ? "
|
|
218
|
-
lines.push( `${ i + 1 }. ${ c.schema }${ to }${ plan }` )
|
|
222
|
+
const plan = c.fromPlan ? " (your current plan's next step)" : ''
|
|
223
|
+
lines.push( `${ i + 1 }. ${ c.schema }${ to }${ what }${ plan }` )
|
|
219
224
|
})
|
|
220
225
|
return lines.join( '\n' )
|
|
221
226
|
}
|
|
@@ -464,6 +464,8 @@ export class MotorSchemaExecutor implements CognitiveEngine {
|
|
|
464
464
|
payload: {
|
|
465
465
|
schema: intent.schema, intentId: intent.id,
|
|
466
466
|
targetEntityId: intent.targetEntityId, parameters: intent.parameters, tick,
|
|
467
|
+
// The ability's declared meaning, carried to the host handler.
|
|
468
|
+
description: this._resolve( intent.schema )?.description,
|
|
467
469
|
},
|
|
468
470
|
})
|
|
469
471
|
}
|
|
@@ -21,37 +21,54 @@
|
|
|
21
21
|
// entity schema. See CUSTOM_EFFECTOR_WIRING_TODO.md.
|
|
22
22
|
// ─────────────────────────────────────────────────────────────
|
|
23
23
|
|
|
24
|
-
import type { MotorSchema } from '#agency/types'
|
|
24
|
+
import type { MotorSchema, EffectorDeclaration } from '#agency/types'
|
|
25
25
|
import { EXPLICIT_EFFECTORS } from '#agency/access.grants'
|
|
26
26
|
import { INNATE_SCHEMA_BY_ID } from '#agency/schemas/innate'
|
|
27
27
|
|
|
28
28
|
/** Default effort/energy demand for a host action when none is specified. */
|
|
29
29
|
const DEFAULT_EXTERNAL_COST = 0.15
|
|
30
30
|
|
|
31
|
+
const clamp = ( n: number, lo: number, hi: number ): number => n < lo ? lo : n > hi ? hi : n
|
|
32
|
+
|
|
31
33
|
/**
|
|
32
34
|
* Build enactable MotorSchemas for a host's declared domain effectors. Comms
|
|
33
35
|
* names and innate-shadowing names are filtered out; the rest become objectless,
|
|
34
36
|
* `external`-tagged primitives that route to the host on enaction.
|
|
37
|
+
*
|
|
38
|
+
* A declaration may be a bare name (uniform prior) or an object carrying the
|
|
39
|
+
* ability's meaning + intrinsic priors (`description`, `cost`, `valence`,
|
|
40
|
+
* `preconditions`) — these seed the affordance so the ability competes with a
|
|
41
|
+
* real effort/reward/gating profile instead of a flat default. Reafference
|
|
42
|
+
* refines them from there.
|
|
35
43
|
*/
|
|
36
|
-
export function externalSchemas( effectors?:
|
|
44
|
+
export function externalSchemas( effectors?: EffectorDeclaration[] | null ): MotorSchema[] {
|
|
37
45
|
const seen = new Set<string>()
|
|
38
46
|
const out: MotorSchema[] = []
|
|
39
47
|
|
|
40
|
-
for( const
|
|
48
|
+
for( const decl of effectors ?? [] ){
|
|
49
|
+
const name = typeof decl === 'string' ? decl : decl?.name
|
|
41
50
|
if( typeof name !== 'string' || name.length === 0 ) continue
|
|
42
51
|
if( EXPLICIT_EFFECTORS.has( name ) ) continue // communication — handled by AccessGrants
|
|
43
52
|
if( INNATE_SCHEMA_BY_ID.has( name ) ) continue // shadows an innate stance
|
|
44
53
|
if( seen.has( name ) ) continue
|
|
45
54
|
seen.add( name )
|
|
46
55
|
|
|
56
|
+
const meta = typeof decl === 'string' ? null : decl
|
|
57
|
+
const binds = meta?.binds === 'entity' ? 'entity' : meta?.binds === 'object' ? 'object' : 'none'
|
|
58
|
+
// Declared routing tags merged with the mandatory 'external'/'host' markers
|
|
59
|
+
// (the executor routes on 'external'; dedup keeps the set clean).
|
|
60
|
+
const tags = [ ...new Set( [ ...( meta?.tags ?? [] ), 'external', 'host' ] ) ]
|
|
61
|
+
|
|
47
62
|
out.push({
|
|
48
|
-
id:
|
|
49
|
-
kind:
|
|
50
|
-
source:
|
|
51
|
-
cost:
|
|
52
|
-
binds
|
|
53
|
-
baseValence: 0,
|
|
54
|
-
|
|
63
|
+
id: name,
|
|
64
|
+
kind: 'primitive',
|
|
65
|
+
source: 'external',
|
|
66
|
+
cost: typeof meta?.cost === 'number' ? clamp( meta.cost, 0, 1 ) : DEFAULT_EXTERNAL_COST,
|
|
67
|
+
binds,
|
|
68
|
+
baseValence: typeof meta?.valence === 'number' ? clamp( meta.valence, -1, 1 ) : 0,
|
|
69
|
+
...( meta?.preconditions ? { preconditions: meta.preconditions } : {} ),
|
|
70
|
+
...( meta?.description ? { description: meta.description } : {} ),
|
|
71
|
+
tags,
|
|
55
72
|
})
|
|
56
73
|
}
|
|
57
74
|
|
|
@@ -71,6 +71,16 @@ export class SchemaRepertoire {
|
|
|
71
71
|
this._skills.set( schema.id, freshSkill( schema.id, 0.4, 0 ) )
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Register a host effector's primitive schema at runtime (post-create
|
|
76
|
+
* `.effector()`). Unlike a composite it is NOT marked learned — it is a
|
|
77
|
+
* capacity the host granted, which the synthesizer surfaces immediately and
|
|
78
|
+
* reafference then builds skill on. Idempotent; re-registering updates it.
|
|
79
|
+
*/
|
|
80
|
+
registerExternal( schema: MotorSchema ): void {
|
|
81
|
+
this._templates.set( schema.id, schema )
|
|
82
|
+
}
|
|
83
|
+
|
|
74
84
|
// ── skills ────────────────────────────────────────────────────
|
|
75
85
|
skills(): ReadonlyMap<string, LearnedSkill> { return this._skills }
|
|
76
86
|
getSkill( id: string ): LearnedSkill | undefined { return this._skills.get( id ) }
|
|
@@ -221,6 +231,7 @@ function schemaEntity( s: MotorSchema ): EntityInput {
|
|
|
221
231
|
preconditions: s.preconditions,
|
|
222
232
|
composedOf: s.composedOf,
|
|
223
233
|
baseValence: s.baseValence,
|
|
234
|
+
description: s.description,
|
|
224
235
|
tags: s.tags,
|
|
225
236
|
},
|
|
226
237
|
}
|
|
@@ -241,6 +252,7 @@ function readSchema( m: Record<string, unknown> | undefined ): MotorSchema | und
|
|
|
241
252
|
preconditions: meta['preconditions'] as MotorSchema['preconditions'],
|
|
242
253
|
composedOf: Array.isArray( meta['composedOf'] ) ? meta['composedOf'] as string[] : undefined,
|
|
243
254
|
baseValence: typeof meta['baseValence'] === 'number' ? meta['baseValence'] as number : undefined,
|
|
255
|
+
description: typeof meta['description'] === 'string' ? meta['description'] as string : undefined,
|
|
244
256
|
tags: Array.isArray( meta['tags'] ) ? meta['tags'] as string[] : undefined,
|
|
245
257
|
}
|
|
246
258
|
}
|
|
@@ -29,8 +29,12 @@ export interface SchemaPrecondition {
|
|
|
29
29
|
value: number
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
/**
|
|
33
|
-
|
|
32
|
+
/**
|
|
33
|
+
* What kind of target a schema binds when it becomes an affordance.
|
|
34
|
+
* 'entity' = a sentient known-entity (a person); 'object' = a non-sentient
|
|
35
|
+
* known-entity (a thing); 'percept' = a salient percept; 'none' = objectless.
|
|
36
|
+
*/
|
|
37
|
+
export type SchemaBinding = 'none' | 'entity' | 'object' | 'percept'
|
|
34
38
|
|
|
35
39
|
/**
|
|
36
40
|
* A MotorSchema — a parameterized control program, not a flat effector row.
|
|
@@ -52,9 +56,52 @@ export interface MotorSchema {
|
|
|
52
56
|
composedOf?: string[]
|
|
53
57
|
/** Intrinsic affective prior (−1..1) before any learning has occurred. */
|
|
54
58
|
baseValence?: number
|
|
59
|
+
/** What the schema is *for* — its meaning, carried to the host on enaction. */
|
|
60
|
+
description?: string
|
|
55
61
|
tags?: string[]
|
|
56
62
|
}
|
|
57
63
|
|
|
64
|
+
/**
|
|
65
|
+
* How a host declares a domain effector to a Will. A bare string is the
|
|
66
|
+
* name-only form (`CUSTOM_ABILITY_WIRING.md` Phase 1). The object form seeds the
|
|
67
|
+
* ability as a *learnable affordance*: `description` is its meaning; `cost`,
|
|
68
|
+
* `valence`, and `preconditions` are the intrinsic priors the mind starts from
|
|
69
|
+
* before reafference refines them through use. Args still bind from the
|
|
70
|
+
* situation — this is not a tool-call parameter form.
|
|
71
|
+
*/
|
|
72
|
+
export type EffectorDeclaration =
|
|
73
|
+
| string
|
|
74
|
+
| {
|
|
75
|
+
name: string
|
|
76
|
+
/** What the ability is for — its meaning, carried to perception + the host. */
|
|
77
|
+
description?: string
|
|
78
|
+
/** Intrinsic effort/energy demand 0..1 (default 0.15). */
|
|
79
|
+
cost?: number
|
|
80
|
+
/** Intrinsic affective prior −1..1 the mind expects before learning (default 0). */
|
|
81
|
+
valence?: number
|
|
82
|
+
/** Body-state gates; the affordance is unavailable unless all pass. */
|
|
83
|
+
preconditions?: SchemaPrecondition[]
|
|
84
|
+
/**
|
|
85
|
+
* Whether the ability targets a specific *perceived* target (default
|
|
86
|
+
* 'none'). 'entity' binds it to each sentient known-entity (a person),
|
|
87
|
+
* 'object' to each non-sentient one (a thing) — so the Will can
|
|
88
|
+
* `give`/`greet` someone or `use`/`pick-up` something in particular; the
|
|
89
|
+
* bound target reaches the host as `ctx.targetEntityId`.
|
|
90
|
+
*/
|
|
91
|
+
binds?: 'none' | 'entity' | 'object'
|
|
92
|
+
/**
|
|
93
|
+
* Routing tags folded into the schema (merged with 'external'/'host').
|
|
94
|
+
* A tag the drive system recognises (e.g. 'social', 'nourishment') lets a
|
|
95
|
+
* homeostatic drive lift this ability in the competition when pressing.
|
|
96
|
+
*/
|
|
97
|
+
tags?: string[]
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** The effector name of a declaration, whichever form it takes. */
|
|
101
|
+
export function effectorName( d: EffectorDeclaration ): string {
|
|
102
|
+
return typeof d === 'string' ? d : d.name
|
|
103
|
+
}
|
|
104
|
+
|
|
58
105
|
/**
|
|
59
106
|
* An Affordance — a possibility the current state affords *for this body now*,
|
|
60
107
|
* with its parameters already bound from the thing that evoked it. Parameters
|
|
@@ -85,6 +132,8 @@ export interface Affordance {
|
|
|
85
132
|
available: boolean
|
|
86
133
|
/** Schema tags, carried so selection can route drives/risk without the registry. */
|
|
87
134
|
tags: string[]
|
|
135
|
+
/** The ability's declared meaning (external effectors) — what it is for. */
|
|
136
|
+
description?: string
|
|
88
137
|
/**
|
|
89
138
|
* Top-down planning bias 0..1 — set when an executing plan's frontier step
|
|
90
139
|
* projected this affordance (source 'plan'). It lifts activation in the
|
|
@@ -399,10 +399,15 @@ function resolveKnownEntity( target: string, state: ReadonlySimulationState ): s
|
|
|
399
399
|
}
|
|
400
400
|
|
|
401
401
|
/**
|
|
402
|
-
* Turn the executive's imagined
|
|
403
|
-
*
|
|
404
|
-
*
|
|
405
|
-
*
|
|
402
|
+
* Turn the executive's imagined actions into `ideomotor.intent` entities (the
|
|
403
|
+
* ideomotor leg). Two kinds are pre-activated: communicate actions (as reach-out
|
|
404
|
+
* toward the resolved entity) and *currently-afforded host abilities* — an action
|
|
405
|
+
* whose type names an external affordance in the field, optionally carrying the
|
|
406
|
+
* `args` the executive consciously supplied (a mind formulating "search the docs
|
|
407
|
+
* for X" IS the situation providing the argument). The AffordanceSynthesizer
|
|
408
|
+
* surfaces them as competing candidates next tick — executive intention ENTERS
|
|
409
|
+
* the competition, it never bypasses it. Refreshed each executive cycle — stale
|
|
410
|
+
* intents the executive no longer imagines are deleted.
|
|
406
411
|
*/
|
|
407
412
|
function buildIdeomotorIntents(
|
|
408
413
|
output: ExecutiveOutputFull,
|
|
@@ -413,16 +418,47 @@ function buildIdeomotorIntents(
|
|
|
413
418
|
const seen = new Set<string>()
|
|
414
419
|
const priority = clamp01( output.confidence ?? 0.8 )
|
|
415
420
|
|
|
421
|
+
// The host abilities currently afforded (source 'external' in the live field) —
|
|
422
|
+
// the executive can only pre-activate what the situation actually offers.
|
|
423
|
+
const externalBySchema = new Map<string, string>()
|
|
424
|
+
for( const e of state.entities.values() ){
|
|
425
|
+
if( e.type !== 'affordance' ) continue
|
|
426
|
+
const m = e.metadata as Record<string, unknown> | undefined
|
|
427
|
+
if( m?.['source'] !== 'external' ) continue
|
|
428
|
+
const schema = typeof m['schema'] === 'string' ? m['schema'] as string : undefined
|
|
429
|
+
if( schema ) externalBySchema.set( schema.toLowerCase(), schema )
|
|
430
|
+
}
|
|
431
|
+
|
|
416
432
|
for( const action of output.actions ){
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
433
|
+
const t = action.type.toLowerCase()
|
|
434
|
+
|
|
435
|
+
if( COMMUNICATE_ACTION_TYPES.has( t ) ){
|
|
436
|
+
if( !action.target ) continue
|
|
437
|
+
const keid = resolveKnownEntity( action.target, state )
|
|
438
|
+
if( !keid || seen.has( keid ) ) continue
|
|
439
|
+
seen.add( keid )
|
|
440
|
+
set.push({
|
|
441
|
+
id: `ideomotor-reach-out-${ keid }`,
|
|
442
|
+
type: 'ideomotor.intent',
|
|
443
|
+
metadata: { schema: 'reach-out', targetEntityId: keid, priority, origin: 'executive', tick: footprint.tickObserved },
|
|
444
|
+
})
|
|
445
|
+
continue
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// A host ability the executive imagines enacting, with its conscious args.
|
|
449
|
+
const schema = externalBySchema.get( t )
|
|
450
|
+
if( !schema || seen.has( `ability:${ schema }` ) ) continue
|
|
451
|
+
seen.add( `ability:${ schema }` )
|
|
452
|
+
const keid = action.target ? resolveKnownEntity( action.target, state ) : undefined
|
|
422
453
|
set.push({
|
|
423
|
-
id: `ideomotor
|
|
454
|
+
id: `ideomotor-${ schema }${ keid ? `-${ keid }` : '' }`,
|
|
424
455
|
type: 'ideomotor.intent',
|
|
425
|
-
metadata: {
|
|
456
|
+
metadata: {
|
|
457
|
+
schema,
|
|
458
|
+
...( keid ? { targetEntityId: keid } : {} ),
|
|
459
|
+
...( action.args && typeof action.args === 'object' ? { parameters: action.args } : {} ),
|
|
460
|
+
priority, origin: 'executive', tick: footprint.tickObserved,
|
|
461
|
+
},
|
|
426
462
|
})
|
|
427
463
|
}
|
|
428
464
|
|
|
@@ -322,6 +322,7 @@ export async function buildExecutiveContext(
|
|
|
322
322
|
plans,
|
|
323
323
|
relevantPlanIds,
|
|
324
324
|
percepts,
|
|
325
|
+
abilities: extractAbilities( state ),
|
|
325
326
|
workingMemory,
|
|
326
327
|
memories,
|
|
327
328
|
beliefs,
|
|
@@ -334,6 +335,33 @@ export async function buildExecutiveContext(
|
|
|
334
335
|
}
|
|
335
336
|
}
|
|
336
337
|
|
|
338
|
+
/**
|
|
339
|
+
* Host-declared abilities afforded to the Will right now — read from the current
|
|
340
|
+
* `affordance` field (source 'external', available). Gives System 2 knowledge of
|
|
341
|
+
* what it can do + what each is for; the innate stances are already in the
|
|
342
|
+
* preamble, so only host effectors surface here. Capped so a wide catalog can't
|
|
343
|
+
* bloat the prompt.
|
|
344
|
+
*/
|
|
345
|
+
const MAX_SURFACED_ABILITIES = 8
|
|
346
|
+
export function extractAbilities( state: ReadonlySimulationState ): ExecutiveContext['abilities'] {
|
|
347
|
+
const out: NonNullable<ExecutiveContext['abilities']> = []
|
|
348
|
+
for( const e of state.entities.values() ){
|
|
349
|
+
if( e.type !== 'affordance' ) continue
|
|
350
|
+
const m = e.metadata as Record<string, unknown> | undefined
|
|
351
|
+
if( m?.['source'] !== 'external' || m?.['available'] === false ) continue
|
|
352
|
+
const name = typeof m?.['schema'] === 'string' ? m['schema'] as string : undefined
|
|
353
|
+
if( !name ) continue
|
|
354
|
+
const description = typeof m['description'] === 'string' ? m['description'] as string : undefined
|
|
355
|
+
const params = m['parameters'] as Record<string, unknown> | undefined
|
|
356
|
+
const target = m['targetEntityId']
|
|
357
|
+
? ( typeof params?.['targetEntityName'] === 'string' ? params['targetEntityName'] as string : String( m['targetEntityId'] ) )
|
|
358
|
+
: undefined
|
|
359
|
+
out.push( { name, ...( description ? { description } : {} ), ...( target ? { target } : {} ) } )
|
|
360
|
+
if( out.length >= MAX_SURFACED_ABILITIES ) break
|
|
361
|
+
}
|
|
362
|
+
return out.length > 0 ? out : undefined
|
|
363
|
+
}
|
|
364
|
+
|
|
337
365
|
/**
|
|
338
366
|
* What the Will is currently focused on (the `task-switch-focus` entity from task.switcher)
|
|
339
367
|
* plus the effort cost of switching away (the `task_switch.switch_cost` metric = effective
|
|
@@ -133,9 +133,15 @@ export class FacetSupervisor {
|
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
private _reapIdle( tick: number ): void {
|
|
136
|
-
for( const [ id, facet ] of [ ...this._facets ] )
|
|
136
|
+
for( const [ id, facet ] of [ ...this._facets ] ){
|
|
137
|
+
// Never reap a busy facet: queued reports / an in-flight LLM call span
|
|
138
|
+
// many ticks (a real call is 10–30s), and destroying the facet clears the
|
|
139
|
+
// listeners its pending decision lands on — a conversation reply would
|
|
140
|
+
// vanish silently. The TTL measures *quiet* facets only.
|
|
141
|
+
if( facet.busy ) continue
|
|
137
142
|
if( tick - facet.lastActiveTick > this._idleTtlTicks )
|
|
138
143
|
this._reap( id, 'idle' )
|
|
144
|
+
}
|
|
139
145
|
}
|
|
140
146
|
|
|
141
147
|
/** Destroy + deregister a facet and notify its owner. Shared by the reaper + LRU eviction. */
|
|
@@ -163,8 +169,16 @@ export class FacetSupervisor {
|
|
|
163
169
|
}
|
|
164
170
|
|
|
165
171
|
private _leastRecentlyActive(): string | null {
|
|
172
|
+
// Prefer a quiet victim: evicting a busy facet drops its in-flight decision
|
|
173
|
+
// (same silent-loss mode the idle reaper guards against). Only when every
|
|
174
|
+
// facet is busy does pressure eviction fall back to the absolute LRU — a
|
|
175
|
+
// new conversation still preempts rather than being refused.
|
|
166
176
|
let id: string | null = null
|
|
167
177
|
let min = Infinity
|
|
178
|
+
for( const [ fid, facet ] of this._facets )
|
|
179
|
+
if( !facet.busy && facet.lastActiveTick < min ){ min = facet.lastActiveTick; id = fid }
|
|
180
|
+
if( id ) return id
|
|
181
|
+
|
|
168
182
|
for( const [ fid, facet ] of this._facets )
|
|
169
183
|
if( facet.lastActiveTick < min ){ min = facet.lastActiveTick; id = fid }
|
|
170
184
|
return id
|
|
@@ -149,6 +149,16 @@ export class ExecutiveFacet {
|
|
|
149
149
|
/** Stamp activity at `tick` (called at spawn and on each report). */
|
|
150
150
|
markActive( tick: number ): void { this._lastActiveTick = tick }
|
|
151
151
|
|
|
152
|
+
/** _reason() calls currently in flight — a real LLM call spans many ticks. */
|
|
153
|
+
private _inflight = 0
|
|
154
|
+
/**
|
|
155
|
+
* True while the facet has work the reaper must not discard: queued reports
|
|
156
|
+
* awaiting the pump, or an in-flight _reason() whose decision hasn't landed.
|
|
157
|
+
* The idle TTL only measures *quiet* facets — reaping a busy one destroys the
|
|
158
|
+
* listeners its pending decision needs, silently dropping a conversation reply.
|
|
159
|
+
*/
|
|
160
|
+
get busy(): boolean { return this._inflight > 0 || this._pendingReports.length > 0 }
|
|
161
|
+
|
|
152
162
|
/**
|
|
153
163
|
* Per-facet chunk handler — fires for every LLM token during _reason().
|
|
154
164
|
* Set by the creating engine (e.g. AuditionEngine) for entity-scoped streaming.
|
|
@@ -236,9 +246,27 @@ export class ExecutiveFacet {
|
|
|
236
246
|
}
|
|
237
247
|
|
|
238
248
|
// Legacy (bare facets in unit tests): trigger reasoning immediately.
|
|
239
|
-
this.
|
|
240
|
-
|
|
241
|
-
|
|
249
|
+
this._launchReason( report )
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Launch _reason() with in-flight accounting. `busy` must hold for the whole
|
|
254
|
+
* span (a real LLM call is 10–30s ≈ many ticks) so the supervisor's idle
|
|
255
|
+
* reaper never destroys a facet whose decision is still coming — that would
|
|
256
|
+
* clear its listeners and silently drop the reply. Completion re-stamps
|
|
257
|
+
* activity so the idle TTL measures quiet time *after* the decision, not
|
|
258
|
+
* after the report that started it.
|
|
259
|
+
*/
|
|
260
|
+
private _launchReason( report: FacetReport ): void {
|
|
261
|
+
this._inflight++
|
|
262
|
+
this._reason( report )
|
|
263
|
+
.catch( err =>
|
|
264
|
+
logger.error( `[executive.facet] ${this.facetId} reasoning error:`, err )
|
|
265
|
+
)
|
|
266
|
+
.finally( () => {
|
|
267
|
+
this._inflight--
|
|
268
|
+
this.markActive( ( this._currentStateRef?.tick as number ) ?? this._lastActiveTick )
|
|
269
|
+
})
|
|
242
270
|
}
|
|
243
271
|
|
|
244
272
|
/**
|
|
@@ -257,9 +285,7 @@ export class ExecutiveFacet {
|
|
|
257
285
|
this._pendingReports = []
|
|
258
286
|
|
|
259
287
|
for( const report of batch )
|
|
260
|
-
this.
|
|
261
|
-
logger.error( `[executive.facet] ${this.facetId} reasoning error:`, err )
|
|
262
|
-
)
|
|
288
|
+
this._launchReason( report )
|
|
263
289
|
}
|
|
264
290
|
|
|
265
291
|
subscribe( listener: FacetEventListener ): () => void {
|
|
@@ -344,7 +344,7 @@ ${roleDescription}
|
|
|
344
344
|
${consciousnessArchitecture}
|
|
345
345
|
|
|
346
346
|
## Output Guidelines
|
|
347
|
-
- **actions**: Choose from effectors you know about. If uncertain, describe what you want to achieve in natural language and your body will try to match it.
|
|
347
|
+
- **actions**: Choose from effectors you know about. If uncertain, describe what you want to achieve in natural language and your body will try to match it. When enacting one of your available abilities that needs specifics (a query, a message, a value), supply them in the action's "args" object — e.g. {"type": "search_docs", "args": {"query": "tick loop design"}, ...}. Your body enacts the ability with exactly those args.
|
|
348
348
|
- **plans**: Include for goals without existing plans or where plans need revision. You may keep multiple plans per goal — set **planId** to act on a specific existing plan (validate/execute/revise/cancel); omit it to draft a new one. Your current plans are listed under "## Active Plans".
|
|
349
349
|
- **newBeliefs**: Extract patterns from experiences visible in your current state. Only record a belief if you can point to a specific observation that supports it — do not infer experiences you have no record of. Set 'evidence' honestly: 'single_observation' (first time noticing), 'recurring_pattern' (seen multiple times), 'strong_pattern' (deeply established).
|
|
350
350
|
- **introspection**: Include when significant events occurred or you notice patterns. When you spot a cognitive bias in your own reasoning, name it in 'identifiedBiases' using its common term where one fits (e.g. overgeneralization, confirmation bias, recency bias) — this lets your self-assessment line up with the patterns your faculties detect on their own.
|
|
@@ -643,6 +643,15 @@ Dominance: ${context.affect.dominance.toFixed( 2 )}${context.affect.blends.lengt
|
|
|
643
643
|
? `## Percepts (What You Notice)\n${context.percepts.slice( 0, 10 ).map( p => `- [${p.category}] ${p.summary} (salience: ${p.salience.toFixed( 2 )})` ).join( '\n' ) || 'Nothing notable'}`
|
|
644
644
|
: ''
|
|
645
645
|
|
|
646
|
+
// Host abilities afforded right now + what each is for. Framed as
|
|
647
|
+
// self-knowledge (things you *can* do), NOT a tool-call menu: the Will still
|
|
648
|
+
// expresses intent in natural language and the agency field enacts the fit.
|
|
649
|
+
const abilitiesBlock = ( context.abilities && context.abilities.length > 0 )
|
|
650
|
+
? `## Abilities Available Now\nThings you can do in this situation — name one as an action's "type" (with "args" for any specifics it needs) and your body enacts it:\n${context.abilities.map( a =>
|
|
651
|
+
`- **${a.name}**${a.target ? ` (toward ${a.target})` : ''}${a.description ? ` — ${a.description}` : ''}`
|
|
652
|
+
).join( '\n' )}`
|
|
653
|
+
: ''
|
|
654
|
+
|
|
646
655
|
const ruminationsBlock = has( 'ruminations' )
|
|
647
656
|
? `## Active Ruminations (retrieved memories & thoughts)\n${context.workingMemory.map( w => `- [${w.type}] ${w.summary} (activation: ${w.activation.toFixed( 2 )})` ).join( '\n' ) || 'Nothing actively held in mind'}`
|
|
648
657
|
: ''
|
|
@@ -694,6 +703,7 @@ Dominance: ${context.affect.dominance.toFixed( 2 )}${context.affect.blends.lengt
|
|
|
694
703
|
actionDiversity.trim(),
|
|
695
704
|
recentOutcomesBlock,
|
|
696
705
|
perceptsBlock,
|
|
706
|
+
abilitiesBlock,
|
|
697
707
|
ruminationsBlock,
|
|
698
708
|
recentIntrospection.trim(),
|
|
699
709
|
memoriesBlock,
|