Skip to content

Select language

Current language: English

From Zero: How to Integrate Reasonix CLI into HagiCode System

Edit page
HagiCode for Windows Microsoft Store artwork
HagiCode for Windows is now on Microsoft Store
HagiCode for Windows is officially live on Microsoft Store. Windows users can install it directly from the storefront and stay on the store-managed update path. Open the listing and take a look.
Open Microsoft Store
Awesome Site banner: community-curated open-source collections
Awesome Site: Curated Open-Source Collections
Find your next tool, idea, or inspiration in community-curated open-source collections. Pick a topic, read in your language, and follow the link back to the original source.
Explore Awesome Site
Awesome Design banner: a gallery of DESIGN.md design systems
Awesome Design: A DESIGN.md Gallery
Browse curated design systems through live previews and README references, then copy a DESIGN.md into your project and let your AI agent build a page that matches.
Browse the Design Gallery
OpenSpec banner: agree on the spec, then build with AI
OpenSpec: Agree on the Plan Before Your AI Writes Code
OpenSpec is a lightweight spec layer for AI coding. Describe a change, review the proposal, specs, design, and tasks your AI drafts, then build it and archive it into living specs. No rigid phases, and it fits existing codebases and the AI assistants you already use.
Explore OpenSpec
OmniRoute Docs banner: one endpoint for many AI providers
OmniRoute Docs: One Endpoint for Many AI Providers
Learn how OmniRoute routes many AI providers and models through a single endpoint, with free-tier support and automatic fallback so your coding tools keep working.
Read the OmniRoute Docs

From Zero: How to Integrate Reasonix CLI into HagiCode System

This article shares the complete technical practice of integrating Reasonix CLI as a first-class Agent Provider into the HagiCode system, covering three-layer architecture design, key technical decisions, and frontend and backend implementation details.

Background

Reasonix CLI, well, it’s quite an interesting thing. It’s an AI code assistant tool based on ACP (Agent Communication Protocol) that provides powerful streaming and session management capabilities. Actually, in the HagiCode.Libs layer, we’ve already completed its underlying implementation, but these components are still in isolated states, like beautiful pearls that haven’t been strung into a necklace yet. Users cannot use it through Hero profession selection, session execution chains, or monitoring panels, which is somewhat regrettable.

The problem we face is: how to elevate Reasonix to a first-class Agent Provider on par with Codex, Hermes, etc., and achieve complete backend routing and frontend display? This isn’t as simple as registering an enum value, but requires building a complete chain from underlying abstractions to the user interface. It’s like building a house - you can’t just pour a foundation and call it done, you need to build walls and put on a roof.

The challenge of this integration lies in the fact that Reasonix, as a local CLI tool, has its own personality and temperament. For example, it doesn’t need connection strings; all parameters are configured by the user at runtime. It might not even be installed and requires graceful degradation handling. It’s compatible with anthropic series models but also has its own ACP-specific parameters like effort, budget, etc. It’s like a person with their own unique way of doing things - you can’t force it.

After careful architectural design and multiple rounds of discussion, we ultimately adopted a clear three-layer architecture solution and successfully integrated Reasonix into the system. This solution not only solved the immediate problem but also provided a reusable pattern for integrating similar CLI Providers in the future. Many things are like this - once you find the right method, the path ahead becomes much smoother.

About HagiCode

The solution shared in this article comes from our practical experience in the HagiCode project. HagiCode is an open-source AI code assistant project dedicated to providing developers with powerful code generation, refactoring, and optimization capabilities. During development, we encountered various technical challenges, and integrating Reasonix as a first-class Agent Provider was one of them. If you find the solution shared in this article valuable, it shows our engineering practices are quite good, so HagiCode itself is worth paying attention to.

Core Content

Technical Architecture Design

The system adopts a clear three-layer architecture to separate concerns, with each layer having clear responsibility boundaries:

HagiCode.Libs Layer: This layer is already complete, providing the abstraction and concrete implementation of CLI providers. It defines the ICliProvider<ReasonixOptions> interface, implements ReasonixProvider to handle ACP streaming and session management, and supports parameters such as effort, budget, yolo, transcript, etc. The responsibility of this layer is to provide stable, reusable underlying capabilities without involving any business logic. Like the foundation of a house, although invisible, it’s very important.

