@mindot/will 0.1.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/LICENSE +202 -0
- package/README.md +777 -0
- package/dist/index.d.ts +8144 -0
- package/dist/index.js +26231 -0
- package/dist/index.js.map +1 -0
- package/package.json +73 -0
- package/src/cognition/agency/access.grants.ts +59 -0
- package/src/cognition/agency/competence.codec.ts +89 -0
- package/src/cognition/agency/engines/action.selector.ts +462 -0
- package/src/cognition/agency/engines/affordance.synthesizer.ts +384 -0
- package/src/cognition/agency/engines/deliberation.engine.ts +241 -0
- package/src/cognition/agency/engines/instruction.intake.ts +54 -0
- package/src/cognition/agency/engines/motor.schema.executor.ts +532 -0
- package/src/cognition/agency/engines/reafference.engine.ts +241 -0
- package/src/cognition/agency/execution.primitives.ts +118 -0
- package/src/cognition/agency/index.ts +58 -0
- package/src/cognition/agency/proactive.communicator.ts +274 -0
- package/src/cognition/agency/reconcile.learning.ts +82 -0
- package/src/cognition/agency/schemas/external.ts +59 -0
- package/src/cognition/agency/schemas/innate.ts +111 -0
- package/src/cognition/agency/schemas/repertoire.ts +246 -0
- package/src/cognition/agency/selection.scoring.ts +191 -0
- package/src/cognition/agency/types.ts +165 -0
- package/src/cognition/bus.ts +391 -0
- package/src/cognition/completion.inbox.ts +86 -0
- package/src/cognition/config.mirror.entities.ts +475 -0
- package/src/cognition/conversation.memory.ts +81 -0
- package/src/cognition/event.log.ts +116 -0
- package/src/cognition/event.schemas.ts +568 -0
- package/src/cognition/faculties/aesthetic.evaluator.ts +313 -0
- package/src/cognition/faculties/affective.blender.ts +562 -0
- package/src/cognition/faculties/attachment.evaluator.ts +432 -0
- package/src/cognition/faculties/attention.allocator.ts +422 -0
- package/src/cognition/faculties/autobiographical.narrator.ts +300 -0
- package/src/cognition/faculties/bias.detector.ts +363 -0
- package/src/cognition/faculties/circadian.oscillator.ts +294 -0
- package/src/cognition/faculties/confidence.calibrator.ts +324 -0
- package/src/cognition/faculties/dream.simulator.ts +251 -0
- package/src/cognition/faculties/empathy.simulator.ts +253 -0
- package/src/cognition/faculties/energy.regulator.ts +320 -0
- package/src/cognition/faculties/episodic.consolidator.ts +728 -0
- package/src/cognition/faculties/executive.engine/commands.ts +459 -0
- package/src/cognition/faculties/executive.engine/config.ts +46 -0
- package/src/cognition/faculties/executive.engine/context.ts +605 -0
- package/src/cognition/faculties/executive.engine/deferred.effects.ts +104 -0
- package/src/cognition/faculties/executive.engine/deliberate.reasoning.ts +60 -0
- package/src/cognition/faculties/executive.engine/effort.gate.ts +114 -0
- package/src/cognition/faculties/executive.engine/engine.ts +1028 -0
- package/src/cognition/faculties/executive.engine/escalation.buffer.ts +90 -0
- package/src/cognition/faculties/executive.engine/facet.supervisor.ts +271 -0
- package/src/cognition/faculties/executive.engine/facet.ts +628 -0
- package/src/cognition/faculties/executive.engine/gating.ts +222 -0
- package/src/cognition/faculties/executive.engine/index.ts +6 -0
- package/src/cognition/faculties/executive.engine/messages.ts +102 -0
- package/src/cognition/faculties/executive.engine/parser.ts +380 -0
- package/src/cognition/faculties/executive.engine/prompt.factory.ts +1053 -0
- package/src/cognition/faculties/executive.engine/types.ts +323 -0
- package/src/cognition/faculties/exteroception.ts +338 -0
- package/src/cognition/faculties/forgetting.curve.ts +202 -0
- package/src/cognition/faculties/frustration.evaluator.ts +280 -0
- package/src/cognition/faculties/goal.manager.ts +1008 -0
- package/src/cognition/faculties/inhibition.controller.ts +351 -0
- package/src/cognition/faculties/interoception.ts +417 -0
- package/src/cognition/faculties/introspection.engine.ts +246 -0
- package/src/cognition/faculties/known.entity.tracker.ts +439 -0
- package/src/cognition/faculties/loss.evaluator.ts +224 -0
- package/src/cognition/faculties/moral.evaluator.ts +437 -0
- package/src/cognition/faculties/novelty.detector.ts +254 -0
- package/src/cognition/faculties/persona.consolidator.ts +845 -0
- package/src/cognition/faculties/planning.engine/engine.ts +859 -0
- package/src/cognition/faculties/planning.engine/plan.frontier.ts +83 -0
- package/src/cognition/faculties/planning.engine/plan.store.ts +174 -0
- package/src/cognition/faculties/planning.engine/plan.supervision.ts +494 -0
- package/src/cognition/faculties/planning.engine/types.ts +143 -0
- package/src/cognition/faculties/reputation.tracker.ts +294 -0
- package/src/cognition/faculties/reward.evaluator.ts +304 -0
- package/src/cognition/faculties/self.model.updater.ts +677 -0
- package/src/cognition/faculties/semantic.engine/clustering.ts +673 -0
- package/src/cognition/faculties/semantic.engine/index.ts +6 -0
- package/src/cognition/faculties/semantic.engine/integrator.ts +750 -0
- package/src/cognition/faculties/semantic.engine/types.ts +53 -0
- package/src/cognition/faculties/sleep.pressure.regulator.ts +262 -0
- package/src/cognition/faculties/social.perception.ts +333 -0
- package/src/cognition/faculties/spaced.repetition.ts +605 -0
- package/src/cognition/faculties/stress.regulator.ts +375 -0
- package/src/cognition/faculties/task.switcher.ts +239 -0
- package/src/cognition/faculties/theory.of.mind.ts +333 -0
- package/src/cognition/faculties/threat.evaluator.ts +312 -0
- package/src/cognition/faculties/working.memory.ts +406 -0
- package/src/cognition/generative.model.ts +344 -0
- package/src/cognition/heartbeat.ts +70 -0
- package/src/cognition/index.ts +291 -0
- package/src/cognition/instruction.handler.ts +146 -0
- package/src/cognition/memory/index.ts +35 -0
- package/src/cognition/memory/vector.adapter.ts +316 -0
- package/src/cognition/memory/vector.content.ts +47 -0
- package/src/cognition/memory/vector.embedder.ts +212 -0
- package/src/cognition/memory/vector.index.ts +459 -0
- package/src/cognition/memory/vector.types.ts +65 -0
- package/src/cognition/orchestrator.ts +142 -0
- package/src/cognition/persona.prior.ts +265 -0
- package/src/cognition/schema.registry.ts +145 -0
- package/src/cognition/senses/audition.engine/engine.ts +976 -0
- package/src/cognition/senses/audition.engine/salience.ts +59 -0
- package/src/cognition/senses/base.sense.engine.ts +117 -0
- package/src/cognition/senses/gustation.engine.ts +24 -0
- package/src/cognition/senses/index.ts +172 -0
- package/src/cognition/senses/olfaction.engine.ts +24 -0
- package/src/cognition/senses/somatosensation.engine.ts +23 -0
- package/src/cognition/senses/vision.engine.ts +23 -0
- package/src/cognition/types.ts +63 -0
- package/src/cognition/utilities/token.tracker.ts +492 -0
- package/src/core/abstracts.ts +60 -0
- package/src/core/async.engine.ts +539 -0
- package/src/core/clock.ts +196 -0
- package/src/core/completion.recorder.ts +122 -0
- package/src/core/conflict.detector.ts +140 -0
- package/src/core/distributed.ts +626 -0
- package/src/core/event.bus.ts +292 -0
- package/src/core/inbound.recorder.ts +100 -0
- package/src/core/index.ts +167 -0
- package/src/core/logger.ts +85 -0
- package/src/core/metrics.ts +107 -0
- package/src/core/orchestrator.ts +625 -0
- package/src/core/replay.ts +589 -0
- package/src/core/scenario.ts +120 -0
- package/src/core/serialization.ts +448 -0
- package/src/core/simulation.ts +197 -0
- package/src/core/snapshot.manager.ts +293 -0
- package/src/core/state.manager.ts +283 -0
- package/src/core/types.ts +274 -0
- package/src/core/utils.ts +82 -0
- package/src/core/wall.clock.ts +23 -0
- package/src/extensions/livestream.ext.ts +570 -0
- package/src/extensions/time.ext.ts +339 -0
- package/src/index.ts +91 -0
- package/src/llm/gate.ts +140 -0
- package/src/llm/index.ts +716 -0
- package/src/llm/summarizer.ts +170 -0
- package/src/llm/wire.contracts.ts +67 -0
- package/src/pma/eval.ts +651 -0
- package/src/pma/index.ts +1082 -0
- package/src/profiles/built-in.ts +7 -0
- package/src/profiles/companion.ts +28 -0
- package/src/profiles/company-brain.ts +54 -0
- package/src/profiles/customer-service.ts +30 -0
- package/src/profiles/game-npc.ts +20 -0
- package/src/profiles/index.ts +42 -0
- package/src/profiles/smart-home.ts +34 -0
- package/src/runners/coherence.runner.ts +49 -0
- package/src/runners/outreach.runner.ts +158 -0
- package/src/runners/social.runner.ts +182 -0
- package/src/runners/thin-shim.runner.ts +195 -0
- package/src/sdk/will.ts +334 -0
- package/src/stem/assembly.audit.ts +88 -0
- package/src/stem/distribution.ts +203 -0
- package/src/stem/guards/identity.coherence.ts +152 -0
- package/src/stem/guards/identity.guard.ts +227 -0
- package/src/stem/index.ts +1145 -0
- package/src/stem/mind.ts +1090 -0
- package/src/stem/tracts/ack.reconciler.ts +55 -0
- package/src/stem/tracts/biography.writer.ts +190 -0
- package/src/stem/tracts/effector.controller.ts +143 -0
- package/src/stem/tracts/health.reporter.ts +113 -0
- package/src/stem/tracts/inbound.queue.ts +54 -0
- package/src/stem/tracts/outbox.controller.ts +167 -0
- package/src/stem/tracts/outbox.writer.ts +133 -0
- package/src/stem/tracts/pma.controller.ts +109 -0
- package/src/stem/tracts/replay.controller.ts +177 -0
- package/src/stem/tracts/sensory.controller.ts +168 -0
- package/src/stem/tracts/session.logger.ts +157 -0
- package/src/stem/tracts/transport/index.ts +38 -0
- package/src/stem/tracts/transport/loopback.transport.ts +86 -0
- package/src/stem/tracts/transport/socketio.transport.ts +200 -0
- package/src/stem/tracts/transport/stream.transport.ts +152 -0
- package/src/stem/tracts/transport/types.ts +193 -0
- package/src/stem/tracts/transport.controller.ts +380 -0
- package/src/types.ts +116 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/agency/affordance.synthesizer.ts — head of the agency pipeline
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
//
|
|
5
|
+
// The AffordanceSynthesizer is the Gibsonian move: instead of a static catalog
|
|
6
|
+
// the executive selects from, perception *emits* the field of what is possible
|
|
7
|
+
// right now, for this body, in this state. It runs every tick with no LLM.
|
|
8
|
+
//
|
|
9
|
+
// Each tick it draws on:
|
|
10
|
+
// • the innate floor — reflexive, objectless possibilities (always present)
|
|
11
|
+
// • salient percepts — "inspect this", bound to what perception surfaced
|
|
12
|
+
// • known sentient minds — "reach out to them", bound to the relationship
|
|
13
|
+
// • (Phase 3) the repertoire of learned composite skills
|
|
14
|
+
//
|
|
15
|
+
// Two properties make it mind-shaped, not framework-shaped:
|
|
16
|
+
// 1. Parameters are bound *at perception time* from the evoking thing, so an
|
|
17
|
+
// affordance can never arrive at execution with empty arguments.
|
|
18
|
+
// 2. Attention gates the WIDTH of the field — a depleted or unfocused mind
|
|
19
|
+
// literally has fewer possibilities available to it. The body shapes the
|
|
20
|
+
// option set, not just the choice.
|
|
21
|
+
//
|
|
22
|
+
// Output: transient `affordance` entities (the field is rebuilt every tick),
|
|
23
|
+
// plus metrics and an `affordance.field.synthesized` bus event for the selector
|
|
24
|
+
// and telemetry. See AGENCY_PIPELINE_TODO.md.
|
|
25
|
+
// ─────────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
import { logger } from '#core/logger'
|
|
28
|
+
import type {
|
|
29
|
+
Duration, Tick, SimulationContext,
|
|
30
|
+
ReadonlySimulationState, StateCommands, EntityInput,
|
|
31
|
+
} from '#core/types'
|
|
32
|
+
import type { CognitiveBus } from '#cognition/bus'
|
|
33
|
+
import type { CognitiveEngine, EngineResult } from '#cognition/types'
|
|
34
|
+
import type { CognitiveEventSchema } from '#cognition/schema.registry'
|
|
35
|
+
import type { Affordance, AffordanceSource, MotorSchema, LearnedSkill, SchemaPrecondition } from '#agency/types'
|
|
36
|
+
import type { SchemaRepertoire } from '#agency/schemas/repertoire'
|
|
37
|
+
import { INNATE_SCHEMAS } from '#agency/schemas/innate'
|
|
38
|
+
import { collectGoalTargets } from '#agency/selection.scoring'
|
|
39
|
+
|
|
40
|
+
/** Default field width for non-innate affordances when no attention metric exists. */
|
|
41
|
+
const DEFAULT_ATTENTION_CAP = 5
|
|
42
|
+
/** Hard ceiling so a flood of percepts can never explode the field. */
|
|
43
|
+
const MAX_ATTENTION_CAP = 12
|
|
44
|
+
/** Pre-activation floor for an ideomotor candidate — the executive deliberately willed
|
|
45
|
+
* it, so it should reliably reach the field, but it still competes in the selector. */
|
|
46
|
+
const IDEOMOTOR_BASE_SALIENCE = 0.5
|
|
47
|
+
|
|
48
|
+
type SkillAccessor = () => ReadonlyMap<string, LearnedSkill>
|
|
49
|
+
|
|
50
|
+
interface Candidate {
|
|
51
|
+
/** Coarse evoke-salience used only for attention budgeting (selection scores later). */
|
|
52
|
+
salience: number
|
|
53
|
+
affordance: Affordance
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export class AffordanceSynthesizer implements CognitiveEngine {
|
|
57
|
+
readonly name = 'affordance-synthesizer'
|
|
58
|
+
|
|
59
|
+
private _schemas: MotorSchema[]
|
|
60
|
+
private _skills: SkillAccessor | null = null
|
|
61
|
+
private _repertoire: SchemaRepertoire | null = null
|
|
62
|
+
private _bus: CognitiveBus | null = null
|
|
63
|
+
private _defaultCap: number
|
|
64
|
+
private _lastFieldSize = 0
|
|
65
|
+
|
|
66
|
+
constructor( schemas: MotorSchema[] = INNATE_SCHEMAS, defaultCap = DEFAULT_ATTENTION_CAP ){
|
|
67
|
+
this._schemas = schemas
|
|
68
|
+
this._defaultCap = defaultCap
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ── wiring ────────────────────────────────────────────────────
|
|
72
|
+
attachBus( bus: CognitiveBus ): void { this._bus = bus }
|
|
73
|
+
/** Inject the repertoire's learned-skill accessor (Phase 3). */
|
|
74
|
+
attachSkills( accessor: SkillAccessor ): void { this._skills = accessor }
|
|
75
|
+
/**
|
|
76
|
+
* Attach the live repertoire — its templates (innate floor + learned composites)
|
|
77
|
+
* become the schema set, and its skills feed the affordance priors. This is how
|
|
78
|
+
* a newly-learned composite shows up in the field without a restart.
|
|
79
|
+
*/
|
|
80
|
+
attachRepertoire( repertoire: SchemaRepertoire ): void { this._repertoire = repertoire }
|
|
81
|
+
/** Register an additional schema template (e.g. a learned composite). */
|
|
82
|
+
registerSchema( schema: MotorSchema ): void { this._schemas.push( schema ) }
|
|
83
|
+
|
|
84
|
+
// ── CognitiveEngine interface ─────────────────────────────────
|
|
85
|
+
publishes(): CognitiveEventSchema[] {
|
|
86
|
+
return [ { type: 'affordance.field.synthesized', version: 1, validate: () => null } ]
|
|
87
|
+
}
|
|
88
|
+
subscribes(): string[] { return [] }
|
|
89
|
+
onCognitiveEvent(): void { /* pull model — reads frozen state each tick */ }
|
|
90
|
+
snapshot(): Record<string, unknown> { return { lastFieldSize: this._lastFieldSize } }
|
|
91
|
+
|
|
92
|
+
// ── react ─────────────────────────────────────────────────────
|
|
93
|
+
async react(
|
|
94
|
+
_delta: Duration,
|
|
95
|
+
tick: Tick,
|
|
96
|
+
state: ReadonlySimulationState,
|
|
97
|
+
_context: SimulationContext,
|
|
98
|
+
): Promise<EngineResult> {
|
|
99
|
+
// Rehydrate learned composites the executor needs to resolve. After a
|
|
100
|
+
// snapshot/restore the repertoire is rebuilt innate-only; the composite
|
|
101
|
+
// *definitions* come back as `agency.schema` entities (written by the
|
|
102
|
+
// ReafferenceEngine). Doing this here — the first agency engine each tick —
|
|
103
|
+
// means the executor, which ticks later in the SAME tick, sees a whole
|
|
104
|
+
// repertoire and can expand a restored mid-flight macro. Idempotent.
|
|
105
|
+
this._repertoire?.restoreComposites( state.entities )
|
|
106
|
+
|
|
107
|
+
const schemas = this._repertoire?.schemas() ?? this._schemas
|
|
108
|
+
const skills = this._skills?.() ?? this._repertoire?.skills() ?? null
|
|
109
|
+
const valence = metric( state, 'affect.valence', 0 )
|
|
110
|
+
const energyLow = metric( state, 'energy.level', 0 ) < 30
|
|
111
|
+
|
|
112
|
+
const set: EntityInput[] = []
|
|
113
|
+
const del: string[] = []
|
|
114
|
+
|
|
115
|
+
// Clear the previous tick's field — affordances are transient.
|
|
116
|
+
for( const [ id, e ] of state.entities )
|
|
117
|
+
if( e.type === 'affordance' ) del.push( id )
|
|
118
|
+
|
|
119
|
+
// ── 1. innate floor — always emitted, never attention-capped ──
|
|
120
|
+
const floor = schemas.filter( s => s.binds === 'none' )
|
|
121
|
+
for( const schema of floor )
|
|
122
|
+
set.push( this._toEntity(
|
|
123
|
+
this._build( schema, tick, state, valence, energyLow, skills, {} ),
|
|
124
|
+
) )
|
|
125
|
+
|
|
126
|
+
// ── 2. perception-bound candidates, attention-budgeted ───────
|
|
127
|
+
// Goal-relevance counts at the CAP, not only at selection: an affordance whose
|
|
128
|
+
// target an active goal is directed at is lifted here so goal-driven outreach
|
|
129
|
+
// isn't out-competed by ambient rumination before the selector (which also
|
|
130
|
+
// scores goalRelevance) ever sees it. Same `goalTargets` notion as the selector.
|
|
131
|
+
const candidates: Candidate[] = []
|
|
132
|
+
const goalTargets: Map<string, number> = collectGoalTargets( state )
|
|
133
|
+
|
|
134
|
+
const perceptSchema = schemas.find( s => s.binds === 'percept' )
|
|
135
|
+
if( perceptSchema )
|
|
136
|
+
for( const [ id, e ] of state.entities ){
|
|
137
|
+
if( e.type !== 'percept' ) continue
|
|
138
|
+
const m = e.metadata
|
|
139
|
+
const salience = num( m?.['salience'], 0 )
|
|
140
|
+
const summary = str( m?.['summary'] ) ?? str( m?.['category'] ) ?? 'something'
|
|
141
|
+
const target = str( m?.['entityId'] ) ?? str( m?.['targetEntityId'] )
|
|
142
|
+
candidates.push({
|
|
143
|
+
salience: salience + ( target ? goalTargets.get( target ) ?? 0 : 0 ),
|
|
144
|
+
affordance: this._build( perceptSchema, tick, state, valence, energyLow, skills, {
|
|
145
|
+
evokedBy: id,
|
|
146
|
+
targetEntityId: target,
|
|
147
|
+
parameters: { focus: summary },
|
|
148
|
+
} ),
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const entitySchema = schemas.find( s => s.binds === 'entity' )
|
|
153
|
+
if( entitySchema )
|
|
154
|
+
for( const [ id, e ] of state.entities ){
|
|
155
|
+
if( e.type !== 'known-entity' ) continue
|
|
156
|
+
const m = e.metadata
|
|
157
|
+
if( str( m?.['kind'] ) !== 'sentient' ) continue
|
|
158
|
+
const keid = str( m?.['keid'] ) ?? id
|
|
159
|
+
const fam = num( m?.['familiarity'], 0 )
|
|
160
|
+
const val = num( m?.['valence'], 0 )
|
|
161
|
+
const res = num( m?.['resolutionConfidence'], 0 )
|
|
162
|
+
candidates.push({
|
|
163
|
+
salience: fam * 0.6 + Math.max( 0, val ) * 0.3 + res * 0.1 + ( goalTargets.get( keid ) ?? 0 ),
|
|
164
|
+
affordance: this._build( entitySchema, tick, state, valence, energyLow, skills, {
|
|
165
|
+
evokedBy: id,
|
|
166
|
+
targetEntityId: keid,
|
|
167
|
+
parameters: { targetEntityName: str( m?.['name'] ) ?? keid },
|
|
168
|
+
} ),
|
|
169
|
+
})
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ── ideomotor candidates: the executive's imagined actions, pre-activated ──
|
|
173
|
+
// The executive writes `ideomotor.intent` entities for the actions it imagines
|
|
174
|
+
// (its "what if I…"). They enter the field as HIGH-salience candidates — it
|
|
175
|
+
// deliberately willed them — but still COMPETE in the selector; they never bypass
|
|
176
|
+
// it. This is the AffordanceSource.ideomotor leg: executive intention → an
|
|
177
|
+
// affordance that competes like any other.
|
|
178
|
+
for( const [ id, e ] of state.entities ){
|
|
179
|
+
if( e.type !== 'ideomotor.intent' ) continue
|
|
180
|
+
const m = e.metadata
|
|
181
|
+
const schemaId = str( m?.['schema'] )
|
|
182
|
+
const schema = schemaId ? schemas.find( s => s.id === schemaId ) : undefined
|
|
183
|
+
if( !schema ) continue
|
|
184
|
+
candidates.push({
|
|
185
|
+
salience: IDEOMOTOR_BASE_SALIENCE + num( m?.['priority'], 0.8 ),
|
|
186
|
+
affordance: this._build( schema, tick, state, valence, energyLow, skills, {
|
|
187
|
+
evokedBy: id,
|
|
188
|
+
targetEntityId: str( m?.['targetEntityId'] ),
|
|
189
|
+
parameters: ( m?.['parameters'] as Record<string, unknown> ) ?? {},
|
|
190
|
+
source: 'ideomotor',
|
|
191
|
+
} ),
|
|
192
|
+
})
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ── plan priors: an executing plan's frontier step, pre-activated ──────────
|
|
196
|
+
// A plan does not dispatch its steps; it BIASES the competition toward the
|
|
197
|
+
// action its current frontier needs. The PlanningEngine writes one `plan.prior`
|
|
198
|
+
// per ready frontier step; each enters the field as a HIGH-salience candidate
|
|
199
|
+
// (the frontier is willed) carrying a `planBias` the scorer weighs and the
|
|
200
|
+
// planId/stepId provenance that flows through to action.outcome so the plan
|
|
201
|
+
// advances when the field actually enacts it. Like ideomotor, it never bypasses
|
|
202
|
+
// the selector — if a more pressing affordance wins, the plan simply re-projects
|
|
203
|
+
// next tick. The suggested schema must resolve in the repertoire; if it does not,
|
|
204
|
+
// the prior cannot surface as an action and the plan waits / replans.
|
|
205
|
+
for( const [ id, e ] of state.entities ){
|
|
206
|
+
if( e.type !== 'plan.prior' ) continue
|
|
207
|
+
const m = e.metadata
|
|
208
|
+
const schemaId = str( m?.['schema'] )
|
|
209
|
+
const schema = schemaId ? schemas.find( s => s.id === schemaId ) : undefined
|
|
210
|
+
if( !schema ) continue
|
|
211
|
+
const planBias = clamp01( num( m?.['planBias'], 0.6 ) )
|
|
212
|
+
candidates.push({
|
|
213
|
+
salience: IDEOMOTOR_BASE_SALIENCE + planBias,
|
|
214
|
+
affordance: this._build( schema, tick, state, valence, energyLow, skills, {
|
|
215
|
+
evokedBy: id,
|
|
216
|
+
targetEntityId: str( m?.['targetEntityId'] ),
|
|
217
|
+
parameters: ( m?.['parameters'] as Record<string, unknown> ) ?? {},
|
|
218
|
+
source: 'plan',
|
|
219
|
+
planBias,
|
|
220
|
+
planId: str( m?.['planId'] ),
|
|
221
|
+
stepId: str( m?.['stepId'] ),
|
|
222
|
+
} ),
|
|
223
|
+
})
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Attention gates the WIDTH of the field: keep only the top-salient few.
|
|
227
|
+
const cap = this._attentionCap( state )
|
|
228
|
+
candidates.sort( ( a, b ) => b.salience - a.salience )
|
|
229
|
+
for( const c of candidates.slice( 0, cap ) )
|
|
230
|
+
set.push( this._toEntity( c.affordance ) )
|
|
231
|
+
|
|
232
|
+
// ── 3. metrics + telemetry ───────────────────────────────────
|
|
233
|
+
const fieldSize = set.length
|
|
234
|
+
const availableCount = set.reduce( ( n, e ) => n + ( e.metadata?.['available'] ? 1 : 0 ), 0 )
|
|
235
|
+
this._lastFieldSize = fieldSize
|
|
236
|
+
|
|
237
|
+
const commands: StateCommands = {
|
|
238
|
+
set,
|
|
239
|
+
delete: del,
|
|
240
|
+
metrics: [
|
|
241
|
+
[ 'affordance.field_size', fieldSize ],
|
|
242
|
+
[ 'affordance.available_count', availableCount ],
|
|
243
|
+
],
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if( this._bus ){
|
|
247
|
+
try {
|
|
248
|
+
this._bus.publish({
|
|
249
|
+
type: 'affordance.field.synthesized',
|
|
250
|
+
version: 1,
|
|
251
|
+
sourceEngine: this.name,
|
|
252
|
+
salience: 0.3,
|
|
253
|
+
payload: { size: fieldSize, availableCount, tick },
|
|
254
|
+
})
|
|
255
|
+
}
|
|
256
|
+
catch( err ){
|
|
257
|
+
logger.warn( `[affordance] bus publish failed: ${ err instanceof Error ? err.message : String( err ) }` )
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return { commands }
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ── internals ─────────────────────────────────────────────────
|
|
265
|
+
|
|
266
|
+
/** Compose an Affordance from a schema + the evoking context, folding in learned priors. */
|
|
267
|
+
private _build(
|
|
268
|
+
schema: MotorSchema,
|
|
269
|
+
tick: Tick,
|
|
270
|
+
state: ReadonlySimulationState,
|
|
271
|
+
valence: number,
|
|
272
|
+
energyLow: boolean,
|
|
273
|
+
skills: ReadonlyMap<string, LearnedSkill> | null,
|
|
274
|
+
ctx: {
|
|
275
|
+
evokedBy?: string
|
|
276
|
+
targetEntityId?: string
|
|
277
|
+
parameters?: Record<string, unknown>
|
|
278
|
+
source?: AffordanceSource
|
|
279
|
+
planBias?: number
|
|
280
|
+
planId?: string
|
|
281
|
+
stepId?: string
|
|
282
|
+
},
|
|
283
|
+
): Affordance {
|
|
284
|
+
const skill = skills?.get( schema.id )
|
|
285
|
+
|
|
286
|
+
// Learned value if known, else the schema's intrinsic prior mapped to 0..1.
|
|
287
|
+
const expectedReward = skill?.valueEstimate
|
|
288
|
+
?? clamp01( ( ( schema.baseValence ?? 0 ) + 1 ) / 2 )
|
|
289
|
+
const expectedValence = schema.baseValence ?? valence
|
|
290
|
+
const habitStrength = skill?.habitStrength ?? 0
|
|
291
|
+
// Low energy makes everything feel costlier.
|
|
292
|
+
const cost = clamp01( schema.cost * ( energyLow ? 1.5 : 1 ) )
|
|
293
|
+
|
|
294
|
+
const key = ctx.targetEntityId ?? ctx.evokedBy ?? schema.id
|
|
295
|
+
const source = ctx.source ?? schema.source
|
|
296
|
+
// A non-default-source candidate (ideomotor) gets a distinct id so it can coexist
|
|
297
|
+
// with a same-schema/target candidate evoked from another source (e.g. entity-bound).
|
|
298
|
+
const idTag = ctx.source && ctx.source !== schema.source ? `-${ ctx.source }` : ''
|
|
299
|
+
|
|
300
|
+
return {
|
|
301
|
+
id: `affordance-${ tick }-${ schema.id }-${ key }${ idTag }`,
|
|
302
|
+
schema: schema.id,
|
|
303
|
+
source,
|
|
304
|
+
parameters: { ...( skill?.paramPriors ?? {} ), ...( ctx.parameters ?? {} ) },
|
|
305
|
+
targetEntityId: ctx.targetEntityId,
|
|
306
|
+
evokedBy: ctx.evokedBy,
|
|
307
|
+
expectedValence,
|
|
308
|
+
expectedReward,
|
|
309
|
+
cost,
|
|
310
|
+
habitStrength,
|
|
311
|
+
available: this._available( schema.preconditions, ( k ) => metric( state, k, 0 ) ),
|
|
312
|
+
tags: schema.tags ?? [],
|
|
313
|
+
planBias: ctx.planBias,
|
|
314
|
+
planId: ctx.planId,
|
|
315
|
+
stepId: ctx.stepId,
|
|
316
|
+
tick,
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
private _toEntity( a: Affordance ): EntityInput {
|
|
321
|
+
return {
|
|
322
|
+
id: a.id,
|
|
323
|
+
type: 'affordance',
|
|
324
|
+
metadata: {
|
|
325
|
+
schema: a.schema,
|
|
326
|
+
source: a.source,
|
|
327
|
+
parameters: a.parameters,
|
|
328
|
+
targetEntityId: a.targetEntityId,
|
|
329
|
+
evokedBy: a.evokedBy,
|
|
330
|
+
expectedValence: a.expectedValence,
|
|
331
|
+
expectedReward: a.expectedReward,
|
|
332
|
+
cost: a.cost,
|
|
333
|
+
habitStrength: a.habitStrength,
|
|
334
|
+
available: a.available,
|
|
335
|
+
tags: a.tags,
|
|
336
|
+
planBias: a.planBias,
|
|
337
|
+
planId: a.planId,
|
|
338
|
+
stepId: a.stepId,
|
|
339
|
+
tick: a.tick,
|
|
340
|
+
},
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
private _available(
|
|
345
|
+
preconditions: SchemaPrecondition[] | undefined,
|
|
346
|
+
read: ( metric: string ) => number,
|
|
347
|
+
): boolean {
|
|
348
|
+
if( !preconditions ) return true
|
|
349
|
+
return preconditions.every( pc => {
|
|
350
|
+
const cur = read( pc.metric )
|
|
351
|
+
switch( pc.op ){
|
|
352
|
+
case 'gt': return cur > pc.value
|
|
353
|
+
case 'lt': return cur < pc.value
|
|
354
|
+
case 'gte': return cur >= pc.value
|
|
355
|
+
case 'lte': return cur <= pc.value
|
|
356
|
+
case 'eq': return cur === pc.value
|
|
357
|
+
default: return false
|
|
358
|
+
}
|
|
359
|
+
})
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
private _attentionCap( state: ReadonlySimulationState ): number {
|
|
363
|
+
const cap = state.metrics.get( 'attention.capacity' ) ?? this._defaultCap
|
|
364
|
+
return Math.max( 1, Math.min( MAX_ATTENTION_CAP, Math.round( cap ) ) )
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// ─── module helpers ──────────────────────────────────────────────────────────
|
|
369
|
+
|
|
370
|
+
function metric( state: ReadonlySimulationState, key: string, fallback: number ): number {
|
|
371
|
+
return state.metrics.get( key ) ?? fallback
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function num( v: unknown, fallback: number ): number {
|
|
375
|
+
return typeof v === 'number' && Number.isFinite( v ) ? v : fallback
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function str( v: unknown ): string | undefined {
|
|
379
|
+
return typeof v === 'string' ? v : undefined
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function clamp01( n: number ): number {
|
|
383
|
+
return n < 0 ? 0 : n > 1 ? 1 : n
|
|
384
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/agency/deliberation.engine.ts — the System-2 seam
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
//
|
|
5
|
+
// The DeliberationEngine is the only place the LLM touches the agency pipeline,
|
|
6
|
+
// and it does so through the SAME unified-inference path as everything else: it
|
|
7
|
+
// spawns a facet — "a focused instance of the executive consciousness" — which
|
|
8
|
+
// builds its prompt from the unified PromptFactory (persona, identity, live
|
|
9
|
+
// self-context). There is NO bespoke prompt here. This is the project-wide rule:
|
|
10
|
+
// every LLM call shares one self, so the Will never fractures / hallucinates a
|
|
11
|
+
// different identity when it deliberates vs. converses vs. narrates.
|
|
12
|
+
//
|
|
13
|
+
// It is recruited only when the selector marks a choice 'deliberating' (ambiguous
|
|
14
|
+
// or high-stakes). It then biases the competition — choosing among the candidate
|
|
15
|
+
// affordances the substrate already surfaced (ideomotor: imagining outcomes
|
|
16
|
+
// pre-activates one) — and writes the result back as the now-'selected' intent.
|
|
17
|
+
// It never invents actions outside the field. Message *content* authoring stays
|
|
18
|
+
// with the unified conversation facet path (AuditionEngine), not duplicated here.
|
|
19
|
+
//
|
|
20
|
+
// With no executive attached (basic tier) or the facet budget full, it confirms
|
|
21
|
+
// the substrate's provisional winner — graceful System-1 degradation, never a
|
|
22
|
+
// stall, and never an un-grounded LLM call.
|
|
23
|
+
// ─────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
import { logger } from '#core/logger'
|
|
26
|
+
import type {
|
|
27
|
+
Duration, Tick, SimulationContext,
|
|
28
|
+
ReadonlySimulationState, StateCommands, EntityInput,
|
|
29
|
+
} from '#core/types'
|
|
30
|
+
import type { CognitiveBus } from '#cognition/bus'
|
|
31
|
+
import type { CognitiveEngine, EngineResult } from '#cognition/types'
|
|
32
|
+
import type { CognitiveEventSchema } from '#cognition/schema.registry'
|
|
33
|
+
|
|
34
|
+
// ── Minimal structural view of the executive's facet machinery ───────────────
|
|
35
|
+
// Kept structural (not an import of the executive engine) so the agency module
|
|
36
|
+
// stays decoupled; ExecutiveEngine is assignable to DeliberationFacetProvider.
|
|
37
|
+
|
|
38
|
+
interface DeliberationFacetHandle {
|
|
39
|
+
setFocus( focus: { title: string; content: string; instructions?: string; function?: string } ): void
|
|
40
|
+
report( report: { type: string; payload: unknown } ): Promise<void> | void
|
|
41
|
+
subscribe( listener: ( decision: { decision: unknown } ) => void ): () => void
|
|
42
|
+
destroy(): void
|
|
43
|
+
}
|
|
44
|
+
export interface DeliberationFacetProvider {
|
|
45
|
+
spawnFacet(): { attention: 'available' | 'full'; handle?: DeliberationFacetHandle }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface Candidate {
|
|
49
|
+
schema: string
|
|
50
|
+
targetEntityId?: string
|
|
51
|
+
parameters?: Record<string, unknown>
|
|
52
|
+
activation?: number
|
|
53
|
+
/** Channel B: this candidate is an active plan's current frontier step. */
|
|
54
|
+
fromPlan?: boolean
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const DELIBERATION_INSTRUCTIONS =
|
|
58
|
+
'Automatic action-selection was uncertain or the stakes were high. From the candidate actions ' +
|
|
59
|
+
'listed above, choose the ONE that best fits who you are and your situation. Do not invent ' +
|
|
60
|
+
'actions that are not listed. Put your chosen action as your single action; its "type" must be ' +
|
|
61
|
+
'exactly one of the candidate names.'
|
|
62
|
+
|
|
63
|
+
export class DeliberationEngine implements CognitiveEngine {
|
|
64
|
+
readonly name = 'deliberation'
|
|
65
|
+
|
|
66
|
+
private _provider: DeliberationFacetProvider | null = null
|
|
67
|
+
private _handle: DeliberationFacetHandle | null = null
|
|
68
|
+
private _bus: CognitiveBus | null = null
|
|
69
|
+
private _willName = 'the mind'
|
|
70
|
+
private _deliberations = 0
|
|
71
|
+
|
|
72
|
+
attachBus( bus: CognitiveBus ): void { this._bus = bus }
|
|
73
|
+
/**
|
|
74
|
+
* Attach the executive (facet provider). Absent ⇒ the engine confirms the
|
|
75
|
+
* substrate's winner (System 1) — used at basic tier where no LLM runs.
|
|
76
|
+
*/
|
|
77
|
+
attachExecutive( provider: DeliberationFacetProvider ): void { this._provider = provider }
|
|
78
|
+
setWillName( name: string ): void { this._willName = name }
|
|
79
|
+
|
|
80
|
+
publishes(): CognitiveEventSchema[] {
|
|
81
|
+
return [ { type: 'agency.deliberated', version: 1, validate: () => null } ]
|
|
82
|
+
}
|
|
83
|
+
subscribes(): string[] { return [] }
|
|
84
|
+
onCognitiveEvent(): void { /* pull model — reads deliberating intents from frozen state */ }
|
|
85
|
+
snapshot(): Record<string, unknown> { return { deliberations: this._deliberations } }
|
|
86
|
+
|
|
87
|
+
async react(
|
|
88
|
+
_delta: Duration,
|
|
89
|
+
tick: Tick,
|
|
90
|
+
state: ReadonlySimulationState,
|
|
91
|
+
_context: SimulationContext,
|
|
92
|
+
): Promise<EngineResult> {
|
|
93
|
+
// One deliberation per tick (the serial bottleneck).
|
|
94
|
+
let target: { id: string; meta: Record<string, unknown> } | null = null
|
|
95
|
+
for( const [ id, e ] of state.entities ){
|
|
96
|
+
if( e.type !== 'agency.intent' ) continue
|
|
97
|
+
if( str( e.metadata?.['status'] ) !== 'deliberating' ) continue
|
|
98
|
+
const meta = ( e.metadata ?? {} ) as Record<string, unknown>
|
|
99
|
+
if( !target || id < target.id ) target = { id, meta } // stable pick
|
|
100
|
+
}
|
|
101
|
+
if( !target ) return { commands: {} }
|
|
102
|
+
|
|
103
|
+
const { id, meta } = target
|
|
104
|
+
const provisional = str( meta['schema'] ) ?? 'wait'
|
|
105
|
+
const candidates: Candidate[] = Array.isArray( meta['candidates'] ) ? ( meta['candidates'] as Candidate[] ) : []
|
|
106
|
+
|
|
107
|
+
// No executive → confirm the substrate's winner (graceful System-1).
|
|
108
|
+
if( !this._provider )
|
|
109
|
+
return { commands: { set: [ this._commit( id, meta, provisional, candidates, 'no-executive' ) ] } }
|
|
110
|
+
|
|
111
|
+
// Deliberate through a UNIFIED facet (same persona/context as the master).
|
|
112
|
+
const chosen = await this._deliberate( state, candidates, provisional, meta )
|
|
113
|
+
|
|
114
|
+
this._deliberations++
|
|
115
|
+
if( this._bus ){
|
|
116
|
+
try {
|
|
117
|
+
this._bus.publish({
|
|
118
|
+
type: 'agency.deliberated', version: 1, sourceEngine: this.name,
|
|
119
|
+
salience: 0.6, payload: { intentId: id, chosen, tick },
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
catch { /* unregistered schema is telemetry-only */ }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
commands: {
|
|
127
|
+
set: [ this._commit( id, meta, chosen, candidates, 'facet' ) ],
|
|
128
|
+
metrics: [ [ 'agency.deliberation.count', 1 ] ],
|
|
129
|
+
},
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ── internals ─────────────────────────────────────────────────
|
|
134
|
+
|
|
135
|
+
/** Run one unified-inference deliberation. Returns the chosen schema (or the provisional winner on any failure). */
|
|
136
|
+
private async _deliberate(
|
|
137
|
+
state: ReadonlySimulationState,
|
|
138
|
+
candidates: Candidate[],
|
|
139
|
+
provisional: string,
|
|
140
|
+
meta: Record<string, unknown>,
|
|
141
|
+
): Promise<string> {
|
|
142
|
+
try {
|
|
143
|
+
if( !this._handle ){
|
|
144
|
+
const spawned = this._provider!.spawnFacet()
|
|
145
|
+
if( spawned.attention === 'full' || !spawned.handle ){
|
|
146
|
+
logger.info( '[deliberation] facet budget full — confirming substrate winner' )
|
|
147
|
+
return provisional
|
|
148
|
+
}
|
|
149
|
+
this._handle = spawned.handle
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
let capturedDecision: unknown = undefined
|
|
153
|
+
const unsub = this._handle.subscribe( d => { capturedDecision = d.decision } )
|
|
154
|
+
|
|
155
|
+
this._handle.setFocus({
|
|
156
|
+
title: 'Deliberation',
|
|
157
|
+
function: 'deliberation',
|
|
158
|
+
content: this._buildFocusContent( state, candidates, meta ),
|
|
159
|
+
instructions: DELIBERATION_INSTRUCTIONS,
|
|
160
|
+
})
|
|
161
|
+
await this._handle.report({ type: 'deliberation', payload: { candidateSchemas: candidates.map( c => c.schema ) } })
|
|
162
|
+
unsub()
|
|
163
|
+
|
|
164
|
+
const picked = extractChosen( capturedDecision, candidates )
|
|
165
|
+
return picked ?? provisional
|
|
166
|
+
}
|
|
167
|
+
catch( err ){
|
|
168
|
+
// Director/state not ready (e.g. executive hasn't run yet) or LLM failure →
|
|
169
|
+
// fall back to the substrate winner. Drop a possibly-dead handle.
|
|
170
|
+
this._handle = null
|
|
171
|
+
logger.warn( `[deliberation] facet unavailable, confirming winner: ${ err instanceof Error ? err.message : String( err ) }` )
|
|
172
|
+
return provisional
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Write the deliberating intent back as 'selected' with the chosen action. */
|
|
177
|
+
private _commit(
|
|
178
|
+
id: string,
|
|
179
|
+
meta: Record<string, unknown>,
|
|
180
|
+
chosen: string,
|
|
181
|
+
candidates: Candidate[],
|
|
182
|
+
via: string,
|
|
183
|
+
): EntityInput {
|
|
184
|
+
const cand = candidates.find( c => c.schema === chosen )
|
|
185
|
+
return {
|
|
186
|
+
id, type: 'agency.intent',
|
|
187
|
+
metadata: {
|
|
188
|
+
...meta,
|
|
189
|
+
schema: chosen,
|
|
190
|
+
targetEntityId: cand?.targetEntityId ?? meta['targetEntityId'],
|
|
191
|
+
parameters: { ...( ( meta['parameters'] as Record<string, unknown> ) ?? {} ), ...( cand?.parameters ?? {} ) },
|
|
192
|
+
status: 'selected',
|
|
193
|
+
deliberated: true,
|
|
194
|
+
deliberatedVia: via,
|
|
195
|
+
},
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** The deliberation focus body — the candidate actions the substrate surfaced. */
|
|
200
|
+
private _buildFocusContent(
|
|
201
|
+
state: ReadonlySimulationState,
|
|
202
|
+
candidates: Candidate[],
|
|
203
|
+
meta: Record<string, unknown>,
|
|
204
|
+
): string {
|
|
205
|
+
const lines: string[] = []
|
|
206
|
+
// Channel B: if this choice arose by interrupting a pending action, name it so the
|
|
207
|
+
// facet owns the interruption in-character rather than reasoning in a vacuum.
|
|
208
|
+
const preemptedFrom = str( meta['preemptedFrom'] )
|
|
209
|
+
if( preemptedFrom )
|
|
210
|
+
lines.push( `You just broke off a pending action ("${ preemptedFrom }") because something more pressing pulled at you. Decide what to do now:` )
|
|
211
|
+
else
|
|
212
|
+
lines.push( 'Your automatic action-selection was uncertain. Candidate actions:' )
|
|
213
|
+
candidates.forEach( ( c, i ) => {
|
|
214
|
+
const to = c.targetEntityId ? ` toward ${ c.targetEntityId }` : ''
|
|
215
|
+
// Channel B: name the plan link so the facet chooses as the self pursuing it,
|
|
216
|
+
// not blindly among labels ("this one is the next step of the plan I'm on").
|
|
217
|
+
const plan = c.fromPlan ? " — your current plan's next step" : ''
|
|
218
|
+
lines.push( `${ i + 1 }. ${ c.schema }${ to }${ plan }` )
|
|
219
|
+
})
|
|
220
|
+
return lines.join( '\n' )
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ─── decoding ────────────────────────────────────────────────────────────────
|
|
225
|
+
|
|
226
|
+
/** Pull the chosen schema from a facet decision, validated against the candidates. */
|
|
227
|
+
function extractChosen( decision: unknown, candidates: Candidate[] ): string | undefined {
|
|
228
|
+
if( !decision || typeof decision !== 'object' ) return undefined
|
|
229
|
+
const actions = ( decision as Record<string, unknown> )['actions']
|
|
230
|
+
if( !Array.isArray( actions ) ) return undefined
|
|
231
|
+
const valid = new Set( candidates.map( c => c.schema ) )
|
|
232
|
+
for( const a of actions ){
|
|
233
|
+
const type = ( a as Record<string, unknown> )?.['type']
|
|
234
|
+
if( typeof type === 'string' && valid.has( type ) ) return type
|
|
235
|
+
}
|
|
236
|
+
return undefined
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function str( v: unknown ): string | undefined {
|
|
240
|
+
return typeof v === 'string' ? v : undefined
|
|
241
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/agency/instruction.intake.ts — instruction → goal
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
//
|
|
5
|
+
// External instructions (developer/host directives injected into state) become
|
|
6
|
+
// the Will's own goals here. This role used to live inside the legacy
|
|
7
|
+
// ActionExecutor; on the agency cutover it moves to a small dedicated intake so
|
|
8
|
+
// the legacy executor can be retired. Behaviour is preserved verbatim: each tick
|
|
9
|
+
// it drains pending instructions and converts them to goals via the same
|
|
10
|
+
// InstructionHandler + GoalManager.
|
|
11
|
+
// ─────────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
import type {
|
|
14
|
+
Duration, Tick, SimulationContext, ReadonlySimulationState,
|
|
15
|
+
} from '#core/types'
|
|
16
|
+
import type { CognitiveEngine, EngineResult } from '#cognition/types'
|
|
17
|
+
import type { CognitiveEventSchema } from '#cognition/schema.registry'
|
|
18
|
+
import type { InstructionHandler } from '#cognition/instruction.handler'
|
|
19
|
+
import type { GoalManager } from '#faculties/goal.manager'
|
|
20
|
+
|
|
21
|
+
export class InstructionIntake implements CognitiveEngine {
|
|
22
|
+
readonly name = 'instruction-intake'
|
|
23
|
+
|
|
24
|
+
private _instructionHandler: InstructionHandler | null = null
|
|
25
|
+
private _goalManager: GoalManager | null = null
|
|
26
|
+
|
|
27
|
+
attachInstructionHandler( h: InstructionHandler ): void { this._instructionHandler = h }
|
|
28
|
+
attachGoalManager( gm: GoalManager ): void { this._goalManager = gm }
|
|
29
|
+
|
|
30
|
+
publishes(): CognitiveEventSchema[] { return [] }
|
|
31
|
+
subscribes(): string[] { return [] }
|
|
32
|
+
onCognitiveEvent(): void { /* pull model — reads pending instructions from state */ }
|
|
33
|
+
snapshot(): Record<string, unknown> {
|
|
34
|
+
return { pending: this._instructionHandler?.getPending().length ?? 0 }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async react(
|
|
38
|
+
_delta: Duration,
|
|
39
|
+
_tick: Tick,
|
|
40
|
+
state: ReadonlySimulationState,
|
|
41
|
+
_context: SimulationContext,
|
|
42
|
+
): Promise<EngineResult> {
|
|
43
|
+
const handler = this._instructionHandler
|
|
44
|
+
const goals = this._goalManager
|
|
45
|
+
if( !handler || !goals ) return { commands: {} }
|
|
46
|
+
|
|
47
|
+
for( const instruction of handler.evaluatePending( state, [] ) ){
|
|
48
|
+
const g = handler.convertToGoal( instruction )
|
|
49
|
+
goals.addGoal( g.description, g.priority, g.tags, undefined, undefined, g.completionType, g.completionCondition )
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return { commands: {} }
|
|
53
|
+
}
|
|
54
|
+
}
|