Ivorn
A web-based chat interface for ACP-compatible AI coding agents (like Kiro CLI, Goose, etc.) that enables browser-based interaction with multiple parallel project sessions.
Named after Ivor the Engine, the beloved Welsh stop-motion steam train.
Tech Stack
| Component | Technology | Purpose |
|---|---|---|
| Backend | Rust + Axum | HTTP server, process management, async runtime |
| Frontend | Alpine.js | Lightweight reactive UI (~15KB, no build step) |
| Communication | SSE + POST | Server-to-browser streaming, client requests |
| Protocol | ACP | JSON-RPC 2.0 over stdio for agent communication |
| Build | cargo only | Single binary, no separate frontend build |
Architecture
flowchart LR
Browser["Browser<br/>(Alpine.js)"]
Axum["Axum Server"]
ACP["ACP Module"]
Agent["ACP Agent<br/>(e.g., Kiro CLI)"]
Browser -->|"POST /api/..."| Axum
Axum -->|"SSE stream"| Browser
Axum --- ACP
ACP -->|"stdin (JSON-RPC)"| Agent
Agent -->|"stdout (JSON-RPC)"| ACP
The browser connects to the Axum server via HTTP. User messages are sent as POST requests. Agent responses stream back via Server-Sent Events (SSE). The ACP module (src/acp/) handles JSON-RPC 2.0 communication with agents over stdin/stdout.
Quick Start
Prerequisites
- Rust (stable toolchain)
Build and Run
# Build
# Run (provide path to projects directory)
# Or use environment variable
IVORN_PROJECTS_DIR=/path/to/projects
Open http://localhost:8181 in your browser.
Configuration
Configuration is loaded from ~/.config/ivorn/config.toml (XDG spec). See config.example.toml for all available settings.
[]
= 8181
= "0.0.0.0"
[]
= "/path/to/projects"
[]
= true # Enable conversation logging to markdown files
[]
= 25 # Maximum upload file size in MB
Environment Variables
All settings support environment variable overrides with IVORN_ prefix:
| Variable | Description | Default |
|---|---|---|
IVORN_SERVER_PORT |
Server port | 8181 |
IVORN_SERVER_BIND_ADDRESS |
Bind address | 0.0.0.0 |
IVORN_PROJECTS_DIR |
Projects directory | Current directory |
IVORN_LOGGING_CONVERSATION_LOG |
Enable conversation logging | true |
IVORN_UPLOAD_MAX_SIZE_MB |
Maximum upload file size in MB | 25 |
Precedence (highest to lowest): CLI arguments > Environment variables > Config file > Defaults
ACP agents are external executables that implement the Agent Client Protocol over stdio. Ivorn discovers agents from configuration files.
Configuration Locations
- Global:
~/.config/ivorn/acp/*.jsonor*.toml - Project-local:
{project}/.ivorn/acp/*.jsonor*.toml(takes precedence)
When both JSON and TOML files define the same agent name, TOML takes precedence.
TOML format (kiro-cli.toml):
= "kiro-cli"
= "Kiro CLI"
= "/usr/local/bin/kiro-cli"
= ["acp"]
= "Default Kiro CLI agent"
[[]]
= "KEY"
= "val"
JSON format (kiro-cli.json):
| Field | Required | Description |
|---|---|---|
name |
Yes | Unique identifier for the agent |
title |
No | Display name (falls back to name) |
command |
Yes | Path to the executable |
args |
No | Command-line arguments |
description |
No | Description shown in UI |
env |
No | Environment variables to set |
Projects can provide custom context injected into every message sent to the ACP agent.
Context is loaded from three sources, merged in order (later overrides earlier):
- Global static —
~/.config/ivorn/context.tomlorcontext.json - Project static —
.ivorn/context.tomlor.ivorn/context.json - Dynamic hook —
.ivorn/context-hookscript (highest priority)
Static Context (.ivorn/context.toml):
= "platform"
= "development"
Dynamic Hook (.ivorn/context-hook):
#!/bin/sh
All values are prefixed with project_hook_ in the ivorn-context block.
Projects can define custom slash commands that execute shell commands or send prompts to the LLM.
Configuration Locations
- Global:
~/.config/ivorn/commands/*.jsonor*.toml - Project-local:
{project}/.ivorn/commands/*.jsonor*.toml(takes precedence)
Example (.ivorn/commands/dev.toml):
[[]]
= "test"
= "Run tests"
= "cargo test {args}"
= "display"
[[]]
= "clippy"
= "Run clippy and fix issues"
= "cargo clippy {args} 2>&1"
= "prompt"
= "Fix these clippy warnings:\n\n{}"
= { = "clippy args (e.g., --fix)" }
| Field | Required | Description |
|---|---|---|
name |
Yes | Command name (without leading /) |
description |
Yes | Description shown in command palette |
command |
No | Shell command to execute ({args} for user arguments) |
output |
No | display (default), file, or prompt |
prompt |
No | Prompt template ({} for output content) |
input |
No | Input hint object with hint field |
Project Structure
ivorn/
├── src/
│ ├── main.rs # Entry point, CLI parsing
│ ├── config.rs # User configuration loading
│ ├── state.rs # Application state management
│ ├── acp_agents.rs # ACP agent discovery
│ ├── acp/ # ACP protocol implementation
│ │ ├── jsonrpc.rs # JSON-RPC types and builders
│ │ └── subprocess/ # Agent subprocess management
│ │ ├── lifecycle.rs # Spawn, kill, stdin writer
│ │ ├── protocol.rs # Initialize, session methods
│ │ ├── reader.rs # Background stdout reader
│ │ └── ... # Notifications, permissions, etc.
│ ├── api/ # HTTP API endpoints
│ │ ├── session/ # Session management
│ │ ├── projects.rs # Project listing
│ │ ├── upload.rs # File upload handling
│ │ └── ...
│ ├── history.rs # Chat history (fjall DB)
│ ├── events.rs # SSE event types
│ └── ...
├── static/ # HTML pages (session.html, projects.html)
├── tests/ # JavaScript tests (Deno)
└── Cargo.toml
Features
- Session naming with click-to-edit in session header
- Light/dark theme toggle with system theme follow
- Running sessions visible on project list with status indicators
- Session termination control (explicit terminate vs. leave running)
- File upload (drag-and-drop, clipboard paste, or button)
- Tool call cards with purpose, parameters, output, and diff display
- Tool call status-based card tinting (blue=in_progress, green=completed, red=failed)
- Tool call selection for follow-up messages (pin button)
- Interactive permission request UI
- Config options UI (model/agent dropdowns with search, keyboard shortcuts)
- Unified prompt menu (type
@or click button) - Command palette (type
/to discover slash commands) - Shell command execution (
!-prefixed messages) - Real-time streaming via Server-Sent Events
- Mobile-friendly responsive design
- Markdown rendering with syntax highlighting
- Clickable items (list items and headings submit as prompts)
- Server-side chat history storage for multi-device sync
- Version mismatch detection with reload banner
- Server-side conversation logging to markdown files
- Ivorn context injection (project metadata in every message)
- Project context hooks (static files and dynamic scripts)
Data Storage
All application data is stored in the OS data directory:
| Platform | Location |
|---|---|
| Linux | ~/.local/share/ivorn/ |
| macOS | ~/Library/Application Support/ivorn/ |
| Windows | C:\Users\<user>\AppData\Roaming\ivorn\ |
~/.local/share/ivorn/
├── state/ # Global state files
│ ├── sessions.json # Active session metadata
│ ├── terminated-sessions.json # Terminated session records
│ └── project-usage.json # Project usage timestamps
└── projects/{project}/ # Per-project data
├── conversations/yyyy/mm/dd/ # Conversation logs
└── uploads/ # Uploaded files
Development
# Format code
# Run CI checks
# Run locally
# Run JavaScript tests (requires Deno)
License
Copyright (C) 2026 Paul Campbell