@mindot/will 0.1.1 → 0.3.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 +71 -4
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +26221 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +52 -7760
- package/dist/index.js +374 -86
- package/dist/index.js.map +1 -1
- 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/cli.ts +75 -0
- 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/host/boot.ts +127 -0
- package/src/host/utterances.ts +53 -0
- package/src/index.ts +6 -0
- package/src/mcp/effectors.ts +159 -0
- package/src/mcp/server.ts +144 -0
- package/src/sdk/will.ts +269 -44
- package/src/serve/server.ts +154 -0
- 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.3.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/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
|
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// ─────────────────────────────────────────────────────────────
|
|
3
|
+
// src/cli.ts — the `will` command: host a persistent mind
|
|
4
|
+
// ─────────────────────────────────────────────────────────────
|
|
5
|
+
//
|
|
6
|
+
// will mcp host over MCP stdio (Claude Desktop / Claude Code / IDEs)
|
|
7
|
+
// will serve host over HTTP (any language; the sidecar) — WILL_PORT/WILL_HOST
|
|
8
|
+
//
|
|
9
|
+
// Both hosts raise the same mind the same way (see host/boot.ts): env-configured,
|
|
10
|
+
// woken from its PMA artifact when one exists, hibernated back on the way out —
|
|
11
|
+
// the mind PERSISTS across sessions. Shared env: WILL_NAME, WILL_IDENTITY,
|
|
12
|
+
// WILL_TIER, WILL_LLM, WILL_TICK_MS, WILL_SEED, WILL_PMA_PATH, WILL_MCP_SERVERS.
|
|
13
|
+
//
|
|
14
|
+
// MCP client config:
|
|
15
|
+
// { "command": "npx", "args": ["-y", "@mindot/will", "mcp"],
|
|
16
|
+
// "env": { "WILL_NAME": "Aria", "WILL_IDENTITY": "I am Aria." } }
|
|
17
|
+
//
|
|
18
|
+
// Sidecar:
|
|
19
|
+
// WILL_NAME=Aria will serve # http://127.0.0.1:7777
|
|
20
|
+
// curl -X POST localhost:7777/perceive -d '{"text":"Hello"}'
|
|
21
|
+
// ─────────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
24
|
+
import { routeLogsToStderr, bootWillFromEnv } from '#root/host/boot'
|
|
25
|
+
import { buildWillMcpServer } from '#root/mcp/server'
|
|
26
|
+
import { buildWillHttpServer } from '#root/serve/server'
|
|
27
|
+
|
|
28
|
+
// stdout is the MCP protocol channel under `will mcp` — route logs FIRST.
|
|
29
|
+
routeLogsToStderr()
|
|
30
|
+
|
|
31
|
+
const USAGE = `usage: will <mcp | serve>
|
|
32
|
+
|
|
33
|
+
mcp host a persistent mind over MCP stdio (Claude Desktop / Claude Code)
|
|
34
|
+
serve host a persistent mind over HTTP (any language; WILL_PORT, default 7777)
|
|
35
|
+
|
|
36
|
+
Shared env: WILL_NAME, WILL_IDENTITY, WILL_TIER, WILL_LLM, WILL_TICK_MS,
|
|
37
|
+
WILL_SEED, WILL_PMA_PATH, WILL_MCP_SERVERS. The mind persists across runs via
|
|
38
|
+
its PMA artifact.`
|
|
39
|
+
|
|
40
|
+
async function main(): Promise<void> {
|
|
41
|
+
const sub = process.argv[2]
|
|
42
|
+
|
|
43
|
+
if( sub !== 'mcp' && sub !== 'serve' ){
|
|
44
|
+
console.error( sub ? `unknown subcommand: ${ sub }\n\n${ USAGE }` : USAGE )
|
|
45
|
+
process.exit( sub ? 2 : 0 )
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const { will, name, pmaPath, tickMs, engineTier, onCleanup, shutdown } = await bootWillFromEnv()
|
|
49
|
+
|
|
50
|
+
if( sub === 'mcp' ){
|
|
51
|
+
// The MCP client owns our stdin — its disconnect is the shutdown signal.
|
|
52
|
+
process.stdin.on( 'end', () => void shutdown( 'client disconnected' ) )
|
|
53
|
+
const server = buildWillMcpServer( will, { pmaPath } )
|
|
54
|
+
await server.connect( new StdioServerTransport() )
|
|
55
|
+
console.error( `[will] ${ name } is listening on MCP stdio (tick ${ tickMs }ms, tier ${ engineTier })` )
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// serve — the HTTP sidecar.
|
|
60
|
+
const port = parseInt( process.env.WILL_PORT ?? '7777' )
|
|
61
|
+
const host = process.env.WILL_HOST ?? '127.0.0.1'
|
|
62
|
+
const server = buildWillHttpServer( will, { pmaPath } )
|
|
63
|
+
onCleanup( () => new Promise<void>( r => server.close( () => r() ) ) )
|
|
64
|
+
await new Promise<void>( ( resolve, reject ) => {
|
|
65
|
+
server.once( 'error', reject )
|
|
66
|
+
server.listen( port, host, () => resolve() )
|
|
67
|
+
} )
|
|
68
|
+
console.error( `[will] ${ name } is listening on http://${ host }:${ port } (tick ${ tickMs }ms, tier ${ engineTier })` )
|
|
69
|
+
console.error( `[will] try: curl -X POST http://${ host }:${ port }/perceive -H 'content-type: application/json' -d '{"text":"Hello"}'` )
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
main().catch( e => {
|
|
73
|
+
console.error( `[will] fatal: ${ e instanceof Error ? e.stack ?? e.message : String( e ) }` )
|
|
74
|
+
process.exit( 1 )
|
|
75
|
+
} )
|
|
@@ -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
|