File size: 7,130 Bytes
b308f46
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# AI Memory Compaction

This document explains how AI agent memory compaction works, how to inspect it,
what happened in the latest checked session, and what can be improved next.

## Goal

Each AI agent writes a `note_to_self` after decisions. If we keep every note in
the prompt forever, memory grows quickly and wastes tokens. Memory compaction
keeps a short long-term summary and preserves only the newest notes verbatim.

Expected behavior:

- When an agent reaches 10 entries in `memory_history`, compaction is triggered.
- The newest 2 notes are kept exactly as they are.
- Older notes are summarized together with relevant recent chat.
- The summary is stored as `long_term_summary`.
- Future prompts include `long_term_summary`, `note_from_last_turn`, and the
  remaining `recent_notes`.

## Configuration

The settings live in `pycatan/ai/config.py` and `pycatan/ai/config_dev.yaml`:

```yaml
memory:
  enable_memory_compaction: true
  memory_compaction_threshold: 10
  memory_compaction_keep_recent: 2
  memory_compaction_chat_messages: 20
  memory_compaction_max_tokens: 800
```

`play_ai_auto.bat` runs `examples/ai_testing/play_with_ai.py --auto`, which
loads `pycatan/ai/config_dev.yaml` by default. In normal auto mode, compaction is
enabled.

Compaction is skipped when LLM calls are disabled, for example in `--no-llm` or
watch-only replay mode.

## Code Flow

The main flow is:

1. The model returns a response with `note_to_self`.
2. `AgentState.update_memory()` appends the note to `memory_history`.
3. `AIManager._maybe_compact_agent_memory()` checks the threshold.
4. `AIManager._normalize_compaction_game_state()` ensures the state is in the
   compact prompt format.
5. `MemoryCompactor` builds and sends a compaction prompt.
6. The same active `llm_client` and model are used, without tools.
7. On success:
   - `AgentState.apply_memory_compaction()` stores `compacted_memory`.
   - `memory_history` is reduced to the newest 2 notes.
   - `agent_memories.json` is updated.
   - before/after inspection artifacts are written.

Important files:

- `pycatan/ai/agent_state.py`
- `pycatan/ai/ai_manager.py`
- `pycatan/ai/memory_compactor.py`
- `pycatan/ai/ai_logger.py`

## Board Format

Compaction uses the same compact board format as normal decision prompts. It
does not use a separate prose board summary.

The compaction prompt receives:

- `H`: hex lookup array by HexID.
- `N`: node lookup array by NodeID.
- `state.bld`: current settlements/cities.
- `state.rds`: current roads.
- `players`: compact player data.
- `meta`: current player, phase, robber, dice.

This keeps compaction aligned with the prompt format the model already uses for
game decisions, and it keeps token usage lower.

## Inspecting Before And After

When compaction succeeds, artifacts are written under the player directory:

```text
examples/ai_testing/my_games/session_YYYYMMDD_HHMMSS/<Player>/memory_compactions/
```

Files:

```text
compaction_1.txt
compaction_1.json
```

The text artifact is easiest to read. It contains:

- `BEFORE: Existing Long-Term Summary`
- `BEFORE: Old Notes Compacted`
- `BEFORE: Recent Notes Kept Verbatim`
- `BEFORE: Relevant Chat Considered`
- `AFTER: New Long-Term Summary`
- `AFTER: Discarded As Irrelevant`

The JSON artifact also includes the full compaction prompt.

You can also inspect:

```text
examples/ai_testing/my_games/session_YYYYMMDD_HHMMSS/agent_memories.json
```

Healthy output should look like:

```json
{
  "long_term_summary": "...",
  "recent_notes": [
    {"note": "..."},
    {"note": "..."}
  ],
  "compaction_count": 1
}
```

The communication log should include:

```text
[MEMORY] Compacting memory for ...
[MEMORY] Memory compacted for ...
```

## Latest Session Check

Checked session:

```text
examples/ai_testing/my_games/session_20260516_020509
```

Compaction did trigger, but it did not work as expected in that run.

Example from `llm_communication.log`:

```text
[02:05:12] [MEMORY] Compacting memory for Hadar (10 notes)
[02:05:15] [ERROR] Memory compaction failed for Hadar: Object of type GameState is not JSON serializable
```

Later attempts reached the model but still did not produce usable summaries:

```text
[MEMORY] Compacting memory for Ziv (26 notes)
[WARNING] Memory compaction produced no usable summary for Ziv
```

The final `agent_memories.json` confirmed that no compaction was applied:

```text
Hadar: compaction_count=0, long_term_summary=null, recent_notes=40
Shon:  compaction_count=0, long_term_summary=null, recent_notes=39
Ziv:   compaction_count=0, long_term_summary=null, recent_notes=34
```

That is unhealthy: after successful compaction, `recent_notes` should drop back
to about 2 and `long_term_summary` should be populated.

## Fix Applied After This Check

The main bug was in replay memory handling. In that path, compaction received a
raw `GameState` object instead of the compact dict used by normal prompts. That
made JSON serialization fail.

The fix:

- `AIManager._normalize_compaction_game_state()` now normalizes any state before
  compaction.
- If the state already has `H/N/state/players/meta`, it is used as-is.
- Otherwise it is converted through `game_state_to_dict()` and
  `optimize_state_for_ai()`.

The compaction response parser was also made more tolerant:

- Plain JSON is accepted.
- JSON wrapped in a fenced code block is accepted.
- If needed, the parser attempts to extract the first JSON object from the text.

## Example Of Healthy Compaction

Before compaction:

```text
memory_history = [note1, note2, ..., note10]
```

After compaction:

```text
long_term_summary = "Hadar leads with 4 VP and strong wood/brick access. Shon needs wood+brick for node 18. Ziv needs to clear the robber from Ore 5..."
recent_notes = [note9, note10]
compaction_count = 1
```

The next prompt should include:

```json
"memory": {
  "note_from_last_turn": "...",
  "recent_notes": ["...", "..."],
  "long_term_summary": "..."
}
```

## Improvement Ideas

1. Add cooldown/backoff after compaction failure so the system does not retry on
   every new note.
2. Write failure artifacts too, including the compaction prompt and raw model
   response.
3. Add an integration test that creates 10 notes and asserts:
   - `compaction_count` increments.
   - `long_term_summary` is populated.
   - `memory_history` shrinks to 2 entries.
4. Add an explicit character budget to the compaction prompt, not only "about
   50%".
5. Show compaction status in the web viewer: last success/failure, count, and
   long-term summary.
6. Include pending trades and recent trade history in compaction input, not only
   chat and notes.

## Health Checklist

Healthy:

- `compaction_count` increases.
- `long_term_summary` is not null.
- `recent_notes` stays near 2 after compaction.
- `memory_compactions/compaction_N.txt` exists.
- `llm_communication.log` includes `Memory compacted for ...`.

Unhealthy:

- `compaction_count=0` despite more than 10 notes.
- `recent_notes` grows into dozens of entries.
- No `memory_compactions` directory exists.
- `llm_communication.log` shows repeated `ERROR` or `WARNING` entries for
  compaction.