hagicode-core Layer: This is the focus of our integration this time. It is responsible for bridging the underlying abstractions to the system’s unified interface. Specific work includes registering the AIProviderType.ReasonixCli = 12 enum value, creating ReasonixCliProvider as a thin adapter to bridge the Libs layer, implementing ReasonixGrain to handle session state and execution flow, and integrating with the Hero system for parameter mapping and configuration management. The core of this layer is to coordinate various components and build a complete business chain. Like load-bearing walls in a house, connecting various parts together.

web Layer: Responsible for displaying to users and collecting configurations. We need to regenerate OpenAPI types to support the new enum value, implement visual type mapping so Reasonix has its own icon and display name, create CLI parameter configuration forms so users can configure various parameters, and add multilingual support. The focus of this layer is user experience and interaction design. Like house decoration, whether it’s done well directly affects how comfortable it is to live in.

This layered design allows each layer to focus on its own responsibilities, reducing system complexity and making subsequent maintenance and expansion easier. Actually, many times, clarifying things makes them simpler.

Key Technical Decisions

During the implementation process, we made several key technical decisions that had important impacts on the final architecture and user experience.

Decision 1: Use Dedicated Grain

We created an independent ReasonixGrain : IReasonixGrain, IExecutorStreamGrain instead of trying to reuse a shared Grain. This decision follows the established pattern of the system’s existing 11 providers. Although it might seem like there could be some code duplication, a dedicated Grain allows us to make fine-grained control for Reasonix’s characteristics, such as its unique session binding mechanism and ACP message mapping. We also defined an empty response DTO ReasonixResponse as a type discriminator, which doesn’t contain actual data but plays an important role in the type system. Like everyone having their own room, even if empty, it’s their own space.

Decision 2: Don’t Create Dedicated Settings Class

Unlike some providers that need connection strings, all of Reasonix’s configurations are set by the user at runtime and don’t require validation at startup. Therefore, we didn’t create a dedicated Settings class, but instead stored all configurations in the AIProviderOptions.Providers[ReasonixCli].Settings dictionary. This pattern is consistent with other local CLI providers like Qoder, Kiro, Kimi, etc., simplifying the code structure and avoiding unnecessary abstraction layers. Supported setting keys include: effort, budgetUsd, transcriptPath, enableYolo, arguments, startupTimeoutMs, reasoning. Sometimes simpler is better.

Decision 3: Provider Strategy Health Monitoring

Reasonix is a CLI installed locally by the user, and might not be installed at all or not in the system PATH. In this case, we shouldn’t directly throw an error but should gracefully degrade. We use the Provider strategy to check if the CLI is available through CommandUtil.TryResolveExecutablePath. If the check fails, the UI will display as “unavailable” but won’t affect other parts of the system. This design makes the system more robust and gives users clear feedback. After all, no one wants the entire system to crash because of a small issue.

Decision 4: Economic System Classification

In the HagiCode system, different providers have different economic system classifications. We decided to let Reasonix default to using the 'claude' economic system classification because Reasonix itself is compatible with anthropic series models. Currently, only Codex and Copilot have dedicated economic system classifications, while other providers reuse existing classifications. This maintains system simplicity while correctly handling billing and cost statistics. Reusing is also wisdom - you don’t need to start everything from scratch.

Decision 5: Model Compatibility

Reasonix supports multiple models through the --model flag, especially the anthropic series. We added compatibility mapping in secondary-professions.index.json so users can select these models in Reasonix. This design respects Reasonix’s capabilities while maintaining system consistency, allowing users to smoothly use various models without needing to understand underlying differences. Users have it hard enough - let’s keep it simple for them.

Backend Implementation Details

The backend implementation is divided into several key parts, each with its unique technical points.

Enum and Type Registration

First, we need to register the new Provider type in the system:

