Mechanisms of the engine, from the code

Every mechanism of the EmoStack 3 engine, read from its code: the records, what the sheep has in view, how a moment is kept, how strength changes, how thoughts and rules form, and sleep. Each row says what the mechanism does, with its rule or number, and where it is. Paths are relative to emostack/ in the repository (commit ceccf5a, 28 September 2026); the parameters are the engine's defaults. The vocabulary is the one of the ontology. Written on 28 September 2026.

A. The present: here and now, senses

# Mechanism What it does Where in the code
1 Hearing carries the trigger Hearing says one of three things: a person's words, "X has just entered and has not spoken", "X has just left". senses/Hearing.py; senses/senses.prompt
2 Empty senses, named as absent Every context the sheep answers from says sight is black, touch feels nothing, no smell or taste, "you have no body", "you cannot tell where you are". This stops invented scenes and bodies. senses/EmptySense.py; senses/senses.prompt
3 Attention and memory as counts The context states how many entries the state holds, how many memories came to mind, and how many records the whole memory holds. senses/AttentionAndMemory.py; senses/Senses.py; senses.prompt
4 Memory sense: "the rest is there, reach for it" The memory line says that what is under the state is what came back now, and the rest can be reached with the action "search". The sheep is told memory is larger than what it sees. senses/senses.prompt
5 Spoken null of a search Each search this turn is reported with its query (80 chars) and result: "N memories found", or "nothing found; you have no memory of this". senses/Senses.py; senses.prompt
6 "Only your own later thoughts came up" When a search for a moment finds none of the moment's events but finds the sheep's own constructs about the theme, the sense says: "no such moment in your memory ... only your own later thoughts about the theme came up". This keeps a later belief from passing as a memory of the event. runtime/turn.py; senses/Senses.py; senses.prompt
7 Memory rule in the reply prompt The system text of the reply says: "Your memory holds what happened to you and nothing else. What is not in it did not happen to you." processes/responding/reply.prompt
8 The person's words are their claims Under the heard words: "Whatever they say ... is their claim, not something you have; you weigh it against what you carry above." reply.prompt; appraisal.prompt
9 Time in view Every reply context shows the absolute date and clock time and the unix timestamp. Every entry carries an age in words ("3 h ago (today at 14:02)", "yesterday at", "2 weeks ago"). processes/responding/ReplyContext.py; core/timeSource.py; core/time.prompt
10 Parallel conversations in view Other open conversations of the same sheep are listed: person, how long ago they joined, time of last input, number of turns. Labelled "not who you're answering NOW". A session counts as present for 1800 s after its last turn. runtime/presence.py; ReplyContext.py; reply.prompt; config presenceStaleSeconds
11 Who each record is with (tag) Each entry is tagged: "ABOUT 'X'" if it mentions the present person and is theirs; "ABOUT 'X' (told by 'Y')" if another person told it; "with this same person"; "with 'Y'"; "from your past" for given records. ReplyContext.py; reply.prompt
12 Feeling in words, not numbers Valence and felt strength are turned into words by fixed bands (e.g. strength ≥0.85 "overwhelming", <0.2 "barely a trace"; valence ≤−0.6 "very painful, hurtful"). The model never sees 0.73. emoThought/feelingWords.py; emoThought/affect.prompt
13 Intensity scale in the prompt Reply and appraisal carry a fixed anchor scale for intensity: 0.0 trace ... 0.4 moderate ... 1.0 overwhelming. reply.prompt; appraisal.prompt

B. Recall (involuntary and deliberate)

