Install
$ agentstack add mcp-dsswift-ion Open-source listing — not yet scanned by AgentStack. Follow the source repository for install instructions.
Security review
⚠ Flagged3 finding(s); flagged for manual review. · v0.1.0 How review works →
- • Prompt-injection patterns
- • Secret / credential exfiltration
- • Dangerous shell & filesystem operations
- • Untrusted network calls
- • Known-malicious package signatures
- high Reads credentials/environment and may exfiltrate them.
- high Dangerous shell/eval execution.
- high Destructive filesystem operation.
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ● Shell / process execution Used
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
A headless agent runtime. One binary. Zero opinions. Seventy-three hooks to make it yours.
~9 MB static binary · 14 LLM providers · 73 extension hooks · 15 built-in tools · MIT license
Ion Engine is a headless, multi-provider LLM runtime for building agent systems in any domain. It runs as a single static Go binary with no runtime dependencies. Extensions speak JSON-RPC over stdin/stdout in any language. Your job is the interface, the workflow, and the domain. The engine handles the rest.
The Age of Harness Engineering
The AI agent ecosystem is splitting in two. On one side, opinionated apps that decide how you work. On the other, raw APIs that leave you building the agent loop, the tool execution, the sandboxing, and the conversation management from scratch.
Ion Engine sits in between. It handles the hard parts: the agent loop, parallel tool execution, conversation persistence with branching, and multi-provider abstraction. It ships opt-in security primitives (dangerous command patterns, sensitive path protection, secret redaction, OS-level sandboxing) that you enable when you need them. But it has zero opinions about your interface, your workflow, your permission model, or your deployment target.
You get a raw agent and shape what it becomes.
Where Ion Fits
Tools like Claude Code and Pi are excellent at what they do. If you want a polished coding assistant or a batteries-included agent toolkit, use them. They're built for that, they're well-supported, and they have strong communities.
Ion is different in kind, not in quality. It's a foundation: a headless runtime you build on top of, not a product you use as-is. Think of it as the engine block, not the car.
What Ion gives you:
- A domain-agnostic runtime. No coding-tool assumptions. Build agent systems for warehouse ops, research pipelines, farm management, incident response, or your own coding assistant.
- Any-language extensions. Extensions communicate via JSON-RPC over stdin/stdout. Write them in Go, Python, Rust, TypeScript, or a shell script. If your language can read stdin and write stdout, it's an extension language.
- Daemon architecture with multi-client broadcast. Multiple clients connect to the same engine simultaneously. Build a CLI, a desktop app, and a web interface that all share one running daemon.
- Native parallel sub-agents. Spawn child agents with different models that run their own tool loops concurrently. A parent on Opus coordinates researchers on Sonnet and formatters on Haiku.
- Built-in security primitives. OS-level sandboxing (Seatbelt on macOS, bwrap on Linux), secret redaction, dangerous command blocking, and a permission engine with LLM-based classification.
- Enterprise policy enforcement. 4-layer config merge (defaults, user, project, enterprise) where the enterprise layer is sealed. Security sets the floor; developers customize above it.
- Zero vendor SDK dependencies. LLM providers via raw HTTP with manual SSE parsing. No transitive dependency chain you don't control. Fork the binary, keep your agents.
- MCP support. Dual-transport MCP client (stdio for local servers, SSE for remote) with resource reading exposed as first-class tools.
- Credential management. Five-level resolver: programmatic override, environment variables, OS keychain, encrypted file store, and CLI proxy for TOS-compliant subscription access via Claude Code.
- Extension hooks across every stage of the agent loop. Intercept and modify behavior wherever you need to.
You want to build your own coding agent? Use this engine and wrap your own opinions around it. You want non-coding agent orchestrations? Build harnesses that coordinate domain-specific agents. You want to embed an agent runtime in a container sidecar, a CI pipeline, or a web API? It's a single static Go binary with no runtime dependencies.
Ion is a starting point. The runtime handles the hard parts and stays out of the way on everything else.
Quick Start
# Install (macOS)
curl -fsSL https://github.com/dsswift/ion/releases/latest/download/ion-darwin-arm64 \
-o /usr/local/bin/ion && chmod +x /usr/local/bin/ion
Ion ships with no default model. That is a deliberate choice. You bring the opinions, including which model runs your agents. Drop a config at ~/.ion/engine.json before your first prompt:
{
"defaultModel": "qwen2.5:14b",
"providers": {
"ollama": {}
}
}
The example above runs qwen2.5:14b locally on Ollama with no API key required. Other supported models work the same way: gpt-4o for OpenAI, claude-sonnet-4-6 for Anthropic, mistral-large-latest for Mistral. Set defaultModel to whichever model you want and configure its provider block. See the [Model and Provider Configuration](#model-and-provider-configuration) section below or the [Provider Setup](docs/providers/index.md) docs for the full catalog.
For hosted providers, export the matching credential before running:
export OPENAI_API_KEY="sk-..."
# or ANTHROPIC_API_KEY, GOOGLE_API_KEY, MISTRAL_API_KEY, etc.
Now run your first prompt:
ion prompt "What files are in the current directory?"
One command. The engine starts in-process, calls the LLM, executes tools, streams the result to stdout, and exits. No daemon, no socket, no background process.
One-shot mode works well for scripted workflows: shell scripts, cron jobs, git hooks, CI pipelines, orchestration scripts. Each invocation is self-contained.
# One-shot with JSON output
ion prompt --output json "Summarize the last 5 commits" | jq -r '.result'
# One-shot with streaming NDJSON (for piping to other tools)
ion prompt --output stream-json "Review this diff for security issues"
# Skip configured extensions for this run
ion prompt --no-extensions "What time is it in UTC?"
# Clear configured extensions, load only this one
ion prompt --no-extensions --extension ./my-reviewer/index.js "Review the staged changes"
Daemon mode
When you need persistent sessions, multiple clients, or real-time event streaming, run the engine as a daemon.
# Start the daemon
ion serve
# Ion Engine v1.0.0 started (pid 42871)
# Socket: /Users/you/.ion/engine.sock
# Start a session with a working directory and extensions
ion start --key myproject --dir /path/to/project \
--extension ~/.ion/extensions/my-harness.js
# Send a prompt (routed to the session by key)
ion prompt --key myproject "What files are in the current directory?"
# Prompt sent. Use `ion attach` to stream output.
# Stream events (NDJSON lines flow as the agent works)
ion attach
# {"type":"text_chunk","text":"I can see the following files..."}
# {"type":"tool_call","toolName":"Bash","toolId":"tool_1","index":0}
# {"type":"tool_result","toolId":"tool_1","output":"README.md\nsrc/\ntests/\n"}
# {"type":"text_chunk","text":"The directory contains a README..."}
# {"type":"task_complete","result":"...","costUsd":0.003,"numTurns":1}
# Send a follow-up (the session remembers context)
ion prompt --key myproject "Which of those files changed in the last week?"
# Send another (still the same conversation)
ion prompt --key myproject "Show me the diff for the most recent change"
# Start a second session with different extensions
ion start --key infra --dir /path/to/infra-repo \
--extension ~/.ion/extensions/terraform-tools.js
# Both sessions run in parallel, each with their own extensions
ion prompt --key infra "Plan the changes in modules/networking"
# Check what sessions are running
ion status
# KEY DIRECTORY STATE
# -------------------------------------------------------
# myproject /path/to/project active
# infra /path/to/infra-repo active
# Filter attach to one session
ion attach --key myproject
# Stop a session when done
ion stop --key myproject
# Shut down the daemon
ion shutdown
Why daemon mode:
- Persistent sessions. Conversation history survives across prompts. Ask a follow-up without re-sending context. Branch a conversation to explore alternatives. The engine manages session state, compaction, and JSONL persistence automatically.
- Multiple clients. Any number of clients connect to the same daemon simultaneously. Every client receives broadcast events. One engine serves all your interfaces.
- Warm extensions. Extension subprocesses stay alive between prompts. No spawn/init overhead on each invocation. Custom tools, hooks, and agents are ready instantly.
- Real-time streaming. Connect with
ion attachand watch events flow as the agent works. Pipe NDJSON intojq, a monitoring dashboard, or an approval workflow. Build integrations that react to tool calls, text output, and errors in real time. - Session management. Run multiple sessions in parallel, each with its own model, extensions, and working directory. Route prompts to the right session by key. One daemon, many workstreams.
One-shot mode runs a fresh engine per invocation. Daemon mode runs one engine that serves everything. Use one-shot for scripts and automation. Use daemon mode for applications and interactive workflows. Both load the same config, run the extensions you specify (passed with --extension or listed in ~/.ion/settings.json profiles), and run the same agent loop.
See the [engine docs](engine/README.md) for Linux, Windows, and Docker install instructions.
Documentation
Full technical documentation lives in [docs/](docs/index.md). Start here based on what you're building:
| Audience | Start with | |----------|-----------| | Harness engineer building extensions | [Quick Start](docs/getting-started/quickstart.md), [Extension Guide](docs/extensions/getting-started.md), [Hooks Reference](docs/hooks/reference.md) | | IT admin deploying Ion | [Configuration](docs/configuration/index.md), [Security](docs/security/index.md), [Enterprise](docs/enterprise/index.md) | | Contributor working on the engine | [Architecture](docs/architecture/engine.md), [Contributing](docs/contributing/index.md) |
Key references: [Socket Protocol](docs/protocol/index.md) | [CLI Reference](docs/cli/reference.md) | [Tools Reference](docs/tools/reference.md) | [Provider Setup](docs/providers/index.md) | [MCP Integration](docs/mcp/index.md)
Model and Provider Configuration
Ion supports many providers and lets you plug in your own through a common interface. The engine picks a provider in this order: a registered model in ~/.ion/models.json, then a built-in name-prefix match (claude-*, gpt-*, qwen*, llama*, gemini-*, mistral*, grok*, deepseek-*, and so on), then a hard error if neither matches.
Set the model you want in ~/.ion/engine.json and configure its provider block:
{
"defaultModel": "qwen2.5:14b",
"providers": {
"ollama": {},
"openai": { "apiKey": "CUSTOM_OPEN_API_KEY_VAR" },
"anthropic": { "apiKey": "sk-ant-..." }
}
}
> How apiKey is resolved. The apiKey field accepts three forms: > > 1. Omitted: the engine auto-resolves the key from provider's default environment variables (e.g. OPENAI_API_KEY), the system keychain, or its encrypted file store. > 2. An environment variable name (all-caps, digits, and underscores, e.g. "OPENAI_API_KEY"): expanded from the environment at startup. > 3. A literal key (e.g. "sk-proj-..."): used directly as-is. > > The examples above use both form 1 and form 2. The value "CUSTOM_OPEN_API_KEY_VAR" is not a literal API key; it tells the engine to read the corresponding environment variable.
Need a custom name (a finetune, an OpenRouter route, a tier alias)? Register it under ~/.ion/models.json:
{
"tiers": {
"fast": "qwen2.5:7b",
"smart": "claude-sonnet-4-6"
},
"providers": {
"ollama": {
"models": {
"myteam/qwen-finetune:latest": { "contextWindow": 32768 }
}
}
}
}
Need to mix cloud and local models in one workflow? See the [Workflows: Model Routing](#workflows-model-routing) example below.
For the full picture, see the [models.json reference](docs/configuration/models.md), the [engine.json reference](docs/configuration/engine-json.md), and the [provider catalog](docs/providers/index.md).
One Engine, Many Shapes
Ion Engine is a raw agent. On its own, it takes a prompt, talks to an LLM, executes tools, and streams results back. That's it. No opinions. No workflow. No interface. No system prompt. The engine ships with no guiding instructions. Every behavior is something you bring.
What matters is what you build around it.
A shell script
The simplest harness is a few lines of bash. Send a prompt, get a result. Embed an AI agent in a cron job, a git hook, or a CI pipeline with nothing more than a shell script.
#!/bin/bash
ion prompt --output json "Review the diff and flag any security concerns" \
| jq -r '.result' \
>> review-output.md
A full application
Any socket client can be a full application. Your UI connects to the engine's Unix socket, sends JSON commands, and renders streamed events. The engine runs every session. Your app is purely an interface.
Your App ──[Unix socket]──> Ion Engine
The wiring is a thin socket client. Connect, write JSON lines, parse events:
// Example socket client (simplified)
import { createConnection } from 'net'
import { join } from 'path'
import { homedir } from 'os'
const SOCKET = join(homedir(), '.ion', 'engine.sock')
const conn = createConnection(SOCKET)
// Send commands as NDJSON
conn.write(JSON.stringify({ cmd: 'start_session', key: 's1', config: { model: 'qwen2.5:14b' } }) + '\n')
conn.write(JSON.stringify({ cmd: 'prompt', key: 's1', text: 'What files are here?' }) + '\n')
// Receive events as NDJSON
let buffer = ''
conn.on('data', (chunk) => {
buffer += chunk.toString()
let nl
while ((nl = buffer.indexOf('\n')) !== -1) {
const event = JSON.parse(buffer.slice(0, nl))
buffer = buffer.slice(nl + 1)
// Render event.type: 'text', 'tool_use', 'tool_result', 'exit', ...
}
})
The client never calls an LLM or executes a tool. It sends commands to the engine, and the engine handles the rest.
Notice the key field in every command. That's session routing. Every session gets a unique key, and any client can send prompts or filter events by key. A tabbed desktop app uses keys for tabs. A container singleton launched by a KEDA scaler uses keys to multiplex queue events. A CI pipeline uses keys to run parallel review sessions. One daemon, many independent sessions, any number of clients.
A workflow orchestration
An extension can register multiple agents, each with its own model, tools, and system prompt. A single prompt to a well-built harness can trigger an entire workflow: the LLM delegates to specialized agents, they run their tool loops in parallel, and the results flow back to the parent session.
#!/bin/bash
# Full QA pipeline in two harness invocations
# Step 1: QA harness -- one extension with review, test, and report agents
# The LLM orchestrates internally: reviews the diff, generates tests,
# runs them, and produces a structured report. One prompt, multiple agents.
report=$(ion prompt --output json --extension ./extensions/qa-harness.js \
"Review the staged diff for security and style issues. Generate tests \
for anything flagged. Run the tests. Produce a structured QA report \
with pass/fail status, issue severity, and coverage metrics.")
# Step 2: Publication harness -- different extension, different agents
# This harness has agents for formatting, email drafting, and Slack posting.
# It picks up where the QA harness left off.
echo "$report" | jq -r '.result' | \
ion prompt --extension ./extensions/publish-harness.js \
"Format this QA report for stakeholders. Post a summary to #engineering \
in Slack. Email the full report to the team leads."
The QA harne
…
Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: dsswift
- Source: dsswift/ion
- License: MIT
- Homepage: https://github.com/dsswift/ion
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.