Agent State and Memory
Agent memory should be an explicit state object that can be serialized, replayed, and inspected. Chat history is not enough because it loses typed evidence, tool provenance, and quality gate status.
The Problem
Long-running research agents make dozens of tool calls and intermediate judgments. If the only memory is the prompt transcript, the run cannot be resumed safely, audited by a reviewer, or compared against an ablation. The agent may also reuse stale claims because evidence has no timestamp or source boundary.
The Pattern
WRONG
messages = []
messages.append({"role": "user", "content": task})
while True:
answer = llm(messages)
messages.append(answer)
if "done" in answer["content"].lower():
break
CORRECT
from dataclasses import asdict, dataclass, field
from pathlib import Path
from typing import Any
import json
@dataclass
class AgentState:
task: str
evidence: list[dict[str, Any]] = field(default_factory=list)
tool_trace: list[dict[str, Any]] = field(default_factory=list)
decisions: list[dict[str, Any]] = field(default_factory=list)
quality_gates: dict[str, bool] = field(default_factory=dict)
def checkpoint(self, path: Path) -> None:
path.write_text(json.dumps(asdict(self), indent=2), encoding="utf-8")
@classmethod
def load(cls, path: Path) -> "AgentState":
return cls(**json.loads(path.read_text(encoding="utf-8")))
state = AgentState(task="Evaluate whether the ensemble improves holdout Sharpe")
state.tool_trace.append(
{"tool": "query_registry", "status": "ok", "observed_at": current_utc_iso()}
)
state.quality_gates["read_relevant_skills"] = True
state.checkpoint(Path("runs/ensemble_eval/state.json"))
Memory Layers
- Run state - current task, evidence, decisions, open questions, gates
- Tool trace - every external observation with arguments and status
- Evidence memory - source, timestamp, freshness, and extracted claims
- Long-term memory - only stable project facts; never live market facts without dates
- Replay artifact - enough inputs and outputs to re-render the run without live APIs
Guardrails
- Transcript-only replay - reviewers cannot verify which tools produced which facts
- Undated evidence - market and API observations need explicit timestamps
- Mutable memory overwrite - append decisions; do not silently edit prior reasoning
- Cross-run contamination - reset task state between independent experiments
Checklist
1---2name: ml4t-agent-state-memory3description: Durable agent state, memory, and replay for autonomous research workflows. Use when an agent must resume, audit, or compare multi-step runs.4---5# Agent State and Memory67Agent memory should be an explicit state object that can be serialized, replayed, and inspected. Chat history is not enough because it loses typed evidence, tool provenance, and quality gate status.89## The Problem1011Long-running research agents make dozens of tool calls and intermediate judgments. If the only memory is the prompt transcript, the run cannot be resumed safely, audited by a reviewer, or compared against an ablation. The agent may also reuse stale claims because evidence has no timestamp or source boundary.1213## The Pattern1415### WRONG16```python17messages = []18messages.append({"role": "user", "content": task})1920while True:21 answer = llm(messages)22 messages.append(answer)23 if "done" in answer["content"].lower():24 break25```2627### CORRECT28```python29from dataclasses import asdict, dataclass, field30from pathlib import Path31from typing import Any32import json333435@dataclass36class AgentState:37 task: str38 evidence: list[dict[str, Any]] = field(default_factory=list)39 tool_trace: list[dict[str, Any]] = field(default_factory=list)40 decisions: list[dict[str, Any]] = field(default_factory=list)41 quality_gates: dict[str, bool] = field(default_factory=dict)4243 def checkpoint(self, path: Path) -> None:44 path.write_text(json.dumps(asdict(self), indent=2), encoding="utf-8")4546 @classmethod47 def load(cls, path: Path) -> "AgentState":48 return cls(**json.loads(path.read_text(encoding="utf-8")))495051state = AgentState(task="Evaluate whether the ensemble improves holdout Sharpe")52state.tool_trace.append(53 {"tool": "query_registry", "status": "ok", "observed_at": current_utc_iso()}54)55state.quality_gates["read_relevant_skills"] = True56state.checkpoint(Path("runs/ensemble_eval/state.json"))57```5859## Memory Layers6061- **Run state** - current task, evidence, decisions, open questions, gates62- **Tool trace** - every external observation with arguments and status63- **Evidence memory** - source, timestamp, freshness, and extracted claims64- **Long-term memory** - only stable project facts; never live market facts without dates65- **Replay artifact** - enough inputs and outputs to re-render the run without live APIs6667## Guardrails6869- **Transcript-only replay** - reviewers cannot verify which tools produced which facts70- **Undated evidence** - market and API observations need explicit timestamps71- **Mutable memory overwrite** - append decisions; do not silently edit prior reasoning72- **Cross-run contamination** - reset task state between independent experiments7374## Checklist7576- [ ] State serializes to a stable JSON artifact77- [ ] Evidence includes source, timestamp, and freshness notes78- [ ] Tool trace can be replayed or inspected without the LLM79- [ ] Quality gates are explicit booleans or statuses80- [ ] Long-term memory excludes ephemeral market observations