IronHive CLI - command-line agent frontend for the IronHive.Host SDK
Install
No framework information available.
README
ironhive-host
Universal Agent Host β CLI Β· server Β· embedded
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. The agent loop runs on
any injected IChatClient (UseChatClient / UseChatClientFactory), and the server surfaces
expose a generic turn-stream rather than an application-specific wire format.
Features
- Three surfaces, one core β CLI (
ironhive), embeddable SDK (IronHive.Host), and server runners (stdio or HTTP/SSE) all drive the sameIronHive.Agentloop. - Approvals on every surface β one tool-call policy (
IToolCallPolicy) judges each call; anAskis answered on the terminal, or β inrun --serverand by any host that passes aHitlBridgeto its runner β by the client over the wire (hitl_requestβhitl_response). See Human approval over the wire. - 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, wired on every surface includingAddIronHive) prevents silent context overflows, including on small quantized models. The CLI andrun --serveradditionally install a hard-backstopTokenBudgetChatClient; library embedders wrap their ownIChatClientwith it (see TokenBudgetChatClient). - Resilient tool-calling β
ResilientArgumentsMiddleware(a tool invocation pipeline step) andResilientFunctionInvoker(the same behaviour as a plainFunctionInvoker) turn tool-call arguments that do not fit the tool into model-actionable recovery hints instead of aborting the stream. Installed by the CLI andrun --server(after the loop guards and the permission gate);AddIronHive/AddIronHiveWithOpenAIdo not decorate the client, so embedders install it themselves (see ResilientFunctionInvoker). - Advisor β set
advisor.modeland every CLI /run --serversession gets anadvisortool (the libraryAddIronHivepath has no advisor option): the working model can send the conversation so far to a stronger model and read its review (before committing to an approach, when stuck, before declaring done). It appears on the wire as an ordinarytool_start/tool_end. - Layered configuration β global β project β environment β
.env, with automatic migration from legacysettings.json.
β οΈ 0.16.0 breaking repackage (upgrading from an older version? read this)
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 μ μ§).
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) |
tool_result |
id, name, success, result |
When a tool finishes (id pairs it with its tool_call; success is null when the outcome is unknown). Only when the chat client invokes tools |
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
CLI Reference
| Command | Purpose |
|---|---|
ironhive |
Interactive mode (or one prompt with -p) |
ironhive run [PROMPT] |
Run a single prompt and exit (--server for JSON Lines server mode) |
ironhive set <key> <value> |
Set a configuration value, e.g. ironhive set openai.apiKey <key> |
ironhive get [key] |
Get a configuration value (all values without a key) |
ironhive unset <key> |
Remove a configuration value |
ironhive config [show|path] |
Show all configuration, or the config file path |
ironhive models |
List available models from configured providers (-p/--provider, --json) |
ironhive update |
Check for and install updates (--check only checks, --force) |
ironhive doctor |
Diagnose configuration and connectivity (--verbose, --fix) |
ironhive sessions [list|delete <id>] |
List and manage sessions (-n/--limit, default 10; -o/--output json) |
Options of the default command (ironhive / ironhive -p):
| Flag | Purpose |
|---|---|
-m, --model <MODEL> |
Model to use |
--provider <PROVIDER> |
Provider to use |
--show-tokens |
Show token usage statistics |
--show-thinking |
Show the model's thinking/reasoning content |
--no-stream |
Wait for the complete response instead of streaming |
--plan |
Start in planning mode (read-only exploration) |
--dry-run |
Show what would be done without executing |
-c, --continue / -r, --resume <ID> |
Continue the most recent session / resume a specific one |
--fork |
Fork the resumed session into a new session |
-o, --output <FORMAT> / --plain |
text, json, jsonl / no ANSI formatting |
run takes -p/--prompt, -m/--model, --provider, --show-tokens, --show-thinking, plus:
| Flag | Purpose |
|---|---|
--json |
Output the response as JSON |
--server |
Server mode (JSON Lines on stdin/stdout β see AgentServerRunner) |
--session-id <ID> |
Session ID for server mode |
--auto-commit |
Commit changes after a successful run |
--commit-message <MESSAGE> |
Commit message for --auto-commit (default: generated from the prompt) |
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: exactly these variables are read (there is no
IRONHIVE_*variable):GPUSTACK_ENDPOINT,GPUSTACK_API_KEY,GPUSTACK_MODEL,GPUSTACK_EMBEDDING_MODEL,GPUSTACK_RERANK_MODELOPENAI_API_KEY,OPENAI_MODEL,OPENAI_ENDPOINTANTHROPIC_API_KEY,ANTHROPIC_MODELGOOGLEAI_API_KEY(orGOOGLE_API_KEY),GOOGLEAI_MODELXAI_API_KEY,XAI_MODEL,XAI_ENDPOINTAZURE_OPENAI_ENDPOINT,AZURE_OPENAI_API_KEY,AZURE_OPENAI_DEPLOYMENTOLLAMA_ENDPOINT,OLLAMA_MODEL,OLLAMA_ENABLEDLMSTUDIO_ENDPOINT,LMSTUDIO_MODEL,LMSTUDIO_ENABLEDLMSUPPLY_ENABLED
- .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 β CLI / run --server loops only |
advisor |
provider, model, maxCalls (per session, default 5) β off until model is set; CLI / run --server loops only |
skills |
roots, enabled, exclude, maxMetadataCharacters (default 12,000), acceptUnknownFields β off until roots names a directory (see Skills) |
# ~/.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
maxContextTokens: 65536 # the model's window, when the catalog does not know the model
compactOnOverflow: true # window unknown: compact when the server reports an overflow, not against a guess
# A stronger model the working model can consult through the `advisor` tool:
advisor:
provider: anthropic
model: claude-opus-5
maxCalls: 5
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. It applies to the loops the CLI and run --server build; the AddIronHive DI path has no ChatBehavior option β embedders set these caps on their own UseFunctionInvocation client.
| 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 tool errors; rarely hit in the CLI, where argument errors become recovery hints and the same error three times in a row ends the turn first |
# .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 - Tool results:
enableToolResultCompaction(default on, results overmaxToolResultChars= 30,000 keep head and tail) andenableObservationMasking(default on, results older thanobservationMaskingProtectedTurns= 3 user turns become placeholders;observationMaskingProtectedRoundsalso masks older tool rounds inside one turn). Only what is sent is reduced; history keeps full results - Complements (does not replace)
TokenBudgetChatClient, which remains the hard backstop against per-request overflow β installed by the CLI andrun --server;AddIronHiveembedders add it by wrapping their client (below)
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).
The CLI and run --server wrap every provider client in it (under a tool invocation pipeline with the loop guards, the permission gate and ResilientArgumentsMiddleware). AddIronHive(...) / AddIronHiveWithOpenAI(...) register the provider client as-is β to get the same backstop in an embedded host, wrap the client yourself (constructor below, then function invocation as shown under ResilientFunctionInvoker) and pass the result to AddIronHive(chatClient) / options.UseChatClient(...).
- 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
Converts the marshaller-level ArgumentException (missing/malformed tool arguments) and JsonException a tool invocation raises into model-actionable procedural error strings, enabling small quantized models to self-correct without aborting the stream. It comes in two forms with one implementation.
With IronHive.Agent's tool invocation pipeline, add ResilientArgumentsMiddleware as the last step, so it sits directly around the tool (this is what the CLI does):
var pipeline = new ToolInvocationPipeline(
[
new ArgumentParseFailureMiddleware(), // unparseable arguments: refused before the tool runs
new RepeatedCallGuardMiddleware(),
new RepeatedErrorGuardMiddleware(),
new ApprovalGateMiddleware(modeToolFilter, approvalService),
new ResilientArgumentsMiddleware(), // parsed arguments that do not fit the tool: recovery directive
]);
var client = new TokenBudgetChatClient(innerClient)
.AsBuilder()
.UseToolInvocationPipeline(pipeline)
.Build();
With DI, services.AddToolInvocationMiddleware<ResilientArgumentsMiddleware>() registered after the other steps does the same for UseToolInvocationPipeline().
With a plain UseFunctionInvocation client, set it as the FunctionInvoker:
var client = innerClient
.AsBuilder()
.UseFunctionInvocation(configure: c => c.FunctionInvoker = ResilientFunctionInvoker.Create())
.Build();
When the model sends a tool call with a missing required parameter, instead of throwing and aborting, the step 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. It does not overlap with ArgumentParseFailureMiddleware: that step refuses a call whose arguments text could not be parsed at all, before the tool runs; this one answers arguments that were parsed but do not fit the tool's parameters, which only shows when the tool is invoked. Behind a RepeatedCallGuardMiddleware, a model that keeps sending the same empty arguments is refused after three directives.
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, hitlBridge: hitlBridge);
await runner.RunAsync(ct);
// HTTP/SSE (host spawns the agent and communicates via REST)
var httpRunner = new AgentHttpRunner("http://localhost:5100", sessionId, ProcessMessage, logger, hitlBridge: hitlBridge);
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 PublishEvent for out-of-band event delivery (e.g. provider fallback notices).
Human approval over the wire
HitlBridge is an IHumanApprovalService (the approver IronHive.Agent's approval gate asks on an Ask verdict) whose
human is the client. Give the same instance to the gate and to the runner:
var hitlBridge = new HitlBridge(); // timeout: 5 minutes by default
var pipeline = new ToolInvocationPipeline([new ApprovalGateMiddleware(policy, hitlBridge)]);
// ... build the loop's chat client with UseToolInvocationPipeline(pipeline) ...
var runner = new AgentServerRunner(ProcessMessage, logger, hitlBridge: hitlBridge);
While the runner runs, each asked call is sent as a hitl_request (between the call's tool_start and tool_end; its
call_id matches the tool_start) and the call waits. The client answers with a hitl_response carrying the request's
id: approved: true runs the call (with modified_arguments when the person edited them), approved: false returns
reason to the model as the tool's result. Several requests can wait at once; an answer without an id is accepted only
while exactly one waits. No answer within the timeout, a cancelled turn, a runner that stops, or no runner at all β
each is a rejection, never a pass. Session rules of your own (trust lists, "already denied this session") fit as an
IHumanApprovalService that decorates the bridge. ironhive run --server does this for you; in an interactive CLI
session the terminal answers.
Skills. skills: in config.yaml (roots: [~/.ironhive/skills, .ironhive/skills], optional enabled, exclude, maxMetadataCharacters, acceptUnknownFields) loads Agent Skills (SKILL.md bundles per the specification): every skill's name and description is in the system instructions, and the model calls load_skill for a body β only from inside the skill's directory. Skills the specification's validator rejects are not loaded (acceptUnknownFields: true admits bundles carrying another client's frontmatter keys). The load_skill tool is added by the CLI / run --server loop factory. The library AddIronHive(...) path does not add it β its loop gets only options.Tools. services.AddAgentSkills(new SkillsConfig { Roots = [...] }) registers the SkillsLoader and puts the skills' metadata in the instructions, but an embedder must put SkillsLoader.LoadTool in its loop's tools itself (e.g. build the loader with SkillsLoader.Create(config) and add loader.LoadTool to options.Tools).
Permissions. Every tool call runs through the permission rules (IronHive.Agent's ApprovalGateMiddleware, a step of the tool invocation pipeline installed on the chat client): Allow runs the tool, Deny returns the reason to the model, Ask prompts on the console β or, in run --server, is sent to the client as a hitl_request (see Human approval over the wire). Planning mode is enforced at the same gate: while the session is planning, only read-only tools run. A console prompt needs a terminal β a piped one-shot (stdin or stdout redirected, no server client) rejects an Ask verdict with a reason instead, so a prompt never lands in an output stream. Rules come from the project's .ironhive/permissions.yaml (or .yml / .json; keys read, edit, bash, external_directory, mcp_tools, tools, read_only_tools, ask_before_delete, default_action) when it exists; otherwise from the permissions section of config.yaml β global ~/.ironhive/config.yaml, then the project's, camelCase keys like the rest of the file (externalDirectory, mcpTools, readOnlyTools, askBeforeDelete, defaultAction); otherwise the built-in defaults (before 0.29.4 the config.yaml section was read and then discarded). tools matches by tool name any tool with no dedicated category, and an unmatched tool falls to the default action (ask by default β so an unknown tool is asked about, not run). ask_before_delete (true by default) asks before deleting a file even where the edit rules allow it; false lets the edit rules decide deletes.
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?, Id? (the request being answered), ModifiedArguments?, AlwaysApprove? |
CancelRequest |
cancel |
β |
ShutdownRequest |
shutdown |
β |
SessionStartedEvent |
session_started |
SessionId β first line run --server writes, before any request is read (--session-id value, or a generated id) |
ToolStartEvent |
tool_start |
Tool, Input?, CallId? |
HitlRequestEvent |
hitl_request |
Id, Action, Target, Description, ToolName?, Arguments?, CallId? (matches the tool_start), Level? β a call waiting for approval; answer with hitl_response |
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 |
IronHive.Host.Protocol also declares agent_selected (AgentSelectedEvent), plan_created, plan_step_started,
plan_step_completed and plan_completed (Plan*ServerEvent). No runner emits them today β they are reserved; a client
may accept them but should not wait for them.
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, ResilientArgumentsMiddleware/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. The scope is the hosting surface described in the introduction
above; please open an issue to discuss a feature before sending a PR. 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
Changes from Previous Version
Adoption Assessment
Assess this package against its supported frameworks, license, maintenance signals and dependency graph before making it a project standard.
Related Guides & Workflows
Learn how to evaluate, integrate, or audit this package in your .NET project.