Practical Journey of Integrating Pi Agent into HagiCode
This article shares our practical experience in integrating Pi agent as a first-class workflow entry point in the HagiCode project, including architecture design, core component implementation, and frontend and backend adaptation details.
Background
Actually, everything started with that question: in the HagiCode Mono project, although repos/Hagicode.Libs already implemented the reusable PiProvider, repos/hagicode-core and repos/web had not yet elevated Pi to a project-level first-class Agent CLI. This is like having a good pair of shoes but not tying the laces properly—you can walk, but it always feels like something is missing.
The existing PiProvider is located at HagiCode.Libs/src/HagiCode.Libs.Providers/Pi/PiProvider.cs. It implements a CLI communication protocol based on line-delimited JSON (--mode json --print), supporting session management, tool calls, and streaming output. This code is well-written, but it's just lying there, not yet awakened.
This situation created a gap: Pi's underlying capabilities were complete, but the project-level integration chain (from user configuration to execution monitoring) was missing. It's like painting a good picture but not hanging it on the wall for people to appreciate. Therefore, we needed to integrate Pi as a new active provider PiCli into the entire system, making it a first-class workflow entry point like other Agent CLIs.
After all, what we wanted was a complete experience, not scattered fragments.
About HagiCode
The solution shared in this article comes from our practical experience in the HagiCode project. HagiCode is an intelligent code assistant project, and during development we needed to integrate multiple AI capability providers. The Pi agent integration solution described in this article is a reusable integration pattern summarized from our practice, which has reference value for similar multi-provider integration scenarios. The project's GitHub address is github.com/HagiCode-org/site.
Architecture Design
Pi agent integration adopted the thin adapter pattern, rather than re-implementing the Pi process protocol at the core layer. This design decision is not complex—we just thought, why reinvent the wheel?
After all:
-
Avoid duplicate implementation: The parameter building, process launching, JSON parsing, and error handling logic in
PiProviderare already complete. Re-implementing would create two sources of behavior and two test matrices. That's fine, but maintenance would be troublesome. - Maintain consistency: Consistent with the integration methods of existing CLIs like Kimi, Gemini, Reasonix, and DeepAgents—all delegate through thin adapters to the libs layer implementation. Everyone does this, just follow along.
-
Separation of concerns:
hagicode-corefocuses on runtime contracts and business logic, while process details are left toHagiCode.Libs. Each does their own job, isn't that beautiful?
This design allows HagiCode to quickly integrate new AI capability providers while keeping the core layer clean. After all, simplicity is beauty, and efficiency is money.
Core Component Implementation
Provider Enumeration and Factory
Added PiCli = 13 enumeration value in hagicode-core/src/PCode.Models/AIProviderType.cs:
public enum AIProviderType
{
ClaudeCodeCli = 0,
// ... other providers
PiCli = 13,
}
This enumeration value is the root of the provider identity, affecting the OpenAPI-generated PCode_Models_AIProviderType.ts frontend enumeration, the creation branch of AIProviderFactory, and Provider parsing and active provider judgment logic. Register the new creation branch in AIProviderFactory:
case AIProviderType.PiCli:
provider = new PiCliProvider(
logger,
configuration,
providerFactory.GetRequiredService<ICliProvider<PiOptions>>(),
providerFactory.GetService<IAgentCliRuntimeEnvironmentResolver>());
break;
Actually, this code is nothing special, just an enumeration plus a switch case. But they are important, like notes on a musical score—adding them one by one creates a complete melody.
Thin Adapter Implementation
PiCliProvider.cs is the core thin adapter, implementing the IAIProvider, IVersionedAIProvider, and IAsyncDisposable interfaces. It receives ICliProvider<PiOptions> (from HagiCode.Libs) through the constructor, maps AIRequest / ProviderConfiguration to PiOptions, and then delegates operations like execution, ping, and version query to the underlying provider.
The key is to handle Pi's unique JSON event stream, including assistant.thought, assistant, terminal.completed, and other events. These events need to be correctly parsed and converted to the system's standard format during streaming output. It's a bit like translation—conveying meaning from one language to another.
Main Profession Preset
Added profession-pi main profession entry in main-professions.yaml:
- Id: "profession-pi"
Name: "Pi"
Family: "pi"
Summary: "hero.professionCopy.primary.pi.summary"
Icon: "executor-avatar:Pi"
SourceLabel: "hero.professionCopy.sources.aiProvidersPiCli"
ProviderType: "PiCli"
SortOrder: 59
DefaultEnabled: true
DefaultParameters:
binary: "pi"
provider: "omniroute"
thinking: "balanced"
This ensures that the backend snapshot and frontend fallback directory have consistent Pi identity, allowing Pi configuration to be managed through the existing Hero editor, and Pi's addition won't break the consumability of existing main profession entries. After all, we don't want to break existing things just because we added a new feature—that would be penny-wise and pound-foolish.
Monitoring Registry
AgentCliMonitoringRegistry needs to add Pi's monitoring descriptor so the system can parse executable paths, display brand names, perform health probes, and display Pi status in the status bar and health details:
new AgentCliMonitoringDescriptor(
CliId: "pi",
DisplayName: "Pi",
ProviderType: AIProviderType.PiCli,
DisplayOrder: 13,
Strategy: AgentCliMonitoringStrategy.Grain,
NotConfiguredMessage: "Pi CLI is not configured or executable not found.",
EnabledPaths: [],
ExecutablePathConfigPaths: ["Hero:PrimaryProfessions:Pi:ExecutablePath"],
DefaultExecutablePath: "pi")
The monitoring system is like a dashboard, telling you how the car is running. Pi's status is clear at a glance, so users can know if everything is normal.
Frontend Adaptation
Executor Type Adaptation
The frontend needs to update executorTypeAdapter.ts, adding Pi's type recognition logic:
const PI_IDS = new Set<string>([
PCode_Models_AIProviderType.PI_CLI,
'Pi',
'PiCli',
'pi',
'picli',
'pi-cli',
]);
export function isPi(value: string): boolean {
const normalized = normalize(value);
const normalizedLower = normalized.toLowerCase();
return PI_IDS.has(normalized)
|| normalizedLower.includes('pi-cli')
|| normalizedLower.includes('picli')
|| normalizedLower === 'pi';
}
This is like giving Pi several names—no matter what you call it, it knows you're referring to it. After all, what you call it doesn't matter, what matters is knowing who it is.
Fallback Hero Directory
Add Pi's fallback entry in hero.ts to ensure that even if backend data is not loaded, the frontend can still display Pi configuration normally:
{
id: "profession-pi",
name: "Pi",
family: "pi",
summary: "hero.professionCopy.primary.pi.summary",
icon: "executor-avatar:Pi",
sourceLabel: "hero.professionCopy.sources.aiProvidersPiCli",
providerType: AIProviderType.PI_CLI,
sortOrder: 59,
isReadOnly: true,
managedParameterKeys: {
// parameter keys supported in the first version
},
defaultParameters: {
binary: "pi",
provider: "omniroute",
thinking: "balanced",
},
}
Fallback is like a backup plan—just in case the backend goes down or data doesn't load, the frontend can still work normally. After all, no one wants to be in a panic when that happens.
Localization Copy and Forms
Add Pi-related translations in locales/*/common/{hero,settings}.yml, and add a configuration field block for Pi in HeroCliEquipmentForm.tsx, supporting binary, provider, thinking, sessionDirectory, and tool/session switch fields.
The first version of Pi only exposes the minimum necessary fields. Complex features like tool allowlist/denylist and environment variable editors are explicitly deferred to subsequent changes. After all, you can't become fat in one bite, take it slow and go faster.
Configuration Examples
Backend Configuration
Configure Pi parameters in appsettings.yml:
Hero:
PrimaryProfessions:
Pi:
ExecutablePath: /usr/local/bin/pi
Provider: omniroute
Thinking: balanced
SessionDirectory: ~/.pi/sessions
NoSession: false
DisableAllTools: false
DisableBuiltinTools: false
Frontend Configuration
Configure Pi profession in the Hero editor:
{
id: "my-pi-profession",
name: "My Pi Profession",
family: "pi",
providerType: AIProviderType.PI_CLI,
primaryModel: {
provider: "PiCli",
model: "glm-4.7",
providerSettings: {
provider: "omniroute",
thinking: "balanced",
sessionDirectory: "/Users/username/.pi/sessions",
noSession: false,
disableAllTools: false,
disableBuiltinTools: false,
},
},
}
Configuration files are like recipes—follow them and you'll make good food. But sometimes, even following the recipe, you can still burn the food... though this configuration is quite clear, so there shouldn't be any problems.
Validation and Testing
Backend Validation
Validate main profession presets in tests:
var snapshot = await presetProvider.GetSnapshotAsync();
var piProfession = snapshot.FindById("profession-pi");
piProfession.ShouldNotBeNull();
piProfession.ProviderType.ShouldBe(AIProviderType.PiCli);
piProfession.Family.ShouldBe("pi");
Also need to verify Pi availability and health check:
# Check Pi executable
which pi
pi --version
# Verify backend provider registration
curl http://localhost:35168/api/health/agent-cli/pi
Testing is like exams—passing means you really know it. But sometimes, passing an exam doesn't mean you really understand it, but at least it shows you can get the answers right.
Frontend Validation
// Verify type parsing and display name
expect(resolveExecutorVisualType('pi-cli')).toBe('Pi');
expect(resolveExecutorVisualType(PCode_Models_AIProviderType.PI_CLI)).toBe('Pi');
expect(resolveExecutorDisplayName('PiCli')).toBe('Pi');
// Verify fallback directory
const piFallback = findFallbackProfessionById('profession-pi');
expect(piFallback?.providerType).toBe(AIProviderType.PI_CLI);
These test cases cover the main logic paths, ensuring Pi can be correctly identified and displayed. After all, things shown to users can't have errors.
Common Troubleshooting
Pi Executable Not Found
If the health check returns "Pi executable was not found.", you need to check if pi is in PATH, or confirm if the configured path is correct. The solution is to ensure pi is installed and in PATH, or configure the correct ExecutablePath in appsettings.yml.
This is like not being able to find your house keys—you need to think if you put them in the wrong place. Actually, the solution is simple—either put the keys back in the original place, or get a new lock.
Configuration Fields Not Recognized
If you get the error "PiCli runtime settings [...] are not supported" at startup, check if you're only using the configuration fields supported in the first version. Fields supported in the first version include: provider, thinking, sessionDirectory, noSession, disableAllTools, disableBuiltinTools.
Sometimes it's just being greedy—wanting too many features, but the system doesn't support them. Actually, the first version's features are already enough; you can't chew too much at once.
Cannot Select Pi in Frontend
If there's no Pi option in the Hero editor, check if you've run npm run generate:api to regenerate the frontend enumeration, whether there's a profession-pi entry in hero.ts, and whether the localization copy is added correctly.
Troubleshooting is like finding a lost item—you have to do it step by step. After all, blind searching won't find it; you need to search logically.
Best Practices
Use thin adapter pattern: Don't re-implement the process protocol at the core layer; delegate to the libs layer provider. This avoids duplicate implementation and maintains code consistency. After all, reinventing the wheel is not only tiring but also prone to problems.
Maintain naming consistency: Use consistent naming conventions across frontend and backend to avoid confusion. Provider enumeration uses
PiCli, CLI ID uses"pi", display name uses"Pi". Good names mean lower communication costs.Prioritize presets: The first version should be based on the
profession-pipreset, rather than requiring users to manually configure. This allows users to get started quickly and reduces configuration complexity. Users like simple things; leave the complex ones to me.Focus on error messages: Ensure error messages are clear and actionable, helping users quickly locate problems. Clear error messages mean users won't panic over a single error.
Version compatibility: Consider the serialization stability of
AIProviderTypeenumeration values; changes need to be handled carefully. TheAIProviderType.PiCli = 13enumeration value cannot be easily modified. After all, changing this value might break backward compatibility—that would be troublesome.
Summary
Through the thin adapter pattern, we successfully integrated Pi agent into the HagiCode system, making it a first-class workflow entry point like other Agent CLIs. The core advantages of this solution are:
- Avoided duplicate implementation and reused the existing
PiProviderfrom the libs layer - Maintained consistent integration methods with existing providers, reducing maintenance costs
- Implemented a complete chain from user configuration to execution monitoring
HagiCode's practice proves that the thin adapter pattern is an effective solution for integrating AI capability providers. It enables us to quickly support new agents while maintaining system stability and maintainability.
Actually, doing technology is like this—find a good pattern, then reuse it. This allows you to move forward quickly without losing direction. Like walking—once you find a good path, keep walking on it, just occasionally stop to enjoy the scenery...
If you're also doing multi-provider AI capability integration, I hope this solution gives you some inspiration. If you're interested in the HagiCode project, welcome to exchange on GitHub. After all, technology makes progress through communication.
References
- HagiCode GitHub Repository
- HagiCode Official Website
- HagiCode Installation Guide
- Pi Agent Documentation
Summary
Around "Practical Journey of Integrating Pi Agent into HagiCode," a more robust approach is to first run through key configurations, dependency boundaries, and implementation paths step by step, then fill in optimization details.
When goals, steps, and acceptance criteria are clear, such solutions can more smoothly enter actual delivery.
Original Article & License
Thanks for reading. If this article helped, consider liking, bookmarking, or sharing it.
This article was created with AI assistance and reviewed by the author before publication.
- Author: newbe36524
- Original URL: https://docs.hagicode.com/go?platform=devto&target=%2Fblog%2F2026-06-08-integrating-pi-agent-into-hagicode%2F
- License: Unless otherwise stated, this article is licensed under CC BY-NC-SA. Please retain attribution when sharing.
Top comments (0)