# How-To: Working with the Memory System
The memory tiers, how they get filled, how they reach the model, and what is
not wired up yet. Sources: `src/agentic/memory.rs`, `memory_store.rs`,
`memory_coordinator.rs`, `tools/memory.rs`.
---
## 1. The three tiers
| **Working** | `SessionRecord::WorkingMemory { facts, version, .. }` | Curated facts (`MemoryFact`) | The `memory` tool, or `update_working_memory` |
| **Observation** | `SessionRecord::Observation { observations, token_count, .. }` | Time-scoped `ObservationEntry`s | `persist_observation` (yours to call) |
| **Reflection** | `SessionRecord::Reflection { reflections, .. }` | Condensed `ReflectionEntry`s | `persist_reflection` (yours to call) |
Each tier keeps only its **latest** record; writing replaces the previous one.
## 2. What is automatic today
| Rolling token counter (`TokenLedger::rolling`) | ✅ updated after every generation |
| Trigger check after each inner round (`check_triggers`) | ✅ |
| Signal when a threshold is crossed | ✅ `AgentProgress::ProcessingMemory { kind }` |
| Calling a memory model to write the observation / reflection | ❌ not implemented |
| Working memory curated by the agent | ✅ via the `memory` tool |
| Tiers included in the assembled context | ✅ |
So with only defaults, the observation and reflection tiers stay empty. If
you want them, watch for `ProcessingMemory`, generate the entries yourself
(e.g. with a cheap model through `agent.router()`), and persist them:
```rust
let hierarchy = agent.memory_hierarchy();
if hierarchy.begin_generation() { // prevents re-triggering meanwhile
let entries: Vec<ObservationEntry> = summarise_with_my_model(&recent)?;
futures_lite::future::block_on(hierarchy.persist_observation(entries, token_count))?;
hierarchy.end_generation();
}
```
`persist_observation` resets the rolling counter and adds `token_count` to the
observation counter; `persist_reflection` resets the observation counter.
## 3. Triggers
```rust
let agent = builder
.with_memory_config(MemoryConfig {
observation_trigger_tokens: 30_000, // default
reflection_trigger_tokens: 40_000, // default
..MemoryConfig::default()
})
.build()?;
```
`check_triggers()` returns:
- `None` while a generation is marked in progress (`begin_generation`)
- `GenerateObservation` when rolling tokens ≥ `observation_trigger_tokens`
- `GenerateReflection` when observation tokens ≥ `reflection_trigger_tokens`
- otherwise `None`
`MemoryConfig::memory_model` and `parse_strategy` are stored for the future
memory generator; nothing reads them yet.
## 4. The `memory` tool
One multi-command tool, `memory`, with `add`, `remove` and `replace` commands
over working-memory facts. Each command loads the latest working memory,
edits the fact list and writes it back with a version bump.
```rust
use foundation_ai::agentic::ToolShed;
use foundation_ai::harness::ToolPreset;
// Over the session's own memory hierarchy, built inside build():
let agent = AgentSession::builder(router)
.with_model("my-model")
.with_toolshed(ToolShed::new().tools(ToolPreset::session_memory()))
.build()?;
```
`ToolPreset::memory(hierarchy)` builds the same tool over a hierarchy you
pass in.
## 5. Storage
`MemoryStore` is a small async trait — get / set / hydrate / clear the latest
record per tier, plus a synchronous `hydrate_sync` the loop uses.
`KvMemoryStore<K: KeyValueStore>` implements it over any `foundation_db`
key-value store, keeping the whole bundle under one key:
```
memory:{session_id} → SessionMemory { working, observation, reflection }
```
`MemoryCoordinator<M, D>` dual-writes: it appends each memory record to an
audit `DocumentStore` and overwrites the latest copy in the `MemoryStore`.
`MemoryHierarchy` writes through the coordinator.
## 6. One store for everything
`AgentSession::build()` gives the memory hierarchy the same memory store and
document store the rest of the session uses, so facts added with the `memory`
tool and tiers written with `persist_*` show up in the next request's context.
(The hierarchy used to get its own default-constructed stores, and its writes
never reached the context.)
## 7. How memory reaches the model
At every `InnerAssemble`, the loop hydrates `SessionMemory` from the context
provider's store and prepends, as system-role messages:
1. working memory (`[working_memory]` + one line per fact)
2. reflection (`[reflection]`)
3. observation (`[observation]`) — only if newer than the reflection
then the recent conversation (Doc 01 §4).
## 8. Resume
Memory lives in the session's stores, keyed by `SessionId`. Building a
session with the same id over the same stores
(`AgentSession::builder(router).resume(id).with_doc_store(..).with_memory_store(..)`)
picks it up on the first turn. See Doc 08 §5.