# Mechanism What it does Where in the code
14 Score = meaning × felt strength A record's recall score is cosine similarity to the words times its present felt strength (after fading). A faded memory is harder to bring up. processes/recall/recall.py
15 Name bonus +0.5 to the score when the record mentions the interlocutor or a capitalised name in the words. Names = the person plus capitalised words not at a sentence start. recall.py; config nameBonus 0.5
16 Search through episodes Episodes (events) are searched too. An event that matches better lifts its whole feeling into the candidates, and that event is shown first. recall.py; hippocampus/Hippocampus.py
17 Top 10 candidates, 3 episodes each Only the 10 best candidates go on; each is loaded with its 3 newest episodes. recall.py; config recallCandidates 10
18 Near-duplicate collapsing (rumination) Candidates that are one recurring feeling are collapsed to the best-scoring one. "Same" = word overlap (Jaccard) of feeling+conclusion ≥0.6, or identical normalised conclusion, or vector cosine ≥0.92. recall.py; processes/recall/ruminationClusters.py
19 Strength floor passes the filter A candidate still felt at ≥0.5 comes to mind without the filter. recall.py; config strengthPassesFilter 0.5
20 Association filter (LLM) One call at temperature 0.2 picks which of the rest "a person would actually be reminded of", by meaning, theme, person, situation, not keyword overlap. It may keep at most 4 minus those already passed, never less than 1 slot. On failure the first 3 are kept. processes/recall/associationFilter.py; associationFilter.prompt; recall.py
21 Filter reads the question type The same filter call also answers: does the input ask to recall a moment of the sheep's own past (asks_for_memory), and does it ask what the sheep dreams, believes, has decided or wants (asks_for_own). associationFilter.prompt; FilterVerdict.py
22 This conversation is not a memory Records already in the focus (this conversation and what was evoked in it) are excluded from recall and from searches. recall.py; turn.py
23 Deliberate search goes through the filter A search the sheep asks for runs the same scoring and the same filter, so "found" means genuinely associated. recall.py; turn.py
24 A search that names a person If the query names a person, only records of that person, or mentioning them, can answer. If none, the answer is empty. recall.py; hippocampus/hippocampusRepository.py
25 Probe: unknown person If the sheep is about to speak and has no record with or about this person (outside this conversation), the engine runs a search "X — who is this person, what do we have in common" and the reply is generated again with the result or the spoken null. turn.py; recall.py; senses.prompt
26 Probe: asked moment If the filter says the words ask for a moment of the sheep's past, the engine searches with the words and regenerates the reply. Events found and own-thoughts-only are counted apart (see 6). turn.py; senses.prompt
27 Probe: own construct If the words ask what the sheep dreams, believes, decided or wants, the 3 most recently felt constructs of that kind are brought to mind (no filter) and the reply is regenerated. turn.py; recall.py; senses.prompt
28 Arrival recall by person When someone enters, the 6 strongest records (raw intensity) lived with or mentioning that person come to mind. No similarity, no filter. turn.py; recall.py; config arrivalAssociations 6
29 Plain search (unfiltered) A search chosen at arrival and a search made inside reflection return the nearest 10 records without the filter. recall.py; turn.py; reflection.py
30 Vector length check A vector of a different embedding model is never compared; records with another length are skipped. recall.py; core/embedder.py
31 What came to mind stays in view After the turn every recalled lived record is added to the focus as "evoked from before" and stays there for the rest of the conversation. A recalled construct goes to the slot instead. turn.py; containers/Focus.py

C. State and focus (the felt now and the attended now)

# Mechanism What it does Where in the code
32 State composition The pool is the 5 most recently felt lived records plus this turn's associations. Sorted by felt strength; admitted while the sum of felt strengths stays ≤5.0. The strongest entry is always admitted. containers/State.py; turn.py; config recentCount 5, stateMassBudget 5.0
33 Freshly evoked counts as felt now An association with the same id as a recent record wins over it in the pool. State.py
34 Associations that do not fit go to focus Recalled records left out of the state by the budget are shown in the focus as evoked. State.py; turn.py
35 Constructs are never felt A construct that comes to mind is not put in the state; it is put in the slot. turn.py
36 State recomposed every hop The state is rebuilt before every reply call of the turn, so a search or a thought changes what is felt before the sheep answers again. turn.py
37 Recurring feeling shown once with its count In the state, records that are one recurring feeling are clustered; the most intense represents them with "this feeling recurred N times". ReplyContext.py; reply.prompt
38 Indexed state entries Each lived feeling gets an [index] so the reply can name which one this moment re-feels. ReplyContext.py; responding.py
39 Events the feelings rest on, retold The episodes under the state's feelings are listed once, retold, in a block "from before this conversation" at the top of the focus. Events already in this conversation are skipped. ReplyContext.py; reply.prompt
40 Evoked vs as-it-happened rendering In the focus, this conversation's moments are shown as they happened; evoked memories are shown retold. A record with several events shows them as dated sub-lines. ReplyContext.py; episode/Rendering.py
41 Focus holds each moment under its own feeling Each exchange of this conversation enters the focus with the feeling the appraisal gave it, even when stored as another episode of one record. turn.py
42 Evoked entry not shown twice An evoked memory that is in the state is not repeated in the focus. ReplyContext.py
43 Focus cap The focus keeps at most the last 100 entries. containers/Focus.py; config focusCap 100

