# Grid-agent conversation memory

`ConversationStore` owns bounded conversational context independently of the
grid connection generation. Its key is the immutable avatar UUID plus exactly
one channel kind: public chat or direct IM. Group and conference conversations
are deliberately not representable. Public sessions expire at exactly 30
minutes of monotonic inactivity and direct IM sessions at exactly 24 hours. A
subsequent turn creates a cryptographically random new session ID and receives
none of the expired transcript.

The mutex-protected store assigns one sequence order to concurrent turns. It
stores wall-clock timestamps, normalized avatar/agent/tool roles, visible agent
responses, bounded tool summaries, and externally meaningful action results.
Avatar input and untrusted tool data remain untrusted when compacted. There is
no system-message, model-scratchpad, hidden-reasoning, credential, capability
URL, or binary-asset input variant. Text is bounded and sensitive URL/token
forms are redacted before allocation in the store.

Configured limits lower hard ceilings for active sessions, turns, per-session
bytes, aggregate bytes, tool-result count and size, summary bytes, and total
snapshot storage. Old records compact deterministically into a smaller factual
summary followed by recent context. Session and aggregate pressure evict the
least-recently-active key with UUID/channel tie-breaking. Expiry, compaction,
eviction, quarantine, and operator deletion publish stable reason codes through
a bounded event queue.

`list_metadata` returns session ID, UUID, channel, timestamps, turn count, and
byte count but never content. Operators can delete or expire an exact key.
`context` is the separate LLM-facing projection and can read only that key;
untrusted summaries are emitted as explicitly marked user-role data.

Persistence is local and opt-in through `conversation.persistence_enabled`.
`ConversationStore::from_config` uses `storage_path/conversations`; callers
flush at their durability boundary. A flush writes and syncs a new immutable,
versioned generation before an atomic rename, then retains only generations
whose aggregate bytes fit the configured storage ceiling. Linux and other Unix
targets force directory mode 0700 and file mode 0600. Rust standard library
does not expose a portable Windows ACL editor, so Windows emits the explicit
`PermissionsNotVerified` event and operators must restrict the directory ACL to
the service identity.

Restart recovery validates schema, UUIDs, IDs, unique sequences, timestamps,
roles, bounds, and redaction before any record can become LLM context. A
truncated, corrupt, oversized, or unsupported generation is quarantined and an
older valid generation is tried. If the wall clock moved backwards, recovered
age is zero; forward elapsed time is applied to the channel TTL. Neither case
can grant policy authority or prevent startup.

Focused gates:

```sh
cargo test --locked -p metacrate-grid-agent --lib conversation_tests
cargo test --locked -p metacrate-grid-agent --test conversation_memory
cargo clippy --locked -p metacrate-grid-agent --all-targets -- -D warnings
```
