Memory Management Strategies
Building on Examples 01–03, this example demonstrates memory management for long-running agentic loops. As conversations grow with many LLM turns, agents can manage history via different strategies: sliding window (drop old turns), summarization (compress via a dedicated small LLM), or keep-all. The example also shows a dedicated summarizer LLM on a separate GPU partition to avoid contention with the main reasoning model.
Prerequisites: Read 03_hitl_approval.py first.
What you’ll learn:
How to configure memory strategies for different agents
SLIDING_WINDOW— drop old turns, keep last N; lowest costSUMMARIZE— keep last N turns + a summary of older ones (via separate LLM)FULL— keep everything (suitable for short-lived agents only)How to set up a dedicated summarizer LLM on a separate node
How to balance context preservation with token usage and latency
Architecture:
Node 0, GPUs [0,1]: Main inference service (large reasoning LLM, tp_size=2)
Node 1, GPU [0]: Summarizer service (small LLM, tp_size=1)
Main Code
Below is the complete example:
1"""04 — Memory Management Strategies + Dedicated Summarizer LLM.
2
3**Prerequisites:** Read ``03_hitl_approval.py`` first.
4
5**What's new:**
6
7* **MemoryConfig** and **MemoryStrategy** — control how agents manage
8 conversation history as the agentic loop grows
9* **Three strategies side-by-side:**
10
11 - ``planner_agent`` → ``SLIDING_WINDOW`` — drops old turn-pairs beyond
12 ``max_kept_turns``, replacing them with a synthetic note
13 - ``runner_agent`` → ``SUMMARIZE`` — calls a second LLM to compress
14 old turns into a summary instead of dropping them
15 - ``analyzer_agent`` / ``reporter_agent`` → ``FULL`` (default) — keep
16 everything (fine for short-lived agents)
17
18* **Dedicated summarizer LLM** — a separate, smaller model on its own
19 GPU partition, used only for summarization. The main model stays
20 uncontended for reasoning and tool-calling.
21
22Architecture::
23
24 Node 0 GPUs [0,1] → Main InferenceWorker (large model, tp_size=2)
25 Node 1 GPU [0] → Summarizer Worker (small model, tp_size=1)
26
27Usage::
28
29 # Terminal 1 (needs 2 nodes with GPUs):
30 dragon 04_memory.py
31
32 # Terminal 2 (HITL approval client):
33 python -m dragon.ai.agent.hitl --tcp HOST:PORT
34"""
35
36import asyncio
37from typing import Any
38
39import dragon
40import multiprocessing as mp
41
42from dragon.ai.agent.core import create_sub_agent
43from dragon.ai.agent.config import (
44 AgentConfig,
45 MemoryConfig, # ← NEW
46 MemoryStrategy, # ← NEW
47 OrchestratorConfig,
48 Pipeline,
49 PipelineNode,
50)
51from dragon import Policy, System, Batch
52from dragon.ai.agent.tools import ToolRegistry
53from dragon.ai.agent.orchestrator import DAGOrchestrator
54from dragon.native.event import Event
55from dragon.native.machine import Node
56from dragon.native.process import Process
57from dragon.native.queue import Queue
58
59from dragon.ai.inference import (
60 BatchingConfig,
61 HardwareConfig,
62 InferenceConfig,
63 ModelConfig,
64 Inference,
65)
66
67from tools import (
68 propose_experiment,
69 launch_experiment,
70 check_progress,
71 collect_results,
72 analyze_convergence,
73 format_results_table,
74)
75from tools.runner import cleanup_experiment_state
76
77
78# ===========================================================================
79# User-configurable constants
80# ===========================================================================
81
82MODEL_NAME = "/path/to/your/model"
83HF_TOKEN = ""
84
85# ← NEW: Smaller instruct model used exclusively for summarization
86SUMMARIZER_MODEL_NAME = "/path/to/your/summarizer/model"
87
88
89# ===========================================================================
90# Inference Pipeline Configuration — Main (reasoning + tool-calling)
91# ===========================================================================
92
93INFERENCE_CONFIG = InferenceConfig(
94 model=ModelConfig(
95 model_name=MODEL_NAME,
96 hf_token=HF_TOKEN,
97 tp_size=2,
98 max_tokens=8192,
99 max_model_len=32768,
100 ),
101 hardware=HardwareConfig(
102 num_nodes=1,
103 num_gpus=2,
104 num_inf_workers_per_cpu=1,
105 ),
106 batching=BatchingConfig(
107 batch_wait_seconds=0.1,
108 max_batch_size=32,
109 ),
110)
111
112
113# ===========================================================================
114# ← NEW: Inference Pipeline Configuration — Summarizer
115#
116# Placed on the SECOND node (node_offset=1) so the main model's GPUs on
117# node 0 are not contended. A small instruct model fits on a single GPU.
118# ===========================================================================
119
120SUMMARIZER_CONFIG = InferenceConfig(
121 model=ModelConfig(
122 model_name=SUMMARIZER_MODEL_NAME,
123 hf_token=HF_TOKEN,
124 tp_size=1,
125 max_tokens=2048,
126 max_model_len=8192,
127 ),
128 hardware=HardwareConfig(
129 num_nodes=1,
130 num_gpus=1,
131 node_offset=1, # ← start from node 1 (second node)
132 num_inf_workers_per_cpu=1,
133 ),
134 batching=BatchingConfig(
135 batch_wait_seconds=0.05,
136 max_batch_size=8,
137 ),
138)
139
140
141# ===========================================================================
142# Tool registries
143# ===========================================================================
144
145planner_registry = ToolRegistry()
146planner_registry.register(propose_experiment)
147
148runner_registry = ToolRegistry()
149runner_registry.register(launch_experiment)
150runner_registry.register(check_progress)
151runner_registry.register(collect_results)
152
153analyzer_registry = ToolRegistry()
154analyzer_registry.register(analyze_convergence)
155
156reporter_registry = ToolRegistry()
157reporter_registry.register(format_results_table)
158
159
160# ===========================================================================
161# DAG pipeline
162# ===========================================================================
163
164pipeline = Pipeline(nodes=[
165 PipelineNode(
166 agent_id="planner_agent",
167 task_description=(
168 "You are a scientific experiment planner. The user wants to study "
169 "Monte Carlo convergence for estimating π.\n\n"
170 "Propose an experiment plan by calling propose_experiment with:\n"
171 " - description, sample_sizes, convergence_target, methodology\n\n"
172 "A human operator will review your plan. If they provide "
173 "feedback, revise and resubmit. Once approved, report verbatim."
174 ),
175 depends_on=[],
176 ),
177 PipelineNode(
178 agent_id="runner_agent",
179 task_description=(
180 "You manage parallel Monte Carlo simulations on an HPC cluster.\n\n"
181 "Tools:\n"
182 " 1. launch_experiment(sample_sizes, seeds) — launches ALL "
183 "simulations in parallel. Operator must approve.\n"
184 " 2. check_progress() — shows done/running/pending status.\n"
185 " 3. collect_results() — call ONLY when all_done=true.\n\n"
186 "STRICT workflow:\n"
187 " 1. Call launch_experiment.\n"
188 " 2. Call check_progress until all_done=true.\n"
189 " 3. Call collect_results — REQUIRED before final answer.\n"
190 " 4. Report collect_results output verbatim."
191 ),
192 depends_on=["planner_agent"],
193 ),
194 PipelineNode(
195 agent_id="analyzer_agent",
196 task_description=(
197 "Call analyze_convergence with a list of dicts (keys: "
198 "'n_samples', 'absolute_error'). Report all metrics verbatim."
199 ),
200 depends_on=["runner_agent"],
201 ),
202 PipelineNode(
203 agent_id="reporter_agent",
204 task_description=(
205 "Write a structured report.\n\n"
206 "STRICT workflow:\n"
207 " 1. Call format_results_table to get a Markdown table.\n"
208 " 2. COPY-PASTE the actual table into your final answer.\n"
209 " 3. Include sections: Plan, Results Table, Parallel Execution,\n"
210 " Convergence Analysis, Quality Assessment, Recommendations.\n\n"
211 "Never invent data — use only data from upstream agents."
212 ),
213 depends_on=["planner_agent", "runner_agent", "analyzer_agent"],
214 ),
215])
216
217
218# ===========================================================================
219# Helpers
220# ===========================================================================
221
222def _make_agent_kwargs(agent_id, name, role, registry, inference_queue,
223 approval_filter=None,
224 max_tool_call_iterations=20,
225 memory=None,
226 summarizer_inference_queue=None):
227 """Build kwargs for create_sub_agent().
228
229 NEW in this example:
230 ``memory`` — MemoryConfig controlling conversation history pruning
231 ``summarizer_inference_queue`` — separate queue for the summarizer LLM
232 """
233 return {
234 "config": AgentConfig(
235 agent_id=agent_id, name=name, role=role,
236 inference_queue=inference_queue,
237 summarizer_inference_queue=summarizer_inference_queue,
238 approval_filter=approval_filter,
239 max_tool_call_iterations=max_tool_call_iterations,
240 memory=memory,
241 ),
242 "tool_registry": registry,
243 "shutdown_event": Event(),
244 "reply_queue": Queue(),
245 }
246
247
248def _start_agents(specs, policies):
249 procs = []
250 for spec, policy in zip(specs, policies):
251 p = Process(target=create_sub_agent, kwargs=spec, policy=policy)
252 p.start()
253 procs.append(p)
254 queues = {}
255 for spec in specs:
256 aid = spec["config"].agent_id
257 queues[aid] = spec["reply_queue"].get()
258 print(f"[startup] Agent '{aid}' ready.", flush=True)
259 return procs, queues
260
261
262# ===========================================================================
263# Main
264# ===========================================================================
265
266async def main():
267 # -------------------------------------------------------------------
268 # 1. Main inference pipeline
269 # -------------------------------------------------------------------
270 input_queue = Queue()
271
272 print("[startup] Initializing main inference pipeline...", flush=True)
273
274 inference_pipeline = None
275 try:
276 inference_pipeline = Inference(INFERENCE_CONFIG, input_queue)
277 inference_pipeline.initialize()
278 except Exception as exc:
279 import traceback
280 print(f"\n[FATAL] Main inference pipeline failed to initialize: {exc}", flush=True)
281 traceback.print_exc()
282 if inference_pipeline is not None:
283 inference_pipeline.destroy()
284 return
285 print("[startup] Main inference pipeline ready.\n", flush=True)
286
287 # -------------------------------------------------------------------
288 # ← NEW: 2. Summarizer inference pipeline (separate model, separate GPU)
289 # -------------------------------------------------------------------
290 summarizer_input_queue = Queue()
291
292 print("[startup] Initializing summarizer inference pipeline...", flush=True)
293 print(f"[startup] Summarizer model: {SUMMARIZER_MODEL_NAME}", flush=True)
294
295 summarizer_pipeline = None
296 try:
297 summarizer_pipeline = Inference(SUMMARIZER_CONFIG, summarizer_input_queue)
298 summarizer_pipeline.initialize()
299 except Exception as exc:
300 import traceback
301 print(f"\n[FATAL] Summarizer pipeline failed to initialize: {exc}", flush=True)
302 traceback.print_exc()
303 inference_pipeline.destroy()
304 if summarizer_pipeline is not None:
305 summarizer_pipeline.destroy()
306 return
307 print("[startup] Summarizer inference pipeline ready.\n", flush=True)
308
309 my_alloc = System()
310 node_list = my_alloc.nodes
311 compute_host = (
312 Node(node_list[1]).hostname if len(node_list) > 1
313 else Node(node_list[0]).hostname
314 )
315 compute_policy = Policy(
316 placement=Policy.Placement.HOST_NAME,
317 host_name=compute_host,
318 )
319
320 procs, agent_specs = [], []
321 try:
322 agent_specs = [
323 _make_agent_kwargs(
324 "planner_agent", "Experiment Planner",
325 "You are an experiment planner for Monte Carlo convergence "
326 "studies on an HPC cluster. Propose plans via "
327 "propose_experiment. Revise on feedback.",
328 planner_registry, input_queue,
329 approval_filter=lambda n, a: n == "propose_experiment",
330 max_tool_call_iterations=60,
331 # ← NEW: SLIDING_WINDOW — drop old turns beyond max_kept_turns
332 memory=MemoryConfig(
333 strategy=MemoryStrategy.SLIDING_WINDOW,
334 max_kept_turns=3,
335 ),
336 ),
337 _make_agent_kwargs(
338 "runner_agent", "Parallel Simulation Runner",
339 "You manage parallel Monte Carlo simulations.\n"
340 "You MUST call all three tools in order: "
341 "launch_experiment → check_progress → collect_results.\n"
342 "NEVER give a final answer without calling collect_results first.",
343 runner_registry, input_queue,
344 approval_filter=lambda n, a: n == "launch_experiment",
345 max_tool_call_iterations=60,
346 # ← NEW: SUMMARIZE — compress old turns via LLM
347 memory=MemoryConfig(
348 strategy=MemoryStrategy.SUMMARIZE,
349 max_kept_turns=1,
350 summarize_after_turns=2,
351 ),
352 # ← NEW: use dedicated summarizer LLM
353 summarizer_inference_queue=summarizer_input_queue,
354 ),
355 _make_agent_kwargs(
356 "analyzer_agent", "Convergence Analyzer",
357 "Analyse convergence. Call analyze_convergence with "
358 "n_samples + absolute_error dicts. Report verbatim.",
359 analyzer_registry, input_queue,
360 # No memory config → FULL (default) — keep everything
361 ),
362 _make_agent_kwargs(
363 "reporter_agent", "Report Writer",
364 "Write a structured report with Markdown tables. "
365 "Include parallel execution details. "
366 "Always include tool output verbatim.",
367 reporter_registry, input_queue,
368 # No memory config → FULL (default)
369 ),
370 ]
371 policies = [None, compute_policy, None, None]
372
373 procs, queues = _start_agents(agent_specs, policies)
374 for spec in agent_specs:
375 spec["config"].input_queue = queues[spec["config"].agent_id]
376
377 orchestrator = DAGOrchestrator(
378 config=OrchestratorConfig(
379 agents=[s["config"] for s in agent_specs],
380 poll_interval=0.5,
381 poll_timeout=14400.0,
382 ),
383 pipeline=pipeline,
384 )
385
386 user_input = (
387 "Design and run a Monte Carlo convergence experiment for π. "
388 "I want to review the plan first, then approve the parallel "
389 "launch. Monitor progress and report which nodes ran what."
390 )
391
392 batch = Batch()
393 try:
394 print("=" * 60, flush=True)
395 print("Dragon AI — 04 Memory Strategies + Summarizer LLM", flush=True)
396 print("=" * 60, flush=True)
397 print(f"Request: {user_input}\n", flush=True)
398
399 # Print memory strategy summary
400 print("Memory strategies:", flush=True)
401 print(" planner_agent → SLIDING_WINDOW (max_kept_turns=3)", flush=True)
402 print(" runner_agent → SUMMARIZE (dedicated summarizer LLM)", flush=True)
403 print(" analyzer_agent → FULL (default)", flush=True)
404 print(" reporter_agent → FULL (default)", flush=True)
405 print(flush=True)
406
407 if orchestrator.hitl_address:
408 h, p = orchestrator.hitl_address
409 print(
410 f"HITL approval client — start in another terminal:\n"
411 f" python -m dragon.ai.agent.hitl --tcp {h}:{p}\n"
412 f" Options:\n"
413 f" --jsonl PATH Custom JSONL path (default: auto-generated)\n"
414 f" --report PATH Custom .txt report path\n"
415 f" --no-color Disable ANSI colours\n",
416 flush=True,
417 )
418
419 result = orchestrator.run(
420 user_input=user_input,
421 batch=batch,
422 )
423
424 print("\n" + "=" * 60, flush=True)
425 print("FINAL RESULT", flush=True)
426 print("=" * 60, flush=True)
427 print(result, flush=True)
428
429 except Exception as exc:
430 import traceback
431 print(f"\n[error] Pipeline failed: {exc}", flush=True)
432 traceback.print_exc()
433 finally:
434 orchestrator.destroy()
435 batch.join()
436
437 except Exception as exc:
438 import traceback
439 print(f"\n[error] Fatal: {exc}", flush=True)
440 traceback.print_exc()
441 finally:
442 cleanup_experiment_state()
443 for spec in agent_specs:
444 try:
445 spec["shutdown_event"].set()
446 except Exception:
447 pass
448 for p in procs:
449 try:
450 p.join()
451 except Exception:
452 pass
453 print("\n[teardown] All agents stopped.", flush=True)
454 try:
455 inference_pipeline.destroy()
456 except Exception:
457 pass
458 print("[teardown] Main inference pipeline stopped.", flush=True)
459 try:
460 summarizer_pipeline.destroy()
461 except Exception:
462 pass
463 print("[teardown] Summarizer inference pipeline stopped.", flush=True)
464
465
466if __name__ == "__main__":
467 mp.set_start_method("dragon")
468 asyncio.run(main())
Key Concepts
SLIDING_WINDOW:
Keep the most recent max_kept_turns conversation turns. Drop older turns
and replace with a synthetic “earlier context” note. Lowest memory and token
cost; suitable for short-term reasoning tasks.
SUMMARIZE:
Keep the last max_kept_turns turns in full detail. Older turns are
compressed into a summary (generated by a dedicated small LLM) inserted
before the kept turns. Good for long-running workflows where full history
matters but you want to control latency.
FULL:
Keep all conversation turns from the start. Suitable only for short-lived agents (will run out of context window eventually). Good for understanding agent reasoning in logs.
Dedicated Summarizer LLM:
By running the summarizer on a separate GPU partition (different node, smaller model), the main reasoning agent stays uncontended. This improves throughput and latency of the critical reasoning path.
Installation
See Example 01 (same dependencies).
System Description
Tested on HPE Cray EX:
Node 0 (main inference): 2 Nvidia A100 GPUs
Node 1 (summarizer): 1 Nvidia A100 GPU
Total: 2 compute nodes required
How to Run
Step 1: Edit model paths
Open 04_memory.py and set:
MODEL_NAME(main reasoning model, e.g., 70B Llama)SUMMARIZER_MODEL_NAME(small model, e.g., 7B Llama)
Step 2: Allocate nodes
salloc --nodes=2 --exclusive
Step 3: Run
dragon 04_memory.py
Example output:
$ dragon 04_memory.py
Node 0: Starting main inference service (70B model, tp_size=2)
Node 1: Starting summarizer service (7B model, tp_size=1)
Planner (SLIDING_WINDOW): Turn 1, kept_turns=1/5
Planner (SLIDING_WINDOW): Turn 2, kept_turns=2/5
...
Planner (SLIDING_WINDOW): Turn 6, kept_turns=5/5 (dropped turn 1)
Runner (SUMMARIZE): Turn 1, kept_turns=1/10
...
Runner (SUMMARIZE): Turn 11, kept_turns=10/10 + summary of turns 1-5
All agents completed
For HITL approval (in a second terminal):
python -m dragon.ai.agent.hitl_client --tcp HOST:PORT
Next Steps
05 — MCP Tools (integrate remote Model Context Protocol servers)