IronHive.Cli
0.22.5
dotnet tool install --global IronHive.Cli --version 0.22.5
dotnet new tool-manifest
dotnet tool install --local IronHive.Cli --version 0.22.5
#tool dotnet:?package=IronHive.Cli&version=0.22.5
nuke :add-package IronHive.Cli --version 0.22.5
ironhive-host
Universal Agent Host — CLI · server · embedded
역할·범위·기능 명세(미들웨어 정체성)는
CHARTER.md참조 — product-middleware §⑤ 에이전트 호스트.
A foundation tool for AI-powered automation—not just coding, but any task that benefits from intelligent command execution. One agent core (plan → execute → tool-call), exposed as an installable CLI, an embeddable .NET SDK, or a long-running server (stdio/JSON-Lines or HTTP/SSE) — pick the surface that fits, without re-implementing the agent loop.
The agent loop, context/compaction, mode system, MCP plugins, and permission engine itself live in
IronHive.Agent (from ironhive-agent),
which this package consumes as a PackageReference. IronHive.Host adds the hosting surface on top:
CLI/server/embed entry points, the IronHive.Host.Protocol turn-stream contract, layered config,
provider adapters, and session/execution-log/memory integration. See CHARTER.md for
the full role boundary.
Features
- Three surfaces, one core — CLI (
ironhive), embeddable SDK (IronHive.Host), and server runners (stdio or HTTP/SSE) all drive the sameIronHive.Agentloop. - MCP-native tooling — plugs into MCP servers (memory, code execution, custom tools) instead of hardcoding a tool set.
- Multi-provider out of the box — OpenAI, Anthropic, GoogleAI, Azure OpenAI, xAI, Ollama, LM Studio, GPUStack, and local inference via
LMSUPPLY_ENABLED. - Context-window safe by default — automatic history compaction (
ContextManager) and a hard-backstopTokenBudgetChatClientprevent silent context overflows, including on small quantized models. - Resilient tool-calling —
ResilientFunctionInvokerturns malformed tool-call arguments into model-actionable recovery hints instead of aborting the stream. - Layered configuration — global → project → environment →
.env, with automatic migration from legacysettings.json.
<details> <summary><strong>⚠️ 0.16.0 breaking repackage</strong> (upgrading from an older version? read this)</summary>
SDK 라이브러리 IronHive.Host.Core → IronHive.Host(SDK가 top-level 이름을 소유,
네임스페이스 IronHive.Host.Core.* → IronHive.Host.*). CLI tool 패키지 IronHive.Host → IronHive.Cli
(실행 명령은 ironhive 그대로). turn-stream 프로토콜 계약은 별도 thin 패키지 IronHive.Host.Protocol(무의존)로 분리 유지.
옛 IronHive.Host tool 패키지는 배포 중단(unlist 아님 — 기존 복원 계속 동작); 신규 설치는 dotnet tool install -g IronHive.Cli.
릴리스는 이제 iyulab/ironhive-host에서 self-host — 옛 ironhive-cli-releases는 archive(read-only, 기존 v0.11-0.15 다운로드 URL 유지).
</details>
Contents
- Philosophy
- As an SDK (IronHive.Host)
- As a CLI (IronHive.Cli)
- Quick Start
- Configuration
- Core Library Integration
- Samples
- Development
- Contributing
- Related Projects
- License
Philosophy
Do one thing well.
Receive a command. Plan. Execute. Return.
┌─────────────────────────────────────────┐
│ External Systems │
│ CI/CD · Schedulers · Orchestrators │
└────────────────────┬────────────────────┘
│ invoke
▼
┌─────────────────────────────────────────┐
│ ironhive-host │
│ Command → Plan → Execute → Done │
└────────────────────┬────────────────────┘
│ MCP
▼
┌─────────────────────────────────────────┐
│ Plugins (MCP Servers) │
│ code-beaker · memory-indexer · ... │
└─────────────────────────────────────────┘
As an SDK (IronHive.Host)
dotnet add package IronHive.Host
Build reusable agent hosts (agent loop, tools, session, providers). The CLI below is one consumption surface of this SDK.
As a CLI (IronHive.Cli)
dotnet tool install -g IronHive.Cli
ironhive
Or build from source:
git clone https://github.com/iyulab/ironhive-host
cd ironhive-host
dotnet build
Quick Start
# Interactive mode
ironhive
# Single command
ironhive -p "Write a README for this project"
# JSON output (for programmatic use)
ironhive -p "Hello" --output json
# Streaming JSON Lines
ironhive -p "Hello" --output jsonl
# Plain text (no ANSI)
ironhive -p "Hello" --plain
Without an API key
ironhive does not need a cloud provider to be useful. Local inference is on by default
(lmsupply.enabled: true; LMSUPPLY_ENABLED=false turns it off): with no other provider
configured, the first run downloads a GGUF model (cached under the LMSupply model cache) and runs
it in-process through LMSupply — no key, no server:
ironhive -p "Summarize this directory" # works with an empty config
ironhive doctor # shows which providers are configured and reachable
doctor reports Using local inference only (lmsupply) on such a setup and suggests a remote
provider only as a performance option — that is the expected state, not a warning to fix. The first
run is slower (model download + load); later runs start from the cache.
JSON Output Schema
--output json/jsonl is meant for programmatic consumption — piping into jq, another process,
or a script. Fields that would otherwise serialize as null (sessionId, usage, thinking,
toolCalls) are omitted from the object entirely rather than written as null.
--output json (single response, -p/--prompt):
{
"content": "string", // always present — the assistant's text reply
"sessionId": "string", // omitted if no session is active
"usage": { // omitted if usage wasn't reported
"inputTokens": 0,
"outputTokens": 0,
"totalTokens": 0
},
"thinking": { // present only with --show-thinking AND the model returned thinking content
"content": "string",
"tokenCount": 0
},
"toolCalls": [ // omitted if no tools were called
{
"name": "string",
"arguments": "string", // JSON-encoded arguments, as a string
"result": "string",
"success": true // true | false | null — null means the outcome is unknown
// (unset unless the underlying IChatClient has
// Microsoft.Extensions.AI function-invocation middleware)
}
]
}
On cancellation (Ctrl+C) or an unhandled error, the object is replaced with an error shape instead:
{ "error": "cancelled", "code": 130 } or { "error": "<message>", "code": 1 }.
--output jsonl (streaming JSON Lines — one object per line, discriminated by type):
type |
Fields | When |
|---|---|---|
start |
sessionId |
First line, always |
thinking |
content |
Only with --show-thinking, once per thinking delta |
text |
content |
Once per text delta |
tool_call |
id, name, arguments |
Once per tool-call delta (arguments is JSON-encoded) |
done |
sessionId |
Last line on success |
error |
error |
Instead of done, on cancellation or an unhandled exception |
sessions list --output json — an array ([] if empty), one object per session:
[
{
"id": "string",
"status": "string", // lowercased, e.g. "active", "completed"
"model": "string",
"created": "2026-08-31T00:00:00.0000000Z", // ISO 8601 (round-trip "o" format)
"messageCount": 0,
"firstMessage": "string"
}
]
sessions delete <id> --output json:
- Success:
{ "deleted": "<id>", "success": true } - Missing
<id>or session not found:{ "error": "string", "code": 1 }
Session Management
# Continue most recent session
ironhive -c
# Resume specific session
ironhive -r <session-id>
# List sessions
ironhive sessions list
ironhive sessions list --output json
Model Configuration
# Environment variables
export GPUSTACK_ENDPOINT=http://localhost:8080/v1
export GPUSTACK_API_KEY=your-key
export GPUSTACK_MODEL=gpt-4o-mini
# Or OpenAI
export OPENAI_API_KEY=sk-xxx
Configuration
Configuration is merged in order (later overrides earlier):
- Global:
~/.ironhive/config.yaml - Project:
.ironhive/config.yaml - Environment:
IRONHIVE_*,GPUSTACK_*,OPENAI_*,ANTHROPIC_*,GOOGLEAI_*/GOOGLE_API_KEY,XAI_*,AZURE_OPENAI_*,OLLAMA_*,LMSTUDIO_*,LMSUPPLY_ENABLED, and other provider-specific vars - .env file: Project root
.env
On first run, a legacy
~/.ironhive/settings.json(from earlier versions) is automatically migrated toconfig.yaml.
Config keys
The loader accepts these top-level keys in config.yaml. Acronym provider sections use lowercase keys; unknown top-level keys are ignored with a logged warning.
| Key | Notes |
|---|---|
gpuStack |
camelCase |
openai |
lowercase (acronym) |
anthropic |
|
googleai |
lowercase (acronym) |
azureopenai |
lowercase (acronym) |
xai |
|
ollama |
|
lmstudio |
lowercase (acronym) |
lmsupply |
lowercase (acronym) |
permissions |
|
compaction |
|
webSearch |
camelCase |
deepResearch |
camelCase |
chatBehavior |
camelCase |
# ~/.ironhive/config.yaml
openai:
apiKey: sk-...
model: gpt-4o-mini
gpuStack:
endpoint: http://localhost:8080
apiKey: ...
model: gpt-4o-mini
# .ironhive/config.yaml
# Context compaction is active by default — long sessions compact history
# instead of overflowing. Tune via the compaction section:
compaction:
useTokenBasedCompaction: true
protectRecentTokens: 40000 # most-recent tokens always kept
minimumPruneTokens: 20000 # only compact when at least this much is prunable
targetRatio: 0.70 # compact down to ~70% of the context window
Core Library Integration
Use IronHive.Host for direct .NET integration:
// Add to your project
<PackageReference Include="IronHive.Host" />
// Configure with DI
services.AddIronHiveWithOpenAI(apiKey, "gpt-4o-mini");
// Or
services.AddIronHiveWithOllama("llama3.2");
// Use
var agentLoop = serviceProvider.GetRequiredService<IAgentLoop>();
var response = await agentLoop.RunAsync("Hello");
// Streaming
await foreach (var chunk in agentLoop.RunStreamingAsync("Hello"))
{
Console.Write(chunk.TextDelta);
}
See samples/console-chat for a complete example.
ChatBehaviorConfig
Controls how FunctionInvokingChatClient orchestrates the tool-call iteration loop. Exposed in IronHiveConfig.ChatBehavior so you can tune per-model without forking the source.
| Property | Default | Notes |
|---|---|---|
MaximumIterationsPerRequest |
10 | Lower (5–7) for small 4K-window models; raise (15–20) for large-context models |
MaximumConsecutiveErrorsPerRequest |
3 | Backstop on back-to-back marshaller errors; rarely hit when ResilientFunctionInvoker is installed |
# .ironhive/config.yaml
chatBehavior:
maximumIterationsPerRequest: 7 # tune down for small/quantized models
maximumConsecutiveErrorsPerRequest: 3
Context Compaction
Long sessions are kept within the model's context window automatically. When the agent loop is built
(via the CLI, the server runners, or IronHive.Host DI), a ContextManager is wired from the
compaction config so older history is compacted (token-based, protecting the most recent turns and
important tool outputs) instead of silently overflowing.
- Enabled by default; tune via the
compaction:config section (see Configuration above) - Model-aware — the context window is sized from the active model
- Embedded consumers can set
options.CompactiononAddIronHive(...); manual loop builders can wire it viaHostContextManagerFactory.Create(compactionConfig, modelName)and pass the result toAgentLoop/ThinkingAgentLoop - Complements (does not replace)
TokenBudgetChatClient, which remains the hard backstop against per-request overflow
TokenBudgetChatClient
IChatClient decorator that short-circuits streaming calls when the accumulated message-history size would exceed a configurable fraction of the model's context window. Prevents context-overflow silent failures on small quantized models (e.g. 4K-window Gemma E4B).
- Sits between
FunctionInvokingChatClientand the underlying provider - Estimates tokens as
total-chars ÷ 4(conservative upper bound) - When the estimate exceeds
maxContextTokens × threshold, emits a gracefulChatFinishReason.Lengthresponse instead of letting the model error silently - Context window auto-detected via
IContextSizeProviderif the inner client exposes it; otherwise falls back todefaultMaxContextTokens
var client = new TokenBudgetChatClient(
inner: innerClient,
defaultMaxContextTokens: 4096,
threshold: 0.8); // trigger at 80 % of context window
ResilientFunctionInvoker
Factory for the M.E.AI FunctionInvoker delegate that converts marshaller-level ArgumentException (missing/malformed tool arguments) into model-actionable procedural error strings, enabling small quantized models to self-correct without aborting the stream.
Install via UseFunctionInvocation:
chatClient.UseFunctionInvocation(configure: c =>
{
c.FunctionInvoker = ResilientFunctionInvoker.Create();
});
When the model sends a tool call with a missing required parameter, instead of throwing and aborting, the invoker returns a numbered recovery directive telling the model exactly what is missing, where to find the value, and explicitly forbidding the empty-args retry pattern.
AgentServerRunner / AgentHttpRunner
Two runner implementations share the same processor delegate signature, enabling a single agent pipeline to serve either transport:
async IAsyncEnumerable<ServerEvent> ProcessMessage(
UserMessageRequest msg,
CancellationToken token)
{
// msg.Model carries the per-message model override (nullable)
await foreach (var evt in agentLoop.RunStreamingAsync(msg.Content, token)
.ToServerEvents(executionLog, token))
{
yield return evt;
}
}
// stdin/stdout JSON Lines (used by `ironhive run --server`)
var runner = new AgentServerRunner(ProcessMessage, logger);
await runner.RunAsync(ct);
// HTTP/SSE (host spawns the agent and communicates via REST)
var httpRunner = new AgentHttpRunner("http://localhost:5100", sessionId, ProcessMessage, logger);
await httpRunner.RunAsync(ct);
AgentHttpRunner expects these host endpoints:
| Endpoint | Method | Purpose |
|---|---|---|
/api/agent/{id}/ready |
POST | Signal agent is up |
/api/agent/{id}/inbox |
GET (SSE) | Receive ServerRequest commands |
/api/agent/{id}/events |
POST | Deliver ServerEvent batches |
Both runners support typeInfoModifiers to extend polymorphic type registrations without mutating the base JsonSerializerOptions:
var runner = new AgentServerRunner(ProcessMessage, logger,
typeInfoModifiers: [ApplyCustomPolymorphismOverrides]);
AgentHttpRunner additionally exposes WaitForHitlResponseAsync / ResolveHitl for human-in-the-loop flows, and PublishEvent for out-of-band event delivery (e.g. provider fallback notices). No runner emits hitl_request yet: in server mode a tool call whose permission verdict is Ask is refused with a reason (see Permissions), and the refusal reaches the client as a tool_end.
Permissions. Every tool call runs through the permission rules (IronHive.Agent's ApprovalGatedFunctionInvoker, installed on the chat client): Allow runs the tool, Deny returns the reason to the model, Ask prompts on the console. The prompt needs a terminal — when stdin or stdout is redirected (run --server, or a piped one-shot) an Ask verdict is rejected with a reason instead, so a prompt never lands in the protocol stream. Rules live in ~/.ironhive/config.yaml under permissions (read, edit, bash, external_directory, mcp_tools, tools, default_action); tools matches by tool name any tool with no dedicated category, and an unmatched tool falls to default_action (ask by default — so an unknown tool is asked about, not run).
Protocol types (ServerRequest → agent, ServerEvent → host):
| Type | Discriminator | Key fields |
|---|---|---|
UserMessageRequest |
user_message |
Content, Model?, Options? (TurnOptions: tool_names, tool_mode, reasoning_effort, temperature, max_output_tokens — see below) |
ContextUpdateRequest |
context_update |
WorkingPath?, SelectedItems? |
HitlResponseRequest |
hitl_response |
Approved, Reason? |
CancelRequest |
cancel |
— |
ShutdownRequest |
shutdown |
— |
ToolStartEvent |
tool_start |
Tool, Input?, CallId? |
ToolEndEvent |
tool_end |
Tool, Success, Output? (≤ 8 KB), CallId? — one per tool call once its outcome is known, before turn_end; CallId matches the tool_start. A call the permission gate refused arrives with Success: false and the refusal as Output (Permission denied: … / Approval rejected: …) |
ThinkingDeltaEvent |
thinking_delta |
Content — extended-thinking text, its own stream, never folded into text_delta |
FallbackServerEvent |
fallback |
Kind (retry|fallback|exhausted), Category, Message, ProviderIndex, TotalProviders, Attempt, MaxAttempts |
TextDeltaEvent |
text_delta |
Content |
AddendumEvent |
addendum |
Content — a turn observer's note, at most once per turn, after the last text_delta and before turn_end; not the model's words, never in history |
TurnEndEvent |
turn_end |
InputTokens?, OutputTokens?, TotalTokens? (summed across every model round-trip in the turn; null when the provider reported no usage) |
ErrorEvent |
error |
Message |
Per-turn options. UserMessageRequest.Options narrows or tunes one turn: tool_names (a subset of
the agent's registered tools; [] = no tools this turn), tool_mode (auto | none | require_any |
require:<tool>), reasoning_effort (none | low | medium | high | extra_high),
temperature, max_output_tokens. The merge is field by field: a set field replaces the agent's
configured value for this turn only, an unset field keeps it, and the next request without options
is back on the agent's configuration — nothing is remembered across turns. A tool name that is not
registered, or an unknown mode/effort, is answered with an error event, never silently dropped. A
request without options is byte-identical to the pre-0.21.0 wire form.
Samples
| Sample | Path | Description |
|---|---|---|
| Console Chat | samples/console-chat/ |
Core library direct integration |
| Web Chat | samples/web-ai-chat/ |
Next.js + CLI subprocess |
# Console Chat
cd samples/console-chat
dotnet run
# Web Chat
cd samples/web-ai-chat
npm install && npm run dev
Development
Requirements
- .NET 10 SDK
- Git
Build & Test
dotnet build
dotnet test
Project Structure
ironhive-host/
├── src/
│ ├── IronHive.Host.Protocol/ # Thin turn-stream contracts (zero-dep NuGet)
│ ├── IronHive.Host/ # SDK / core library (NuGet)
│ │ ├── Config/ # Configuration classes
│ │ ├── Extensions/ # DI helpers
│ │ ├── Providers/ # LMSupply, IronHive chat client providers
│ │ ├── Server/ # AgentServerRunner, AgentHttpRunner (references Host.Protocol)
│ │ ├── Session/ # Session management
│ │ └── Tools/ # Built-in tools, ResilientFunctionInvoker, TokenBudgetChatClient
│ └── IronHive.Cli/ # CLI application (tool command: ironhive)
├── samples/
│ ├── console-chat/ # .NET Core integration
│ └── web-ai-chat/ # Next.js + subprocess
└── tests/
Contributing
Issues and pull requests are welcome. This project is pre-1.0 (0.x) — breaking changes land
freely for structural correctness; see CHARTER.md for scope and design intent
before proposing a feature. dotnet build && dotnet test (see Development) should
pass before opening a PR.
Related Projects
- ironhive — LLM abstraction
- ironbees — Multi-agent management
- memory-indexer — Semantic memory MCP
- code-beaker — Code execution platform
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
This package has no dependencies.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.22.5 | 0 | 9/14/2026 |
| 0.22.4 | 0 | 9/14/2026 |
| 0.22.3 | 0 | 9/14/2026 |
| 0.22.2 | 37 | 9/14/2026 |
| 0.22.1 | 35 | 9/13/2026 |
| 0.22.0 | 39 | 9/13/2026 |
| 0.21.2 | 46 | 9/13/2026 |
| 0.21.1 | 44 | 9/12/2026 |
| 0.21.0 | 42 | 9/12/2026 |
| 0.20.15 | 43 | 9/12/2026 |
| 0.20.14 | 48 | 9/11/2026 |
| 0.20.13 | 62 | 9/11/2026 |
| 0.20.12 | 48 | 9/10/2026 |
| 0.20.11 | 83 | 9/9/2026 |
| 0.20.10 | 94 | 9/9/2026 |
| 0.20.9 | 87 | 9/9/2026 |
| 0.20.8 | 102 | 9/7/2026 |
| 0.20.7 | 115 | 9/7/2026 |
| 0.20.6 | 109 | 9/7/2026 |
| 0.20.5 | 104 | 9/7/2026 |