How HagiCode integrated 13 Agent CLIs into a single system
How HagiCode integrated 13 Agent CLIs into a single system
It’s not really that hard, but then again, it’s not that simple either. Let’s talk about how we used a layered architecture to uniformly manage these diverse Agent CLIs like Claude Code, Codex, Copilot, and Gemini, while still being able to plug in a new one at any time.
Background
The story started suddenly, stemming from a pretty headache-inducing problem.
Agent CLIs have been popping up like bamboo shoots in recent years—Claude Code, OpenAI Codex, GitHub Copilot, Gemini CLI, Kimi, Qoder, Kiro… Every few months a new one emerges. As a project that wants users to “install one HagiCode and use the full suite of Agents,” we can’t just bet on a single CLI, but we also can’t write a complete set of logic from installation and health checks to scheduling for each CLI—the code would swell to the point of being unmaintainable, like a tangled ball of yarn that no one dares to touch.
More troublesome is that these CLIs have very different temperaments: some use stdio, some use gRPC, some only give you a shell entry, and streaming output formats all say different things. If we write judgments like if (provider == ClaudeCode) directly in business code, within half a year it will become a lump of “legacy code” that no one dares to touch. After all, who wants to touch a brick that looks like it’s crumbling?
To contain all these pain points, we made a decision: add a thin abstraction layer and shared runtime between the business layer and the specific CLI. This looks simple, but it directly determines whether HagiCode can quickly integrate new CLIs. I’ll elaborate on how to do this shortly.
About HagiCode
The solution shared in this article comes from our practical experience in the HagiCode project. HagiCode is an AI code assistant integration platform with a very pure goal—use one installation, one configuration, to integrate all mainstream Agent CLIs for users.
Where the number “13” comes from
Let’s start with a repeatedly asked number—why 13 Agent CLIs.
Actually, the answer is hidden in the AIProviderType enum, like bamboo shadows outside the window, as long as you’re willing to look, you can see it. The original definition looks like this:
public enum AIProviderType{ ClaudeCodeCli = 0, CodexCli = 1, GitHubCopilot = 2, CodebuddyCli = 3, OpenCodeCli = 4, IFlowCli = 5, // Deprecated HermesCli = 6, QoderCli = 7, KiroCli = 8, KimiCli = 9, GeminiCli = 10, DeepAgentsCli = 11, ReasonixCli = 12, PiCli = 13,}The enum has 14 values in total, but the IFlowCli=5 path is no longer viable. In AIProviderFactory, it’s explicitly blocked:
if (providerType == AIProviderType.IFlowCli){ throw new NotSupportedException("IFlowCli is no longer supported");}Combined with filtering through IsActivelySupportedProviderType(), the ones truly “alive” in the system are 13: Claude Code, Codex, GitHub Copilot, CodeBuddy, OpenCode, Hermes, Qoder, Kiro, Kimi, Gemini, DeepAgents, Reasonix, Pi.
This is the origin of “13.” It’s not a marketing number, it’s actually counted in the code. After all, numbers don’t lie, only we deceive ourselves.
Layered Architecture: Locking change in a cage
The core idea of connecting 13 CLIs is actually one sentence: make business code not care which specific one it’s calling.
We broke it down into six layers, looking from top to bottom:
1. Identity Layer — AIProviderType
The enum is the “ID number” for each CLI. Anywhere a CLI is mentioned, it’s identified with this enum value, with ToStringValue() / ToAIProviderType() for mutual conversion between strings and enums. Simple, yet indispensable.
2. Business Contract Layer — IAIProvider / IAIProviderFactory
The business side only recognizes the IAIProvider interface, which defines generic actions like “send a prompt, get a streaming response.” As for whether it’s Claude or Codex underneath, the business doesn’t care—like writing a letter, you just hand it out, who cares what the postman’s last name is?
3. Adapter Layer — *CliProvider
Each CLI corresponds to a thin adapter, like PiCliProvider, ReasonixCliProvider, ClaudeCodeCliProvider. These adapters have very little to do: translate generic business requests into parameters that specific CLIs understand, and translate specific CLI output back. They’re intentionally kept very thin, so adding a new CLI basically means copying an existing one and tweaking it.
4. Shared Runtime Layer — ICliProvider<TOptions>
This layer in HagiCode.Libs is where the real dirty work happens: cross-platform process launching, handling stdio transmission, parsing streaming output, handling timeouts and retries. All adapters reuse the same runtime, so when integrating a new CLI, the process management part basically doesn’t need rewriting.
To use an analogy, the adapter layer is the “interpreter,” and the shared runtime layer is the “courier company.” The interpreter only cares about making things clear; how the package is delivered, whether there’s traffic on the road, that’s the courier company’s business. Each does their job, and the world becomes peaceful.
5. Factory Routing Layer — AIProviderFactory
A switch in CreateProvider instantiates the corresponding adapter based on AIProviderType, along with validating IsConfigured. This is the only place that “knows the specific type,” strictly isolated in the factory. Changes are only allowed to happen in one corner, everywhere else stays clean.
6. Catalog / UI Projection Layer — main-professions.yaml
This layer is interesting, it’s not code, it’s data.
The main profession catalog (role profiles like “I’m a frontend developer,” “I’m a backend developer,” “I’m a full-stack developer”) is driven by the preset file main-professions.yaml, read through HeroPrimaryProfessionPresetProvider, then projected to the frontend UI. Adding a new main profession doesn’t require changing a line of code, just modify the YAML. Data replaces code, worry-free.
By the way, this is the biggest refactoring in HagiCode. Early versions had a code-internal registry called
AgentCliInstallRegistry, but we later found the maintenance cost was too high—with more code, people get tired—so the whole thing was overturned and replaced with a data-driven + health monitoring solution. This is why HagiCode can now quickly expand profession types.
How the installation issue is solved
All 13 CLIs need to be installed, and each has different official installation methods—this is another mountain.
Our approach is Docker Compose pre-installation + external management as fallback. The image pre-installs mainstream CLIs (Claude Code, Codex, Copilot, CodeBuddy, OpenCode, Qoder, Kiro, Kimi, Gemini, Pi), users can pull the image and use it directly without typing commands one by one. Once installed, the mood naturally improves too.
For those that need separate installation in the local environment, the installation command matrix roughly looks like this (verified with official documentation):
| CLI | Official Installation Method |
|---|---|
| Claude Code | npm install -g @anthropic-ai/claude-code |
| Codex | npm install -g @openai/codex |
| GitHub Copilot | npm install -g @github/copilot |
| CodeBuddy | npm install -g @tencent-ai/codebuddy-code |
| OpenCode | npm i -g opencode-ai@latest |
| Qoder | npm install -g @qoder-ai/qodercli |
| Kiro | curl -fsSL https://cli.kiro.dev/install | bash |
| Kimi | curl -LsSf https://code.kimi.com/install.sh | bash |
| Gemini | npm |
| Hermes | Official script, keep docs-only fallback |
| DeepAgents / Reasonix | See respective official documentation |
The frontend PrimaryProfessionCard.tsx also changed accordingly—it now has no “Install CLI” button, but instead displays CLI availability, version detection results, and a fallback prompt indicating “this CLI is externally managed.” That is, whether it can be installed is the system layer’s responsibility, and UI only truthfully reflects the status. Status and logic written separately will eventually fall out of sync, so why bother?
What needs to be done to add a new CLI
Putting it into practice, adding a new CLI in HagiCode basically takes these steps:
- Add an enum value in
AIProviderType - Copy an existing
*CliProvider, modify it for the new CLI’s parameters and output parsing - Add a routing line in the
switchinAIProviderFactory - If entering the main profession catalog, configure it in
main-professions.yaml - Add an installation command in the image (or go with external management fallback)
The whole process involves no more than two hundred lines of core code changes—this is the true value of this abstraction. Each additional CLI integrated has very low marginal cost, with no changes needed to business code. All roads lead to Rome, our path is just a bit easier to walk.
Summary
Looking back, “connecting 13 CLIs” sounds daunting, but breaking it down, it’s actually just two layers of work:
One layer is isolating change—through AIProviderType enum + IAIProvider contract + thin adapters + shared runtime, decoupling business code from specific CLIs; the other layer is making configuration data-driven—using YAML presets like main-professions.yaml to drive the catalog and UI, avoiding code changes every time something is added.
This solution is what we stabilized through pitfalls and several iterations of actual HagiCode development. If you’re working on a similar “multi-provider integration” system, I hope this layered approach gives you some reference. After all, Agent CLIs will continue to emerge in the coming years, and an architecture that can quickly integrate new CLIs is far more important than “how many are currently supported”…
开始使用 HagiCode
一次安装,几分钟上手
HagiCode for Windows 在 Microsoft Store 免费提供。打开商店即可安装并保持更新;也可以先对比各版本与定价,再决定从哪个渠道开始。