GitLab Duo Agent Platform orchestrates and automates complex tasks through agentic flows. A key part of the platform is the Flow Registry, a declarative configuration framework, built from reusable components, that compiles YAML into fully functional LangGraph flows. By using Flow Registry, agent builders — both our GitLab engineers and our customers can use declarative YAML configurations instead of repetitive, ad-hoc Python implementations. Flow Registry turns bespoke state management and agent wiring duplicated across agents into a set of reusable components and primitives available to agent builders.

Using Flow Registry has reduced our own code-per-agentic-flow by 45%. What is this translating to?

  • Faster iteration speed due to a declaration framework with reusable components and primitives.
  • Increased reliability because one-off implementation mistakes and boilerplate bugs are handled on a component level.
  • Lower maintenance cost because platform improvements are made once, but benefit all agents.
  • Backward compatibility for our functionalities, for both our GitLab-authored foundational flows and also customers’ custom flows, because of the abstraction layer.

All of these benefits are available to customers orchestrating and building agents on GitLab Duo Agent Platform.

In this article, we share the architectural principles and lessons from this effort and how to apply them in your environment.

LangGraph as the foundation for GitLab Duo Agent Platform

After releasing GitLab Duo Code Suggestions and Duo Chat, we dug into a then-novel technology, autonomous agents. We researched available AI frameworks and selected LangGraph, an agent runtime and low-level orchestration framework from LangChain, as the foundation for GitLab Duo Agent Platform.

LangGraph's rich feature set, which includes a broad range of model adapters, durable execution, and traceability, combined with an excellent level of engineering autonomy, brought all the necessary building blocks we looked for to start GitLab Duo Agent Platform development.

During the initial months, GitLab engineers, empowered by LangGraph, swiftly built the foundations of Duo Agent Platform, and before long the team shipped four agentic flows:

  • Software Development Flow
  • Duo Agentic Chat
  • Convert to GitLab CI/CD Flow
  • Developer Flow

We also quickly realized a critical gap that low-level frameworks such as LangGraph do not address: a lack of structure to support consistent development at scale.

With just four flows present, and a small engineering team working on GitLab Duo Agent Platform at that time, the codebase was growing rapidly. Every flow was implemented as an ad-hoc directed graph, turning into a web of interconnected nodes and edges. The early Duo Agent Platform codebase had no reusability, no composability, and little in the way of shared standards. It became very difficult to develop new features, and any horizontal platform-wide change seemed like an impossible task.

With every flow taking at least 450 lines of ad-hoc Python code and looking like this example, the team's velocity slowed down as engineers struggled to introduce changes, overwhelmed by complexity and coupling.

The graph's complexity spilled into the test suite, as well. Each test case depended on an execution propagating through a whole graph, which changed tests from a quality assurance safety net into a boogeyman that nobody wanted to look at.

It became clear to us that graphs used as an atomic building block at this low abstraction level are not a good match for a platform implementation. To support the scale we envisioned, it was necessary to introduce smaller units, that break down the complexity and reduce cognitive load put on platform engineers maintaining the project.

Furthermore, graphs with low-level nodes managing model API calls or executing function calls produced by said models, were not the right abstraction for AI engineers either, as they are more accustomed to terms like agents and agent orchestration.

Looking for a way out of that maze, we decided to separate those two concerns — AI engineering from platform development — with the introduction of a new layer of abstraction. To do so, we reviewed existing graphs and identified and extracted repeated structures (for example, cycles going between large language model (LLM) calls and tool execution, implementing agent loops). The refactor brought some relief, as the most complex files had been broken down into smaller pieces that formed the new abstraction layer.

However, the platform was still far from a scalable state. The extracted graph pieces unfortunately operated with their own state structures, tightly coupled with the flow from which they originated. This prevented us from reusing extracted entities between different flows, and we were concerned that at that point every new flow would be more likely to create its own set of pieces, rather than be composed from ones that already existed. The system was neither collaborative nor efficient, and it was not sustainable in that state for a longer period of time.

That realization made it apparent — we had to put more effort in, continue to evolve the architecture, and provide clear development guidelines. At that time we already knew that Duo Agent Platform flows wouldn't be exclusively built by other product teams, but that a wider GitLab community would be invited to contribute as well.

Abstracting LangGraph details behind the new Flow Registry framework

Equipped with the past experience, and inspired by ambitious goals, my teammate Alexander Chueshev and I went back to the drawing board, and rethought the system. We set out to introduce a solution that is highly collaborative, composable, and optimized for AI development efficiency.

We wanted this new iteration to hide low-level LangGraph implementation details, and to stop bothering developers with nodes or edges. The system ought to speak their language — the language of AI engineering — with agents being a central component.

It was clear to us that AI development reasons in terms of agents, rather than nodes that invoke models, execute tools, etc. Drawing lessons from the past iteration, we decided to base the new framework on three pillars:

  1. Components
  2. Routers
  3. Shared state structure

