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:

Listing 33 06_full_pipeline.py: All features combined with tracing
  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:

  1. Multi-agent DAG (from 02): Four agents form a pipeline with automatic upstream result flow

  2. HITL Approval (from 03): Planner and runner require human review for tool calls

  3. Memory Management (from 04): Planner uses sliding window, runner uses summarization with dedicated small LLM

  4. MCP Tools (from 05): Reporter can call tools from remote MCP servers

  5. 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