Back to Works

CSG Data Agent

2025 Ongoing

An AI agent built for the Cancer Screening Group at Lunit to let internal teams and external stakeholders across the organization query data, metrics, and specifications through natural language. Instead of digging through dashboards or documentation, users ask questions and the agent delegates to the right specialist to find an answer.

I lead this project — scoping requirements, writing the technical plan, defining tasks and timelines, selecting frameworks and models, authoring evaluation guides, implementing the core system, and coordinating with team members on integrations and tooling.

The primary integration target is our internal DAP (Data Analytics Platform) — a tool that applies pre-defined analytical functions over internal databases. By connecting the agent to DAP, users can explore data without writing SQL or knowing the underlying schema. Future integrations include Confluence MCP for internal documentation and wiki search.

The entire system runs on local models only. No data leaves the internal network — no cloud API calls, no external providers. This was a hard constraint from the start given the sensitivity of clinical and product data involved.

Architecture

The agent follows a subagent delegation pattern built on LangChain DeepAgents, which compiles down to a LangGraph graph. An orchestrator sits at the top: it receives the user query, writes a structured TODO plan, then delegates each task to an isolated specialist subagent via a task() tool call.

Each subagent operates in isolation — it only sees its task string, not the full chat history. Subagents write their findings to a virtual shared filesystem (write_file / read_file / ls), which acts as the shared memory layer between the orchestrator and its specialists. The orchestrator synthesizes the final answer from these files once all delegated tasks complete.

Parallel delegation is a first-class concern: when multiple independent sub-tasks exist, the orchestrator emits them all in a single iteration, letting LangGraph execute them concurrently rather than serially.

Evaluation-Driven Development

Development is structured around evaluation from the start. Before building new capabilities, we define ground-truth datasets and scoring criteria first — then implement until evals pass. This keeps the system honest, surfaces failure modes early so we know where to focus, and a proper harness makes those eval runs fast enough to run regularly.

The eval spans multiple parallel tracks, each on its own timeline:

Failures are tracked against a taxonomy — wrong tool, right tool wrong parameters, hallucinated tool, failed to disambiguate, faithfulness drift, scope leak, prompt injection success — so each failure class maps to a specific fix surface rather than "the model got it wrong."

Observability

The agent streams structured events back to the caller: trace events for each agent/tool step, digest events for streaming tokens in the final answer, sources for extracted references, and done on completion. A gating mechanism prevents planning tokens from leaking into the user-facing stream — only the synthesized answer is emitted.

Tracing is currently handled via LangSmith. The plan is to replace it with Langfuse — a self-hosted alternative — to keep observability data on-prem alongside the rest of the stack.

Stack
LangChain DeepAgents, LangGraph, Ollama, LangSmith, Python 3.13, Pydantic, uv, pytest, ruff, pyright
Integrations
  • DAP (Data Analytics Platform) — internal analytics tool exposing pre-built functions over CSG databases
  • Confluence MCP — planned; natural language search over internal docs and specs
  • Local LLMs via Ollama — air-gapped, no external providers
Roadmap
  • Evaluation Harness — automated eval runs, ground-truth datasets, prompt versioning, and A/B scoring
  • Langfuse — replace LangSmith with self-hosted Langfuse to keep tracing data on-prem
  • Data Curation — curated eval and fine-tuning datasets from real user queries
  • SGLang — scaled local inference with better throughput and batching
  • Deep Agent Skills — specialized skill modules for domain-specific reasoning patterns
Organization
LUNIT Inc. — Cancer Screening Group

System Architecture

graph TB
    User["👤 User Query\n(CLI / API)"]

    subgraph Core ["🤖 CSG Data Agent"]
        Orch["Orchestrator\nwrite_todos · read_todos · task()"]
        VFS["📁 Virtual Filesystem\nread_file · write_file · ls"]
        Stream["📡 Streaming\ntrace · digest · sources · done"]
    end

    subgraph Specialists ["⚙️ Specialist Subagents (isolated context)"]
        DataAgent["Data Agent\n(DAP Tools)"]
        KnowAgent["Knowledge Agent\n(Confluence MCP)"]
    end

    subgraph Backends ["🔧 Backends"]
        DAP["DAP\nData Analytics Platform"]
        CMCP["Confluence MCP\nInternal Docs"]
        LLM["Local LLMs\nOllama · air-gapped"]
    end

    User --> Orch
    Orch -->|"parallel task() delegation"| DataAgent
    Orch -->|"parallel task() delegation"| KnowAgent
    Orch <-->|"shared memory"| VFS
    DataAgent <-->|"write findings"| VFS
    KnowAgent <-->|"write findings"| VFS
    DataAgent --> DAP
    KnowAgent --> CMCP
    Core --> Stream
    Stream --> User
    Orch --- LLM
    DataAgent --- LLM
    KnowAgent --- LLM

    style Orch fill:#2d3748,stroke:#f59e0b,color:#f59e0b
    style VFS fill:#2d3748,stroke:#9ca3af,color:#e2e8f0
    style Stream fill:#2d3748,stroke:#9ca3af,color:#e2e8f0
    style DataAgent fill:#2d3748,stroke:#9f7aea,color:#e2e8f0
    style KnowAgent fill:#2d3748,stroke:#9f7aea,color:#e2e8f0
    style DAP fill:#2d3748,stroke:#9ca3af,color:#e2e8f0
    style CMCP fill:#2d3748,stroke:#9ca3af,color:#e2e8f0
    style LLM fill:#1a202c,stroke:#4b5563,color:#6b7280
    style Core fill:#1a202c,stroke:#f59e0b,color:#d1d5db,stroke-width:2px
    style Specialists fill:#1a202c,stroke:#9f7aea,color:#d1d5db,stroke-width:2px
    style Backends fill:#1a202c,stroke:#4b5563,color:#6b7280,stroke-width:1px