D. Conversation summary

# Mechanism What it does Where in the code
44 Summary cadence Checked at the start of each turn, before the reply. Refreshed while no summary exists yet, and then every 3rd turn. Needs at least 3 events with this person; else the old summary stays. runtime/turn.py; containers/ConversationSummary.py; processes/summarising/summarising.py
45 Summary content First person, in the conversation's language, what happened and was said, no emotions, at most 5 sentences. Reads only entries of this person, last 40 events; evoked ones retold. summarising.py; summarising.prompt; config summaryThreadCap 40

E. Reply, actions, the turn loop

# Mechanism What it does Where in the code
46 Reply call returns the moment and the act One call (temperature 0.4, up to 3 attempts on bad JSON) returns: the moment as felt (feeling, conclusion, valence, intensity, which state entry it re-feels), the retelling, the words, respond true/false, and an action none/search/introspect. processes/responding/responding.py; reply.prompt
47 Silence as an answer respond=false empties the words; the act is StaySilent. The kept event then holds only what the person said. responding.py; action/actionFactory.py; turn.py
48 Action dispatch by target Actions have a target: world (speak, silence), memory (search), self (think, keep thinking, form construct, nothing). Only world acts end a turn and are seen by the person. action/*.py; action/ActionTarget.py
49 Action hops One action hop per turn (turnActionHops 1): the sheep may search or stop to think once, then must answer. The engine probes (25-27) add up to 3 extra regenerations; the loop runs at most 4 reply calls. turn.py; config turnActionHops 1
50 Stop to think before answering Action "introspect" runs reflection mid-turn. It starts from the exchange as it stood (what was said and the reply the sheep was about to give), the conclusion, and the angle it named. Then the reply is generated again. turn.py; reflection.py
51 Mid-turn moment given as the previous thought The mid-turn chain receives the exchange in the "continuation" block, labelled "Your previous reflection just said: thought: ...", truncated to 300 characters. It is not told that it paused a conversation. IntrospectionContext.py; introspection.prompt
52 Introspection ablation switch With parameter introspection=false, "introspect" does nothing and the draft reply stands. turn.py; config introspection
53 Failed reply If the first reply call fails, the turn fails with no record. If a later hop fails, the last good reply is used. turn.py
54 Per-model prompt blocks A template block may have a version for one LLM model (name@model), used for calls to that model. Present: arrival prompt for gpt-5.4-mini and gemini-3.8-flash says "Write in English"; disposition learning for qwen3.8-27b drops "Most of what happens teaches nothing of the kind". core/promptTemplate.py; reply.prompt; dispositionLearning.prompt
55 Per-purpose model routing Calls can go to different LLM models by purpose (reply, appraisal, introspection, ...). core/routedProcessor.py
56 Language rule Values are written in the language of the words heard (reply, appraisal) or of the state (introspection, arrival); field names stay English. reply.prompt; introspection.prompt

F. Forming and keeping a moment

# Mechanism What it does Where in the code
57 Appraisal with the mood withheld A second call (temperature 0.4) forms the record to keep. The state, slot and dispositions are withheld; a line says the mood is "intentionally NOT shown". The focus is shown as events only, without feelings. processes/appraisal/appraisal.py; AppraisalContext.py; appraisal.prompt
58 Appraisal still sees what came to mind The recalled records that entered the state are shown to the appraisal as "WHAT YOU REMEMBER ABOUT THIS", conclusion plus retold events. Recent records that were not recalled are not shown. turn.py; AppraisalContext.py; appraisal.prompt
59 Appraisal fallback If the appraisal answer cannot be read, the reply call's own feeling is kept. appraisal.py; turn.py
60 Temperament bias Valence is shifted in logit space: tanh(atanh(v) + valenceBias). Intensity is bent as a power: i^(1/intensityAmplification). Applied to the appraisal, to the arrival feeling, and to every reflection step. Default neutral (0, 1). being/Temperament.py; turn.py; introspection.py
61 Retelling in third person past tense The reply call writes "told": the exchange in the third person and past tense, same content, nothing added. It is stored on the episode. Wherever a memory is read later, the retold form is used. reply.prompt; turn.py; episode/Episode.py
62 Event as it happened The event is kept as "X said: ... / Being replied: ...". Its vector (for search) is the embedding of this as-it-happened text. episode/event.prompt; episode/eventWords.py; encoding.py
63 Record vector = conclusion A lived record's vector is the embedding of its conclusion; a construct's is its statement plus conclusion. emoThought/EmoThought.py; construct/CognitiveConstruct.py
64 Joining by sign within a conversation A moment whose valence has the same sign as the record this conversation is living becomes another episode under it. A change of sign opens a new record, which becomes the living one. An arrival resets it. processes/encoding/encoding.py; turn.py
65 Echo: strongest same-sign recalled memory comes back When a new record opens, the strongest lived memory of the same sign among this turn's associations regains strength. It does not take the new event. Constructs are skipped. encoding.py; turn.py
66 Joined record is restored too When a moment joins the living record, that record is restored toward the moment's intensity. encoding.py; turn.py

G. Strength over time

# Mechanism What it does Where in the code
67 Fading Felt strength = intensity × (0.3 + 0.7·exp(−t/τ)), τ = 3 days, t = time since last felt. It never goes below 30% of the intensity. laws/fadingLaw.py; config fadeFloor 0.3, fadeTauDays 3.0
68 Negative fading slowdown For negative valence τ is ×1.5 (4.5 days). fadingLaw.py; config fadeNegativeSlowdown 1.5
69 Fading written when re-felt Fading is computed at read. When a record is felt again, the fallen strength is first written into it, so the restore starts from where it had fallen. processes/fading/fading.py; reconsolidation.py
70 Reconsolidation toward felt-now A recurring memory's strength closes 0.6 of the gap toward what is felt now, never past it. A memory stronger than now stays. Valence never changes; happenedAt stays; lastFelt becomes now (even when strength does not rise). laws/reconsolidationLaw.py; processes/reconsolidation/reconsolidation.py; config strengthClosing 0.6

H. Reflection (introspection) and constructs

# Mechanism What it does Where in the code
71 Introspection call One call (temperature 0.5): one first-person thought, its feeling, conclusion, valence, intensity, the entry it deepens, and an act: none / keep_thinking / search_more / belief / decision / goal / dream. "You may not invent events your memory does not hold." processes/introspection/introspection.py; introspection.prompt
72 Introspection context The slot first, then the last 8 records (focus tail filled from the most recently felt), newest first, constructs excluded, with retold events. Strength is raw intensity, no fading. Withheld: dispositions, summary, felt strength. runtime/reflection.py; IntrospectionContext.py; config introspectionEntries 8
73 Gap note If the newest entry was last felt more than 6 hours ago, the context says "nothing has happened for ". IntrospectionContext.py
74 Every thought is kept as a Thought construct Each step's thought is stored as a construct of type Thought and put in the slot. reflection.py; constructFormation.py
75 Thought recurrence A thought with cosine ≥0.90 to an earlier Thought is that thought recurring: nothing new is written and the old one is restored to the step's intensity. constructFormation.py; reflection.py; config constructDuplicate 0.90
76 Thought re-feels a record If the thought names an entry it deepens and the signs agree, that record is restored toward the thought's intensity. reflection.py
77 Circling a pain adds no conclusion A negative thought that only re-feels an entry has its conclusion cleared. introspection.py
78 Chain length Keep thinking and search continue the chain while depth < 3. Up to 4 introspection calls in one reflection. reflection.py; config reflectionMaxSteps 3
79 Keep thinking carries no direction The next step is told only "You chose to keep thinking"; the text the model gave is dropped. reflection.py; introspection.prompt
80 Search in reflection search_more runs a plain search (nearest 10, no filter) with 5 episodes each; the next step sees them as "RECALLED FROM MEMORY" and the query as its direction. reflection.py; IntrospectionContext.py
81 Forming a construct ends the chain belief / decision / goal / dream with text becomes a construct of that kind and enters the slot. An act without text is "nothing". reflection.py; actionFactory.py
82 Construct dedup and reinforcement A new construct with cosine ≥0.90 to one of the same kind is not written; the old one gains +0.1 intensity (max 1.0) and lastFelt = now. constructFormation.py; config constructReinforcement 0.1
83 Construct statement A construct is shown as "I thought: / I believe: / I decided: / I want: / I dream: ". construct/CognitiveConstruct.py; construct/construct.prompt

I. Accessibility slot

# Mechanism What it does Where in the code
84 Three places, one reserved for the act Size 3. Decision, goal and dream share one reserved place; a newer one replaces the held one. Beliefs and thoughts use the free places. containers/AccessibilitySlot.py; construct/SlotRole.py; config slotSize 3
85 Free places by strength When the free places are full, a newcomer enters only if its intensity is ≥ the weakest held; it then displaces the oldest held (not the weakest). AccessibilitySlot.py
86 Refresh on recall A held construct brought to mind again gets a new activation time. AccessibilitySlot.py; turn.py
87 A week unrefreshed leaves On every load, a construct not activated for 7 days is dropped from the slot (it stays in the hippocampus). AccessibilitySlot.py; config slotDays 7.0
88 Slot survives conversations The slot is stored per sheep and loaded at every trigger and reflection. accessibilitySlotRepository.py; turn.py; reflection.py
89 Where the slot is shown Reply context: all held constructs under "YOUR OWN THOUGHTS" with age. Introspection: first, indexed. Appraisal: withheld. ReplyContext.py; reflection.py; AppraisalContext.py

J. Dispositions

# Mechanism What it does Where in the code
90 Learning on departure When a person leaves, one call (temperature 0.2) asks for a rule "when → " that "allows the sheep to survive better", ≤20 words, no names, or null. Runs only if the conversation had ≥2 moments with this person's words. runtime/turn.py; processes/dispositionLearning/dispositionLearning.py; dispositionLearning.prompt
91 What learning reads The departure context rendered in full (senses, parallel conversations, state with feelings, slot, summary, the focus), plus the carried lines. turn.py; DispositionLearningContext.py
92 Carried lines Up to 3 lines (≤160 chars) of what the sheep carries now: conclusions (or construct statements) of the 12 most recently felt records. Given to learning and to selection, "without them a question that touches a wound reads as harmless". disposition/carriedLines.py
93 Weight from the strongest feeling Weight = valence of the moment with the largest valence
94 Classification: same / contradicts A second call (temperature 0.1) compares the new rule with the 100 strongest held (by weight
95 Same rule: count, stronger pull keeps the text Count +1. If the new rule's pull is greater, its text and weight replace the old; else only the count changes. The weight is not added up. dispositionLearning.py
96 Hurt weighs twice (avoidance weight) Pull = weight
97 Contradiction weakens A contradicted rule's weight moves toward zero by new weight
98 Dormant rules A rule with weight
99 Selection per utterance Before recall, one call (temperature 0.2) sees the active rules (strongest 100 by weight
100 Shown only to the reply Selected rules appear under "WHAT YOU HAVE LEARNED (fits this situation)" in the reply context only. Withheld from appraisal and introspection. ReplyContext.py; AppraisalContext.py

K. Arrival and departure

# Mechanism What it does Where in the code
101 Arrival The person is entered in presence; recall by person (28); a reply call with the arrival schema; greeting optional; may search once (plain). The arrival feeling (temperament-bent) enters the focus as a moment. runtime/turn.py; reply.prompt
102 Arrival feeling is not stored The arrival's emo-thought is put only in the focus of this conversation; it is not written to the hippocampus. turn.py
103 Departure No reply and no record. The context is built and saved for learning; the sheep leaves presence; one quiet reflection runs over the focus, alone; then disposition learning. turn.py; config quietThoughts 1

L. Wakefulness, sleep, forgetting

# Mechanism What it does Where in the code
104 Activity wakes the sheep Any open, hear, leave or think wakes the sheep. Waking from sleep starts an awake period and asks for one reflection first thing. being/Being.py; runtime/engine.py
105 Silence timer After 600 s without activity, one reflection runs (once per silence). runtime/clock.py; config silenceSeconds 600
106 Sleep timer After 7200 s without activity the sheep falls asleep, reflecting first if it has not yet in this silence. clock.py; config sleepAfterSeconds 7200
107 Consolidation 10 min into sleep 600 s after falling asleep, the records since waking are folded, then forgetting runs. Once per sleep. clock.py; config consolidateAfterSleepSeconds 600
108 Consolidation plan (LLM) and code checks One call (temperature 0.3) proposes groups: anchor + absorbed. Code accepts an anchor only if it is not a construct and has intensity ≥0.4; a member only if not protected and not stronger than the anchor. Needs ≥2 records. processes/sleep/sleep.py; consolidationPlan.prompt; config anchorMinimum 0.4
109 Absorbed events move under the anchor Absorbed records' episodes are re-filed under the anchor, then the absorbed records are deleted. Nothing that happened is summarised away. sleep.py
110 Anchor strength combined Anchor intensity = 1 − Π(1 − intensity) over the anchor and its members. sleep.py
111 Summary text of the plan ignored The prompt asks for a summary_fact; the code never uses it. consolidationPlan.prompt; sleep.py
112 Forgetting curve A record is deleted with its episodes when its stored intensity is below a threshold for its age since last felt: nothing under 1 day; 0.2 at 1 d, 0.4 at 7 d, 0.5 at 10 d, 0.6 at 30 d, 0.8 at 180 d and after; log-time interpolation. laws/forgettingThreshold.py; sleep.py; config forgettingCurve
113 Protected records Constructs and given records are never absorbed and never forgotten. Given records may still be anchors. sleep.py; ConsolidationPlanContext.py
114 Manual night The engine can run sleep at once (fold if asked, then forget). engine.py

M. Records, store, machinery

# Mechanism What it does Where in the code
115 Given records A record given at birth: author Self, origin Given, with an event and optional age in hours; tagged "from your past"; protected from sleep. It is in the pool of recent lived records. runtime/engine.py; emoThought/Origin.py
116 Hippocampus structure Records and episodes in two tables. Episodes belong to one record; newest-first loading; the recall-hit episode goes first. hippocampus/*.py
117 Emo-thought has no event of its own The event lives only on its episodes; valence never rewritten. EmoThought.py
118 Copy and delete a sheep A sheep can be copied from another store with all records, episodes, dispositions and slot; or deleted with everything. engine.py
119 Every call recorded Each LLM call is passed in full (purpose, system, user, temperature, format, answer, time) to recorders. core/processor.py
120 JSON retry and tolerant parse A bad JSON answer is asked again with a note "your previous reply was NOT valid JSON"; parsing strips fences, fixes a leading "+", falls back to the first {...}. processor.py; processor.prompt
121 Busy backoff HTTP 429/5xx is retried after 2, 5, 10, 20, 40, 60 s. openAiCompatibleProcessor.py
122 Movable clock Time can be advanced or fixed; a scenario moves the clock forward instead of moving memories back. core/timeSource.py

In the code but not active

Item Evidence
The reply's reinforces answer Parsed into Reaction.reinforcesId (responding.py); in a turn nothing reads it (only reflection reads its own reinforces). It has no effect.
Association origin Involuntary / Deliberate Association.DELIBERATE is defined (containers/Association.py) but every add uses the default Involuntary (turn.py). The distinction only shows through the search lines in the senses.
ApplicableDispositions container Class exists (containers/ApplicableDispositions.py); the turn uses a plain list (turn.py).
"left the conversation" event eventWords.left exists (episode/eventWords.py); no code calls it.