@mindot/will 0.9.0 → 0.10.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 +15 -11
- package/dist/index.d.ts +188 -141
- package/dist/index.js +16660 -15206
- package/dist/index.js.map +1 -1
- package/dist/{channels → surface/channels}/discord.d.ts +50 -3
- package/dist/{channels → surface/channels}/discord.js +102 -7
- package/dist/surface/channels/discord.js.map +1 -0
- package/dist/{channels → surface/channels}/whatsapp.d.ts +2 -2
- package/dist/{channels → surface/channels}/whatsapp.js +7 -4
- package/dist/surface/channels/whatsapp.js.map +1 -0
- package/dist/{cli.js → surface/cli.js} +4406 -2904
- package/dist/surface/cli.js.map +1 -0
- package/dist/{mcp → surface/mcp}/effectors.d.ts +1 -1
- package/dist/{mcp → surface/mcp}/effectors.js +10 -4
- package/dist/surface/mcp/effectors.js.map +1 -0
- package/dist/{will-DbDj_TEH.d.ts → will-evj9_vrd.d.ts} +5699 -4854
- package/package.json +11 -12
- package/src/cognition/agency/consequence.ts +237 -3
- package/src/cognition/agency/conversation.aim.ts +32 -0
- package/src/cognition/agency/engines/action.selector.ts +59 -3
- package/src/cognition/agency/engines/affordance.synthesizer.ts +114 -10
- package/src/cognition/agency/engines/deliberation.engine.ts +76 -3
- package/src/cognition/agency/engines/motor.schema.executor.ts +362 -27
- package/src/cognition/agency/engines/reafference.engine.ts +43 -4
- package/src/cognition/agency/execution.primitives.ts +17 -4
- package/src/cognition/agency/reconcile.learning.ts +5 -4
- package/src/cognition/agency/schemas/innate.ts +96 -1
- package/src/cognition/agency/schemas/repertoire.ts +19 -1
- package/src/cognition/agency/selection.scoring.ts +16 -0
- package/src/cognition/agency/settlement.ts +203 -0
- package/src/cognition/agency/types.ts +38 -1
- package/src/cognition/config.mirror.entities.ts +0 -1
- package/src/cognition/event.schemas.ts +0 -8
- package/src/cognition/faculties/affective.blender.ts +21 -4
- package/src/cognition/faculties/circadian.oscillator.ts +36 -7
- package/src/cognition/faculties/executive.engine/action.record.ts +129 -0
- package/src/cognition/faculties/executive.engine/context.ts +41 -35
- package/src/cognition/faculties/executive.engine/engine.ts +270 -113
- package/src/cognition/faculties/executive.engine/escalation.buffer.ts +126 -59
- package/src/cognition/faculties/executive.engine/facet.supervisor.ts +10 -0
- package/src/cognition/faculties/executive.engine/parser.ts +100 -38
- package/src/cognition/faculties/executive.engine/prompt.factory.ts +168 -23
- package/src/cognition/faculties/executive.engine/types.ts +18 -15
- package/src/cognition/faculties/exteroception.ts +99 -98
- package/src/cognition/faculties/goal.manager.ts +50 -2
- package/src/cognition/faculties/known.entity.tracker.ts +22 -3
- package/src/cognition/faculties/planning.engine/engine.ts +4 -4
- package/src/cognition/faculties/planning.engine/plan.frontier.ts +1 -1
- package/src/cognition/faculties/semantic.engine/integrator.ts +1 -1
- package/src/cognition/faculties/working.memory.ts +6 -1
- package/src/cognition/instruction.handler.ts +1 -1
- package/src/cognition/memory/vector.embedder.ts +1 -3
- package/src/cognition/percept.entity.ts +122 -0
- package/src/cognition/sense.boundary.ts +176 -0
- package/src/cognition/senses/audition.engine/engine.ts +97 -16
- package/src/cognition/senses/base.sense.engine.ts +104 -6
- package/src/cognition/senses/index.ts +71 -13
- package/src/cognition/senses/provenance.ts +128 -0
- package/src/cognition/senses/somatosensation.engine.ts +117 -9
- package/src/cognition/social.identity.ts +23 -0
- package/src/cognition/utilities/token.tracker.ts +36 -5
- package/src/core/orchestrator.ts +13 -0
- package/src/index.ts +39 -5
- package/src/llm/gate.ts +48 -0
- package/src/llm/index.ts +102 -25
- package/src/llm/wire.contracts.ts +38 -0
- package/src/pma/index.ts +1 -1
- package/src/stem/index.ts +92 -21
- package/src/stem/mind.ts +26 -2
- package/src/stem/policy/arbiter.ts +10 -0
- package/src/stem/profiles/built-in.ts +7 -0
- package/src/{profiles → stem/profiles}/companion.ts +1 -1
- package/src/{profiles → stem/profiles}/company-brain.ts +1 -1
- package/src/{profiles → stem/profiles}/customer-service.ts +1 -1
- package/src/{profiles → stem/profiles}/game-npc.ts +1 -1
- package/src/{profiles → stem/profiles}/index.ts +1 -1
- package/src/{profiles → stem/profiles}/smart-home.ts +1 -1
- package/src/stem/tracts/effector/escalation.lifecycle.ts +239 -0
- package/src/stem/tracts/effector/policy.enforcement.ts +261 -0
- package/src/stem/tracts/effector/types.ts +110 -0
- package/src/stem/tracts/effector.controller.ts +112 -401
- package/src/stem/tracts/outbox.controller.ts +30 -17
- package/src/stem/tracts/sensory.controller.ts +6 -6
- package/src/stem/tracts/session.logger.ts +6 -1
- package/src/stem/tracts/transport/types.ts +20 -1
- package/src/stem/tracts/transport.controller.ts +33 -9
- package/src/{channels → surface/channels}/discord.ts +224 -7
- package/src/{channels → surface/channels}/roster.ts +1 -1
- package/src/{channels → surface/channels}/types.ts +1 -1
- package/src/{channels → surface/channels}/whatsapp.ts +9 -6
- package/src/{cli.ts → surface/cli.ts} +6 -6
- package/src/{host → surface/host}/boot.ts +3 -3
- package/src/{host → surface/host}/utterances.ts +2 -2
- package/src/{mcp → surface/mcp}/effectors.ts +19 -6
- package/src/{mcp → surface/mcp}/server.ts +19 -5
- package/src/{sdk → surface/sdk}/will.ts +156 -28
- package/src/{serve → surface/serve}/server.ts +11 -4
- package/src/types.ts +24 -3
- package/dist/channels/discord.js.map +0 -1
- package/dist/channels/whatsapp.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/mcp/effectors.js.map +0 -1
- package/src/extensions/livestream.ext.ts +0 -570
- package/src/extensions/time.ext.ts +0 -339
- package/src/profiles/built-in.ts +0 -7
- package/src/runners/coherence.runner.ts +0 -49
- package/src/runners/outreach.runner.ts +0 -155
- package/src/runners/social.runner.ts +0 -179
- package/src/runners/thin-shim.runner.ts +0 -205
- /package/dist/{cli.d.ts → surface/cli.d.ts} +0 -0
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
// ─────────────────────────────────────────────────────────────
|
|
32
32
|
|
|
33
33
|
import type { EntityInput } from '#core/types'
|
|
34
|
+
import { perceptEntity } from '#cognition/percept.entity'
|
|
34
35
|
|
|
35
36
|
/**
|
|
36
37
|
* Work the master should plan or re-goal. The facet has already said whatever
|
|
@@ -42,14 +43,33 @@ export interface EscalationHandoff {
|
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
46
|
+
* SOMETHING I SAID I WOULD DO, declared by the part of me that said it.
|
|
47
|
+
*
|
|
48
|
+
* It was `{ target, gist }` — a contact and nothing else — because the defect it
|
|
49
|
+
* was built for was capability-shaped rather than promise-shaped: a conversation
|
|
50
|
+
* facet can speak in its own thread but cannot open a channel to a third party,
|
|
51
|
+
* so the handoff carried the one act it structurally could not perform. Named
|
|
52
|
+
* "undertaking" and dressed in promise language, it read as a general commitment
|
|
53
|
+
* system with a strange restriction, and it was not one.
|
|
54
|
+
*
|
|
55
|
+
* `what` is the commitment in the mind's own words — "reach FKEM about the demo",
|
|
56
|
+
* "have the scoping doc by Friday", "look into the pricing". `target` is set only
|
|
57
|
+
* when it is toward someone the mind would have to REACH, which is still the case
|
|
58
|
+
* the tract exists for; a promise about work has no target and needs none.
|
|
59
|
+
*
|
|
60
|
+
* Note what this deliberately does NOT become: a task list. It is filed beside
|
|
61
|
+
* what the facet concluded and read once by the master, which may make a goal of
|
|
62
|
+
* it — the conversation format already says to raise [GOALS_NEW] for anything to
|
|
63
|
+
* follow through on, and goals are the thing that persists. This only ensures the
|
|
64
|
+
* seat that decides never learns of it too late to decide.
|
|
49
65
|
*/
|
|
50
66
|
export interface UndertakingHandoff {
|
|
51
67
|
kind: 'undertaking'
|
|
52
|
-
|
|
68
|
+
/** What I said I would do, in my own words. */
|
|
69
|
+
what: string
|
|
70
|
+
/** Who it is toward — only when keeping it means reaching someone. */
|
|
71
|
+
target?: string
|
|
72
|
+
/** The words I had in mind for them, when I had any. */
|
|
53
73
|
gist?: string
|
|
54
74
|
reasoning: string
|
|
55
75
|
}
|
|
@@ -68,11 +88,18 @@ export function validateFacetHandoff( payload: unknown ): string | null {
|
|
|
68
88
|
if( !body || typeof body !== 'object') return 'payload.body is required'
|
|
69
89
|
const kind = ( body as { kind?: unknown } ).kind
|
|
70
90
|
if( kind === 'escalation') return null
|
|
71
|
-
if( kind === 'undertaking')
|
|
72
|
-
|
|
73
|
-
|
|
91
|
+
if( kind === 'undertaking'){
|
|
92
|
+
// `what` is the requirement now, not `target`. A promise about work has no
|
|
93
|
+
// one to reach and is still a promise; a promise with a target but nothing
|
|
94
|
+
// said is not one.
|
|
95
|
+
const what = ( body as { what?: unknown } ).what
|
|
96
|
+
if( typeof what !== 'string' || what.trim().length === 0 )
|
|
97
|
+
return 'undertaking handoff requires a non-empty what'
|
|
98
|
+
const target = ( body as { target?: unknown } ).target
|
|
99
|
+
return target === undefined || ( typeof target === 'string' && target.trim().length > 0 )
|
|
74
100
|
? null
|
|
75
|
-
: 'undertaking handoff
|
|
101
|
+
: 'undertaking handoff target, when present, must be a non-empty string'
|
|
102
|
+
}
|
|
76
103
|
return `unknown handoff kind: ${String( kind )}`
|
|
77
104
|
}
|
|
78
105
|
|
|
@@ -126,6 +153,26 @@ export class EscalationBuffer {
|
|
|
126
153
|
* Convert every buffered handoff into a high-salience percept entity and clear
|
|
127
154
|
* the buffer. Returns the percepts plus the first handoff's requester context
|
|
128
155
|
* (used to tag goals the master creates in response).
|
|
156
|
+
*
|
|
157
|
+
* WHY THESE DO NOT GO THROUGH A SENSE DOOR (SIGNAL_BOUNDARY P4).
|
|
158
|
+
*
|
|
159
|
+
* P4's rule is that only a sense or exteroception writes a percept, and these
|
|
160
|
+
* two are the standing exception. A sense door carries AFFERENCE: a signal
|
|
161
|
+
* crossing into the mind, either from the world (exafferent) or from the
|
|
162
|
+
* mind's own act returning THROUGH the world (reafferent). A facet handing off
|
|
163
|
+
* to the master crosses neither boundary — it never left the mind. Routing it
|
|
164
|
+
* through a sense would dress one part of a mind up as news from outside,
|
|
165
|
+
* which is precisely the laundering P1 removed from `inspect`, and §6 of the
|
|
166
|
+
* epoch names that class of over-unification as the thing not to do.
|
|
167
|
+
*
|
|
168
|
+
* They still had to stop being hand-rolled literals, and they have: everything
|
|
169
|
+
* goes through `perceptEntity()`, so the shape is the one shape.
|
|
170
|
+
*
|
|
171
|
+
* What is left open, and worth deciding rather than inheriting: a handoff is
|
|
172
|
+
* using the percept entity as a DELIVERY MECHANISM, because the percept block
|
|
173
|
+
* is what the executive prompt renders. A `self.handoff` type with its own
|
|
174
|
+
* section would say what this actually is. That is a prompt change and a
|
|
175
|
+
* `_reconcileUndertakings` change, so it is a decision, not a cleanup.
|
|
129
176
|
*/
|
|
130
177
|
drainToPercepts(): DrainedEscalations {
|
|
131
178
|
const percepts: EntityInput[] = []
|
|
@@ -136,63 +183,83 @@ export class EscalationBuffer {
|
|
|
136
183
|
// silently collapse them into whichever came last.
|
|
137
184
|
const id = `escalation-percept-${h.subjectEntityId ?? h.facetId ?? 'self'}-${h.tick}-${seq++}`
|
|
138
185
|
|
|
139
|
-
|
|
186
|
+
// FACTS the builder owns, and the writer's OWN fields beside them. Split
|
|
187
|
+
// this way because `perceptEntity()` spreads `extra` first: a writer can
|
|
188
|
+
// add whatever it likes and still cannot clobber `tick` or `provenance`.
|
|
189
|
+
const core = {
|
|
190
|
+
id,
|
|
191
|
+
tick: h.tick,
|
|
140
192
|
salience: 0.85,
|
|
141
|
-
|
|
193
|
+
// REAFFERENT. Nothing outside the mind produced this: a facet of it
|
|
194
|
+
// raised the handoff, and the master is now noticing its own part's
|
|
195
|
+
// doing. Tagging it costs no behaviour — the rupture gate excludes
|
|
196
|
+
// reafferent and untagged alike — but it stops the exclusion being an
|
|
197
|
+
// accident, and it is the honest answer rather than the convenient one.
|
|
198
|
+
provenance: 'reafferent' as const,
|
|
142
199
|
...( h.subjectEntityId ? { entityId: h.subjectEntityId } : {} ),
|
|
143
|
-
|
|
144
|
-
|
|
200
|
+
}
|
|
201
|
+
const mine = {
|
|
202
|
+
source: 'executive-facet',
|
|
203
|
+
...( h.threadId ? { threadId: h.threadId } : {} ),
|
|
204
|
+
...( h.facetId ? { facetId: h.facetId } : {} ),
|
|
145
205
|
}
|
|
146
206
|
|
|
207
|
+
// Built through `perceptEntity()` like every other percept in the package
|
|
208
|
+
// (SIGNAL_BOUNDARY P4). These were the last hand-rolled `type: 'percept'`
|
|
209
|
+
// literals, and a hand-rolled one is exactly how `tick` went missing on
|
|
210
|
+
// the wake and delivery percepts and made them immortal.
|
|
211
|
+
//
|
|
212
|
+
// They do NOT go through a sense door, and should not — see the note on
|
|
213
|
+
// `drainToPercepts` above for why.
|
|
147
214
|
percepts.push( h.body.kind === 'undertaking'
|
|
148
|
-
? {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
metadata: {
|
|
185
|
-
...common,
|
|
186
|
-
category: 'task-escalation',
|
|
187
|
-
// The steer lives IN the summary because that is the only field the
|
|
188
|
-
// executive context renders (context.ts extractPercepts reads
|
|
189
|
-
// summary/content and nothing else). It sat in a sibling `directive`
|
|
190
|
-
// field for its whole life and never once reached the master.
|
|
191
|
-
summary: `[Task from ${h.subjectName ?? h.subjectEntityId ?? 'my own focused work'}] ${h.body.reasoning}`
|
|
192
|
-
+ ' — this is mine to plan or re-goal; the part of me that raised it handles the talking.',
|
|
193
|
-
tick: h.tick,
|
|
215
|
+
? perceptEntity( {
|
|
216
|
+
...core,
|
|
217
|
+
category: 'undertaking',
|
|
218
|
+
// THE GAP, AND ONLY THE GAP. First person because the mind is noticing
|
|
219
|
+
// what IT said it would do, not because it is being told what to do
|
|
220
|
+
// about it. A mind that has said it will make contact remembers
|
|
221
|
+
// deciding and cannot tell from the inside whether the words went out
|
|
222
|
+
// — so it follows up on a message it never sent. Naming the gap is
|
|
223
|
+
// what lets it check; naming the remedy was never this line's job.
|
|
224
|
+
//
|
|
225
|
+
// What used to follow this sentence: "If I still mean it, I reach out
|
|
226
|
+
// with target 'X'. If I no longer do, I let it go — but I do not leave
|
|
227
|
+
// it half-done while telling them it is handled." An instruction
|
|
228
|
+
// naming an effector and its argument, and a value judgement, both
|
|
229
|
+
// written in the mind's own voice so it could not disagree with
|
|
230
|
+
// either. The pull now lives in the affordance field where it can be
|
|
231
|
+
// out-competed, and the judgement belongs to a persona, not a buffer.
|
|
232
|
+
summary: `${whereItHappened( h )} I said I would reach ${h.body.target}`
|
|
233
|
+
+ `. Nothing has gone to them since; saying it in that conversation did not send it.`,
|
|
234
|
+
}, {
|
|
235
|
+
...mine,
|
|
236
|
+
// Who was promised, and when the promise was made. The executive
|
|
237
|
+
// reconciles against these (see _reconcileUndertakings): once the mind
|
|
238
|
+
// has actually reached this target since `tick`, the percept is retired
|
|
239
|
+
// rather than left standing as a claim the words are still unsent.
|
|
240
|
+
undertakingTarget: h.body.target,
|
|
241
|
+
// The EVIDENCE, rendered beneath the label since P2 — which is what
|
|
242
|
+
// makes the prose above unnecessary. `gist` is what the mind meant to
|
|
243
|
+
// say; it is a fact about the promise, not a script for keeping it,
|
|
244
|
+
// and it is carried whole rather than clipped into a sentence.
|
|
245
|
+
data: {
|
|
246
|
+
target: h.body.target,
|
|
247
|
+
promisedAt: h.tick,
|
|
248
|
+
...( h.subjectName ?? h.subjectEntityId ? { promisedTo: h.subjectName ?? h.subjectEntityId } : {} ),
|
|
249
|
+
...( h.body.gist ? { gist: h.body.gist } : {} ),
|
|
250
|
+
contactedSince: false,
|
|
194
251
|
},
|
|
195
252
|
} )
|
|
253
|
+
: perceptEntity( {
|
|
254
|
+
...core,
|
|
255
|
+
category: 'task-escalation',
|
|
256
|
+
// What was raised, and by which part of me. It used to close with
|
|
257
|
+
// "this is mine to plan or re-goal; the part of me that raised it
|
|
258
|
+
// handles the talking" — a role instruction. Whether to plan it,
|
|
259
|
+
// re-goal it or drop it is the master's call, and it has a whole
|
|
260
|
+
// faculty for making it.
|
|
261
|
+
summary: `[Raised by ${h.subjectName ?? h.subjectEntityId ?? 'my own focused work'}] ${h.body.reasoning}`,
|
|
262
|
+
}, mine ) )
|
|
196
263
|
}
|
|
197
264
|
|
|
198
265
|
// Capture requester context before clearing — used to tag new goals. Only a
|
|
@@ -333,6 +333,11 @@ export class FacetSupervisor {
|
|
|
333
333
|
type: 'executive.facet.destroy',
|
|
334
334
|
tick: this._lastStateRef?.tick as unknown as number ?? 0,
|
|
335
335
|
facetId,
|
|
336
|
+
// The key a reader needs to tell the two populations apart. A KEYED facet
|
|
337
|
+
// dying is a conversation ending; a keyless one dying is a deliberation
|
|
338
|
+
// finishing, which is what the executive does every time it thinks. Without
|
|
339
|
+
// it a churn metric counts both and reads engagement as damage.
|
|
340
|
+
...( key ? { key } : {} ),
|
|
336
341
|
reason,
|
|
337
342
|
totalFacets: this._facets.size,
|
|
338
343
|
} as any)
|
|
@@ -519,6 +524,11 @@ export class FacetSupervisor {
|
|
|
519
524
|
type: 'executive.facet.destroy',
|
|
520
525
|
tick: this._lastStateRef?.tick as unknown as number ?? 0,
|
|
521
526
|
facetId,
|
|
527
|
+
...( key ? { key } : {} ),
|
|
528
|
+
// Named so the three ways a facet can end are distinguishable in a
|
|
529
|
+
// trace: its owner closed it, the TTL took a quiet one, or pressure
|
|
530
|
+
// evicted it. Only the last two are ever a symptom.
|
|
531
|
+
reason: 'explicit',
|
|
522
532
|
totalFacets: this._facets.size,
|
|
523
533
|
} as any)
|
|
524
534
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// ─────────────────────────────────────────────────────────────
|
|
4
4
|
|
|
5
5
|
import { logger } from '#core/logger'
|
|
6
|
-
import { REPLY_TEXT_TAG, NO_MESSAGE_TAG, NO_MESSAGE_OPEN } from '#llm/wire.contracts'
|
|
6
|
+
import { REPLY_TEXT_TAG, NO_MESSAGE_TAG, NO_MESSAGE_OPEN, stripProtocolMarkers } from '#llm/wire.contracts'
|
|
7
7
|
import type { ReadonlySimulationState } from '#core/types'
|
|
8
8
|
import type { ExecutiveOutputFull, ExecutiveOutputMinimal, IdeationCandidate, IdeationOutput } from '#faculties/executive.engine/types'
|
|
9
9
|
|
|
@@ -27,7 +27,9 @@ export function parseResponse(
|
|
|
27
27
|
|
|
28
28
|
let
|
|
29
29
|
actions: Array<{ type: string; reasoning: string; expectedOutcome: string }>,
|
|
30
|
-
confidence = 0.5
|
|
30
|
+
confidence = 0.5,
|
|
31
|
+
/** The mind's own sentence, when the response parsed as JSON and carried one. */
|
|
32
|
+
stated: string | undefined
|
|
31
33
|
|
|
32
34
|
// Strategy 1: direct JSON.parse
|
|
33
35
|
try {
|
|
@@ -41,6 +43,7 @@ export function parseResponse(
|
|
|
41
43
|
|
|
42
44
|
actions = parsed.actions
|
|
43
45
|
confidence = parsed.confidence ?? 0.5
|
|
46
|
+
stated = typeof parsed.reasoning === 'string' ? parsed.reasoning : undefined
|
|
44
47
|
}
|
|
45
48
|
catch {
|
|
46
49
|
// Strategy 2: balanced-bracket extractor
|
|
@@ -60,9 +63,28 @@ export function parseResponse(
|
|
|
60
63
|
confidence = confidenceMatch ? parseFloat( confidenceMatch[1]! ) : 0.5
|
|
61
64
|
}
|
|
62
65
|
|
|
63
|
-
//
|
|
66
|
+
// Tagged blocks are scanned over the FULL text — they are documented as embedded
|
|
67
|
+
// in the reasoning field, but a model puts them wherever it likes and the scan
|
|
68
|
+
// has always been forgiving about that. Keep that surface.
|
|
64
69
|
const full = parseTaggedBlocks({ actions, reasoning: fullText, confidence }, state )
|
|
65
70
|
|
|
71
|
+
// …but `reasoning` itself is the mind's SENTENCE, not its serialization.
|
|
72
|
+
//
|
|
73
|
+
// It was `fullText` — the whole JSON blob — for every consumer downstream, and
|
|
74
|
+
// that was invisible for as long as nothing read it as prose. Three things do:
|
|
75
|
+
// the session log records it, a facet is handed the master's as "What I've Been
|
|
76
|
+
// Turning Over" (clipped to 400 chars, so facets have been reading the master's
|
|
77
|
+
// JSON truncated mid-object), and the master now reads a facet's back as what it
|
|
78
|
+
// worked out. Seen live in a prompt: `What I worked out there: { "actions": [...
|
|
79
|
+
//
|
|
80
|
+
// The scan surface and the sentence were the same string only because nothing
|
|
81
|
+
// had ever needed them to be different.
|
|
82
|
+
// Stripped of the tagged blocks, because the format tells the mind to embed them
|
|
83
|
+
// IN this field — so the sentence and the structured payload arrive in one
|
|
84
|
+
// string, and handing the whole thing back as "what I worked out" would just
|
|
85
|
+
// move the serialization problem one level in.
|
|
86
|
+
if( stated ) full.reasoning = stripTaggedBlocks( stated )
|
|
87
|
+
|
|
66
88
|
// [REPLY_TEXT] is a plain-text block that lives OUTSIDE the JSON code block.
|
|
67
89
|
// Search the full response text so we find it even when the LLM used a code fence.
|
|
68
90
|
const replyText = extractTextBlock( responseText, REPLY_TEXT_TAG )
|
|
@@ -176,6 +198,29 @@ function extractBalancedArray( text: string, key: string ): string | null {
|
|
|
176
198
|
return null
|
|
177
199
|
}
|
|
178
200
|
|
|
201
|
+
/** Every block name the reasoning field may carry, for stripping it back to prose. */
|
|
202
|
+
const TAGGED_BLOCK_NAMES = [
|
|
203
|
+
'PLANS', 'BELIEFS', 'INTROSPECTION', 'NARRATIVE', 'IDENTITY', 'KNOWN_ENTITIES',
|
|
204
|
+
'GOALS_NEW', 'GOALS_ABANDON', 'GOALS_REPRIORITIZE', 'EFFECTORS', 'SELF_OBS',
|
|
205
|
+
'SKILLS', 'REPLY_TEXT', 'ACK',
|
|
206
|
+
] as const
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* The sentence, with the structured payload taken back out of it.
|
|
210
|
+
*
|
|
211
|
+
* Unclosed blocks are cut to the end of the string on purpose: a truncated
|
|
212
|
+
* `[PLANS]{…` is not prose either, and leaving it in puts half a JSON object in
|
|
213
|
+
* front of whoever reads this as the mind's thinking.
|
|
214
|
+
*/
|
|
215
|
+
export function stripTaggedBlocks( reasoning: string ): string {
|
|
216
|
+
let out = reasoning
|
|
217
|
+
for( const tag of TAGGED_BLOCK_NAMES )
|
|
218
|
+
out = out
|
|
219
|
+
.replace( new RegExp( `\\[${ tag }\\][\\s\\S]*?\\[/${ tag }\\]`, 'g'), '')
|
|
220
|
+
.replace( new RegExp( `\\[${ tag }\\][\\s\\S]*$`, 'g'), '')
|
|
221
|
+
return out.replace( /\n{3,}/g, '\n\n').trim()
|
|
222
|
+
}
|
|
223
|
+
|
|
179
224
|
/**
|
|
180
225
|
* Parse tagged JSON blocks from the reasoning text.
|
|
181
226
|
* The LLM embeds optional outputs as [TAG]...[/TAG] blocks.
|
|
@@ -339,7 +384,15 @@ function extractBlock( text: string, tag: string ): string | null {
|
|
|
339
384
|
* Used for [REPLY_TEXT] — content is streamed directly to the client,
|
|
340
385
|
* so it must be clean prose, not a JSON payload.
|
|
341
386
|
*/
|
|
342
|
-
|
|
387
|
+
/**
|
|
388
|
+
* Pull one plain-text block out of a response.
|
|
389
|
+
*
|
|
390
|
+
* Exported for the guard: this slice is where a stray marker survives into
|
|
391
|
+
* content, and the seam is the thing worth testing. `parseResponse` needs a full
|
|
392
|
+
* well-formed response around it, which is exactly the setup that hides a defect
|
|
393
|
+
* living in one line of string handling.
|
|
394
|
+
*/
|
|
395
|
+
export function extractTextBlock( text: string, tag: string ): string | null {
|
|
343
396
|
const open = `[${tag}]`
|
|
344
397
|
const close = `[/${tag}]`
|
|
345
398
|
const openIdx = text.indexOf( open )
|
|
@@ -350,51 +403,60 @@ function extractTextBlock( text: string, tag: string ): string | null {
|
|
|
350
403
|
? text.slice( contentStart, closeIdx )
|
|
351
404
|
: text.slice( contentStart ) // unclosed block — take everything after the open marker
|
|
352
405
|
|
|
353
|
-
|
|
406
|
+
// A stray marker INSIDE the body survives this slice — the block runs from the
|
|
407
|
+
// first open to the first close after it, and anything between is content by
|
|
408
|
+
// construction. Live, a reply went out as four substantive bubbles and a fifth
|
|
409
|
+
// reading exactly `[REPLY_TEXT]`: the person received a message whose whole
|
|
410
|
+
// content was the name of the slot it should have filled.
|
|
411
|
+
return stripProtocolMarkers( content ) || null
|
|
354
412
|
}
|
|
355
413
|
|
|
356
414
|
/**
|
|
357
|
-
*
|
|
358
|
-
*
|
|
415
|
+
* What the executive intends when its own reasoning did not come back.
|
|
416
|
+
*
|
|
417
|
+
* Two things had to change here. Every action this produced was PROSE, not a
|
|
418
|
+
* schema: `observe`, `replenish energy`, `enter deep rest to reduce fatigue`,
|
|
419
|
+
* `calm my mind and reduce tension`, `explore`, `learn`, `express_emotion`.
|
|
420
|
+
* Only `rest` names anything the body can enact. The rest reach the agency, fail
|
|
421
|
+
* to resolve, and become an ideomotor push toward an act that does not exist —
|
|
422
|
+
* observed live as `master decided [observe] conf=0.4` four times across two
|
|
423
|
+
* boots, each one a decision that could go nowhere.
|
|
424
|
+
*
|
|
425
|
+
* And the "no urgent need" branch was INVENTION. The executive could not read
|
|
426
|
+
* its own thought, so it made one up and pushed it into the field as intention.
|
|
427
|
+
* That is the mind being told what it wants. The substrate does not need the
|
|
428
|
+
* help: the affordance field is always there, System 1 selects from it every
|
|
429
|
+
* tick without an executive, and it now carries real satiation — the monotony
|
|
430
|
+
* this used to break by cycling a hardcoded list is handled where monotony
|
|
431
|
+
* actually lives.
|
|
432
|
+
*
|
|
433
|
+
* So: reflexes stay, invention goes. Critically low energy, high sleep pressure
|
|
434
|
+
* or high stress still push a real regulatory stance, because a body in trouble
|
|
435
|
+
* should not depend on an LLM parsing correctly. Anything else yields NO action
|
|
436
|
+
* and lets the substrate decide, which is what it is for.
|
|
359
437
|
*/
|
|
360
438
|
export function buildFallbackOutput(
|
|
361
439
|
state: ReadonlySimulationState,
|
|
362
|
-
|
|
440
|
+
_recentActionTypes: string[]
|
|
363
441
|
): ExecutiveOutputFull {
|
|
364
|
-
const energy
|
|
442
|
+
const energy = state.metrics.get('energy.level') ?? 100
|
|
365
443
|
const sleepPressure = state.metrics.get('sleep.pressure') ?? 0
|
|
366
|
-
const stressLoad
|
|
367
|
-
|
|
368
|
-
let actionType = 'observe'
|
|
369
|
-
let actionReason = 'No urgent needs — observing surroundings.'
|
|
444
|
+
const stressLoad = state.metrics.get('stress.load') ?? 0
|
|
370
445
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
actionReason = 'Sleep pressure high — attempting to sleep.'
|
|
378
|
-
}
|
|
379
|
-
else if( stressLoad > 70 ){
|
|
380
|
-
actionType = 'calm my mind and reduce tension'
|
|
381
|
-
actionReason = 'Stress elevated — attempting to meditate.'
|
|
382
|
-
}
|
|
383
|
-
else {
|
|
384
|
-
// Vary the fallback when low-effort actions have dominated recently
|
|
385
|
-
const monotonous = recentActionTypes.filter( t => t === 'reflect' || t === 'observe').length >= 3
|
|
386
|
-
if( monotonous) {
|
|
387
|
-
const alternatives = ['explore', 'learn', 'express_emotion', 'rest']
|
|
388
|
-
const tick = state.tick as number
|
|
389
|
-
|
|
390
|
-
actionType = alternatives[ tick % alternatives.length ]!
|
|
391
|
-
actionReason = 'Breaking monotony — choosing a varied action during LLM fallback.'
|
|
392
|
-
}
|
|
393
|
-
}
|
|
446
|
+
// Innate stances only — what the body can actually enact.
|
|
447
|
+
const reflex: { type: string; reasoning: string } | null =
|
|
448
|
+
energy < 20 ? { type: 'rest', reasoning: 'Energy critically low — I let myself recover.' }
|
|
449
|
+
: sleepPressure > 60 ? { type: 'rest', reasoning: 'Sleep pressure high — I let myself recover.' }
|
|
450
|
+
: stressLoad > 70 ? { type: 'withdraw', reasoning: 'Stress elevated — I pull back from the press of things.' }
|
|
451
|
+
: null
|
|
394
452
|
|
|
395
453
|
return {
|
|
396
|
-
actions:
|
|
397
|
-
|
|
454
|
+
actions: reflex
|
|
455
|
+
? [ { type: reflex.type, reasoning: reflex.reasoning, expectedOutcome: 'The body settles' } ]
|
|
456
|
+
: [],
|
|
457
|
+
reasoning: reflex
|
|
458
|
+
? `Heuristic fallback — my reasoning did not come back. ${ reflex.reasoning }`
|
|
459
|
+
: 'Heuristic fallback — my reasoning did not come back, and nothing about my state is pressing. I intend nothing in particular; my body goes on choosing.',
|
|
398
460
|
confidence: 0.4
|
|
399
461
|
}
|
|
400
462
|
}
|