Full Production Pipeline
This is the capstone example, combining all features from the previous five examples into a production-ready reference implementation. It demonstrates: multi-agent DAG, HITL approval gates, memory strategies, MCP tool servers, and real-time tracing / observability. This is your template for building complex agentic systems on Dragon.
Prerequisites: Read Examples 01–05 first.
What you’ll learn:
How to integrate all five techniques into one system
How to enable tracing to log every LLM call, tool invocation, approval, and memory operation to a structured JSONL file
How to use the trace viewer to inspect and replay execution
Best practices for production agent deployment on HPC clusters
Architecture:
Node 0, GPUs [0,1]: Main inference service (large reasoning LLM)
Node 1, GPU [0]: Dedicated summarizer LLM
DAG: planner → runner → analyzer → reporter → save_report (function node)
All agents have memory management, some have HITL approval, one has MCP tools
Tracing enabled for full observability
Main Code
Below is the complete example:
1"""06 — Full Pipeline: DAG + HITL + Memory + MCP + Tracing.
2
3**Prerequisites:** Read ``01``–``05`` first.
4
5**What's new:**
6
7* **Tracing / observability** — ``tracing=True`` on ``OrchestratorConfig``
8 writes a per-run JSONL trace with every LLM call, tool call,
9 approval/rejection, memory operation, and DAG state transition
10* **Trace viewer CLI** — inspect the trace interactively after a run
11* **Everything combined** — this is the production-ready reference
12 combining all features from the previous examples:
13
14 - Multi-agent DAG + function node (from 02)
15 - HITL approval gates (from 03)
16 - Memory strategies + summarizer LLM (from 04)
17 - MCP tool server integration (from 05)
18
19Architecture::
20
21 Node 0 GPUs [0,1] → Main InferenceWorker (tp_size=2)
22 Node 1 GPU [0] → Summarizer Worker (tp_size=1)
23
24 planner_agent ──► runner_agent ──► analyzer_agent ──┐
25 [HITL] [HITL] [memory] ├──► reporter_agent ──► save_report(fn)
26 SLIDING_WINDOW SUMMARIZE │ [MCP: jupyter]
27 └──────────────┴───────────────────────────────┘
28
29 tracing → ./traces/<run-id>.jsonl
30
31Usage::
32
33 # Without MCP:
34 dragon 06_full_pipeline.py
35
36 # With MCP:
37 echo '<token>' > token.txt
38 dragon 06_full_pipeline.py <mcp_url>
39
40 # HITL approval client (in another terminal):
41 python -m dragon.ai.agent.hitl --tcp HOST:PORT
42
43 # Trace viewer (in another terminal):
44 python -m dragon.ai.agent.observability --tcp HOST:PORT -i
45"""
46
47import asyncio
48import json
49import os
50import sys
51import time
52from typing import Any
53
54import dragon
55import multiprocessing as mp
56
57from dragon.ai.agent.core import create_sub_agent
58from dragon.ai.agent.config import (
59 AgentConfig,
60 MCPServerConfig,
61 MemoryConfig,
62 MemoryStrategy,
63 OrchestratorConfig,
64 Pipeline,
65 PipelineNode,
66 TaskResult,
67 TaskStatus,
68 DISPATCH_ID_KEY,
69 RESULT_KEY,
70 STATUS_KEY,
71)
72from dragon import DDict, Policy, System, Batch
73from dragon.ai.agent.tools import ToolRegistry
74from dragon.ai.agent.orchestrator import DAGOrchestrator
75from dragon.native.event import Event
76from dragon.native.machine import Node
77from dragon.native.process import Process
78from dragon.native.queue import Queue
79
80from dragon.ai.inference import (
81 BatchingConfig,
82 HardwareConfig,
83 InferenceConfig,
84 ModelConfig,
85 Inference,
86)
87
88from tools import (
89 propose_experiment,
90 launch_experiment,
91 check_progress,
92 collect_results,
93 analyze_convergence,
94 format_results_table,
95)
96from tools.runner import cleanup_experiment_state
97
98
99# ===========================================================================
100# User-configurable constants
101# ===========================================================================
102
103MODEL_NAME = "/path/to/your/model"
104HF_TOKEN = ""
105SUMMARIZER_MODEL_NAME = "/path/to/your/summarizer/model"
106
107
108# ===========================================================================
109# Inference — Main model (reasoning + tool-calling)
110# ===========================================================================
111
112INFERENCE_CONFIG = InferenceConfig(
113 model=ModelConfig(
114 model_name=MODEL_NAME,
115 hf_token=HF_TOKEN,
116 tp_size=2,
117 max_tokens=8192,
118 max_model_len=32768,
119 ),
120 hardware=HardwareConfig(
121 num_nodes=1,
122 num_gpus=2,
123 num_inf_workers_per_cpu=1,
124 ),
125 batching=BatchingConfig(
126 batch_wait_seconds=0.1,
127 max_batch_size=32,
128 ),
129)
130
131
132# ===========================================================================
133# Inference — Summarizer (separate GPU on node 1)
134# ===========================================================================
135
136SUMMARIZER_CONFIG = InferenceConfig(
137 model=ModelConfig(
138 model_name=SUMMARIZER_MODEL_NAME,
139 hf_token=HF_TOKEN,
140 tp_size=1,
141 max_tokens=2048,
142 max_model_len=8192,
143 ),
144 hardware=HardwareConfig(
145 num_nodes=1,
146 num_gpus=1,
147 node_offset=1,
148 num_inf_workers_per_cpu=1,
149 ),
150 batching=BatchingConfig(
151 batch_wait_seconds=0.05,
152 max_batch_size=8,
153 ),
154)
155
156
157# ===========================================================================
158# Tool registries
159# ===========================================================================
160
161planner_registry = ToolRegistry()
162planner_registry.register(propose_experiment)
163
164runner_registry = ToolRegistry()
165runner_registry.register(launch_experiment)
166runner_registry.register(check_progress)
167runner_registry.register(collect_results)
168
169analyzer_registry = ToolRegistry()
170analyzer_registry.register(analyze_convergence)
171
172reporter_registry = ToolRegistry()
173reporter_registry.register(format_results_table)
174
175
176# ===========================================================================
177# Function node — persists the final report to disk + DDict
178# ===========================================================================
179
180REPORT_DIR = os.environ.get("DRAGON_REPORT_DIR", os.getcwd())
181
182
183def save_report(*upstreams: TaskResult) -> TaskResult:
184 """Post-processing function node — saves the reporter's output to disk.
185
186 Runs as a plain Python function inside Dragon Batch (no LLM, no agent
187 queue). Receives upstream TaskResult tokens, attaches to the shared
188 DDict, reads the reporter's result, and writes it to disk.
189
190 :param upstreams: One or more TaskResult tokens from upstream nodes.
191 The first upstream carries ``task_id`` and ``serialized_ddict``.
192 :return: TaskResult with status DONE for the orchestrator to collect.
193 """
194 upstream = upstreams[0]
195 task_id = upstream.task_id
196 serialized_ddict = upstream.serialized_ddict
197
198 print(f"\n[save_report] Function node started (task_id={task_id[:8]}...)",
199 flush=True)
200
201 ddict = DDict.attach(serialized_ddict)
202 try:
203 # Read the reporter agent's result from DDict
204 reporter_dispatch_key = DISPATCH_ID_KEY.format(
205 task_id=task_id, agent_id="reporter_agent"
206 )
207 reporter_dispatch_id = ddict[reporter_dispatch_key]
208 reporter_result_key = RESULT_KEY.format(
209 task_id=task_id,
210 agent_id="reporter_agent",
211 dispatch_id=reporter_dispatch_id,
212 )
213 reporter_result = ddict[reporter_result_key]
214
215 if isinstance(reporter_result, dict):
216 report_text = reporter_result.get("response", str(reporter_result))
217 else:
218 report_text = str(reporter_result)
219
220 # Write Markdown file
221 md_path = os.path.join(REPORT_DIR, "monte_carlo_report.md")
222 with open(md_path, "w") as f:
223 f.write(report_text)
224 print(f"[save_report] Written: {md_path}", flush=True)
225
226 # Write JSON artifact with metadata
227 json_path = os.path.join(REPORT_DIR, "monte_carlo_report.json")
228 artifact = {
229 "task_id": task_id,
230 "timestamp": time.strftime("%Y-%m-%dT%H:%M:%S%z"),
231 "report": report_text,
232 "source_agent": "reporter_agent",
233 "output_files": [md_path],
234 }
235 with open(json_path, "w") as f:
236 json.dump(artifact, f, indent=2)
237 print(f"[save_report] Written: {json_path}", flush=True)
238
239 # Write own result to DDict so the orchestrator can collect it
240 own_dispatch_id = f"fn-save-report-{task_id[:8]}"
241 dispatch_id_key = DISPATCH_ID_KEY.format(
242 task_id=task_id, agent_id="save_report"
243 )
244 result_key = RESULT_KEY.format(
245 task_id=task_id, agent_id="save_report", dispatch_id=own_dispatch_id
246 )
247 status_key = STATUS_KEY.format(
248 task_id=task_id, agent_id="save_report", dispatch_id=own_dispatch_id
249 )
250 result_payload = {
251 "response": (
252 f"Report saved to:\n"
253 f" - {md_path}\n"
254 f" - {json_path}"
255 )
256 }
257 ddict[dispatch_id_key] = own_dispatch_id
258 ddict[result_key] = result_payload
259 ddict[status_key] = TaskStatus.DONE
260 finally:
261 ddict.detach()
262
263 print("[save_report] Function node complete.\n", flush=True)
264
265 return TaskResult(
266 task_id=task_id,
267 agent_id="save_report",
268 status=TaskStatus.DONE,
269 serialized_ddict=serialized_ddict,
270 )
271
272
273# ===========================================================================
274# DAG pipeline
275# ===========================================================================
276
277pipeline = Pipeline(nodes=[
278 PipelineNode(
279 agent_id="planner_agent",
280 task_description=(
281 "You are a scientific experiment planner. The user wants to study "
282 "Monte Carlo convergence for estimating π.\n\n"
283 "Propose an experiment plan by calling propose_experiment with:\n"
284 " - description, sample_sizes, convergence_target, methodology\n\n"
285 "A human operator will review your plan. If they provide "
286 "feedback, revise and resubmit. Once approved, report verbatim."
287 ),
288 depends_on=[],
289 ),
290 PipelineNode(
291 agent_id="runner_agent",
292 task_description=(
293 "You manage parallel Monte Carlo simulations on an HPC cluster.\n\n"
294 "Tools:\n"
295 " 1. launch_experiment(sample_sizes, seeds) — launches ALL "
296 "simulations in parallel. Operator must approve.\n"
297 " 2. check_progress() — shows done/running/pending status.\n"
298 " 3. collect_results() — call ONLY when all_done=true.\n\n"
299 "STRICT workflow:\n"
300 " 1. Call launch_experiment.\n"
301 " 2. Call check_progress until all_done=true.\n"
302 " 3. Call collect_results — REQUIRED before final answer.\n"
303 " 4. Report collect_results output verbatim."
304 ),
305 depends_on=["planner_agent"],
306 ),
307 PipelineNode(
308 agent_id="analyzer_agent",
309 task_description=(
310 "Call analyze_convergence with a list of dicts (keys: "
311 "'n_samples', 'absolute_error'). Report all metrics verbatim."
312 ),
313 depends_on=["runner_agent"],
314 ),
315 PipelineNode(
316 agent_id="reporter_agent",
317 task_description=(
318 "Write a structured report with the experiment results.\n\n"
319 "STRICT workflow — follow every step IN ORDER:\n\n"
320 " STEP 1. Call format_results_table to get a Markdown table.\n\n"
321 " STEP 2. Check your available tools. If you have tools that\n"
322 " can manage Jupyter kernels and notebook files, do\n"
323 " BOTH sub-steps:\n"
324 " a. Create a Jupyter kernel.\n"
325 " b. Create a notebook file named\n"
326 " 'monte_carlo_convergence_report' with the\n"
327 " COMPLETE report as code_content.\n"
328 " If no such tools are available, skip Step 2.\n\n"
329 " STEP 3. Give your final answer with the SAME complete\n"
330 " report text. COPY-PASTE the actual table.\n\n"
331 "Never invent data — use only data from upstream agents."
332 ),
333 depends_on=["planner_agent", "runner_agent", "analyzer_agent"],
334 ),
335 # ← Function node: runs save_report() after reporter_agent finishes
336 PipelineNode(
337 agent_id="save_report",
338 fn=save_report,
339 task_description="Persist final report to DDict.",
340 depends_on=["reporter_agent"],
341 ),
342])
343
344
345# ===========================================================================
346# Helpers
347# ===========================================================================
348
349def _make_agent_kwargs(agent_id, name, role, registry, inference_queue,
350 approval_filter=None,
351 max_tool_call_iterations=20,
352 memory=None,
353 summarizer_inference_queue=None,
354 mcp_servers=None):
355 """Build kwargs for create_sub_agent() — all features combined."""
356 return {
357 "config": AgentConfig(
358 agent_id=agent_id, name=name, role=role,
359 inference_queue=inference_queue,
360 summarizer_inference_queue=summarizer_inference_queue,
361 approval_filter=approval_filter,
362 max_tool_call_iterations=max_tool_call_iterations,
363 memory=memory,
364 ),
365 "tool_registry": registry,
366 "mcp_servers": mcp_servers,
367 "shutdown_event": Event(),
368 "reply_queue": Queue(),
369 }
370
371
372def _start_agents(specs, policies):
373 procs = []
374 for spec, policy in zip(specs, policies):
375 p = Process(target=create_sub_agent, kwargs=spec, policy=policy)
376 p.start()
377 procs.append(p)
378 queues = {}
379 for spec in specs:
380 aid = spec["config"].agent_id
381 queues[aid] = spec["reply_queue"].get()
382 print(f"[startup] Agent '{aid}' ready.", flush=True)
383 return procs, queues
384
385
386# ===========================================================================
387# Main
388# ===========================================================================
389
390async def main():
391 # -------------------------------------------------------------------
392 # 1. Main inference pipeline
393 # -------------------------------------------------------------------
394 input_queue = Queue()
395
396 print("[startup] Initializing main inference pipeline...", flush=True)
397
398 inference_pipeline = None
399 try:
400 inference_pipeline = Inference(INFERENCE_CONFIG, input_queue)
401 inference_pipeline.initialize()
402 except Exception as exc:
403 import traceback
404 print(f"\n[FATAL] Main inference pipeline failed: {exc}", flush=True)
405 traceback.print_exc()
406 if inference_pipeline is not None:
407 inference_pipeline.destroy()
408 return
409 print("[startup] Main inference pipeline ready.\n", flush=True)
410
411 # -------------------------------------------------------------------
412 # 2. Summarizer inference pipeline
413 # -------------------------------------------------------------------
414 summarizer_input_queue = Queue()
415
416 print("[startup] Initializing summarizer inference pipeline...", flush=True)
417
418 summarizer_pipeline = None
419 try:
420 summarizer_pipeline = Inference(SUMMARIZER_CONFIG, summarizer_input_queue)
421 summarizer_pipeline.initialize()
422 except Exception as exc:
423 import traceback
424 print(f"\n[FATAL] Summarizer pipeline failed: {exc}", flush=True)
425 traceback.print_exc()
426 inference_pipeline.destroy()
427 if summarizer_pipeline is not None:
428 summarizer_pipeline.destroy()
429 return
430 print("[startup] Summarizer inference pipeline ready.\n", flush=True)
431
432 # -------------------------------------------------------------------
433 # 3. MCP server (optional)
434 # -------------------------------------------------------------------
435 mcp_url = sys.argv[1] if len(sys.argv) >= 2 else None
436
437 TOKEN_FILE = os.path.join(os.getcwd(), "token.txt")
438 mcp_token = None
439 if mcp_url:
440 try:
441 with open(TOKEN_FILE, "rb") as f:
442 raw = f.read()
443 mcp_token = raw.decode("utf-8").strip().strip("\ufeff")
444 print(f"[startup] MCP token loaded from {TOKEN_FILE}", flush=True)
445 except FileNotFoundError:
446 print(f"[startup] WARNING: {TOKEN_FILE} not found.", flush=True)
447
448 reporter_mcp_servers = None
449 if mcp_url:
450 reporter_mcp_servers = [
451 MCPServerConfig(url=mcp_url, alias="jupyter", token=mcp_token),
452 ]
453 print(f"[startup] Reporter MCP: {mcp_url}", flush=True)
454 else:
455 print("[startup] No MCP URL — reporter uses local tools only.", flush=True)
456
457 # -------------------------------------------------------------------
458 # 4. Node placement
459 # -------------------------------------------------------------------
460 my_alloc = System()
461 node_list = my_alloc.nodes
462 compute_host = (
463 Node(node_list[1]).hostname if len(node_list) > 1
464 else Node(node_list[0]).hostname
465 )
466 compute_policy = Policy(
467 placement=Policy.Placement.HOST_NAME,
468 host_name=compute_host,
469 )
470
471 procs, agent_specs = [], []
472 try:
473 agent_specs = [
474 _make_agent_kwargs(
475 "planner_agent", "Experiment Planner",
476 "You are an experiment planner for Monte Carlo convergence "
477 "studies on an HPC cluster.",
478 planner_registry, input_queue,
479 approval_filter=lambda n, a: n == "propose_experiment",
480 max_tool_call_iterations=60,
481 memory=MemoryConfig(
482 strategy=MemoryStrategy.SLIDING_WINDOW,
483 max_kept_turns=3,
484 ),
485 ),
486 _make_agent_kwargs(
487 "runner_agent", "Parallel Simulation Runner",
488 "You manage parallel Monte Carlo simulations.\n"
489 "You MUST call all three tools in order: "
490 "launch_experiment → check_progress → collect_results.\n"
491 "NEVER give a final answer without calling collect_results first.",
492 runner_registry, input_queue,
493 approval_filter=lambda n, a: n == "launch_experiment",
494 max_tool_call_iterations=60,
495 memory=MemoryConfig(
496 strategy=MemoryStrategy.SUMMARIZE,
497 max_kept_turns=1,
498 summarize_after_turns=2,
499 ),
500 summarizer_inference_queue=summarizer_input_queue,
501 ),
502 _make_agent_kwargs(
503 "analyzer_agent", "Convergence Analyzer",
504 "Analyse convergence. Call analyze_convergence with "
505 "n_samples + absolute_error dicts. Report verbatim.",
506 analyzer_registry, input_queue,
507 ),
508 _make_agent_kwargs(
509 "reporter_agent", "Report Writer",
510 "Write a structured report with Markdown tables. "
511 "If you have Jupyter tools, also create a notebook. "
512 "Always include tool output verbatim.",
513 reporter_registry, input_queue,
514 mcp_servers=reporter_mcp_servers,
515 ),
516 ]
517 policies = [None, compute_policy, None, None]
518
519 procs, queues = _start_agents(agent_specs, policies)
520 for spec in agent_specs:
521 spec["config"].input_queue = queues[spec["config"].agent_id]
522
523 # ← NEW: tracing=True enables the JSONL trace log
524 orchestrator = DAGOrchestrator(
525 config=OrchestratorConfig(
526 agents=[s["config"] for s in agent_specs],
527 poll_interval=0.5,
528 poll_timeout=14400.0,
529 tracing=True, # ← writes traces/<run-id>.jsonl
530 ),
531 pipeline=pipeline,
532 )
533
534 user_input = (
535 "Design and run a Monte Carlo convergence experiment for π. "
536 "I want to review the plan first, then approve the parallel "
537 "launch. Monitor progress and report which nodes ran what."
538 + (
539 " Save the final report to a Jupyter notebook on the cluster."
540 if mcp_url else ""
541 )
542 )
543
544 batch = Batch()
545 try:
546 print("=" * 60, flush=True)
547 print("Dragon AI — 06 Full Pipeline (all features)", flush=True)
548 print("=" * 60, flush=True)
549 print(f"Request: {user_input}\n", flush=True)
550
551 # Feature summary
552 print("Active features:", flush=True)
553 print(" DAG .............. 4 agents + save_report fn node", flush=True)
554 print(" HITL ............. planner (propose_experiment), "
555 "runner (launch_experiment)", flush=True)
556 print(" Memory ........... planner=SLIDING_WINDOW, "
557 "runner=SUMMARIZE", flush=True)
558 print(f" MCP .............. {'reporter → ' + mcp_url if mcp_url else 'disabled'}", flush=True)
559 print(" Tracing .......... enabled → ./traces/", flush=True)
560 print(flush=True)
561
562 # HITL instructions
563 if orchestrator.hitl_address:
564 h, p = orchestrator.hitl_address
565 print(
566 f"HITL approval client — start in another terminal:\n"
567 f" python -m dragon.ai.agent.hitl --tcp {h}:{p}\n"
568 f" Options:\n"
569 f" --jsonl PATH Custom JSONL path (default: auto-generated)\n"
570 f" --report PATH Custom .txt report path\n"
571 f" --no-color Disable ANSI colours\n",
572 flush=True,
573 )
574
575 # Trace viewer instructions
576 if orchestrator.trace_address:
577 th, tp = orchestrator.trace_address
578 print(
579 f"Trace viewer — start in another terminal:\n"
580 f" python -m dragon.ai.agent.observability --tcp {th}:{tp} -i\n"
581 f" Options:\n"
582 f" -i, --interactive Curses TUI (recommended)\n"
583 f" --jsonl PATH Custom JSONL path (default: auto-generated)\n"
584 f" --report PATH Custom .txt report path\n"
585 f" --no-color Disable ANSI colours\n",
586 flush=True,
587 )
588
589 result = orchestrator.run(
590 user_input=user_input,
591 batch=batch,
592 )
593
594 print("\n" + "=" * 60, flush=True)
595 print("FINAL RESULT", flush=True)
596 print("=" * 60, flush=True)
597 print(result, flush=True)
598
599 except Exception as exc:
600 import traceback
601 print(f"\n[error] Pipeline failed: {exc}", flush=True)
602 traceback.print_exc()
603 finally:
604 orchestrator.destroy()
605 batch.join()
606
607 except Exception as exc:
608 import traceback
609 print(f"\n[error] Fatal: {exc}", flush=True)
610 traceback.print_exc()
611 finally:
612 cleanup_experiment_state()
613 for spec in agent_specs:
614 try:
615 spec["shutdown_event"].set()
616 except Exception:
617 pass
618 for p in procs:
619 try:
620 p.join()
621 except Exception:
622 pass
623 print("\n[teardown] All agents stopped.", flush=True)
624 try:
625 inference_pipeline.destroy()
626 except Exception:
627 pass
628 print("[teardown] Main inference pipeline stopped.", flush=True)
629 try:
630 summarizer_pipeline.destroy()
631 except Exception:
632 pass
633 print("[teardown] Summarizer inference pipeline stopped.", flush=True)
634
635
636if __name__ == "__main__":
637 mp.set_start_method("dragon")
638 asyncio.run(main())
Key Concepts
Feature Integration:
Multi-agent DAG (from 02): Four agents form a pipeline with automatic upstream result flow
HITL Approval (from 03): Planner and runner require human review for tool calls
Memory Management (from 04): Planner uses sliding window, runner uses summarization with dedicated small LLM
MCP Tools (from 05): Reporter can call tools from remote MCP servers
Tracing (new feature): Every operation logged to
./traces/{run_id}.jsonl
Trace Structure:
Each line in the trace file is a JSON object:
{"timestamp": "...", "type": "llm_call", "agent": "planner", "model": "llama-70b", ...}
{"timestamp": "...", "type": "tool_call", "agent": "planner", "tool": "propose_strategy", ...}
{"timestamp": "...", "type": "hitl_approval", "agent": "planner", "status": "approved", ...}
{"timestamp": "...", "type": "memory_op", "agent": "runner", "op": "summarize", ...}
{"timestamp": "...", "type": "dag_transition", "from": "planner", "to": "runner", ...}
Trace Viewer:
Interactively explore the trace to understand agent reasoning, tool execution paths, approval decisions, and memory operations in real-time.
Installation
See Example 01 (same dependencies).
System Description
Tested on HPE Cray EX:
Node 0 (main inference): 2 Nvidia A100 GPUs, AMD EPYC 64-core CPU
Node 1 (summarizer): 1 Nvidia A100 GPU, AMD EPYC 64-core CPU
Total: 2 compute nodes required
How to Run
Step 1: Edit configuration
Open 06_full_pipeline.py and set:
MODEL_NAME(main reasoning model)SUMMARIZER_MODEL_NAME(small model for summarization)MCP server URLs (if using external tools)
Step 2: Generate auth token (if using MCP)
echo "your-mcp-server-token" > token.txt
Step 3: Allocate nodes
salloc --nodes=2 --exclusive
Step 4: Run
# Without MCP:
dragon 06_full_pipeline.py
# With MCP (e.g., Jupyter server):
dragon 06_full_pipeline.py http://jupyter-mcp-server:8888
Example output:
$ dragon 06_full_pipeline.py
Starting 2-node infrastructure...
Node 0: Main inference service (70B model) initialized
Node 1: Summarizer service (7B model) initialized
Agent 'planner' started
Agent 'runner' started
Agent 'analyzer' started
Agent 'reporter' started (with MCP: jupyter)
Task: 'Execute complex analysis workflow'
Planner proposing strategy... ✓
HITL: Approve planner's tool call? [A/R/F]: A
Runner executing strategy... ✓
HITL: Approve runner's tool call? [A/R/F]: A
Analyzer reviewing results... ✓
Reporter generating final report... ✓
Function node: save_report() executed
Workflow completed
Trace written to: ./traces/20260421-163045.jsonl
Step 5: Inspect trace (optional, in another terminal)
python -m dragon.ai.agent.observability --trace-file ./traces/20260421-163045.jsonl
HITL approval client (in another terminal, during run)
python -m dragon.ai.agent.hitl_client --tcp 127.0.0.1:9000
Use to approve/reject tool calls in real-time or add feedback.
Next Steps
Deploy to production using this example as a blueprint
Customize memory strategies for your task’s characteristics
Add more MCP servers for access to domain-specific tools
Monitor trace files to detect failure patterns and refine agents
Replay traces for debugging and testing recovery scenarios