AIProviderType.cs
public enum AIProviderType
{
// ... other providers
ReasonixCli = 12,
}
// AIProviderTypeExtensions.cs
private static readonly Dictionary<string, AIProviderType> _typeMap = new()
{
// ... other mappings
["Reasonix"] = AIProviderType.ReasonixCli,
["reasonix"] = AIProviderType.ReasonixCli,
["reasonix-cli"] = AIProviderType.ReasonixCli,
["ReasonixCli"] = AIProviderType.ReasonixCli,
};

This enum value needs to be coordinated with other concurrent changes to avoid conflicts. We chose the value 12 because it’s the next available number. Like queuing, there needs to be an order.

Thin Adapter Implementation

ReasonixCliProvider is the key component connecting the Libs layer and the system’s unified interface:

public sealed class ReasonixCliProvider : IAIProvider, IVersionedAIProvider, IAsyncDisposable
{
private static readonly IReadOnlyList<string> SupportedSettingKeys =
[
"effort",
"budgetUsd",
"transcriptPath",
"enableYolo",
"arguments",
"startupTimeoutMs",
"reasoning"
];
private readonly ICliProvider<ReasonixOptions> _provider;
private readonly ConcurrentDictionary<string, string> _sessionBindings = new(StringComparer.Ordinal);
public async IAsyncEnumerable<AIStreamingChunk> StreamCoreAsync(
AIRequest request,
string? sessionId = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var options = BuildOptions(request, sessionId);
await foreach (var message in _provider.StreamAsync(options, cancellationToken))
{
yield return MapToStreamingChunk(message);
}
}
private ReasonixOptions BuildOptions(AIRequest request, string? sessionId)
{
return new ReasonixOptions
{
ExecutablePath = GetExecutablePath(),
WorkingDirectory = GetWorkingDirectory(),
Model = _config.Model,
Effort = _config.Settings.GetValueOrDefault("effort", "medium"),
Budget = _config.Settings.GetValueOrDefault("budgetUsd", 10.0),
Yolo = _config.Settings.GetValueOrDefault("enableYolo", false),
TranscriptPath = _config.Settings.GetValueOrDefault("transcriptPath"),
Arguments = _config.Settings.GetValueOrDefault("arguments", ""),
StartupTimeout = GetStartupTimeout(),
EnvironmentVariables = _environmentVariables,
CessionId = sessionId ?? GetCessionId()
};
}
}

The key responsibilities of this adapter are:

  1. Validate configuration parameters and reject unsupported setting keys
  2. Maintain session binding relationships and support session recovery
  3. Map ACP messages to the system’s unified format
  4. Use ProviderErrorAutoRetryCoordinator to implement automatic retry

Like a translator, translating one language to another while ensuring the meaning is accurate.

Orleans Grain Implementation

ReasonixGrain is responsible for handling session state and execution flow:

public class ReasonixGrain : Grain, IReasonixGrain, IExecutorStreamGrain
{
private readonly Dictionary<string, ExecutorToolLifecycleStatus> _toolLifecycleState =
new(StringComparer.Ordinal);
public IAsyncEnumerable<ReasonixResponse> ExecuteCommandStreamAsync(
string command,
string? heroId = null,
CancellationToken token = default,
string? executionMessageId = null,
string? systemMessage = null,
Dictionary<string, string>? requestSettings = null)
{
var request = BuildRequest(command, isEdit: false, heroId, executionMessageId, systemMessage, requestSettings);
return SendAsync(request, heroId, token);
}
private async IAsyncEnumerable<ReasonixResponse> SendAsync(
AIRequest request,
string? heroId,
[EnumeratorCancellation] CancellationToken token)
{
_cancellationTokenSource = new CancellationTokenSource();
var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(token, _cancellationTokenSource.Token);
var provider = await ResolveReasonixProviderAsync(heroId);
await foreach (var chunk in provider.StreamAsync(request, linkedToken.Token))
{
var response = BuildChunkResponse(chunk);
yield return response;
}
}
private async Task<IAIProvider> ResolveReasonixProviderAsync(string? heroId)
{
// Hero-aware configuration fallback logic
var config = await HeroProviderResolver.ResolveAsync(AIProviderType.ReasonixCli, heroId);
return _aiProviderFactory.CreateProvider(AIProviderType.ReasonixCli, config);
}
}