We were convinced that if we were able to design them well, the new framework would be flexible enough to support any AI flow that users might want to build.

Pillar 1. AI engineering primitives as components

Components are the central and most important pillar of Flow Registry. They model common primitives such as agents, human-in-the-loop checkpoints, and fixed-logic steps. This pillar lifts the abstraction level to match terminology used within the AI engineering domain. Thanks to components agent builders no longer need to reimplement those primitives from scratch, but can declare them with YAML snippets that look like this example:

     - type: AgentComponent
        name: "developer_agent"
        prompt_id: "developer_agent_prompt"
        inputs:
          - from: "context:goal"
            as: "goal"
          - from: "context:project_id"
            as: "project_id"
        toolset:
          - "read_file"
          - "find_files"
          - "edit_file"
          - "run_command"
          - "create_merge_request"

Under the hood, the AgentComponent is still a piece of a LangGraph’s graph, whose simplified structure is shown in the diagram below. However, now its implementation complexity is hidden from agent builders, who operate with a more familiar primitive. The same architectural boundaries also benefit framework maintainers, giving them more freedom to modify and extend the underlying implementation, with changes propagating to flows transparently.

flowchart LR
    %% External input/output
    input((inputs<br>from<br>shared state)) --> LLMCall
    End --> output((outputs<br>to shared state))

    %% Prompts
    Prompt["You are expert<br>software<br>engineer ..."] --> LLMCall
    subgraph Prompts
        direction TB
        style Prompts stroke-dasharray: 4 4, stroke:#3CB371
        Prompt
    end

    %% LLM and internal component
    LLMCall --> End
    LLMCall --> RunTools
    RunTools --> LLMCall

    subgraph Component
        direction LR
        LLMCall[LLM Call]
        RunTools[Run Tools]
        End[END]
    end

    %% Tools
    EditFile --> RunTools
    ReadFile --> RunTools

    subgraph Tools
        direction LR
        style Tools stroke-dasharray: 4 4, stroke:#1E90FF
        EditFile[Edit file]
        ReadFile[Read file]
    end

The agent as a component, with the ability to delegate work to subagents, is already a powerful base delivered by Pillar 1 alone. Many contemporary agent platforms consider it a complete and sufficient offering. However, GitLab has larger ambitions for Duo Agent Platform, which Flow Registry realizes with the next two pillars.

Pillar 2. Routers to orchestrate components into flows

Flow Registry Routers enable agent builders to orchestrate multiple specialized agents, or even agentic teams, into a flow to model highly complex business, or software development processes. Even though the largest contemporary models are powerful enough to drive complex assignments on their own, a recent rise in popularity of subagent architecture shows that there are many benefits of assembling multiple agents to collaborate over a single task.

To demonstrate a practical example, let’s take a look at GitLab’s foundational flow: Fix pipeline. This flow is configured with an automated trigger to triage, and fix failing CI pipelines. Because CI pipelines can be very complex, not every failure requires any code change to be resolved, for example sometimes a dependency service might be not responsive, and a plain retry is enough to fix a failure. To acknowledge that dual approach, the flow branches early based on an agent that acts as a judge’s decision. The judge agent's ruling on whether a failure is actionable is then used by Flow Registry Routers to navigate flow execution into the correct branch.

routers:
 - from: "fix_pipeline_context"
    condition:
      input: "context:fix_pipeline_context.final_answer.decision"
      routes:
        "add_comment": "fix_pipeline_add_comment"
        "create_plan": "fix_pipeline_checkout_existing_branch"
        "direct_code_suggestions": "fix_pipeline_code_suggestions"
        "no_action": "end"
        "default_route": "end"

It is true that state-of-the-art models should be able to make similar decisions and act on them simultaneously. However, thanks to multi-agent architecture, agent builders can capitalize on the following benefits:

  1. Smaller, cheaper models can replace the largest and most expensive ones — a compounding cost advantage for high-frequency automated flows running hundreds of times per day.
  2. Security posture improves through role separation — read and write capabilities can be split across distinct agents.
  3. Process guardrails can be enforced when the workflow is known upfront, reducing reliance on model judgment for structured tasks.

Pillar 2 gives agent builders a choice: Use a simple flow architecture with powerful models, or offload complexity from models prompts into explicit flow structure — catering to a broad range of possible use cases, cost targets, and risk profiles.

Pillar 3. Shared state structure

The third and final pillar of Flow Registry is a shared state structure that acts as a communication protocol between components. Without it, the previous two pillars could not function, because components would lack a reliable way to communicate. Referring back to the Fix pipeline example: The judge agent's ruling would be of little value if it could not be reliably forwarded to a Flow Registry Router. More broadly, data produced by one agent is often required by subsequent ones, making a well-defined communication contract essential.

