Concept · Session memory

Typed sections, per-section budgets, an append-only audit trail.

The agent runtime keeps its working state in named sections rather than one flat conversation buffer. Each section declares its own token budget, its own rule for what to drop when full, and how it survives across restarts. A separate audit section is append-only and hash-chained, so every read of every other section leaves a tamper-evident trace. That structure is what lets a deal-room corpus, a chatty user log, an immutable role definition, and a privileged set of findings live in one session without bleeding into each other.

Why typed sections

A flat conversation buffer fails legal work in three places. Privilege boundaries collapse when every message lives in one undifferentiated stream. Cost ceilings can't be enforced when every kind of content pays the same per-token rate against one shared budget. Audit trails are unreliable when there is nothing typed to audit against.

Typed sections fix all three. Cross-tenant queries can be refused at the schema layer rather than a runtime check. Per-section token budgets let a deal-room of parsed contracts ride for free in the documents section while the chatty user-message section pays its own rate. The audit section is append-only and hash-chained, so every read of every other section leaves a verifiable trace that cannot be silently rewritten.

The sections, grouped by mutability

Each section declares a token budget, a rule for what to drop when full, and how it survives across restarts. The full-text search index built on top of the searchable sections uses BM25 — implemented in the retrieval module.

AUDIT append-only · hash-chained · always streamed PLAN_EXAMPLES · PLAYBOOKS · ROLE explicit — loaded from config, immutable PLANNING_CONTEXT · WORKING ephemeral — cleared between turns PLAN_HISTORY · LAST_INTENT · REFLECTION learned, persisted across turns MESSAGES · ACTIONS · FINDINGS learned, persisted, searchable DOCUMENTS learned, unbounded, searchable — fits a deal-room corpus

Eviction, summarization, persistence

When a section runs over budget, the rule it declared at startup decides what happens — first-in-first-out, last-in-first-out, least-recently-accessed, least-frequently-accessed, lowest-priority-first, refuse the write outright, or never evict at all because the section is bounded by design. The documents section never evicts so a thousand-PDF deal-room fits; the user-message section drops the oldest items first to stay in the model's context window.

Each section also declares how it survives a restart — gone at process exit, written to disk in full at commit time, or appended event-by-event to a JSONL file. The audit section is always the appended-event mode and always hash-chained; configuration cannot quietly make it forgetful.

A typed stream of memory-change events rides the same wire as the runtime exposes elsewhere — server-sent events for browsers, JSONL for log replay, WebSocket dictionaries for ops dashboards, plain REST for blocking clients. The same change record reaches each consumer; nothing has to be special-cased per channel.

What it looks like in code

A buy-side associate has loaded twenty contracts into the deal-room session and asked the agent to surface every change-of-control trigger. The findings, the parsed contracts, and the audit trail all live in named sections of the same memory.

from kaos_agents.memory import (
DEFAULT_SECTIONS, MemoryType, SessionMemory,
)
from kaos_agents.memory.search import search_memory
memory = SessionMemory(session_id="acme-buyside-2026")
# Each section declares its own eviction policy
policies = {cfg.memory_type: cfg.eviction_policy for cfg in DEFAULT_SECTIONS}
print(policies[MemoryType.MESSAGES]) # fifo
print(policies[MemoryType.DOCUMENTS]) # none — fits the 20-contract deal-room
# Add a parsed contract and a finding the agent surfaced
memory.add(MemoryType.DOCUMENTS, msa_acme_text, tags=("contract", "acme"))
memory.add(
MemoryType.FINDINGS,
"Acme MSA section 12.3 grants Acme a termination right on change of control.",
tags=("change-of-control",),
)
# BM25 over the searchable sections (MESSAGES, ACTIONS, DOCUMENTS, FINDINGS)
hits = search_memory(memory, "change of control termination right", top_k=5)
for hit in hits:
print(hit.section, hit.item_id, hit.content[:80])
# Every section is append-only; each item carries a timestamp — the audit trail
for item in memory.get(MemoryType.FINDINGS):
print(item.added_at, item.content[:120])

Read next

The agentic page covers the runner that drives the memory turn-by-turn. The recipes concept shows how a matching plan loads into the plan-examples section at session start.

On learn-kaos: memory-as-context-assembly · memory-sections.