The core functions of the Grain include:

  1. Use [PersistentState("reasonix-interop")] to maintain session state
  2. Implement Hero-aware configuration fallback logic
  3. Track tool lifecycle status
  4. Support cancellation token chains, ensuring execution can be interrupted in a timely manner

Like a housekeeper, arranging things neatly.

Hero System Integration

The Hero system is HagiCode’s profession configuration system, and we need to integrate Reasonix into this system:

HeroAppService.cs
// Family inference
AIProviderType.ReasonixCli => "reasonix"
// Managed CLI parameters
ManagedCliParameterKeysByProvider[AIProviderType.ReasonixCli] =
["binary", "effort", "budgetUsd", "transcriptPath", "enableYolo", "arguments", "startupTimeoutMs"];
// Managed model parameters
ManagedModelParameterKeysByProvider[AIProviderType.ReasonixCli] =
["model", "reasoning"];

In the profession directory configuration file main-professions.yaml:

- Id: "profession-reasonix"
Name: "Reasonix"
Family: "reasonix"
Summary: "hero.professionCopy.primary.reasonix.summary"
Icon: "executor-avatar:Reasonix"
SourceLabel: "hero.professionCopy.sources.aiProvidersReasonixCli"
ProviderType: "ReasonixCli"
SortOrder: 130
DefaultEnabled: true
DefaultParameters:
binary: "reasonix"
effort: "medium"
enableYolo: "false"
startupTimeoutMs: "15000"

With this configuration, users can see the Reasonix option in the Hero configuration interface and make personalized settings. Like registering a person’s household registration - with identity, they can live normally in society.

Frontend Implementation Details

The frontend implementation is mainly responsible for user interaction and display, and is also divided into several key parts.

Type Generation and Visual Mapping

First, we need to regenerate OpenAPI types:

终端窗口
npm run generate:api:once

This will generate type definitions containing REASONIX_CLI = 'ReasonixCli'. Then in visual mapping:

executorTypeAdapter.ts
export type ExecutorVisualType = 'Claude' | 'Codex' | 'Copilot' | 'Reasonix' | ...;
export const resolveExecutorVisualTypeFromProviderType = (
providerType: PCode_Models_AIProviderType
): ExecutorVisualType => {
switch (providerType) {
// ... other cases
case PCode_Models_AIProviderType.REASONIX_CLI:
return 'Reasonix';
}
};

This way Reasonix has its own visual type and can display corresponding icons and styles. Like everyone having their own ID photo.

Configuration Form Implementation

In HeroCliEquipmentForm.tsx, we added a dedicated configuration form for Reasonix:

case PCode_Models_AIProviderType.REASONIX_CLI:
return (
<>
<Form.Item name="binary">
<Input />
</Form.Item>
<Form.Item name="effort">
<Select>
<Select.Option value="none">None</Select.Option>
<Select.Option value="low">Low</Select.Option>
<Select.Option value="medium">Medium</Select.Option>
<Select.Option value="high">High</Select.Option>
</Select>
</Form.Item>
<Form.Item name="budgetUsd">
<InputNumber />
</Form.Item>
<Form.Item name="transcriptPath">
<Input />
</Form.Item>
<Form.Item name="enableYolo">
<Switch />
</Form.Item>
<Form.Item name="arguments">
<Input />
</Form.Item>
<Form.Item name="startupTimeoutMs">
<InputNumber />
</Form.Item>
</>
);

This form covers all parameters supported by Reasonix, and users can configure them according to their needs. Like tailoring clothes for a person - the fit is most important.

Multilingual Support

To allow international users to use it as well, we added multilingual support:

# locales/*/common/hero.yml
profession:
primary:
reasonix:
name: "Reasonix"
summary: "ACP-based AI code assistant"
parameters:
effort: "Compute Effort"
budgetUsd: "Budget (USD)"
transcriptPath: "Transcript Path"
enableYolo: "Enable YOLO Mode"

After all, if the language doesn’t work, even the best thing can’t be used.

Health Monitoring Mapping

The frontend also needs to display Reasonix’s health status:

healthApi.ts
export const MONITORING_CHANNEL_FALLBACKS = {
// ... other providers
reasonix: {
displayName: 'Reasonix',
icon: 'executor-avatar:Reasonix'
}
};
export const mapProviderTypeToMonitoringCliId = (
providerType: PCode_Models_AIProviderType
): string => {
switch (providerType) {
// ... other cases
case PCode_Models_AIProviderType.REASONIX_CLI:
return 'reasonix';
}
};

This way users can see Reasonix’s status in the monitoring panel, and if the CLI is not installed or unavailable, they will get clear prompts. Like a doctor checking a patient - discover and treat problems early.

Best Practices and Considerations

During the implementation process, we summarized some best practices and things to pay attention to.

Parameter Validation

ReasonixCliProvider must strictly validate configuration parameters and reject unsupported setting keys:

public void ValidateConfigurationOverrides(Dictionary<string, string?> overrides)
{
foreach (var key in overrides.Keys)
{
if (!SupportedSettingKeys.Contains(key))
{
throw new HeroProviderConfigurationException(
$"Unsupported setting key '{key}' for Reasonix provider");
}
}
}

This can prevent users from configuring wrong parameters and avoid runtime errors. Like a goalkeeper, you can’t let things that shouldn’t get in get in.

Session Binding Management

Use ConcurrentDictionary to manage session bindings and support session recovery:

_sessionBindings[cessionId] = sessionId;
// Bind existing sessions in subsequent requests
if (_sessionBindings.TryGetValue(cessionId, out var boundSessionId))
{
options.SessionId = boundSessionId;
}

This design allows users to resume previous sessions after interruption, providing a better experience. Like remembering a story so you can continue telling it next time.

Graceful Degradation Handling

The frontend should check CLI availability and give friendly prompts:

const reasonixAvailable = await healthApi.checkCliAvailable('reasonix');
if (!reasonixAvailable) {
showMessage('Reasonix CLI is not installed, please configure it in the system PATH');
}

Don’t let users encounter inexplicable errors - check in advance and give clear guidance. After all, no one wants to get stuck inexplicably.

Test Coverage

Comprehensive testing is key to quality assurance:

[Fact]
public async Task ExecuteCommandStreamAsync_WithValidCommand_StreamsReasonixResponse()
{
// Arrange
var grain = _grainFactory.GetGrain<IReasonixGrain>("test-cession");
var responses = new List<ReasonixResponse>();
// Act
await foreach (var response in grain.ExecuteCommandStreamAsync("help"))
{
responses.Add(response);
}
// Assert
responses.Should().NotBeEmpty();
responses.All(r => r.Kind == ExecutorResponseKind.Content).Should().BeTrue();
}

Such unit tests can verify that core functionality works correctly and prevent regression errors. Like doing practice problems before an exam - you need to ensure you really understand it.

Summary

Through this Reasonix integration practice, we successfully elevated a local CLI tool to a first-class Agent Provider in the system. Throughout this process, we followed established architectural patterns, made reasonable technical decisions, and ultimately achieved a fully functional, user experience-friendly integration solution.

The core value of this solution lies in:

  1. Clear three-layer architecture separates concerns and reduces complexity
  2. Dedicated Grain and thin adapter design maintains flexibility
  3. Graceful degradation and health monitoring improve user experience
  4. Comprehensive parameter validation and session management ensure reliability

For integrating other similar CLI Providers, this solution provides a reusable pattern. We hope this practice can help other developers, and we welcome everyone to exchange experiences in the HagiCode project.

Actually, many things are like this - they seem difficult at first, but as long as the method is right and you take it step by step, you can always solve them. Like climbing a mountain - the peak looks far, but as long as you don’t give up, you can always climb up.

Summary

Returning to the theme “From Zero: How to Integrate Reasonix CLI into HagiCode System,” what really needs to be repeatedly confirmed is not scattered tricks, but whether the constraint conditions, implementation boundaries, and engineering trade-offs have been clearly seen.

As long as the judgment basis in the article is settled into stable check items, you can make reliable decisions faster when facing similar problems in the future.

开始使用 HagiCode

一次安装,几分钟上手

HagiCode for Windows 在 Microsoft Store 免费提供。打开商店即可安装并保持更新;也可以先对比各版本与定价,再决定从哪个渠道开始。