Flow Registry state structure includes a special catchall attribute called context, which behaves like a nested key-value store (or a JSON object) granting components a versatile storage space. To further complement context attribute flexibility, Flow Registry introduced a dot-notation declarative access to context, a convention familiar from other domains (such as GitLab CI Functions), where access to shared key-value storage must be expressed within static configurations.

To complete the third pillar, convention is required: Flow Registry supports flexible read operations from shared state via said dot-notation, however all writes follow strict rules, providing a set of stable, predictable outputs on which agent builders can rely. To see that in practice, let’s take a look again at a piece of Flow Registry config for another foundational flow: Code review.

# …

 # Step 5: Fetch lightweight MR metadata (file paths + custom instructions)
  - name: "fetch_mr_metadata"
    type: DeterministicStepComponent
    tool_name: "build_review_merge_request_context"

# ….

  # Step 6: Transform prescan results into structured JSON for review consumption
  - name: "analyze_prescan_results"
    type: AgentComponent
    prompt_id: "analyze_prescan_codebase_results"
    prompt_version: "^1.0.0"
    inputs:
      - from: "context:fetch_mr_metadata.tool_responses"
        as: "mr_context"
      - from: "context:prescan_codebase.tool_responses"
        as: "prescan_tool_responses"
        optional: True
    toolset: []
    ui_log_events:
      - "on_agent_final_answer"

# …..

routers:
 #  ….
  - from: "fetch_mr_metadata"
    to: "analyze_prescan_results"

Code Review’s agent analyze_prescan_results requires data pulled by a preceding fixed step action fetch_mr_metadata, that dependency is expressed via inputs declared for analyze_prescan_results agent

    inputs:
      - from: "context:fetch_mr_metadata.tool_responses"
        as: "mr_context"

Here, dot-notation and strict output conventions work in tandem, giving agent builders a stable and predictable protocol for moving data between components within a flow.

From Python to YAML

Even though Flow Registry uses declarative YAML configurations, we started the design and rearchitecture in Python, and deferred any declarative configuration API to future iterations. However, once all three Flow Registry pillars came together within a single Python block, it became obvious to us that converting those declarations into a YAML config was just a step away, so we took it.

That change completely decoupled the Flow Registry framework from Python and LangGraph, offering a high-level abstraction syntax for declarative AI flow creation. It established a clean boundary between the platform still implemented on LangGraph foundations, and the external framework's declarative interface, which enabled AI engineers to operate with concepts more familiar to them.

Introduction of Flow Registry framework as a basis for GitLab Duo Agent Platform propelled the whole system from vanilla LangGraph per-use-case implementations into declarative YAML configs like this one behind GitLab Duo Developer Flow, which is currently operating in production.

With the platform decoupled from the framework API designed for AI engineers, the underlying Python codebase becomes shareable across all flows, and any improvements introduced to the engine itself are brought to all flows, further emphasizing the efficiency gains from the clear separation.

In addition, the per-flow code cost drops with every new flow added. At the time of writing, the ratio of Python source code per flow has been reduced by 45% in favor of Flow Registry — and it will keep improving with every new flow being built.

Finally, AI engineers and domain experts are no longer required to understand any of the underlying platform implementation details, nor do they need to implement any repetitive boilerplate Python code that would require its own test suite and maintenance. This was proven by almost 7,000 developers who signed up for the GitLab AI Hackathon earlier this year and submitted 600+ agents and flows.

Key learnings

A key observation we made is that modern AI engineering is still a very young branch of software development, in which common architectural patterns and paradigms haven’t fully been formed yet. However, it does not mean that already established good software engineering practices can’t be applied to AI engineering. In fact, as Flow Registry's story shows, reaching back to existing software engineering paradigms and practices, such as identifying repeated code, extracting it into named entities with clear roles within a system, and forming abstraction layers from them, can yield powerful results.

Beyond that, we would also like to share a few other takeaways that apply broadly to any team building agentic systems, while others are practical starting points for teams working with low-level frameworks like LangGraph.

General principles

  1. Some contemporary agentic frameworks, despite being very powerful, operate at too low a level of abstraction for AI engineering needs, conflating platform concerns with agent development.
  2. Separation of the execution platform from AI engineering enables experts in each domain to operate with more confidence and speed.

Practical starting points for low-level framework users

  1. Separation of AI flows from platform implementation can be started by extracting repeated structures from existing AI flows — agentic loops are a good place to start.
  2. A flexible shared data model can be achieved thanks to a key-value-store-like attribute introduced to the model, following patterns established by other orchestration frameworks even outside of the AI domain.
Try Flow Registry

To get a feel for how it all works in practice, visit the AI Catalog where GitLab exposes the resulting Flow Registry orchestration framework for anyone to build custom flows.

You can also try GitLab Duo Agent Platform for free.