anton-agent 2.26.5.29.4__py3-none-any.whl

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.
Files changed (112) hide show
  1. anton/README.md +1069 -0
  2. anton/__init__.py +1 -0
  3. anton/__main__.py +3 -0
  4. anton/analytics.py +129 -0
  5. anton/channel/__init__.py +0 -0
  6. anton/channel/branding.py +237 -0
  7. anton/channel/theme.py +91 -0
  8. anton/chat.py +1831 -0
  9. anton/chat_session.py +132 -0
  10. anton/chat_ui.py +695 -0
  11. anton/checks.py +109 -0
  12. anton/cli.py +1645 -0
  13. anton/clipboard.py +428 -0
  14. anton/commands/__init__.py +0 -0
  15. anton/commands/datasource/__init__.py +23 -0
  16. anton/commands/datasource/connect.py +556 -0
  17. anton/commands/datasource/custom.py +348 -0
  18. anton/commands/datasource/helpers.py +121 -0
  19. anton/commands/datasource/manage.py +120 -0
  20. anton/commands/datasource/verify.py +188 -0
  21. anton/commands/goal.py +191 -0
  22. anton/commands/session.py +132 -0
  23. anton/commands/setup.py +177 -0
  24. anton/commands/share.py +529 -0
  25. anton/commands/skills.py +422 -0
  26. anton/commands/ui.py +221 -0
  27. anton/config/__init__.py +0 -0
  28. anton/config/settings.py +153 -0
  29. anton/connect_collector.py +273 -0
  30. anton/context/__init__.py +0 -0
  31. anton/context/self_awareness.py +97 -0
  32. anton/core/__init__.py +0 -0
  33. anton/core/artifacts/__init__.py +43 -0
  34. anton/core/artifacts/models.py +114 -0
  35. anton/core/artifacts/snapshot.py +89 -0
  36. anton/core/artifacts/store.py +367 -0
  37. anton/core/backends/__init__.py +0 -0
  38. anton/core/backends/base.py +251 -0
  39. anton/core/backends/local.py +719 -0
  40. anton/core/backends/manager.py +86 -0
  41. anton/core/backends/remote.py +310 -0
  42. anton/core/backends/scratchpad_boot.py +831 -0
  43. anton/core/backends/utils.py +14 -0
  44. anton/core/backends/wire.py +10 -0
  45. anton/core/datasources/__init__.py +0 -0
  46. anton/core/datasources/data_vault.py +358 -0
  47. anton/core/datasources/datasource_registry.py +213 -0
  48. anton/core/datasources/datasources.md +726 -0
  49. anton/core/dispatch/__init__.py +148 -0
  50. anton/core/dispatch/adapter.py +227 -0
  51. anton/core/dispatch/channels/__init__.py +7 -0
  52. anton/core/dispatch/channels/cli.py +221 -0
  53. anton/core/dispatch/entities.py +170 -0
  54. anton/core/dispatch/local_runtime.py +447 -0
  55. anton/core/dispatch/policy.py +241 -0
  56. anton/core/dispatch/registry.py +159 -0
  57. anton/core/dispatch/repository.py +537 -0
  58. anton/core/dispatch/router.py +641 -0
  59. anton/core/dispatch/session_store.py +189 -0
  60. anton/core/llm/__init__.py +0 -0
  61. anton/core/llm/anthropic.py +281 -0
  62. anton/core/llm/client.py +291 -0
  63. anton/core/llm/openai.py +1148 -0
  64. anton/core/llm/prompt_builder.py +179 -0
  65. anton/core/llm/prompts.py +466 -0
  66. anton/core/llm/provider.py +383 -0
  67. anton/core/llm/structured.py +128 -0
  68. anton/core/llm/tracing.py +51 -0
  69. anton/core/memory/__init__.py +0 -0
  70. anton/core/memory/acc.py +744 -0
  71. anton/core/memory/base.py +145 -0
  72. anton/core/memory/cerebellum.py +385 -0
  73. anton/core/memory/consolidator.py +194 -0
  74. anton/core/memory/cortex.py +538 -0
  75. anton/core/memory/episodes.py +300 -0
  76. anton/core/memory/hippocampus.py +579 -0
  77. anton/core/memory/skills.py +422 -0
  78. anton/core/runtime.py +209 -0
  79. anton/core/session.py +2145 -0
  80. anton/core/settings.py +22 -0
  81. anton/core/tools/recall_skill.py +130 -0
  82. anton/core/tools/registry.py +56 -0
  83. anton/core/tools/tool_defs.py +334 -0
  84. anton/core/tools/tool_handlers.py +501 -0
  85. anton/core/tools/web_tools.py +290 -0
  86. anton/core/utils/__init__.py +0 -0
  87. anton/core/utils/scratchpad.py +66 -0
  88. anton/demo_data/__init__.py +0 -0
  89. anton/demo_data/nvda_btc_scratchpad_backup.py +498 -0
  90. anton/explainability.py +271 -0
  91. anton/memory/__init__.py +0 -0
  92. anton/memory/history_store.py +200 -0
  93. anton/memory/learnings.py +86 -0
  94. anton/memory/manage.py +535 -0
  95. anton/memory/reconsolidator.py +152 -0
  96. anton/memory/store.py +160 -0
  97. anton/minds_client.py +205 -0
  98. anton/policies.py +236 -0
  99. anton/prompts.py +30 -0
  100. anton/publisher.py +204 -0
  101. anton/tools.py +540 -0
  102. anton/updater.py +158 -0
  103. anton/utils/__init__.py +0 -0
  104. anton/utils/clipboard.py +408 -0
  105. anton/utils/datasources.py +304 -0
  106. anton/utils/prompt.py +155 -0
  107. anton/workspace.py +242 -0
  108. anton_agent-2.26.5.29.4.dist-info/METADATA +353 -0
  109. anton_agent-2.26.5.29.4.dist-info/RECORD +112 -0
  110. anton_agent-2.26.5.29.4.dist-info/WHEEL +4 -0
  111. anton_agent-2.26.5.29.4.dist-info/entry_points.txt +2 -0
  112. anton_agent-2.26.5.29.4.dist-info/licenses/LICENSE +7 -0
