ivorn 0.1.3

A web-based chat interface for ACP-compatible AI coding agents
Documentation

Ivorn

A web-based chat interface for Kiro CLI that enables browser-based interaction with multiple parallel project sessions.

Quick Start

# Build
cargo build --release

# Run (provide path to projects directory)
./target/release/ivorn /path/to/projects

# Or use environment variable
IVORN_PROJECTS_DIR=/path/to/projects ./target/release/ivorn

Open http://localhost:8181 in your browser.

Configuration

Config File

Configuration is loaded from ~/.config/ivorn/config.toml (XDG spec).

See config.example.toml for all available settings.

[server]
port = 8181
bind_address = "0.0.0.0"

[paths]
projects_dir = "/path/to/projects"

[logging]
conversation_log = true  # Enable conversation logging to markdown files

[upload]
max_size_mb = 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

Configuration values are resolved in this order (highest to lowest):

  1. CLI arguments
  2. Environment variables
  3. Config file
  4. Defaults

ACP Agents

ACP (Agent Client Protocol) agents are external executables that implement the ACP protocol over stdio. Ivorn discovers agents from JSON configuration files.

Configuration Locations

  • Global: ~/.config/ivorn/acp/*.json
  • Project-local: {project}/.ivorn/acp/*.json (takes precedence over global)

Config File Format

{
  "name": "kiro-cli",
  "title": "Kiro CLI",
  "command": "/usr/local/bin/kiro-cli",
  "args": ["acp"],
  "description": "Default Kiro CLI agent",
  "env": [{"name": "KEY", "value": "val"}]
}
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

Example: Kiro CLI

Create ~/.config/ivorn/acp/kiro-cli.json:

{
  "name": "kiro-cli",
  "title": "Kiro CLI",
  "command": "kiro-cli",
  "args": ["acp"],
  "description": "Kiro CLI ACP agent"
}

API Endpoint

GET /api/acp-agents returns the list of discovered agents (global only, project-local requires session context).

Projects Directory Structure

The projects directory should contain project subdirectories directly:

/path/to/projects/
├── project-a/
├── project-b/
└── project-c/

Context Hooks

Projects can provide custom context that is injected into every message sent to the ACP agent. This enables project-specific metadata like CI status, deployment environment, or feature flags.

Setup

Create an executable script at one of these locations (first found wins):

  • .ivorn/context-hook (executable)
  • .ivorn/context-hook.sh

The script runs with the project directory as its working directory.

Output Format

The script should output key=value lines to stdout:

#!/bin/sh
echo "ci_status=passing"
echo "environment=development"
echo "feature_flags=dark_mode,beta_api"

Lines are parsed and prefixed with project_hook_ in the ivorn-context block:

project_hook_ci_status=passing
project_hook_environment=development
project_hook_feature_flags=dark_mode,beta_api

Parsing Rules

  • Each line should be key=value format
  • Lines starting with # are ignored (comments)
  • Blank lines are ignored
  • Lines without = are ignored
  • Whitespace around keys and values is trimmed

Timeout and Errors

  • Scripts have a 5-second timeout
  • On timeout: project_hook_error=timeout is injected
  • On non-zero exit: project_hook_error=exit(N) is injected (where N is the exit code)
  • On IO error: project_hook_error=io_error is injected

Example: Git Branch Context

#!/bin/sh
# .ivorn/context-hook

branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
echo "git_branch=$branch"

# Check if there are uncommitted changes
if git diff --quiet 2>/dev/null; then
    echo "git_dirty=false"
else
    echo "git_dirty=true"
fi

Features

  • Multi-tab interface for parallel project sessions
  • Standalone session pages: /projects for ACP-only project list, /session/{id} for single-session view (opens in new browser tab)
  • Running sessions visible on project list: each project shows its active sessions with status indicators and clickable links to open in new tab
  • Rich activity indicators on session tabs: 5 distinct states (idle, streaming, permission_pending, turn_complete, timeout) with visual icons and colors; timeout triggers after 30s of no SSE events (pauses during tool execution); standalone pages update browser title with state prefix (⟳, ⚠, ✓, ⏳)
  • Terminated session auto-resume: "Show history" toggle on projects page reveals terminated sessions; clicking resurrects the session by spawning a new ACP subprocess and calling session/load to replay kiro-cli's history
  • Real-time project page updates: project list subscribes to global SSE stream (/api/stream) for instant session lifecycle events (created, closed, status changes) without page refresh
  • Session termination control: ⏻ button explicitly terminates session (kills subprocess); closing browser tab leaves session alive for later reconnection; beforeunload prompt reminds user session is still running
  • Per-tab input state (draft messages preserved when switching tabs)
  • Project list sorted by most recently used, then alphabetically
  • ACP (Agent Client Protocol) sessions using JSON-RPC 2.0 for structured agent communication
  • File upload for ACP sessions: drag-and-drop, clipboard paste, or button; stored in OS data directory with text reference Attached file: ./uploads/{filename} (mime/type, size)
  • All file types supported; images (detected via magic bytes) sent as base64 Image content blocks + text reference; non-images sent as text reference only
  • File attachment display: images render as clickable thumbnails with modal; non-images render as card-style badges with MIME-based icons (📄 PDF, 📦 archives, 📝 text, 📋 JSON, 📎 other)
  • MIME type detection using infer crate (magic bytes) with extension fallback for text files (txt, md, json, xml, csv, log)
  • Session-type-aware SSE handlers with shared event infrastructure
  • ACP spec-aligned tool call visibility (kind, status, title, raw I/O)
  • Tool call cards with purpose display, collapsible Parameters and Output sections, and status badges
  • Tool call status-based card tinting: subtle background and border color changes for at-a-glance state visibility (blue=in_progress, green=completed, red=failed, amber=rejected)
  • Tool call Parameters and Output sections: both shown when expanded, each independently collapsible with state persisted across page reloads
  • Tool call output formatting: structured key-value tree rendering (matching Parameters style); MCP response wrappers (items[*].Json and items[*].Text variants) automatically unwrapped; direct JSON objects rendered as tree without unwrapping
  • Tool call diff display: file changes shown in separate collapsible "Diff" section (outside Output section) with colored inline diff (red/green); edit tools with diff hide the Output section when rawOutput is empty
  • Tool call kind-based default expansion: impactful tools (edit, delete, move, execute) expanded by default; read-only tools collapsed
  • Tool call expansion state persisted across SSE updates and page reloads via toolCallsById
  • Tool call content display: text (markdown), diff, terminal, and unknown content types rendered in expandable cards
  • Tool call error display: failed tool calls show error details in collapsible section with red styling; auto-expands when tool fails
  • Interspersed tool cards: tool calls appear inline at their first occurrence position, not grouped at bottom
  • Turn completion dividers with elapsed time and timestamps
  • Unified inline activity indicator: single indicator appears after the last user message; shows "Waiting for response..." with spinner + timer + cancel during waiting state, transitions to bouncing dots during typing state; timer and cancel button visible in both states
  • Bidirectional agent communication (incoming requests, interactive permission UI)
  • Interactive permission request UI: inline menu above input area, "View" link to highlight tool card, user approves/rejects tool calls
  • Agent info and capability detection (name, version, image prompts, session restore, MCP transports)
  • ACP authentication support (auth method parsing and automatic authenticate flow)
  • ACP agent selection: projects page shows collapsible sections with agent cards; click agent to start session; auto-expand projects with active sessions; localStorage persistence for expand/collapse state
  • Session mode and config option parsing from session/new response
  • Session config option setting via ACP session/set_config_option (generic key-value)
  • Config change system messages: full-width banner in chat history when config options change (e.g., agent, model), showing old→new values; distinguishes user-initiated vs agent-initiated changes
  • Config snapshot in message footer: assistant messages display current config values (agent, model) left of timestamp for context
  • Config options UI for ACP sessions (model/agent dropdowns with search, keyboard shortcuts: Ctrl+M model, Ctrl+A agent)
  • Unified prompt menu: type @ at start of message OR click the @ button to search and select prompts (arrow keys, Enter/Tab to select, Esc to dismiss), sorted alphabetically by name; ACP sessions receive prompts via SSE with full content for local/global prompts, auto-refreshed on agent change
  • Session load/resume via fjall history database (single source of truth for chat history)
  • Session resume auto-message: on page reload, automatically sends a message to the agent with time since last activity, asking it to summarize where things left off and suggest next steps
  • Config options restored on page reload via /messages/latest API (ACP sessions only)
  • Real-time streaming via Server-Sent Events (SSE) including stop reason events
  • Agent execution plan display (ACP plan events)
  • Graceful handling of unknown/future ACP session update types
  • Mobile-friendly responsive design with consolidated gear menu (⚙️) for config options, history, prompts, and file upload
  • Text selection popup: desktop centers on mouse position, mobile centers on viewport with smart vertical placement
  • Markdown rendering with syntax highlighting for both user and agent messages; diff code blocks display with red/green line coloring
  • Clickable lines in ACP sessions: list items and headings submit as prompts on click (cursor pointer, green hover border)
  • Session persistence across page reloads
  • Session isolation: every message stamped with _sid (session ID), validated on load/tab-switch/periodic, misplaced messages auto-corrected
  • Server-side chat history storage (fjall embedded DB) for multi-device sync and server restart resilience
  • Build info system: compile-time capture of version, git hash, jj change id, build timestamp via build.rs
  • About modal: hamburger menu → "About" displays version, git hash, jj change id, build time
  • Version mismatch detection: polls /api/health every 5 min + checks before each message send; non-blocking gold banner with reload button when server version changes
  • Graceful model unavailability handling (ACP): inline error banner, pulsing model selector highlight, automatic retry on model change
  • Session error surfacing (ACP): fatal errors (subprocess death) show red banner with "New Session" button; recoverable errors (JSON-RPC failures) show yellow banner with "Retry" button
  • Scroll-to-bottom button: circular floating button (bottom-right) appears when scrolled up, with unread message count badge
  • Loading progress indicator: thin indeterminate progress bar at top of input area during history sync, terminated session load, and SSE connection setup
  • Prompt navigation buttons: ↑/↓ buttons on user messages navigate between user messages; ↑/↓ buttons on write tool calls (edit/delete/move) navigate between write tools
  • Keyboard shortcuts for navigation: J (next user message) and K (previous user message), j (next write tool) and k (previous write tool) when input not focused
  • Frontend performance optimizations for long conversations: CSS content-visibility: auto for virtual scrolling (95% DOM reduction at 1000 messages), rendered HTML caching on message objects, debounced scroll and copy button operations via requestAnimationFrame
  • Server-side conversation logging (ACP only): real-time markdown logs saved to OS data directory for conversation recovery after server issues
  • Shell command execution (ACP only): !-prefixed messages execute as shell commands in the project directory; output streamed with ANSI color rendering (standard colors, bright colors, bold, 256-color palette) in dark terminal-style container with copy button; stop button kills process group
  • Ivorn context injection (ACP only): every user message is prepended with <ivorn-context> XML block containing project name, working directory, session ID, turn count, last activity timestamp, OS/hostname/timezone, VCS type/root/branch, project type, conversation log paths, uploads directory, recent uploads, project file count, previous sessions, and server version
  • Project context hooks (ACP only): projects can provide .ivorn/context-hook or .ivorn/context-hook.sh scripts that inject custom metadata into the ivorn-context block; script stdout is parsed as key=value lines and prefixed with project_hook_; 5-second timeout with error field injection on failure

Data Storage

All application data is stored in the OS data directory, keeping project directories clean:

Platform Location
Linux ~/.local/share/ivorn/
macOS ~/Library/Application Support/ivorn/
Windows C:\Users\<user>\AppData\Roaming\ivorn\

Directory Layout

~/.local/share/ivorn/
├── state/                              # Global state files
│   ├── sessions.json                   # Active session metadata
│   ├── terminated-sessions.json        # Terminated session records
│   ├── project-usage.json              # Project usage timestamps
│   └── .ivorn-sessions.lock            # File lock for atomic updates
└── projects/{project}/                 # Per-project data
    ├── conversations/yyyy/mm/dd/       # Conversation logs
    │   ├── {session_id}.md
    │   └── {session_id}/               # Tool output attachments
    └── uploads/                        # Uploaded files

Conversation Log Files

ACP sessions automatically log conversations to markdown files for recovery after server issues.

Logs include:

  • User messages (as blockquotes)
  • Assistant messages (plain text)
  • Tool calls with purpose and parameters
  • Tool status updates (✅ completed, ❌ failed, ⚠️ rejected)
  • Tool results: short results (<21 lines) inlined, long results saved to {session_id}/{tool_call_id}.txt
  • Turn separators

Logs are created lazily on first user message and can be used to resume conversations in new sessions by referencing the file.

Privacy Note: Conversation logs contain full message content. Set conversation_log = false in the [logging] config section to disable.

Session State Files

  • sessions.json — Active session IDs, projects, types, and clean termination status
  • terminated-sessions.json — Terminated session metadata for auto-resume
  • project-usage.json — Last-used timestamps for project list sorting

Migration

On first run, files are automatically migrated from old locations:

Old Location New Location
{projects_dir}/ivorn-sessions.json state/sessions.json
{projects_dir}/ivorn-terminated-sessions.json state/terminated-sessions.json
{projects_dir}/ivorn-project-usage.json state/project-usage.json
{projects_dir}/{project}/uploads/ projects/{project}/uploads/
conversations/{project}/ projects/{project}/conversations/

Migration uses rename() for efficiency. If migration fails (e.g., cross-filesystem), a warning is logged with the manual mv command to run. The application continues working from the old location until manually migrated.

Development

# Format code
just fmt

# Run CI checks
just ci

# Run locally
just run /path/to/projects

JavaScript Tests

Frontend unit tests use Deno (no Node.js required):

# Run JS tests locally
just test-js

# Or directly with Deno
deno test tests/

Requirements: Deno must be installed locally (mise install deno or see deno.land).

See design-docs/initial-design.md for architecture details.

License

Copyright (C) 2026 Paul Campbell

This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License along with this program. If not, see https://www.gnu.org/licenses/.