anton/README.md ADDED
@@ -0,0 +1,1069 @@
1
+ # Inside Anton
2
+
3
+ ## Introduction
4
+ In 2015, after reading *How to Create a Mind* by Ray Kurzweil, I became convinced that we could programmatically build a mind by mirroring the brain’s core building blocks.
5
+ I tried. I failed — but I learned something important: one fundamental piece was missing. I called it the **Anticipation Block Architecture**. You can read about it [here](https://torrmal.github.io/2015/12/29/anticipation-loop/).
6
+
7
+ It turns out the world went on to build something remarkably similar: transformers and now, in 2026, LLMs have matured to the point where the ideas seeded by *How to Create a Mind* are no longer just philosophical — they’re implementable.
8
+ And here we are: Like an adrenaline junkie eyeing at a bungee looking for another fix, trying again: Meet **Anton**.
9
+
10
+ ## A mini Mind
11
+
12
+ It is probably obvious now, but Anton has a brain-inspired architecture, and the more we build it the more it resembles/mirrors functional parts of the brain. On the other hand we also understand that people don't need to know anything about the brain to play with Anton, so we mapped some of the places/files where users can have inputs, or investigate what's up, to names that make more sense than the scientific name of that function of the brain.
13
+
14
+ The current implementation has seven blocks, mapping the major learning systems:
15
+
16
+ | Brain Region | Function | Anton Equivalent |
17
+ |------------------------------|---------------------------------------------------|---------------------------------------------------------------|
18
+ | Prefrontal Cortex (PFC) | Executive control, planning, the "inner voice" | Orchestrator — decides what to work on, how, and when to stop |
19
+ | Working Memory (dlPFC) | Temporary reasoning space, ~4 slots | Scratchpads — isolated reasoning environments |
20
+ | Hippocampus | Episodic memory, records experiences | Experience Store — logs of problem + context + solution |
21
+ | Cortex (semantic memory) | Facts, rules, identity — the consolidated knowledge | Engrams — `lessons.md`, `rules.md`, `profile.md` |
22
+ | Striatum (procedural memory) | Habits and learned procedures — patterns of action | Skills — multi-stage reusable procedures with declarative + chunked + code representations |
23
+ | Cerebellum (per-cell error learning) | Supervised correction on a single action — "what I expected vs what happened" | Cerebellum — buffers errored scratchpad cells, extracts generalizable lessons via post-mortem |
24
+ | Anterior Cingulate Cortex (turn-level error detection) | Notices when the same kind of error pattern fires more than once within an episode — the brain's ERN | ACC — observes turn events, flags repeat patterns, produces lessons that flow through the same Engram pipeline |
25
+
26
+ These seven systems coexist the way they coexist in the brain: declarative and procedural memory are dissociable (a person with hippocampal damage like H.M. can lose new declarative memories but still learn motor skills); the cerebellum operates in parallel with continued action rather than blocking it; and the ACC watches the whole turn rather than any single cell, complementing rather than replacing the cerebellum.
27
+
28
+
29
+
30
+ ## Architecture of Anton
31
+
32
+ The high-level flow — how the executive, scratchpads, and the long-term stores collaborate on every turn:
33
+
34
+ ```
35
+ ┌────────────────────────────────────────────────────┐
36
+ │ EXECUTIVE (the orchestrator) │
37
+ │ │
38
+ │ On new problem: │
39
+ │ 1. Check SKILL LIBRARY → match? │
40
+ │ YES → recall_skill(label) → load procedure │
41
+ │ NO → open fresh scratchpad │
42
+ │ 2. Monitor scratchpad progress │
43
+ │ 3. Detect stuck/failure → pivot strategy │
44
+ │ 4. On success → record to experience store │
45
+ └────────────┬────────────────────↑──────────────────┘
46
+ │ spawns & monitors │
47
+ ▼ │
48
+ ┌──────────────────────────────────────────────────────┐
49
+ │ SCRATCHPADS (working memory) │
50
+ │ │
51
+ │ Each scratchpad is: │
52
+ │ - An isolated reasoning environment (its own venv) │
53
+ │ - A chain-of-thought trace (code + observations) │
54
+ │ - Has a goal, constraints, and a budget │
55
+ │ - Can request sub-scratchpads (decomposition) │
56
+ │ - Can invoke the hypocampus in a loop │
57
+ │ │
58
+ │ Every cell execution fires pre/post hooks observed │
59
+ │ by the CEREBELLUM (post-mortem error learning). │
60
+ └──────┬──────────────┬───────────────────┬────────────┘
61
+ │ │ │
62
+ │ on success │ on cell errors │ on success
63
+ ▼ ▼ ▼
64
+ ┌────────────┐ ┌──────────────┐ ┌─────────────────────┐
65
+ │ EXPERIENCE │ │ CEREBELLUM │ │ SKILL LIBRARY │
66
+ │ STORE │ │ │ │ │
67
+ │ (hipp.) │ │ Buffers bad │ │ /skill save → LLM │
68
+ │ │ │ cells, runs │ │ drafts a procedure │
69
+ │ Episodes — │ │ post-mortem │ │ with label + name + │
70
+ │ JSONL log │ │ via LLM, │ │ when_to_use + │
71
+ │ of every │ │ encodes new │ │ declarative_md. │
72
+ │ turn. │ │ lessons via │ │ │
73
+ │ │ │ Cortex. │ │ Future turns recall │
74
+ │ Recall via │ │ │ │ the procedure via │
75
+ │ `recall` │ │ Lessons feed │ │ recall_skill tool. │
76
+ │ tool. │ │ next code │ │ │
77
+ │ │ │ generation │ │ Stored at │
78
+ │ │ │ (procedural │ │ ~/.anton/skills/ │
79
+ │ │ │ priming). │ │ <label>/ │
80
+ └────────────┘ └──────────────┘ └─────────────────────┘
81
+ ```
82
+
83
+ The brain analog: the executive (PFC) plans and delegates to working memory (scratchpads), which can pull on procedural memory (striatum/skills) for known recipes and on declarative memory (hippocampus/cortex/engrams) for facts. The cerebellum runs in parallel with continued action — it never blocks the agent, it just refines future cells through supervised error learning.
84
+
85
+ And the Hipocampus also is controlled as follows:
86
+
87
+ ```
88
+ ┌───────────────────────────────────────────────┐
89
+ │ CORTEX (cortex.py) │
90
+ │ Prefrontal Cortex — Executive Control │
91
+ │ Coordinates all memory systems, decides what │
92
+ │ to load into working memory (context window) │
93
+ └────────┬──────────────┬──────────────┬────────┘
94
+ │ │ │
95
+ ┌─────────────┘ ┌──────┘ └──────┐
96
+ ▼ ▼ ▼
97
+ ┌──────────────────┐ ┌───────────────────┐ ┌───────────────────┐
98
+ │ HIPPOCAMPUS │ │ CONSOLIDATOR │ │ RECONSOLIDATOR │
99
+ │ (hippocampus.py) │ │(consolidator.py) │ │(reconsolidator.py)│
100
+ │ │ │ │ │ │
101
+ │ Encodes & reads │ │ Sleep replay — │ │ Reactivates old │
102
+ │ memory traces │ │ reviews scratchpad│ │ memories, converts│
103
+ │ at one scope │ │ sessions offline, │ │ legacy formats to │
104
+ │ (global / proj) │ │ extracts lessons │ │ new schema │
105
+ └────────┬─────────┘ └───────────────────┘ └───────────────────┘
106
+
107
+
108
+ ┌────────────────────────────────────────────────┐
109
+ │ SEMANTIC MEMORY FILES (on disk) │
110
+ │ │
111
+ │ profile.md ← Identity (Default Mode Network)│
112
+ │ rules.md ← Behavioral gates (Basal Gangli)│
113
+ │ lessons.md ← Semantic facts (Temporal Lobe) │
114
+ │ topics/*.md ← Domain expertise (Association │
115
+ │ Areas), loaded on demand │
116
+ └────────────────────────────────────────────────┘
117
+
118
+ ┌───────────────────────────────────────────────┐
119
+ │ EPISODIC MEMORY (episodes.py) │
120
+ │ Medial Temporal Lobe — raw experience │
121
+ │ │
122
+ │ episodes/*.jsonl ← One file per session │
123
+ │ Timestamped log of every turn, tool call, │
124
+ │ and scratchpad execution. Searchable via │
125
+ │ the `recall` tool. │
126
+ └───────────────────────────────────────────────┘
127
+ ```
128
+
129
+ ## Brain Mapping
130
+
131
+ | Brain Structure | Module | What It Does |
132
+ |---|---|---|
133
+ | **Hippocampus** (CA3/CA1) | `hippocampus.py` | The storage engine. Reads and writes individual memory traces (engrams) to markdown files. One instance per scope — it doesn't decide *what* to remember, just executes storage and retrieval. |
134
+ | **Prefrontal Cortex** (dlPFC/vmPFC) | `cortex.py` | The executive coordinator. Manages two hippocampi (global + project), decides which memories to load into the LLM's context window, gates whether new memories need confirmation. |
135
+ | **Medial Temporal Lobe** (episodic) | `episodes.py` | Raw episodic memory. Logs every conversation turn as timestamped JSONL — user input, assistant responses, tool calls, scratchpad output. Searchable via the `recall` tool. Like HSAM: never forgets. |
136
+ | **Hippocampal Replay** (SWS consolidation) | `consolidator.py` | After a scratchpad session ends, replays what happened in compressed form and extracts durable lessons via a fast LLM call. Like sleep — offline, post-hoc, selective. |
137
+ | **Striatum** (procedural memory) | `skills.py` | Long-term procedural memory. Stores reusable skills as multi-stage directories (declarative → chunks → code). The LLM retrieves skills on demand via the `recall_skill` tool, the way the basal ganglia activates a learned action sequence in response to a familiar context. |
138
+ | **Cerebellum** (supervised error learning) | `cerebellum.py` | Forward-model + error correction at the *single-cell* time scale. Observes every scratchpad cell via pre/post execute hooks, buffers errored/warning cells across the turn, and runs a post-mortem LLM diff to extract generalizable lessons. Lessons flow through the existing wisdom-injection pipeline into future code generation. Operates in parallel with the agent — never blocks. |
139
+ | **Anterior Cingulate Cortex** (ERN — turn-level error detection) | `acc.py` | Pattern-level error detection at the *whole-turn* time scale. Watches a stream of typed events (scratchpad calls/results, tool calls/results, history repairs, round milestones) and runs pure-function detectors at end-of-turn. Lessons it emits flow through the same `cortex.encode()` path the cerebellum uses; it does not own storage. Implemented as a standalone module with passing tests; not yet wired into `ChatSession`. |
140
+ | **Reconsolidation** (Nader et al.) | `reconsolidator.py` | One-time migration. When old memory formats are reactivated, they enter a labile state and get re-encoded in the new format. Preserves content, updates structure. |
141
+ | **Medial PFC / Default Mode Network** | `profile.md` | Always-on self-model. Identity facts (name, timezone, preferences) that contextualize all processing — you don't "look up" your own name. |
142
+ | **Basal Ganglia + OFC** | `rules.md` | Go/No-Go behavioral gates. The direct pathway enables ("always"), the indirect pathway suppresses ("never"), the OFC handles conditions ("when X → do Y"). |
143
+ | **Anterior Temporal Lobe** | `lessons.md` | Semantic knowledge hub. Facts that started as episodes but have been distilled into general knowledge. |
144
+ | **Cortical Association Areas** | `topics/*.md` | Deep domain expertise stored in specialized regions. Not all active simultaneously — retrieved when contextual cues indicate relevance. |
145
+ | **Locus Coeruleus-NE** | Memory modes | The encoding gate. Controls how aggressively Anton writes new memories — from broad/indiscriminate to fully suppressed. |
146
+ | **Synaptic Homeostasis** | Compaction | During "sleep", weak traces are pruned and redundant memories are merged, preventing unbounded growth. |
147
+
148
+ ## File Layout on Disk
149
+
150
+ ```
151
+ ~/.anton/ GLOBAL scope (cross-project)
152
+ ├── memory/
153
+ │ ├── profile.md Identity — who the user is
154
+ │ ├── rules.md Always/never/when behavioral rules
155
+ │ ├── lessons.md Semantic facts from experience
156
+ │ └── topics/ Deep domain expertise
157
+ │ └── *.md
158
+ └── skills/ PROCEDURAL MEMORY (striatum)
159
+ └── <label>/ One directory per skill
160
+ ├── meta.json label, name, when_to_use, provenance, presence flags
161
+ ├── declarative.md Stage 1 — step-by-step procedure (always present)
162
+ ├── chunks.md Stage 2 — higher-level recipes/macros (optional, v2+)
163
+ ├── code/ Stage 3 — runnable helper modules (optional, v2+)
164
+ │ └── __init__.py
165
+ ├── requirements.txt Stage 3 dependencies (optional)
166
+ └── stats.json Per-stage usage counters (recommended/used)
167
+
168
+ <project>/.anton/ PROJECT scope (workspace-specific)
169
+ ├── memory/
170
+ │ ├── rules.md Project-specific rules
171
+ │ ├── lessons.md Project-specific knowledge (cerebellum writes here)
172
+ │ └── topics/
173
+ │ └── *.md
174
+ ├── episodes/ EPISODIC MEMORY (conversation archive)
175
+ │ ├── 20260227_143052.jsonl One file per session (YYYYMMDD_HHMMSS)
176
+ │ └── 20260228_091522.jsonl
177
+ ├── anton.md User-written project context (unchanged)
178
+ └── .env Secrets (unchanged)
179
+ ```
180
+
181
+ Profile (`profile.md`) is global-only — identity is singular. Rules and lessons exist at both scopes. Skills live globally (one library across projects) at `~/.anton/skills/`. `anton.md` stays as the user-written instruction file and is not managed by the memory system.
182
+
183
+ ## Memory Entry Format
184
+
185
+ All memory files are human-readable markdown. Metadata lives in HTML comments so the files look clean when you open them:
186
+
187
+ **rules.md:**
188
+ ```markdown
189
+ # Rules
190
+
191
+ ## Always
192
+ - Use httpx instead of requests <!-- confidence:high source:user ts:2026-02-27 -->
193
+ - Call progress() before llm.complete() in scratchpad <!-- confidence:high source:consolidation ts:2026-02-27 -->
194
+
195
+ ## Never
196
+ - Use time.sleep() in scratchpad cells <!-- confidence:high source:consolidation ts:2026-02-27 -->
197
+
198
+ ## When
199
+ - If fetching paginated API data → async + progress() between pages <!-- confidence:medium source:consolidation ts:2026-02-27 -->
200
+ ```
201
+
202
+ **lessons.md:**
203
+ ```markdown
204
+ # Lessons
205
+ - CoinGecko free tier rate-limits at ~50 req/min <!-- topic:api-coingecko ts:2026-02-27 -->
206
+ - Bitcoin price data via /coins/bitcoin/market_chart/range <!-- topic:api-coingecko ts:2026-02-27 -->
207
+ - pandas read_csv needs encoding='utf-8-sig' for BOM files <!-- topic:pandas ts:2026-02-27 -->
208
+ ```
209
+
210
+ **profile.md:**
211
+ ```markdown
212
+ # Profile
213
+ - Name: Jorge
214
+ - Timezone: PST
215
+ - Expertise: Python, data analysis, API integrations
216
+ - Communication: concise, direct
217
+ - Tools: prefers uv over pip, uses VS Code, macOS
218
+ ```
219
+
220
+ ### Metadata Fields
221
+
222
+ Each entry can carry HTML-comment metadata:
223
+
224
+ | Field | Values | Meaning |
225
+ |---|---|---|
226
+ | `confidence` | `high`, `medium`, `low` | How certain the system is. Drives the encoding gate in copilot mode. |
227
+ | `source` | `user`, `consolidation`, `llm` | Where the memory originated. User-sourced = explicit tool call or user request. Consolidation = extracted from scratchpad replay. LLM = the model decided to save it mid-conversation. |
228
+ | `ts` | `YYYY-MM-DD` | When the memory was encoded. Used for recency ordering in lessons. |
229
+ | `topic` | slug string | Topic tag for lessons. Used to cross-file into `topics/{slug}.md`. |
230
+
231
+ ## Episodic Memory — Raw Conversation Archive
232
+
233
+ Episodic memory is a complete, timestamped log of everything that happens in a conversation. Brain analog: the **Medial Temporal Lobe** episodic memory system.
234
+
235
+ ### File Format
236
+
237
+ Each session produces one JSONL file in `.anton/episodes/`:
238
+
239
+ ```jsonl
240
+ {"ts":"2026-02-27T14:30:52","session":"20260227_143052","turn":1,"role":"user","content":"What's the bitcoin price?","meta":{}}
241
+ {"ts":"2026-02-27T14:30:55","session":"20260227_143052","turn":1,"role":"assistant","content":"Let me check that.","meta":{}}
242
+ {"ts":"2026-02-27T14:31:00","session":"20260227_143052","turn":1,"role":"tool_call","content":"{'action': 'exec', ...}","meta":{"tool":"scratchpad"}}
243
+ {"ts":"2026-02-27T14:31:02","session":"20260227_143052","turn":1,"role":"scratchpad","content":"$67,432","meta":{"description":"Fetch BTC"}}
244
+ {"ts":"2026-02-27T14:31:03","session":"20260227_143052","turn":1,"role":"tool_result","content":"[output]\n$67,432","meta":{"tool":"scratchpad"}}
245
+ ```
246
+
247
+ ### Roles
248
+
249
+ | Role | What's Logged |
250
+ |------|---------------|
251
+ | `user` | User's input (text or stringified multimodal content) |
252
+ | `assistant` | Anton's text response |
253
+ | `tool_call` | Tool invocation input (truncated to 500 chars) |
254
+ | `tool_result` | Tool output (truncated to 2000 chars) |
255
+ | `scratchpad` | Scratchpad cell stdout (truncated to 2000 chars) |
256
+
257
+ ### The `recall` Tool
258
+
259
+ The LLM has a `recall` tool that searches episodic memory. It's included in the tool list when episodic memory is enabled.
260
+
261
+ ```json
262
+ {
263
+ "name": "recall",
264
+ "input": {
265
+ "query": "bitcoin",
266
+ "max_results": 20,
267
+ "days_back": 30
268
+ }
269
+ }
270
+ ```
271
+
272
+ Search is case-insensitive substring matching across all JSONL files, newest-first. The `days_back` parameter filters by session file timestamp.
273
+
274
+ **When recall happens:** The LLM decides to call the `recall` tool during conversation — typically when the user asks about previous sessions, past work, or "what did we talk about last time?" It's a standard tool call like `scratchpad` or `memorize`, not automatic.
275
+
276
+ ### Design Principles
277
+
278
+ - **Fire-and-forget**: `log()` catches all exceptions and never raises. Logging never blocks the conversation.
279
+ - **File locking**: Uses `fcntl.flock(LOCK_EX)` for safe concurrent appends.
280
+ - **Truncation**: Tool inputs capped at 500 chars, results at 2000 chars — prevents JSONL bloat from large scratchpad outputs.
281
+ - **Toggle**: Controlled by `ANTON_EPISODIC_MEMORY` env var or `/setup` > Memory. Default: ON.
282
+
283
+ ## How Memory Flows Through a Session
284
+
285
+ Memory reaches the LLM at two distinct moments:
286
+
287
+ ### Moment A — System Prompt (Strategic Retrieval)
288
+
289
+ When a turn begins, the Cortex assembles memories into the system prompt. This is like the prefrontal cortex loading relevant memories into working memory before a task:
290
+
291
+ 1. **Identity** (profile) — always loaded (~300 tokens)
292
+ 2. **Global rules** — behavioral constraints (~1500 tokens)
293
+ 3. **Project rules** — scope-specific constraints (~1500 tokens)
294
+ 4. **Global lessons** — semantic knowledge, most recent first (~1000 tokens)
295
+ 5. **Project lessons** — scope-specific facts, most recent first (~1000 tokens)
296
+
297
+ Total budget: ~5800 tokens, about 3% of a 200K context window.
298
+
299
+ The Cortex inserts these as labeled sections in the system prompt (`## Your Memory — Identity`, `## Your Memory — Global Rules`, etc.) so the LLM knows they're its own memories, not user instructions. The `anton.md` user-written context is injected *after* memory, giving user instructions higher priority.
300
+
301
+ ### Moment B — Scratchpad Tool Description (Procedural Priming)
302
+
303
+ When scratchpads are active, relevant lessons are appended to the scratchpad tool description. The LLM sees them right when composing code — like procedural memory that activates automatically when you get on a bike:
304
+
305
+ ```python
306
+ scratchpad_tool["description"] += f"\n\nLessons from past sessions:\n{wisdom}"
307
+ ```
308
+
309
+ This combines all "when" rules + lessons with `scratchpad-*` topics from both scopes. The content comes from `cortex.get_scratchpad_context()`, which calls `recall_scratchpad_wisdom()` on both hippocampi.
310
+
311
+ ## The `memorize` Tool
312
+
313
+ Anton has a tool called `memorize` that it can call during conversation to encode new memories. The LLM decides what to save and classifies each entry:
314
+
315
+ ```json
316
+ {
317
+ "entries": [
318
+ {
319
+ "text": "CoinGecko rate-limits at 50 req/min",
320
+ "kind": "lesson",
321
+ "scope": "global",
322
+ "topic": "api-coingecko"
323
+ },
324
+ {
325
+ "text": "Always use progress() for long API calls in scratchpad",
326
+ "kind": "always",
327
+ "scope": "global"
328
+ }
329
+ ]
330
+ }
331
+ ```
332
+
333
+ **Entry kinds:**
334
+ - **always** — Something to always do. Written to the `## Always` section of `rules.md`.
335
+ - **never** — Something to never do. Written to `## Never`.
336
+ - **when** — A conditional rule ("if X then Y"). Written to `## When`.
337
+ - **lesson** — A factual discovery. Written to `lessons.md` and optionally to `topics/{slug}.md`.
338
+ - **profile** — A fact about the user. Rewrites `profile.md` as a coherent snapshot.
339
+
340
+ **Scope determines where the memory lives:**
341
+ - **global** — Universal knowledge useful across any project. Written to `~/.anton/memory/`.
342
+ - **project** — Specific to this workspace. Written to `<project>/.anton/memory/`.
343
+
344
+ **Handler flow** (in `tools.py`):
345
+ 1. `handle_memorize()` receives the tool call input
346
+ 2. Each entry is converted to an `Engram` with `confidence="high"` and `source="user"` (explicit tool calls are trusted)
347
+ 3. The encoding gate is checked per engram — in autopilot/copilot mode, high-confidence entries auto-encode
348
+ 4. Any entries needing confirmation are queued in `session._pending_memory_confirmations`
349
+ 5. The confirmation UI shows before the next user prompt
350
+
351
+ ## Memory Modes — The Encoding Gate
352
+
353
+ Like the Locus Coeruleus-Norepinephrine system that controls how aggressively the brain writes new memories, Anton has three memory modes:
354
+
355
+ | Mode | Behavior | Brain Analog |
356
+ |------|----------|---|
357
+ | **autopilot** (default) | Anton decides what to save, no confirmation | High tonic NE — broad encoding |
358
+ | **copilot** | Auto-save high-confidence memories, confirm ambiguous ones after the answer | Moderate NE — selective encoding |
359
+ | **off** | Never save (still reads existing memory) | Suppressed — encoding blocked |
360
+
361
+ Configure via `/setup` > Memory, or the `ANTON_MEMORY_MODE` environment variable.
362
+
363
+ **The encoding gate logic** (in `cortex.py`):
364
+ ```python
365
+ def encoding_gate(self, engram: Engram) -> bool:
366
+ """Returns True if user confirmation is needed."""
367
+ if self.mode == "autopilot": return False # never confirm
368
+ if self.mode == "off": return False # won't reach encoding anyway
369
+ # copilot: auto-encode high confidence, confirm rest
370
+ return engram.confidence != "high"
371
+ ```
372
+
373
+ **Important design rule:** Memory confirmations are *never* shown during scratchpad execution or while Anton is composing an answer. They only appear after the user has received their full response, right before the next prompt. This ensures memory never interrupts the workflow.
374
+
375
+ **Confirmation UX** (copilot mode, after the answer):
376
+ ```
377
+ Lessons learned from this session:
378
+ 1. [always] Call progress() before long API calls in scratchpad
379
+ 2. [lesson] CoinGecko rate-limits at 50 req/min
380
+
381
+ Save to memory? (y/n/pick numbers): 1
382
+ Saved 1 entries.
383
+ ```
384
+
385
+ ## Consolidation — Learning from Scratchpad Sessions
386
+
387
+ After a scratchpad session ends, the Consolidator runs in the background — like hippocampal replay during sleep.
388
+
389
+ ### When It Triggers
390
+
391
+ The `should_replay()` method uses heuristics (no LLM call) to decide if a session is worth reviewing:
392
+
393
+ | Condition | Why |
394
+ |---|---|
395
+ | Any cell had an error | High-signal learning opportunity — errors are emotional |
396
+ | Session was long (5+ cells) | Rich experience with enough steps to mine patterns |
397
+ | Any cell was cancelled/killed | Something went wrong — worth understanding what |
398
+ | Session had < 2 cells | Skipped — too short to learn from |
399
+
400
+ ### What It Does
401
+
402
+ 1. **Compresses** the cell history into a compact summary — one line per cell with description, status, and first output line. Error cells include a code snippet.
403
+ 2. **Sends** the summary to the fast coding model with a structured extraction prompt.
404
+ 3. **Parses** the JSON response into `Engram` objects with `source="consolidation"`.
405
+ 4. **Routes** through the encoding gate: high-confidence auto-encode, medium-confidence queue for confirmation.
406
+
407
+ ### What the LLM Extracts
408
+
409
+ The consolidation prompt asks for two types of memories:
410
+
411
+ - **Rules**: behavioral patterns
412
+ - "Always call progress() before long API calls in scratchpad"
413
+ - "Never use time.sleep() in scratchpad cells"
414
+ - "If fetching paginated data → use async + progress()"
415
+
416
+ - **Lessons**: factual knowledge
417
+ - "CoinGecko free tier rate-limits at ~50 req/min"
418
+ - "pandas read_csv needs encoding='utf-8-sig' for BOM files"
419
+ - "Bitcoin price data via /coins/bitcoin/market_chart/range"
420
+
421
+ Each extracted memory includes a `scope` (global vs. project) and `confidence` (high vs. medium) so the encoding gate knows how to handle it.
422
+
423
+ ## Identity Extraction — The Default Mode Network
424
+
425
+ Every 5 conversation turns, the Cortex passively checks if the user's message reveals identity-relevant information — like the Default Mode Network monitoring for self-relevant signals.
426
+
427
+ **How it works:**
428
+ 1. A fast LLM call with the user's message and a prompt asking for identity facts
429
+ 2. Returns a JSON array like `["Name: Jorge", "Timezone: PST"]`
430
+ 3. Merges with existing profile: facts with the same key prefix (e.g., `Name:`) are replaced, not duplicated
431
+ 4. Rewrites `~/.anton/memory/profile.md` atomically (exclusive file lock + write `.tmp` + rename)
432
+
433
+ This runs as a background `asyncio.create_task()` — never blocks the conversation. Only fires when `memory_mode != "off"`.
434
+
435
+ ## Compaction — Synaptic Homeostasis
436
+
437
+ When memory files grow past 50 entries, the Cortex triggers compaction at session start — like the Synaptic Homeostasis Hypothesis (Tononi-Cirelli) where sleep prunes overgrown synapses.
438
+
439
+ **Compaction uses the coding model to:**
440
+ 1. Remove exact duplicates
441
+ 2. Merge entries that say the same thing differently (keep the clearest version)
442
+ 3. Remove entries superseded by newer, more specific ones
443
+
444
+ **Safety guarantees:**
445
+ - Rewrite is atomic: write `.tmp`, then `os.rename`
446
+ - Uses exclusive file lock to prevent concurrent compaction
447
+ - If the LLM call fails, compaction is silently skipped — never corrupts existing memory
448
+ - Conservative by default: the prompt tells the model "when in doubt, keep the entry"
449
+
450
+ Compaction runs as a background `asyncio.create_task()` at session start — doesn't block the first user prompt.
451
+
452
+ ## Reconsolidation — Legacy Migration
453
+
454
+ On first run after upgrading, Anton automatically migrates old memory formats:
455
+
456
+ | Legacy Format | Source | Destination |
457
+ |---|---|---|
458
+ | `.anton/context/*.md` | SelfAwarenessContext files | `memory/lessons.md` + `memory/topics/` |
459
+ | `.anton/learnings/*.md` | LearningStore files | `memory/lessons.md` + `memory/topics/` |
460
+
461
+ **Detection** (`needs_reconsolidation()`): runs when old directories exist with files AND new `memory/` directory doesn't have `rules.md`, `lessons.md`, or `profile.md`.
462
+
463
+ **Migration logic:**
464
+ - Context files: each `.md` file becomes a topic. Lines are split, bullets stripped, short fragments (<6 chars) skipped. Source is set to `"user"`.
465
+ - Learning files: the `index.json` is read for topic metadata. Content is split into individual facts. Source is set to `"consolidation"`.
466
+ - Old files are preserved — nothing is deleted.
467
+ - Runs synchronously at startup (fast, no LLM calls needed).
468
+
469
+ ## Procedural Memory — The Skills System
470
+
471
+ Skills are Anton's **procedural memory** — reusable workflows the user has marked as worth remembering. Brain analog: the **striatum** stores motor programs and habits, learned action sequences that fire when a familiar context is recognized. Anton's skill system mirrors this: when the LLM sees a request that matches a stored skill, it pulls the procedure into working memory and follows it instead of reasoning from scratch.
472
+
473
+ Skills are intentionally distinct from engrams. **Engrams hold facts** ("CoinGecko rate-limits at 50 req/min"), are loaded into every prompt unconditionally because they're cheap, and live in `lessons.md` / `rules.md` / `profile.md`. **Skills hold whole procedures** ("how to summarize a CSV end-to-end"), are NOT loaded into every prompt, and the LLM explicitly retrieves them via the `recall_skill` tool when it recognizes a match. Both systems coexist in the brain — declarative and procedural memory are dissociable — and both coexist in Anton.
474
+
475
+ ### Skill Directory Format
476
+
477
+ Each skill is a directory at `~/.anton/skills/<label>/` containing multi-stage representations that coexist (rather than graduating between stages):
478
+
479
+ ```
480
+ ~/.anton/skills/csv_summary/
481
+ ├── meta.json ← label, name, description, when_to_use, provenance, presence flags
482
+ ├── declarative.md ← Stage 1: step-by-step procedure the LLM reads (always present)
483
+ ├── chunks.md ← Stage 2: higher-level recipes/macros (emerges with use, v2+)
484
+ ├── code/ ← Stage 3: runnable helper modules (emerges with reliability, v2+)
485
+ │ └── __init__.py
486
+ ├── requirements.txt ← Stage 3 dependencies (optional)
487
+ └── stats.json ← per-stage usage counters
488
+ ```
489
+
490
+ The three stages mirror the cortico-striatal-cerebellar gradient:
491
+ - **Stage 1 (declarative)** — what the prefrontal cortex reads when first learning a skill. Slow, deliberate, fully flexible.
492
+ - **Stage 2 (chunks)** — chunked sub-procedures (associative striatum). Faster than Stage 1, still LLM-mediated.
493
+ - **Stage 3 (code)** — runnable helpers (sensorimotor striatum). Cheapest, fastest, used when context is highly familiar.
494
+
495
+ The executive picks the highest stage that's reliable enough for the current context. v1 only ships Stage 1; the directory format pre-allocates the other slots so consolidation can fill them later without a migration.
496
+
497
+ ### Naming: `label`, not `slug`
498
+
499
+ Each skill's unique identifier is its `label`. In cognitive psychology, a *label* is the declarative handle by which a procedural memory is addressed in working memory — the verbal token the executive holds when deciding to invoke a stored procedure. It's deliberately distinct from `name` (the human-readable display like "CSV Summary") and `when_to_use` (the retrieval cue describing the matching context).
500
+
501
+ ### How Skills Get Created
502
+
503
+ Skills are created manually in v1 via the `/skill save` command. The user runs it after a successful task; the LLM reads the recent scratchpad cells + chat history and drafts the skill via `LLMClient.generate_object` with a `_SkillDraft` Pydantic schema:
504
+
505
+ ```
506
+ you> Take a quick look at sales_q3.csv
507
+
508
+ anton> [opens scratchpad, loads pandas, infers schema, prints describe(), plots distributions]
509
+ Here's what I found...
510
+
511
+ you> /skill save csv summary
512
+ anton> Drafting a skill from recent work…
513
+ Saved skill csv_summary → ~/.anton/skills/csv_summary/
514
+ Name: CSV Summary
515
+ When to use: User asks to explore, summarize, or describe a CSV file.
516
+ ```
517
+
518
+ Automatic skill extraction (the consolidator promoting recurring scratchpad patterns into skills) is a v2/v3 feature. v1 deliberately uses manual curation to learn what "good" skills look like before automating.
519
+
520
+ ### How Skills Get Used
521
+
522
+ On every turn, the system prompt includes a compact `## Procedural memory` section listing every available skill as one line: `- <label> — <when_to_use>`. The full procedures stay on disk. When the LLM recognizes a match, it calls the `recall_skill` tool:
523
+
524
+ ```
525
+ {"name": "recall_skill", "input": {"label": "csv_summary"}}
526
+ ```
527
+
528
+ The tool reads `declarative.md` and returns it as the tool result, which the LLM follows as guidance for the rest of the turn. Each successful recall increments `stats.json::stage_1::recommended` — that's the classifier signal, mechanically captured without any LLM compliance dance.
529
+
530
+ Brain analog: the prefrontal cortex doesn't keep every skill loaded. It has fast pattern recognition that flags "I might need skill X" and *retrieves* the skill into working memory only when it actually needs it. The `recall_skill` tool is exactly this retrieval operation.
531
+
532
+ ### Skill Slash Commands
533
+
534
+ | Command | What it does |
535
+ |---|---|
536
+ | `/skill save [name hint]` | LLM drafts a new skill from recent work and saves it |
537
+ | `/skills list` (or `/skill list`) | Show all saved skills with usage counters |
538
+ | `/skill show <label>` | Print one skill's procedure + stats (typo-tolerant via closest_match) |
539
+ | `/skill remove <label>` | Delete a skill from disk |
540
+
541
+ ### Typo Recovery
542
+
543
+ When the LLM passes a label that doesn't exist (typos, guesses), `recall_skill` uses `closest_match()` to find the nearest existing slug via difflib and returns that skill's procedure with a warning. The `recommended` counter is credited to the *resolved* label, not the input — so `recall_skill('csv_sumary')` still increments `csv_summary` in the stats. The LLM gets useful behavior even when it gets the spelling wrong.
544
+
545
+ ## Cerebellum — Supervised Error Learning
546
+
547
+ The Cerebellum is Anton's **supervised error-correction system**. It observes every scratchpad cell and learns from the ones that diverge from intent. Brain analog: the cerebellum's classical role is *forward modeling and error correction* — when a motor command is issued, the cerebellum predicts the expected sensory consequences, and when actual feedback arrives, it computes the prediction error and uses it to refine future commands.
548
+
549
+ For Anton, the "motor command" is a scratchpad cell. Before the cell runs, the LLM declares its intent via the `one_line_description` field on the scratchpad tool. That description IS the forward model — the prediction of what the cell should do. After the cell runs, we have its actual outcome (stdout, stderr, error). The Cerebellum compares the two and, when they diverge meaningfully, encodes a generalizable lesson that future code-generating LLM calls will see.
550
+
551
+ ### Decoupling: Hooks Live in the Dispatcher, Not the Runtime
552
+
553
+ The Cerebellum operates via two observer hooks called from the scratchpad tool dispatcher (`handle_scratchpad`), NOT from the runtime backend itself:
554
+
555
+ ```
556
+ handle_scratchpad (orchestration layer)
557
+ ├─ build prelim Cell from tool input
558
+ ├─ FIRE pre-execute observers ──→ Cerebellum.on_pre_execute (counter)
559
+ ├─ pad.execute(code, ...) (pure execution — runtime never sees observers)
560
+ ├─ FIRE post-execute observers ─→ Cerebellum.on_post_execute (buffer if errored)
561
+ └─ return formatted result
562
+ ```
563
+
564
+ This decoupling is intentional. `LocalScratchpadRuntime`, `ScratchpadManager`, and any future `RemoteScratchpadRuntime` are **completely hook-agnostic** — they don't import the Cerebellum, they don't have hook attributes, they never call observers. When a remote runtime backend is added, it inherits zero hook code because there is none to inherit. The orchestration layer is the only place where execution and observation meet.
565
+
566
+ ### Cheap Path
567
+
568
+ Most cells succeed cleanly. The Cerebellum's `on_post_execute` hook checks `cell.error is None and not cell.stderr.strip()` and returns immediately for clean cells — they're never buffered, no LLM call is ever made for them. Only cells that errored or warned trigger the buffer. The cost of running the Cerebellum on a happy-path turn is **zero LLM calls**.
569
+
570
+ ### Batched Per-Turn Diff
571
+
572
+ When errored cells exist, they accumulate in a buffer across the turn. At end-of-turn, `_schedule_cerebellum_flush()` fires `Cerebellum.flush()` as a fire-and-forget background task. The user gets their reply immediately while the diff runs in parallel:
573
+
574
+ 1. The buffered cells get formatted into a compact post-mortem prompt
575
+ 2. One LLM call via `LLMClient.generate_object_code` (the cheap coding model) returns a `_DiffPassResult` Pydantic model with extracted lessons
576
+ 3. Each lesson is wrapped as an `Engram` with `kind="lesson"`, `topic="scratchpad"`, `source="consolidation"`, and routed through `Cortex.encode()` — the same path manual lessons and the consolidator already use
577
+ 4. Future scratchpad cells see those lessons via the existing `recall_scratchpad_wisdom()` injection into the scratchpad tool description
578
+
579
+ The cerebellum is a **producer** only — it generates new lesson entries for the existing storage and retrieval pipeline. There's no parallel storage system, no separate `corrections.md` file. Whatever the consolidator and `/memorize` write to, the cerebellum also writes to.
580
+
581
+ Brain analog: cerebellar plasticity (LTD at parallel-fiber → Purkinje cell synapses) operates in parallel with continued action, never blocking it. Lessons compound silently across turns; future cells avoid traps that earlier cells fell into.
582
+
583
+ ### The Generated Lessons Look Like
584
+
585
+ ```markdown
586
+ - For CSV files with mixed column types, pass low_memory=False to pd.read_csv. <!-- topic:scratchpad source:consolidation ts:2026-04-11 -->
587
+ - Wrap pd.to_datetime() calls in errors='coerce' when the input may contain malformed strings. <!-- topic:scratchpad source:consolidation ts:2026-04-11 -->
588
+ ```
589
+
590
+ These appear in `lessons.md` like any other engram, carry the same metadata, and get pruned by the same compaction loop when memory grows past threshold.
591
+
592
+ ## Anterior Cingulate Cortex — Pattern-Level Error Detection
593
+
594
+ The Cerebellum learns from a single failed cell. The ACC learns from a *pattern across multiple events* within one turn. Brain analog: the anterior cingulate cortex fires the *error-related negativity* (ERN) ~80 ms after the brain notices that an actual outcome diverged from an expected one. That signal flows downstream to the dopaminergic midbrain (reward prediction error), the striatum (action policy update), and the dorsolateral PFC (strategy adjustment).
595
+
596
+ Anton's ACC watches a turn unfold, classifies events as they arrive, and at end-of-turn extracts actionable lessons from patterns that fired more than once. Real failure modes it's designed to catch:
597
+
598
+ - **Scratchpad name switch** — the LLM started in `build_pres`, switched to `write_html`, then `pres1`. Each scratchpad name is a separate isolated environment; variables in one don't exist in another. Burned 8 rounds on recovery in the original session.
599
+ - **Oversized cell drops** — large HTML strings serialised to empty `code` in the tool-call schema. Repeated >5KB cells fail the same way; the LLM didn't realise the schema was clipping it.
600
+ - **Repeated tool error** — the same tool failed three times in a row with the same args (the publish-from-chat bug — three identical failed calls before pivoting).
601
+
602
+ ### Status
603
+
604
+ Implemented at `anton/core/memory/acc.py` with 51 passing tests, plus 14 wiring tests at `tests/test_session_acc_init.py`. Layers 1 and 2 are wired into `ChatSession`:
605
+
606
+ - **Layer 1 — passive learning.** Emit sites fire `acc.observe(...)` at scratchpad/tool/repair/cap-exhaust hooks; `_schedule_acc_flush()` runs at end-of-turn alongside the cerebellum flush. Detected lessons become `Engram` objects whose `kind` (always/never/when) was tagged by the detector, so they flow into the right section of `rules.md`. Future turns pick them up via the existing memory→system-prompt pipeline.
607
+ - **Layer 2 — mid-turn nudges.** `at_round_n()` runs after each tool-call round; when a NEW detector fires (one nudge per detector per turn), the lesson text gets appended as a `text` block inside the same user-role message that carries the round's `tool_result` blocks. The LLM sees the alarm on its very next round, not on the next turn. Off by default — gated on `ANTON_ACC_MODE=active`.
608
+
609
+ Modes (env var `ANTON_ACC_MODE`, mirrors `ANTON_MEMORY_MODE`):
610
+
611
+ | Mode | Behaviour |
612
+ |---|---|
613
+ | `off` | ACC observes nothing — events drop at the safe-emit wrapper. Use to disable the feature entirely. |
614
+ | `passive` (default) | Layer 1 only. Lessons drain to memory at end-of-turn; next turn's system prompt picks them up. Adds zero surface to the turn loop. |
615
+ | `active` | Layer 1 + Layer 2. Lessons ALSO inject inline as text blocks in `tool_results` so the LLM sees them on the very next round. Stronger learning signal; more invasive. |
616
+
617
+ What is NOT yet wired (deliberate):
618
+ - **Layer 3 — retrieval-scored rule ranking.** At system-prompt assembly, score each candidate rule by relevance to the current turn's context and load the top-K within the token budget. Per-rule retrieval counters age out rules that never make the cut. Pairs with optional outcome-tracking (did the rule reduce its target pattern after it landed?). Needs a small embedding index over rules + a ranker call on the load path.
619
+
620
+ ### Vocabulary discipline
621
+
622
+ The ACC enforces a closed event vocabulary via the `EVENT_KINDS` frozenset. `observe()` raises `ValueError` on unknown kinds. A test asserts that every kind in `EVENT_KINDS` is read by at least one detector — the previous wide `KNOWN_PRODUCER_ONLY` allowlist was deliberately collapsed to a single justified entry (`tool_call`, reserved for a future `detect_orphaned_tool_call`). A second test guards against dropped kinds (`context_compaction`, `round_milestone`) silently reappearing.
623
+
624
+ ### Event vocabulary (9 kinds)
625
+
626
+ | Kind | Detail shape | Read by |
627
+ |---|---|---|
628
+ | `scratchpad_call` | `{name, code_len, one_line_description}` | `detect_name_switch`, `detect_oversized_cell` |
629
+ | `scratchpad_result` | `{name, success, stdout_len, error}` | `detect_repeated_error_signature` |
630
+ | `scratchpad_empty_code` | `{name}` | `detect_oversized_cell` |
631
+ | `scratchpad_reset` | `{name, reason}` | `detect_reset_churn` |
632
+ | `scratchpad_killed` | `{name, reason}` | `detect_kill_loop` |
633
+ | `tool_call` | `{name, args_summary}` | *(producer-only — reserved for future `detect_orphaned_tool_call`)* |
634
+ | `tool_result` | `{name, success, error}` | `detect_repeated_tool_error`, `detect_repeated_error_signature` |
635
+ | `history_repair` | `{reason}` | `detect_repair_churn` |
636
+ | `cap_exhausted` | `{}` | `detect_cap_exhausted` |
637
+
638
+ ### Detectors (9 pure functions over the event stream)
639
+
640
+ Detectors are stateless functions of `Sequence[Event] → Lesson | None`. Each detector that fires produces a one-sentence rule. Cross-detector dedupe at `at_end_of_turn()` collapses overlapping lessons.
641
+
642
+ | Detector | Fires when | Cognitive failure it learns from |
643
+ |---|---|---|
644
+ | `detect_name_switch` | ≥2 distinct scratchpad names in one turn | Identity sprawl across scratchpads — each name is a separate venv. |
645
+ | `detect_oversized_cell` | Observed empty-code drops OR ≥2 cells over ~5 KB | Silent schema truncation of large `code` strings. |
646
+ | `detect_repeated_tool_error` | ≥2 consecutive failures of same tool | Blind retry of same tool. |
647
+ | `detect_repeated_error_signature` | Same normalised error signature ≥3 times across any producers | Blind retry across tools / arg tweaks (generalises the publish-from-chat + gmail bug patterns). |
648
+ | `detect_reset_churn` | ≥2 scratchpad resets in one turn | Abandoning state instead of debugging in place. |
649
+ | `detect_kill_loop` | ≥2 cells killed on the same scratchpad name | Writing cells that hang — approach too heavy. |
650
+ | `detect_severity_climb` | Per-producer strictly-increasing severity run of length ≥3 ending ≥5 | Situation deteriorating without strategy change — ERN crossed threshold. |
651
+ | `detect_repair_churn` | ≥3 `history_repair` events in one turn | LLM generating malformed tool_use/result structurally; conversation derailing. |
652
+ | `detect_cap_exhausted` | A single `cap_exhausted` event | Round cap hit → mandatory post-mortem rather than silent retry. |
653
+
654
+ ### Error-signature normalisation
655
+
656
+ `detect_repeated_error_signature` runs each error string through `_normalise_error_signature()` before counting — a cheap regex pass that collapses paths, integers, hex addresses, and short quoted tokens into placeholders. That way `"Refusing to save record for engine='gmail-1'"` and `"Refusing to save record for engine='gmail-2'"` hash to the same signature and the detector catches the underlying loop even when the LLM tweaks args between attempts.
657
+
658
+ ### Producer, not storage
659
+
660
+ Like the cerebellum, the ACC is a *producer*. It does not own storage. Lessons it generates flow into the same Engram pipeline that the cerebellum and consolidator already use. The de-dupe predicate is caller-supplied (`has_similar_lesson`) so the wiring layer can choose substring, embedding, or semantic similarity without changing ACC internals.
661
+
662
+ ## Structured Output — `LLMClient.generate_object`
663
+
664
+ Anton has a single primitive for getting structured data out of the LLM, used by the cerebellum, the consolidator, the cortex's identity/compaction passes, the connect collector, the skill drafter, and the custom-datasource flow. It lives at `anton/llm/client.py`:
665
+
666
+ ```python
667
+ async def generate_object(
668
+ self,
669
+ schema_class, # A Pydantic BaseModel subclass, or list[Model]
670
+ *,
671
+ system: str,
672
+ messages: list[dict],
673
+ max_tokens: int | None = None,
674
+ ):
675
+ """Forced-tool-call structured output via the planning provider."""
676
+ ```
677
+
678
+ There's also a paired `generate_object_code(...)` that uses the cheap *coding* provider — appropriate for fast/cheap structured tasks like the cerebellum's post-mortem and the cortex's identity extraction.
679
+
680
+ ### How It Works
681
+
682
+ 1. The Pydantic model is converted to a JSON schema via `model_json_schema()`
683
+ 2. A synthetic tool is built whose `input_schema` is that JSON schema
684
+ 3. The LLM provider is called with `tool_choice={"type": "tool", "name": tool_name}` — this *forces* the LLM to call the tool rather than returning text
685
+ 4. The tool's input dict is validated through `model_validate()` and returned as a typed instance
686
+
687
+ ### Why It Beats Asking for JSON in Text
688
+
689
+ | Old pattern (text JSON) | New pattern (`generate_object`) |
690
+ |---|---|
691
+ | "Return ONLY valid JSON, no commentary, no markdown fences" | Forced tool_choice — the LLM cannot return text |
692
+ | Manual `json.loads()` with try/except | Pydantic `model_validate()` with structural validation |
693
+ | Strip markdown fences with regex (`_strip_json_fences`) | Never needed — there's no text response to strip |
694
+ | Defensive `if not isinstance(data, dict): return` checks | Pydantic catches type errors at the schema layer |
695
+ | Field-by-field `.get(key, default)` extraction | Typed attribute access on the validated instance |
696
+
697
+ ### The Shared Helper
698
+
699
+ The schema-derivation and validation logic lives in exactly one place — `anton/llm/structured.py` — and is shared by both `LLMClient.generate_object` (main process, async) and `_ScratchpadLLM.generate_object` (subprocess bridge, sync). Two pure helper functions:
700
+
701
+ ```python
702
+ def build_structured_tool(schema_class) -> tuple[dict, type, bool]:
703
+ """Pydantic model → (tool_dict, validator_class, is_list)."""
704
+
705
+ def unwrap_structured_response(tool_call_input, validator_class, is_list):
706
+ """LLM tool call input → validated typed Pydantic instance."""
707
+ ```
708
+
709
+ This pattern is what every extraction call site uses. Adding a new one is mechanical: define a Pydantic model with `Field(description=...)` on each field, call `await session._llm.generate_object(MySchema, ...)`, wrap in try/except for graceful degradation. The field descriptions on the Pydantic model double as the LLM's instructions — there's no separate prompt explaining the schema.
710
+
711
+ ### Where It's Used
712
+
713
+ | Module | Schema | Provider | Purpose |
714
+ |---|---|---|---|
715
+ | `connect_collector.py::extract_variables` | `_ExtractionResult` | planning | Parse free-form credential input into structured fields |
716
+ | `commands/skills.py::handle_skill_save` | `_SkillDraft` | planning | LLM drafts a skill from recent scratchpad work |
717
+ | `commands/datasource.py::handle_add_custom_datasource` | `_CustomDatasourceSpec` | planning | LLM identifies a custom datasource's auth fields |
718
+ | `cortex.py::_compact_file` | `_CompactionResult` | **coding** | Memory deduplication during synaptic homeostasis |
719
+ | `cortex.py::maybe_update_identity` | `_IdentityFacts` | **coding** | Default-mode identity extraction every 5 turns |
720
+ | `consolidator.py::replay_and_extract` | `_ConsolidatedLessons` | **coding** | Sleep-replay extraction of lessons from scratchpad sessions |
721
+ | `cerebellum.py::_run_diff` | `_DiffPassResult` | **coding** | Post-mortem error learning from cell failures |
722
+
723
+ The split between *planning* and *coding* providers preserves the original intent of each call site — anything that previously used `_llm.code()` now uses `generate_object_code` (cheap, fast model), and anything that previously used `_llm.plan()` now uses `generate_object` (planning model).
724
+
725
+ ## Concurrency Safety
726
+
727
+ | Operation | Scope | Strategy |
728
+ |---|---|---|
729
+ | Normal writes (rules, lessons) | Global | `fcntl.flock(LOCK_EX)` on each file — append-only, no read-modify-write race |
730
+ | Normal writes | Project | No locking needed — one session per project |
731
+ | Compaction | Global | Exclusive lock + atomic rename (write `.tmp` then `os.rename`) |
732
+ | Identity updates | Global | Exclusive lock (full rewrite via `.tmp` + rename) |
733
+ | Concurrent compaction | Global | Other sessions skip — only one "sleeps" at a time |
734
+
735
+ ## The Engram — Fundamental Unit of Memory
736
+
737
+ Every memory trace is represented as an `Engram` dataclass, defined in `anton/core/memory/base.py`:
738
+
739
+ ```python
740
+ @dataclass
741
+ class Engram:
742
+ text: str # The memory content
743
+ kind: "always" | "never" | "when" | "lesson" | "profile" # Classification
744
+ scope: "global" | "project" # Where to store it
745
+ confidence: "high" | "medium" | "low" = "medium" # Encoding gate signal
746
+ topic: str = "" # For lessons — topic slug
747
+ source: "user" | "consolidation" | "llm" = "llm" # Origin of the memory
748
+ ```
749
+
750
+ Named for Karl Lashley's *engram* — the hypothesized physical substrate of a memory trace. Each engram flows through the system:
751
+
752
+ ```
753
+ Source (user/LLM/consolidation/cerebellum/ACC)
754
+ → Engram created
755
+ → Cortex.encoding_gate() — needs confirmation?
756
+ → yes: queued for user review before next prompt
757
+ → no: Cortex.encode() → routes to correct Hippocampus by scope
758
+ → Hippocampus writes to disk with file locking
759
+ → profile: full rewrite (atomic)
760
+ → rule: insert into correct section of rules.md
761
+ → lesson: append to lessons.md + optionally topics/{slug}.md
762
+ ```
763
+
764
+ ### `HippocampusProtocol` — the storage interface
765
+
766
+ `base.py` also defines a `HippocampusProtocol` — a `runtime_checkable` structural `typing.Protocol` describing the public contract of a single-scope memory store (the `recall_*` and `encode_*` methods listed in the next section). The concrete file-backed `Hippocampus` in `hippocampus.py` satisfies it automatically via structural sub-typing. The protocol exists so alternate backends (database-backed, cloud-synced) can be substituted without inheriting from the file-based implementation. This is the seam Enterprise adapters plug into.
767
+
768
+ ## Module Reference
769
+
770
+ The long-term memory system lives under `anton/core/memory/`. A small set of legacy / orthogonal modules still lives at the top level under `anton/memory/`.
771
+
772
+ ```
773
+ anton/core/memory/ LONG-TERM MEMORY (brain-mapped modules)
774
+ ├── base.py Engram dataclass + HippocampusProtocol (structural backend interface)
775
+ ├── hippocampus.py Hippocampus class — file-backed implementation of the protocol
776
+ ├── cortex.py Cortex class (executive declarative-memory coordinator)
777
+ ├── episodes.py Episode + EpisodicMemory class
778
+ ├── consolidator.py Consolidator class (sleep-replay → Engrams)
779
+ ├── cerebellum.py Cerebellum class (per-cell supervised error learning)
780
+ ├── acc.py AnteriorCingulate class (turn-level pattern error detection)
781
+ └── skills.py Skill, SkillStore, SkillStats — procedural memory storage layer
782
+
783
+ anton/memory/ LEGACY / ORTHOGONAL (not the brain-mapped memory system)
784
+ ├── reconsolidator.py needs_reconsolidation() + reconsolidate() — one-time format migration
785
+ ├── manage.py MemoryManage class — handlers for /memory and /setup > Memory, MEMORY_MODES dict
786
+ ├── history_store.py HistoryStore — chat session persistence (transcripts on disk)
787
+ ├── store.py SessionStore — session list / metadata (different from history_store)
788
+ └── learnings.py [legacy] LearningStore — pre-Hippocampus format, kept only for migration
789
+
790
+ anton/core/llm/
791
+ ├── client.py LLMClient with plan/code/generate_object/generate_object_code
792
+ ├── structured.py build_structured_tool + unwrap_structured_response (shared helper)
793
+ └── ... anthropic.py, openai.py, provider.py, prompt_builder.py, prompts.py
794
+
795
+ anton/core/tools/
796
+ ├── recall_skill.py RECALL_SKILL_TOOL — the LLM's procedural memory retrieval primitive
797
+ ├── tool_handlers.py handle_scratchpad with pre/post-execute observer firing
798
+ └── ... registry.py, tool_defs.py
799
+ ```
800
+
801
+ ### `base.py` — Engram + HippocampusProtocol
802
+
803
+ `Engram` is the fundamental memory-trace dataclass (see "The Engram" section above). `HippocampusProtocol` is a `runtime_checkable` structural Protocol defining the read/write contract of a single-scope memory store. Alternate backends (Enterprise, cloud-synced, database-backed) satisfy the protocol by shape — no inheritance from the file-based class needed.
804
+
805
+ ### `hippocampus.py` — Storage Engine
806
+
807
+ The Hippocampus handles one scope (global OR project) and is the canonical file-backed implementation of `HippocampusProtocol`. It doesn't decide what to remember — it just reads and writes.
808
+
809
+ **Retrieval methods:**
810
+ | Method | Reads | Brain Analog |
811
+ |---|---|---|
812
+ | `recall_identity()` | `profile.md` | Medial PFC / Default Mode Network |
813
+ | `recall_rules()` | `rules.md` | Basal Ganglia + OFC |
814
+ | `recall_lessons(token_budget)` | `lessons.md` (budget-limited, most recent first) | Anterior Temporal Lobe |
815
+ | `recall_topic(slug)` | `topics/{slug}.md` | Cortical Association Areas |
816
+ | `recall_scratchpad_wisdom()` | "when" rules + scratchpad-related lessons + `topics/scratchpad-*.md` | Procedural memory |
817
+
818
+ **Encoding methods:**
819
+ | Method | Writes | Behavior |
820
+ |---|---|---|
821
+ | `encode_rule(text, kind, confidence, source)` | `rules.md` under correct `## Always/Never/When` section | Deduplicates. Uses file lock. |
822
+ | `encode_lesson(text, topic, source)` | `lessons.md` + optionally `topics/{slug}.md` | Deduplicates. Append-only with lock. |
823
+ | `rewrite_identity(entries)` | `profile.md` | Full rewrite (atomic via `.tmp` + rename). |
824
+
825
+ ### `cortex.py` — Executive Coordinator
826
+
827
+ The Cortex manages two Hippocampus instances and orchestrates all declarative memory operations. It is also the encoding endpoint that the cerebellum and consolidator route their generated lessons through.
828
+
829
+ | Method | Purpose |
830
+ |---|---|
831
+ | `build_memory_context()` | Assemble memories for system prompt injection (~5800 token budget) |
832
+ | `get_scratchpad_context()` | Combine scratchpad wisdom from both scopes for tool description injection. **This is the channel the cerebellum's lessons flow through into future code generation.** |
833
+ | `encode(engrams)` | Route engrams to correct hippocampus by scope. Returns action log. Called by `/memorize`, the consolidator, and the cerebellum. |
834
+ | `encoding_gate(engram)` | Check if an engram needs user confirmation (mode-dependent) |
835
+ | `needs_compaction()` | Check if any file exceeds the threshold |
836
+ | `compact_all()` | LLM-assisted deduplication + merge on all oversized files. Uses `generate_object_code(_CompactionResult, ...)`. |
837
+ | `maybe_update_identity(message)` | Extract identity facts from user message via `generate_object_code(_IdentityFacts, ...)`. Background, fires every 5 turns. |
838
+
839
+ ### `episodes.py` — Episodic Memory
840
+
841
+ The EpisodicMemory handles raw conversation logging and recall.
842
+
843
+ | Method | Purpose |
844
+ |---|---|
845
+ | `start_session()` | Create a new JSONL file, return session ID |
846
+ | `log(episode)` | Append an Episode to the current session file (fire-and-forget) |
847
+ | `log_turn(turn, role, content, **meta)` | Convenience wrapper — builds Episode and calls log() |
848
+ | `recall(query, max_results, days_back)` | Search all JSONL files for matching episodes (newest first) |
849
+ | `recall_formatted(query, **kwargs)` | Return human-readable string of matching episodes |
850
+ | `session_count()` | Count the number of session JSONL files |
851
+
852
+ ### `consolidator.py` — Scratchpad Replay
853
+
854
+ | Method | Purpose |
855
+ |---|---|
856
+ | `should_replay(cells)` | Heuristic check: errors, 5+ cells, or cancellations → True |
857
+ | `replay_and_extract(cells, llm)` | Compress cells → `generate_object_code(_ConsolidatedLessons, ...)` → return Engrams |
858
+
859
+ ### `cerebellum.py` — Supervised Error Learning (per-cell)
860
+
861
+ | Method | Purpose |
862
+ |---|---|
863
+ | `on_pre_execute(cell)` | Pre-execute hook called by `handle_scratchpad`. Counter only in v1. |
864
+ | `on_post_execute(cell)` | Post-execute hook. Cheap path skips clean cells; errored/warning cells get buffered. |
865
+ | `flush()` | Run the batched diff pass on all buffered cells, encode lessons via Cortex, clear buffer. Fire-and-forget at end-of-turn. |
866
+ | `reset()` | Drop the buffer without encoding (used when a turn is cancelled mid-flight). |
867
+ | `buffered_count` | Number of cells waiting for the next flush. |
868
+ | `_run_diff(cells)` | Internal: send buffered cells to `generate_object_code(_DiffPassResult, ...)` and return validated lessons. |
869
+ | `_encode_lessons(lessons)` | Internal: wrap lessons as Engrams (`kind="lesson"`, `topic="scratchpad"`, `source="consolidation"`) and route through `Cortex.encode()`. |
870
+
871
+ ### `acc.py` — Anterior Cingulate Cortex (turn-level)
872
+
873
+ Pattern-level error detection across a turn. Pure detectors as free functions plus a small stateful `AnteriorCingulate` that holds the event stream.
874
+
875
+ | Element | Purpose |
876
+ |---|---|
877
+ | `Event` | Dataclass with `kind`, `severity`, `detail`, `round_idx`. The atomic observation. |
878
+ | `Lesson` | Dataclass with `rule`, `triggers`, `detector`. What a detector emits. |
879
+ | `EVENT_KINDS` | Frozenset of 9 canonical event-kind strings. `observe()` rejects unknown kinds. |
880
+ | `DETECTORS` | Tuple of 9 pure detector functions — see the table in the ACC section above. |
881
+ | `AnteriorCingulate.observe(kind, ...)` | Append an `Event`. Rejects unknown kinds. |
882
+ | `AnteriorCingulate.at_end_of_turn(has_similar_lesson=...)` | Run all detectors, dedupe via caller-supplied predicate, return new `Lesson` list. |
883
+ | `AnteriorCingulate.clear()` | Drop the event stream (between turns). |
884
+ | `AnteriorCingulate.events` / `event_kind_counts` | Read-only views for inspection and testing. |
885
+
886
+ Tests live at `tests/test_acc.py` (44 tests, 4 layers: pure-function detectors → state tests → JSON-fixture replay → vocabulary discipline). Fixtures at `tests/fixtures/acc/{name_switch,oversized_cell,publish_failure_loop,reset_churn,kill_loop}.json`.
887
+
888
+ ### `skills.py` — Procedural Memory Store
889
+
890
+ | Method | Purpose |
891
+ |---|---|
892
+ | `SkillStore.list_all()` | Return every loadable skill, sorted by label. |
893
+ | `SkillStore.list_summaries()` | Lightweight listing — `[{"label": "...", "name": "...", "when_to_use": "..."}]`. Used by the prompt builder to inject the procedural-memory section without loading any declarative content. |
894
+ | `SkillStore.load(label)` | Read a single skill by label. Returns None if absent or malformed. |
895
+ | `SkillStore.save(skill)` | Write the skill directory. Creates `meta.json`, `declarative.md`, `stats.json`. Never wipes accumulated counters. |
896
+ | `SkillStore.delete(label)` | Remove a skill directory. |
897
+ | `SkillStore.increment_recommended(label, *, stage)` | Atomic-ish bump of the per-stage `recommended` counter (called by `recall_skill`). |
898
+ | `SkillStore.closest_match(bad_label, *, cutoff=0.6)` | Difflib-based fuzzy match for typo recovery. |
899
+ | `make_unique_label(base, store)` | Generate a slug that doesn't collide with any existing skill (`csv_summary`, `csv_summary_2`, ...). |
900
+ | `slugify(text)` | Normalize arbitrary text into a snake_case identifier. |
901
+
902
+ ### `tools/recall_skill.py` — Procedural Memory Retrieval Tool
903
+
904
+ The LLM-facing tool that pulls a skill into working memory. Lives alongside the other tool defs but is the only tool whose handler reads `session._skill_store`.
905
+
906
+ | Element | Purpose |
907
+ |---|---|
908
+ | `RECALL_SKILL_TOOL` | The `ToolDef` registered with the session — name, description, input_schema, handler. |
909
+ | `handle_recall_skill(session, tc_input)` | Resolve the label (with closest_match fallback for typos), increment the per-stage `recommended` counter, return a formatted procedure to the LLM as the tool result. |
910
+
911
+ ### `llm/structured.py` — Shared Schema Helper
912
+
913
+ Two pure helper functions for forced-tool-call structured output. Used by both `LLMClient.generate_object` (main process, async) and `_ScratchpadLLM.generate_object` (subprocess bridge, sync) — they share this code via lazy imports so neither runtime forces pydantic at module load time.
914
+
915
+ | Function | Purpose |
916
+ |---|---|
917
+ | `build_structured_tool(schema_class)` | Pydantic model (or `list[Model]`) → `(tool_dict, validator_class, is_list)`. The `tool_dict` is ready to pass as `tools=[...]` with `tool_choice={"type": "tool", "name": tool_dict["name"]}`. |
918
+ | `unwrap_structured_response(tool_call_input, validator_class, is_list)` | Validate the LLM's tool call input via Pydantic and unwrap the wrapper if it was a list. Raises `pydantic.ValidationError` on schema drift. |
919
+
920
+ ### `reconsolidator.py` — Legacy Migration
921
+
922
+ | Function | Purpose |
923
+ |---|---|
924
+ | `needs_reconsolidation(project_dir)` | Check if old formats exist and new ones don't |
925
+ | `reconsolidate(project_dir)` | Migrate `.anton/context/` and `.anton/learnings/` → `.anton/memory/` |
926
+
927
+ ## Integration Points in chat.py
928
+
929
+ The memory + skills + cerebellum + ACC systems are wired into `ChatSession` (defined in `anton/chat.py` for the CLI entry-points; the runtime class actually lives in `anton/core/session.py`, with chat-loop wiring at the top level in `anton/chat.py`). The cerebellum wires via the dispatcher's scratchpad observer list; the ACC wires via direct `session._acc.observe(...)` calls at each emit site (broader emit footprint than scratchpad alone — also tools, history-repair, round-cap).
930
+
931
+ ### ACC emit sites (Layer 1)
932
+
933
+ | Event kind | Emit site | File |
934
+ |---|---|---|
935
+ | `scratchpad_call` | After args validation in `handle_scratchpad` exec branch | `core/tools/tool_handlers.py` |
936
+ | `scratchpad_result` | After `pad.execute()` returns a non-killed cell | `core/tools/tool_handlers.py` |
937
+ | `scratchpad_empty_code` | When `prepare_scratchpad_exec` rejects the call | `core/tools/tool_handlers.py` |
938
+ | `scratchpad_reset` | After `pad.reset()` in the reset action | `core/tools/tool_handlers.py` |
939
+ | `scratchpad_killed` | After `pad.execute()` returns a cell whose `error` starts with `Cancelled`/`Cell timed out`/`Cell killed` | `core/tools/tool_handlers.py` |
940
+ | `tool_call` | At top of per-tc loop in `_stream_and_handle_tools` | `core/session.py` |
941
+ | `tool_result` | After result_text is finalized, before `tool_results.append` | `core/session.py` |
942
+ | `history_repair` | After `_seal_dangling_tool_uses` actually inserts synthetic blocks | `core/session.py` |
943
+ | `cap_exhausted` | When `tool_round > self._max_tool_rounds` | `core/session.py` |
944
+
945
+ ### End-of-turn drain
946
+
947
+ `_schedule_acc_flush()` lives next to `_schedule_cerebellum_flush()` and runs at the same two spots — end of `turn()` and end of `turn_stream()`. Fire-and-forget: detectors are pure (no LLM call) and the only async work is `cortex.encode()`, but we still wrap the encode in `asyncio.create_task` so file I/O doesn't block the user-facing reply.
948
+
949
+ Each `Lesson` becomes an `Engram(text=rule, kind=lesson.kind, scope="global", confidence="high", source="consolidation")`. The `kind` is whatever the detector tagged (`always`/`never`/`when`) — no string-matching at the wiring layer. Lessons land in `~/.anton/memory/rules.md` under the corresponding `## Always` / `## Never` / `## When` section, and the next turn's system prompt picks them up via the existing `cortex.build_memory_context()` pipeline.
950
+
951
+ ### Mid-turn nudge (`_acc_maybe_nudge`)
952
+
953
+ Called by `_stream_and_handle_tools` immediately after `tool_results` is built and before that user message gets appended to history. When `ANTON_ACC_MODE == "active"`:
954
+
955
+ 1. Runs `acc.at_round_n()` — re-evaluates every detector against the events buffered so far this turn and returns only lessons whose detector hasn't already nudged this turn.
956
+ 2. For each newly-fired lesson, appends `{"type": "text", "text": "[Anton self-check — <detector>] <rule>"}` to the `tool_results` content array.
957
+ 3. The LLM sees those text blocks alongside the tool_result blocks on its very next round.
958
+
959
+ One nudge per detector per turn — re-stating the same alarm round after round would inflate history without changing behaviour. Cleared on the same `clear()` boundary the event buffer uses. The mid-turn path deliberately does NOT consult `has_similar_lesson`: if a rule is already in `rules.md` but the LLM is violating it right now, repeating the rule inline is the whole point.
960
+
961
+ ### `_acc_observe` safe-emit wrapper
962
+
963
+ Every emit site calls `session._acc_observe(kind, detail, ...)` rather than touching `session._acc` directly. This wrapper:
964
+ - Returns silently when the ACC isn't attached (defensive).
965
+ - Returns silently when the cortex is disabled (`mode == "off"`) — observation without persistence is pointless.
966
+ - Catches `ValueError` from `observe()` on unknown kinds so emit-site drift never breaks a turn.
967
+
968
+ ### De-dupe predicate
969
+
970
+ The ACC is constructed with `has_similar_lesson=_acc_has_similar`, a closure that does a cheap substring match against the current `rules.md` content. Prevents the same lesson being re-encoded on every turn. Embedding similarity is a v2 upgrade.
971
+
972
+ ```
973
+ 1. _chat_loop() startup:
974
+ → Creates Cortex(global_hc=Hippocampus(global_dir), project_hc=Hippocampus(project_dir), mode, llm)
975
+ → Creates EpisodicMemory(episodes_dir, enabled=settings.episodic_memory)
976
+ → Starts episodic session if enabled
977
+ → Runs reconsolidation if needed
978
+ → Fires background compaction if needed
979
+
980
+ 2. ChatSession.__init__():
981
+ → Stores cortex as self._cortex
982
+ → Stores episodic as self._episodic
983
+ → Initializes self._skill_store = SkillStore() (procedural memory)
984
+ → Initializes self._cerebellum = Cerebellum(cortex=self._cortex, llm=self._llm)
985
+ → Initializes self._scratchpad_observers = [self._cerebellum]
986
+ → Initializes self._pending_memory_confirmations = []
987
+
988
+ 3. ChatSession._build_system_prompt():
989
+ → Calls cortex.build_memory_context() → injected before anton.md
990
+ → Passes self._skill_store to prompt builder
991
+ → Builder appends "## Procedural memory" section listing all available skills
992
+
993
+ 4. ChatSession._build_tools():
994
+ → Calls cortex.get_scratchpad_context() → appended to scratchpad tool desc
995
+ → Includes MEMORIZE_TOOL in tool list
996
+ → Includes RECALL_TOOL when episodic memory is enabled
997
+ → Includes RECALL_SKILL_TOOL (always available — no-op if no skills saved)
998
+
999
+ 5. Tool dispatch (tools.py):
1000
+ → "memorize" → handle_memorize() → cortex.encode()
1001
+ → "recall" → handle_recall() → episodic.recall_formatted()
1002
+ → "recall_skill" → handle_recall_skill() → SkillStore.load() + increment_recommended()
1003
+ → "scratchpad" exec → handle_scratchpad() fires pre/post observers around pad.execute()
1004
+
1005
+ 6. handle_scratchpad (tool_handlers.py) — observer dispatch:
1006
+ → Build prelim Cell from tool input (code + description + estimated_time)
1007
+ → _fire_pre_execute(session, prelim_cell) → cerebellum.on_pre_execute (counter)
1008
+ → pad.execute(...) — pure execution, runtime never sees observers
1009
+ → _fire_post_execute(session, cell) → cerebellum.on_post_execute (buffer if errored)
1010
+
1011
+ 7. turn_stream():
1012
+ → Logs user input to episodic memory (before LLM call)
1013
+ → Logs assistant response to episodic memory (after LLM call)
1014
+
1015
+ 8. _stream_and_handle_tools() tool loop:
1016
+ → Logs each tool_call to episodic memory
1017
+ → Logs each tool_result to episodic memory
1018
+ → Logs scratchpad cell output to episodic memory
1019
+ → _maybe_consolidate_scratchpads() → background asyncio.create_task
1020
+
1021
+ 9. End of turn (turn / turn_stream):
1022
+ → Every 5 turns → cortex.maybe_update_identity() as background task
1023
+ → _schedule_cerebellum_flush() → fire-and-forget background task
1024
+ → Runs cerebellum diff on all buffered cells
1025
+ → Encodes extracted lessons via cortex.encode()
1026
+ → Lessons appear in next turn's scratchpad tool description automatically
1027
+
1028
+ 10. Before user prompt (_chat_loop):
1029
+ → Show pending memory confirmations → user approves/rejects/picks
1030
+
1031
+ 11. Slash commands for skills (chat.py):
1032
+ → /skill save [name hint] → handle_skill_save() → drafts via generate_object → SkillStore.save()
1033
+ → /skills or /skill list → handle_skills_list() → tabular display of skills + counters
1034
+ → /skill show <label> → handle_skill_show() → full procedure + stats
1035
+ → /skill remove <label> → handle_skill_remove() → SkillStore.delete()
1036
+
1037
+ 12. /setup wizard (sub-menu):
1038
+ → Option 1: Models — provider, API key, planning & coding models
1039
+ → Option 2: Memory — memory mode (autopilot/copilot/off) + episodic toggle
1040
+ → Persisted to ANTON_MEMORY_MODE and ANTON_EPISODIC_MEMORY in .anton/.env
1041
+
1042
+ 13. /memory (read-only dashboard):
1043
+ → Shows semantic memory counts (global/project rules, lessons, topics)
1044
+ → Shows episodic memory status (ON/OFF) and session count
1045
+ → No configuration prompts — directs to /setup > Memory
1046
+
1047
+ 14. _rebuild_session():
1048
+ → Updates cortex._llm and cortex.mode when settings change
1049
+ → Propagates episodic memory instance
1050
+ → Re-creates cerebellum if llm or cortex changed
1051
+ ```
1052
+
1053
+ ## Context Budget Summary
1054
+
1055
+ | Section | Brain Analog | Budget | Loaded When |
1056
+ |---------|---|--------|-------------|
1057
+ | Identity | mPFC / DMN | ~300 tokens | Always (system prompt) |
1058
+ | Global rules | Basal Ganglia | ~1500 tokens | Always (system prompt) |
1059
+ | Project rules | Basal Ganglia | ~1500 tokens | Always (system prompt) |
1060
+ | Global lessons | ATL semantics | ~1000 tokens | Always (most recent first) |
1061
+ | Project lessons | ATL semantics | ~1000 tokens | Always (most recent first) |
1062
+ | Scratchpad wisdom | Procedural priming | ~500 tokens | Scratchpad active (tool desc). Cerebellum-generated lessons flow through here. |
1063
+ | Procedural memory list | Striatum (skill labels) | ~50 tokens per skill (compact list) | Always — when any skills are saved. Full procedures NOT loaded; only labels + when_to_use. |
1064
+ | Topic files | Cortical association | Unlimited | On demand |
1065
+ | Skill procedures | Striatum (full skills) | Variable per skill | On demand (`recall_skill` tool) — only when the LLM recognizes a match |
1066
+ | Episodic recall | MTL episodic | Variable | On demand (`recall` tool) |
1067
+ | **Total in prompt** | **Working memory** | **~5800 tokens + ~50/skill** | ~3% of 200K context |
1068
+
1069
+ The procedural memory list scales linearly with the number of saved skills but stays cheap (~50 tokens each — slug + one-line `when_to_use`). The full skill procedures are *paid for only when retrieved*, the same way the prefrontal cortex doesn't keep every procedural memory loaded — it has fast pattern recognition that flags relevance and pulls the full procedure from storage on demand.